Compare commits

...

6 Commits

Author SHA1 Message Date
Vikhyath Mondreti
33aa581351 address comments 2026-04-03 15:42:45 -07:00
Vikhyath Mondreti
bc41ea02b9 improvement(execution): multiple response blocks 2026-04-03 14:44:31 -07:00
Waleed
b0c0ee29a8 feat(email): send onboarding followup email 3 days after signup (#3906)
* feat(email): send onboarding followup email 3 days after signup

* fix(email): add trigger guard, idempotency key, and shared task ID constant

* fix(email): increase onboarding followup delay from 3 to 5 days
2026-04-02 18:08:14 -07:00
Waleed
20c05644ab fix(enterprise): smooth audit log list animation (#3905) 2026-04-02 16:03:03 -07:00
Waleed
f9d73db65c feat(rootly): expand Rootly integration from 14 to 27 tools (#3902)
* feat(rootly): expand Rootly integration from 14 to 27 tools

Add 13 new tools: delete_incident, get_alert, update_alert,
acknowledge_alert, resolve_alert, create_action_item, list_action_items,
list_users, list_on_calls, list_schedules, list_escalation_policies,
list_causes, list_playbooks. Includes tool files, types, registry,
block definition with subBlocks/conditions/params, and docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rootly): handle 204 No Content response for delete_incident

DELETE /v1/incidents/{id} returns 204 with empty body. Avoid calling
response.json() on success — return success/message instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rootly): remove non-TSDoc comments, add empty body to acknowledge_alert

Remove all inline section comments from block definition per CLAUDE.md
guidelines. Add explicit empty JSON:API body to acknowledge_alert POST
to prevent potential 400 from servers expecting a body with Content-Type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rootly): send empty body on resolve_alert, guard assignedToUserId parse

resolve_alert now sends { data: {} } instead of undefined when no
optional params are provided, matching the acknowledge_alert fix.
create_action_item now validates assignedToUserId is numeric before
parseInt to avoid silent NaN coercion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rootly): extract on-call relationships from JSON:API relationships/included

On-call user, schedule, and escalation policy are exposed as JSON:API
relationships, not flat attributes. Now extracts IDs from
item.relationships and looks up names from the included array.
Adds ?include=user,schedule,escalation_policy to the request URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rootly): remove last non-TSDoc comment from block definition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 13:40:45 -07:00
Waleed
e2e53aba76 feat(agentmail): add AgentMail integration with 21 tools (#3901)
* feat(agentmail): add AgentMail integration with 21 tools

* fix(agentmail): clear stale to field when switching to reply_message operation

* fix(agentmail): guard messageId and label remappings with operation checks

* fix(agentmail): clean up subBlock titles

* fix(agentmail): guard replyTo and thread label remappings with operation checks

* fix(agentmail): guard inboxIdParam remapping with operation check

* fix(agentmail): guard permanent, replyAll, and draftInReplyTo with operation checks
2026-04-02 13:40:23 -07:00
65 changed files with 7447 additions and 36 deletions

View File

@@ -1,6 +1,33 @@
import type { SVGProps } from 'react'
import { useId } from 'react'
export function AgentMailIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 350 363' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M318.029 88.3407C196.474 115.33 153.48 115.321 33.9244 88.3271C30.6216 87.5814 27.1432 88.9727 25.3284 91.8313L1.24109 129.774C-1.76483 134.509 0.965276 140.798 6.46483 141.898C152.613 171.13 197.678 171.182 343.903 141.835C349.304 140.751 352.064 134.641 349.247 129.907L326.719 92.0479C324.95 89.0744 321.407 87.5907 318.029 88.3407Z'
fill='currentColor'
/>
<path
d='M75.9931 246.6L149.939 311.655C151.973 313.444 151.633 316.969 149.281 318.48L119.141 337.84C117.283 339.034 114.951 338.412 113.933 336.452L70.1276 252.036C68.0779 248.086 72.7553 243.751 75.9931 246.6Z'
fill='currentColor'
/>
<path
d='M274.025 246.6L200.08 311.655C198.046 313.444 198.385 316.969 200.737 318.48L230.877 337.84C232.736 339.034 235.068 338.412 236.085 336.452L279.891 252.036C281.941 248.086 277.263 243.751 274.025 246.6Z'
fill='currentColor'
/>
<path
d='M138.75 198.472L152.436 192.983C155.238 191.918 157.77 191.918 158.574 191.918C164.115 192.126 169.564 192.232 175.009 192.235C180.454 192.232 185.904 192.126 191.444 191.918C192.248 191.918 194.78 191.918 197.583 192.983L211.269 198.472C212.645 199.025 214.082 199.382 215.544 199.448C218.585 199.587 221.733 199.464 224.63 198.811C225.706 198.568 226.728 198.103 227.704 197.545L243.046 188.784C244.81 187.777 246.726 187.138 248.697 186.9L258.276 185.5H259.242H263.556L262.713 190.965L256.679 234.22C255.957 238.31 254.25 242.328 250.443 245.834L187.376 299.258C184.555 301.648 181.107 302.942 177.562 302.942H175.009H172.457C168.911 302.942 165.464 301.648 162.643 299.258L99.5761 245.834C95.7684 242.328 94.0614 238.31 93.3393 234.22L87.3059 190.965L86.4624 185.5H90.7771H91.7429L101.322 186.9C103.293 187.138 105.208 187.777 106.972 188.784L122.314 197.545C123.291 198.103 124.313 198.568 125.389 198.811C128.286 199.464 131.434 199.587 134.474 199.448C135.936 199.382 137.373 199.025 138.75 198.472Z'
fill='currentColor'
/>
<path
d='M102.47 0.847827C205.434 44.796 156.456 42.1015 248.434 1.63153C252.885 -1.09955 258.353 1.88915 259.419 7.69219L269.235 61.1686L270.819 69.7893L263.592 71.8231L263.582 71.8259C190.588 92.3069 165.244 92.0078 86.7576 71.7428L79.1971 69.7905L80.9925 60.8681L91.8401 6.91975C92.9559 1.3706 98.105 -1.55777 102.47 0.847827Z'
fill='currentColor'
/>
</svg>
)
}
export function SearchIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg

View File

