From d995d6f7e10ffc98654d06a79f1c456c12b151cb Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 23 Apr 2026 14:37:06 -0700 Subject: [PATCH 1/6] feat(agentphone): add AgentPhone integration --- apps/docs/components/icons.tsx | 41 +- apps/docs/components/ui/icon-mapping.ts | 2 + .../docs/content/docs/en/tools/agentphone.mdx | 629 +++++++++++++++ apps/docs/content/docs/en/tools/meta.json | 1 + .../integrations/data/icon-mapping.ts | 2 + .../integrations/data/integrations.json | 107 +++ apps/sim/blocks/blocks/agentphone.ts | 739 ++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 30 + apps/sim/tools/agentphone/create_call.ts | 132 ++++ apps/sim/tools/agentphone/create_contact.ts | 109 +++ apps/sim/tools/agentphone/create_number.ts | 106 +++ apps/sim/tools/agentphone/delete_contact.ts | 67 ++ apps/sim/tools/agentphone/get_call.ts | 146 ++++ .../tools/agentphone/get_call_transcript.ts | 94 +++ apps/sim/tools/agentphone/get_contact.ts | 81 ++ apps/sim/tools/agentphone/get_conversation.ts | 137 ++++ .../agentphone/get_conversation_messages.ts | 116 +++ .../tools/agentphone/get_number_messages.ts | 115 +++ apps/sim/tools/agentphone/get_usage.ts | 127 +++ apps/sim/tools/agentphone/get_usage_daily.ts | 88 +++ .../sim/tools/agentphone/get_usage_monthly.ts | 88 +++ apps/sim/tools/agentphone/index.ts | 23 + apps/sim/tools/agentphone/list_calls.ts | 169 ++++ apps/sim/tools/agentphone/list_contacts.ts | 110 +++ .../tools/agentphone/list_conversations.ts | 113 +++ apps/sim/tools/agentphone/list_numbers.ts | 100 +++ apps/sim/tools/agentphone/react_to_message.ts | 76 ++ apps/sim/tools/agentphone/release_number.ts | 67 ++ apps/sim/tools/agentphone/send_message.ts | 105 +++ apps/sim/tools/agentphone/types.ts | 450 +++++++++++ apps/sim/tools/agentphone/update_contact.ts | 114 +++ .../tools/agentphone/update_conversation.ts | 146 ++++ apps/sim/tools/registry.ts | 46 ++ 34 files changed, 4476 insertions(+), 2 deletions(-) create mode 100644 apps/docs/content/docs/en/tools/agentphone.mdx create mode 100644 apps/sim/blocks/blocks/agentphone.ts create mode 100644 apps/sim/tools/agentphone/create_call.ts create mode 100644 apps/sim/tools/agentphone/create_contact.ts create mode 100644 apps/sim/tools/agentphone/create_number.ts create mode 100644 apps/sim/tools/agentphone/delete_contact.ts create mode 100644 apps/sim/tools/agentphone/get_call.ts create mode 100644 apps/sim/tools/agentphone/get_call_transcript.ts create mode 100644 apps/sim/tools/agentphone/get_contact.ts create mode 100644 apps/sim/tools/agentphone/get_conversation.ts create mode 100644 apps/sim/tools/agentphone/get_conversation_messages.ts create mode 100644 apps/sim/tools/agentphone/get_number_messages.ts create mode 100644 apps/sim/tools/agentphone/get_usage.ts create mode 100644 apps/sim/tools/agentphone/get_usage_daily.ts create mode 100644 apps/sim/tools/agentphone/get_usage_monthly.ts create mode 100644 apps/sim/tools/agentphone/index.ts create mode 100644 apps/sim/tools/agentphone/list_calls.ts create mode 100644 apps/sim/tools/agentphone/list_contacts.ts create mode 100644 apps/sim/tools/agentphone/list_conversations.ts create mode 100644 apps/sim/tools/agentphone/list_numbers.ts create mode 100644 apps/sim/tools/agentphone/react_to_message.ts create mode 100644 apps/sim/tools/agentphone/release_number.ts create mode 100644 apps/sim/tools/agentphone/send_message.ts create mode 100644 apps/sim/tools/agentphone/types.ts create mode 100644 apps/sim/tools/agentphone/update_contact.ts create mode 100644 apps/sim/tools/agentphone/update_conversation.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 061e0c0cdf..35a34a6677 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -28,6 +28,36 @@ export function AgentMailIcon(props: SVGProps) { ) } +export function AgentPhoneIcon(props: SVGProps) { + return ( + + + + + + + + ) +} + export function CrowdStrikeIcon(props: SVGProps) { return ( @@ -4683,9 +4713,16 @@ export function IAMIcon(props: SVGProps) { export function IdentityCenterIcon(props: SVGProps) { return ( - + + + + + + + + diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 13061ba781..f45d31d1d7 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -6,6 +6,7 @@ import type { ComponentType, SVGProps } from 'react' import { A2AIcon, AgentMailIcon, + AgentPhoneIcon, AgiloftIcon, AhrefsIcon, AirtableIcon, @@ -204,6 +205,7 @@ type IconComponent = ComponentType> export const blockTypeToIconMap: Record = { a2a: A2AIcon, agentmail: AgentMailIcon, + agentphone: AgentPhoneIcon, agiloft: AgiloftIcon, ahrefs: AhrefsIcon, airtable: AirtableIcon, diff --git a/apps/docs/content/docs/en/tools/agentphone.mdx b/apps/docs/content/docs/en/tools/agentphone.mdx new file mode 100644 index 0000000000..2fc798710b --- /dev/null +++ b/apps/docs/content/docs/en/tools/agentphone.mdx @@ -0,0 +1,629 @@ +--- +title: AgentPhone +description: Provision numbers, send SMS and iMessage, and place voice calls with AgentPhone +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[AgentPhone](https://agentphone.to/) is an API-first voice and messaging platform built for AI agents. AgentPhone lets you provision real phone numbers, place outbound AI voice calls, send SMS and iMessage, manage conversations and contacts, and monitor usage — all through a simple REST API designed for programmatic access. + +**Why AgentPhone?** +- **Agent-Native Telephony:** Purpose-built for AI agents — provision numbers, place calls, and send messages without carrier contracts or telephony plumbing. +- **Voice + Messaging in One API:** Drive outbound AI voice calls alongside SMS, MMS, and iMessage from the same account and phone numbers. +- **Conversation & Transcript Management:** Every call returns an ordered transcript; every message thread is tracked as a conversation with full history and metadata. +- **Contacts Built In:** Create, search, update, and delete contacts on the account so your agents can reference people by name instead of raw phone numbers. +- **Usage Visibility:** Inspect plan limits, current counts, and daily/monthly aggregation so workflows can stay inside guardrails. + +**Using AgentPhone in Sim** + +Sim's AgentPhone integration connects your agentic workflows directly to AgentPhone using an API key. With 22 operations spanning numbers, calls, conversations, contacts, and usage, you can build powerful voice and messaging automations without writing backend code. + +**Key benefits of using AgentPhone in Sim:** +- **Dynamic number provisioning:** Reserve US or Canadian numbers on the fly — per agent, per customer, or per workflow — and release them when no longer needed. +- **Outbound AI voice calls:** Place calls from an agent with an optional greeting, voice override, or system prompt, and read the full transcript back as structured data once the call completes. +- **Two-way messaging:** Send SMS, MMS, or iMessage, fetch conversation history, and react to incoming iMessages — all from inside your workflow. +- **Contact and metadata management:** Keep an account-level contact list and attach custom JSON metadata to conversations so downstream blocks can branch on state. +- **Operational insight:** Pull current usage stats and daily/monthly breakdowns to monitor consumption and enforce plan limits before making the next call. + +Whether you're building an outbound AI voice agent, running automated SMS follow-ups, managing two-way customer conversations, or monitoring phone usage across your organization, AgentPhone in Sim gives you direct, secure access to the full AgentPhone 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 + +Give your workflow a phone. Provision SMS- and voice-enabled numbers, send messages and tapback reactions, place outbound voice calls, manage conversations and contacts, and track usage — all through a single AgentPhone API key. + + + +## Tools + +### `agentphone_create_call` + +Initiate an outbound voice call from an AgentPhone agent + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `agentId` | string | Yes | Agent that will handle the call | +| `toNumber` | string | Yes | Phone number to call in E.164 format \(e.g. +14155551234\) | +| `fromNumberId` | string | No | Phone number ID to use as caller ID. Must belong to the agent. If omitted, the agent's first assigned number is used. | +| `initialGreeting` | string | No | Optional greeting spoken when the recipient answers | +| `voice` | string | No | Voice ID override for this call \(defaults to the agent's configured voice\) | +| `systemPrompt` | string | No | When provided, uses a built-in LLM for the conversation instead of forwarding to your webhook | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique call identifier | +| `agentId` | string | Agent handling the call | +| `status` | string | Initial call status | +| `toNumber` | string | Destination phone number | +| `fromNumber` | string | Caller ID used for the call | +| `phoneNumberId` | string | ID of the phone number used as caller ID | +| `direction` | string | Call direction \(outbound\) | +| `startedAt` | string | ISO 8601 timestamp | + +### `agentphone_create_contact` + +Create a new contact in AgentPhone + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `phoneNumber` | string | Yes | Phone number in E.164 format \(e.g. +14155551234\) | +| `name` | string | Yes | Contact's full name | +| `email` | string | No | Contact's email address | +| `notes` | string | No | Freeform notes stored on the contact | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Contact ID | +| `phoneNumber` | string | Phone number in E.164 format | +| `name` | string | Contact name | +| `email` | string | Contact email address | +| `notes` | string | Freeform notes | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 update timestamp | + +### `agentphone_create_number` + +Provision a new SMS- and voice-enabled phone number + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `country` | string | No | Two-letter country code \(e.g. US, CA\). Defaults to US. | +| `areaCode` | string | No | Preferred area code \(US/CA only, e.g. "415"\). Best-effort — may be ignored if unavailable. | +| `agentId` | string | No | Optionally attach the number to an agent immediately | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique phone number ID | +| `phoneNumber` | string | Provisioned phone number in E.164 format | +| `country` | string | Two-letter country code | +| `status` | string | Number status \(e.g. active\) | +| `type` | string | Number type \(e.g. sms\) | +| `agentId` | string | Agent the number is attached to | +| `createdAt` | string | ISO 8601 timestamp when the number was created | + +### `agentphone_delete_contact` + +Delete a contact by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `contactId` | string | Yes | Contact ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | ID of the deleted contact | +| `deleted` | boolean | Whether the contact was deleted successfully | + +### `agentphone_get_call` + +Fetch a call and its full transcript + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `callId` | string | Yes | ID of the call to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Call ID | +| `agentId` | string | Agent that handled the call | +| `phoneNumberId` | string | Phone number ID | +| `phoneNumber` | string | Phone number used for the call | +| `fromNumber` | string | Caller phone number | +| `toNumber` | string | Recipient phone number | +| `direction` | string | inbound or outbound | +| `status` | string | Call status | +| `startedAt` | string | ISO 8601 timestamp | +| `endedAt` | string | ISO 8601 timestamp | +| `durationSeconds` | number | Call duration in seconds | +| `lastTranscriptSnippet` | string | Last transcript snippet | +| `recordingUrl` | string | Recording audio URL | +| `recordingAvailable` | boolean | Whether a recording is available | +| `transcripts` | array | Ordered transcript turns for the call | +| ↳ `id` | string | Transcript turn ID | +| ↳ `transcript` | string | User utterance | +| ↳ `confidence` | number | Speech recognition confidence | +| ↳ `response` | string | Agent response \(when available\) | +| ↳ `createdAt` | string | ISO 8601 timestamp | + +### `agentphone_get_call_transcript` + +Get the full ordered transcript for a call + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `callId` | string | Yes | ID of the call to retrieve the transcript for | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `callId` | string | Call ID | +| `transcript` | array | Ordered transcript turns for the call | +| ↳ `role` | string | Speaker role \(user or agent\) | +| ↳ `content` | string | Turn content | +| ↳ `createdAt` | string | ISO 8601 timestamp | + +### `agentphone_get_contact` + +Fetch a single contact by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `contactId` | string | Yes | Contact ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Contact ID | +| `phoneNumber` | string | Phone number in E.164 format | +| `name` | string | Contact name | +| `email` | string | Contact email address | +| `notes` | string | Freeform notes | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 update timestamp | + +### `agentphone_get_conversation` + +Get a conversation along with its recent messages + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `conversationId` | string | Yes | Conversation ID | +| `messageLimit` | number | No | Number of recent messages to include \(default 50, max 100\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Conversation ID | +| `agentId` | string | Agent ID | +| `phoneNumberId` | string | Phone number ID | +| `phoneNumber` | string | Phone number | +| `participant` | string | External participant phone number | +| `lastMessageAt` | string | ISO 8601 timestamp | +| `messageCount` | number | Number of messages in the conversation | +| `metadata` | json | Custom metadata stored on the conversation | +| `createdAt` | string | ISO 8601 timestamp | +| `messages` | array | Recent messages in the conversation | +| ↳ `id` | string | Message ID | +| ↳ `body` | string | Message text | +| ↳ `fromNumber` | string | Sender phone number | +| ↳ `toNumber` | string | Recipient phone number | +| ↳ `direction` | string | inbound or outbound | +| ↳ `channel` | string | sms, mms, or imessage | +| ↳ `mediaUrl` | string | Attached media URL | +| ↳ `receivedAt` | string | ISO 8601 timestamp | + +### `agentphone_get_conversation_messages` + +Get paginated messages for a conversation + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `conversationId` | string | Yes | Conversation ID | +| `limit` | number | No | Number of messages to return \(default 50, max 200\) | +| `before` | string | No | Return messages received before this ISO 8601 timestamp | +| `after` | string | No | Return messages received after this ISO 8601 timestamp | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Messages in the conversation | +| ↳ `id` | string | Message ID | +| ↳ `body` | string | Message text | +| ↳ `fromNumber` | string | Sender phone number | +| ↳ `toNumber` | string | Recipient phone number | +| ↳ `direction` | string | inbound or outbound | +| ↳ `channel` | string | sms, mms, or imessage | +| ↳ `mediaUrl` | string | Attached media URL | +| ↳ `receivedAt` | string | ISO 8601 timestamp | +| `hasMore` | boolean | Whether more messages are available | + +### `agentphone_get_number_messages` + +Fetch messages received on a specific phone number + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `numberId` | string | Yes | ID of the phone number | +| `limit` | number | No | Number of messages to return \(default 50, max 200\) | +| `before` | string | No | Return messages received before this ISO 8601 timestamp | +| `after` | string | No | Return messages received after this ISO 8601 timestamp | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Messages received on the number | +| ↳ `id` | string | Message ID | +| ↳ `from_` | string | Sender phone number \(E.164\) | +| ↳ `to` | string | Recipient phone number \(E.164\) | +| ↳ `body` | string | Message text | +| ↳ `direction` | string | inbound or outbound | +| ↳ `channel` | string | Channel \(sms, mms, etc.\) | +| ↳ `receivedAt` | string | ISO 8601 timestamp | +| `hasMore` | boolean | Whether more messages are available | + +### `agentphone_get_usage` + +Retrieve current usage statistics for the AgentPhone account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `plan` | json | Plan name and limits \(name, limits: numbers/messagesPerMonth/voiceMinutesPerMonth/maxCallDurationMinutes/concurrentCalls\) | +| `numbers` | json | Phone number usage \(used, limit, remaining\) | +| `stats` | json | Usage stats: totalMessages, messagesLast24h/7d/30d, totalCalls, callsLast24h/7d/30d, totalWebhookDeliveries, successfulWebhookDeliveries, failedWebhookDeliveries | +| `periodStart` | string | Billing period start | +| `periodEnd` | string | Billing period end | + +### `agentphone_get_usage_daily` + +Get a daily breakdown of usage (messages, calls, webhooks) for the last N days + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `days` | number | No | Number of days to return \(1-365, default 30\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Daily usage entries | +| ↳ `date` | string | Day \(YYYY-MM-DD\) | +| ↳ `messages` | number | Messages that day | +| ↳ `calls` | number | Calls that day | +| ↳ `webhooks` | number | Webhook deliveries that day | +| `days` | number | Number of days returned | + +### `agentphone_get_usage_monthly` + +Get monthly usage aggregation (messages, calls, webhooks) for the last N months + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `months` | number | No | Number of months to return \(1-24, default 6\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Monthly usage entries | +| ↳ `month` | string | Month \(YYYY-MM\) | +| ↳ `messages` | number | Messages that month | +| ↳ `calls` | number | Calls that month | +| ↳ `webhooks` | number | Webhook deliveries that month | +| `months` | number | Number of months returned | + +### `agentphone_list_calls` + +List voice calls for this AgentPhone account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `limit` | number | No | Number of results to return \(default 20, max 100\) | +| `offset` | number | No | Number of results to skip \(min 0\) | +| `status` | string | No | Filter by status \(completed, in-progress, failed\) | +| `direction` | string | No | Filter by direction \(inbound, outbound\) | +| `type` | string | No | Filter by call type \(pstn, web\) | +| `search` | string | No | Search by phone number \(matches fromNumber or toNumber\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Calls | +| ↳ `id` | string | Call ID | +| ↳ `agentId` | string | Agent that handled the call | +| ↳ `phoneNumberId` | string | Phone number ID used for the call | +| ↳ `phoneNumber` | string | Phone number used for the call | +| ↳ `fromNumber` | string | Caller phone number | +| ↳ `toNumber` | string | Recipient phone number | +| ↳ `direction` | string | inbound or outbound | +| ↳ `status` | string | Call status | +| ↳ `startedAt` | string | ISO 8601 timestamp | +| ↳ `endedAt` | string | ISO 8601 timestamp | +| ↳ `durationSeconds` | number | Call duration in seconds | +| ↳ `lastTranscriptSnippet` | string | Last transcript snippet | +| ↳ `recordingUrl` | string | Recording audio URL | +| ↳ `recordingAvailable` | boolean | Whether a recording is available | +| `hasMore` | boolean | Whether more results are available | +| `total` | number | Total number of matching calls | + +### `agentphone_list_contacts` + +List contacts for this AgentPhone account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `search` | string | No | Filter by name or phone number \(case-insensitive contains\) | +| `limit` | number | No | Number of results to return \(default 50\) | +| `offset` | number | No | Number of results to skip \(min 0\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Contacts | +| ↳ `id` | string | Contact ID | +| ↳ `phoneNumber` | string | Phone number in E.164 format | +| ↳ `name` | string | Contact name | +| ↳ `email` | string | Contact email address | +| ↳ `notes` | string | Freeform notes | +| ↳ `createdAt` | string | ISO 8601 creation timestamp | +| ↳ `updatedAt` | string | ISO 8601 update timestamp | +| `hasMore` | boolean | Whether more results are available | +| `total` | number | Total number of contacts | + +### `agentphone_list_conversations` + +List conversations (message threads) for this AgentPhone account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `limit` | number | No | Number of results to return \(default 20, max 100\) | +| `offset` | number | No | Number of results to skip \(min 0\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Conversations | +| ↳ `id` | string | Conversation ID | +| ↳ `agentId` | string | Agent ID | +| ↳ `phoneNumberId` | string | Phone number ID | +| ↳ `phoneNumber` | string | Phone number | +| ↳ `participant` | string | External participant phone number | +| ↳ `lastMessageAt` | string | ISO 8601 timestamp | +| ↳ `lastMessagePreview` | string | Last message preview | +| ↳ `messageCount` | number | Number of messages in the conversation | +| ↳ `metadata` | json | Custom metadata stored on the conversation | +| ↳ `createdAt` | string | ISO 8601 timestamp | +| `hasMore` | boolean | Whether more results are available | +| `total` | number | Total number of conversations | + +### `agentphone_list_numbers` + +List all phone numbers provisioned for this AgentPhone account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `limit` | number | No | Number of results to return \(default 20, max 100\) | +| `offset` | number | No | Number of results to skip \(min 0\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | array | Phone numbers | +| ↳ `id` | string | Phone number ID | +| ↳ `phoneNumber` | string | Phone number in E.164 format | +| ↳ `country` | string | Two-letter country code | +| ↳ `status` | string | Number status | +| ↳ `type` | string | Number type \(e.g. sms\) | +| ↳ `agentId` | string | Attached agent ID | +| ↳ `createdAt` | string | ISO 8601 creation timestamp | +| `hasMore` | boolean | Whether more results are available | +| `total` | number | Total number of phone numbers | + +### `agentphone_react_to_message` + +Send an iMessage tapback reaction to a message (iMessage only) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `messageId` | string | Yes | ID of the message to react to | +| `reaction` | string | Yes | Reaction type: love, like, dislike, laugh, emphasize, or question | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Reaction ID | +| `reactionType` | string | Reaction type applied | +| `messageId` | string | ID of the message that was reacted to | +| `channel` | string | Channel \(imessage\) | + +### `agentphone_release_number` + +Release (delete) a phone number. This action is irreversible. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `numberId` | string | Yes | ID of the phone number to release | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | ID of the released phone number | +| `released` | boolean | Whether the number was released successfully | + +### `agentphone_send_message` + +Send an outbound SMS or iMessage from an AgentPhone agent + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `agentId` | string | Yes | Agent sending the message | +| `toNumber` | string | Yes | Recipient phone number in E.164 format \(e.g. +14155551234\) | +| `body` | string | Yes | Message text to send | +| `mediaUrl` | string | No | Optional URL of an image, video, or file to attach | +| `numberId` | string | No | Phone number ID to send from. If omitted, the agent's first assigned number is used. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Message ID | +| `status` | string | Delivery status | +| `channel` | string | sms, mms, or imessage | +| `fromNumber` | string | Sender phone number | +| `toNumber` | string | Recipient phone number | + +### `agentphone_update_contact` + +Update a contact + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `contactId` | string | Yes | Contact ID | +| `phoneNumber` | string | No | New phone number in E.164 format | +| `name` | string | No | New contact name | +| `email` | string | No | New email address | +| `notes` | string | No | New freeform notes | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Contact ID | +| `phoneNumber` | string | Phone number in E.164 format | +| `name` | string | Contact name | +| `email` | string | Contact email address | +| `notes` | string | Freeform notes | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 update timestamp | + +### `agentphone_update_conversation` + +Update conversation metadata (stored state). Pass null to clear existing metadata. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | AgentPhone API key | +| `conversationId` | string | Yes | Conversation ID | +| `metadata` | json | No | Custom key-value metadata to store on the conversation. Pass null to clear existing metadata. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Conversation ID | +| `agentId` | string | Agent ID | +| `phoneNumberId` | string | Phone number ID | +| `phoneNumber` | string | Phone number | +| `participant` | string | External participant phone number | +| `lastMessageAt` | string | ISO 8601 timestamp | +| `messageCount` | number | Number of messages | +| `metadata` | json | Custom metadata stored on the conversation | +| `createdAt` | string | ISO 8601 timestamp | +| `messages` | array | Messages in the conversation | +| ↳ `id` | string | Message ID | +| ↳ `body` | string | Message body | +| ↳ `fromNumber` | string | Sender phone number | +| ↳ `toNumber` | string | Recipient phone number | +| ↳ `direction` | string | inbound or outbound | +| ↳ `channel` | string | Channel \(sms, mms, etc.\) | +| ↳ `mediaUrl` | string | Media URL if any | +| ↳ `receivedAt` | string | ISO 8601 timestamp | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 91d84fa1e9..f7ce46bd76 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -3,6 +3,7 @@ "index", "a2a", "agentmail", + "agentphone", "agiloft", "ahrefs", "airtable", diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index f5ac6f926a..8417728536 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -6,6 +6,7 @@ import type { ComponentType, SVGProps } from 'react' import { A2AIcon, AgentMailIcon, + AgentPhoneIcon, AgiloftIcon, AhrefsIcon, AirtableIcon, @@ -204,6 +205,7 @@ type IconComponent = ComponentType> export const blockTypeToIconMap: Record = { a2a: A2AIcon, agentmail: AgentMailIcon, + agentphone: AgentPhoneIcon, agiloft: AgiloftIcon, ahrefs: AhrefsIcon, airtable: AirtableIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 0adc9ac930..8accfa7035 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -208,6 +208,113 @@ "integrationTypes": ["email", "communication"], "tags": ["messaging"] }, + { + "type": "agentphone", + "slug": "agentphone", + "name": "AgentPhone", + "description": "Provision numbers, send SMS and iMessage, and place voice calls with AgentPhone", + "longDescription": "Give your workflow a phone. Provision SMS- and voice-enabled numbers, send messages and tapback reactions, place outbound voice calls, manage conversations and contacts, and track usage — all through a single AgentPhone API key.", + "bgColor": "linear-gradient(135deg, #050505 0%, #003d20 55%, #00e676 100%)", + "iconName": "AgentPhoneIcon", + "docsUrl": "https://docs.sim.ai/tools/agentphone", + "operations": [ + { + "name": "Create Number", + "description": "Provision a new SMS- and voice-enabled phone number" + }, + { + "name": "List Numbers", + "description": "List all phone numbers provisioned for this AgentPhone account" + }, + { + "name": "Release Number", + "description": "Release (delete) a phone number. This action is irreversible." + }, + { + "name": "Get Number Messages", + "description": "Fetch messages received on a specific phone number" + }, + { + "name": "Create Call", + "description": "Initiate an outbound voice call from an AgentPhone agent" + }, + { + "name": "List Calls", + "description": "List voice calls for this AgentPhone account" + }, + { + "name": "Get Call", + "description": "Fetch a call and its full transcript" + }, + { + "name": "Get Call Transcript", + "description": "Get the full ordered transcript for a call" + }, + { + "name": "List Conversations", + "description": "List conversations (message threads) for this AgentPhone account" + }, + { + "name": "Get Conversation", + "description": "Get a conversation along with its recent messages" + }, + { + "name": "Update Conversation", + "description": "Update conversation metadata (stored state). Pass null to clear existing metadata." + }, + { + "name": "Get Conversation Messages", + "description": "Get paginated messages for a conversation" + }, + { + "name": "Send Message", + "description": "Send an outbound SMS or iMessage from an AgentPhone agent" + }, + { + "name": "React to Message", + "description": "Send an iMessage tapback reaction to a message (iMessage only)" + }, + { + "name": "Create Contact", + "description": "Create a new contact in AgentPhone" + }, + { + "name": "List Contacts", + "description": "List contacts for this AgentPhone account" + }, + { + "name": "Get Contact", + "description": "Fetch a single contact by ID" + }, + { + "name": "Update Contact", + "description": "Update a contact" + }, + { + "name": "Delete Contact", + "description": "Delete a contact by ID" + }, + { + "name": "Get Usage", + "description": "Retrieve current usage statistics for the AgentPhone account" + }, + { + "name": "Get Daily Usage", + "description": "Get a daily breakdown of usage (messages, calls, webhooks) for the last N days" + }, + { + "name": "Get Monthly Usage", + "description": "Get monthly usage aggregation (messages, calls, webhooks) for the last N months" + } + ], + "operationCount": 22, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationTypes": ["communication", "developer-tools"], + "tags": ["messaging", "automation"] + }, { "type": "agiloft", "slug": "agiloft", diff --git a/apps/sim/blocks/blocks/agentphone.ts b/apps/sim/blocks/blocks/agentphone.ts new file mode 100644 index 0000000000..312b1badf2 --- /dev/null +++ b/apps/sim/blocks/blocks/agentphone.ts @@ -0,0 +1,739 @@ +import { AgentPhoneIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +const CONVERSATION_OPS = [ + 'get_conversation', + 'update_conversation', + 'get_conversation_messages', +] as const + +const CONTACT_ID_OPS = ['get_contact', 'update_contact', 'delete_contact'] as const + +const CALL_ID_OPS = ['get_call', 'get_call_transcript'] as const + +const NUMBER_ID_OPS = ['release_number', 'get_number_messages'] as const + +const OFFSET_LIMIT_OPS = [ + 'list_numbers', + 'list_calls', + 'list_conversations', + 'list_contacts', +] as const + +const CURSOR_MESSAGE_OPS = ['get_number_messages', 'get_conversation_messages'] as const + +export const AgentPhoneBlock: BlockConfig = { + type: 'agentphone', + name: 'AgentPhone', + description: 'Provision numbers, send SMS and iMessage, and place voice calls with AgentPhone', + longDescription: + 'Give your workflow a phone. Provision SMS- and voice-enabled numbers, send messages and tapback reactions, place outbound voice calls, manage conversations and contacts, and track usage — all through a single AgentPhone API key.', + docsLink: 'https://docs.sim.ai/tools/agentphone', + category: 'tools', + integrationType: IntegrationType.Communication, + tags: ['messaging', 'automation'], + bgColor: 'linear-gradient(135deg, #050505 0%, #003d20 55%, #00e676 100%)', + icon: AgentPhoneIcon, + authMode: AuthMode.ApiKey, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Number', id: 'create_number' }, + { label: 'List Numbers', id: 'list_numbers' }, + { label: 'Release Number', id: 'release_number' }, + { label: 'Get Number Messages', id: 'get_number_messages' }, + { label: 'Create Call', id: 'create_call' }, + { label: 'List Calls', id: 'list_calls' }, + { label: 'Get Call', id: 'get_call' }, + { label: 'Get Call Transcript', id: 'get_call_transcript' }, + { label: 'List Conversations', id: 'list_conversations' }, + { label: 'Get Conversation', id: 'get_conversation' }, + { label: 'Update Conversation', id: 'update_conversation' }, + { label: 'Get Conversation Messages', id: 'get_conversation_messages' }, + { label: 'Send Message', id: 'send_message' }, + { label: 'React to Message', id: 'react_to_message' }, + { label: 'Create Contact', id: 'create_contact' }, + { label: 'List Contacts', id: 'list_contacts' }, + { label: 'Get Contact', id: 'get_contact' }, + { label: 'Update Contact', id: 'update_contact' }, + { label: 'Delete Contact', id: 'delete_contact' }, + { label: 'Get Usage', id: 'get_usage' }, + { label: 'Get Daily Usage', id: 'get_usage_daily' }, + { label: 'Get Monthly Usage', id: 'get_usage_monthly' }, + ], + value: () => 'create_number', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your AgentPhone API key', + required: true, + password: true, + }, + + // Numbers - Create + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: [ + { label: 'United States (US)', id: 'US' }, + { label: 'Canada (CA)', id: 'CA' }, + ], + value: () => 'US', + condition: { field: 'operation', value: 'create_number' }, + }, + { + id: 'areaCode', + title: 'Area Code', + type: 'short-input', + placeholder: '415 (US/CA only, optional)', + condition: { field: 'operation', value: 'create_number' }, + mode: 'advanced', + }, + { + id: 'attachAgentId', + title: 'Agent ID', + type: 'short-input', + placeholder: 'Attach the number to this agent (optional)', + condition: { field: 'operation', value: 'create_number' }, + mode: 'advanced', + }, + + // Numbers - shared numberId + { + id: 'numberId', + title: 'Phone Number ID', + type: 'short-input', + placeholder: 'num_xxx', + condition: { field: 'operation', value: [...NUMBER_ID_OPS] }, + required: { field: 'operation', value: [...NUMBER_ID_OPS] }, + }, + + // Calls - Create + { + id: 'callAgentId', + title: 'Agent ID', + type: 'short-input', + placeholder: 'agt_xxx', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + }, + { + id: 'toNumberCall', + title: 'To Phone Number', + type: 'short-input', + placeholder: '+14155551234', + condition: { field: 'operation', value: 'create_call' }, + required: { field: 'operation', value: 'create_call' }, + }, + { + id: 'fromNumberId', + title: 'From Phone Number ID', + type: 'short-input', + placeholder: "num_xxx (defaults to the agent's first number)", + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + }, + { + id: 'initialGreeting', + title: 'Initial Greeting', + type: 'long-input', + placeholder: 'Hi, this is Acme Corp calling about your recent order.', + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a short, natural-sounding phone greeting for an AI agent to speak when the recipient answers. Keep it under 2 sentences. Return ONLY the greeting text - no explanations, no extra text.', + placeholder: 'Describe the greeting tone and purpose...', + }, + }, + { + id: 'voice', + title: 'Voice', + type: 'short-input', + placeholder: "Polly.Amy (defaults to the agent's configured voice)", + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + }, + { + id: 'systemPrompt', + title: 'System Prompt', + type: 'long-input', + placeholder: 'You are a friendly support agent from Acme Corp...', + condition: { field: 'operation', value: 'create_call' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a concise system prompt for an AI phone agent. Describe personality, objective, and constraints clearly. Return ONLY the prompt text - no explanations, no extra text.', + placeholder: 'Describe the agent persona and objective...', + }, + }, + + // Calls - shared callId + { + id: 'callId', + title: 'Call ID', + type: 'short-input', + placeholder: 'call_xxx', + condition: { field: 'operation', value: [...CALL_ID_OPS] }, + required: { field: 'operation', value: [...CALL_ID_OPS] }, + }, + + // Calls - list filters + { + id: 'callsStatus', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Completed', id: 'completed' }, + { label: 'In Progress', id: 'in-progress' }, + { label: 'Failed', id: 'failed' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_calls' }, + mode: 'advanced', + }, + { + id: 'callsDirection', + title: 'Direction Filter', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Inbound', id: 'inbound' }, + { label: 'Outbound', id: 'outbound' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_calls' }, + mode: 'advanced', + }, + { + id: 'callsType', + title: 'Type Filter', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'PSTN', id: 'pstn' }, + { label: 'Web', id: 'web' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_calls' }, + mode: 'advanced', + }, + { + id: 'callsSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Phone number to match against fromNumber or toNumber', + condition: { field: 'operation', value: 'list_calls' }, + mode: 'advanced', + }, + + // Conversations - shared conversationId + { + id: 'conversationId', + title: 'Conversation ID', + type: 'short-input', + placeholder: 'conv_xxx', + condition: { field: 'operation', value: [...CONVERSATION_OPS] }, + required: { field: 'operation', value: [...CONVERSATION_OPS] }, + }, + { + id: 'messageLimit', + title: 'Message Limit', + type: 'short-input', + placeholder: '50 (max 100)', + condition: { field: 'operation', value: 'get_conversation' }, + mode: 'advanced', + }, + { + id: 'metadata', + title: 'Metadata', + type: 'long-input', + placeholder: '{"customerName":"Jane","orderId":"ORD-12345"}', + condition: { field: 'operation', value: 'update_conversation' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a valid JSON object to store on the conversation as metadata. Use flat string/number values where possible. Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the fields to store (customer name, order ID, topic)...', + }, + }, + + // Messages - Send + { + id: 'sendAgentId', + title: 'Agent ID', + type: 'short-input', + placeholder: 'agt_xxx', + condition: { field: 'operation', value: 'send_message' }, + required: { field: 'operation', value: 'send_message' }, + }, + { + id: 'toNumberMessage', + title: 'To Phone Number', + type: 'short-input', + placeholder: '+14155551234', + condition: { field: 'operation', value: 'send_message' }, + required: { field: 'operation', value: 'send_message' }, + }, + { + id: 'messageBody', + title: 'Message Body', + type: 'long-input', + placeholder: 'Hi! Your appointment is confirmed for tomorrow at 3 PM.', + condition: { field: 'operation', value: 'send_message' }, + required: { field: 'operation', value: 'send_message' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a friendly, concise SMS or iMessage body. Keep it under 160 characters where possible. Return ONLY the message text - no explanations, no extra text.', + placeholder: 'Describe the message purpose and tone...', + }, + }, + { + id: 'mediaUrl', + title: 'Media URL', + type: 'short-input', + placeholder: 'https://cdn.example.com/image.png (optional)', + condition: { field: 'operation', value: 'send_message' }, + mode: 'advanced', + }, + { + id: 'sendNumberId', + title: 'From Phone Number ID', + type: 'short-input', + placeholder: "num_xxx (defaults to the agent's first number)", + condition: { field: 'operation', value: 'send_message' }, + mode: 'advanced', + }, + + // Messages - React + { + id: 'messageId', + title: 'Message ID', + type: 'short-input', + placeholder: 'msg_xxx', + condition: { field: 'operation', value: 'react_to_message' }, + required: { field: 'operation', value: 'react_to_message' }, + }, + { + id: 'reaction', + title: 'Reaction', + type: 'dropdown', + options: [ + { label: 'Love', id: 'love' }, + { label: 'Like', id: 'like' }, + { label: 'Dislike', id: 'dislike' }, + { label: 'Laugh', id: 'laugh' }, + { label: 'Emphasize', id: 'emphasize' }, + { label: 'Question', id: 'question' }, + ], + value: () => 'love', + condition: { field: 'operation', value: 'react_to_message' }, + required: { field: 'operation', value: 'react_to_message' }, + }, + + // Contacts - Create / Update shared fields + { + id: 'contactPhoneNumber', + title: 'Phone Number', + type: 'short-input', + placeholder: '+14155551234', + condition: { field: 'operation', value: ['create_contact', 'update_contact'] }, + required: { field: 'operation', value: 'create_contact' }, + }, + { + id: 'contactName', + title: 'Name', + type: 'short-input', + placeholder: 'Alice Johnson', + condition: { field: 'operation', value: ['create_contact', 'update_contact'] }, + required: { field: 'operation', value: 'create_contact' }, + }, + { + id: 'contactEmail', + title: 'Email', + type: 'short-input', + placeholder: 'alice@example.com (optional)', + condition: { field: 'operation', value: ['create_contact', 'update_contact'] }, + mode: 'advanced', + }, + { + id: 'contactNotes', + title: 'Notes', + type: 'long-input', + placeholder: 'Freeform notes (optional)', + condition: { field: 'operation', value: ['create_contact', 'update_contact'] }, + mode: 'advanced', + }, + + // Contacts - shared contactId + { + id: 'contactId', + title: 'Contact ID', + type: 'short-input', + placeholder: 'contact_xxx', + condition: { field: 'operation', value: [...CONTACT_ID_OPS] }, + required: { field: 'operation', value: [...CONTACT_ID_OPS] }, + }, + { + id: 'contactsSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Filter by name or phone number', + condition: { field: 'operation', value: 'list_contacts' }, + mode: 'advanced', + }, + + // Pagination - offset/limit for list_* operations + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '20 (max 100)', + condition: { field: 'operation', value: [...OFFSET_LIMIT_OPS] }, + mode: 'advanced', + }, + { + id: 'offset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: [...OFFSET_LIMIT_OPS] }, + mode: 'advanced', + }, + + // Pagination - limit/before/after for message endpoints + { + id: 'messagesLimit', + title: 'Limit', + type: 'short-input', + placeholder: '50 (max 200)', + condition: { field: 'operation', value: [...CURSOR_MESSAGE_OPS] }, + mode: 'advanced', + }, + { + id: 'before', + title: 'Before', + type: 'short-input', + placeholder: 'ISO 8601 timestamp (e.g. 2025-01-15T12:00:00Z)', + condition: { field: 'operation', value: [...CURSOR_MESSAGE_OPS] }, + mode: 'advanced', + wandConfig: { + enabled: true, + generationType: 'timestamp', + prompt: + 'Convert the natural-language time description to an ISO 8601 timestamp (UTC). Return ONLY the timestamp - no explanations, no extra text.', + placeholder: 'Describe the cutoff time (e.g. 2 hours ago)...', + }, + }, + { + id: 'after', + title: 'After', + type: 'short-input', + placeholder: 'ISO 8601 timestamp (e.g. 2025-01-15T12:00:00Z)', + condition: { field: 'operation', value: [...CURSOR_MESSAGE_OPS] }, + mode: 'advanced', + wandConfig: { + enabled: true, + generationType: 'timestamp', + prompt: + 'Convert the natural-language time description to an ISO 8601 timestamp (UTC). Return ONLY the timestamp - no explanations, no extra text.', + placeholder: 'Describe the start time (e.g. 2 hours ago)...', + }, + }, + + // Usage + { + id: 'usageDays', + title: 'Days', + type: 'short-input', + placeholder: '30 (1-365)', + condition: { field: 'operation', value: 'get_usage_daily' }, + mode: 'advanced', + }, + { + id: 'usageMonths', + title: 'Months', + type: 'short-input', + placeholder: '6 (1-24)', + condition: { field: 'operation', value: 'get_usage_monthly' }, + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'agentphone_create_call', + 'agentphone_create_contact', + 'agentphone_create_number', + 'agentphone_delete_contact', + 'agentphone_get_call', + 'agentphone_get_call_transcript', + 'agentphone_get_contact', + 'agentphone_get_conversation', + 'agentphone_get_conversation_messages', + 'agentphone_get_number_messages', + 'agentphone_get_usage', + 'agentphone_get_usage_daily', + 'agentphone_get_usage_monthly', + 'agentphone_list_calls', + 'agentphone_list_contacts', + 'agentphone_list_conversations', + 'agentphone_list_numbers', + 'agentphone_react_to_message', + 'agentphone_release_number', + 'agentphone_send_message', + 'agentphone_update_contact', + 'agentphone_update_conversation', + ], + config: { + tool: (params) => `agentphone_${params.operation || 'create_number'}`, + params: (params) => { + const { + operation, + attachAgentId, + callAgentId, + toNumberCall, + toNumberMessage, + sendAgentId, + sendNumberId, + messageBody, + contactPhoneNumber, + contactName, + contactEmail, + contactNotes, + contactsSearch, + callsStatus, + callsDirection, + callsType, + callsSearch, + messageLimit, + messagesLimit, + limit, + offset, + usageDays, + usageMonths, + metadata, + ...rest + } = params + + if (operation === 'create_number' && attachAgentId) { + rest.agentId = attachAgentId + } + + if (operation === 'create_call') { + if (callAgentId) rest.agentId = callAgentId + if (toNumberCall) rest.toNumber = toNumberCall + } + + if (operation === 'send_message') { + if (sendAgentId) rest.agentId = sendAgentId + if (toNumberMessage) rest.toNumber = toNumberMessage + if (sendNumberId) rest.numberId = sendNumberId + if (messageBody !== undefined) rest.body = messageBody + } + + if (['create_contact', 'update_contact'].includes(operation as string)) { + if (contactPhoneNumber !== undefined) rest.phoneNumber = contactPhoneNumber + if (contactName !== undefined) rest.name = contactName + if (contactEmail !== undefined) rest.email = contactEmail + if (contactNotes !== undefined) rest.notes = contactNotes + } + + if (operation === 'list_contacts' && contactsSearch !== undefined) { + rest.search = contactsSearch + } + + if (operation === 'list_calls') { + if (callsStatus) rest.status = callsStatus + if (callsDirection) rest.direction = callsDirection + if (callsType) rest.type = callsType + if (callsSearch) rest.search = callsSearch + } + + if (operation === 'get_conversation' && messageLimit !== undefined && messageLimit !== '') { + rest.messageLimit = Number(messageLimit) + } + + if ( + (operation === 'get_number_messages' || operation === 'get_conversation_messages') && + messagesLimit !== undefined && + messagesLimit !== '' + ) { + rest.limit = Number(messagesLimit) + } + + if ( + operation !== 'get_number_messages' && + operation !== 'get_conversation_messages' && + limit !== undefined && + limit !== '' + ) { + rest.limit = Number(limit) + } + + if (offset !== undefined && offset !== '') { + rest.offset = Number(offset) + } + + if (operation === 'get_usage_daily' && usageDays !== undefined && usageDays !== '') { + rest.days = Number(usageDays) + } + + if (operation === 'get_usage_monthly' && usageMonths !== undefined && usageMonths !== '') { + rest.months = Number(usageMonths) + } + + if (operation === 'update_conversation' && metadata !== undefined) { + if (metadata === null || metadata === '') { + rest.metadata = null + } else if (typeof metadata === 'string') { + try { + rest.metadata = JSON.parse(metadata) + } catch { + rest.metadata = metadata + } + } else { + rest.metadata = metadata + } + } + + return rest + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'AgentPhone API key' }, + country: { type: 'string', description: 'Country code (US or CA)' }, + areaCode: { type: 'string', description: 'Preferred area code (US/CA only)' }, + attachAgentId: { type: 'string', description: 'Agent ID to attach on number provisioning' }, + numberId: { type: 'string', description: 'Phone number ID' }, + callAgentId: { type: 'string', description: 'Agent ID to place the call from' }, + toNumberCall: { type: 'string', description: 'Destination phone number for the call' }, + fromNumberId: { type: 'string', description: 'Phone number ID to use as caller ID' }, + initialGreeting: { type: 'string', description: 'Optional initial greeting' }, + voice: { type: 'string', description: 'Voice override' }, + systemPrompt: { type: 'string', description: 'System prompt for built-in LLM' }, + callId: { type: 'string', description: 'Call ID' }, + callsStatus: { type: 'string', description: 'Filter calls by status' }, + callsDirection: { type: 'string', description: 'Filter calls by direction' }, + callsType: { type: 'string', description: 'Filter calls by type (pstn or web)' }, + callsSearch: { type: 'string', description: 'Search calls by phone number' }, + conversationId: { type: 'string', description: 'Conversation ID' }, + messageLimit: { type: 'string', description: 'Number of messages to include' }, + metadata: { type: 'string', description: 'JSON metadata object to store on conversation' }, + sendAgentId: { type: 'string', description: 'Agent ID sending the message' }, + toNumberMessage: { type: 'string', description: 'Recipient phone number' }, + messageBody: { type: 'string', description: 'Message body' }, + mediaUrl: { type: 'string', description: 'Media URL to attach' }, + sendNumberId: { type: 'string', description: 'Phone number ID to send from' }, + messageId: { type: 'string', description: 'Message ID' }, + reaction: { type: 'string', description: 'Reaction type' }, + contactPhoneNumber: { type: 'string', description: 'Contact phone number' }, + contactName: { type: 'string', description: 'Contact name' }, + contactEmail: { type: 'string', description: 'Contact email' }, + contactNotes: { type: 'string', description: 'Contact notes' }, + contactId: { type: 'string', description: 'Contact ID' }, + contactsSearch: { type: 'string', description: 'Contact search filter' }, + limit: { type: 'string', description: 'Pagination limit' }, + offset: { type: 'string', description: 'Pagination offset' }, + messagesLimit: { type: 'string', description: 'Messages pagination limit' }, + before: { type: 'string', description: 'Cursor: ISO 8601 upper bound' }, + after: { type: 'string', description: 'Cursor: ISO 8601 lower bound' }, + usageDays: { type: 'string', description: 'Number of days for daily usage' }, + usageMonths: { type: 'string', description: 'Number of months for monthly usage' }, + }, + + outputs: { + id: { type: 'string', description: 'ID of the primary resource returned' }, + phoneNumber: { type: 'string', description: 'Phone number in E.164 format' }, + country: { type: 'string', description: 'Country code' }, + status: { type: 'string', description: 'Status field (varies by operation)' }, + type: { type: 'string', description: 'Resource type (e.g. sms)' }, + agentId: { type: 'string', description: 'Agent ID associated with the resource' }, + phoneNumberId: { type: 'string', description: 'Phone number ID' }, + fromNumber: { type: 'string', description: 'Originating phone number' }, + toNumber: { type: 'string', description: 'Destination phone number' }, + direction: { type: 'string', description: 'inbound or outbound' }, + startedAt: { type: 'string', description: 'ISO 8601 start timestamp' }, + endedAt: { type: 'string', description: 'ISO 8601 end timestamp' }, + durationSeconds: { type: 'number', description: 'Call duration in seconds' }, + lastTranscriptSnippet: { type: 'string', description: 'Last transcript snippet' }, + recordingUrl: { type: 'string', description: 'Recording audio URL' }, + recordingAvailable: { type: 'boolean', description: 'Whether a recording is available' }, + transcripts: { + type: 'json', + description: + 'Ordered transcript turns on call detail: [{id, transcript, confidence, response, createdAt}]', + }, + transcript: { + type: 'json', + description: 'Flat transcript entries from the transcript endpoint: [{role, content}]', + }, + callId: { type: 'string', description: 'Call ID' }, + channel: { type: 'string', description: 'Message channel: sms, mms, or imessage' }, + from_: { type: 'string', description: 'Sender phone number on a number message' }, + body: { type: 'string', description: 'Message body text' }, + mediaUrl: { type: 'string', description: 'Attached media URL' }, + receivedAt: { type: 'string', description: 'ISO 8601 timestamp' }, + participant: { type: 'string', description: 'External participant phone number' }, + lastMessageAt: { type: 'string', description: 'ISO 8601 timestamp' }, + lastMessagePreview: { + type: 'string', + description: 'Last message preview (list_conversations only)', + }, + messageCount: { type: 'number', description: 'Number of messages in a conversation' }, + metadata: { type: 'json', description: 'Custom metadata stored on a conversation' }, + messages: { + type: 'json', + description: + 'Conversation messages: [{id, body, fromNumber, toNumber, direction, channel, mediaUrl, receivedAt}]', + }, + reactionType: { type: 'string', description: 'Reaction type applied' }, + messageId: { type: 'string', description: 'Message ID' }, + name: { type: 'string', description: 'Contact name' }, + email: { type: 'string', description: 'Contact email' }, + notes: { type: 'string', description: 'Contact notes' }, + createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' }, + updatedAt: { type: 'string', description: 'ISO 8601 update timestamp' }, + data: { + type: 'json', + description: 'Array of items returned by list operations (shape varies by operation)', + }, + hasMore: { type: 'boolean', description: 'Whether more results are available' }, + total: { type: 'number', description: 'Total number of matching items' }, + released: { type: 'boolean', description: 'Whether a phone number was released' }, + deleted: { type: 'boolean', description: 'Whether a contact was deleted' }, + plan: { + type: 'json', + description: + 'Usage plan (name, limits: numbers/messagesPerMonth/voiceMinutesPerMonth/maxCallDurationMinutes/concurrentCalls)', + }, + numbers: { + type: 'json', + description: 'Number usage breakdown (used, limit, remaining)', + }, + stats: { + type: 'json', + description: + 'Usage stats: totalMessages/messagesLast{24h,7d,30d}, totalCalls/callsLast{24h,7d,30d}, webhook delivery counts', + }, + periodStart: { type: 'string', description: 'Usage period start' }, + periodEnd: { type: 'string', description: 'Usage period end' }, + days: { type: 'number', description: 'Days returned for daily usage' }, + months: { type: 'number', description: 'Months returned for monthly usage' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index bb5eddb875..3590122684 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -1,6 +1,7 @@ import { A2ABlock } from '@/blocks/blocks/a2a' import { AgentBlock } from '@/blocks/blocks/agent' import { AgentMailBlock } from '@/blocks/blocks/agentmail' +import { AgentPhoneBlock } from '@/blocks/blocks/agentphone' import { AgiloftBlock } from '@/blocks/blocks/agiloft' import { AhrefsBlock } from '@/blocks/blocks/ahrefs' import { AirtableBlock } from '@/blocks/blocks/airtable' @@ -233,6 +234,7 @@ export const registry: Record = { a2a: A2ABlock, agent: AgentBlock, agentmail: AgentMailBlock, + agentphone: AgentPhoneBlock, agiloft: AgiloftBlock, ahrefs: AhrefsBlock, airtable: AirtableBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 69840638eb..35a34a6677 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -28,6 +28,36 @@ export function AgentMailIcon(props: SVGProps) { ) } +export function AgentPhoneIcon(props: SVGProps) { + return ( + + + + + + + + ) +} + export function CrowdStrikeIcon(props: SVGProps) { return ( diff --git a/apps/sim/tools/agentphone/create_call.ts b/apps/sim/tools/agentphone/create_call.ts new file mode 100644 index 0000000000..ac45ee404e --- /dev/null +++ b/apps/sim/tools/agentphone/create_call.ts @@ -0,0 +1,132 @@ +import type { + AgentPhoneCreateCallParams, + AgentPhoneCreateCallResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneCreateCallTool: ToolConfig< + AgentPhoneCreateCallParams, + AgentPhoneCreateCallResult +> = { + id: 'agentphone_create_call', + name: 'Create Outbound Call', + description: 'Initiate an outbound voice call from an AgentPhone agent', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + agentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Agent that will handle the call', + }, + toNumber: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Phone number to call in E.164 format (e.g. +14155551234)', + }, + fromNumberId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + "Phone number ID to use as caller ID. Must belong to the agent. If omitted, the agent's first assigned number is used.", + }, + initialGreeting: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional greeting spoken when the recipient answers', + }, + voice: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: "Voice ID override for this call (defaults to the agent's configured voice)", + }, + systemPrompt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'When provided, uses a built-in LLM for the conversation instead of forwarding to your webhook', + }, + }, + + request: { + url: 'https://api.agentphone.to/v1/calls', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + agentId: params.agentId, + toNumber: params.toNumber, + } + if (params.fromNumberId) body.fromNumberId = params.fromNumberId + if (params.initialGreeting) body.initialGreeting = params.initialGreeting + if (params.voice) body.voice = params.voice + if (params.systemPrompt) body.systemPrompt = params.systemPrompt + return body + }, + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to create call', + output: { + id: '', + agentId: null, + status: null, + toNumber: null, + fromNumber: null, + phoneNumberId: null, + direction: null, + startedAt: null, + }, + } + } + + return { + success: true, + output: { + id: data.id ?? data.callId ?? '', + agentId: data.agentId ?? null, + status: data.status ?? null, + toNumber: data.toNumber ?? null, + fromNumber: data.fromNumber ?? null, + phoneNumberId: data.phoneNumberId ?? null, + direction: data.direction ?? null, + startedAt: data.startedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique call identifier' }, + agentId: { type: 'string', description: 'Agent handling the call', optional: true }, + status: { type: 'string', description: 'Initial call status', optional: true }, + toNumber: { type: 'string', description: 'Destination phone number', optional: true }, + fromNumber: { type: 'string', description: 'Caller ID used for the call', optional: true }, + phoneNumberId: { + type: 'string', + description: 'ID of the phone number used as caller ID', + optional: true, + }, + direction: { type: 'string', description: 'Call direction (outbound)', optional: true }, + startedAt: { type: 'string', description: 'ISO 8601 timestamp', optional: true }, + }, +} diff --git a/apps/sim/tools/agentphone/create_contact.ts b/apps/sim/tools/agentphone/create_contact.ts new file mode 100644 index 0000000000..d83122f055 --- /dev/null +++ b/apps/sim/tools/agentphone/create_contact.ts @@ -0,0 +1,109 @@ +import type { + AgentPhoneCreateContactParams, + AgentPhoneCreateContactResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneCreateContactTool: ToolConfig< + AgentPhoneCreateContactParams, + AgentPhoneCreateContactResult +> = { + id: 'agentphone_create_contact', + name: 'Create Contact', + description: 'Create a new contact in AgentPhone', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + phoneNumber: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Phone number in E.164 format (e.g. +14155551234)', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: "Contact's full name", + }, + email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: "Contact's email address", + }, + notes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Freeform notes stored on the contact', + }, + }, + + request: { + url: 'https://api.agentphone.to/v1/contacts', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + phoneNumber: params.phoneNumber, + name: params.name, + } + if (params.email) body.email = params.email + if (params.notes) body.notes = params.notes + return body + }, + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to create contact', + output: { + id: '', + phoneNumber: '', + name: '', + email: null, + notes: null, + createdAt: '', + updatedAt: '', + }, + } + } + + return { + success: true, + output: { + id: data.id ?? '', + phoneNumber: data.phoneNumber ?? '', + name: data.name ?? '', + email: data.email ?? null, + notes: data.notes ?? null, + createdAt: data.createdAt ?? '', + updatedAt: data.updatedAt ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Contact ID' }, + phoneNumber: { type: 'string', description: 'Phone number in E.164 format' }, + name: { type: 'string', description: 'Contact name' }, + email: { type: 'string', description: 'Contact email address', optional: true }, + notes: { type: 'string', description: 'Freeform notes', optional: true }, + createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' }, + updatedAt: { type: 'string', description: 'ISO 8601 update timestamp' }, + }, +} diff --git a/apps/sim/tools/agentphone/create_number.ts b/apps/sim/tools/agentphone/create_number.ts new file mode 100644 index 0000000000..fddf0550f5 --- /dev/null +++ b/apps/sim/tools/agentphone/create_number.ts @@ -0,0 +1,106 @@ +import type { + AgentPhoneCreateNumberParams, + AgentPhoneCreateNumberResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneCreateNumberTool: ToolConfig< + AgentPhoneCreateNumberParams, + AgentPhoneCreateNumberResult +> = { + id: 'agentphone_create_number', + name: 'Create Phone Number', + description: 'Provision a new SMS- and voice-enabled phone number', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + country: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Two-letter country code (e.g. US, CA). Defaults to US.', + }, + areaCode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Preferred area code (US/CA only, e.g. "415"). Best-effort — may be ignored if unavailable.', + }, + agentId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optionally attach the number to an agent immediately', + }, + }, + + request: { + url: 'https://api.agentphone.to/v1/numbers', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.country) body.country = params.country + if (params.areaCode) body.areaCode = params.areaCode + if (params.agentId) body.agentId = params.agentId + return body + }, + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to create phone number', + output: { + id: '', + phoneNumber: '', + country: '', + status: '', + type: '', + agentId: null, + createdAt: '', + }, + } + } + + return { + success: true, + output: { + id: data.id ?? '', + phoneNumber: data.phoneNumber ?? '', + country: data.country ?? '', + status: data.status ?? '', + type: data.type ?? '', + agentId: data.agentId ?? null, + createdAt: data.createdAt ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Unique phone number ID' }, + phoneNumber: { type: 'string', description: 'Provisioned phone number in E.164 format' }, + country: { type: 'string', description: 'Two-letter country code' }, + status: { type: 'string', description: 'Number status (e.g. active)' }, + type: { type: 'string', description: 'Number type (e.g. sms)', optional: true }, + agentId: { + type: 'string', + description: 'Agent the number is attached to', + optional: true, + }, + createdAt: { type: 'string', description: 'ISO 8601 timestamp when the number was created' }, + }, +} diff --git a/apps/sim/tools/agentphone/delete_contact.ts b/apps/sim/tools/agentphone/delete_contact.ts new file mode 100644 index 0000000000..694c3b6295 --- /dev/null +++ b/apps/sim/tools/agentphone/delete_contact.ts @@ -0,0 +1,67 @@ +import type { + AgentPhoneDeleteContactParams, + AgentPhoneDeleteContactResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneDeleteContactTool: ToolConfig< + AgentPhoneDeleteContactParams, + AgentPhoneDeleteContactResult +> = { + id: 'agentphone_delete_contact', + name: 'Delete Contact', + description: 'Delete a contact by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + contactId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Contact ID', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/contacts/${params.contactId.trim()}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response, params): Promise => { + const contactId = params?.contactId?.trim() ?? '' + + if (!response.ok) { + let errorMessage = 'Failed to delete contact' + try { + const data = await response.json() + errorMessage = data?.detail?.[0]?.msg ?? data?.message ?? errorMessage + } catch { + // Response body may be empty; ignore parse failures. + } + return { + success: false, + error: errorMessage, + output: { id: contactId, deleted: false }, + } + } + + return { + success: true, + output: { id: contactId, deleted: true }, + } + }, + + outputs: { + id: { type: 'string', description: 'ID of the deleted contact' }, + deleted: { type: 'boolean', description: 'Whether the contact was deleted successfully' }, + }, +} diff --git a/apps/sim/tools/agentphone/get_call.ts b/apps/sim/tools/agentphone/get_call.ts new file mode 100644 index 0000000000..6966e4d560 --- /dev/null +++ b/apps/sim/tools/agentphone/get_call.ts @@ -0,0 +1,146 @@ +import type { + AgentPhoneGetCallParams, + AgentPhoneGetCallResult, + AgentPhoneTranscriptTurn, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetCallTool: ToolConfig = { + id: 'agentphone_get_call', + name: 'Get Call', + description: 'Fetch a call and its full transcript', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + callId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the call to retrieve', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/calls/${params.callId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch call', + output: { + id: '', + agentId: null, + phoneNumberId: null, + phoneNumber: null, + fromNumber: '', + toNumber: '', + direction: '', + status: '', + startedAt: null, + endedAt: null, + durationSeconds: null, + lastTranscriptSnippet: null, + recordingUrl: null, + recordingAvailable: null, + transcripts: [], + }, + } + } + + const transcripts: AgentPhoneTranscriptTurn[] = (data.transcripts ?? []).map( + (turn: Record) => ({ + id: (turn.id as string) ?? '', + transcript: (turn.transcript as string) ?? '', + confidence: (turn.confidence as number | null) ?? null, + response: (turn.response as string | null) ?? null, + createdAt: (turn.createdAt as string) ?? '', + }) + ) + + return { + success: true, + output: { + id: data.id ?? '', + agentId: data.agentId ?? null, + phoneNumberId: data.phoneNumberId ?? null, + phoneNumber: data.phoneNumber ?? null, + fromNumber: data.fromNumber ?? '', + toNumber: data.toNumber ?? '', + direction: data.direction ?? '', + status: data.status ?? '', + startedAt: data.startedAt ?? null, + endedAt: data.endedAt ?? null, + durationSeconds: data.durationSeconds ?? null, + lastTranscriptSnippet: data.lastTranscriptSnippet ?? null, + recordingUrl: data.recordingUrl ?? null, + recordingAvailable: data.recordingAvailable ?? null, + transcripts, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Call ID' }, + agentId: { type: 'string', description: 'Agent that handled the call', optional: true }, + phoneNumberId: { type: 'string', description: 'Phone number ID', optional: true }, + phoneNumber: { + type: 'string', + description: 'Phone number used for the call', + optional: true, + }, + fromNumber: { type: 'string', description: 'Caller phone number' }, + toNumber: { type: 'string', description: 'Recipient phone number' }, + direction: { type: 'string', description: 'inbound or outbound', optional: true }, + status: { type: 'string', description: 'Call status' }, + startedAt: { type: 'string', description: 'ISO 8601 timestamp', optional: true }, + endedAt: { type: 'string', description: 'ISO 8601 timestamp', optional: true }, + durationSeconds: { type: 'number', description: 'Call duration in seconds', optional: true }, + lastTranscriptSnippet: { + type: 'string', + description: 'Last transcript snippet', + optional: true, + }, + recordingUrl: { type: 'string', description: 'Recording audio URL', optional: true }, + recordingAvailable: { + type: 'boolean', + description: 'Whether a recording is available', + optional: true, + }, + transcripts: { + type: 'array', + description: 'Ordered transcript turns for the call', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Transcript turn ID' }, + transcript: { type: 'string', description: 'User utterance' }, + confidence: { + type: 'number', + description: 'Speech recognition confidence', + optional: true, + }, + response: { + type: 'string', + description: 'Agent response (when available)', + optional: true, + }, + createdAt: { type: 'string', description: 'ISO 8601 timestamp' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/agentphone/get_call_transcript.ts b/apps/sim/tools/agentphone/get_call_transcript.ts new file mode 100644 index 0000000000..56800214ac --- /dev/null +++ b/apps/sim/tools/agentphone/get_call_transcript.ts @@ -0,0 +1,94 @@ +import type { + AgentPhoneGetCallTranscriptParams, + AgentPhoneGetCallTranscriptResult, + AgentPhoneTranscriptEntry, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetCallTranscriptTool: ToolConfig< + AgentPhoneGetCallTranscriptParams, + AgentPhoneGetCallTranscriptResult +> = { + id: 'agentphone_get_call_transcript', + name: 'Get Call Transcript', + description: 'Get the full ordered transcript for a call', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + callId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the call to retrieve the transcript for', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/calls/${params.callId.trim()}/transcript`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response, params): Promise => { + const data = await response.json() + const callId = params?.callId?.trim() ?? '' + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch transcript', + output: { callId, transcript: [] }, + } + } + + const rawTurns = Array.isArray(data?.transcript) + ? data.transcript + : Array.isArray(data) + ? data + : [] + + const transcript: AgentPhoneTranscriptEntry[] = rawTurns.map( + (turn: Record) => ({ + role: (turn.role as string) ?? '', + content: (turn.content as string) ?? '', + createdAt: (turn.createdAt as string) ?? (turn.created_at as string) ?? null, + }) + ) + + return { + success: true, + output: { callId: data?.callId ?? callId, transcript }, + } + }, + + outputs: { + callId: { type: 'string', description: 'Call ID' }, + transcript: { + type: 'array', + description: 'Ordered transcript turns for the call', + items: { + type: 'object', + properties: { + role: { + type: 'string', + description: 'Speaker role (user or agent)', + }, + content: { type: 'string', description: 'Turn content' }, + createdAt: { + type: 'string', + description: 'ISO 8601 timestamp', + optional: true, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/agentphone/get_contact.ts b/apps/sim/tools/agentphone/get_contact.ts new file mode 100644 index 0000000000..378b9782b7 --- /dev/null +++ b/apps/sim/tools/agentphone/get_contact.ts @@ -0,0 +1,81 @@ +import type { + AgentPhoneGetContactParams, + AgentPhoneGetContactResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetContactTool: ToolConfig< + AgentPhoneGetContactParams, + AgentPhoneGetContactResult +> = { + id: 'agentphone_get_contact', + name: 'Get Contact', + description: 'Fetch a single contact by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + contactId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Contact ID', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/contacts/${params.contactId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch contact', + output: { + id: '', + phoneNumber: '', + name: '', + email: null, + notes: null, + createdAt: '', + updatedAt: '', + }, + } + } + + return { + success: true, + output: { + id: data.id ?? '', + phoneNumber: data.phoneNumber ?? '', + name: data.name ?? '', + email: data.email ?? null, + notes: data.notes ?? null, + createdAt: data.createdAt ?? '', + updatedAt: data.updatedAt ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Contact ID' }, + phoneNumber: { type: 'string', description: 'Phone number in E.164 format' }, + name: { type: 'string', description: 'Contact name' }, + email: { type: 'string', description: 'Contact email address', optional: true }, + notes: { type: 'string', description: 'Freeform notes', optional: true }, + createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' }, + updatedAt: { type: 'string', description: 'ISO 8601 update timestamp' }, + }, +} diff --git a/apps/sim/tools/agentphone/get_conversation.ts b/apps/sim/tools/agentphone/get_conversation.ts new file mode 100644 index 0000000000..ee8c13d741 --- /dev/null +++ b/apps/sim/tools/agentphone/get_conversation.ts @@ -0,0 +1,137 @@ +import type { + AgentPhoneConversationMessage, + AgentPhoneGetConversationParams, + AgentPhoneGetConversationResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetConversationTool: ToolConfig< + AgentPhoneGetConversationParams, + AgentPhoneGetConversationResult +> = { + id: 'agentphone_get_conversation', + name: 'Get Conversation', + description: 'Get a conversation along with its recent messages', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + conversationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Conversation ID', + }, + messageLimit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of recent messages to include (default 50, max 100)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.messageLimit === 'number') { + query.set('message_limit', String(params.messageLimit)) + } + const qs = query.toString() + return `https://api.agentphone.to/v1/conversations/${params.conversationId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch conversation', + output: { + id: '', + agentId: null, + phoneNumberId: '', + phoneNumber: '', + participant: '', + lastMessageAt: '', + messageCount: 0, + metadata: null, + createdAt: '', + messages: [], + }, + } + } + + const messages: AgentPhoneConversationMessage[] = (data.messages ?? []).map( + (msg: Record) => ({ + id: (msg.id as string) ?? '', + body: (msg.body as string) ?? '', + fromNumber: (msg.fromNumber as string) ?? '', + toNumber: (msg.toNumber as string) ?? '', + direction: (msg.direction as string) ?? '', + channel: (msg.channel as string | null) ?? null, + mediaUrl: (msg.mediaUrl as string | null) ?? null, + receivedAt: (msg.receivedAt as string) ?? '', + }) + ) + + return { + success: true, + output: { + id: data.id ?? '', + agentId: data.agentId ?? null, + phoneNumberId: data.phoneNumberId ?? '', + phoneNumber: data.phoneNumber ?? '', + participant: data.participant ?? '', + lastMessageAt: data.lastMessageAt ?? '', + messageCount: data.messageCount ?? 0, + metadata: data.metadata ?? null, + createdAt: data.createdAt ?? '', + messages, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Conversation ID' }, + agentId: { type: 'string', description: 'Agent ID', optional: true }, + phoneNumberId: { type: 'string', description: 'Phone number ID' }, + phoneNumber: { type: 'string', description: 'Phone number' }, + participant: { type: 'string', description: 'External participant phone number' }, + lastMessageAt: { type: 'string', description: 'ISO 8601 timestamp' }, + messageCount: { type: 'number', description: 'Number of messages in the conversation' }, + metadata: { + type: 'json', + description: 'Custom metadata stored on the conversation', + optional: true, + }, + createdAt: { type: 'string', description: 'ISO 8601 timestamp' }, + messages: { + type: 'array', + description: 'Recent messages in the conversation', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID' }, + body: { type: 'string', description: 'Message text' }, + fromNumber: { type: 'string', description: 'Sender phone number' }, + toNumber: { type: 'string', description: 'Recipient phone number' }, + direction: { type: 'string', description: 'inbound or outbound' }, + channel: { type: 'string', description: 'sms, mms, or imessage', optional: true }, + mediaUrl: { type: 'string', description: 'Attached media URL', optional: true }, + receivedAt: { type: 'string', description: 'ISO 8601 timestamp' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/agentphone/get_conversation_messages.ts b/apps/sim/tools/agentphone/get_conversation_messages.ts new file mode 100644 index 0000000000..66f53ec7c4 --- /dev/null +++ b/apps/sim/tools/agentphone/get_conversation_messages.ts @@ -0,0 +1,116 @@ +import type { + AgentPhoneConversationMessage, + AgentPhoneGetConversationMessagesParams, + AgentPhoneGetConversationMessagesResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetConversationMessagesTool: ToolConfig< + AgentPhoneGetConversationMessagesParams, + AgentPhoneGetConversationMessagesResult +> = { + id: 'agentphone_get_conversation_messages', + name: 'Get Conversation Messages', + description: 'Get paginated messages for a conversation', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + conversationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Conversation ID', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of messages to return (default 50, max 200)', + }, + before: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return messages received before this ISO 8601 timestamp', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return messages received after this ISO 8601 timestamp', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.limit === 'number') query.set('limit', String(params.limit)) + if (params.before) query.set('before', params.before) + if (params.after) query.set('after', params.after) + const qs = query.toString() + return `https://api.agentphone.to/v1/conversations/${params.conversationId.trim()}/messages${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch messages', + output: { data: [], hasMore: false }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map( + (msg: Record): AgentPhoneConversationMessage => ({ + id: (msg.id as string) ?? '', + body: (msg.body as string) ?? '', + fromNumber: (msg.fromNumber as string) ?? '', + toNumber: (msg.toNumber as string) ?? '', + direction: (msg.direction as string) ?? '', + channel: (msg.channel as string | null) ?? null, + mediaUrl: (msg.mediaUrl as string | null) ?? null, + receivedAt: (msg.receivedAt as string) ?? '', + }) + ), + hasMore: data.hasMore ?? false, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Messages in the conversation', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID' }, + body: { type: 'string', description: 'Message text' }, + fromNumber: { type: 'string', description: 'Sender phone number' }, + toNumber: { type: 'string', description: 'Recipient phone number' }, + direction: { type: 'string', description: 'inbound or outbound' }, + channel: { type: 'string', description: 'sms, mms, or imessage', optional: true }, + mediaUrl: { type: 'string', description: 'Attached media URL', optional: true }, + receivedAt: { type: 'string', description: 'ISO 8601 timestamp' }, + }, + }, + }, + hasMore: { type: 'boolean', description: 'Whether more messages are available' }, + }, +} diff --git a/apps/sim/tools/agentphone/get_number_messages.ts b/apps/sim/tools/agentphone/get_number_messages.ts new file mode 100644 index 0000000000..b585ccbc13 --- /dev/null +++ b/apps/sim/tools/agentphone/get_number_messages.ts @@ -0,0 +1,115 @@ +import type { + AgentPhoneGetNumberMessagesParams, + AgentPhoneGetNumberMessagesResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetNumberMessagesTool: ToolConfig< + AgentPhoneGetNumberMessagesParams, + AgentPhoneGetNumberMessagesResult +> = { + id: 'agentphone_get_number_messages', + name: 'Get Phone Number Messages', + description: 'Fetch messages received on a specific phone number', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + numberId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the phone number', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of messages to return (default 50, max 200)', + }, + before: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return messages received before this ISO 8601 timestamp', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return messages received after this ISO 8601 timestamp', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.limit === 'number') query.set('limit', String(params.limit)) + if (params.before) query.set('before', params.before) + if (params.after) query.set('after', params.after) + const qs = query.toString() + return `https://api.agentphone.to/v1/numbers/${params.numberId.trim()}/messages${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch messages', + output: { data: [], hasMore: false }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map((msg: Record) => ({ + id: (msg.id as string) ?? '', + from_: (msg.from_ as string) ?? (msg.from as string) ?? '', + to: (msg.to as string) ?? '', + body: (msg.body as string) ?? '', + direction: (msg.direction as string) ?? '', + channel: (msg.channel as string | null) ?? null, + receivedAt: (msg.receivedAt as string) ?? '', + })), + hasMore: data.hasMore ?? false, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Messages received on the number', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID' }, + from_: { type: 'string', description: 'Sender phone number (E.164)' }, + to: { type: 'string', description: 'Recipient phone number (E.164)' }, + body: { type: 'string', description: 'Message text' }, + direction: { type: 'string', description: 'inbound or outbound' }, + channel: { + type: 'string', + description: 'Channel (sms, mms, etc.)', + optional: true, + }, + receivedAt: { type: 'string', description: 'ISO 8601 timestamp' }, + }, + }, + }, + hasMore: { type: 'boolean', description: 'Whether more messages are available' }, + }, +} diff --git a/apps/sim/tools/agentphone/get_usage.ts b/apps/sim/tools/agentphone/get_usage.ts new file mode 100644 index 0000000000..13e3827a5b --- /dev/null +++ b/apps/sim/tools/agentphone/get_usage.ts @@ -0,0 +1,127 @@ +import type { AgentPhoneGetUsageParams, AgentPhoneGetUsageResult } from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetUsageTool: ToolConfig< + AgentPhoneGetUsageParams, + AgentPhoneGetUsageResult +> = { + id: 'agentphone_get_usage', + name: 'Get Usage', + description: 'Retrieve current usage statistics for the AgentPhone account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + }, + + request: { + url: 'https://api.agentphone.to/v1/usage', + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch usage', + output: { + plan: { + name: '', + limits: { + numbers: null, + messagesPerMonth: null, + voiceMinutesPerMonth: null, + maxCallDurationMinutes: null, + concurrentCalls: null, + }, + }, + numbers: { used: null, limit: null, remaining: null }, + stats: { + totalMessages: null, + messagesLast24h: null, + messagesLast7d: null, + messagesLast30d: null, + totalCalls: null, + callsLast24h: null, + callsLast7d: null, + callsLast30d: null, + totalWebhookDeliveries: null, + successfulWebhookDeliveries: null, + failedWebhookDeliveries: null, + }, + periodStart: '', + periodEnd: '', + }, + } + } + + const planLimits = data?.plan?.limits ?? {} + const numbers = data?.numbers ?? {} + const stats = data?.stats ?? {} + + return { + success: true, + output: { + plan: { + name: data?.plan?.name ?? '', + limits: { + numbers: planLimits.numbers ?? null, + messagesPerMonth: planLimits.messagesPerMonth ?? null, + voiceMinutesPerMonth: planLimits.voiceMinutesPerMonth ?? null, + maxCallDurationMinutes: planLimits.maxCallDurationMinutes ?? null, + concurrentCalls: planLimits.concurrentCalls ?? null, + }, + }, + numbers: { + used: numbers.used ?? null, + limit: numbers.limit ?? null, + remaining: numbers.remaining ?? null, + }, + stats: { + totalMessages: stats.totalMessages ?? null, + messagesLast24h: stats.messagesLast24h ?? null, + messagesLast7d: stats.messagesLast7d ?? null, + messagesLast30d: stats.messagesLast30d ?? null, + totalCalls: stats.totalCalls ?? null, + callsLast24h: stats.callsLast24h ?? null, + callsLast7d: stats.callsLast7d ?? null, + callsLast30d: stats.callsLast30d ?? null, + totalWebhookDeliveries: stats.totalWebhookDeliveries ?? null, + successfulWebhookDeliveries: stats.successfulWebhookDeliveries ?? null, + failedWebhookDeliveries: stats.failedWebhookDeliveries ?? null, + }, + periodStart: data.periodStart ?? '', + periodEnd: data.periodEnd ?? '', + }, + } + }, + + outputs: { + plan: { + type: 'json', + description: + 'Plan name and limits (name, limits: numbers/messagesPerMonth/voiceMinutesPerMonth/maxCallDurationMinutes/concurrentCalls)', + }, + numbers: { + type: 'json', + description: 'Phone number usage (used, limit, remaining)', + }, + stats: { + type: 'json', + description: + 'Usage stats: totalMessages, messagesLast24h/7d/30d, totalCalls, callsLast24h/7d/30d, totalWebhookDeliveries, successfulWebhookDeliveries, failedWebhookDeliveries', + }, + periodStart: { type: 'string', description: 'Billing period start' }, + periodEnd: { type: 'string', description: 'Billing period end' }, + }, +} diff --git a/apps/sim/tools/agentphone/get_usage_daily.ts b/apps/sim/tools/agentphone/get_usage_daily.ts new file mode 100644 index 0000000000..07abec15dd --- /dev/null +++ b/apps/sim/tools/agentphone/get_usage_daily.ts @@ -0,0 +1,88 @@ +import type { + AgentPhoneGetUsageDailyParams, + AgentPhoneGetUsageDailyResult, + AgentPhoneUsageDailyEntry, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetUsageDailyTool: ToolConfig< + AgentPhoneGetUsageDailyParams, + AgentPhoneGetUsageDailyResult +> = { + id: 'agentphone_get_usage_daily', + name: 'Get Daily Usage', + description: 'Get a daily breakdown of usage (messages, calls, webhooks) for the last N days', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + days: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of days to return (1-365, default 30)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.days === 'number') query.set('days', String(params.days)) + const qs = query.toString() + return `https://api.agentphone.to/v1/usage/daily${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch daily usage', + output: { data: [], days: 0 }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map( + (entry: Record): AgentPhoneUsageDailyEntry => ({ + date: (entry.date as string) ?? '', + messages: (entry.messages as number) ?? 0, + calls: (entry.calls as number) ?? 0, + webhooks: (entry.webhooks as number) ?? 0, + }) + ), + days: data.days ?? 0, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Daily usage entries', + items: { + type: 'object', + properties: { + date: { type: 'string', description: 'Day (YYYY-MM-DD)' }, + messages: { type: 'number', description: 'Messages that day' }, + calls: { type: 'number', description: 'Calls that day' }, + webhooks: { type: 'number', description: 'Webhook deliveries that day' }, + }, + }, + }, + days: { type: 'number', description: 'Number of days returned' }, + }, +} diff --git a/apps/sim/tools/agentphone/get_usage_monthly.ts b/apps/sim/tools/agentphone/get_usage_monthly.ts new file mode 100644 index 0000000000..9d52d2b02e --- /dev/null +++ b/apps/sim/tools/agentphone/get_usage_monthly.ts @@ -0,0 +1,88 @@ +import type { + AgentPhoneGetUsageMonthlyParams, + AgentPhoneGetUsageMonthlyResult, + AgentPhoneUsageMonthlyEntry, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneGetUsageMonthlyTool: ToolConfig< + AgentPhoneGetUsageMonthlyParams, + AgentPhoneGetUsageMonthlyResult +> = { + id: 'agentphone_get_usage_monthly', + name: 'Get Monthly Usage', + description: 'Get monthly usage aggregation (messages, calls, webhooks) for the last N months', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + months: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of months to return (1-24, default 6)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.months === 'number') query.set('months', String(params.months)) + const qs = query.toString() + return `https://api.agentphone.to/v1/usage/monthly${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to fetch monthly usage', + output: { data: [], months: 0 }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map( + (entry: Record): AgentPhoneUsageMonthlyEntry => ({ + month: (entry.month as string) ?? '', + messages: (entry.messages as number) ?? 0, + calls: (entry.calls as number) ?? 0, + webhooks: (entry.webhooks as number) ?? 0, + }) + ), + months: data.months ?? 0, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Monthly usage entries', + items: { + type: 'object', + properties: { + month: { type: 'string', description: 'Month (YYYY-MM)' }, + messages: { type: 'number', description: 'Messages that month' }, + calls: { type: 'number', description: 'Calls that month' }, + webhooks: { type: 'number', description: 'Webhook deliveries that month' }, + }, + }, + }, + months: { type: 'number', description: 'Number of months returned' }, + }, +} diff --git a/apps/sim/tools/agentphone/index.ts b/apps/sim/tools/agentphone/index.ts new file mode 100644 index 0000000000..57b30d5af2 --- /dev/null +++ b/apps/sim/tools/agentphone/index.ts @@ -0,0 +1,23 @@ +export { agentphoneCreateCallTool } from '@/tools/agentphone/create_call' +export { agentphoneCreateContactTool } from '@/tools/agentphone/create_contact' +export { agentphoneCreateNumberTool } from '@/tools/agentphone/create_number' +export { agentphoneDeleteContactTool } from '@/tools/agentphone/delete_contact' +export { agentphoneGetCallTool } from '@/tools/agentphone/get_call' +export { agentphoneGetCallTranscriptTool } from '@/tools/agentphone/get_call_transcript' +export { agentphoneGetContactTool } from '@/tools/agentphone/get_contact' +export { agentphoneGetConversationTool } from '@/tools/agentphone/get_conversation' +export { agentphoneGetConversationMessagesTool } from '@/tools/agentphone/get_conversation_messages' +export { agentphoneGetNumberMessagesTool } from '@/tools/agentphone/get_number_messages' +export { agentphoneGetUsageTool } from '@/tools/agentphone/get_usage' +export { agentphoneGetUsageDailyTool } from '@/tools/agentphone/get_usage_daily' +export { agentphoneGetUsageMonthlyTool } from '@/tools/agentphone/get_usage_monthly' +export { agentphoneListCallsTool } from '@/tools/agentphone/list_calls' +export { agentphoneListContactsTool } from '@/tools/agentphone/list_contacts' +export { agentphoneListConversationsTool } from '@/tools/agentphone/list_conversations' +export { agentphoneListNumbersTool } from '@/tools/agentphone/list_numbers' +export { agentphoneReactToMessageTool } from '@/tools/agentphone/react_to_message' +export { agentphoneReleaseNumberTool } from '@/tools/agentphone/release_number' +export { agentphoneSendMessageTool } from '@/tools/agentphone/send_message' +export * from '@/tools/agentphone/types' +export { agentphoneUpdateContactTool } from '@/tools/agentphone/update_contact' +export { agentphoneUpdateConversationTool } from '@/tools/agentphone/update_conversation' diff --git a/apps/sim/tools/agentphone/list_calls.ts b/apps/sim/tools/agentphone/list_calls.ts new file mode 100644 index 0000000000..df140fde31 --- /dev/null +++ b/apps/sim/tools/agentphone/list_calls.ts @@ -0,0 +1,169 @@ +import type { + AgentPhoneCallSummary, + AgentPhoneListCallsParams, + AgentPhoneListCallsResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneListCallsTool: ToolConfig< + AgentPhoneListCallsParams, + AgentPhoneListCallsResult +> = { + id: 'agentphone_list_calls', + name: 'List Calls', + description: 'List voice calls for this AgentPhone account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to return (default 20, max 100)', + }, + offset: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip (min 0)', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by status (completed, in-progress, failed)', + }, + direction: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by direction (inbound, outbound)', + }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by call type (pstn, web)', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search by phone number (matches fromNumber or toNumber)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.limit === 'number') query.set('limit', String(params.limit)) + if (typeof params.offset === 'number') query.set('offset', String(params.offset)) + if (params.status) query.set('status', params.status) + if (params.direction) query.set('direction', params.direction) + if (params.type) query.set('type', params.type) + if (params.search) query.set('search', params.search) + const qs = query.toString() + return `https://api.agentphone.to/v1/calls${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to list calls', + output: { data: [], hasMore: false, total: 0 }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map( + (call: Record): AgentPhoneCallSummary => ({ + id: (call.id as string) ?? '', + agentId: (call.agentId as string | null) ?? null, + phoneNumberId: (call.phoneNumberId as string | null) ?? null, + phoneNumber: (call.phoneNumber as string | null) ?? null, + fromNumber: (call.fromNumber as string) ?? '', + toNumber: (call.toNumber as string) ?? '', + direction: (call.direction as string) ?? '', + status: (call.status as string) ?? '', + startedAt: (call.startedAt as string | null) ?? null, + endedAt: (call.endedAt as string | null) ?? null, + durationSeconds: (call.durationSeconds as number | null) ?? null, + lastTranscriptSnippet: (call.lastTranscriptSnippet as string | null) ?? null, + recordingUrl: (call.recordingUrl as string | null) ?? null, + recordingAvailable: (call.recordingAvailable as boolean | null) ?? null, + }) + ), + hasMore: data.hasMore ?? false, + total: data.total ?? 0, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Calls', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Call ID' }, + agentId: { + type: 'string', + description: 'Agent that handled the call', + optional: true, + }, + phoneNumberId: { + type: 'string', + description: 'Phone number ID used for the call', + optional: true, + }, + phoneNumber: { + type: 'string', + description: 'Phone number used for the call', + optional: true, + }, + fromNumber: { type: 'string', description: 'Caller phone number' }, + toNumber: { type: 'string', description: 'Recipient phone number' }, + direction: { type: 'string', description: 'inbound or outbound', optional: true }, + status: { type: 'string', description: 'Call status' }, + startedAt: { type: 'string', description: 'ISO 8601 timestamp', optional: true }, + endedAt: { type: 'string', description: 'ISO 8601 timestamp', optional: true }, + durationSeconds: { + type: 'number', + description: 'Call duration in seconds', + optional: true, + }, + lastTranscriptSnippet: { + type: 'string', + description: 'Last transcript snippet', + optional: true, + }, + recordingUrl: { type: 'string', description: 'Recording audio URL', optional: true }, + recordingAvailable: { + type: 'boolean', + description: 'Whether a recording is available', + optional: true, + }, + }, + }, + }, + hasMore: { type: 'boolean', description: 'Whether more results are available' }, + total: { type: 'number', description: 'Total number of matching calls' }, + }, +} diff --git a/apps/sim/tools/agentphone/list_contacts.ts b/apps/sim/tools/agentphone/list_contacts.ts new file mode 100644 index 0000000000..19f9d1bae1 --- /dev/null +++ b/apps/sim/tools/agentphone/list_contacts.ts @@ -0,0 +1,110 @@ +import type { + AgentPhoneContact, + AgentPhoneListContactsParams, + AgentPhoneListContactsResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneListContactsTool: ToolConfig< + AgentPhoneListContactsParams, + AgentPhoneListContactsResult +> = { + id: 'agentphone_list_contacts', + name: 'List Contacts', + description: 'List contacts for this AgentPhone account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by name or phone number (case-insensitive contains)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to return (default 50)', + }, + offset: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip (min 0)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (params.search) query.set('search', params.search) + if (typeof params.limit === 'number') query.set('limit', String(params.limit)) + if (typeof params.offset === 'number') query.set('offset', String(params.offset)) + const qs = query.toString() + return `https://api.agentphone.to/v1/contacts${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to list contacts', + output: { data: [], hasMore: false, total: 0 }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map( + (c: Record): AgentPhoneContact => ({ + id: (c.id as string) ?? '', + phoneNumber: (c.phoneNumber as string) ?? '', + name: (c.name as string) ?? '', + email: (c.email as string | null) ?? null, + notes: (c.notes as string | null) ?? null, + createdAt: (c.createdAt as string) ?? '', + updatedAt: (c.updatedAt as string) ?? '', + }) + ), + hasMore: data.hasMore ?? false, + total: data.total ?? 0, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Contacts', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Contact ID' }, + phoneNumber: { type: 'string', description: 'Phone number in E.164 format' }, + name: { type: 'string', description: 'Contact name' }, + email: { type: 'string', description: 'Contact email address', optional: true }, + notes: { type: 'string', description: 'Freeform notes', optional: true }, + createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' }, + updatedAt: { type: 'string', description: 'ISO 8601 update timestamp' }, + }, + }, + }, + hasMore: { type: 'boolean', description: 'Whether more results are available' }, + total: { type: 'number', description: 'Total number of contacts' }, + }, +} diff --git a/apps/sim/tools/agentphone/list_conversations.ts b/apps/sim/tools/agentphone/list_conversations.ts new file mode 100644 index 0000000000..7a742b2221 --- /dev/null +++ b/apps/sim/tools/agentphone/list_conversations.ts @@ -0,0 +1,113 @@ +import type { + AgentPhoneConversationSummary, + AgentPhoneListConversationsParams, + AgentPhoneListConversationsResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneListConversationsTool: ToolConfig< + AgentPhoneListConversationsParams, + AgentPhoneListConversationsResult +> = { + id: 'agentphone_list_conversations', + name: 'List Conversations', + description: 'List conversations (message threads) for this AgentPhone account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to return (default 20, max 100)', + }, + offset: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip (min 0)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.limit === 'number') query.set('limit', String(params.limit)) + if (typeof params.offset === 'number') query.set('offset', String(params.offset)) + const qs = query.toString() + return `https://api.agentphone.to/v1/conversations${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to list conversations', + output: { data: [], hasMore: false, total: 0 }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map( + (conv: Record): AgentPhoneConversationSummary => ({ + id: (conv.id as string) ?? '', + agentId: (conv.agentId as string | null) ?? null, + phoneNumberId: (conv.phoneNumberId as string) ?? '', + phoneNumber: (conv.phoneNumber as string) ?? '', + participant: (conv.participant as string) ?? '', + lastMessageAt: (conv.lastMessageAt as string) ?? '', + lastMessagePreview: (conv.lastMessagePreview as string) ?? '', + messageCount: (conv.messageCount as number) ?? 0, + metadata: (conv.metadata as Record | null) ?? null, + createdAt: (conv.createdAt as string) ?? '', + }) + ), + hasMore: data.hasMore ?? false, + total: data.total ?? 0, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Conversations', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Conversation ID' }, + agentId: { type: 'string', description: 'Agent ID', optional: true }, + phoneNumberId: { type: 'string', description: 'Phone number ID' }, + phoneNumber: { type: 'string', description: 'Phone number' }, + participant: { type: 'string', description: 'External participant phone number' }, + lastMessageAt: { type: 'string', description: 'ISO 8601 timestamp' }, + lastMessagePreview: { type: 'string', description: 'Last message preview' }, + messageCount: { type: 'number', description: 'Number of messages in the conversation' }, + metadata: { + type: 'json', + description: 'Custom metadata stored on the conversation', + optional: true, + }, + createdAt: { type: 'string', description: 'ISO 8601 timestamp' }, + }, + }, + }, + hasMore: { type: 'boolean', description: 'Whether more results are available' }, + total: { type: 'number', description: 'Total number of conversations' }, + }, +} diff --git a/apps/sim/tools/agentphone/list_numbers.ts b/apps/sim/tools/agentphone/list_numbers.ts new file mode 100644 index 0000000000..96c2472e75 --- /dev/null +++ b/apps/sim/tools/agentphone/list_numbers.ts @@ -0,0 +1,100 @@ +import type { + AgentPhoneListNumbersParams, + AgentPhoneListNumbersResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneListNumbersTool: ToolConfig< + AgentPhoneListNumbersParams, + AgentPhoneListNumbersResult +> = { + id: 'agentphone_list_numbers', + name: 'List Phone Numbers', + description: 'List all phone numbers provisioned for this AgentPhone account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to return (default 20, max 100)', + }, + offset: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results to skip (min 0)', + }, + }, + + request: { + url: (params) => { + const query = new URLSearchParams() + if (typeof params.limit === 'number') query.set('limit', String(params.limit)) + if (typeof params.offset === 'number') query.set('offset', String(params.offset)) + const qs = query.toString() + return `https://api.agentphone.to/v1/numbers${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to list phone numbers', + output: { data: [], hasMore: false, total: 0 }, + } + } + + return { + success: true, + output: { + data: (data.data ?? []).map((num: Record) => ({ + id: (num.id as string) ?? '', + phoneNumber: (num.phoneNumber as string) ?? '', + country: (num.country as string) ?? '', + status: (num.status as string) ?? '', + type: (num.type as string) ?? '', + agentId: (num.agentId as string | null) ?? null, + createdAt: (num.createdAt as string) ?? '', + })), + hasMore: data.hasMore ?? false, + total: data.total ?? 0, + }, + } + }, + + outputs: { + data: { + type: 'array', + description: 'Phone numbers', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Phone number ID' }, + phoneNumber: { type: 'string', description: 'Phone number in E.164 format' }, + country: { type: 'string', description: 'Two-letter country code' }, + status: { type: 'string', description: 'Number status' }, + type: { type: 'string', description: 'Number type (e.g. sms)', optional: true }, + agentId: { type: 'string', description: 'Attached agent ID', optional: true }, + createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' }, + }, + }, + }, + hasMore: { type: 'boolean', description: 'Whether more results are available' }, + total: { type: 'number', description: 'Total number of phone numbers' }, + }, +} diff --git a/apps/sim/tools/agentphone/react_to_message.ts b/apps/sim/tools/agentphone/react_to_message.ts new file mode 100644 index 0000000000..f0c65e4f89 --- /dev/null +++ b/apps/sim/tools/agentphone/react_to_message.ts @@ -0,0 +1,76 @@ +import type { + AgentPhoneReactToMessageParams, + AgentPhoneReactToMessageResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneReactToMessageTool: ToolConfig< + AgentPhoneReactToMessageParams, + AgentPhoneReactToMessageResult +> = { + id: 'agentphone_react_to_message', + name: 'React to Message', + description: 'Send an iMessage tapback reaction to a message (iMessage only)', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + messageId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the message to react to', + }, + reaction: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Reaction type: love, like, dislike, laugh, emphasize, or question', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/messages/${params.messageId.trim()}/reactions`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => ({ reaction: params.reaction }), + }, + + transformResponse: async (response, params): Promise => { + const data = await response.json() + const messageId = params?.messageId?.trim() ?? '' + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to send reaction', + output: { id: '', reactionType: '', messageId, channel: '' }, + } + } + + return { + success: true, + output: { + id: data.id ?? '', + reactionType: data.reaction_type ?? '', + messageId: data.message_id ?? messageId, + channel: data.channel ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Reaction ID' }, + reactionType: { type: 'string', description: 'Reaction type applied' }, + messageId: { type: 'string', description: 'ID of the message that was reacted to' }, + channel: { type: 'string', description: 'Channel (imessage)' }, + }, +} diff --git a/apps/sim/tools/agentphone/release_number.ts b/apps/sim/tools/agentphone/release_number.ts new file mode 100644 index 0000000000..805d969bfc --- /dev/null +++ b/apps/sim/tools/agentphone/release_number.ts @@ -0,0 +1,67 @@ +import type { + AgentPhoneReleaseNumberParams, + AgentPhoneReleaseNumberResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneReleaseNumberTool: ToolConfig< + AgentPhoneReleaseNumberParams, + AgentPhoneReleaseNumberResult +> = { + id: 'agentphone_release_number', + name: 'Release Phone Number', + description: 'Release (delete) a phone number. This action is irreversible.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + numberId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the phone number to release', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/numbers/${params.numberId.trim()}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response, params): Promise => { + const numberId = params?.numberId?.trim() ?? '' + + if (!response.ok) { + let errorMessage = 'Failed to release phone number' + try { + const data = await response.json() + errorMessage = data?.detail?.[0]?.msg ?? data?.message ?? errorMessage + } catch { + // Response body may be empty on DELETE errors; ignore parse failures. + } + return { + success: false, + error: errorMessage, + output: { id: numberId, released: false }, + } + } + + return { + success: true, + output: { id: numberId, released: true }, + } + }, + + outputs: { + id: { type: 'string', description: 'ID of the released phone number' }, + released: { type: 'boolean', description: 'Whether the number was released successfully' }, + }, +} diff --git a/apps/sim/tools/agentphone/send_message.ts b/apps/sim/tools/agentphone/send_message.ts new file mode 100644 index 0000000000..df08a020d8 --- /dev/null +++ b/apps/sim/tools/agentphone/send_message.ts @@ -0,0 +1,105 @@ +import type { + AgentPhoneSendMessageParams, + AgentPhoneSendMessageResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneSendMessageTool: ToolConfig< + AgentPhoneSendMessageParams, + AgentPhoneSendMessageResult +> = { + id: 'agentphone_send_message', + name: 'Send Message', + description: 'Send an outbound SMS or iMessage from an AgentPhone agent', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + agentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Agent sending the message', + }, + toNumber: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Recipient phone number in E.164 format (e.g. +14155551234)', + }, + body: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Message text to send', + }, + mediaUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional URL of an image, video, or file to attach', + }, + numberId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + "Phone number ID to send from. If omitted, the agent's first assigned number is used.", + }, + }, + + request: { + url: 'https://api.agentphone.to/v1/messages', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + agent_id: params.agentId, + to_number: params.toNumber, + body: params.body, + } + if (params.mediaUrl) body.media_url = params.mediaUrl + if (params.numberId) body.number_id = params.numberId + return body + }, + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to send message', + output: { id: '', status: '', channel: '', fromNumber: '', toNumber: '' }, + } + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? '', + channel: data.channel ?? '', + fromNumber: data.from_number ?? '', + toNumber: data.to_number ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Message ID' }, + status: { type: 'string', description: 'Delivery status' }, + channel: { type: 'string', description: 'sms, mms, or imessage' }, + fromNumber: { type: 'string', description: 'Sender phone number' }, + toNumber: { type: 'string', description: 'Recipient phone number' }, + }, +} diff --git a/apps/sim/tools/agentphone/types.ts b/apps/sim/tools/agentphone/types.ts new file mode 100644 index 0000000000..129ce5acce --- /dev/null +++ b/apps/sim/tools/agentphone/types.ts @@ -0,0 +1,450 @@ +import type { ToolResponse } from '@/tools/types' + +export interface AgentPhoneNumber { + id: string + phoneNumber: string + country: string + status: string + type: string + agentId: string | null + createdAt: string +} + +export interface AgentPhoneNumberMessage { + id: string + from_: string + to: string + body: string + direction: string + channel: string | null + receivedAt: string +} + +export interface AgentPhoneConversationSummary { + id: string + agentId: string | null + phoneNumberId: string + phoneNumber: string + participant: string + lastMessageAt: string + lastMessagePreview: string + messageCount: number + metadata: Record | null + createdAt: string +} + +export interface AgentPhoneConversationMessage { + id: string + body: string + fromNumber: string + toNumber: string + direction: string + channel: string | null + mediaUrl: string | null + receivedAt: string +} + +export interface AgentPhoneConversationDetail { + id: string + agentId: string | null + phoneNumberId: string + phoneNumber: string + participant: string + lastMessageAt: string + messageCount: number + metadata: Record | null + createdAt: string + messages: AgentPhoneConversationMessage[] +} + +export interface AgentPhoneCallSummary { + id: string + agentId: string | null + phoneNumberId: string | null + phoneNumber: string | null + fromNumber: string + toNumber: string + direction: string + status: string + startedAt: string | null + endedAt: string | null + durationSeconds: number | null + lastTranscriptSnippet: string | null + recordingUrl: string | null + recordingAvailable: boolean | null +} + +export interface AgentPhoneTranscriptTurn { + id: string + transcript: string + confidence: number | null + response: string | null + createdAt: string +} + +export interface AgentPhoneTranscriptEntry { + role: string + content: string + createdAt: string | null +} + +export interface AgentPhoneCallDetail extends AgentPhoneCallSummary { + transcripts: AgentPhoneTranscriptTurn[] +} + +export interface AgentPhoneContact { + id: string + phoneNumber: string + name: string + email: string | null + notes: string | null + createdAt: string + updatedAt: string +} + +export interface AgentPhoneCreateNumberParams { + apiKey: string + country?: string + areaCode?: string + agentId?: string +} + +export interface AgentPhoneCreateNumberResult extends ToolResponse { + output: AgentPhoneNumber +} + +export interface AgentPhoneListNumbersParams { + apiKey: string + limit?: number + offset?: number +} + +export interface AgentPhoneListNumbersResult extends ToolResponse { + output: { + data: AgentPhoneNumber[] + hasMore: boolean + total: number + } +} + +export interface AgentPhoneReleaseNumberParams { + apiKey: string + numberId: string +} + +export interface AgentPhoneReleaseNumberResult extends ToolResponse { + output: { + id: string + released: boolean + } +} + +export interface AgentPhoneGetNumberMessagesParams { + apiKey: string + numberId: string + limit?: number + before?: string + after?: string +} + +export interface AgentPhoneGetNumberMessagesResult extends ToolResponse { + output: { + data: AgentPhoneNumberMessage[] + hasMore: boolean + } +} + +export interface AgentPhoneCreateCallParams { + apiKey: string + agentId: string + toNumber: string + fromNumberId?: string + initialGreeting?: string + voice?: string + systemPrompt?: string +} + +export interface AgentPhoneCreateCallResult extends ToolResponse { + output: { + id: string + agentId: string | null + status: string | null + toNumber: string | null + fromNumber: string | null + phoneNumberId: string | null + direction: string | null + startedAt: string | null + } +} + +export interface AgentPhoneListCallsParams { + apiKey: string + limit?: number + offset?: number + status?: string + direction?: string + type?: string + search?: string +} + +export interface AgentPhoneListCallsResult extends ToolResponse { + output: { + data: AgentPhoneCallSummary[] + hasMore: boolean + total: number + } +} + +export interface AgentPhoneGetCallParams { + apiKey: string + callId: string +} + +export interface AgentPhoneGetCallResult extends ToolResponse { + output: AgentPhoneCallDetail +} + +export interface AgentPhoneGetCallTranscriptParams { + apiKey: string + callId: string +} + +export interface AgentPhoneGetCallTranscriptResult extends ToolResponse { + output: { + callId: string + transcript: AgentPhoneTranscriptEntry[] + } +} + +export interface AgentPhoneListConversationsParams { + apiKey: string + limit?: number + offset?: number +} + +export interface AgentPhoneListConversationsResult extends ToolResponse { + output: { + data: AgentPhoneConversationSummary[] + hasMore: boolean + total: number + } +} + +export interface AgentPhoneGetConversationParams { + apiKey: string + conversationId: string + messageLimit?: number +} + +export interface AgentPhoneGetConversationResult extends ToolResponse { + output: AgentPhoneConversationDetail +} + +export interface AgentPhoneUpdateConversationParams { + apiKey: string + conversationId: string + metadata?: Record | null +} + +export interface AgentPhoneUpdateConversationResult extends ToolResponse { + output: AgentPhoneConversationDetail +} + +export interface AgentPhoneGetConversationMessagesParams { + apiKey: string + conversationId: string + limit?: number + before?: string + after?: string +} + +export interface AgentPhoneGetConversationMessagesResult extends ToolResponse { + output: { + data: AgentPhoneConversationMessage[] + hasMore: boolean + } +} + +export interface AgentPhoneSendMessageParams { + apiKey: string + agentId: string + toNumber: string + body: string + mediaUrl?: string + numberId?: string +} + +export interface AgentPhoneSendMessageResult extends ToolResponse { + output: { + id: string + status: string + channel: string + fromNumber: string + toNumber: string + } +} + +export type AgentPhoneReactionType = + | 'love' + | 'like' + | 'dislike' + | 'laugh' + | 'emphasize' + | 'question' + +export interface AgentPhoneReactToMessageParams { + apiKey: string + messageId: string + reaction: AgentPhoneReactionType +} + +export interface AgentPhoneReactToMessageResult extends ToolResponse { + output: { + id: string + reactionType: string + messageId: string + channel: string + } +} + +export interface AgentPhoneCreateContactParams { + apiKey: string + phoneNumber: string + name: string + email?: string + notes?: string +} + +export interface AgentPhoneCreateContactResult extends ToolResponse { + output: AgentPhoneContact +} + +export interface AgentPhoneListContactsParams { + apiKey: string + search?: string + limit?: number + offset?: number +} + +export interface AgentPhoneListContactsResult extends ToolResponse { + output: { + data: AgentPhoneContact[] + hasMore: boolean + total: number + } +} + +export interface AgentPhoneGetContactParams { + apiKey: string + contactId: string +} + +export interface AgentPhoneGetContactResult extends ToolResponse { + output: AgentPhoneContact +} + +export interface AgentPhoneUpdateContactParams { + apiKey: string + contactId: string + phoneNumber?: string + name?: string + email?: string + notes?: string +} + +export interface AgentPhoneUpdateContactResult extends ToolResponse { + output: AgentPhoneContact +} + +export interface AgentPhoneDeleteContactParams { + apiKey: string + contactId: string +} + +export interface AgentPhoneDeleteContactResult extends ToolResponse { + output: { + id: string + deleted: boolean + } +} + +export interface AgentPhoneUsagePlan { + name: string + limits: { + numbers: number | null + messagesPerMonth: number | null + voiceMinutesPerMonth: number | null + maxCallDurationMinutes: number | null + concurrentCalls: number | null + } +} + +export interface AgentPhoneUsageStats { + totalMessages: number | null + messagesLast24h: number | null + messagesLast7d: number | null + messagesLast30d: number | null + totalCalls: number | null + callsLast24h: number | null + callsLast7d: number | null + callsLast30d: number | null + totalWebhookDeliveries: number | null + successfulWebhookDeliveries: number | null + failedWebhookDeliveries: number | null +} + +export interface AgentPhoneGetUsageParams { + apiKey: string +} + +export interface AgentPhoneGetUsageResult extends ToolResponse { + output: { + plan: AgentPhoneUsagePlan + numbers: { + used: number | null + limit: number | null + remaining: number | null + } + stats: AgentPhoneUsageStats + periodStart: string + periodEnd: string + } +} + +export interface AgentPhoneUsageDailyEntry { + date: string + messages: number + calls: number + webhooks: number +} + +export interface AgentPhoneGetUsageDailyParams { + apiKey: string + days?: number +} + +export interface AgentPhoneGetUsageDailyResult extends ToolResponse { + output: { + data: AgentPhoneUsageDailyEntry[] + days: number + } +} + +export interface AgentPhoneUsageMonthlyEntry { + month: string + messages: number + calls: number + webhooks: number +} + +export interface AgentPhoneGetUsageMonthlyParams { + apiKey: string + months?: number +} + +export interface AgentPhoneGetUsageMonthlyResult extends ToolResponse { + output: { + data: AgentPhoneUsageMonthlyEntry[] + months: number + } +} diff --git a/apps/sim/tools/agentphone/update_contact.ts b/apps/sim/tools/agentphone/update_contact.ts new file mode 100644 index 0000000000..680052e6ed --- /dev/null +++ b/apps/sim/tools/agentphone/update_contact.ts @@ -0,0 +1,114 @@ +import type { + AgentPhoneUpdateContactParams, + AgentPhoneUpdateContactResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneUpdateContactTool: ToolConfig< + AgentPhoneUpdateContactParams, + AgentPhoneUpdateContactResult +> = { + id: 'agentphone_update_contact', + name: 'Update Contact', + description: "Update a contact's fields", + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + contactId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Contact ID', + }, + phoneNumber: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New phone number in E.164 format', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New contact name', + }, + email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New email address', + }, + notes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New freeform notes', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/contacts/${params.contactId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.phoneNumber !== undefined) body.phoneNumber = params.phoneNumber + if (params.name !== undefined) body.name = params.name + if (params.email !== undefined) body.email = params.email + if (params.notes !== undefined) body.notes = params.notes + return body + }, + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to update contact', + output: { + id: '', + phoneNumber: '', + name: '', + email: null, + notes: null, + createdAt: '', + updatedAt: '', + }, + } + } + + return { + success: true, + output: { + id: data.id ?? '', + phoneNumber: data.phoneNumber ?? '', + name: data.name ?? '', + email: data.email ?? null, + notes: data.notes ?? null, + createdAt: data.createdAt ?? '', + updatedAt: data.updatedAt ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Contact ID' }, + phoneNumber: { type: 'string', description: 'Phone number in E.164 format' }, + name: { type: 'string', description: 'Contact name' }, + email: { type: 'string', description: 'Contact email address', optional: true }, + notes: { type: 'string', description: 'Freeform notes', optional: true }, + createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' }, + updatedAt: { type: 'string', description: 'ISO 8601 update timestamp' }, + }, +} diff --git a/apps/sim/tools/agentphone/update_conversation.ts b/apps/sim/tools/agentphone/update_conversation.ts new file mode 100644 index 0000000000..2cf6f75387 --- /dev/null +++ b/apps/sim/tools/agentphone/update_conversation.ts @@ -0,0 +1,146 @@ +import type { + AgentPhoneConversationMessage, + AgentPhoneUpdateConversationParams, + AgentPhoneUpdateConversationResult, +} from '@/tools/agentphone/types' +import type { ToolConfig } from '@/tools/types' + +export const agentphoneUpdateConversationTool: ToolConfig< + AgentPhoneUpdateConversationParams, + AgentPhoneUpdateConversationResult +> = { + id: 'agentphone_update_conversation', + name: 'Update Conversation', + description: 'Update conversation metadata (stored state). Pass null to clear existing metadata.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AgentPhone API key', + }, + conversationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Conversation ID', + }, + metadata: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Custom key-value metadata to store on the conversation. Pass null to clear existing metadata.', + }, + }, + + request: { + url: (params) => `https://api.agentphone.to/v1/conversations/${params.conversationId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.metadata !== undefined) body.metadata = params.metadata + return body + }, + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data?.detail?.[0]?.msg ?? data?.message ?? 'Failed to update conversation', + output: { + id: '', + agentId: null, + phoneNumberId: '', + phoneNumber: '', + participant: '', + lastMessageAt: '', + messageCount: 0, + metadata: null, + createdAt: '', + messages: [], + }, + } + } + + const rawMessages = Array.isArray(data?.messages) ? data.messages : [] + const messages: AgentPhoneConversationMessage[] = rawMessages.map( + (message: Record) => ({ + id: (message.id as string) ?? '', + body: (message.body as string) ?? '', + fromNumber: (message.fromNumber as string) ?? '', + toNumber: (message.toNumber as string) ?? '', + direction: (message.direction as string) ?? '', + channel: (message.channel as string | null) ?? null, + mediaUrl: (message.mediaUrl as string | null) ?? null, + receivedAt: (message.receivedAt as string) ?? '', + }) + ) + + return { + success: true, + output: { + id: data.id ?? '', + agentId: data.agentId ?? null, + phoneNumberId: data.phoneNumberId ?? '', + phoneNumber: data.phoneNumber ?? '', + participant: data.participant ?? '', + lastMessageAt: data.lastMessageAt ?? '', + messageCount: data.messageCount ?? 0, + metadata: data.metadata ?? null, + createdAt: data.createdAt ?? '', + messages, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Conversation ID' }, + agentId: { type: 'string', description: 'Agent ID', optional: true }, + phoneNumberId: { type: 'string', description: 'Phone number ID' }, + phoneNumber: { type: 'string', description: 'Phone number' }, + participant: { type: 'string', description: 'External participant phone number' }, + lastMessageAt: { type: 'string', description: 'ISO 8601 timestamp' }, + messageCount: { type: 'number', description: 'Number of messages' }, + metadata: { + type: 'json', + description: 'Custom metadata stored on the conversation', + optional: true, + }, + createdAt: { type: 'string', description: 'ISO 8601 timestamp' }, + messages: { + type: 'array', + description: 'Messages in the conversation', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID' }, + body: { type: 'string', description: 'Message body' }, + fromNumber: { type: 'string', description: 'Sender phone number' }, + toNumber: { type: 'string', description: 'Recipient phone number' }, + direction: { type: 'string', description: 'inbound or outbound' }, + channel: { + type: 'string', + description: 'Channel (sms, mms, etc.)', + optional: true, + }, + mediaUrl: { + type: 'string', + description: 'Media URL if any', + optional: true, + }, + receivedAt: { type: 'string', description: 'ISO 8601 timestamp' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index b365143a00..12cdda7d22 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -31,6 +31,30 @@ import { agentmailUpdateMessageTool, agentmailUpdateThreadTool, } from '@/tools/agentmail' +import { + agentphoneCreateCallTool, + agentphoneCreateContactTool, + agentphoneCreateNumberTool, + agentphoneDeleteContactTool, + agentphoneGetCallTool, + agentphoneGetCallTranscriptTool, + agentphoneGetContactTool, + agentphoneGetConversationMessagesTool, + agentphoneGetConversationTool, + agentphoneGetNumberMessagesTool, + agentphoneGetUsageDailyTool, + agentphoneGetUsageMonthlyTool, + agentphoneGetUsageTool, + agentphoneListCallsTool, + agentphoneListContactsTool, + agentphoneListConversationsTool, + agentphoneListNumbersTool, + agentphoneReactToMessageTool, + agentphoneReleaseNumberTool, + agentphoneSendMessageTool, + agentphoneUpdateContactTool, + agentphoneUpdateConversationTool, +} from '@/tools/agentphone' import { agiloftAttachFileTool, agiloftAttachmentInfoTool, @@ -2903,6 +2927,28 @@ export const tools: Record = { agentmail_update_inbox: agentmailUpdateInboxTool, agentmail_update_message: agentmailUpdateMessageTool, agentmail_update_thread: agentmailUpdateThreadTool, + agentphone_create_call: agentphoneCreateCallTool, + agentphone_create_contact: agentphoneCreateContactTool, + agentphone_create_number: agentphoneCreateNumberTool, + agentphone_delete_contact: agentphoneDeleteContactTool, + agentphone_get_call: agentphoneGetCallTool, + agentphone_get_call_transcript: agentphoneGetCallTranscriptTool, + agentphone_get_contact: agentphoneGetContactTool, + agentphone_get_conversation: agentphoneGetConversationTool, + agentphone_get_conversation_messages: agentphoneGetConversationMessagesTool, + agentphone_get_number_messages: agentphoneGetNumberMessagesTool, + agentphone_get_usage: agentphoneGetUsageTool, + agentphone_get_usage_daily: agentphoneGetUsageDailyTool, + agentphone_get_usage_monthly: agentphoneGetUsageMonthlyTool, + agentphone_list_calls: agentphoneListCallsTool, + agentphone_list_contacts: agentphoneListContactsTool, + agentphone_list_conversations: agentphoneListConversationsTool, + agentphone_list_numbers: agentphoneListNumbersTool, + agentphone_react_to_message: agentphoneReactToMessageTool, + agentphone_release_number: agentphoneReleaseNumberTool, + agentphone_send_message: agentphoneSendMessageTool, + agentphone_update_contact: agentphoneUpdateContactTool, + agentphone_update_conversation: agentphoneUpdateConversationTool, agiloft_attach_file: agiloftAttachFileTool, agiloft_attachment_info: agiloftAttachmentInfoTool, agiloft_create_record: agiloftCreateRecordTool, From 3a11d9949b4a10b48fa6559aad9970c58aee9499 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 23 Apr 2026 14:47:18 -0700 Subject: [PATCH 2/6] fix(agentphone): validate numeric inputs and metadata JSON --- apps/sim/blocks/blocks/agentphone.ts | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/sim/blocks/blocks/agentphone.ts b/apps/sim/blocks/blocks/agentphone.ts index 312b1badf2..7decae0774 100644 --- a/apps/sim/blocks/blocks/agentphone.ts +++ b/apps/sim/blocks/blocks/agentphone.ts @@ -1,3 +1,4 @@ +import { toError } from '@sim/utils/errors' import { AgentPhoneIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' @@ -562,8 +563,16 @@ export const AgentPhoneBlock: BlockConfig = { if (callsSearch) rest.search = callsSearch } + const toFiniteNumber = (value: unknown, field: string): number => { + const parsed = Number(value) + if (!Number.isFinite(parsed)) { + throw new Error(`Invalid numeric value for ${field}: ${String(value)}`) + } + return parsed + } + if (operation === 'get_conversation' && messageLimit !== undefined && messageLimit !== '') { - rest.messageLimit = Number(messageLimit) + rest.messageLimit = toFiniteNumber(messageLimit, 'Message Limit') } if ( @@ -571,7 +580,7 @@ export const AgentPhoneBlock: BlockConfig = { messagesLimit !== undefined && messagesLimit !== '' ) { - rest.limit = Number(messagesLimit) + rest.limit = toFiniteNumber(messagesLimit, 'Limit') } if ( @@ -580,19 +589,19 @@ export const AgentPhoneBlock: BlockConfig = { limit !== undefined && limit !== '' ) { - rest.limit = Number(limit) + rest.limit = toFiniteNumber(limit, 'Limit') } if (offset !== undefined && offset !== '') { - rest.offset = Number(offset) + rest.offset = toFiniteNumber(offset, 'Offset') } if (operation === 'get_usage_daily' && usageDays !== undefined && usageDays !== '') { - rest.days = Number(usageDays) + rest.days = toFiniteNumber(usageDays, 'Days') } if (operation === 'get_usage_monthly' && usageMonths !== undefined && usageMonths !== '') { - rest.months = Number(usageMonths) + rest.months = toFiniteNumber(usageMonths, 'Months') } if (operation === 'update_conversation' && metadata !== undefined) { @@ -601,8 +610,8 @@ export const AgentPhoneBlock: BlockConfig = { } else if (typeof metadata === 'string') { try { rest.metadata = JSON.parse(metadata) - } catch { - rest.metadata = metadata + } catch (error) { + throw new Error(`Invalid JSON for Metadata: ${toError(error).message}`) } } else { rest.metadata = metadata From bbf1c7bd7642fc0734eebbb93ea7d18aa752ce34 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 23 Apr 2026 15:01:34 -0700 Subject: [PATCH 3/6] chore(agentphone): remove dead from fallback in get_number_messages --- apps/sim/tools/agentphone/get_number_messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/tools/agentphone/get_number_messages.ts b/apps/sim/tools/agentphone/get_number_messages.ts index b585ccbc13..744d94e040 100644 --- a/apps/sim/tools/agentphone/get_number_messages.ts +++ b/apps/sim/tools/agentphone/get_number_messages.ts @@ -77,7 +77,7 @@ export const agentphoneGetNumberMessagesTool: ToolConfig< output: { data: (data.data ?? []).map((msg: Record) => ({ id: (msg.id as string) ?? '', - from_: (msg.from_ as string) ?? (msg.from as string) ?? '', + from_: (msg.from_ as string) ?? '', to: (msg.to as string) ?? '', body: (msg.body as string) ?? '', direction: (msg.direction as string) ?? '', From e1f6c5fa4279bdae7a2dcca91f9a65c06407c590 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 23 Apr 2026 15:09:41 -0700 Subject: [PATCH 4/6] fix(agentphone): drop empty-string updates in update_contact --- apps/sim/blocks/blocks/agentphone.ts | 8 ++++---- apps/sim/tools/agentphone/update_contact.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/sim/blocks/blocks/agentphone.ts b/apps/sim/blocks/blocks/agentphone.ts index 7decae0774..ea49087b67 100644 --- a/apps/sim/blocks/blocks/agentphone.ts +++ b/apps/sim/blocks/blocks/agentphone.ts @@ -546,10 +546,10 @@ export const AgentPhoneBlock: BlockConfig = { } if (['create_contact', 'update_contact'].includes(operation as string)) { - if (contactPhoneNumber !== undefined) rest.phoneNumber = contactPhoneNumber - if (contactName !== undefined) rest.name = contactName - if (contactEmail !== undefined) rest.email = contactEmail - if (contactNotes !== undefined) rest.notes = contactNotes + if (contactPhoneNumber) rest.phoneNumber = contactPhoneNumber + if (contactName) rest.name = contactName + if (contactEmail) rest.email = contactEmail + if (contactNotes) rest.notes = contactNotes } if (operation === 'list_contacts' && contactsSearch !== undefined) { diff --git a/apps/sim/tools/agentphone/update_contact.ts b/apps/sim/tools/agentphone/update_contact.ts index 680052e6ed..cf88cbc390 100644 --- a/apps/sim/tools/agentphone/update_contact.ts +++ b/apps/sim/tools/agentphone/update_contact.ts @@ -61,10 +61,10 @@ export const agentphoneUpdateContactTool: ToolConfig< }), body: (params) => { const body: Record = {} - if (params.phoneNumber !== undefined) body.phoneNumber = params.phoneNumber - if (params.name !== undefined) body.name = params.name - if (params.email !== undefined) body.email = params.email - if (params.notes !== undefined) body.notes = params.notes + if (params.phoneNumber) body.phoneNumber = params.phoneNumber + if (params.name) body.name = params.name + if (params.email) body.email = params.email + if (params.notes) body.notes = params.notes return body }, }, From c93ec2408e35bcc4024d938c11ce8fe014ae41c2 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 23 Apr 2026 15:13:16 -0700 Subject: [PATCH 5/6] fix(agentphone): scope limit/offset to list ops and revert stray IdentityCenter change --- apps/docs/components/icons.tsx | 11 ++--------- apps/sim/blocks/blocks/agentphone.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 35a34a6677..cf687a2129 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4713,16 +4713,9 @@ export function IAMIcon(props: SVGProps) { export function IdentityCenterIcon(props: SVGProps) { return ( - - - - - - - - + diff --git a/apps/sim/blocks/blocks/agentphone.ts b/apps/sim/blocks/blocks/agentphone.ts index ea49087b67..3ae4690c7d 100644 --- a/apps/sim/blocks/blocks/agentphone.ts +++ b/apps/sim/blocks/blocks/agentphone.ts @@ -584,15 +584,18 @@ export const AgentPhoneBlock: BlockConfig = { } if ( - operation !== 'get_number_messages' && - operation !== 'get_conversation_messages' && + OFFSET_LIMIT_OPS.includes(operation as (typeof OFFSET_LIMIT_OPS)[number]) && limit !== undefined && limit !== '' ) { rest.limit = toFiniteNumber(limit, 'Limit') } - if (offset !== undefined && offset !== '') { + if ( + OFFSET_LIMIT_OPS.includes(operation as (typeof OFFSET_LIMIT_OPS)[number]) && + offset !== undefined && + offset !== '' + ) { rest.offset = toFiniteNumber(offset, 'Offset') } From 0252f2dae7871db90b358de91e8bba16397bc3a4 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 23 Apr 2026 15:14:35 -0700 Subject: [PATCH 6/6] lint --- apps/docs/components/icons.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index cf687a2129..35a34a6677 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4713,9 +4713,16 @@ export function IAMIcon(props: SVGProps) { export function IdentityCenterIcon(props: SVGProps) { return ( - + + + + + + + +