UNPKG

phonic

Version:
811 lines (659 loc) 19.2 kB
# Phonic Node.js SDK Node.js library for the Phonic API. - [Installation](#installation) - [Setup](#setup) - [Agents](#agents) - [List agents](#list-agents) - [Get agent](#get-agent) - [Create agent](#create-agent) - [Update agent](#update-agent) - [Upsert agent](#upsert-agent) - [Delete agent](#delete-agent) - [Tools](#tools) - [List tools](#list-tools) - [Get tool](#get-tool) - [Create tool](#create-tool) - [Update tool](#update-tool) - [Delete tool](#delete-tool) - [Voices](#voices) - [List voices](#list-voices) - [Get voice](#get-voice) - [Conversations](#conversations) - [List conversations](#list-conversations) - [Get conversation by id](#get-conversation-by-id) - [Get conversation by external id](#get-conversation-by-external-id) - [Outbound call](#outbound-call) - [Outbound call using own Twilio account](#outbound-call-using-own-twilio-account) - [STS via WebSocket](#sts-via-websocket) - [Messages that Phonic sends back to you](#messages-that-phonic-sends-back-to-you) - [Projects](#projects) - [List projects](#list-projects) - [Get project](#get-project) - [Create project](#create-project) - [Update project](#update-project) - [Delete project](#delete-project) - [License](#license) ## Installation ```bash npm i phonic ``` ## Setup Grab an API key from the [Phonic API Keys](https://phonic.co/api-keys) section and pass it to the Phonic constructor. ```ts import { Phonic } from "phonic"; const phonic = new Phonic("ph_..."); ``` ## Agents ### List agents ```ts const result = await phonic.agents.list({ project: "main" }); ``` ### Get agent Returns an agent by name or ID. ```ts const result = await phonic.agents.get("chris", { project: "main" }); ``` ### Create agent ```ts const result = await phonic.agents.create({ name: "chris", // Optional fields project: "main", // Defaults to "main" phoneNumber: "assign-automatically", // Defaults to null timezone: "Australia/Melbourne", // Defaults to "America/Los_Angeles" audioFormat: "mulaw_8000", // Defaults to "pcm_44100". Must be "mulaw_8000" when `phoneNumber` is "assign-automatically" voiceId: "sarah", // Defaults to "grant" welcomeMessage: "Hello, how can I help you?", // Defaults to "" systemPrompt: "You are an expert in {{subject}}. Be kind to {{user_name}}.", // Defaults to "Respond in 1-2 sentences." templateVariables: { subject: { defaultValue: "Maths" }, user_name: { defaultValue: null } }, tools: ["keypad_input", "natural_conversation_ending", "my-custom-tool"], // Defaults to [] noInputPokeSec: 30, // Defaults to null noInputPokeText: "Hey, are you with me?", // Defaults to "Are you still there?" noInputEndConversationSec: 150, // Defaults to 180 boostedKeywords: ["Salamanca Market", "Bonorong Wildlife Sanctuary"], // Defaults to [] configurationEndpoint: { url: "https://myapp.com/webhooks/phonic-config", headers: { Authorization: "Bearer 123" }, timeoutMs: 10000 // Defaults to 3000 } // Defaults to null }); ``` ### Update agent ```ts const result = await phonic.agents.update("chris", { name: "chris", // Optional fields project: "main", phoneNumber: "assign-automatically", // or null timezone: "Australia/Melbourne", voiceId: "sarah", audioFormat: "mulaw_8000", // Must be "mulaw_8000" when `phoneNumber` is "assign-automatically" welcomeMessage: "Hello, how can I help you?", systemPrompt: "You are an expert in {{subject}}. Be kind to {{user_name}}.", templateVariables: { subject: { defaultValue: "Maths" }, user_name: { defaultValue: null } }, tools: ["keypad_input", "natural_conversation_ending", "my-custom-tool"], noInputPokeSec: 30, noInputPokeText: "Hey, are you with me?", noInputEndConversationSec: 150, boostedKeywords: ["Salamanca Market", "Bonorong Wildlife Sanctuary"], configurationEndpoint: { url: "https://myapp.com/webhooks/phonic-config", headers: { Authorization: "Bearer 123" }, timeoutMs: 7000 } }); ``` ### Upsert agent ```ts const result = await phonic.agents.upsert({ name: "chris", // Optional fields project: "main", phoneNumber: "assign-automatically", // or null timezone: "Australia/Melbourne", voiceId: "sarah", audioFormat: "mulaw_8000", // Must be "mulaw_8000" when `phoneNumber` is "assign-automatically" welcomeMessage: "Hello, how can I help you?", systemPrompt: "You are an expert in {{subject}}. Be kind to {{user_name}}.", templateVariables: { subject: { defaultValue: "Maths" }, user_name: { defaultValue: null } }, tools: ["keypad_input", "natural_conversation_ending", "my-custom-tool"], noInputPokeSec: 30, noInputPokeText: "Hey, are you with me?", noInputEndConversationSec: 150, boostedKeywords: ["Salamanca Market", "Bonorong Wildlife Sanctuary"], configurationEndpoint: { url: "https://myapp.com/webhooks/phonic-config", headers: { Authorization: "Bearer 123" }, timeoutMs: 7000 } }); ``` ### Delete agent ```ts const result = await phonic.agents.delete("chris", { // Optional fields project: "main", }); ``` ## Tools ### List tools ```ts const result = await phonic.tools.list({ project: "customer-support" // Optional. Defaults to "main". }); ``` ### Get tool Returns a tool by name or ID. ```ts const result = await phonic.tools.get("next_invoice", { project: "customer-support" // Optional. Defaults to "main". }); ``` ### Create tool #### Create webhook tool ```ts const result = await phonic.tools.create({ project: "customer-support", // Optional. Defaults to "main". name: "next_invoice", description: "Returns the next invoice of the given user", type: "custom_webhook", executionMode: "sync", endpointMethod: "POST", endpointUrl: "https://myapp.com/webhooks/next-invoice", endpointHeaders: { Authorization: "Bearer 123" }, endpointTimeoutMs: 20000, // Optional, defaults to 15000 parameters: [ { type: "string", name: "user", description: "Full name of the user to get the invoice for", isRequired: true }, { type: "array", itemType: "string", name: "invoice_items", description: "List of invoice items", isRequired: false }, { type: "number", name: "invoice_total", description: "Total invoice amount in USD", isRequired: true }, ] }); ``` #### Create WebSocket tool WebSocket tools allow you to handle tool execution through the WebSocket connection. When the agent calls a WebSocket tool, you'll receive a `tool_call` message and must respond with a `tool_call_output` message that contains the tool result. ```ts const result = await phonic.tools.create({ project: "customer-support", // Optional. Defaults to "main". name: "get_product_recommendations", description: "Gets personalized product recommendations", type: "custom_websocket", executionMode: "async", toolCallOutputTimeoutMs: 5000, // Optional, defaults to 15000 parameters: [ { type: "string", name: "category", description: "Product category (e.g., 'handbags', 'shoes', 'electronics')", isRequired: true } ] }); ``` To use this tool in a conversation, add it to your agent or config: ```ts // When creating an agent const result = await phonic.agents.create({ name: "shopping-assistant", tools: ["get_product_recommendations"], // ... other config }); // Or, override agent settings when starting a WebSocket conversation const phonicWebSocket = phonic.sts.websocket({ name: "shopping-assistant", tools: ["get_product_recommendations"], // ... other config }); // Handle the tool call when it's invoked phonicWebSocket.onMessage(async (message) => { if (message.type === "tool_call" && message.name === "get_product_recommendations") { const category = message.parameters.category; // Execute your business logic const recommendations = fetchRecommendations(category); // Send the result back phonicWebSocket.[sendToolCallOutput](#send-tool-output-to-phonic)({ toolCallId: message.tool_call_id, output: { products: recommendations, total: recommendations.length } }); } }); ``` ### Update tool Updates a tool by ID or name. All fields are optional - only provided fields will be updated. ```ts const result = await phonic.tools.update("next_invoice", { project: "customer-support", // Optional. Defaults to "main". name: "next_invoice_updated", description: "Updated description.", type: "custom_webhook", executionMode: "sync", endpointMethod: "POST", endpointUrl: "https://myapp.com/webhooks/next-invoice-updated", endpointHeaders: { Authorization: "Bearer 456" }, endpointTimeoutMs: 30000, parameters: [ { type: "string", name: "user", description: "Full name of the user to get the invoice for", isRequired: true }, { type: "array", itemType: "string", name: "invoice_items", description: "List of invoice items", isRequired: true }, { type: "number", name: "invoice_total", description: "Total invoice amount in USD", isRequired: true }, ] }); ``` For WebSocket tools, you would use `toolCallOutputTimeoutMs` instead of the endpoint fields: ```ts const result = await phonic.tools.update("get_product_recommendations", { description: "Updated product recommendation tool", toolCallOutputTimeoutMs: 7000 }); ``` ### Delete tool Deletes a tool by ID or name. ```ts const result = await phonic.tools.delete("next_invoice", { project: "customer-support" // Optional. Defaults to "main". }); ``` ## Voices ### List voices ```ts const result = await phonic.voices.list({ model: "merritt" }); ``` ### Get voice ```ts const result = await phonic.voices.get("grant"); ``` ## Conversations ### List conversations ```ts const result = await phonic.conversations.list({ project: "main", durationMin: 10, // sec durationMax: 20, // sec startedAtMin: "2025-04-17", // 00:00:00 UTC time is assumed startedAtMax: "2025-09-05T10:30:00.000Z", }); ``` ### Get conversation by id ```ts const result = await phonic.conversations.get("conv_b1804883-5be4-42fe-b1cf-aa84450d5c84"); ``` ### Get conversation by external id ```ts const result = await phonic.conversations.getByExternalId({ project: "main", externalId: "CAdb9c032c809fec7feb932ea4c96d71e1" }); ``` ### Outbound call ```ts const result = await phonic.conversations.outboundCall("+19189396241", { // Optional fields agent: "chris", template_variables: { customer_name: "David", subject: "Chess" }, }); ``` ### Outbound call using own Twilio account In Twilio, create a restricted API key with the following permissions: `voice -> calls -> read` and `voice -> calls -> create`. ```ts const result = await phonic.conversations.twilio.outboundCall( { account_sid: "AC...", api_key_sid: "SK...", api_key_secret: "...", from_phone_number: "+19189372905", to_phone_number: "+19189396241", }, { // Optional fields agent: "chris", welcome_message: "Hello, how can I help you?", project: "main", system_prompt: "You are a helpful assistant.", voice_id: "grant", enable_silent_audio_fallback: true, vad_prebuffer_duration_ms: 1800, vad_min_speech_duration_ms: 40, vad_min_silence_duration_ms: 550, vad_threshold: 0.6, tools: ["keypad_input", "natural_conversation_ending"] } ); ``` ## STS via WebSocket To start a conversation, open a WebSocket connection: ```ts const phonicWebSocket = phonic.sts.websocket({ input_format: "mulaw_8000", // Optional fields agent: "chris", template_variables: { customer_name: "David", subject: "Chess" }, welcome_message: "Hello, how can I help you?", voice_id: "grant", output_format: "mulaw_8000", }); ``` Stream input (user) audio chunks: ```ts phonicWebSocket.audioChunk({ audio: "...", // base64 encoded audio chunk }); ``` Process messages that Phonic sends back to you: ```ts phonicWebSocket.onMessage((message) => { switch (message.type) { case "input_text": { console.log(`User: ${message.text}`); break; } case "audio_chunk": { // Send the audio chunk to Twilio, for example: twilioWebSocket.send( JSON.stringify({ event: "media", streamSid: "...", media: { payload: message.audio, }, }), ); break; } case "tool_call": { // Handle WebSocket tool calls console.log(`Tool ${message.tool_name} called with parameters:`, message.parameters); // Example: Process a product recommendations tool call if (message.tool_name === "get_product_recommendations") { const category = message.parameters.category; const recommendations = fetchRecommendations(category); phonicWebSocket.[sendToolCallOutput](#send-tool-output-to-phonic)({ toolCallId: message.tool_call_id, output: { products: recommendations, total: recommendations.length } }); } break; } } }); ``` Update the system prompt mid-conversation: ```ts phonicWebSocket.updateSystemPrompt({ systemPrompt: "..." }) ``` Set an external id for the conversation (can be the Twilio Call SID, for example): ```ts phonicWebSocket.setExternalId({ externalId: "..." }) ``` ### Send tool output to Phonic When you receive a `tool_call` message for a WebSocket tool, you must respond with the tool's output using `sendToolCallOutput()`. This method sends the execution result back to Phonic so the conversation can continue. ```ts phonicWebSocket.sendToolCallOutput({ toolCallId: "tool_call_123...", // The tool_call_id from the tool_call message output: "Success! Found 2 items" // Can be any JSON-serializable value (string, number, object, array, etc.) }); // Or with an object: phonicWebSocket.sendToolCallOutput({ toolCallId: message.tool_call_id, output: { result: "success", data: { items: ["item1", "item2"], total: 2 } } }); ``` **Important notes:** - You must use the exact `tool_call_id` received in the `tool_call` message - The `output` can be any JSON-serializable value (string, number, boolean, object, array, etc.) - If you don't send a response within `toolCallOutputTimeoutMs`, the tool call will be marked as failed. To end the conversation, close the WebSocket: ```ts phonicWebSocket.close(); ``` You can also listen for close and error events: ```ts phonicWebSocket.onClose((event) => { console.log( `Phonic WebSocket closed with code ${event.code} and reason "${event.reason}"`, ); }); phonicWebSocket.onError((event) => { console.log(`Error from Phonic WebSocket: ${event.message}`); }); ``` ### Messages that Phonic sends back to you #### `conversation_created` ```ts { type: "conversation_created"; conversation_id: string; } ``` Sent when the conversation has been successfully created. #### `ready_to_start_conversation` ```ts { type: "ready_to_start_conversation"; } ``` Sent when Phonic is ready to begin processing audio. You should start sending audio chunks after receiving this message. #### `input_text` ```ts { type: "input_text"; text: string; } ``` Phonic sends this message once user's audio is transcribed. #### `audio_chunk` ```ts { type: "audio_chunk"; audio: string; // base64 encoded array of audio data (each value is in range [-32768..32767] for "pcm_44100" output format, and in range [0..255] for "mulaw_8000" output format) text: string; // May potentially be "", but will typically be one word. } ``` These are the assistant response audio chunks. #### `audio_finished` ```ts { type: "audio_finished"; } ``` Sent after the last "audio_chunk" is sent. #### `user_started_speaking` ```ts { type: "user_started_speaking"; } ``` Sent when the user begins speaking. #### `user_finished_speaking` ```ts { type: "user_finished_speaking"; } ``` Sent when the user stops speaking. #### `interrupted_response` ```ts { type: "interrupted_response", text: string, // partial assistant response that cuts off approximately where the user interrupted } ``` Sent when the user interrupts the assistant, after the user has finished speaking. #### `assistant_chose_not_to_respond` ```ts { type: "assistant_chose_not_to_respond"; } ``` Sent when the assistant decides not to respond to the user's input. #### `assistant_ended_conversation` ```ts { type: "assistant_ended_conversation"; } ``` Sent when the assistant decides to end the conversation. #### `tool_call` ```ts { type: "tool_call"; tool_call_id: string; tool_name: string; parameters: Record<string, unknown>; } ``` Sent when a WebSocket tool is called during the conversation. When you receive this message, you should: 1. Process the tool call using the provided `tool_name` and `parameters` 2. Send back the result using [`phonicWebSocket.sendToolCallOutput`](#send-tool-output-to-phonic) This is only sent for tools created with `type: "custom_websocket"`. Webhook tools are executed server-side and only send `tool_call_output_processed` messages. #### `tool_call_output_processed` ```ts { type: "tool_call_output_processed"; tool_call_id: string; tool: { id: string; name: string; }; endpoint_url: string | null; endpoint_timeout_ms: number | null; endpoint_called_at: string | null; request_body: { call_info: { from_phone_number: string; to_phone_number: string; } | null; [key: string]: unknown; } | null; response_body: Record<string, unknown> | null; response_status_code: number | null; timed_out: boolean | null; error_message: string | null; } ``` Sent when a tool is called during the conversation. Built-in tools will have null values for endpoint-related fields. When a custom tool is called, the `request_body` field always includes a `call_info` field. If the conversation is not a phone call, `call_info` will be `null`. If it is a phone call, `call_info` will be an object with `from_phone_number` and `to_phone_number` fields, both formatted as E.164 phone numbers (e.g., "+1234567890"). #### `error` ```ts { type: "error"; error: { message: string; code?: string; }; param_errors?: Record<string, string>; } ``` Sent when an error occurs during the conversation. ## Projects ### List projects ```ts const result = await phonic.projects.list(); ``` ### Get project Returns a project by name or ID. ```ts const result = await phonic.projects.get("main"); ``` ### Create project ```ts const result = await phonic.projects.create({ name: "customer-support", }); ``` ### Update project ```ts const result = await phonic.projects.update("customer-support", { name: "updated-customer-support", defaultAgent: "another-agent" }); ``` ### Delete project ```ts const result = await phonic.projects.delete("customer-support"); ``` ## License MIT