@@ -5,6 +5,7 @@
import type { ComponentType, SVGProps } from 'react'
import {
A2AIcon,
AgentMailIcon,
AhrefsIcon,
AirtableIcon,
AirweaveIcon,
@@ -189,6 +190,7 @@ type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
a2a: A2AIcon,
agentmail: AgentMailIcon,
ahrefs: AhrefsIcon,
airtable: AirtableIcon,
airweave: AirweaveIcon,

View File

@@ -20,7 +20,7 @@ The Response block formats and sends structured HTTP responses back to API calle
</div>
<Callout type="info">
Response blocks are terminal blocks - they end workflow execution and cannot connect to other blocks.
Response blocks are exit points — when a Response block executes, it ends the workflow and sends the HTTP response immediately. Multiple Response blocks can be placed on different branches (e.g. after a Router or Condition), but only the first one to execute determines the API response.
</Callout>
## Configuration Options
@@ -77,7 +77,11 @@ Condition (Error Detected) → Router → Response (400/500, Error Details)
## Outputs
Response blocks are terminal — no downstream blocks execute after them. However, the block does define outputs (`data`, `status`, `headers`) which are used to construct the HTTP response sent back to the API caller.
Response blocks are exit points — when one executes, no further blocks run. The block defines outputs (`data`, `status`, `headers`) which are used to construct the HTTP response sent back to the API caller.
<Callout type="warning">
If a Response block is placed on a parallel branch, there are no guarantees about whether other parallel blocks will run or not. Execution order across parallel branches is non-deterministic, so a parallel block may execute before or after the Response block on any given run. Avoid placing Response blocks in parallel with blocks that have important side effects.
</Callout>
## Variable References
@@ -110,10 +114,10 @@ Use the `<variable.name>` syntax to dynamically insert workflow variables into y
- **Validate variable references**: Ensure all referenced variables exist and contain the expected data types before the Response block executes
<FAQ items={[
{ question: "Can I have multiple Response blocks in a workflow?", answer: "No. The Response block is a single-instance block — only one is allowed per workflow. If you need different responses for different conditions, use a Condition or Router block upstream to determine what data reaches the single Response block." },
{ question: "Can I have multiple Response blocks in a workflow?", answer: "Yes. You can place multiple Response blocks on different branches (e.g. after a Router or Condition block). The first Response block to execute determines the API response and ends the workflow. This is useful for returning different responses based on conditions — for example, a 200 on the success branch and a 500 on the error branch." },
{ question: "What triggers require a Response block?", answer: "The Response block is designed for use with the API Trigger. When your workflow is invoked via the API, the Response block sends the structured HTTP response back to the caller. Other trigger types (like webhooks or schedules) do not require a Response block." },
{ question: "What is the difference between Builder and Editor mode?", answer: "Builder mode provides a visual interface for constructing your response structure with fields and types. Editor mode gives you a raw JSON code editor where you can write the response body directly. Builder mode is recommended for most use cases." },
{ question: "What is the default status code?", answer: "If you do not specify a status code, the Response block defaults to 200 (OK). You can set any valid HTTP status code including error codes like 400, 404, or 500." },
{ question: "Can the Response block connect to downstream blocks?", answer: "No. Response blocks are terminal — they end workflow execution and send the HTTP response. No further blocks can be connected after a Response block." },
{ question: "Can the Response block connect to downstream blocks?", answer: "No. Response blocks are exit points — they end workflow execution and send the HTTP response. No further blocks can execute after a Response block." },
]} />

View File

@@ -96,8 +96,9 @@ Understanding these core principles will help you build better workflows:
2. **Automatic Parallelization**: Independent blocks run concurrently without configuration
3. **Smart Data Flow**: Outputs flow automatically to connected blocks
4. **Error Handling**: Failed blocks stop their execution path but don't affect independent paths
5. **State Persistence**: All block outputs and execution details are preserved for debugging
6. **Cycle Protection**: Workflows that call other workflows (via Workflow blocks, MCP tools, or API blocks) are tracked with a call chain. If the chain exceeds 25 hops, execution is stopped to prevent infinite loops
5. **Response Blocks as Exit Points**: When a Response block executes, the entire workflow stops and the API response is sent immediately. Multiple Response blocks can exist on different branches — the first one to execute wins
6. **State Persistence**: All block outputs and execution details are preserved for debugging
7. **Cycle Protection**: Workflows that call other workflows (via Workflow blocks, MCP tools, or API blocks) are tracked with a call chain. If the chain exceeds 25 hops, execution is stopped to prevent infinite loops
## Next Steps

View File

@@ -0,0 +1,592 @@
---
title: AgentMail
description: Manage email inboxes, threads, and messages with AgentMail
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="agentmail"
color="#000000"
/>
{/* MANUAL-CONTENT-START:intro */}
[AgentMail](https://agentmail.to/) is an API-first email platform built for agents and automation. AgentMail lets you create email inboxes on the fly, send and receive messages, reply to threads, manage drafts, and organize conversations with labels — all through a simple REST API designed for programmatic access.
**Why AgentMail?**
- **Agent-Native Email:** Purpose-built for AI agents and automation — create inboxes, send messages, and manage threads without human-facing UI overhead.
- **Full Email Lifecycle:** Send new messages, reply to threads, forward emails, manage drafts, and schedule sends — all from a single API.
- **Thread & Conversation Management:** Organize emails into threads with full read, reply, forward, and label support for structured conversation tracking.
- **Draft Workflow:** Compose drafts, update them, schedule sends, and dispatch when ready — perfect for review-before-send workflows.
- **Label Organization:** Tag threads and messages with custom labels for filtering, routing, and downstream automation.
**Using AgentMail in Sim**
Sim's AgentMail integration connects your agentic workflows directly to AgentMail using an API key. With 20 operations spanning inboxes, threads, messages, and drafts, you can build powerful email automations without writing backend code.
**Key benefits of using AgentMail in Sim:**
- **Dynamic inbox creation:** Spin up new inboxes on the fly for each agent, workflow, or customer — perfect for multi-tenant email handling.
- **Automated email processing:** List and read incoming messages, then trigger downstream actions based on content, sender, or labels.
- **Conversational email:** Reply to threads and forward messages to keep conversations flowing naturally within your automated workflows.
- **Draft and review workflows:** Create drafts, update them with AI-generated content, and send when approved — ideal for human-in-the-loop patterns.
- **Email organization:** Apply labels to threads and messages to categorize, filter, and route emails through your automation pipeline.
Whether you're building an AI email assistant, automating customer support replies, processing incoming leads, or managing multi-agent email workflows, AgentMail in Sim gives you direct, secure access to the full AgentMail API — no middleware required. Simply configure your API key, select the operation you need, and let Sim handle the rest.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate AgentMail into your workflow. Create and manage email inboxes, send and receive messages, reply to threads, manage drafts, and organize threads with labels. Requires API Key.
## Tools
### `agentmail_create_draft`
Create a new email draft in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to create the draft in |
| `to` | string | No | Recipient email addresses \(comma-separated\) |
| `subject` | string | No | Draft subject line |
| `text` | string | No | Plain text draft body |
| `html` | string | No | HTML draft body |
| `cc` | string | No | CC recipient email addresses \(comma-separated\) |
| `bcc` | string | No | BCC recipient email addresses \(comma-separated\) |
| `inReplyTo` | string | No | ID of message being replied to |
| `sendAt` | string | No | ISO 8601 timestamp to schedule sending |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `draftId` | string | Unique identifier for the draft |
| `inboxId` | string | Inbox the draft belongs to |
| `subject` | string | Draft subject |
| `to` | array | Recipient email addresses |
| `cc` | array | CC email addresses |
| `bcc` | array | BCC email addresses |
| `text` | string | Plain text content |
| `html` | string | HTML content |
| `preview` | string | Draft preview text |
| `labels` | array | Labels assigned to the draft |
| `inReplyTo` | string | Message ID this draft replies to |
| `sendStatus` | string | Send status \(scheduled, sending, failed\) |
| `sendAt` | string | Scheduled send time |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last updated timestamp |
### `agentmail_create_inbox`
Create a new email inbox with AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `username` | string | No | Username for the inbox email address |
| `domain` | string | No | Domain for the inbox email address |
| `displayName` | string | No | Display name for the inbox |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `inboxId` | string | Unique identifier for the inbox |
| `email` | string | Email address of the inbox |
| `displayName` | string | Display name of the inbox |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last updated timestamp |
### `agentmail_delete_draft`
Delete an email draft in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the draft |
| `draftId` | string | Yes | ID of the draft to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the draft was successfully deleted |
### `agentmail_delete_inbox`
Delete an email inbox in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the inbox was successfully deleted |
### `agentmail_delete_thread`
Delete an email thread in AgentMail (moves to trash, or permanently deletes if already in trash)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the thread |
| `threadId` | string | Yes | ID of the thread to delete |
| `permanent` | boolean | No | Force permanent deletion instead of moving to trash |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the thread was successfully deleted |
### `agentmail_forward_message`
Forward an email message to new recipients in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the message |
| `messageId` | string | Yes | ID of the message to forward |
| `to` | string | Yes | Recipient email addresses \(comma-separated\) |
| `subject` | string | No | Override subject line |
| `text` | string | No | Additional plain text to prepend |
| `html` | string | No | Additional HTML to prepend |
| `cc` | string | No | CC recipient email addresses \(comma-separated\) |
| `bcc` | string | No | BCC recipient email addresses \(comma-separated\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `messageId` | string | ID of the forwarded message |
| `threadId` | string | ID of the thread |
### `agentmail_get_draft`
Get details of a specific email draft in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox the draft belongs to |
| `draftId` | string | Yes | ID of the draft to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `draftId` | string | Unique identifier for the draft |
| `inboxId` | string | Inbox the draft belongs to |
| `subject` | string | Draft subject |
| `to` | array | Recipient email addresses |
| `cc` | array | CC email addresses |
| `bcc` | array | BCC email addresses |
| `text` | string | Plain text content |
| `html` | string | HTML content |
| `preview` | string | Draft preview text |
| `labels` | array | Labels assigned to the draft |
| `inReplyTo` | string | Message ID this draft replies to |
| `sendStatus` | string | Send status \(scheduled, sending, failed\) |
| `sendAt` | string | Scheduled send time |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last updated timestamp |
### `agentmail_get_inbox`
Get details of a specific email inbox in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `inboxId` | string | Unique identifier for the inbox |
| `email` | string | Email address of the inbox |
| `displayName` | string | Display name of the inbox |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last updated timestamp |
### `agentmail_get_message`
Get details of a specific email message in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the message |
| `messageId` | string | Yes | ID of the message to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `messageId` | string | Unique identifier for the message |
| `threadId` | string | ID of the thread this message belongs to |
| `from` | string | Sender email address |
| `to` | array | Recipient email addresses |
| `cc` | array | CC email addresses |
| `bcc` | array | BCC email addresses |
| `subject` | string | Message subject |
| `text` | string | Plain text content |
| `html` | string | HTML content |
| `createdAt` | string | Creation timestamp |
### `agentmail_get_thread`
Get details of a specific email thread including messages in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the thread |
| `threadId` | string | Yes | ID of the thread to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `threadId` | string | Unique identifier for the thread |
| `subject` | string | Thread subject |
| `senders` | array | List of sender email addresses |
| `recipients` | array | List of recipient email addresses |
| `messageCount` | number | Number of messages in the thread |
| `labels` | array | Labels assigned to the thread |
| `lastMessageAt` | string | Timestamp of last message |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last updated timestamp |
| `messages` | array | Messages in the thread |
| ↳ `messageId` | string | Unique identifier for the message |
| ↳ `from` | string | Sender email address |
| ↳ `to` | array | Recipient email addresses |
| ↳ `cc` | array | CC email addresses |
| ↳ `bcc` | array | BCC email addresses |
| ↳ `subject` | string | Message subject |
| ↳ `text` | string | Plain text content |
| ↳ `html` | string | HTML content |
| ↳ `createdAt` | string | Creation timestamp |
### `agentmail_list_drafts`
List email drafts in an inbox in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to list drafts from |
| `limit` | number | No | Maximum number of drafts to return |
| `pageToken` | string | No | Pagination token for next page of results |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `drafts` | array | List of drafts |
| ↳ `draftId` | string | Unique identifier for the draft |
| ↳ `inboxId` | string | Inbox the draft belongs to |
| ↳ `subject` | string | Draft subject |
| ↳ `to` | array | Recipient email addresses |
| ↳ `cc` | array | CC email addresses |
| ↳ `bcc` | array | BCC email addresses |
| ↳ `preview` | string | Draft preview text |
| ↳ `sendStatus` | string | Send status \(scheduled, sending, failed\) |
| ↳ `sendAt` | string | Scheduled send time |
| ↳ `createdAt` | string | Creation timestamp |
| ↳ `updatedAt` | string | Last updated timestamp |
| `count` | number | Total number of drafts |
| `nextPageToken` | string | Token for retrieving the next page |
### `agentmail_list_inboxes`
List all email inboxes in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `limit` | number | No | Maximum number of inboxes to return |
| `pageToken` | string | No | Pagination token for next page of results |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `inboxes` | array | List of inboxes |
| ↳ `inboxId` | string | Unique identifier for the inbox |
| ↳ `email` | string | Email address of the inbox |
| ↳ `displayName` | string | Display name of the inbox |
| ↳ `createdAt` | string | Creation timestamp |
| ↳ `updatedAt` | string | Last updated timestamp |
| `count` | number | Total number of inboxes |
| `nextPageToken` | string | Token for retrieving the next page |
### `agentmail_list_messages`
List messages in an inbox in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to list messages from |
| `limit` | number | No | Maximum number of messages to return |
| `pageToken` | string | No | Pagination token for next page of results |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `messages` | array | List of messages in the inbox |
| ↳ `messageId` | string | Unique identifier for the message |
| ↳ `from` | string | Sender email address |
| ↳ `to` | array | Recipient email addresses |
| ↳ `subject` | string | Message subject |
| ↳ `preview` | string | Message preview text |
| ↳ `createdAt` | string | Creation timestamp |
| `count` | number | Total number of messages |
| `nextPageToken` | string | Token for retrieving the next page |
### `agentmail_list_threads`
List email threads in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to list threads from |
| `limit` | number | No | Maximum number of threads to return |
| `pageToken` | string | No | Pagination token for next page of results |
| `labels` | string | No | Comma-separated labels to filter threads by |
| `before` | string | No | Filter threads before this ISO 8601 timestamp |
| `after` | string | No | Filter threads after this ISO 8601 timestamp |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `threads` | array | List of email threads |
| ↳ `threadId` | string | Unique identifier for the thread |
| ↳ `subject` | string | Thread subject |
| ↳ `senders` | array | List of sender email addresses |
| ↳ `recipients` | array | List of recipient email addresses |
| ↳ `messageCount` | number | Number of messages in the thread |
| ↳ `lastMessageAt` | string | Timestamp of last message |
| ↳ `createdAt` | string | Creation timestamp |
| ↳ `updatedAt` | string | Last updated timestamp |
| `count` | number | Total number of threads |
| `nextPageToken` | string | Token for retrieving the next page |
### `agentmail_reply_message`
Reply to an existing email message in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to reply from |
| `messageId` | string | Yes | ID of the message to reply to |
| `text` | string | No | Plain text reply body |
| `html` | string | No | HTML reply body |
| `to` | string | No | Override recipient email addresses \(comma-separated\) |
| `cc` | string | No | CC email addresses \(comma-separated\) |
| `bcc` | string | No | BCC email addresses \(comma-separated\) |
| `replyAll` | boolean | No | Reply to all recipients of the original message |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `messageId` | string | ID of the sent reply message |
| `threadId` | string | ID of the thread |
### `agentmail_send_draft`
Send an existing email draft in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the draft |
| `draftId` | string | Yes | ID of the draft to send |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `messageId` | string | ID of the sent message |
| `threadId` | string | ID of the thread |
### `agentmail_send_message`
Send an email message from an AgentMail inbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to send from |
| `to` | string | Yes | Recipient email address \(comma-separated for multiple\) |
| `subject` | string | Yes | Email subject line |
| `text` | string | No | Plain text email body |
| `html` | string | No | HTML email body |
| `cc` | string | No | CC recipient email addresses \(comma-separated\) |
| `bcc` | string | No | BCC recipient email addresses \(comma-separated\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `threadId` | string | ID of the created thread |
| `messageId` | string | ID of the sent message |
| `subject` | string | Email subject line |
| `to` | string | Recipient email address |
### `agentmail_update_draft`
Update an existing email draft in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the draft |
| `draftId` | string | Yes | ID of the draft to update |
| `to` | string | No | Recipient email addresses \(comma-separated\) |
| `subject` | string | No | Draft subject line |
| `text` | string | No | Plain text draft body |
| `html` | string | No | HTML draft body |
| `cc` | string | No | CC recipient email addresses \(comma-separated\) |
| `bcc` | string | No | BCC recipient email addresses \(comma-separated\) |
| `sendAt` | string | No | ISO 8601 timestamp to schedule sending |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `draftId` | string | Unique identifier for the draft |
| `inboxId` | string | Inbox the draft belongs to |
| `subject` | string | Draft subject |
| `to` | array | Recipient email addresses |
| `cc` | array | CC email addresses |
| `bcc` | array | BCC email addresses |
| `text` | string | Plain text content |
| `html` | string | HTML content |
| `preview` | string | Draft preview text |
| `labels` | array | Labels assigned to the draft |
| `inReplyTo` | string | Message ID this draft replies to |
| `sendStatus` | string | Send status \(scheduled, sending, failed\) |
| `sendAt` | string | Scheduled send time |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last updated timestamp |
### `agentmail_update_inbox`
Update the display name of an email inbox in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox to update |
| `displayName` | string | Yes | New display name for the inbox |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `inboxId` | string | Unique identifier for the inbox |
| `email` | string | Email address of the inbox |
| `displayName` | string | Display name of the inbox |
| `createdAt` | string | Creation timestamp |
| `updatedAt` | string | Last updated timestamp |
### `agentmail_update_message`
Add or remove labels on an email message in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the message |
| `messageId` | string | Yes | ID of the message to update |
| `addLabels` | string | No | Comma-separated labels to add to the message |
| `removeLabels` | string | No | Comma-separated labels to remove from the message |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `messageId` | string | Unique identifier for the message |
| `labels` | array | Current labels on the message |
### `agentmail_update_thread`
Add or remove labels on an email thread in AgentMail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentMail API key |
| `inboxId` | string | Yes | ID of the inbox containing the thread |
| `threadId` | string | Yes | ID of the thread to update |
| `addLabels` | string | No | Comma-separated labels to add to the thread |
| `removeLabels` | string | No | Comma-separated labels to remove from the thread |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `threadId` | string | Unique identifier for the thread |
| `labels` | array | Current labels on the thread |

View File

@@ -2,6 +2,7 @@
"pages": [
"index",
"a2a",
"agentmail",
"ahrefs",
"airtable",
"airweave",

View File

@@ -245,6 +245,7 @@ Create a new alert in Rootly for on-call notification and routing.
| --------- | ---- | ----------- |
| `alert` | object | The created alert |
| ↳ `id` | string | Unique alert ID |
| ↳ `shortId` | string | Short alert ID |
| ↳ `summary` | string | Alert summary |
| ↳ `description` | string | Alert description |
| ↳ `source` | string | Alert source |
@@ -254,6 +255,8 @@ Create a new alert in Rootly for on-call notification and routing.
| ↳ `deduplicationKey` | string | Deduplication key |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| ↳ `startedAt` | string | Start date |
| ↳ `endedAt` | string | End date |
### `rootly_list_alerts`
@@ -278,6 +281,7 @@ List alerts from Rootly with optional filtering by status, source, and services.
| --------- | ---- | ----------- |
| `alerts` | array | List of alerts |
| ↳ `id` | string | Unique alert ID |
| ↳ `shortId` | string | Short alert ID |
| ↳ `summary` | string | Alert summary |
| ↳ `description` | string | Alert description |
| ↳ `source` | string | Alert source |
@@ -287,6 +291,8 @@ List alerts from Rootly with optional filtering by status, source, and services.
| ↳ `deduplicationKey` | string | Deduplication key |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| ↳ `startedAt` | string | Start date |
| ↳ `endedAt` | string | End date |
| `totalCount` | number | Total number of alerts returned |
### `rootly_add_incident_event`
@@ -507,4 +513,379 @@ List incident retrospectives (post-mortems) from Rootly.
| ↳ `updatedAt` | string | Last update date |
| `totalCount` | number | Total number of retrospectives returned |
### `rootly_delete_incident`
Delete an incident by ID from Rootly.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `incidentId` | string | Yes | The ID of the incident to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion succeeded |
| `message` | string | Result message |
### `rootly_get_alert`
Retrieve a single alert by ID from Rootly.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `alertId` | string | Yes | The ID of the alert to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `alert` | object | The alert details |
| ↳ `id` | string | Unique alert ID |
| ↳ `shortId` | string | Short alert ID |
| ↳ `summary` | string | Alert summary |
| ↳ `description` | string | Alert description |
| ↳ `source` | string | Alert source |
| ↳ `status` | string | Alert status |
| ↳ `externalId` | string | External ID |
| ↳ `externalUrl` | string | External URL |
| ↳ `deduplicationKey` | string | Deduplication key |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| ↳ `startedAt` | string | Start date |
| ↳ `endedAt` | string | End date |
### `rootly_update_alert`
Update an existing alert in Rootly.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `alertId` | string | Yes | The ID of the alert to update |
| `summary` | string | No | Updated alert summary |
| `description` | string | No | Updated alert description |
| `source` | string | No | Updated alert source |
| `serviceIds` | string | No | Comma-separated service IDs to attach |
| `groupIds` | string | No | Comma-separated team/group IDs to attach |
| `environmentIds` | string | No | Comma-separated environment IDs to attach |
| `externalId` | string | No | Updated external ID |
| `externalUrl` | string | No | Updated external URL |
| `deduplicationKey` | string | No | Updated deduplication key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `alert` | object | The updated alert |
| ↳ `id` | string | Unique alert ID |
| ↳ `shortId` | string | Short alert ID |
| ↳ `summary` | string | Alert summary |
| ↳ `description` | string | Alert description |
| ↳ `source` | string | Alert source |
| ↳ `status` | string | Alert status |
| ↳ `externalId` | string | External ID |
| ↳ `externalUrl` | string | External URL |
| ↳ `deduplicationKey` | string | Deduplication key |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| ↳ `startedAt` | string | Start date |
| ↳ `endedAt` | string | End date |
### `rootly_acknowledge_alert`
Acknowledge an alert in Rootly.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `alertId` | string | Yes | The ID of the alert to acknowledge |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `alert` | object | The acknowledged alert |
| ↳ `id` | string | Unique alert ID |
| ↳ `shortId` | string | Short alert ID |
| ↳ `summary` | string | Alert summary |
| ↳ `description` | string | Alert description |
| ↳ `source` | string | Alert source |
| ↳ `status` | string | Alert status |
| ↳ `externalId` | string | External ID |
| ↳ `externalUrl` | string | External URL |
| ↳ `deduplicationKey` | string | Deduplication key |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| ↳ `startedAt` | string | Start date |
| ↳ `endedAt` | string | End date |
### `rootly_resolve_alert`
Resolve an alert in Rootly.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `alertId` | string | Yes | The ID of the alert to resolve |
| `resolutionMessage` | string | No | Message describing how the alert was resolved |
| `resolveRelatedIncidents` | boolean | No | Whether to also resolve related incidents |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `alert` | object | The resolved alert |
| ↳ `id` | string | Unique alert ID |
| ↳ `shortId` | string | Short alert ID |
| ↳ `summary` | string | Alert summary |
| ↳ `description` | string | Alert description |
| ↳ `source` | string | Alert source |
| ↳ `status` | string | Alert status |
| ↳ `externalId` | string | External ID |
| ↳ `externalUrl` | string | External URL |
| ↳ `deduplicationKey` | string | Deduplication key |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| ↳ `startedAt` | string | Start date |
| ↳ `endedAt` | string | End date |
### `rootly_create_action_item`
Create a new action item for an incident in Rootly.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `incidentId` | string | Yes | The ID of the incident to add the action item to |
| `summary` | string | Yes | The title of the action item |
| `description` | string | No | A detailed description of the action item |
| `kind` | string | No | The kind of action item \(task, follow_up\) |
| `priority` | string | No | Priority level \(high, medium, low\) |
| `status` | string | No | Action item status \(open, in_progress, cancelled, done\) |
| `assignedToUserId` | string | No | The user ID to assign the action item to |
| `dueDate` | string | No | Due date for the action item |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `actionItem` | object | The created action item |
| ↳ `id` | string | Unique action item ID |
| ↳ `summary` | string | Action item title |
| ↳ `description` | string | Action item description |
| ↳ `kind` | string | Action item kind \(task, follow_up\) |
| ↳ `priority` | string | Priority level |
| ↳ `status` | string | Action item status |
| ↳ `dueDate` | string | Due date |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
### `rootly_list_action_items`
List action items for an incident in Rootly.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `incidentId` | string | Yes | The ID of the incident to list action items for |
| `pageSize` | number | No | Number of items per page \(default: 20\) |
| `pageNumber` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `actionItems` | array | List of action items |
| ↳ `id` | string | Unique action item ID |
| ↳ `summary` | string | Action item title |
| ↳ `description` | string | Action item description |
| ↳ `kind` | string | Action item kind \(task, follow_up\) |
| ↳ `priority` | string | Priority level |
| ↳ `status` | string | Action item status |
| ↳ `dueDate` | string | Due date |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| `totalCount` | number | Total number of action items returned |
### `rootly_list_users`
List users from Rootly with optional search and email filtering.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `search` | string | No | Search term to filter users |
| `email` | string | No | Filter users by email address |
| `pageSize` | number | No | Number of items per page \(default: 20\) |
| `pageNumber` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | List of users |
| ↳ `id` | string | Unique user ID |
| ↳ `email` | string | User email address |
| ↳ `firstName` | string | User first name |
| ↳ `lastName` | string | User last name |
| ↳ `fullName` | string | User full name |
| ↳ `timeZone` | string | User time zone |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| `totalCount` | number | Total number of users returned |
### `rootly_list_on_calls`
List current on-call entries from Rootly with optional filtering.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `scheduleIds` | string | No | Comma-separated schedule IDs to filter by |
| `escalationPolicyIds` | string | No | Comma-separated escalation policy IDs to filter by |
| `userIds` | string | No | Comma-separated user IDs to filter by |
| `serviceIds` | string | No | Comma-separated service IDs to filter by |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `onCalls` | array | List of on-call entries |
| ↳ `id` | string | Unique on-call entry ID |
| ↳ `userId` | string | ID of the on-call user |
| ↳ `userName` | string | Name of the on-call user |
| ↳ `scheduleId` | string | ID of the associated schedule |
| ↳ `scheduleName` | string | Name of the associated schedule |
| ↳ `escalationPolicyId` | string | ID of the associated escalation policy |
| ↳ `startTime` | string | On-call start time |
| ↳ `endTime` | string | On-call end time |
| `totalCount` | number | Total number of on-call entries returned |
### `rootly_list_schedules`
List on-call schedules from Rootly with optional search filtering.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `search` | string | No | Search term to filter schedules |
| `pageSize` | number | No | Number of items per page \(default: 20\) |
| `pageNumber` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `schedules` | array | List of schedules |
| ↳ `id` | string | Unique schedule ID |
| ↳ `name` | string | Schedule name |
| ↳ `description` | string | Schedule description |
| ↳ `allTimeCoverage` | boolean | Whether schedule provides 24/7 coverage |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| `totalCount` | number | Total number of schedules returned |
### `rootly_list_escalation_policies`
List escalation policies from Rootly with optional search filtering.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `search` | string | No | Search term to filter escalation policies |
| `pageSize` | number | No | Number of items per page \(default: 20\) |
| `pageNumber` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `escalationPolicies` | array | List of escalation policies |
| ↳ `id` | string | Unique escalation policy ID |
| ↳ `name` | string | Escalation policy name |
| ↳ `description` | string | Escalation policy description |
| ↳ `repeatCount` | number | Number of times to repeat escalation |
| ↳ `groupIds` | array | Associated group IDs |
| ↳ `serviceIds` | array | Associated service IDs |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| `totalCount` | number | Total number of escalation policies returned |
### `rootly_list_causes`
List causes from Rootly with optional search filtering.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `search` | string | No | Search term to filter causes |
| `pageSize` | number | No | Number of items per page \(default: 20\) |
| `pageNumber` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `causes` | array | List of causes |
| ↳ `id` | string | Unique cause ID |
| ↳ `name` | string | Cause name |
| ↳ `slug` | string | Cause slug |
| ↳ `description` | string | Cause description |
| ↳ `position` | number | Cause position |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| `totalCount` | number | Total number of causes returned |
### `rootly_list_playbooks`
List playbooks from Rootly with pagination support.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Rootly API key |
| `pageSize` | number | No | Number of items per page \(default: 20\) |
| `pageNumber` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `playbooks` | array | List of playbooks |
| ↳ `id` | string | Unique playbook ID |
| ↳ `title` | string | Playbook title |
| ↳ `summary` | string | Playbook summary |
| ↳ `externalUrl` | string | External URL |
| ↳ `createdAt` | string | Creation date |
| ↳ `updatedAt` | string | Last update date |
| `totalCount` | number | Total number of playbooks returned |

View File

@@ -166,14 +166,14 @@ export function AuditLogPreview() {
const counterRef = useRef(ENTRY_TEMPLATES.length)
const templateIndexRef = useRef(6 % ENTRY_TEMPLATES.length)
const now = Date.now()
const [entries, setEntries] = useState<LogEntry[]>(() =>
ENTRY_TEMPLATES.slice(0, 6).map((t, i) => ({
const [entries, setEntries] = useState<LogEntry[]>(() => {
const now = Date.now()
return ENTRY_TEMPLATES.slice(0, 6).map((t, i) => ({
...t,
id: i,
insertedAt: now - INITIAL_OFFSETS_MS[i],
}))
)
})
const [, tick] = useState(0)
useEffect(() => {
@@ -208,10 +208,9 @@ export function AuditLogPreview() {
exit={{ opacity: 0 }}
transition={{
layout: {
type: 'spring',
stiffness: 350,
damping: 50,
mass: 0.8,
type: 'tween',
duration: 0.32,
ease: [0.25, 0.46, 0.45, 0.94],
},
y: { duration: 0.32, ease: [0.25, 0.46, 0.45, 0.94] },
opacity: { duration: 0.25 },

View File

@@ -5,6 +5,7 @@
import type { ComponentType, SVGProps } from 'react'
import {
A2AIcon,
AgentMailIcon,
AhrefsIcon,
AirtableIcon,
AirweaveIcon,
@@ -189,6 +190,7 @@ type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
a2a: A2AIcon,
agentmail: AgentMailIcon,
ahrefs: AhrefsIcon,
airtable: AirtableIcon,
airweave: AirweaveIcon,

View File

@@ -105,6 +105,109 @@
"integrationType": "developer-tools",
"tags": ["agentic", "automation"]
},
{
"type": "agentmail",
"slug": "agentmail",
"name": "AgentMail",
"description": "Manage email inboxes, threads, and messages with AgentMail",
"longDescription": "Integrate AgentMail into your workflow. Create and manage email inboxes, send and receive messages, reply to threads, manage drafts, and organize threads with labels. Requires API Key.",
"bgColor": "#000000",
"iconName": "AgentMailIcon",
"docsUrl": "https://docs.sim.ai/tools/agentmail",
"operations": [
{
"name": "Send Message",
"description": "Send an email message from an AgentMail inbox"
},
{
"name": "Reply to Message",
"description": "Reply to an existing email message in AgentMail"
},
{
"name": "Forward Message",
"description": "Forward an email message to new recipients in AgentMail"
},
{
"name": "List Threads",
"description": "List email threads in AgentMail"
},
{
"name": "Get Thread",
"description": "Get details of a specific email thread including messages in AgentMail"
},
{
"name": "Update Thread Labels",
"description": "Add or remove labels on an email thread in AgentMail"
},
{
"name": "Delete Thread",
"description": "Delete an email thread in AgentMail (moves to trash, or permanently deletes if already in trash)"
},
{
"name": "List Messages",
"description": "List messages in an inbox in AgentMail"
},
{
"name": "Get Message",
"description": "Get details of a specific email message in AgentMail"
},
{
"name": "Update Message Labels",
"description": "Add or remove labels on an email message in AgentMail"
},
{
"name": "Create Draft",
"description": "Create a new email draft in AgentMail"
},
{
"name": "List Drafts",
"description": "List email drafts in an inbox in AgentMail"
},
{
"name": "Get Draft",
"description": "Get details of a specific email draft in AgentMail"
},
{
"name": "Update Draft",
"description": "Update an existing email draft in AgentMail"
},
{
"name": "Delete Draft",
"description": "Delete an email draft in AgentMail"
},
{
"name": "Send Draft",
"description": "Send an existing email draft in AgentMail"
},
{
"name": "Create Inbox",
"description": "Create a new email inbox with AgentMail"
},
{
"name": "List Inboxes",
"description": "List all email inboxes in AgentMail"
},
{
"name": "Get Inbox",
"description": "Get details of a specific email inbox in AgentMail"
},
{
"name": "Update Inbox",
"description": "Update the display name of an email inbox in AgentMail"
},
{
"name": "Delete Inbox",
"description": "Delete an email inbox in AgentMail"
}
],
"operationCount": 21,
"triggers": [],
"triggerCount": 0,
"authType": "api-key",
"category": "tools",
"integrationType": "email",
"tags": ["messaging"]
},
{
"type": "ahrefs",
"slug": "ahrefs",
@@ -9717,9 +9820,61 @@
{
"name": "List Retrospectives",
"description": "List incident retrospectives (post-mortems) from Rootly."
},
{
"name": "Delete Incident",
"description": "Delete an incident by ID from Rootly."
},
{
"name": "Get Alert",
"description": "Retrieve a single alert by ID from Rootly."
},
{
"name": "Update Alert",
"description": "Update an existing alert in Rootly."
},
{
"name": "Acknowledge Alert",
"description": "Acknowledge an alert in Rootly."
},
{
"name": "Resolve Alert",
"description": "Resolve an alert in Rootly."
},
{
"name": "Create Action Item",
"description": "Create a new action item for an incident in Rootly."
},
{
"name": "List Action Items",
"description": "List action items for an incident in Rootly."
},
{
"name": "List Users",
"description": "List users from Rootly with optional search and email filtering."
},
{
"name": "List On-Calls",
"description": "List current on-call entries from Rootly with optional filtering."
},
{
"name": "List Schedules",
"description": "List on-call schedules from Rootly with optional search filtering."
},
{
"name": "List Escalation Policies",
"description": "List escalation policies from Rootly with optional search filtering."
},
{
"name": "List Causes",
"description": "List causes from Rootly with optional search filtering."
},
{
"name": "List Playbooks",
"description": "List playbooks from Rootly with pagination support."
}
],
"operationCount": 14,
"operationCount": 27,
"triggers": [],
"triggerCount": 0,
"authType": "api-key",

View File

@@ -0,0 +1,65 @@
import { db } from '@sim/db'
import { user } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { task } from '@trigger.dev/sdk'
import { eq } from 'drizzle-orm'
import { getEmailSubject, renderOnboardingFollowupEmail } from '@/components/emails'
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
import { checkEnterprisePlan } from '@/lib/billing/subscriptions/utils'
import { sendEmail } from '@/lib/messaging/email/mailer'
import { getPersonalEmailFrom } from '@/lib/messaging/email/utils'
import { LIFECYCLE_EMAIL_TASK_ID, type LifecycleEmailType } from '@/lib/messaging/lifecycle'
const logger = createLogger('LifecycleEmail')
interface LifecycleEmailParams {
userId: string
type: LifecycleEmailType
}
async function sendLifecycleEmail({ userId, type }: LifecycleEmailParams): Promise<void> {
const [userData] = await db.select().from(user).where(eq(user.id, userId)).limit(1)
if (!userData?.email) {
logger.warn('[lifecycle-email] User not found or has no email', { userId, type })
return
}
const subscription = await getHighestPrioritySubscription(userId)
if (checkEnterprisePlan(subscription)) {
logger.info('[lifecycle-email] Skipping lifecycle email for enterprise user', { userId, type })
return
}
const { from, replyTo } = getPersonalEmailFrom()
let html: string
switch (type) {
case 'onboarding-followup':
html = await renderOnboardingFollowupEmail(userData.name || undefined)
break
default:
logger.warn('[lifecycle-email] Unknown lifecycle email type', { type })
return
}
await sendEmail({
to: userData.email,
subject: getEmailSubject(type),
html,
from,
replyTo,
emailType: 'transactional',
})
logger.info('[lifecycle-email] Sent lifecycle email', { userId, type })
}
export const lifecycleEmailTask = task({
id: LIFECYCLE_EMAIL_TASK_ID,
retry: { maxAttempts: 2 },
run: async (params: LifecycleEmailParams) => {
await sendLifecycleEmail(params)
},
})

View File

@@ -0,0 +1,621 @@
import { AgentMailIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
export const AgentMailBlock: BlockConfig = {
type: 'agentmail',
name: 'AgentMail',
description: 'Manage email inboxes, threads, and messages with AgentMail',
longDescription:
'Integrate AgentMail into your workflow. Create and manage email inboxes, send and receive messages, reply to threads, manage drafts, and organize threads with labels. Requires API Key.',
docsLink: 'https://docs.sim.ai/tools/agentmail',
category: 'tools',
integrationType: IntegrationType.Email,
tags: ['messaging'],
bgColor: '#000000',
icon: AgentMailIcon,
authMode: AuthMode.ApiKey,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Send Message', id: 'send_message' },
{ label: 'Reply to Message', id: 'reply_message' },
{ label: 'Forward Message', id: 'forward_message' },
{ label: 'List Threads', id: 'list_threads' },
{ label: 'Get Thread', id: 'get_thread' },
{ label: 'Update Thread Labels', id: 'update_thread' },
{ label: 'Delete Thread', id: 'delete_thread' },
{ label: 'List Messages', id: 'list_messages' },
{ label: 'Get Message', id: 'get_message' },
{ label: 'Update Message Labels', id: 'update_message' },
{ label: 'Create Draft', id: 'create_draft' },
{ label: 'List Drafts', id: 'list_drafts' },
{ label: 'Get Draft', id: 'get_draft' },
{ label: 'Update Draft', id: 'update_draft' },
{ label: 'Delete Draft', id: 'delete_draft' },
{ label: 'Send Draft', id: 'send_draft' },
{ label: 'Create Inbox', id: 'create_inbox' },
{ label: 'List Inboxes', id: 'list_inboxes' },
{ label: 'Get Inbox', id: 'get_inbox' },
{ label: 'Update Inbox', id: 'update_inbox' },
{ label: 'Delete Inbox', id: 'delete_inbox' },
],
value: () => 'send_message',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your AgentMail API key',
required: true,
password: true,
},
// Send Message fields
{
id: 'inboxId',
title: 'Inbox ID',
type: 'short-input',
placeholder: 'Inbox ID',
condition: {
field: 'operation',
value: [
'send_message',
'reply_message',
'forward_message',
'list_threads',
'get_thread',
'update_thread',
'delete_thread',
'list_messages',
'get_message',
'update_message',
'create_draft',
'list_drafts',
'get_draft',
'update_draft',
'delete_draft',
'send_draft',
],
},
required: {
field: 'operation',
value: [
'send_message',
'reply_message',
'forward_message',
'list_threads',
'get_thread',
'update_thread',
'delete_thread',
'list_messages',
'get_message',
'update_message',
'create_draft',
'list_drafts',
'get_draft',
'update_draft',
'delete_draft',
'send_draft',
],
},
},
{
id: 'to',
title: 'To',
type: 'short-input',
placeholder: 'recipient@example.com',
condition: {
field: 'operation',
value: ['send_message', 'forward_message', 'create_draft', 'update_draft'],
},
required: { field: 'operation', value: ['send_message', 'forward_message'] },
},
{
id: 'subject',
title: 'Subject',
type: 'short-input',
placeholder: 'Email subject',
condition: {
field: 'operation',
value: ['send_message', 'forward_message', 'create_draft', 'update_draft'],
},
required: { field: 'operation', value: 'send_message' },
wandConfig: {
enabled: true,
prompt:
'Generate a compelling email subject line based on the description. Keep it concise. Return ONLY the subject line.',
placeholder: 'Describe the email topic...',
},
},
{
id: 'text',
title: 'Text',
type: 'long-input',
placeholder: 'Plain text email body',
condition: {
field: 'operation',
value: ['send_message', 'reply_message', 'forward_message', 'create_draft', 'update_draft'],
},
wandConfig: {
enabled: true,
prompt:
'Generate email content based on the description. Use clear formatting with short paragraphs. Return ONLY the email body.',
placeholder: 'Describe the email content...',
},
},
{
id: 'html',
title: 'HTML',
type: 'long-input',
placeholder: '<p>HTML email body</p>',
condition: {
field: 'operation',
value: ['send_message', 'reply_message', 'forward_message', 'create_draft', 'update_draft'],
},
mode: 'advanced',
},
{
id: 'cc',
title: 'CC',
type: 'short-input',
placeholder: 'cc@example.com',
condition: {
field: 'operation',
value: ['send_message', 'reply_message', 'forward_message', 'create_draft', 'update_draft'],
},
mode: 'advanced',
},
{
id: 'bcc',
title: 'BCC',
type: 'short-input',
placeholder: 'bcc@example.com',
condition: {
field: 'operation',
value: ['send_message', 'reply_message', 'forward_message', 'create_draft', 'update_draft'],
},
mode: 'advanced',
},
// Reply to Message fields
{
id: 'replyMessageId',
title: 'Message ID to Reply To',
type: 'short-input',
placeholder: 'Message ID',
condition: { field: 'operation', value: 'reply_message' },
required: { field: 'operation', value: 'reply_message' },
},
{
id: 'replyTo',
title: 'Override To',
type: 'short-input',
placeholder: 'Override recipient (optional)',
condition: { field: 'operation', value: 'reply_message' },
mode: 'advanced',
},
{
id: 'replyAll',
title: 'Reply All',
type: 'dropdown',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
],
value: () => 'false',
condition: { field: 'operation', value: 'reply_message' },
mode: 'advanced',
},
// Thread ID fields (shared across thread operations)
{
id: 'threadId',
title: 'Thread ID',
type: 'short-input',
placeholder: 'Thread ID',
condition: {
field: 'operation',
value: ['get_thread', 'update_thread', 'delete_thread'],
},
required: {
field: 'operation',
value: ['get_thread', 'update_thread', 'delete_thread'],
},
},
// Update Thread Labels fields
{
id: 'addLabels',
title: 'Add Labels',
type: 'short-input',
placeholder: 'important, follow-up',
condition: { field: 'operation', value: 'update_thread' },
},
{
id: 'removeLabels',
title: 'Remove Labels',
type: 'short-input',
placeholder: 'inbox, unread',
condition: { field: 'operation', value: 'update_thread' },
},
// Delete Thread fields
{
id: 'permanent',
title: 'Permanent Delete',
type: 'dropdown',
options: [
{ label: 'No (move to trash)', id: 'false' },
{ label: 'Yes (permanent)', id: 'true' },
],
value: () => 'false',
condition: { field: 'operation', value: 'delete_thread' },
mode: 'advanced',
},
// Forward Message fields
{
id: 'forwardMessageId',
title: 'Message ID to Forward',
type: 'short-input',
placeholder: 'Message ID',
condition: { field: 'operation', value: 'forward_message' },
required: { field: 'operation', value: 'forward_message' },
},
// Update Message Labels fields
{
id: 'updateMessageId',
title: 'Message ID',
type: 'short-input',
placeholder: 'Message ID',
condition: { field: 'operation', value: 'update_message' },
required: { field: 'operation', value: 'update_message' },
},
{
id: 'msgAddLabels',
title: 'Add Labels',
type: 'short-input',
placeholder: 'important, follow-up',
condition: { field: 'operation', value: 'update_message' },
},
{
id: 'msgRemoveLabels',
title: 'Remove Labels',
type: 'short-input',
placeholder: 'inbox, unread',
condition: { field: 'operation', value: 'update_message' },
},
// Get Message fields
{
id: 'messageId',
title: 'Message ID',
type: 'short-input',
placeholder: 'Message ID',
condition: { field: 'operation', value: 'get_message' },
required: { field: 'operation', value: 'get_message' },
},
// Draft ID fields (shared across draft operations)
{
id: 'draftId',
title: 'Draft ID',
type: 'short-input',
placeholder: 'Draft ID',
condition: {
field: 'operation',
value: ['get_draft', 'update_draft', 'delete_draft', 'send_draft'],
},
required: {
field: 'operation',
value: ['get_draft', 'update_draft', 'delete_draft', 'send_draft'],
},
},
// Create/Update Draft fields
{
id: 'draftInReplyTo',
title: 'In Reply To',
type: 'short-input',
placeholder: 'Message ID this draft replies to',
condition: { field: 'operation', value: 'create_draft' },
mode: 'advanced',
},
{
id: 'sendAt',
title: 'Schedule Send',
type: 'short-input',
placeholder: 'ISO 8601 timestamp to schedule sending',
condition: { field: 'operation', value: ['create_draft', 'update_draft'] },
mode: 'advanced',
wandConfig: {
enabled: true,
generationType: 'timestamp',
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
placeholder: 'Describe when to send (e.g., "tomorrow at 9am")...',
},
},
// Create Inbox fields
{
id: 'username',
title: 'Username',
type: 'short-input',
placeholder: 'Optional username for email address',
condition: { field: 'operation', value: 'create_inbox' },
},
{
id: 'domain',
title: 'Domain',
type: 'short-input',
placeholder: 'Optional domain for email address',
condition: { field: 'operation', value: 'create_inbox' },
mode: 'advanced',
},
{
id: 'displayName',
title: 'Display Name',
type: 'short-input',
placeholder: 'Inbox display name',
condition: { field: 'operation', value: ['create_inbox', 'update_inbox'] },
required: { field: 'operation', value: 'update_inbox' },
},
// Inbox ID for get/update/delete inbox
{
id: 'inboxIdParam',
title: 'Inbox ID',
type: 'short-input',
placeholder: 'Inbox ID',
condition: {
field: 'operation',
value: ['get_inbox', 'update_inbox', 'delete_inbox'],
},
required: {
field: 'operation',
value: ['get_inbox', 'update_inbox', 'delete_inbox'],
},
},
// Pagination fields (advanced)
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: 'Max results to return',
condition: {
field: 'operation',
value: ['list_inboxes', 'list_threads', 'list_messages', 'list_drafts'],
},
mode: 'advanced',
},
{
id: 'pageToken',
title: 'Page Token',
type: 'short-input',
placeholder: 'Pagination token',
condition: {
field: 'operation',
value: ['list_inboxes', 'list_threads', 'list_messages', 'list_drafts'],
},
mode: 'advanced',
},
// List Threads filters (advanced)
{
id: 'labels',
title: 'Labels Filter',
type: 'short-input',
placeholder: 'Filter by labels (comma-separated)',
condition: { field: 'operation', value: 'list_threads' },
mode: 'advanced',
},
{
id: 'before',
title: 'Before',
type: 'short-input',
placeholder: 'Filter threads before this date',
condition: { field: 'operation', value: 'list_threads' },
mode: 'advanced',
wandConfig: {
enabled: true,
generationType: 'timestamp',
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
placeholder: 'Describe the date (e.g., "yesterday")...',
},
},
{
id: 'after',
title: 'After',
type: 'short-input',
placeholder: 'Filter threads after this date',
condition: { field: 'operation', value: 'list_threads' },
mode: 'advanced',
wandConfig: {
enabled: true,
generationType: 'timestamp',
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
placeholder: 'Describe the date (e.g., "last week")...',
},
},
],
tools: {
access: [
'agentmail_create_draft',
'agentmail_create_inbox',
'agentmail_delete_draft',
'agentmail_delete_inbox',
'agentmail_delete_thread',
'agentmail_forward_message',
'agentmail_get_draft',
'agentmail_get_inbox',
'agentmail_get_message',
'agentmail_get_thread',
'agentmail_list_drafts',
'agentmail_list_inboxes',
'agentmail_list_messages',
'agentmail_list_threads',
'agentmail_reply_message',
'agentmail_send_draft',
'agentmail_send_message',
'agentmail_update_draft',
'agentmail_update_inbox',
'agentmail_update_message',
'agentmail_update_thread',
],
config: {
tool: (params) => `agentmail_${params.operation || 'send_message'}`,
params: (params) => {
const {
operation,
inboxIdParam,
permanent,
replyMessageId,
replyTo,
replyAll,
forwardMessageId,
updateMessageId,
msgAddLabels,
msgRemoveLabels,
addLabels,
removeLabels,
draftInReplyTo,
...rest
} = params
if (['get_inbox', 'update_inbox', 'delete_inbox'].includes(operation) && inboxIdParam) {
rest.inboxId = inboxIdParam
}
if (operation === 'delete_thread' && permanent !== undefined) {
rest.permanent = permanent === 'true'
}
if (operation === 'reply_message' && replyAll !== undefined) {
rest.replyAll = replyAll === 'true'
}
if (operation === 'reply_message' && replyMessageId) {
rest.messageId = replyMessageId
}
if (operation === 'reply_message' && replyTo) {
rest.to = replyTo
} else if (operation === 'reply_message') {
rest.to = undefined
}
if (operation === 'forward_message' && forwardMessageId) {
rest.messageId = forwardMessageId
}
if (operation === 'update_message' && updateMessageId) {
rest.messageId = updateMessageId
}
if (operation === 'update_message' && msgAddLabels) {
rest.addLabels = msgAddLabels
}
if (operation === 'update_message' && msgRemoveLabels) {
rest.removeLabels = msgRemoveLabels
}
if (operation === 'update_thread' && addLabels) {
rest.addLabels = addLabels
}
if (operation === 'update_thread' && removeLabels) {
rest.removeLabels = removeLabels
}
if (operation === 'create_draft' && draftInReplyTo) {
rest.inReplyTo = draftInReplyTo
}
if (rest.limit) {
rest.limit = Number(rest.limit)
}
return rest
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'AgentMail API key' },
inboxId: { type: 'string', description: 'Inbox ID' },
inboxIdParam: {
type: 'string',
description: 'Inbox ID for get/update/delete inbox operations',
},
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
text: { type: 'string', description: 'Plain text email body' },
html: { type: 'string', description: 'HTML email body' },
cc: { type: 'string', description: 'CC email addresses' },
bcc: { type: 'string', description: 'BCC email addresses' },
replyMessageId: { type: 'string', description: 'Message ID to reply to' },
replyTo: { type: 'string', description: 'Override recipient for reply' },
replyAll: { type: 'string', description: 'Reply to all recipients' },
forwardMessageId: { type: 'string', description: 'Message ID to forward' },
updateMessageId: { type: 'string', description: 'Message ID to update labels on' },
msgAddLabels: { type: 'string', description: 'Labels to add to message' },
msgRemoveLabels: { type: 'string', description: 'Labels to remove from message' },
threadId: { type: 'string', description: 'Thread ID' },
addLabels: { type: 'string', description: 'Labels to add to thread (comma-separated)' },
removeLabels: { type: 'string', description: 'Labels to remove from thread (comma-separated)' },
permanent: { type: 'string', description: 'Whether to permanently delete' },
messageId: { type: 'string', description: 'Message ID' },
draftId: { type: 'string', description: 'Draft ID' },
draftInReplyTo: { type: 'string', description: 'Message ID this draft replies to' },
sendAt: { type: 'string', description: 'ISO 8601 timestamp to schedule sending' },
username: { type: 'string', description: 'Username for new inbox' },
domain: { type: 'string', description: 'Domain for new inbox' },
displayName: { type: 'string', description: 'Display name for inbox' },
limit: { type: 'string', description: 'Max results to return' },
pageToken: { type: 'string', description: 'Pagination token' },
labels: { type: 'string', description: 'Labels filter for threads' },
before: { type: 'string', description: 'Filter threads before this date' },
after: { type: 'string', description: 'Filter threads after this date' },
},
outputs: {
inboxId: { type: 'string', description: 'Inbox ID' },
email: { type: 'string', description: 'Inbox email address' },
displayName: { type: 'string', description: 'Inbox display name' },
threadId: { type: 'string', description: 'Thread ID' },
messageId: { type: 'string', description: 'Message ID' },
draftId: { type: 'string', description: 'Draft ID' },
subject: { type: 'string', description: 'Email subject' },
to: { type: 'string', description: 'Recipient email address' },
from: { type: 'string', description: 'Sender email address' },
text: { type: 'string', description: 'Plain text content' },
html: { type: 'string', description: 'HTML content' },
preview: { type: 'string', description: 'Message or draft preview text' },
senders: { type: 'json', description: 'List of sender email addresses' },
recipients: { type: 'json', description: 'List of recipient email addresses' },
labels: { type: 'json', description: 'Thread or draft labels' },
messages: { type: 'json', description: 'List of messages' },
threads: { type: 'json', description: 'List of threads' },
inboxes: { type: 'json', description: 'List of inboxes' },
drafts: { type: 'json', description: 'List of drafts' },
messageCount: { type: 'number', description: 'Number of messages in thread' },
count: { type: 'number', description: 'Total number of results' },
nextPageToken: { type: 'string', description: 'Token for next page of results' },
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
sendStatus: { type: 'string', description: 'Draft send status' },
sendAt: { type: 'string', description: 'Scheduled send time' },
inReplyTo: { type: 'string', description: 'Message ID this draft replies to' },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
}

View File

@@ -12,12 +12,13 @@ export const ResponseBlock: BlockConfig<ResponseBlockOutput> = {
bestPractices: `
- Only use this if the trigger block is the API Trigger.
- Prefer the builder mode over the editor mode.
- This is usually used as the last block in the workflow.
- The Response block is an exit point. When it executes, the workflow stops and the API response is sent immediately.
- Multiple Response blocks can be placed on different branches (e.g. after a Router or Condition). The first one to execute determines the API response and ends the workflow.
- If a Response block is on a parallel branch, there are no guarantees about whether other parallel blocks will run. Avoid placing Response blocks in parallel with blocks that have important side effects.
`,
category: 'blocks',
bgColor: '#2F55FF',
icon: ResponseIcon,
singleInstance: true,
subBlocks: [
{
id: 'dataMode',

View File

@@ -36,11 +36,23 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
{ label: 'List Incident Types', id: 'rootly_list_incident_types' },
{ label: 'List Functionalities', id: 'rootly_list_functionalities' },
{ label: 'List Retrospectives', id: 'rootly_list_retrospectives' },
{ label: 'Delete Incident', id: 'rootly_delete_incident' },
{ label: 'Get Alert', id: 'rootly_get_alert' },
{ label: 'Update Alert', id: 'rootly_update_alert' },
{ label: 'Acknowledge Alert', id: 'rootly_acknowledge_alert' },
{ label: 'Resolve Alert', id: 'rootly_resolve_alert' },
{ label: 'Create Action Item', id: 'rootly_create_action_item' },
{ label: 'List Action Items', id: 'rootly_list_action_items' },
{ label: 'List Users', id: 'rootly_list_users' },
{ label: 'List On-Calls', id: 'rootly_list_on_calls' },
{ label: 'List Schedules', id: 'rootly_list_schedules' },
{ label: 'List Escalation Policies', id: 'rootly_list_escalation_policies' },
{ label: 'List Causes', id: 'rootly_list_causes' },
{ label: 'List Playbooks', id: 'rootly_list_playbooks' },
],
value: () => 'rootly_create_incident',
},
// Create Incident fields
{
id: 'title',
title: 'Title',
@@ -170,7 +182,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// Get Incident fields
{
id: 'getIncidentId',
title: 'Incident ID',
@@ -180,7 +191,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
required: { field: 'operation', value: 'rootly_get_incident' },
},
// Update Incident fields
{
id: 'updateIncidentId',
title: 'Incident ID',
@@ -341,7 +351,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Incidents fields
{
id: 'listIncidentsStatus',
title: 'Status Filter',
@@ -434,7 +443,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// Create Alert fields
{
id: 'alertSummary',
title: 'Summary',
@@ -520,7 +528,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Alerts fields
{
id: 'listAlertsStatus',
title: 'Status Filter',
@@ -585,7 +592,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// Add Incident Event fields
{
id: 'eventIncidentId',
title: 'Incident ID',
@@ -616,7 +622,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Services fields
{
id: 'servicesSearch',
title: 'Search',
@@ -641,7 +646,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Severities fields
{
id: 'severitiesSearch',
title: 'Search',
@@ -666,7 +670,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Teams fields
{
id: 'teamsSearch',
title: 'Search',
@@ -691,7 +694,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Environments fields
{
id: 'environmentsSearch',
title: 'Search',
@@ -716,7 +718,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Incident Types fields
{
id: 'incidentTypesSearch',
title: 'Name Filter',
@@ -741,7 +742,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Functionalities fields
{
id: 'functionalitiesSearch',
title: 'Search',
@@ -766,7 +766,6 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// List Retrospectives fields
{
id: 'retrospectivesStatus',
title: 'Status Filter',
@@ -803,7 +802,404 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
mode: 'advanced',
},
// API Key (common)
{
id: 'deleteIncidentId',
title: 'Incident ID',
type: 'short-input',
placeholder: 'The ID of the incident to delete',
condition: { field: 'operation', value: 'rootly_delete_incident' },
required: { field: 'operation', value: 'rootly_delete_incident' },
},
{
id: 'getAlertId',
title: 'Alert ID',
type: 'short-input',
placeholder: 'The ID of the alert to retrieve',
condition: { field: 'operation', value: 'rootly_get_alert' },
required: { field: 'operation', value: 'rootly_get_alert' },
},
{
id: 'updateAlertId',
title: 'Alert ID',
type: 'short-input',
placeholder: 'The ID of the alert to update',
condition: { field: 'operation', value: 'rootly_update_alert' },
required: { field: 'operation', value: 'rootly_update_alert' },
},
{
id: 'updateAlertSummary',
title: 'Summary',
type: 'short-input',
placeholder: 'Updated alert summary',
condition: { field: 'operation', value: 'rootly_update_alert' },
},
{
id: 'updateAlertDescription',
title: 'Description',
type: 'long-input',
placeholder: 'Updated alert description',
condition: { field: 'operation', value: 'rootly_update_alert' },
},
{
id: 'updateAlertSource',
title: 'Source',
type: 'short-input',
placeholder: 'Alert source (e.g., api, datadog)',
condition: { field: 'operation', value: 'rootly_update_alert' },
mode: 'advanced',
},
{
id: 'updateAlertServiceIds',
title: 'Service IDs',
type: 'short-input',
placeholder: 'Comma-separated service IDs',
condition: { field: 'operation', value: 'rootly_update_alert' },
mode: 'advanced',
},
{
id: 'updateAlertGroupIds',
title: 'Team IDs',
type: 'short-input',
placeholder: 'Comma-separated team/group IDs',
condition: { field: 'operation', value: 'rootly_update_alert' },
mode: 'advanced',
},
{
id: 'updateAlertEnvironmentIds',
title: 'Environment IDs',
type: 'short-input',
placeholder: 'Comma-separated environment IDs',
condition: { field: 'operation', value: 'rootly_update_alert' },
mode: 'advanced',
},
{
id: 'updateAlertExternalId',
title: 'External ID',
type: 'short-input',
placeholder: 'External alert ID',
condition: { field: 'operation', value: 'rootly_update_alert' },
mode: 'advanced',
},
{
id: 'updateAlertExternalUrl',
title: 'External URL',
type: 'short-input',
placeholder: 'Link to external source',
condition: { field: 'operation', value: 'rootly_update_alert' },
mode: 'advanced',
},
{
id: 'updateAlertDeduplicationKey',
title: 'Deduplication Key',
type: 'short-input',
placeholder: 'Key to deduplicate alerts',
condition: { field: 'operation', value: 'rootly_update_alert' },
mode: 'advanced',
},
{
id: 'ackAlertId',
title: 'Alert ID',
type: 'short-input',
placeholder: 'The ID of the alert to acknowledge',
condition: { field: 'operation', value: 'rootly_acknowledge_alert' },
required: { field: 'operation', value: 'rootly_acknowledge_alert' },
},
{
id: 'resolveAlertId',
title: 'Alert ID',
type: 'short-input',
placeholder: 'The ID of the alert to resolve',
condition: { field: 'operation', value: 'rootly_resolve_alert' },
required: { field: 'operation', value: 'rootly_resolve_alert' },
},
{
id: 'resolveResolutionMessage',
title: 'Resolution Message',
type: 'long-input',
placeholder: 'How was the alert resolved?',
condition: { field: 'operation', value: 'rootly_resolve_alert' },
},
{
id: 'resolveRelatedIncidents',
title: 'Resolve Related Incidents',
type: 'dropdown',
options: [
{ label: 'No', id: '' },
{ label: 'Yes', id: 'true' },
],
value: () => '',
condition: { field: 'operation', value: 'rootly_resolve_alert' },
mode: 'advanced',
},
{
id: 'actionItemIncidentId',
title: 'Incident ID',
type: 'short-input',
placeholder: 'The ID of the incident',
condition: { field: 'operation', value: 'rootly_create_action_item' },
required: { field: 'operation', value: 'rootly_create_action_item' },
},
{
id: 'actionItemSummary',
title: 'Summary',
type: 'short-input',
placeholder: 'Action item title',
condition: { field: 'operation', value: 'rootly_create_action_item' },
required: { field: 'operation', value: 'rootly_create_action_item' },
},
{
id: 'actionItemDescription',
title: 'Description',
type: 'long-input',
placeholder: 'Describe the action item',
condition: { field: 'operation', value: 'rootly_create_action_item' },
},
{
id: 'actionItemKind',
title: 'Kind',
type: 'dropdown',
options: [
{ label: 'Default', id: '' },
{ label: 'Task', id: 'task' },
{ label: 'Follow Up', id: 'follow_up' },
],
value: () => '',
condition: { field: 'operation', value: 'rootly_create_action_item' },
},
{
id: 'actionItemPriority',
title: 'Priority',
type: 'dropdown',
options: [
{ label: 'Default', id: '' },
{ label: 'High', id: 'high' },
{ label: 'Medium', id: 'medium' },
{ label: 'Low', id: 'low' },
],
value: () => '',
condition: { field: 'operation', value: 'rootly_create_action_item' },
},
{
id: 'actionItemStatus',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'Default', id: '' },
{ label: 'Open', id: 'open' },
{ label: 'In Progress', id: 'in_progress' },
{ label: 'Cancelled', id: 'cancelled' },
{ label: 'Done', id: 'done' },
],
value: () => '',
condition: { field: 'operation', value: 'rootly_create_action_item' },
mode: 'advanced',
},
{
id: 'actionItemAssignedToUserId',
title: 'Assigned To User ID',
type: 'short-input',
placeholder: 'User ID to assign (use List Users to find IDs)',
condition: { field: 'operation', value: 'rootly_create_action_item' },
mode: 'advanced',
},
{
id: 'actionItemDueDate',
title: 'Due Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
condition: { field: 'operation', value: 'rootly_create_action_item' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYY-MM-DD format for the requested due date. Return ONLY the date string - no explanations, no extra text.',
placeholder: 'Describe the due date (e.g., "next Friday", "in 2 weeks")...',
generationType: 'timestamp',
},
},
{
id: 'listActionItemsIncidentId',
title: 'Incident ID',
type: 'short-input',
placeholder: 'The ID of the incident',
condition: { field: 'operation', value: 'rootly_list_action_items' },
required: { field: 'operation', value: 'rootly_list_action_items' },
},
{
id: 'listActionItemsPageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'rootly_list_action_items' },
mode: 'advanced',
},
{
id: 'listActionItemsPageNumber',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: { field: 'operation', value: 'rootly_list_action_items' },
mode: 'advanced',
},
{
id: 'usersSearch',
title: 'Search',
type: 'short-input',
placeholder: 'Search users...',
condition: { field: 'operation', value: 'rootly_list_users' },
},
{
id: 'usersEmail',
title: 'Email Filter',
type: 'short-input',
placeholder: 'Filter by email address',
condition: { field: 'operation', value: 'rootly_list_users' },
mode: 'advanced',
},
{
id: 'usersPageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'rootly_list_users' },
mode: 'advanced',
},
{
id: 'usersPageNumber',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: { field: 'operation', value: 'rootly_list_users' },
mode: 'advanced',
},
{
id: 'onCallsScheduleIds',
title: 'Schedule IDs',
type: 'short-input',
placeholder: 'Comma-separated schedule IDs',
condition: { field: 'operation', value: 'rootly_list_on_calls' },
},
{
id: 'onCallsEscalationPolicyIds',
title: 'Escalation Policy IDs',
type: 'short-input',
placeholder: 'Comma-separated escalation policy IDs',
condition: { field: 'operation', value: 'rootly_list_on_calls' },
},
{
id: 'onCallsUserIds',
title: 'User IDs',
type: 'short-input',
placeholder: 'Comma-separated user IDs',
condition: { field: 'operation', value: 'rootly_list_on_calls' },
mode: 'advanced',
},
{
id: 'onCallsServiceIds',
title: 'Service IDs',
type: 'short-input',
placeholder: 'Comma-separated service IDs',
condition: { field: 'operation', value: 'rootly_list_on_calls' },
mode: 'advanced',
},
{
id: 'schedulesSearch',
title: 'Search',
type: 'short-input',
placeholder: 'Search schedules...',
condition: { field: 'operation', value: 'rootly_list_schedules' },
},
{
id: 'schedulesPageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'rootly_list_schedules' },
mode: 'advanced',
},
{
id: 'schedulesPageNumber',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: { field: 'operation', value: 'rootly_list_schedules' },
mode: 'advanced',
},
{
id: 'escalationPoliciesSearch',
title: 'Search',
type: 'short-input',
placeholder: 'Search escalation policies...',
condition: { field: 'operation', value: 'rootly_list_escalation_policies' },
},
{
id: 'escalationPoliciesPageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'rootly_list_escalation_policies' },
mode: 'advanced',
},
{
id: 'escalationPoliciesPageNumber',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: { field: 'operation', value: 'rootly_list_escalation_policies' },
mode: 'advanced',
},
{
id: 'causesSearch',
title: 'Search',
type: 'short-input',
placeholder: 'Search causes...',
condition: { field: 'operation', value: 'rootly_list_causes' },
},
{
id: 'causesPageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'rootly_list_causes' },
mode: 'advanced',
},
{
id: 'causesPageNumber',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: { field: 'operation', value: 'rootly_list_causes' },
mode: 'advanced',
},
{
id: 'playbooksPageSize',
title: 'Page Size',
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'rootly_list_playbooks' },
mode: 'advanced',
},
{
id: 'playbooksPageNumber',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: { field: 'operation', value: 'rootly_list_playbooks' },
mode: 'advanced',
},
{
id: 'apiKey',
title: 'API Key',
@@ -829,6 +1225,19 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
'rootly_list_incident_types',
'rootly_list_functionalities',
'rootly_list_retrospectives',
'rootly_delete_incident',
'rootly_get_alert',
'rootly_update_alert',
'rootly_acknowledge_alert',
'rootly_resolve_alert',
'rootly_create_action_item',
'rootly_list_action_items',
'rootly_list_users',
'rootly_list_on_calls',
'rootly_list_schedules',
'rootly_list_escalation_policies',
'rootly_list_causes',
'rootly_list_playbooks',
],
config: {
tool: (params) => params.operation,
@@ -1012,6 +1421,131 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
: undefined,
}
case 'rootly_delete_incident':
return {
...baseParams,
incidentId: params.deleteIncidentId,
}
case 'rootly_get_alert':
return {
...baseParams,
alertId: params.getAlertId,
}
case 'rootly_update_alert':
return {
...baseParams,
alertId: params.updateAlertId,
summary: params.updateAlertSummary,
description: params.updateAlertDescription,
source: params.updateAlertSource,
serviceIds: params.updateAlertServiceIds,
groupIds: params.updateAlertGroupIds,
environmentIds: params.updateAlertEnvironmentIds,
externalId: params.updateAlertExternalId,
externalUrl: params.updateAlertExternalUrl,
deduplicationKey: params.updateAlertDeduplicationKey,
}
case 'rootly_acknowledge_alert':
return {
...baseParams,
alertId: params.ackAlertId,
}
case 'rootly_resolve_alert':
return {
...baseParams,
alertId: params.resolveAlertId,
resolutionMessage: params.resolveResolutionMessage,
resolveRelatedIncidents: params.resolveRelatedIncidents
? params.resolveRelatedIncidents === 'true'
: undefined,
}
case 'rootly_create_action_item':
return {
...baseParams,
incidentId: params.actionItemIncidentId,
summary: params.actionItemSummary,
description: params.actionItemDescription,
kind: params.actionItemKind,
priority: params.actionItemPriority,
status: params.actionItemStatus,
assignedToUserId: params.actionItemAssignedToUserId,
dueDate: params.actionItemDueDate,
}
case 'rootly_list_action_items':
return {
...baseParams,
incidentId: params.listActionItemsIncidentId,
pageSize: params.listActionItemsPageSize
? Number(params.listActionItemsPageSize)
: undefined,
pageNumber: params.listActionItemsPageNumber
? Number(params.listActionItemsPageNumber)
: undefined,
}
case 'rootly_list_users':
return {
...baseParams,
search: params.usersSearch,
email: params.usersEmail,
pageSize: params.usersPageSize ? Number(params.usersPageSize) : undefined,
pageNumber: params.usersPageNumber ? Number(params.usersPageNumber) : undefined,
}
case 'rootly_list_on_calls':
return {
...baseParams,
scheduleIds: params.onCallsScheduleIds,
escalationPolicyIds: params.onCallsEscalationPolicyIds,
userIds: params.onCallsUserIds,
serviceIds: params.onCallsServiceIds,
}
case 'rootly_list_schedules':
return {
...baseParams,
search: params.schedulesSearch,
pageSize: params.schedulesPageSize ? Number(params.schedulesPageSize) : undefined,
pageNumber: params.schedulesPageNumber
? Number(params.schedulesPageNumber)
: undefined,
}
case 'rootly_list_escalation_policies':
return {
...baseParams,
search: params.escalationPoliciesSearch,
pageSize: params.escalationPoliciesPageSize
? Number(params.escalationPoliciesPageSize)
: undefined,
pageNumber: params.escalationPoliciesPageNumber
? Number(params.escalationPoliciesPageNumber)
: undefined,
}
case 'rootly_list_causes':
return {
...baseParams,
search: params.causesSearch,
pageSize: params.causesPageSize ? Number(params.causesPageSize) : undefined,
pageNumber: params.causesPageNumber ? Number(params.causesPageNumber) : undefined,
}
case 'rootly_list_playbooks':
return {
...baseParams,
pageSize: params.playbooksPageSize ? Number(params.playbooksPageSize) : undefined,
pageNumber: params.playbooksPageNumber
? Number(params.playbooksPageNumber)
: undefined,
}
default:
return baseParams
}
@@ -1101,6 +1635,58 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
retrospectivesSearch: { type: 'string', description: 'Search retrospectives' },
retrospectivesPageSize: { type: 'string', description: 'Retrospectives page size' },
retrospectivesPageNumber: { type: 'string', description: 'Retrospectives page number' },
deleteIncidentId: { type: 'string', description: 'Incident ID to delete' },
getAlertId: { type: 'string', description: 'Alert ID to retrieve' },
updateAlertId: { type: 'string', description: 'Alert ID to update' },
updateAlertSummary: { type: 'string', description: 'Updated alert summary' },
updateAlertDescription: { type: 'string', description: 'Updated alert description' },
updateAlertSource: { type: 'string', description: 'Updated alert source' },
updateAlertServiceIds: { type: 'string', description: 'Updated alert service IDs' },
updateAlertGroupIds: { type: 'string', description: 'Updated alert team IDs' },
updateAlertEnvironmentIds: { type: 'string', description: 'Updated alert environment IDs' },
updateAlertExternalId: { type: 'string', description: 'Updated external alert ID' },
updateAlertExternalUrl: { type: 'string', description: 'Updated external URL' },
updateAlertDeduplicationKey: { type: 'string', description: 'Updated deduplication key' },
ackAlertId: { type: 'string', description: 'Alert ID to acknowledge' },
resolveAlertId: { type: 'string', description: 'Alert ID to resolve' },
resolveResolutionMessage: { type: 'string', description: 'Resolution message' },
resolveRelatedIncidents: { type: 'string', description: 'Resolve related incidents' },
actionItemIncidentId: { type: 'string', description: 'Incident ID for action item' },
actionItemSummary: { type: 'string', description: 'Action item summary' },
actionItemDescription: { type: 'string', description: 'Action item description' },
actionItemKind: { type: 'string', description: 'Action item kind' },
actionItemPriority: { type: 'string', description: 'Action item priority' },
actionItemStatus: { type: 'string', description: 'Action item status' },
actionItemAssignedToUserId: { type: 'string', description: 'Assigned user ID' },
actionItemDueDate: { type: 'string', description: 'Action item due date' },
listActionItemsIncidentId: { type: 'string', description: 'Incident ID for action items' },
listActionItemsPageSize: { type: 'string', description: 'Action items page size' },
listActionItemsPageNumber: { type: 'string', description: 'Action items page number' },
usersSearch: { type: 'string', description: 'Search users' },
usersEmail: { type: 'string', description: 'Filter users by email' },
usersPageSize: { type: 'string', description: 'Users page size' },
usersPageNumber: { type: 'string', description: 'Users page number' },
onCallsScheduleIds: { type: 'string', description: 'Filter on-calls by schedule IDs' },
onCallsEscalationPolicyIds: {
type: 'string',
description: 'Filter on-calls by escalation policy IDs',
},
onCallsUserIds: { type: 'string', description: 'Filter on-calls by user IDs' },
onCallsServiceIds: { type: 'string', description: 'Filter on-calls by service IDs' },
schedulesSearch: { type: 'string', description: 'Search schedules' },
schedulesPageSize: { type: 'string', description: 'Schedules page size' },
schedulesPageNumber: { type: 'string', description: 'Schedules page number' },
escalationPoliciesSearch: { type: 'string', description: 'Search escalation policies' },
escalationPoliciesPageSize: { type: 'string', description: 'Escalation policies page size' },
escalationPoliciesPageNumber: {
type: 'string',
description: 'Escalation policies page number',
},
causesSearch: { type: 'string', description: 'Search causes' },
causesPageSize: { type: 'string', description: 'Causes page size' },
causesPageNumber: { type: 'string', description: 'Causes page number' },
playbooksPageSize: { type: 'string', description: 'Playbooks page size' },
playbooksPageNumber: { type: 'string', description: 'Playbooks page number' },
},
outputs: {
incident: {
@@ -1150,6 +1736,42 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
type: 'json',
description: 'List of retrospectives (id, title, status, url, timestamps)',
},
users: {
type: 'json',
description: 'List of users (id, name, email)',
},
onCalls: {
type: 'json',
description:
'List of on-call entries (userId, userName, scheduleId, scheduleName, escalationPolicyId)',
},
schedules: {
type: 'json',
description: 'List of schedules (id, name, description)',
},
escalationPolicies: {
type: 'json',
description: 'List of escalation policies (id, name, description)',
},
causes: {
type: 'json',
description: 'List of causes (id, name, slug, description)',
},
playbooks: {
type: 'json',
description: 'List of playbooks (id, title, summary)',
},
actionItem: {
type: 'json',
description: 'Action item data (id, summary, description, kind, priority, status, dueDate)',
},
actionItems: {
type: 'json',
description:
'List of action items (id, summary, description, kind, priority, status, dueDate)',
},
success: { type: 'boolean', description: 'Whether the operation succeeded' },
message: { type: 'string', description: 'Operation result message' },
totalCount: { type: 'number', description: 'Total count of items returned' },
},
}

View File

@@ -1,5 +1,6 @@
import { A2ABlock } from '@/blocks/blocks/a2a'
import { AgentBlock } from '@/blocks/blocks/agent'
import { AgentMailBlock } from '@/blocks/blocks/agentmail'
import { AhrefsBlock } from '@/blocks/blocks/ahrefs'
import { AirtableBlock } from '@/blocks/blocks/airtable'
import { AirweaveBlock } from '@/blocks/blocks/airweave'
@@ -217,6 +218,7 @@ import type { BlockConfig } from '@/blocks/types'
export const registry: Record<string, BlockConfig> = {
a2a: A2ABlock,
agent: AgentBlock,
agentmail: AgentMailBlock,
ahrefs: AhrefsBlock,
airtable: AirtableBlock,
airweave: AirweaveBlock,

View File

@@ -1,3 +1,4 @@
export { OnboardingFollowupEmail } from './onboarding-followup-email'
export { OTPVerificationEmail } from './otp-verification-email'
export { ResetPasswordEmail } from './reset-password-email'
export { WelcomeEmail } from './welcome-email'

View File

@@ -0,0 +1,57 @@
import { Body, Head, Html, Preview, Text } from '@react-email/components'
interface OnboardingFollowupEmailProps {
userName?: string
}
const styles = {
body: {
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
backgroundColor: '#ffffff',
margin: '0',
padding: '0',
},
container: {
maxWidth: '560px',
margin: '40px auto',
padding: '0 24px',
},
p: {
fontSize: '15px',
lineHeight: '1.6',
color: '#1a1a1a',
margin: '0 0 16px',
},
} as const
export function OnboardingFollowupEmail({ userName }: OnboardingFollowupEmailProps) {
return (
<Html>
<Head />
<Preview>Quick question</Preview>
<Body style={styles.body}>
<div style={styles.container}>
<Text style={styles.p}>{userName ? `Hey ${userName},` : 'Hey,'}</Text>
<Text style={styles.p}>
It&apos;s been a few days since you signed up. I hope you&apos;re enjoying Sim!
</Text>
<Text style={styles.p}>
I&apos;d love to know what did you expect when you signed up vs. what did you get?
</Text>
<Text style={styles.p}>
A reply with your thoughts would really help us improve the product for everyone.
</Text>
<Text style={styles.p}>
Thanks,
<br />
Emir
<br />
Founder, Sim
</Text>
</div>
</Body>
</Html>
)
}
export default OnboardingFollowupEmail

View File

@@ -1,5 +1,10 @@
import { render } from '@react-email/components'
import { OTPVerificationEmail, ResetPasswordEmail, WelcomeEmail } from '@/components/emails/auth'
import {
OnboardingFollowupEmail,
OTPVerificationEmail,
ResetPasswordEmail,
WelcomeEmail,
} from '@/components/emails/auth'
import {
CreditPurchaseEmail,
EnterpriseSubscriptionEmail,
@@ -159,6 +164,10 @@ export async function renderWelcomeEmail(userName?: string): Promise<string> {
return await render(WelcomeEmail({ userName }))
}
export async function renderOnboardingFollowupEmail(userName?: string): Promise<string> {
return await render(OnboardingFollowupEmail({ userName }))
}
export async function renderCreditPurchaseEmail(params: {
userName?: string
amount: number

View File

@@ -16,6 +16,7 @@ export type EmailSubjectType =
| 'plan-welcome-pro'
| 'plan-welcome-team'
| 'credit-purchase'
| 'onboarding-followup'
| 'welcome'
/**
@@ -55,6 +56,8 @@ export function getEmailSubject(type: EmailSubjectType): string {
return `Your Team plan is now active on ${brandName}`
case 'credit-purchase':
return `Credits added to your ${brandName} account`
case 'onboarding-followup':
return `Quick question about ${brandName}`
case 'welcome':
return `Welcome to ${brandName}`
default:

View File

@@ -1,6 +1,33 @@
import type { SVGProps } from 'react'
import { useId } from 'react'
export function AgentMailIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 350 363' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M318.029 88.3407C196.474 115.33 153.48 115.321 33.9244 88.3271C30.6216 87.5814 27.1432 88.9727 25.3284 91.8313L1.24109 129.774C-1.76483 134.509 0.965276 140.798 6.46483 141.898C152.613 171.13 197.678 171.182 343.903 141.835C349.304 140.751 352.064 134.641 349.247 129.907L326.719 92.0479C324.95 89.0744 321.407 87.5907 318.029 88.3407Z'
fill='currentColor'
/>
<path
d='M75.9931 246.6L149.939 311.655C151.973 313.444 151.633 316.969 149.281 318.48L119.141 337.84C117.283 339.034 114.951 338.412 113.933 336.452L70.1276 252.036C68.0779 248.086 72.7553 243.751 75.9931 246.6Z'
fill='currentColor'
/>
<path
d='M274.025 246.6L200.08 311.655C198.046 313.444 198.385 316.969 200.737 318.48L230.877 337.84C232.736 339.034 235.068 338.412 236.085 336.452L279.891 252.036C281.941 248.086 277.263 243.751 274.025 246.6Z'
fill='currentColor'
/>
<path
d='M138.75 198.472L152.436 192.983C155.238 191.918 157.77 191.918 158.574 191.918C164.115 192.126 169.564 192.232 175.009 192.235C180.454 192.232 185.904 192.126 191.444 191.918C192.248 191.918 194.78 191.918 197.583 192.983L211.269 198.472C212.645 199.025 214.082 199.382 215.544 199.448C218.585 199.587 221.733 199.464 224.63 198.811C225.706 198.568 226.728 198.103 227.704 197.545L243.046 188.784C244.81 187.777 246.726 187.138 248.697 186.9L258.276 185.5H259.242H263.556L262.713 190.965L256.679 234.22C255.957 238.31 254.25 242.328 250.443 245.834L187.376 299.258C184.555 301.648 181.107 302.942 177.562 302.942H175.009H172.457C168.911 302.942 165.464 301.648 162.643 299.258L99.5761 245.834C95.7684 242.328 94.0614 238.31 93.3393 234.22L87.3059 190.965L86.4624 185.5H90.7771H91.7429L101.322 186.9C103.293 187.138 105.208 187.777 106.972 188.784L122.314 197.545C123.291 198.103 124.313 198.568 125.389 198.811C128.286 199.464 131.434 199.587 134.474 199.448C135.936 199.382 137.373 199.025 138.75 198.472Z'
fill='currentColor'
/>
<path
d='M102.47 0.847827C205.434 44.796 156.456 42.1015 248.434 1.63153C252.885 -1.09955 258.353 1.88915 259.419 7.69219L269.235 61.1686L270.819 69.7893L263.592 71.8231L263.582 71.8259C190.588 92.3069 165.244 92.0078 86.7576 71.7428L79.1971 69.7905L80.9925 60.8681L91.8401 6.91975C92.9559 1.3706 98.105 -1.55777 102.47 0.847827Z'
fill='currentColor'
/>
</svg>
)
}
export function SearchIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg

View File

@@ -957,6 +957,297 @@ describe('ExecutionEngine', () => {
})
})
describe('Response block exit-point behavior', () => {
it('should lock finalOutput and stop execution when a terminal Response block fires', async () => {
const startNode = createMockNode('start', 'starter')
const responseNode = createMockNode('response', 'response')
startNode.outgoingEdges.set('edge1', { target: 'response' })
const dag = createMockDAG([startNode, responseNode])
const context = createMockContext()
const edgeManager = createMockEdgeManager((node) => {
if (node.id === 'start') return ['response']
return []
})
const nodeOrchestrator = {
executionCount: 0,
executeNode: vi.fn().mockImplementation(async (_ctx: ExecutionContext, nodeId: string) => {
nodeOrchestrator.executionCount++
if (nodeId === 'response') {
return {
nodeId,
output: { data: { message: 'ok' }, status: 200, headers: {} },
isFinalOutput: true,
}
}
return { nodeId, output: {}, isFinalOutput: false }
}),
handleNodeCompletion: vi.fn(),
} as unknown as MockNodeOrchestrator
const engine = new ExecutionEngine(context, dag, edgeManager, nodeOrchestrator)
const result = await engine.run('start')
expect(result.success).toBe(true)
expect(result.output).toEqual({ data: { message: 'ok' }, status: 200, headers: {} })
expect(nodeOrchestrator.executionCount).toBe(2)
})
it('should stop execution after Response block on a branch (Router)', async () => {
const startNode = createMockNode('start', 'starter')
const routerNode = createMockNode('router', 'router')
const successResponse = createMockNode('success-response', 'response')
const errorResponse = createMockNode('error-response', 'response')
startNode.outgoingEdges.set('edge1', { target: 'router' })
routerNode.outgoingEdges.set('success', { target: 'success-response' })
routerNode.outgoingEdges.set('error', { target: 'error-response' })
const dag = createMockDAG([startNode, routerNode, successResponse, errorResponse])
const context = createMockContext()
const edgeManager = createMockEdgeManager((node) => {
if (node.id === 'start') return ['router']
if (node.id === 'router') return ['success-response']
return []
})
const executedNodes: string[] = []
const nodeOrchestrator = {
executionCount: 0,
executeNode: vi.fn().mockImplementation(async (_ctx: ExecutionContext, nodeId: string) => {
executedNodes.push(nodeId)
nodeOrchestrator.executionCount++
if (nodeId === 'success-response') {
return {
nodeId,
output: { data: { result: 'success' }, status: 200, headers: {} },
isFinalOutput: true,
}
}
return { nodeId, output: {}, isFinalOutput: false }
}),
handleNodeCompletion: vi.fn(),
} as unknown as MockNodeOrchestrator
const engine = new ExecutionEngine(context, dag, edgeManager, nodeOrchestrator)
const result = await engine.run('start')
expect(result.success).toBe(true)
expect(result.output).toEqual({ data: { result: 'success' }, status: 200, headers: {} })
expect(executedNodes).not.toContain('error-response')
})
it('should stop all branches when a parallel Response block fires first', async () => {
const startNode = createMockNode('start', 'starter')
const responseNode = createMockNode('fast-response', 'response')
const slowNode = createMockNode('slow-work', 'function')
const afterSlowNode = createMockNode('after-slow', 'function')
startNode.outgoingEdges.set('edge1', { target: 'fast-response' })
startNode.outgoingEdges.set('edge2', { target: 'slow-work' })
slowNode.outgoingEdges.set('edge3', { target: 'after-slow' })
const dag = createMockDAG([startNode, responseNode, slowNode, afterSlowNode])
const context = createMockContext()
const edgeManager = createMockEdgeManager((node) => {
if (node.id === 'start') return ['fast-response', 'slow-work']
if (node.id === 'slow-work') return ['after-slow']
return []
})
const executedNodes: string[] = []
const nodeOrchestrator = {
executionCount: 0,
executeNode: vi.fn().mockImplementation(async (_ctx: ExecutionContext, nodeId: string) => {
executedNodes.push(nodeId)
nodeOrchestrator.executionCount++
if (nodeId === 'fast-response') {
return {
nodeId,
output: { data: { fast: true }, status: 200, headers: {} },
isFinalOutput: true,
}
}
if (nodeId === 'slow-work') {
await new Promise((resolve) => setTimeout(resolve, 1))
return { nodeId, output: { slow: true }, isFinalOutput: false }
}
return { nodeId, output: {}, isFinalOutput: true }
}),
handleNodeCompletion: vi.fn(),
} as unknown as MockNodeOrchestrator
const engine = new ExecutionEngine(context, dag, edgeManager, nodeOrchestrator)
const result = await engine.run('start')
expect(result.success).toBe(true)
expect(result.output).toEqual({ data: { fast: true }, status: 200, headers: {} })
expect(executedNodes).not.toContain('after-slow')
})
it('should use standard finalOutput logic when no Response block exists', async () => {
const startNode = createMockNode('start', 'starter')
const endNode = createMockNode('end', 'function')
startNode.outgoingEdges.set('edge1', { target: 'end' })
const dag = createMockDAG([startNode, endNode])
const context = createMockContext()
const edgeManager = createMockEdgeManager((node) => {
if (node.id === 'start') return ['end']
return []
})
const nodeOrchestrator = {
executionCount: 0,
executeNode: vi.fn().mockImplementation(async (_ctx: ExecutionContext, nodeId: string) => {
nodeOrchestrator.executionCount++
if (nodeId === 'end') {
return { nodeId, output: { result: 'done' }, isFinalOutput: true }
}
return { nodeId, output: {}, isFinalOutput: false }
}),
handleNodeCompletion: vi.fn(),
} as unknown as MockNodeOrchestrator
const engine = new ExecutionEngine(context, dag, edgeManager, nodeOrchestrator)
const result = await engine.run('start')
expect(result.success).toBe(true)
expect(result.output).toEqual({ result: 'done' })
})
it('should not let a second Response block overwrite the first', async () => {
const startNode = createMockNode('start', 'starter')
const response1 = createMockNode('response1', 'response')
const response2 = createMockNode('response2', 'response')
startNode.outgoingEdges.set('edge1', { target: 'response1' })
startNode.outgoingEdges.set('edge2', { target: 'response2' })
const dag = createMockDAG([startNode, response1, response2])
const context = createMockContext()
const edgeManager = createMockEdgeManager((node) => {
if (node.id === 'start') return ['response1', 'response2']
return []
})
const nodeOrchestrator = {
executionCount: 0,
executeNode: vi.fn().mockImplementation(async (_ctx: ExecutionContext, nodeId: string) => {
nodeOrchestrator.executionCount++
if (nodeId === 'response1') {
return {
nodeId,
output: { data: { first: true }, status: 200, headers: {} },
isFinalOutput: true,
}
}
if (nodeId === 'response2') {
return {
nodeId,
output: { data: { second: true }, status: 201, headers: {} },
isFinalOutput: true,
}
}
return { nodeId, output: {}, isFinalOutput: false }
}),
handleNodeCompletion: vi.fn(),
} as unknown as MockNodeOrchestrator
const engine = new ExecutionEngine(context, dag, edgeManager, nodeOrchestrator)
const result = await engine.run('start')
expect(result.success).toBe(true)
expect(result.output).toEqual({ data: { first: true }, status: 200, headers: {} })
})
it('should not let non-Response terminals overwrite a Response block output', async () => {
const startNode = createMockNode('start', 'starter')
const responseNode = createMockNode('response', 'response')
const otherTerminal = createMockNode('other', 'function')
startNode.outgoingEdges.set('edge1', { target: 'response' })
startNode.outgoingEdges.set('edge2', { target: 'other' })
const dag = createMockDAG([startNode, responseNode, otherTerminal])
const context = createMockContext()
const edgeManager = createMockEdgeManager((node) => {
if (node.id === 'start') return ['response', 'other']
return []
})
const nodeOrchestrator = {
executionCount: 0,
executeNode: vi.fn().mockImplementation(async (_ctx: ExecutionContext, nodeId: string) => {
nodeOrchestrator.executionCount++
if (nodeId === 'response') {
return {
nodeId,
output: { data: { response: true }, status: 200, headers: {} },
isFinalOutput: true,
}
}
if (nodeId === 'other') {
await new Promise((resolve) => setTimeout(resolve, 1))
return { nodeId, output: { other: true }, isFinalOutput: true }
}
return { nodeId, output: {}, isFinalOutput: false }
}),
handleNodeCompletion: vi.fn(),
} as unknown as MockNodeOrchestrator
const engine = new ExecutionEngine(context, dag, edgeManager, nodeOrchestrator)
const result = await engine.run('start')
expect(result.success).toBe(true)
expect(result.output).toEqual({ data: { response: true }, status: 200, headers: {} })
})
it('should honor locked Response output even when a parallel node throws an error', async () => {
const startNode = createMockNode('start', 'starter')
const responseNode = createMockNode('response', 'response')
const errorNode = createMockNode('error-node', 'function')
startNode.outgoingEdges.set('edge1', { target: 'response' })
startNode.outgoingEdges.set('edge2', { target: 'error-node' })
const dag = createMockDAG([startNode, responseNode, errorNode])
const context = createMockContext()
const edgeManager = createMockEdgeManager((node) => {
if (node.id === 'start') return ['response', 'error-node']
return []
})
const nodeOrchestrator = {
executionCount: 0,
executeNode: vi.fn().mockImplementation(async (_ctx: ExecutionContext, nodeId: string) => {
nodeOrchestrator.executionCount++
if (nodeId === 'response') {
return {
nodeId,
output: { data: { ok: true }, status: 200, headers: {} },
isFinalOutput: true,
}
}
if (nodeId === 'error-node') {
await new Promise((resolve) => setTimeout(resolve, 1))
throw new Error('Parallel branch failed')
}
return { nodeId, output: {}, isFinalOutput: false }
}),
handleNodeCompletion: vi.fn(),
} as unknown as MockNodeOrchestrator
const engine = new ExecutionEngine(context, dag, edgeManager, nodeOrchestrator)
const result = await engine.run('start')
expect(result.success).toBe(true)
expect(result.output).toEqual({ data: { ok: true }, status: 200, headers: {} })
})
})
describe('Cancellation flag behavior', () => {
it('should set cancelledFlag when abort signal fires', async () => {
const abortController = new AbortController()

View File

@@ -23,6 +23,7 @@ export class ExecutionEngine {
private executing = new Set<Promise<void>>()
private queueLock = Promise.resolve()
private finalOutput: NormalizedBlockOutput = {}
private responseOutputLocked = false
private pausedBlocks: Map<string, PauseMetadata> = new Map()
private allowResumeTriggers: boolean
private cancelledFlag = false
@@ -127,8 +128,7 @@ export class ExecutionEngine {
await this.waitForAllExecutions()
}
// Rethrow the captured error so it's handled by the catch block
if (this.errorFlag && this.executionError) {
if (this.errorFlag && this.executionError && !this.responseOutputLocked) {
throw this.executionError
}
@@ -399,6 +399,12 @@ export class ExecutionEngine {
return
}
if (this.stoppedEarlyFlag && this.responseOutputLocked) {
// Workflow already ended via Response block. Skip state persistence (setBlockOutput),
// parallel/loop scope tracking, and edge propagation — no downstream blocks will run.
return
}
if (output._pauseMetadata) {
const pauseMetadata = output._pauseMetadata
this.pausedBlocks.set(pauseMetadata.contextId, pauseMetadata)
@@ -410,7 +416,17 @@ export class ExecutionEngine {
await this.nodeOrchestrator.handleNodeCompletion(this.context, nodeId, output)
if (isFinalOutput) {
const isResponseBlock = node.block.metadata?.id === BlockType.RESPONSE
if (isResponseBlock) {
if (!this.responseOutputLocked) {
this.finalOutput = output
this.responseOutputLocked = true
}
this.stoppedEarlyFlag = true
return
}
if (isFinalOutput && !this.responseOutputLocked) {
this.finalOutput = output
}

View File

@@ -75,6 +75,7 @@ import { processCredentialDraft } from '@/lib/credentials/draft-processor'
import { sendEmail } from '@/lib/messaging/email/mailer'
import { getFromEmailAddress, getPersonalEmailFrom } from '@/lib/messaging/email/utils'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { scheduleLifecycleEmail } from '@/lib/messaging/lifecycle'
import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'
import { SSO_TRUSTED_PROVIDERS } from '@/ee/sso/constants'
import { createAnonymousSession, ensureAnonymousUserExists } from './anonymous'
@@ -221,6 +222,19 @@ export const auth = betterAuth({
error,
})
}
try {
await scheduleLifecycleEmail({
userId: user.id,
type: 'onboarding-followup',
delayDays: 5,
})
} catch (error) {
logger.error(
'[databaseHooks.user.create.after] Failed to schedule onboarding followup email',
{ userId: user.id, error }
)
}
}
},
},
@@ -596,6 +610,19 @@ export const auth = betterAuth({
error,
})
}
try {
await scheduleLifecycleEmail({
userId: user.id,
type: 'onboarding-followup',
delayDays: 3,
})
} catch (error) {
logger.error(
'[emailVerification.onEmailVerification] Failed to schedule onboarding followup email',
{ userId: user.id, error }
)
}
}
},
},

View File

@@ -0,0 +1,48 @@
import { createLogger } from '@sim/logger'
import { tasks } from '@trigger.dev/sdk'
import { env } from '@/lib/core/config/env'
import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags'
const logger = createLogger('LifecycleEmail')
export const LIFECYCLE_EMAIL_TASK_ID = 'lifecycle-email' as const
/** Supported lifecycle email types. Add new types here as the sequence grows. */
export type LifecycleEmailType = 'onboarding-followup'
interface ScheduleLifecycleEmailOptions {
userId: string
type: LifecycleEmailType
delayDays: number
}
/**
* Schedules a lifecycle email to be sent after a delay.
* Uses Trigger.dev's built-in delay scheduling — no polling or cron needed.
*/
export async function scheduleLifecycleEmail({
userId,
type,
delayDays,
}: ScheduleLifecycleEmailOptions): Promise<void> {
if (!isTriggerDevEnabled || !env.TRIGGER_SECRET_KEY) {
logger.info('[lifecycle] Trigger.dev not configured, skipping lifecycle email', {
userId,
type,
})
return
}
const delayUntil = new Date(Date.now() + delayDays * 24 * 60 * 60 * 1000)
await tasks.trigger(
LIFECYCLE_EMAIL_TASK_ID,
{ userId, type },
{
delay: delayUntil,
idempotencyKey: `lifecycle-${type}-${userId}`,
}
)
logger.info('[lifecycle] Scheduled lifecycle email', { userId, type, delayDays })
}

View File

@@ -0,0 +1,164 @@
import type { CreateDraftParams, CreateDraftResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailCreateDraftTool: ToolConfig<CreateDraftParams, CreateDraftResult> = {
id: 'agentmail_create_draft',
name: 'Create Draft',
description: 'Create a new email draft in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to create the draft in',
},
to: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Recipient email addresses (comma-separated)',
},
subject: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Draft subject line',
},
text: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Plain text draft body',
},
html: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'HTML draft body',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC recipient email addresses (comma-separated)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC recipient email addresses (comma-separated)',
},
inReplyTo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ID of message being replied to',
},
sendAt: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 timestamp to schedule sending',
},
},
request: {
url: (params) => `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/drafts`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.to) body.to = params.to.split(',').map((e) => e.trim())
if (params.subject) body.subject = params.subject
if (params.text) body.text = params.text
if (params.html) body.html = params.html
if (params.cc) body.cc = params.cc.split(',').map((e) => e.trim())
if (params.bcc) body.bcc = params.bcc.split(',').map((e) => e.trim())
if (params.inReplyTo) body.in_reply_to = params.inReplyTo
if (params.sendAt) body.send_at = params.sendAt
return body
},
},
transformResponse: async (response): Promise<CreateDraftResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to create draft',
output: {
draftId: '',
inboxId: '',
subject: null,
to: [],
cc: [],
bcc: [],
text: null,
html: null,
preview: null,
labels: [],
inReplyTo: null,
sendStatus: null,
sendAt: null,
createdAt: '',
updatedAt: '',
},
}
}
return {
success: true,
output: {
draftId: data.draft_id ?? '',
inboxId: data.inbox_id ?? '',
subject: data.subject ?? null,
to: data.to ?? [],
cc: data.cc ?? [],
bcc: data.bcc ?? [],
text: data.text ?? null,
html: data.html ?? null,
preview: data.preview ?? null,
labels: data.labels ?? [],
inReplyTo: data.in_reply_to ?? null,
sendStatus: data.send_status ?? null,
sendAt: data.send_at ?? null,
createdAt: data.created_at ?? '',
updatedAt: data.updated_at ?? '',
},
}
},
outputs: {
draftId: { type: 'string', description: 'Unique identifier for the draft' },
inboxId: { type: 'string', description: 'Inbox the draft belongs to' },
subject: { type: 'string', description: 'Draft subject', optional: true },
to: { type: 'array', description: 'Recipient email addresses' },
cc: { type: 'array', description: 'CC email addresses' },
bcc: { type: 'array', description: 'BCC email addresses' },
text: { type: 'string', description: 'Plain text content', optional: true },
html: { type: 'string', description: 'HTML content', optional: true },
preview: { type: 'string', description: 'Draft preview text', optional: true },
labels: { type: 'array', description: 'Labels assigned to the draft' },
inReplyTo: { type: 'string', description: 'Message ID this draft replies to', optional: true },
sendStatus: {
type: 'string',
description: 'Send status (scheduled, sending, failed)',
optional: true,
},
sendAt: { type: 'string', description: 'Scheduled send time', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
}

View File

@@ -0,0 +1,87 @@
import type { CreateInboxParams, CreateInboxResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailCreateInboxTool: ToolConfig<CreateInboxParams, CreateInboxResult> = {
id: 'agentmail_create_inbox',
name: 'Create Inbox',
description: 'Create a new email inbox with AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
username: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Username for the inbox email address',
},
domain: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Domain for the inbox email address',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Display name for the inbox',
},
},
request: {
url: 'https://api.agentmail.to/v0/inboxes',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
...(params.username && { username: params.username }),
...(params.domain && { domain: params.domain }),
...(params.displayName && { display_name: params.displayName }),
}),
},
transformResponse: async (response): Promise<CreateInboxResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to create inbox',
output: {
inboxId: '',
email: '',
displayName: null,
createdAt: '',
updatedAt: '',
},
}
}
return {
success: true,
output: {
inboxId: data.inbox_id ?? '',
email: data.email ?? '',
displayName: data.display_name ?? null,
createdAt: data.created_at ?? '',
updatedAt: data.updated_at ?? '',
},
}
},
outputs: {
inboxId: { type: 'string', description: 'Unique identifier for the inbox' },
email: { type: 'string', description: 'Email address of the inbox' },
displayName: { type: 'string', description: 'Display name of the inbox', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
}

View File

@@ -0,0 +1,59 @@
import type { DeleteDraftParams, DeleteDraftResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailDeleteDraftTool: ToolConfig<DeleteDraftParams, DeleteDraftResult> = {
id: 'agentmail_delete_draft',
name: 'Delete Draft',
description: 'Delete an email draft in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the draft',
},
draftId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the draft to delete',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/drafts/${params.draftId.trim()}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<DeleteDraftResult> => {
if (!response.ok) {
const data = await response.json()
return {
success: false,
error: data.message ?? 'Failed to delete draft',
output: { deleted: false },
}
}
return {
success: true,
output: { deleted: true },
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the draft was successfully deleted' },
},
}

View File

@@ -0,0 +1,52 @@
import type { DeleteInboxParams, DeleteInboxResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailDeleteInboxTool: ToolConfig<DeleteInboxParams, DeleteInboxResult> = {
id: 'agentmail_delete_inbox',
name: 'Delete Inbox',
description: 'Delete an email inbox in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to delete',
},
},
request: {
url: (params) => `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<DeleteInboxResult> => {
if (!response.ok) {
const data = await response.json()
return {
success: false,
error: data.message ?? 'Failed to delete inbox',
output: { deleted: false },
}
}
return {
success: true,
output: { deleted: true },
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the inbox was successfully deleted' },
},
}

View File

@@ -0,0 +1,70 @@
import type { DeleteThreadParams, DeleteThreadResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailDeleteThreadTool: ToolConfig<DeleteThreadParams, DeleteThreadResult> = {
id: 'agentmail_delete_thread',
name: 'Delete Thread',
description:
'Delete an email thread in AgentMail (moves to trash, or permanently deletes if already in trash)',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the thread',
},
threadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the thread to delete',
},
permanent: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Force permanent deletion instead of moving to trash',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.permanent) query.set('permanent', 'true')
const qs = query.toString()
return `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/threads/${params.threadId.trim()}${qs ? `?${qs}` : ''}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<DeleteThreadResult> => {
if (!response.ok) {
const data = await response.json()
return {
success: false,
error: data.message ?? 'Failed to delete thread',
output: { deleted: false },
}
}
return {
success: true,
output: { deleted: true },
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the thread was successfully deleted' },
},
}

View File

@@ -0,0 +1,112 @@
import type { ForwardMessageParams, ForwardMessageResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailForwardMessageTool: ToolConfig<ForwardMessageParams, ForwardMessageResult> = {
id: 'agentmail_forward_message',
name: 'Forward Message',
description: 'Forward an email message to new recipients in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the message',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to forward',
},
to: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Recipient email addresses (comma-separated)',
},
subject: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Override subject line',
},
text: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Additional plain text to prepend',
},
html: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Additional HTML to prepend',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC recipient email addresses (comma-separated)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC recipient email addresses (comma-separated)',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/messages/${params.messageId.trim()}/forward`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
to: params.to.split(',').map((e) => e.trim()),
}
if (params.subject) body.subject = params.subject
if (params.text) body.text = params.text
if (params.html) body.html = params.html
if (params.cc) body.cc = params.cc.split(',').map((e) => e.trim())
if (params.bcc) body.bcc = params.bcc.split(',').map((e) => e.trim())
return body
},
},
transformResponse: async (response): Promise<ForwardMessageResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to forward message',
output: { messageId: '', threadId: '' },
}
}
return {
success: true,
output: {
messageId: data.message_id ?? '',
threadId: data.thread_id ?? '',
},
}
},
outputs: {
messageId: { type: 'string', description: 'ID of the forwarded message' },
threadId: { type: 'string', description: 'ID of the thread' },
},
}

View File

@@ -0,0 +1,110 @@
import type { GetDraftParams, GetDraftResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailGetDraftTool: ToolConfig<GetDraftParams, GetDraftResult> = {
id: 'agentmail_get_draft',
name: 'Get Draft',
description: 'Get details of a specific email draft in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox the draft belongs to',
},
draftId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the draft to retrieve',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/drafts/${params.draftId.trim()}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<GetDraftResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to get draft',
output: {
draftId: '',
inboxId: '',
subject: null,
to: [],
cc: [],
bcc: [],
text: null,
html: null,
preview: null,
labels: [],
inReplyTo: null,
sendStatus: null,
sendAt: null,
createdAt: '',
updatedAt: '',
},
}
}
return {
success: true,
output: {
draftId: data.draft_id ?? '',
inboxId: data.inbox_id ?? '',
subject: data.subject ?? null,
to: data.to ?? [],
cc: data.cc ?? [],
bcc: data.bcc ?? [],
text: data.text ?? null,
html: data.html ?? null,
preview: data.preview ?? null,
labels: data.labels ?? [],
inReplyTo: data.in_reply_to ?? null,
sendStatus: data.send_status ?? null,
sendAt: data.send_at ?? null,
createdAt: data.created_at ?? '',
updatedAt: data.updated_at ?? '',
},
}
},
outputs: {
draftId: { type: 'string', description: 'Unique identifier for the draft' },
inboxId: { type: 'string', description: 'Inbox the draft belongs to' },
subject: { type: 'string', description: 'Draft subject', optional: true },
to: { type: 'array', description: 'Recipient email addresses' },
cc: { type: 'array', description: 'CC email addresses' },
bcc: { type: 'array', description: 'BCC email addresses' },
text: { type: 'string', description: 'Plain text content', optional: true },
html: { type: 'string', description: 'HTML content', optional: true },
preview: { type: 'string', description: 'Draft preview text', optional: true },
labels: { type: 'array', description: 'Labels assigned to the draft' },
inReplyTo: { type: 'string', description: 'Message ID this draft replies to', optional: true },
sendStatus: {
type: 'string',
description: 'Send status (scheduled, sending, failed)',
optional: true,
},
sendAt: { type: 'string', description: 'Scheduled send time', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
}

View File

@@ -0,0 +1,69 @@
import type { GetInboxParams, GetInboxResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailGetInboxTool: ToolConfig<GetInboxParams, GetInboxResult> = {
id: 'agentmail_get_inbox',
name: 'Get Inbox',
description: 'Get details of a specific email inbox in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to retrieve',
},
},
request: {
url: (params) => `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<GetInboxResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to get inbox',
output: {
inboxId: '',
email: '',
displayName: null,
createdAt: '',
updatedAt: '',
},
}
}
return {
success: true,
output: {
inboxId: data.inbox_id ?? '',
email: data.email ?? '',
displayName: data.display_name ?? null,
createdAt: data.created_at ?? '',
updatedAt: data.updated_at ?? '',
},
}
},
outputs: {
inboxId: { type: 'string', description: 'Unique identifier for the inbox' },
email: { type: 'string', description: 'Email address of the inbox' },
displayName: { type: 'string', description: 'Display name of the inbox', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
}

View File

@@ -0,0 +1,91 @@
import type { GetMessageParams, GetMessageResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailGetMessageTool: ToolConfig<GetMessageParams, GetMessageResult> = {
id: 'agentmail_get_message',
name: 'Get Message',
description: 'Get details of a specific email message in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the message',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to retrieve',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/messages/${params.messageId.trim()}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<GetMessageResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to get message',
output: {
messageId: '',
threadId: '',
from: null,
to: [],
cc: [],
bcc: [],
subject: null,
text: null,
html: null,
createdAt: '',
},
}
}
return {
success: true,
output: {
messageId: data.message_id ?? '',
threadId: data.thread_id ?? '',
from: data.from ?? null,
to: data.to ?? [],
cc: data.cc ?? [],
bcc: data.bcc ?? [],
subject: data.subject ?? null,
text: data.text ?? null,
html: data.html ?? null,
createdAt: data.created_at ?? '',
},
}
},
outputs: {
messageId: { type: 'string', description: 'Unique identifier for the message' },
threadId: { type: 'string', description: 'ID of the thread this message belongs to' },
from: { type: 'string', description: 'Sender email address', optional: true },
to: { type: 'array', description: 'Recipient email addresses' },
cc: { type: 'array', description: 'CC email addresses' },
bcc: { type: 'array', description: 'BCC email addresses' },
subject: { type: 'string', description: 'Message subject', optional: true },
text: { type: 'string', description: 'Plain text content', optional: true },
html: { type: 'string', description: 'HTML content', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
},
}

View File

@@ -0,0 +1,118 @@
import type { GetThreadParams, GetThreadResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailGetThreadTool: ToolConfig<GetThreadParams, GetThreadResult> = {
id: 'agentmail_get_thread',
name: 'Get Thread',
description: 'Get details of a specific email thread including messages in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the thread',
},
threadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the thread to retrieve',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/threads/${params.threadId.trim()}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<GetThreadResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to get thread',
output: {
threadId: '',
subject: null,
senders: [],
recipients: [],
messageCount: 0,
labels: [],
lastMessageAt: null,
createdAt: '',
updatedAt: '',
messages: [],
},
}
}
return {
success: true,
output: {
threadId: data.thread_id ?? '',
subject: data.subject ?? null,
senders: data.senders ?? [],
recipients: data.recipients ?? [],
messageCount: data.message_count ?? 0,
labels: data.labels ?? [],
lastMessageAt: data.timestamp ?? null,
createdAt: data.created_at ?? '',
updatedAt: data.updated_at ?? '',
messages: (data.messages ?? []).map((msg: Record<string, unknown>) => ({
messageId: msg.message_id ?? '',
from: (msg.from as string) ?? null,
to: (msg.to as string[]) ?? [],
cc: (msg.cc as string[]) ?? [],
bcc: (msg.bcc as string[]) ?? [],
subject: (msg.subject as string) ?? null,
text: (msg.text as string) ?? null,
html: (msg.html as string) ?? null,
createdAt: (msg.created_at as string) ?? '',
})),
},
}
},
outputs: {
threadId: { type: 'string', description: 'Unique identifier for the thread' },
subject: { type: 'string', description: 'Thread subject', optional: true },
senders: { type: 'array', description: 'List of sender email addresses' },
recipients: { type: 'array', description: 'List of recipient email addresses' },
messageCount: { type: 'number', description: 'Number of messages in the thread' },
labels: { type: 'array', description: 'Labels assigned to the thread' },
lastMessageAt: { type: 'string', description: 'Timestamp of last message', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
messages: {
type: 'array',
description: 'Messages in the thread',
items: {
type: 'object',
properties: {
messageId: { type: 'string', description: 'Unique identifier for the message' },
from: { type: 'string', description: 'Sender email address', optional: true },
to: { type: 'array', description: 'Recipient email addresses' },
cc: { type: 'array', description: 'CC email addresses' },
bcc: { type: 'array', description: 'BCC email addresses' },
subject: { type: 'string', description: 'Message subject', optional: true },
text: { type: 'string', description: 'Plain text content', optional: true },
html: { type: 'string', description: 'HTML content', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
},
},
},
},
}

View File

@@ -0,0 +1,65 @@
export { agentmailCreateDraftTool } from '@/tools/agentmail/create_draft'
export { agentmailCreateInboxTool } from '@/tools/agentmail/create_inbox'
export { agentmailDeleteDraftTool } from '@/tools/agentmail/delete_draft'
export { agentmailDeleteInboxTool } from '@/tools/agentmail/delete_inbox'
export { agentmailDeleteThreadTool } from '@/tools/agentmail/delete_thread'
export { agentmailForwardMessageTool } from '@/tools/agentmail/forward_message'
export { agentmailGetDraftTool } from '@/tools/agentmail/get_draft'
export { agentmailGetInboxTool } from '@/tools/agentmail/get_inbox'
export { agentmailGetMessageTool } from '@/tools/agentmail/get_message'
export { agentmailGetThreadTool } from '@/tools/agentmail/get_thread'
export { agentmailListDraftsTool } from '@/tools/agentmail/list_drafts'
export { agentmailListInboxesTool } from '@/tools/agentmail/list_inboxes'
export { agentmailListMessagesTool } from '@/tools/agentmail/list_messages'
export { agentmailListThreadsTool } from '@/tools/agentmail/list_threads'
export { agentmailReplyMessageTool } from '@/tools/agentmail/reply_message'
export { agentmailSendDraftTool } from '@/tools/agentmail/send_draft'
export { agentmailSendMessageTool } from '@/tools/agentmail/send_message'
export type {
CreateDraftParams,
CreateDraftResult,
CreateInboxParams,
CreateInboxResult,
DeleteDraftParams,
DeleteDraftResult,
DeleteInboxParams,
DeleteInboxResult,
DeleteThreadParams,
DeleteThreadResult,
ForwardMessageParams,
ForwardMessageResult,
GetDraftParams,
GetDraftResult,
GetInboxParams,
GetInboxResult,
GetMessageParams,
GetMessageResult,
GetThreadParams,
GetThreadResult,
ListDraftsParams,
ListDraftsResult,
ListInboxesParams,
ListInboxesResult,
ListMessagesParams,
ListMessagesResult,
ListThreadsParams,
ListThreadsResult,
ReplyMessageParams,
ReplyMessageResult,
SendDraftParams,
SendDraftResult,
SendMessageParams,
SendMessageResult,
UpdateDraftParams,
UpdateDraftResult,
UpdateInboxParams,
UpdateInboxResult,
UpdateMessageParams,
UpdateMessageResult,
UpdateThreadParams,
UpdateThreadResult,
} from '@/tools/agentmail/types'
export { agentmailUpdateDraftTool } from '@/tools/agentmail/update_draft'
export { agentmailUpdateInboxTool } from '@/tools/agentmail/update_inbox'
export { agentmailUpdateMessageTool } from '@/tools/agentmail/update_message'
export { agentmailUpdateThreadTool } from '@/tools/agentmail/update_thread'

View File

@@ -0,0 +1,116 @@
import type { ListDraftsParams, ListDraftsResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailListDraftsTool: ToolConfig<ListDraftsParams, ListDraftsResult> = {
id: 'agentmail_list_drafts',
name: 'List Drafts',
description: 'List email drafts in an inbox in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to list drafts from',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of drafts to return',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.limit) query.set('limit', String(params.limit))
if (params.pageToken) query.set('page_token', params.pageToken)
const qs = query.toString()
return `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/drafts${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ListDraftsResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to list drafts',
output: { drafts: [], count: 0, nextPageToken: null },
}
}
return {
success: true,
output: {
drafts: (data.drafts ?? []).map((draft: Record<string, unknown>) => ({
draftId: draft.draft_id ?? '',
inboxId: draft.inbox_id ?? '',
subject: (draft.subject as string) ?? null,
to: (draft.to as string[]) ?? [],
cc: (draft.cc as string[]) ?? [],
bcc: (draft.bcc as string[]) ?? [],
preview: (draft.preview as string) ?? null,
sendStatus: (draft.send_status as string) ?? null,
sendAt: (draft.send_at as string) ?? null,
createdAt: (draft.created_at as string) ?? '',
updatedAt: (draft.updated_at as string) ?? '',
})),
count: data.count ?? 0,
nextPageToken: data.next_page_token ?? null,
},
}
},
outputs: {
drafts: {
type: 'array',
description: 'List of drafts',
items: {
type: 'object',
properties: {
draftId: { type: 'string', description: 'Unique identifier for the draft' },
inboxId: { type: 'string', description: 'Inbox the draft belongs to' },
subject: { type: 'string', description: 'Draft subject', optional: true },
to: { type: 'array', description: 'Recipient email addresses' },
cc: { type: 'array', description: 'CC email addresses' },
bcc: { type: 'array', description: 'BCC email addresses' },
preview: { type: 'string', description: 'Draft preview text', optional: true },
sendStatus: {
type: 'string',
description: 'Send status (scheduled, sending, failed)',
optional: true,
},
sendAt: { type: 'string', description: 'Scheduled send time', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
},
},
count: { type: 'number', description: 'Total number of drafts' },
nextPageToken: {
type: 'string',
description: 'Token for retrieving the next page',
optional: true,
},
},
}

View File

@@ -0,0 +1,98 @@
import type { ListInboxesParams, ListInboxesResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailListInboxesTool: ToolConfig<ListInboxesParams, ListInboxesResult> = {
id: 'agentmail_list_inboxes',
name: 'List Inboxes',
description: 'List all email inboxes in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of inboxes to return',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.limit) query.set('limit', String(params.limit))
if (params.pageToken) query.set('page_token', params.pageToken)
const qs = query.toString()
return `https://api.agentmail.to/v0/inboxes${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ListInboxesResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to list inboxes',
output: { inboxes: [], count: 0, nextPageToken: null },
}
}
return {
success: true,
output: {
inboxes: (data.inboxes ?? []).map((inbox: Record<string, unknown>) => ({
inboxId: inbox.inbox_id ?? '',
email: inbox.email ?? '',
displayName: inbox.display_name ?? null,
createdAt: inbox.created_at ?? '',
updatedAt: inbox.updated_at ?? '',
})),
count: data.count ?? 0,
nextPageToken: data.next_page_token ?? null,
},
}
},
outputs: {
inboxes: {
type: 'array',
description: 'List of inboxes',
items: {
type: 'object',
properties: {
inboxId: { type: 'string', description: 'Unique identifier for the inbox' },
email: { type: 'string', description: 'Email address of the inbox' },
displayName: {
type: 'string',
description: 'Display name of the inbox',
optional: true,
},
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
},
},
count: { type: 'number', description: 'Total number of inboxes' },
nextPageToken: {
type: 'string',
description: 'Token for retrieving the next page',
optional: true,
},
},
}

View File

@@ -0,0 +1,102 @@
import type { ListMessagesParams, ListMessagesResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailListMessagesTool: ToolConfig<ListMessagesParams, ListMessagesResult> = {
id: 'agentmail_list_messages',
name: 'List Messages',
description: 'List messages in an inbox in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to list messages from',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of messages to return',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.limit) query.set('limit', String(params.limit))
if (params.pageToken) query.set('page_token', params.pageToken)
const qs = query.toString()
return `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/messages${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ListMessagesResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to list messages',
output: { messages: [], count: 0, nextPageToken: null },
}
}
return {
success: true,
output: {
messages: (data.messages ?? []).map((msg: Record<string, unknown>) => ({
messageId: msg.message_id ?? '',
from: (msg.from as string) ?? null,
to: (msg.to as string[]) ?? [],
subject: (msg.subject as string) ?? null,
preview: (msg.preview as string) ?? null,
createdAt: (msg.created_at as string) ?? '',
})),
count: data.count ?? 0,
nextPageToken: data.next_page_token ?? null,
},
}
},
outputs: {
messages: {
type: 'array',
description: 'List of messages in the inbox',
items: {
type: 'object',
properties: {
messageId: { type: 'string', description: 'Unique identifier for the message' },
from: { type: 'string', description: 'Sender email address', optional: true },
to: { type: 'array', description: 'Recipient email addresses' },
subject: { type: 'string', description: 'Message subject', optional: true },
preview: { type: 'string', description: 'Message preview text', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
},
},
},
count: { type: 'number', description: 'Total number of messages' },
nextPageToken: {
type: 'string',
description: 'Token for retrieving the next page',
optional: true,
},
},
}

View File

@@ -0,0 +1,135 @@
import type { ListThreadsParams, ListThreadsResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailListThreadsTool: ToolConfig<ListThreadsParams, ListThreadsResult> = {
id: 'agentmail_list_threads',
name: 'List Threads',
description: 'List email threads in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to list threads from',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of threads to return',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination token for next page of results',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated labels to filter threads by',
},
before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter threads before this ISO 8601 timestamp',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter threads after this ISO 8601 timestamp',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.limit) query.set('limit', String(params.limit))
if (params.pageToken) query.set('page_token', params.pageToken)
if (params.labels) {
for (const label of params.labels.split(',')) {
query.append('labels', label.trim())
}
}
if (params.before) query.set('before', params.before)
if (params.after) query.set('after', params.after)
const qs = query.toString()
return `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/threads${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response): Promise<ListThreadsResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to list threads',
output: { threads: [], count: 0, nextPageToken: null },
}
}
return {
success: true,
output: {
threads: (data.threads ?? []).map((thread: Record<string, unknown>) => ({
threadId: thread.thread_id ?? '',
subject: (thread.subject as string) ?? null,
senders: (thread.senders as string[]) ?? [],
recipients: (thread.recipients as string[]) ?? [],
messageCount: (thread.message_count as number) ?? 0,
lastMessageAt: (thread.timestamp as string) ?? null,
createdAt: (thread.created_at as string) ?? '',
updatedAt: (thread.updated_at as string) ?? '',
})),
count: data.count ?? 0,
nextPageToken: data.next_page_token ?? null,
},
}
},
outputs: {
threads: {
type: 'array',
description: 'List of email threads',
items: {
type: 'object',
properties: {
threadId: { type: 'string', description: 'Unique identifier for the thread' },
subject: { type: 'string', description: 'Thread subject', optional: true },
senders: { type: 'array', description: 'List of sender email addresses' },
recipients: { type: 'array', description: 'List of recipient email addresses' },
messageCount: { type: 'number', description: 'Number of messages in the thread' },
lastMessageAt: {
type: 'string',
description: 'Timestamp of last message',
optional: true,
},
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
},
},
count: { type: 'number', description: 'Total number of threads' },
nextPageToken: {
type: 'string',
description: 'Token for retrieving the next page',
optional: true,
},
},
}

View File

@@ -0,0 +1,115 @@
import type { ReplyMessageParams, ReplyMessageResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailReplyMessageTool: ToolConfig<ReplyMessageParams, ReplyMessageResult> = {
id: 'agentmail_reply_message',
name: 'Reply to Message',
description: 'Reply to an existing email message in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to reply from',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to reply to',
},
text: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Plain text reply body',
},
html: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'HTML reply body',
},
to: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Override recipient email addresses (comma-separated)',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC email addresses (comma-separated)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC email addresses (comma-separated)',
},
replyAll: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Reply to all recipients of the original message',
},
},
request: {
url: (params) => {
const endpoint = params.replyAll ? 'reply-all' : 'reply'
return `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/messages/${params.messageId.trim()}/${endpoint}`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.text) body.text = params.text
if (params.html) body.html = params.html
// /reply-all endpoint auto-determines recipients; only /reply accepts to/cc/bcc
if (!params.replyAll) {
if (params.to) body.to = params.to.split(',').map((e) => e.trim())
if (params.cc) body.cc = params.cc.split(',').map((e) => e.trim())
if (params.bcc) body.bcc = params.bcc.split(',').map((e) => e.trim())
}
return body
},
},
transformResponse: async (response): Promise<ReplyMessageResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to reply to message',
output: { messageId: '', threadId: '' },
}
}
return {
success: true,
output: {
messageId: data.message_id ?? '',
threadId: data.thread_id ?? '',
},
}
},
outputs: {
messageId: { type: 'string', description: 'ID of the sent reply message' },
threadId: { type: 'string', description: 'ID of the thread' },
},
}

View File

@@ -0,0 +1,66 @@
import type { SendDraftParams, SendDraftResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailSendDraftTool: ToolConfig<SendDraftParams, SendDraftResult> = {
id: 'agentmail_send_draft',
name: 'Send Draft',
description: 'Send an existing email draft in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the draft',
},
draftId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the draft to send',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/drafts/${params.draftId.trim()}/send`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: () => ({}),
},
transformResponse: async (response): Promise<SendDraftResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to send draft',
output: { messageId: '', threadId: '' },
}
}
return {
success: true,
output: {
messageId: data.message_id ?? '',
threadId: data.thread_id ?? '',
},
}
},
outputs: {
messageId: { type: 'string', description: 'ID of the sent message' },
threadId: { type: 'string', description: 'ID of the thread' },
},
}

View File

@@ -0,0 +1,114 @@
import type { SendMessageParams, SendMessageResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailSendMessageTool: ToolConfig<SendMessageParams, SendMessageResult> = {
id: 'agentmail_send_message',
name: 'Send Message',
description: 'Send an email message from an AgentMail inbox',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to send from',
},
to: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Recipient email address (comma-separated for multiple)',
},
subject: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email subject line',
},
text: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Plain text email body',
},
html: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'HTML email body',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC recipient email addresses (comma-separated)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC recipient email addresses (comma-separated)',
},
},
request: {
url: (params) => `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/messages/send`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
to: params.to.split(',').map((e) => e.trim()),
subject: params.subject,
}
if (params.text) body.text = params.text
if (params.html) body.html = params.html
if (params.cc) body.cc = params.cc.split(',').map((e) => e.trim())
if (params.bcc) body.bcc = params.bcc.split(',').map((e) => e.trim())
return body
},
},
transformResponse: async (response, params): Promise<SendMessageResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to send message',
output: {
threadId: '',
messageId: '',
subject: '',
to: '',
},
}
}
return {
success: true,
output: {
threadId: data.thread_id ?? '',
messageId: data.message_id ?? '',
subject: params?.subject ?? '',
to: params?.to ?? '',
},
}
},
outputs: {
threadId: { type: 'string', description: 'ID of the created thread' },
messageId: { type: 'string', description: 'ID of the sent message' },
subject: { type: 'string', description: 'Email subject line' },
to: { type: 'string', description: 'Recipient email address' },
},
}

View File

@@ -0,0 +1,447 @@
import type { ToolResponse } from '@/tools/types'
/** Create Inbox */
export interface CreateInboxParams {
apiKey: string
username?: string
domain?: string
displayName?: string
}
export interface CreateInboxResult extends ToolResponse {
output: {
inboxId: string
email: string
displayName: string | null
createdAt: string
updatedAt: string
}
}
/** List Inboxes */
export interface ListInboxesParams {
apiKey: string
limit?: number
pageToken?: string
}
export interface ListInboxesResult extends ToolResponse {
output: {
inboxes: Array<{
inboxId: string
email: string
displayName: string | null
createdAt: string
updatedAt: string
}>
count: number
nextPageToken: string | null
}
}
/** Get Inbox */
export interface GetInboxParams {
apiKey: string
inboxId: string
}
export interface GetInboxResult extends ToolResponse {
output: {
inboxId: string
email: string
displayName: string | null
createdAt: string
updatedAt: string
}
}
/** Update Inbox */
export interface UpdateInboxParams {
apiKey: string
inboxId: string
displayName: string
}
export interface UpdateInboxResult extends ToolResponse {
output: {
inboxId: string
email: string
displayName: string | null
createdAt: string
updatedAt: string
}
}
/** Delete Inbox */
export interface DeleteInboxParams {
apiKey: string
inboxId: string
}
export interface DeleteInboxResult extends ToolResponse {
output: {
deleted: boolean
}
}
/** Send Message (Create Thread) */
export interface SendMessageParams {
apiKey: string
inboxId: string
to: string
subject: string
text?: string
html?: string
cc?: string
bcc?: string
}
export interface SendMessageResult extends ToolResponse {
output: {
threadId: string
messageId: string
subject: string
to: string
}
}
/** Reply to Message */
export interface ReplyMessageParams {
apiKey: string
inboxId: string
messageId: string
text?: string
html?: string
to?: string
cc?: string
bcc?: string
replyAll?: boolean
}
export interface ReplyMessageResult extends ToolResponse {
output: {
messageId: string
threadId: string
}
}
/** Forward Message */
export interface ForwardMessageParams {
apiKey: string
inboxId: string
messageId: string
to: string
subject?: string
text?: string
html?: string
cc?: string
bcc?: string
}
export interface ForwardMessageResult extends ToolResponse {
output: {
messageId: string
threadId: string
}
}
/** Update Message Labels */
export interface UpdateMessageParams {
apiKey: string
inboxId: string
messageId: string
addLabels?: string
removeLabels?: string
}
export interface UpdateMessageResult extends ToolResponse {
output: {
messageId: string
labels: string[]
}
}
/** Create Draft */
export interface CreateDraftParams {
apiKey: string
inboxId: string
to?: string
subject?: string
text?: string
html?: string
cc?: string
bcc?: string
inReplyTo?: string
sendAt?: string
}
export interface CreateDraftResult extends ToolResponse {
output: {
draftId: string
inboxId: string
subject: string | null
to: string[]
cc: string[]
bcc: string[]
text: string | null
html: string | null
preview: string | null
labels: string[]
inReplyTo: string | null
sendStatus: string | null
sendAt: string | null
createdAt: string
updatedAt: string
}
}
/** Update Draft */
export interface UpdateDraftParams {
apiKey: string
inboxId: string
draftId: string
to?: string
subject?: string
text?: string
html?: string
cc?: string
bcc?: string
sendAt?: string
}
export interface UpdateDraftResult extends ToolResponse {
output: {
draftId: string
inboxId: string
subject: string | null
to: string[]
cc: string[]
bcc: string[]
text: string | null
html: string | null
preview: string | null
labels: string[]
inReplyTo: string | null
sendStatus: string | null
sendAt: string | null
createdAt: string
updatedAt: string
}
}
/** Delete Draft */
export interface DeleteDraftParams {
apiKey: string
inboxId: string
draftId: string
}
export interface DeleteDraftResult extends ToolResponse {
output: {
deleted: boolean
}
}
/** Send Draft */
export interface SendDraftParams {
apiKey: string
inboxId: string
draftId: string
}
export interface SendDraftResult extends ToolResponse {
output: {
messageId: string
threadId: string
}
}
/** List Drafts */
export interface ListDraftsParams {
apiKey: string
inboxId: string
limit?: number
pageToken?: string
}
export interface ListDraftsResult extends ToolResponse {
output: {
drafts: Array<{
draftId: string
inboxId: string
subject: string | null
to: string[]
cc: string[]
bcc: string[]
preview: string | null
sendStatus: string | null
sendAt: string | null
createdAt: string
updatedAt: string
}>
count: number
nextPageToken: string | null
}
}
/** Get Draft */
export interface GetDraftParams {
apiKey: string
inboxId: string
draftId: string
}
export interface GetDraftResult extends ToolResponse {
output: {
draftId: string
inboxId: string
subject: string | null
to: string[]
cc: string[]
bcc: string[]
text: string | null
html: string | null
preview: string | null
labels: string[]
inReplyTo: string | null
sendStatus: string | null
sendAt: string | null
createdAt: string
updatedAt: string
}
}
/** List Threads */
export interface ListThreadsParams {
apiKey: string
inboxId: string
limit?: number
pageToken?: string
labels?: string
before?: string
after?: string
}
export interface ListThreadsResult extends ToolResponse {
output: {
threads: Array<{
threadId: string
subject: string | null
senders: string[]
recipients: string[]
messageCount: number
lastMessageAt: string | null
createdAt: string
updatedAt: string
}>
count: number
nextPageToken: string | null
}
}
/** Get Thread */
export interface GetThreadParams {
apiKey: string
inboxId: string
threadId: string
}
export interface GetThreadResult extends ToolResponse {
output: {
threadId: string
subject: string | null
senders: string[]
recipients: string[]
messageCount: number
labels: string[]
lastMessageAt: string | null
createdAt: string
updatedAt: string
messages: Array<{
messageId: string
from: string | null
to: string[]
cc: string[]
bcc: string[]
subject: string | null
text: string | null
html: string | null
createdAt: string
}>
}
}
/** Update Thread Labels */
export interface UpdateThreadParams {
apiKey: string
inboxId: string
threadId: string
addLabels?: string
removeLabels?: string
}
export interface UpdateThreadResult extends ToolResponse {
output: {
threadId: string
labels: string[]
}
}
/** Delete Thread */
export interface DeleteThreadParams {
apiKey: string
inboxId: string
threadId: string
permanent?: boolean
}
export interface DeleteThreadResult extends ToolResponse {
output: {
deleted: boolean
}
}
/** List Messages */
export interface ListMessagesParams {
apiKey: string
inboxId: string
limit?: number
pageToken?: string
}
export interface ListMessagesResult extends ToolResponse {
output: {
messages: Array<{
messageId: string
from: string | null
to: string[]
subject: string | null
preview: string | null
createdAt: string
}>
count: number
nextPageToken: string | null
}
}
/** Get Message */
export interface GetMessageParams {
apiKey: string
inboxId: string
messageId: string
}
export interface GetMessageResult extends ToolResponse {
output: {
messageId: string
threadId: string
from: string | null
to: string[]
cc: string[]
bcc: string[]
subject: string | null
text: string | null
html: string | null
createdAt: string
}
}

View File

@@ -0,0 +1,164 @@
import type { UpdateDraftParams, UpdateDraftResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailUpdateDraftTool: ToolConfig<UpdateDraftParams, UpdateDraftResult> = {
id: 'agentmail_update_draft',
name: 'Update Draft',
description: 'Update an existing email draft in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the draft',
},
draftId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the draft to update',
},
to: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Recipient email addresses (comma-separated)',
},
subject: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Draft subject line',
},
text: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Plain text draft body',
},
html: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'HTML draft body',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC recipient email addresses (comma-separated)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC recipient email addresses (comma-separated)',
},
sendAt: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 8601 timestamp to schedule sending',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/drafts/${params.draftId.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.to) body.to = params.to.split(',').map((e) => e.trim())
if (params.subject) body.subject = params.subject
if (params.text) body.text = params.text
if (params.html) body.html = params.html
if (params.cc) body.cc = params.cc.split(',').map((e) => e.trim())
if (params.bcc) body.bcc = params.bcc.split(',').map((e) => e.trim())
if (params.sendAt) body.send_at = params.sendAt
return body
},
},
transformResponse: async (response): Promise<UpdateDraftResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to update draft',
output: {
draftId: '',
inboxId: '',
subject: null,
to: [],
cc: [],
bcc: [],
text: null,
html: null,
preview: null,
labels: [],
inReplyTo: null,
sendStatus: null,
sendAt: null,
createdAt: '',
updatedAt: '',
},
}
}
return {
success: true,
output: {
draftId: data.draft_id ?? '',
inboxId: data.inbox_id ?? '',
subject: data.subject ?? null,
to: data.to ?? [],
cc: data.cc ?? [],
bcc: data.bcc ?? [],
text: data.text ?? null,
html: data.html ?? null,
preview: data.preview ?? null,
labels: data.labels ?? [],
inReplyTo: data.in_reply_to ?? null,
sendStatus: data.send_status ?? null,
sendAt: data.send_at ?? null,
createdAt: data.created_at ?? '',
updatedAt: data.updated_at ?? '',
},
}
},
outputs: {
draftId: { type: 'string', description: 'Unique identifier for the draft' },
inboxId: { type: 'string', description: 'Inbox the draft belongs to' },
subject: { type: 'string', description: 'Draft subject', optional: true },
to: { type: 'array', description: 'Recipient email addresses' },
cc: { type: 'array', description: 'CC email addresses' },
bcc: { type: 'array', description: 'BCC email addresses' },
text: { type: 'string', description: 'Plain text content', optional: true },
html: { type: 'string', description: 'HTML content', optional: true },
preview: { type: 'string', description: 'Draft preview text', optional: true },
labels: { type: 'array', description: 'Labels assigned to the draft' },
inReplyTo: { type: 'string', description: 'Message ID this draft replies to', optional: true },
sendStatus: {
type: 'string',
description: 'Send status (scheduled, sending, failed)',
optional: true,
},
sendAt: { type: 'string', description: 'Scheduled send time', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
}

View File

@@ -0,0 +1,79 @@
import type { UpdateInboxParams, UpdateInboxResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailUpdateInboxTool: ToolConfig<UpdateInboxParams, UpdateInboxResult> = {
id: 'agentmail_update_inbox',
name: 'Update Inbox',
description: 'Update the display name of an email inbox in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox to update',
},
displayName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'New display name for the inbox',
},
},
request: {
url: (params) => `https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
display_name: params.displayName,
}),
},
transformResponse: async (response): Promise<UpdateInboxResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to update inbox',
output: {
inboxId: '',
email: '',
displayName: null,
createdAt: '',
updatedAt: '',
},
}
}
return {
success: true,
output: {
inboxId: data.inbox_id ?? '',
email: data.email ?? '',
displayName: data.display_name ?? null,
createdAt: data.created_at ?? '',
updatedAt: data.updated_at ?? '',
},
}
},
outputs: {
inboxId: { type: 'string', description: 'Unique identifier for the inbox' },
email: { type: 'string', description: 'Email address of the inbox' },
displayName: { type: 'string', description: 'Display name of the inbox', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last updated timestamp' },
},
}

View File

@@ -0,0 +1,87 @@
import type { UpdateMessageParams, UpdateMessageResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailUpdateMessageTool: ToolConfig<UpdateMessageParams, UpdateMessageResult> = {
id: 'agentmail_update_message',
name: 'Update Message',
description: 'Add or remove labels on an email message in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the message',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to update',
},
addLabels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated labels to add to the message',
},
removeLabels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated labels to remove from the message',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/messages/${params.messageId.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.addLabels) {
body.add_labels = params.addLabels.split(',').map((l) => l.trim())
}
if (params.removeLabels) {
body.remove_labels = params.removeLabels.split(',').map((l) => l.trim())
}
return body
},
},
transformResponse: async (response): Promise<UpdateMessageResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to update message',
output: { messageId: '', labels: [] },
}
}
return {
success: true,
output: {
messageId: data.message_id ?? '',
labels: data.labels ?? [],
},
}
},
outputs: {
messageId: { type: 'string', description: 'Unique identifier for the message' },
labels: { type: 'array', description: 'Current labels on the message' },
},
}

View File

@@ -0,0 +1,85 @@
import type { UpdateThreadParams, UpdateThreadResult } from '@/tools/agentmail/types'
import type { ToolConfig } from '@/tools/types'
export const agentmailUpdateThreadTool: ToolConfig<UpdateThreadParams, UpdateThreadResult> = {
id: 'agentmail_update_thread',
name: 'Update Thread Labels',
description: 'Add or remove labels on an email thread in AgentMail',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AgentMail API key',
},
inboxId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the inbox containing the thread',
},
threadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the thread to update',
},
addLabels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated labels to add to the thread',
},
removeLabels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated labels to remove from the thread',
},
},
request: {
url: (params) =>
`https://api.agentmail.to/v0/inboxes/${params.inboxId.trim()}/threads/${params.threadId.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
...(params.addLabels && {
add_labels: params.addLabels.split(',').map((l) => l.trim()),
}),
...(params.removeLabels && {
remove_labels: params.removeLabels.split(',').map((l) => l.trim()),
}),
}),
},
transformResponse: async (response): Promise<UpdateThreadResult> => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.message ?? 'Failed to update thread',
output: { threadId: '', labels: [] },
}
}
return {
success: true,
output: {
threadId: data.thread_id ?? '',
labels: data.labels ?? [],
},
}
},
outputs: {
threadId: { type: 'string', description: 'Unique identifier for the thread' },
labels: { type: 'array', description: 'Current labels on the thread' },
},
}

View File

@@ -8,6 +8,29 @@ import {
a2aSendMessageTool,
a2aSetPushNotificationTool,
} from '@/tools/a2a'
import {
agentmailCreateDraftTool,
agentmailCreateInboxTool,
agentmailDeleteDraftTool,
agentmailDeleteInboxTool,
agentmailDeleteThreadTool,
agentmailForwardMessageTool,
agentmailGetDraftTool,
agentmailGetInboxTool,
agentmailGetMessageTool,
agentmailGetThreadTool,
agentmailListDraftsTool,
agentmailListInboxesTool,
agentmailListMessagesTool,
agentmailListThreadsTool,
agentmailReplyMessageTool,
agentmailSendDraftTool,
agentmailSendMessageTool,
agentmailUpdateDraftTool,
agentmailUpdateInboxTool,
agentmailUpdateMessageTool,
agentmailUpdateThreadTool,
} from '@/tools/agentmail'
import {
ahrefsBacklinksStatsTool,
ahrefsBacklinksTool,
@@ -1986,19 +2009,32 @@ import {
ripplingUpdateWorkLocationTool,
} from '@/tools/rippling'
import {
rootlyAcknowledgeAlertTool,
rootlyAddIncidentEventTool,
rootlyCreateActionItemTool,
rootlyCreateAlertTool,
rootlyCreateIncidentTool,
rootlyDeleteIncidentTool,
rootlyGetAlertTool,
rootlyGetIncidentTool,
rootlyListActionItemsTool,
rootlyListAlertsTool,
rootlyListCausesTool,
rootlyListEnvironmentsTool,
rootlyListEscalationPoliciesTool,
rootlyListFunctionalitiesTool,
rootlyListIncidentsTool,
rootlyListIncidentTypesTool,
rootlyListOnCallsTool,
rootlyListPlaybooksTool,
rootlyListRetrospectivesTool,
rootlyListSchedulesTool,
rootlyListServicesTool,
rootlyListSeveritiesTool,
rootlyListTeamsTool,
rootlyListUsersTool,
rootlyResolveAlertTool,
rootlyUpdateAlertTool,
rootlyUpdateIncidentTool,
} from '@/tools/rootly'
import {
@@ -2680,6 +2716,27 @@ export const tools: Record<string, ToolConfig> = {
a2a_resubscribe: a2aResubscribeTool,
a2a_send_message: a2aSendMessageTool,
a2a_set_push_notification: a2aSetPushNotificationTool,
agentmail_create_draft: agentmailCreateDraftTool,
agentmail_create_inbox: agentmailCreateInboxTool,
agentmail_delete_draft: agentmailDeleteDraftTool,
agentmail_delete_inbox: agentmailDeleteInboxTool,
agentmail_delete_thread: agentmailDeleteThreadTool,
agentmail_forward_message: agentmailForwardMessageTool,
agentmail_get_draft: agentmailGetDraftTool,
agentmail_get_inbox: agentmailGetInboxTool,
agentmail_get_message: agentmailGetMessageTool,
agentmail_get_thread: agentmailGetThreadTool,
agentmail_list_drafts: agentmailListDraftsTool,
agentmail_list_inboxes: agentmailListInboxesTool,
agentmail_list_messages: agentmailListMessagesTool,
agentmail_list_threads: agentmailListThreadsTool,
agentmail_reply_message: agentmailReplyMessageTool,
agentmail_send_draft: agentmailSendDraftTool,
agentmail_send_message: agentmailSendMessageTool,
agentmail_update_draft: agentmailUpdateDraftTool,
agentmail_update_inbox: agentmailUpdateInboxTool,
agentmail_update_message: agentmailUpdateMessageTool,
agentmail_update_thread: agentmailUpdateThreadTool,
airweave_search: airweaveSearchTool,
amplitude_send_event: amplitudeSendEventTool,
amplitude_identify_user: amplitudeIdentifyUserTool,
@@ -3790,19 +3847,32 @@ export const tools: Record<string, ToolConfig> = {
rippling_update_supergroup_inclusion_members: ripplingUpdateSupergroupInclusionMembersTool,
rippling_update_title: ripplingUpdateTitleTool,
rippling_update_work_location: ripplingUpdateWorkLocationTool,
rootly_acknowledge_alert: rootlyAcknowledgeAlertTool,
rootly_add_incident_event: rootlyAddIncidentEventTool,
rootly_create_action_item: rootlyCreateActionItemTool,
rootly_create_alert: rootlyCreateAlertTool,
rootly_create_incident: rootlyCreateIncidentTool,
rootly_delete_incident: rootlyDeleteIncidentTool,
rootly_get_alert: rootlyGetAlertTool,
rootly_get_incident: rootlyGetIncidentTool,
rootly_list_action_items: rootlyListActionItemsTool,
rootly_list_alerts: rootlyListAlertsTool,
rootly_list_causes: rootlyListCausesTool,
rootly_list_environments: rootlyListEnvironmentsTool,
rootly_list_escalation_policies: rootlyListEscalationPoliciesTool,
rootly_list_functionalities: rootlyListFunctionalitiesTool,
rootly_list_incident_types: rootlyListIncidentTypesTool,
rootly_list_incidents: rootlyListIncidentsTool,
rootly_list_on_calls: rootlyListOnCallsTool,
rootly_list_playbooks: rootlyListPlaybooksTool,
rootly_list_retrospectives: rootlyListRetrospectivesTool,
rootly_list_schedules: rootlyListSchedulesTool,
rootly_list_services: rootlyListServicesTool,
rootly_list_severities: rootlyListSeveritiesTool,
rootly_list_teams: rootlyListTeamsTool,
rootly_list_users: rootlyListUsersTool,
rootly_resolve_alert: rootlyResolveAlertTool,
rootly_update_alert: rootlyUpdateAlertTool,
rootly_update_incident: rootlyUpdateIncidentTool,
google_drive_copy: googleDriveCopyTool,
google_drive_create_folder: googleDriveCreateFolderTool,

View File

@@ -0,0 +1,96 @@
import type {
RootlyAcknowledgeAlertParams,
RootlyAcknowledgeAlertResponse,
} from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyAcknowledgeAlertTool: ToolConfig<
RootlyAcknowledgeAlertParams,
RootlyAcknowledgeAlertResponse
> = {
id: 'rootly_acknowledge_alert',
name: 'Rootly Acknowledge Alert',
description: 'Acknowledge an alert in Rootly.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
alertId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the alert to acknowledge',
},
},
request: {
url: (params) => `https://api.rootly.com/v1/alerts/${params.alertId.trim()}/acknowledge`,
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
body: () => ({ data: {} }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { alert: {} as RootlyAcknowledgeAlertResponse['output']['alert'] },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const attrs = data.data?.attributes || {}
return {
success: true,
output: {
alert: {
id: data.data?.id ?? null,
shortId: attrs.short_id ?? null,
summary: attrs.summary ?? '',
description: attrs.description ?? null,
source: attrs.source ?? null,
status: attrs.status ?? null,
externalId: attrs.external_id ?? null,
externalUrl: attrs.external_url ?? null,
deduplicationKey: attrs.deduplication_key ?? null,
createdAt: attrs.created_at ?? '',
updatedAt: attrs.updated_at ?? '',
startedAt: attrs.started_at ?? null,
endedAt: attrs.ended_at ?? null,
},
},
}
},
outputs: {
alert: {
type: 'object',
description: 'The acknowledged alert',
properties: {
id: { type: 'string', description: 'Unique alert ID' },
shortId: { type: 'string', description: 'Short alert ID' },
summary: { type: 'string', description: 'Alert summary' },
description: { type: 'string', description: 'Alert description' },
source: { type: 'string', description: 'Alert source' },
status: { type: 'string', description: 'Alert status' },
externalId: { type: 'string', description: 'External ID' },
externalUrl: { type: 'string', description: 'External URL' },
deduplicationKey: { type: 'string', description: 'Deduplication key' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
startedAt: { type: 'string', description: 'Start date' },
endedAt: { type: 'string', description: 'End date' },
},
},
},
}

View File

@@ -0,0 +1,144 @@
import type {
RootlyCreateActionItemParams,
RootlyCreateActionItemResponse,
} from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyCreateActionItemTool: ToolConfig<
RootlyCreateActionItemParams,
RootlyCreateActionItemResponse
> = {
id: 'rootly_create_action_item',
name: 'Rootly Create Action Item',
description: 'Create a new action item for an incident in Rootly.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
incidentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the incident to add the action item to',
},
summary: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The title of the action item',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'A detailed description of the action item',
},
kind: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The kind of action item (task, follow_up)',
},
priority: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Priority level (high, medium, low)',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Action item status (open, in_progress, cancelled, done)',
},
assignedToUserId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The user ID to assign the action item to',
},
dueDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Due date for the action item',
},
},
request: {
url: (params) => `https://api.rootly.com/v1/incidents/${params.incidentId.trim()}/action_items`,
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
body: (params) => {
const attributes: Record<string, unknown> = {
summary: params.summary,
}
if (params.description) attributes.description = params.description
if (params.kind) attributes.kind = params.kind
if (params.priority) attributes.priority = params.priority
if (params.status) attributes.status = params.status
if (params.assignedToUserId) {
const numericId = Number.parseInt(params.assignedToUserId, 10)
if (!Number.isNaN(numericId)) attributes.assigned_to_user_id = numericId
}
if (params.dueDate) attributes.due_date = params.dueDate
return { data: { type: 'incident_action_items', attributes } }
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { actionItem: {} as RootlyCreateActionItemResponse['output']['actionItem'] },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const attrs = data.data?.attributes || {}
return {
success: true,
output: {
actionItem: {
id: data.data?.id ?? null,
summary: attrs.summary ?? '',
description: attrs.description ?? null,
kind: attrs.kind ?? null,
priority: attrs.priority ?? null,
status: attrs.status ?? null,
dueDate: attrs.due_date ?? null,
createdAt: attrs.created_at ?? '',
updatedAt: attrs.updated_at ?? '',
},
},
}
},
outputs: {
actionItem: {
type: 'object',
description: 'The created action item',
properties: {
id: { type: 'string', description: 'Unique action item ID' },
summary: { type: 'string', description: 'Action item title' },
description: { type: 'string', description: 'Action item description' },
kind: { type: 'string', description: 'Action item kind (task, follow_up)' },
priority: { type: 'string', description: 'Priority level' },
status: { type: 'string', description: 'Action item status' },
dueDate: { type: 'string', description: 'Due date' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
},
},
},
}

View File

@@ -124,6 +124,7 @@ export const rootlyCreateAlertTool: ToolConfig<RootlyCreateAlertParams, RootlyCr
output: {
alert: {
id: data.data?.id ?? null,
shortId: attrs.short_id ?? null,
summary: attrs.summary ?? '',
description: attrs.description ?? null,
source: attrs.source ?? null,
@@ -133,6 +134,8 @@ export const rootlyCreateAlertTool: ToolConfig<RootlyCreateAlertParams, RootlyCr
deduplicationKey: attrs.deduplication_key ?? null,
createdAt: attrs.created_at ?? '',
updatedAt: attrs.updated_at ?? '',
startedAt: attrs.started_at ?? null,
endedAt: attrs.ended_at ?? null,
},
},
}
@@ -144,6 +147,7 @@ export const rootlyCreateAlertTool: ToolConfig<RootlyCreateAlertParams, RootlyCr
description: 'The created alert',
properties: {
id: { type: 'string', description: 'Unique alert ID' },
shortId: { type: 'string', description: 'Short alert ID' },
summary: { type: 'string', description: 'Alert summary' },
description: { type: 'string', description: 'Alert description' },
source: { type: 'string', description: 'Alert source' },
@@ -153,6 +157,8 @@ export const rootlyCreateAlertTool: ToolConfig<RootlyCreateAlertParams, RootlyCr
deduplicationKey: { type: 'string', description: 'Deduplication key' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
startedAt: { type: 'string', description: 'Start date' },
endedAt: { type: 'string', description: 'End date' },
},
},
},

View File

@@ -0,0 +1,64 @@
import type { RootlyDeleteIncidentParams, RootlyDeleteIncidentResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyDeleteIncidentTool: ToolConfig<
RootlyDeleteIncidentParams,
RootlyDeleteIncidentResponse
> = {
id: 'rootly_delete_incident',
name: 'Rootly Delete Incident',
description: 'Delete an incident by ID from Rootly.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
incidentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the incident to delete',
},
},
request: {
url: (params) => `https://api.rootly.com/v1/incidents/${params.incidentId.trim()}`,
method: 'DELETE',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
success: false,
message:
errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
},
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
return {
success: true,
output: {
success: true,
message: 'Incident deleted successfully',
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the deletion succeeded' },
message: { type: 'string', description: 'Result message' },
},
}

View File

@@ -0,0 +1,89 @@
import type { RootlyGetAlertParams, RootlyGetAlertResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyGetAlertTool: ToolConfig<RootlyGetAlertParams, RootlyGetAlertResponse> = {
id: 'rootly_get_alert',
name: 'Rootly Get Alert',
description: 'Retrieve a single alert by ID from Rootly.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
alertId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the alert to retrieve',
},
},
request: {
url: (params) => `https://api.rootly.com/v1/alerts/${params.alertId.trim()}`,
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { alert: {} as RootlyGetAlertResponse['output']['alert'] },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const attrs = data.data?.attributes || {}
return {
success: true,
output: {
alert: {
id: data.data?.id ?? null,
shortId: attrs.short_id ?? null,
summary: attrs.summary ?? '',
description: attrs.description ?? null,
source: attrs.source ?? null,
status: attrs.status ?? null,
externalId: attrs.external_id ?? null,
externalUrl: attrs.external_url ?? null,
deduplicationKey: attrs.deduplication_key ?? null,
createdAt: attrs.created_at ?? '',
updatedAt: attrs.updated_at ?? '',
startedAt: attrs.started_at ?? null,
endedAt: attrs.ended_at ?? null,
},
},
}
},
outputs: {
alert: {
type: 'object',
description: 'The alert details',
properties: {
id: { type: 'string', description: 'Unique alert ID' },
shortId: { type: 'string', description: 'Short alert ID' },
summary: { type: 'string', description: 'Alert summary' },
description: { type: 'string', description: 'Alert description' },
source: { type: 'string', description: 'Alert source' },
status: { type: 'string', description: 'Alert status' },
externalId: { type: 'string', description: 'External ID' },
externalUrl: { type: 'string', description: 'External URL' },
deduplicationKey: { type: 'string', description: 'Deduplication key' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
startedAt: { type: 'string', description: 'Start date' },
endedAt: { type: 'string', description: 'End date' },
},
},
},
}

View File

@@ -1,15 +1,28 @@
export { rootlyAcknowledgeAlertTool } from '@/tools/rootly/acknowledge_alert'
export { rootlyAddIncidentEventTool } from '@/tools/rootly/add_incident_event'
export { rootlyCreateActionItemTool } from '@/tools/rootly/create_action_item'
export { rootlyCreateAlertTool } from '@/tools/rootly/create_alert'
export { rootlyCreateIncidentTool } from '@/tools/rootly/create_incident'
export { rootlyDeleteIncidentTool } from '@/tools/rootly/delete_incident'
export { rootlyGetAlertTool } from '@/tools/rootly/get_alert'
export { rootlyGetIncidentTool } from '@/tools/rootly/get_incident'
export { rootlyListActionItemsTool } from '@/tools/rootly/list_action_items'
export { rootlyListAlertsTool } from '@/tools/rootly/list_alerts'
export { rootlyListCausesTool } from '@/tools/rootly/list_causes'
export { rootlyListEnvironmentsTool } from '@/tools/rootly/list_environments'
export { rootlyListEscalationPoliciesTool } from '@/tools/rootly/list_escalation_policies'
export { rootlyListFunctionalitiesTool } from '@/tools/rootly/list_functionalities'
export { rootlyListIncidentTypesTool } from '@/tools/rootly/list_incident_types'
export { rootlyListIncidentsTool } from '@/tools/rootly/list_incidents'
export { rootlyListOnCallsTool } from '@/tools/rootly/list_on_calls'
export { rootlyListPlaybooksTool } from '@/tools/rootly/list_playbooks'
export { rootlyListRetrospectivesTool } from '@/tools/rootly/list_retrospectives'
export { rootlyListSchedulesTool } from '@/tools/rootly/list_schedules'
export { rootlyListServicesTool } from '@/tools/rootly/list_services'
export { rootlyListSeveritiesTool } from '@/tools/rootly/list_severities'
export { rootlyListTeamsTool } from '@/tools/rootly/list_teams'
export { rootlyListUsersTool } from '@/tools/rootly/list_users'
export { rootlyResolveAlertTool } from '@/tools/rootly/resolve_alert'
export * from '@/tools/rootly/types'
export { rootlyUpdateAlertTool } from '@/tools/rootly/update_alert'
export { rootlyUpdateIncidentTool } from '@/tools/rootly/update_incident'

View File

@@ -0,0 +1,117 @@
import type {
RootlyListActionItemsParams,
RootlyListActionItemsResponse,
} from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyListActionItemsTool: ToolConfig<
RootlyListActionItemsParams,
RootlyListActionItemsResponse
> = {
id: 'rootly_list_action_items',
name: 'Rootly List Action Items',
description: 'List action items for an incident in Rootly.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
incidentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the incident to list action items for',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of items per page (default: 20)',
},
pageNumber: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.pageSize) queryParams.set('page[size]', String(params.pageSize))
if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber))
const qs = queryParams.toString()
return `https://api.rootly.com/v1/incidents/${params.incidentId.trim()}/action_items${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { actionItems: [], totalCount: 0 },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const actionItems = (data.data || []).map((item: Record<string, unknown>) => {
const attrs = (item.attributes || {}) as Record<string, unknown>
return {
id: item.id ?? null,
summary: (attrs.summary as string) ?? '',
description: (attrs.description as string) ?? null,
kind: (attrs.kind as string) ?? null,
priority: (attrs.priority as string) ?? null,
status: (attrs.status as string) ?? null,
dueDate: (attrs.due_date as string) ?? null,
createdAt: (attrs.created_at as string) ?? '',
updatedAt: (attrs.updated_at as string) ?? '',
}
})
return {
success: true,
output: {
actionItems,
totalCount: data.meta?.total_count ?? actionItems.length,
},
}
},
outputs: {
actionItems: {
type: 'array',
description: 'List of action items',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique action item ID' },
summary: { type: 'string', description: 'Action item title' },
description: { type: 'string', description: 'Action item description' },
kind: { type: 'string', description: 'Action item kind (task, follow_up)' },
priority: { type: 'string', description: 'Priority level' },
status: { type: 'string', description: 'Action item status' },
dueDate: { type: 'string', description: 'Due date' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
},
},
},
totalCount: {
type: 'number',
description: 'Total number of action items returned',
},
},
}

View File

@@ -93,6 +93,7 @@ export const rootlyListAlertsTool: ToolConfig<RootlyListAlertsParams, RootlyList
const attrs = (item.attributes || {}) as Record<string, unknown>
return {
id: item.id ?? null,
shortId: (attrs.short_id as string) ?? null,
summary: (attrs.summary as string) ?? '',
description: (attrs.description as string) ?? null,
source: (attrs.source as string) ?? null,
@@ -102,6 +103,8 @@ export const rootlyListAlertsTool: ToolConfig<RootlyListAlertsParams, RootlyList
deduplicationKey: (attrs.deduplication_key as string) ?? null,
createdAt: (attrs.created_at as string) ?? '',
updatedAt: (attrs.updated_at as string) ?? '',
startedAt: (attrs.started_at as string) ?? null,
endedAt: (attrs.ended_at as string) ?? null,
}
})
@@ -122,6 +125,7 @@ export const rootlyListAlertsTool: ToolConfig<RootlyListAlertsParams, RootlyList
type: 'object',
properties: {
id: { type: 'string', description: 'Unique alert ID' },
shortId: { type: 'string', description: 'Short alert ID' },
summary: { type: 'string', description: 'Alert summary' },
description: { type: 'string', description: 'Alert description' },
source: { type: 'string', description: 'Alert source' },
@@ -131,6 +135,8 @@ export const rootlyListAlertsTool: ToolConfig<RootlyListAlertsParams, RootlyList
deduplicationKey: { type: 'string', description: 'Deduplication key' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
startedAt: { type: 'string', description: 'Start date' },
endedAt: { type: 'string', description: 'End date' },
},
},
},

View File

@@ -0,0 +1,108 @@
import type { RootlyListCausesParams, RootlyListCausesResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyListCausesTool: ToolConfig<RootlyListCausesParams, RootlyListCausesResponse> = {
id: 'rootly_list_causes',
name: 'Rootly List Causes',
description: 'List causes from Rootly with optional search filtering.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search term to filter causes',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of items per page (default: 20)',
},
pageNumber: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.search) queryParams.set('filter[search]', params.search)
if (params.pageSize) queryParams.set('page[size]', String(params.pageSize))
if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber))
const qs = queryParams.toString()
return `https://api.rootly.com/v1/causes${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { causes: [], totalCount: 0 },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const causes = (data.data || []).map((item: Record<string, unknown>) => {
const attrs = (item.attributes || {}) as Record<string, unknown>
return {
id: item.id ?? null,
name: (attrs.name as string) ?? '',
slug: (attrs.slug as string) ?? null,
description: (attrs.description as string) ?? null,
position: (attrs.position as number) ?? null,
createdAt: (attrs.created_at as string) ?? '',
updatedAt: (attrs.updated_at as string) ?? '',
}
})
return {
success: true,
output: {
causes,
totalCount: data.meta?.total_count ?? causes.length,
},
}
},
outputs: {
causes: {
type: 'array',
description: 'List of causes',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique cause ID' },
name: { type: 'string', description: 'Cause name' },
slug: { type: 'string', description: 'Cause slug' },
description: { type: 'string', description: 'Cause description' },
position: { type: 'number', description: 'Cause position' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
},
},
},
totalCount: {
type: 'number',
description: 'Total number of causes returned',
},
},
}

View File

@@ -0,0 +1,116 @@
import type {
RootlyListEscalationPoliciesParams,
RootlyListEscalationPoliciesResponse,
} from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyListEscalationPoliciesTool: ToolConfig<
RootlyListEscalationPoliciesParams,
RootlyListEscalationPoliciesResponse
> = {
id: 'rootly_list_escalation_policies',
name: 'Rootly List Escalation Policies',
description: 'List escalation policies from Rootly with optional search filtering.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search term to filter escalation policies',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of items per page (default: 20)',
},
pageNumber: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.search) queryParams.set('filter[search]', params.search)
if (params.pageSize) queryParams.set('page[size]', String(params.pageSize))
if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber))
const qs = queryParams.toString()
return `https://api.rootly.com/v1/escalation_policies${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { escalationPolicies: [], totalCount: 0 },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const escalationPolicies = (data.data || []).map((item: Record<string, unknown>) => {
const attrs = (item.attributes || {}) as Record<string, unknown>
return {
id: item.id ?? null,
name: (attrs.name as string) ?? '',
description: (attrs.description as string) ?? null,
repeatCount: (attrs.repeat_count as number) ?? null,
groupIds: (attrs.group_ids as string[]) ?? null,
serviceIds: (attrs.service_ids as string[]) ?? null,
createdAt: (attrs.created_at as string) ?? '',
updatedAt: (attrs.updated_at as string) ?? '',
}
})
return {
success: true,
output: {
escalationPolicies,
totalCount: data.meta?.total_count ?? escalationPolicies.length,
},
}
},
outputs: {
escalationPolicies: {
type: 'array',
description: 'List of escalation policies',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique escalation policy ID' },
name: { type: 'string', description: 'Escalation policy name' },
description: { type: 'string', description: 'Escalation policy description' },
repeatCount: { type: 'number', description: 'Number of times to repeat escalation' },
groupIds: { type: 'array', description: 'Associated group IDs' },
serviceIds: { type: 'array', description: 'Associated service IDs' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
},
},
},
totalCount: {
type: 'number',
description: 'Total number of escalation policies returned',
},
},
}

View File

@@ -0,0 +1,143 @@
import type { RootlyListOnCallsParams, RootlyListOnCallsResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyListOnCallsTool: ToolConfig<RootlyListOnCallsParams, RootlyListOnCallsResponse> =
{
id: 'rootly_list_on_calls',
name: 'Rootly List On-Calls',
description: 'List current on-call entries from Rootly with optional filtering.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
scheduleIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated schedule IDs to filter by',
},
escalationPolicyIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated escalation policy IDs to filter by',
},
userIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated user IDs to filter by',
},
serviceIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated service IDs to filter by',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.scheduleIds) queryParams.set('filter[schedule_ids]', params.scheduleIds)
if (params.escalationPolicyIds)
queryParams.set('filter[escalation_policy_ids]', params.escalationPolicyIds)
if (params.userIds) queryParams.set('filter[user_ids]', params.userIds)
if (params.serviceIds) queryParams.set('filter[service_ids]', params.serviceIds)
queryParams.set('include', 'user,schedule,escalation_policy')
return `https://api.rootly.com/v1/oncalls?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { onCalls: [], totalCount: 0 },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const included = (data.included || []) as Record<string, unknown>[]
const findIncluded = (
type: string,
id: string | null
): Record<string, unknown> | undefined =>
id ? included.find((i) => i.type === type && i.id === id) : undefined
const onCalls = (data.data || []).map((item: Record<string, unknown>) => {
const attrs = (item.attributes || {}) as Record<string, unknown>
const rels = (item.relationships || {}) as Record<string, Record<string, unknown>>
const userId = ((rels.user?.data as Record<string, unknown>)?.id as string) ?? null
const scheduleId = ((rels.schedule?.data as Record<string, unknown>)?.id as string) ?? null
const escalationPolicyId =
((rels.escalation_policy?.data as Record<string, unknown>)?.id as string) ?? null
const userIncl = findIncluded('users', userId)
const scheduleIncl = findIncluded('schedules', scheduleId)
return {
id: (item.id as string) ?? null,
userId,
userName: userIncl
? (((userIncl.attributes as Record<string, unknown>)?.full_name as string) ?? null)
: null,
scheduleId,
scheduleName: scheduleIncl
? (((scheduleIncl.attributes as Record<string, unknown>)?.name as string) ?? null)
: null,
escalationPolicyId,
startTime: (attrs.start_time as string) ?? null,
endTime: (attrs.end_time as string) ?? null,
}
})
return {
success: true,
output: {
onCalls,
totalCount: data.meta?.total_count ?? onCalls.length,
},
}
},
outputs: {
onCalls: {
type: 'array',
description: 'List of on-call entries',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique on-call entry ID' },
userId: { type: 'string', description: 'ID of the on-call user' },
userName: { type: 'string', description: 'Name of the on-call user' },
scheduleId: { type: 'string', description: 'ID of the associated schedule' },
scheduleName: { type: 'string', description: 'Name of the associated schedule' },
escalationPolicyId: {
type: 'string',
description: 'ID of the associated escalation policy',
},
startTime: { type: 'string', description: 'On-call start time' },
endTime: { type: 'string', description: 'On-call end time' },
},
},
},
totalCount: {
type: 'number',
description: 'Total number of on-call entries returned',
},
},
}

View File

@@ -0,0 +1,102 @@
import type { RootlyListPlaybooksParams, RootlyListPlaybooksResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyListPlaybooksTool: ToolConfig<
RootlyListPlaybooksParams,
RootlyListPlaybooksResponse
> = {
id: 'rootly_list_playbooks',
name: 'Rootly List Playbooks',
description: 'List playbooks from Rootly with pagination support.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of items per page (default: 20)',
},
pageNumber: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.pageSize) queryParams.set('page[size]', String(params.pageSize))
if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber))
const qs = queryParams.toString()
return `https://api.rootly.com/v1/playbooks${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { playbooks: [], totalCount: 0 },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const playbooks = (data.data || []).map((item: Record<string, unknown>) => {
const attrs = (item.attributes || {}) as Record<string, unknown>
return {
id: item.id ?? null,
title: (attrs.title as string) ?? '',
summary: (attrs.summary as string) ?? null,
externalUrl: (attrs.external_url as string) ?? null,
createdAt: (attrs.created_at as string) ?? '',
updatedAt: (attrs.updated_at as string) ?? '',
}
})
return {
success: true,
output: {
playbooks,
totalCount: data.meta?.total_count ?? playbooks.length,
},
}
},
outputs: {
playbooks: {
type: 'array',
description: 'List of playbooks',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique playbook ID' },
title: { type: 'string', description: 'Playbook title' },
summary: { type: 'string', description: 'Playbook summary' },
externalUrl: { type: 'string', description: 'External URL' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
},
},
},
totalCount: {
type: 'number',
description: 'Total number of playbooks returned',
},
},
}

View File

@@ -0,0 +1,112 @@
import type { RootlyListSchedulesParams, RootlyListSchedulesResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyListSchedulesTool: ToolConfig<
RootlyListSchedulesParams,
RootlyListSchedulesResponse
> = {
id: 'rootly_list_schedules',
name: 'Rootly List Schedules',
description: 'List on-call schedules from Rootly with optional search filtering.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search term to filter schedules',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of items per page (default: 20)',
},
pageNumber: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.search) queryParams.set('filter[search]', params.search)
if (params.pageSize) queryParams.set('page[size]', String(params.pageSize))
if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber))
const qs = queryParams.toString()
return `https://api.rootly.com/v1/schedules${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { schedules: [], totalCount: 0 },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const schedules = (data.data || []).map((item: Record<string, unknown>) => {
const attrs = (item.attributes || {}) as Record<string, unknown>
return {
id: item.id ?? null,
name: (attrs.name as string) ?? '',
description: (attrs.description as string) ?? null,
allTimeCoverage: (attrs.all_time_coverage as boolean) ?? null,
createdAt: (attrs.created_at as string) ?? '',
updatedAt: (attrs.updated_at as string) ?? '',
}
})
return {
success: true,
output: {
schedules,
totalCount: data.meta?.total_count ?? schedules.length,
},
}
},
outputs: {
schedules: {
type: 'array',
description: 'List of schedules',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique schedule ID' },
name: { type: 'string', description: 'Schedule name' },
description: { type: 'string', description: 'Schedule description' },
allTimeCoverage: {
type: 'boolean',
description: 'Whether schedule provides 24/7 coverage',
},
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
},
},
},
totalCount: {
type: 'number',
description: 'Total number of schedules returned',
},
},
}

View File

@@ -0,0 +1,117 @@
import type { RootlyListUsersParams, RootlyListUsersResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyListUsersTool: ToolConfig<RootlyListUsersParams, RootlyListUsersResponse> = {
id: 'rootly_list_users',
name: 'Rootly List Users',
description: 'List users from Rootly with optional search and email filtering.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search term to filter users',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter users by email address',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of items per page (default: 20)',
},
pageNumber: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params) => {
const queryParams = new URLSearchParams()
if (params.search) queryParams.set('filter[search]', params.search)
if (params.email) queryParams.set('filter[email]', params.email)
if (params.pageSize) queryParams.set('page[size]', String(params.pageSize))
if (params.pageNumber) queryParams.set('page[number]', String(params.pageNumber))
const qs = queryParams.toString()
return `https://api.rootly.com/v1/users${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { users: [], totalCount: 0 },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const users = (data.data || []).map((item: Record<string, unknown>) => {
const attrs = (item.attributes || {}) as Record<string, unknown>
return {
id: item.id ?? null,
email: (attrs.email as string) ?? '',
firstName: (attrs.first_name as string) ?? null,
lastName: (attrs.last_name as string) ?? null,
fullName: (attrs.full_name as string) ?? null,
timeZone: (attrs.time_zone as string) ?? null,
createdAt: (attrs.created_at as string) ?? '',
updatedAt: (attrs.updated_at as string) ?? '',
}
})
return {
success: true,
output: {
users,
totalCount: data.meta?.total_count ?? users.length,
},
}
},
outputs: {
users: {
type: 'array',
description: 'List of users',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique user ID' },
email: { type: 'string', description: 'User email address' },
firstName: { type: 'string', description: 'User first name' },
lastName: { type: 'string', description: 'User last name' },
fullName: { type: 'string', description: 'User full name' },
timeZone: { type: 'string', description: 'User time zone' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
},
},
},
totalCount: {
type: 'number',
description: 'Total number of users returned',
},
},
}

View File

@@ -0,0 +1,112 @@
import type { RootlyResolveAlertParams, RootlyResolveAlertResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyResolveAlertTool: ToolConfig<
RootlyResolveAlertParams,
RootlyResolveAlertResponse
> = {
id: 'rootly_resolve_alert',
name: 'Rootly Resolve Alert',
description: 'Resolve an alert in Rootly.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
alertId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the alert to resolve',
},
resolutionMessage: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Message describing how the alert was resolved',
},
resolveRelatedIncidents: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to also resolve related incidents',
},
},
request: {
url: (params) => `https://api.rootly.com/v1/alerts/${params.alertId.trim()}/resolve`,
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
body: (params) => {
const attributes: Record<string, unknown> = {}
if (params.resolutionMessage) attributes.resolution_message = params.resolutionMessage
if (params.resolveRelatedIncidents !== undefined)
attributes.resolve_related_incidents = params.resolveRelatedIncidents
if (Object.keys(attributes).length === 0) return { data: {} }
return { data: { type: 'alerts', attributes } }
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { alert: {} as RootlyResolveAlertResponse['output']['alert'] },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const attrs = data.data?.attributes || {}
return {
success: true,
output: {
alert: {
id: data.data?.id ?? null,
shortId: attrs.short_id ?? null,
summary: attrs.summary ?? '',
description: attrs.description ?? null,
source: attrs.source ?? null,
status: attrs.status ?? null,
externalId: attrs.external_id ?? null,
externalUrl: attrs.external_url ?? null,
deduplicationKey: attrs.deduplication_key ?? null,
createdAt: attrs.created_at ?? '',
updatedAt: attrs.updated_at ?? '',
startedAt: attrs.started_at ?? null,
endedAt: attrs.ended_at ?? null,
},
},
}
},
outputs: {
alert: {
type: 'object',
description: 'The resolved alert',
properties: {
id: { type: 'string', description: 'Unique alert ID' },
shortId: { type: 'string', description: 'Short alert ID' },
summary: { type: 'string', description: 'Alert summary' },
description: { type: 'string', description: 'Alert description' },
source: { type: 'string', description: 'Alert source' },
status: { type: 'string', description: 'Alert status' },
externalId: { type: 'string', description: 'External ID' },
externalUrl: { type: 'string', description: 'External URL' },
deduplicationKey: { type: 'string', description: 'Deduplication key' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
startedAt: { type: 'string', description: 'Start date' },
endedAt: { type: 'string', description: 'End date' },
},
},
},
}

View File

@@ -121,6 +121,7 @@ export interface RootlyCreateAlertParams extends RootlyBaseParams {
export interface RootlyAlertData {
id: string | null
shortId: string | null
summary: string
description: string | null
source: string | null
@@ -130,6 +131,8 @@ export interface RootlyAlertData {
deduplicationKey: string | null
createdAt: string
updatedAt: string
startedAt: string | null
endedAt: string | null
}
export interface RootlyCreateAlertResponse extends ToolResponse {
@@ -323,6 +326,109 @@ export interface RootlyListIncidentTypesResponse extends ToolResponse {
}
}
/** List Causes */
export interface RootlyListCausesParams extends RootlyBaseParams {
search?: string
pageSize?: number
pageNumber?: number
}
export interface RootlyCauseData {
id: string | null
name: string
slug: string | null
description: string | null
position: number | null
createdAt: string
updatedAt: string
}
export interface RootlyListCausesResponse extends ToolResponse {
output: {
causes: RootlyCauseData[]
totalCount: number
}
}
/** List Playbooks */
export interface RootlyListPlaybooksParams extends RootlyBaseParams {
pageSize?: number
pageNumber?: number
}
export interface RootlyPlaybookData {
id: string | null
title: string
summary: string | null
externalUrl: string | null
createdAt: string
updatedAt: string
}
export interface RootlyListPlaybooksResponse extends ToolResponse {
output: {
playbooks: RootlyPlaybookData[]
totalCount: number
}
}
/** Delete Incident */
export interface RootlyDeleteIncidentParams extends RootlyBaseParams {
incidentId: string
}
export interface RootlyDeleteIncidentResponse extends ToolResponse {
output: {
success: boolean
message: string
}
}
/** Action Item Data */
export interface RootlyActionItemData {
id: string | null
summary: string
description: string | null
kind: string | null
priority: string | null
status: string | null
dueDate: string | null
createdAt: string
updatedAt: string
}
/** Create Action Item */
export interface RootlyCreateActionItemParams extends RootlyBaseParams {
incidentId: string
summary: string
description?: string
kind?: string
priority?: string
status?: string
assignedToUserId?: string
dueDate?: string
}
export interface RootlyCreateActionItemResponse extends ToolResponse {
output: {
actionItem: RootlyActionItemData
}
}
/** List Action Items */
export interface RootlyListActionItemsParams extends RootlyBaseParams {
incidentId: string
pageSize?: number
pageNumber?: number
}
export interface RootlyListActionItemsResponse extends ToolResponse {
output: {
actionItems: RootlyActionItemData[]
totalCount: number
}
}
/** List Functionalities */
export interface RootlyListFunctionalitiesParams extends RootlyBaseParams {
search?: string
@@ -347,15 +453,177 @@ export interface RootlyListFunctionalitiesResponse extends ToolResponse {
}
}
/** Get Alert */
export interface RootlyGetAlertParams extends RootlyBaseParams {
alertId: string
}
export interface RootlyGetAlertResponse extends ToolResponse {
output: {
alert: RootlyAlertData
}
}
/** Update Alert */
export interface RootlyUpdateAlertParams extends RootlyBaseParams {
alertId: string
summary?: string
description?: string
source?: string
serviceIds?: string
groupIds?: string
environmentIds?: string
externalId?: string
externalUrl?: string
deduplicationKey?: string
}
export interface RootlyUpdateAlertResponse extends ToolResponse {
output: {
alert: RootlyAlertData
}
}
/** Acknowledge Alert */
export interface RootlyAcknowledgeAlertParams extends RootlyBaseParams {
alertId: string
}
export interface RootlyAcknowledgeAlertResponse extends ToolResponse {
output: {
alert: RootlyAlertData
}
}
/** Resolve Alert */
export interface RootlyResolveAlertParams extends RootlyBaseParams {
alertId: string
resolutionMessage?: string
resolveRelatedIncidents?: boolean
}
export interface RootlyResolveAlertResponse extends ToolResponse {
output: {
alert: RootlyAlertData
}
}
/** List Users */
export interface RootlyListUsersParams extends RootlyBaseParams {
search?: string
email?: string
pageSize?: number
pageNumber?: number
}
export interface RootlyUserData {
id: string | null
email: string
firstName: string | null
lastName: string | null
fullName: string | null
timeZone: string | null
createdAt: string
updatedAt: string
}
export interface RootlyListUsersResponse extends ToolResponse {
output: {
users: RootlyUserData[]
totalCount: number
}
}
/** List On-Calls */
export interface RootlyListOnCallsParams extends RootlyBaseParams {
scheduleIds?: string
escalationPolicyIds?: string
userIds?: string
serviceIds?: string
}
export interface RootlyOnCallData {
id: string | null
userId: string | null
userName: string | null
scheduleId: string | null
scheduleName: string | null
escalationPolicyId: string | null
startTime: string | null
endTime: string | null
}
export interface RootlyListOnCallsResponse extends ToolResponse {
output: {
onCalls: RootlyOnCallData[]
totalCount: number
}
}
/** List Schedules */
export interface RootlyListSchedulesParams extends RootlyBaseParams {
search?: string
pageSize?: number
pageNumber?: number
}
export interface RootlyScheduleData {
id: string | null
name: string
description: string | null
allTimeCoverage: boolean | null
createdAt: string
updatedAt: string
}
export interface RootlyListSchedulesResponse extends ToolResponse {
output: {
schedules: RootlyScheduleData[]
totalCount: number
}
}
/** List Escalation Policies */
export interface RootlyListEscalationPoliciesParams extends RootlyBaseParams {
search?: string
pageSize?: number
pageNumber?: number
}
export interface RootlyEscalationPolicyData {
id: string | null
name: string
description: string | null
repeatCount: number | null
groupIds: string[] | null
serviceIds: string[] | null
createdAt: string
updatedAt: string
}
export interface RootlyListEscalationPoliciesResponse extends ToolResponse {
output: {
escalationPolicies: RootlyEscalationPolicyData[]
totalCount: number
}
}
/** Union of all responses */
export type RootlyResponse =
| RootlyCreateIncidentResponse
| RootlyGetIncidentResponse
| RootlyUpdateIncidentResponse
| RootlyDeleteIncidentResponse
| RootlyListIncidentsResponse
| RootlyCreateAlertResponse
| RootlyGetAlertResponse
| RootlyUpdateAlertResponse
| RootlyAcknowledgeAlertResponse
| RootlyResolveAlertResponse
| RootlyListAlertsResponse
| RootlyAddIncidentEventResponse
| RootlyCreateActionItemResponse
| RootlyListActionItemsResponse
| RootlyListServicesResponse
| RootlyListSeveritiesResponse
| RootlyListRetrospectivesResponse
@@ -363,3 +631,9 @@ export type RootlyResponse =
| RootlyListEnvironmentsResponse
| RootlyListIncidentTypesResponse
| RootlyListFunctionalitiesResponse
| RootlyListCausesResponse
| RootlyListPlaybooksResponse
| RootlyListUsersResponse
| RootlyListOnCallsResponse
| RootlyListSchedulesResponse
| RootlyListEscalationPoliciesResponse

View File

@@ -0,0 +1,163 @@
import type { RootlyUpdateAlertParams, RootlyUpdateAlertResponse } from '@/tools/rootly/types'
import type { ToolConfig } from '@/tools/types'
export const rootlyUpdateAlertTool: ToolConfig<RootlyUpdateAlertParams, RootlyUpdateAlertResponse> =
{
id: 'rootly_update_alert',
name: 'Rootly Update Alert',
description: 'Update an existing alert in Rootly.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rootly API key',
},
alertId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the alert to update',
},
summary: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated alert summary',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated alert description',
},
source: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated alert source',
},
serviceIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated service IDs to attach',
},
groupIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated team/group IDs to attach',
},
environmentIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated environment IDs to attach',
},
externalId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated external ID',
},
externalUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated external URL',
},
deduplicationKey: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated deduplication key',
},
},
request: {
url: (params) => `https://api.rootly.com/v1/alerts/${params.alertId.trim()}`,
method: 'PATCH',
headers: (params) => ({
'Content-Type': 'application/vnd.api+json',
Authorization: `Bearer ${params.apiKey}`,
}),
body: (params) => {
const attributes: Record<string, unknown> = {}
if (params.summary) attributes.summary = params.summary
if (params.description) attributes.description = params.description
if (params.source) attributes.source = params.source
if (params.externalId) attributes.external_id = params.externalId
if (params.externalUrl) attributes.external_url = params.externalUrl
if (params.deduplicationKey) attributes.deduplication_key = params.deduplicationKey
if (params.serviceIds) {
attributes.service_ids = params.serviceIds.split(',').map((s: string) => s.trim())
}
if (params.groupIds) {
attributes.group_ids = params.groupIds.split(',').map((s: string) => s.trim())
}
if (params.environmentIds) {
attributes.environment_ids = params.environmentIds.split(',').map((s: string) => s.trim())
}
return { data: { type: 'alerts', id: params.alertId.trim(), attributes } }
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: { alert: {} as RootlyUpdateAlertResponse['output']['alert'] },
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const attrs = data.data?.attributes || {}
return {
success: true,
output: {
alert: {
id: data.data?.id ?? null,
shortId: attrs.short_id ?? null,
summary: attrs.summary ?? '',
description: attrs.description ?? null,
source: attrs.source ?? null,
status: attrs.status ?? null,
externalId: attrs.external_id ?? null,
externalUrl: attrs.external_url ?? null,
deduplicationKey: attrs.deduplication_key ?? null,
createdAt: attrs.created_at ?? '',
updatedAt: attrs.updated_at ?? '',
startedAt: attrs.started_at ?? null,
endedAt: attrs.ended_at ?? null,
},
},
}
},
outputs: {
alert: {
type: 'object',
description: 'The updated alert',
properties: {
id: { type: 'string', description: 'Unique alert ID' },
shortId: { type: 'string', description: 'Short alert ID' },
summary: { type: 'string', description: 'Alert summary' },
description: { type: 'string', description: 'Alert description' },
source: { type: 'string', description: 'Alert source' },
status: { type: 'string', description: 'Alert status' },
externalId: { type: 'string', description: 'External ID' },
externalUrl: { type: 'string', description: 'External URL' },
deduplicationKey: { type: 'string', description: 'Deduplication key' },
createdAt: { type: 'string', description: 'Creation date' },
updatedAt: { type: 'string', description: 'Last update date' },
startedAt: { type: 'string', description: 'Start date' },
endedAt: { type: 'string', description: 'End date' },
},
},
},
}