From 1a1dd7448ad09b1079bb2aa06f17a68017232788 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 28 Oct 2026 23:25:23 +0000 Subject: [PATCH] Add Phonic plugin Co-authored-by: Danny Jiang --- .cursor-plugin/marketplace.json | 5 ++ README.md | 1 + third_party/phonic/.cursor-plugin/plugin.json | 33 ++++++++++ third_party/phonic/LICENSE | 21 ++++++ third_party/phonic/README.md | 61 ++++++++++++++++++ third_party/phonic/assets/logo.png | Bin 0 -> 25656 bytes third_party/phonic/mcp.json | 8 +++ .../phonic/skills/phonic-agents/SKILL.md | 30 +++++++++ .../phonic/skills/phonic-get-started/SKILL.md | 43 ++++++++++++ .../skills/phonic-improvement-loop/SKILL.md | 34 ++++++++++ .../references/mock-tools.md | 7 ++ .../references/responses.md | 29 +++++++++ .../phonic/skills/phonic-post-call/SKILL.md | 28 ++++++++ .../phonic/skills/phonic-steering/SKILL.md | 30 +++++++++ .../phonic/skills/phonic-tools/SKILL.md | 29 +++++++++ .../phonic/skills/phonic-voices/SKILL.md | 32 +++++++++ .../phonic/skills/phonic-websockets/SKILL.md | 39 +++++++++++ third_party/phonic/skills/phonic/SKILL.md | 36 +++++++++++ 18 files changed, 466 insertions(+) create mode 100644 third_party/phonic/.cursor-plugin/plugin.json create mode 100644 third_party/phonic/LICENSE create mode 100644 third_party/phonic/README.md create mode 100644 third_party/phonic/assets/logo.png create mode 100644 third_party/phonic/mcp.json create mode 100644 third_party/phonic/skills/phonic-agents/SKILL.md create mode 100644 third_party/phonic/skills/phonic-get-started/SKILL.md create mode 100644 third_party/phonic/skills/phonic-improvement-loop/SKILL.md create mode 100644 third_party/phonic/skills/phonic-improvement-loop/references/mock-tools.md create mode 100644 third_party/phonic/skills/phonic-improvement-loop/references/responses.md create mode 100644 third_party/phonic/skills/phonic-post-call/SKILL.md create mode 100644 third_party/phonic/skills/phonic-steering/SKILL.md create mode 100644 third_party/phonic/skills/phonic-tools/SKILL.md create mode 100644 third_party/phonic/skills/phonic-voices/SKILL.md create mode 100644 third_party/phonic/skills/phonic-websockets/SKILL.md create mode 100644 third_party/phonic/skills/phonic/SKILL.md diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index a548a5882..ba2d8e8cf 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -672,6 +672,11 @@ "grokbot": "0.49.0", "sand": "0.49.0" } + }, + { + "name": "phonic", + "source": "third_party/phonic", + "description": "Build and test Phonic voice agents: configure agents, discover voices, search docs, and work with phone calls, browser voice, tools, and post-call processing." } ] } diff --git a/README.md b/README.md index ab6550a25..3464411eb 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Official Cursor plugins for popular developer tools, frameworks, and SaaS produc | `gusto` | [Gusto](third_party/gusto/) | Cursor | Integrations | Work with payroll, employees, contractors, time off, and reports. | | `gopuff` | [Gopuff](third_party/gopuff/) | Cursor | Integrations | Shop Gopuff from Grok Bot through Gopuff's official MCP server. Sign in with your Gopuff account; Gopuff handles the sign-in, and your searches, carts, and orders go to Gopuff under that account, so any order you place is charged and delivered like a normal Gopuff order. | | `jobber` | [Jobber](third_party/jobber/) | Cursor | Integrations | Work with clients, requests, quotes, jobs, visits, and invoices. | +| `phonic` | [Phonic](third_party/phonic/) | Phonic | Integrations | Build and test Phonic voice agents: configure agents, discover voices, search docs, and work with phone calls, browser voice, tools, and post-call processing. | | `databricks-genie-one` | [Databricks Genie One](third_party/databricks-genie-one/) | Cursor | Integrations | Ask Databricks Genie questions about your workspace's data in plain language. | | `lark` | [Lark](third_party/lark/) | Cursor | Integrations | Search, create, and update Lark Suite docs, and read Lark messages and groups. | | `feishu` | [Feishu](third_party/feishu/) | Cursor | Integrations | Search, create, and update Feishu docs, and read Feishu messages and groups. | diff --git a/third_party/phonic/.cursor-plugin/plugin.json b/third_party/phonic/.cursor-plugin/plugin.json new file mode 100644 index 000000000..ed12bc5a1 --- /dev/null +++ b/third_party/phonic/.cursor-plugin/plugin.json @@ -0,0 +1,33 @@ +{ + "name": "phonic", + "displayName": "Phonic", + "version": "0.2.0", + "description": "Build and test Phonic voice agents: configure agents, discover voices, search docs, and work with phone calls, browser voice, tools, and post-call processing.", + "author": { + "name": "Phonic", + "email": "support@phonic.ai" + }, + "homepage": "https://phonic.ai", + "repository": "https://github.com/Phonic-Co/skills", + "license": "MIT", + "logo": "assets/logo.png", + "keywords": [ + "phonic", + "voice", + "voice-ai", + "voice-agents", + "conversational-ai", + "speech-to-speech", + "phone-calls", + "telephony", + "mcp" + ], + "category": "integrations", + "tags": [ + "phonic", + "voice", + "mcp" + ], + "skills": "./skills/", + "mcpServers": "./mcp.json" +} diff --git a/third_party/phonic/LICENSE b/third_party/phonic/LICENSE new file mode 100644 index 000000000..e6459a161 --- /dev/null +++ b/third_party/phonic/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Phonic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/phonic/README.md b/third_party/phonic/README.md new file mode 100644 index 000000000..3920e6e2e --- /dev/null +++ b/third_party/phonic/README.md @@ -0,0 +1,61 @@ +# Phonic + +Cursor plugin that connects agents to [Phonic](https://phonic.ai) through Phonic's hosted [Model Context Protocol](https://modelcontextprotocol.io/) server, plus Phonic's skills for building voice agents. + +Build and test Phonic voice agents from your coding assistant. Connect your Phonic account, search product documentation, configure agents, discover voices, and get ready for your first conversation. + +## Install + +1. Open **Cursor Settings → Plugins**. +2. Search for **Phonic**. +3. Click **Install**, then complete the Phonic sign-in prompt. + +Or run `/add-plugin phonic` in chat. + +## MCP + +```json +{ + "mcpServers": { + "phonic": { + "type": "http", + "url": "https://mcp.phonic.ai/mcp" + } + } +} +``` + +Auth is OAuth through the client's MCP sign-in flow; an API key also works. OAuth selects the workspace for the connection. + +## Before you connect + +You need a Phonic account and workspace. Sign up at [phonic.ai](https://phonic.ai). + +## Skills + +| Skill | When to use it | +| --- | --- | +| `phonic-get-started` | Connect MCP, build an agent, and run a first conversation. | +| `phonic` | Choose an integration and find the relevant implementation skill. | +| `phonic-agents` | Configure agents, phone calls, prompts, and project operations. | +| `phonic-voices` | Discover and preview voices. | +| `phonic-websockets` | Build browser voice sessions and server audio bridges. | +| `phonic-tools` | Connect tools and transfer callers. | +| `phonic-post-call` | Process webhooks, extractions, and evaluations. | +| `phonic-improvement-loop` | Run prompt experiments with simulated conversations. | +| `phonic-steering` | Update live conversations. | + +The hosted runtime is the source of truth for tool names and schemas. Documentation search is provided through `search_docs` on the same connection. + +## Docs + +- Phonic: https://phonic.ai +- Documentation: https://docs.phonic.ai +- Source: https://github.com/Phonic-Co/skills +- Support: support@phonic.ai + +Skills, MCP config, and logo are copied from [Phonic-Co/skills](https://github.com/Phonic-Co/skills). + +## License + +MIT diff --git a/third_party/phonic/assets/logo.png b/third_party/phonic/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..940f9fbee8da3c3e67e68ccacb5ad60456b4901e GIT binary patch literal 25656 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 literal 0 HcmV?d00001 diff --git a/third_party/phonic/mcp.json b/third_party/phonic/mcp.json new file mode 100644 index 000000000..cfe1fc59f --- /dev/null +++ b/third_party/phonic/mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "phonic": { + "type": "http", + "url": "https://mcp.phonic.ai/mcp" + } + } +} diff --git a/third_party/phonic/skills/phonic-agents/SKILL.md b/third_party/phonic/skills/phonic-agents/SKILL.md new file mode 100644 index 000000000..0e9e2e135 --- /dev/null +++ b/third_party/phonic/skills/phonic-agents/SKILL.md @@ -0,0 +1,30 @@ +--- +name: phonic-agents +description: Create and configure Phonic agents and phone calls using MCP, SDKs, or REST. Use for saved agents, inbound numbers, outbound calls, prompting, and project operations. +license: MIT +--- + +# Configure Phonic agents and calls + +SDKs: `npm install phonic` / `pip install phonic`. Entry points: `new PhonicClient({ apiKey })` (Node), `Phonic(api_key=...)` / `AsyncPhonic(...)` (Python). REST base: `https://api.phonic.ai/v1` with `Authorization: Bearer $PHONIC_API_KEY`. Always use the `.ai` domain for API and docs URLs — `phonic.co` addresses still resolve for backward compatibility, but all new code should use `phonic.ai`. + +Fetch live docs rather than from memory: use the index at [llms.txt](https://docs.phonic.ai/llms.txt). For the REST API, see [openapi.json](https://docs.phonic.ai/openapi.json). For the WebSocket protocol, see [asyncapi.json](https://docs.phonic.ai/asyncapi.json). If an endpoint does not appear in these docs, it doesn't exist, do not invent one. + +## SDK surface + +- Agents: `client.agents.create/get/list/update/upsert/delete`. `upsert` is create-or-update by name (idempotent). Agents are identified by name. +- Outbound calls: `client.conversations.outboundCall(...)` — greeting goes in `config.welcome_message`. + +## Prompting voice agents + +Voice prompts differ from chat prompts (speech formatting, silence handling, naturalness) — use [the prompting guide](https://docs.phonic.ai/docs/build/agents/prompting_guide.md). + +## Operations + +- **Projects** organize agents/tools/numbers within a workspace; use one project per tenant/product line rather than a new workspace. API keys are workspace-level. +- **Rate limits**: see [the rate limits page](https://docs.phonic.ai/docs/platform/rate_limits.md); high-volume outbound campaigns need client-side concurrency caps. +- **Observability**: per-call outcomes via the webhook events above; live read-only monitoring via the live WebSocket; [audit logs](https://docs.phonic.ai/docs/platform/audit-logs.md). + +## Campaign and batch dialers + +- **Campaign/batch dialers**: claim each number (durable in-progress state) before dialing and design the claim so a second runner instance can't grab it too — per-run completion state alone doesn't prevent two processes from double-dialing. diff --git a/third_party/phonic/skills/phonic-get-started/SKILL.md b/third_party/phonic/skills/phonic-get-started/SKILL.md new file mode 100644 index 000000000..b31abd786 --- /dev/null +++ b/third_party/phonic/skills/phonic-get-started/SKILL.md @@ -0,0 +1,43 @@ +--- +name: phonic-get-started +description: Get started on phonic.ai via MCP, connect Phonic, and build and run a first voice conversation. Use for Phonic setup, a first conversation, and choosing the next implementation workflow. +license: MIT +--- + +# Get started with Phonic + +When the user says “get started on phonic.ai via MCP,” “connect Phonic,” or “build my first Phonic conversation,” carry the task through connection, agent setup, and a first conversation. Use the user's existing context, make reasonable reversible defaults, and ask only for missing details that block the next step. + +## Connect and authenticate + +Inspect the client's available tools and MCP configuration before adding anything. Reuse a working Phonic connection. Otherwise configure `https://mcp.phonic.ai/mcp` through the client's supported setup flow; consult the client-specific installation instructions in [the package README](../../README.md). Preserve other MCP entries. Use the client's OAuth flow for sign-in and workspace selection; never ask the user to paste a token into chat. + +Perform supported setup steps directly when the environment permits. If a login, client approval, or restart requires the user, give the exact next action and resume from that point when completed. Do not claim connection success until a Phonic tool responds. Installing a skill does not itself connect MCP, and connecting MCP does not install skills; automatic selection depends on the host loading this skill. + +Discover the tools actually exposed by the connection; prefixes differ between clients. Use `search_docs` on the same connection for product and API questions. If it is unavailable, use [the documentation index](https://docs.phonic.ai/llms.txt) and check connection status; do not require a separate docs server. + +## Set up the first agent + +Inspect the connected workspace, projects, and existing agents. Use the user's specified project, or an unambiguous existing project; ask only when the choice is unclear. OAuth selects the workspace for the connection; selecting a project does not switch workspaces. + +Use the discovered API tools for account operations. Reuse a suitable agent or create a small demo agent within the user's requested scope. If no use case is given, start with a simple friendly assistant that can hold a short conversation, without business integrations. Use a supported model and available voice from the current schemas/catalog. Avoid making a long questionnaire a prerequisite to trying the product. + +## Run the first conversation + +Follow the user's requested conversation surface. For an unspecified first demo, prefer a browser or dashboard conversation so a phone number is not required; use current docs to find the supported test interface or implement a minimal browser session if that is the user's task. Use a returned or documented destination rather than inventing a dashboard URL. Keep API keys server-side when building a browser session. + +For an outbound call, obtain the user's intended recipient if missing, validate with `dry_run: true`, then place the requested test call once validation succeeds and the task authorizes calling that recipient. A dry run is not a completed conversation. Do not repeat a potentially successful call after an ambiguous error without checking its status. + +Verify the conversation from available status/transcript events and invite the user to speak when microphone interaction is required. Report the selected workspace/project, agent, and observed result concisely. If the host cannot capture audio or start the chosen surface, provide the exact action or runnable entry point that lets the user finish, and state that the conversation has not yet been verified. Continue through available steps rather than stopping after a setup plan. + +## Specialized tasks + +- Use `phonic-improvement-loop` for prompt experiments and Replay or Responses simulations. +- Use `phonic-steering` for context and reply controls during a live conversation. +- Use `phonic-voices` for voice selection and allocation. + +Load a specialized skill only when relevant and installed; otherwise use the live documentation. + +## Continue building + +For implementation details, use the installed `phonic` routing skill or the relevant `phonic-agents`, `phonic-websockets`, `phonic-tools`, or `phonic-post-call` skill. If a skill is unavailable, use the live docs. diff --git a/third_party/phonic/skills/phonic-improvement-loop/SKILL.md b/third_party/phonic/skills/phonic-improvement-loop/SKILL.md new file mode 100644 index 000000000..da552f15a --- /dev/null +++ b/third_party/phonic/skills/phonic-improvement-loop/SKILL.md @@ -0,0 +1,34 @@ +--- +name: phonic-improvement-loop +description: Run Phonic prompt experiments and conversation simulations with Replay or Responses. Use to debug a conversation, compare prompt changes, or simulate tool-dependent behavior. +license: MIT +--- + +# Phonic prompt experiments and simulations + +Use Replay and Responses to investigate a conversation or compare a proposed prompt change. Read the [Replay API](https://docs.phonic.ai/api-reference/conversation-items/replay.md) or [Responses API](https://docs.phonic.ai/api-reference/responses/create.md) for the selected method. + +## Scope the experiment + +Identify the behavior being changed, the affected conversations and tool paths, and the consequence of a regression. Match the experiment to that scope: + +- **One conversation or a small wording change:** inspect the failure, replay the relevant completed assistant turns, and check a few related cases. A mock server or formal dataset split is unnecessary for a local diagnosis. +- **Broader prompt or tool-use changes:** compare the baseline and candidate on representative conversations, including ordinary cases, known failures, and affected tool paths. For repeated prompt development, separate train (development), validation (candidate selection), and test (final assessment) sets. Keep related conversation turns and synthetic variants in the same split. +- **High-volume or consequential deployments:** consider simulations with varied tool outputs, failures, and rare cases. A mock tool server is useful when tool behavior materially affects the result; scale, tool complexity, and failure impact matter more than an enterprise label. Read [mock-tool simulations](references/mock-tools.md) only when this larger experiment is warranted. + +Choose sample counts and a stopping condition from the user's objective, coverage, cost, and available time. Respect a supplied budget; otherwise propose the size and estimated generation count before a substantial batch. There is no fixed iteration limit. Define what a successful reply or tool call looks like before comparing results. + +## Choose Replay or Responses + +- **Replay:** use `replay_conversation_item` when exposed, or `POST /v1/conversation_items/{id}/replay`, for a completed assistant turn from an ended, transcript-retained conversation. Omit `system_prompt` for the as-recorded baseline; supply it for the candidate. Whole-conversation audio replay is a different operation. +- **Responses:** use the discovered `create_response` tool or `POST /v1/responses` for synthetic or edited histories, alternate tool outputs, and multi-turn simulations. Workspace enablement may be required. Read [Responses simulation requests](references/responses.md) when using this method. + +## Run and compare + +Keep inputs, configuration, sample counts, and grading criteria consistent between baseline and candidate. Inspect generated text, proposed tool calls, and actions together; check tool names and arguments rather than judging plausible speech alone. A proposed tool call does not prove an external action executed. Text simulations do not measure voice quality, interruptions, or live latency. + +For a local experiment, report what the related cases establish and their limits. For broader experiments, develop changes on train cases, select using validation, and assess the chosen prompt on untouched test cases. If test results guide a further edit, use a new test set before making a final generalization claim. If grading criteria change, regrade baseline and candidate consistently. + +Stop when the target is met, the agreed resource limit is reached, or further experiments are not justified by the evidence. Separate failed Replay or Responses API requests from poor generated replies and deliberately simulated tool errors. Report attempted, successful, and failed requests, sample sizes, representative failures, and total generations; do not silently drop missing samples. Fix access/eligibility errors before retrying, and bound transient retries within the experiment's resource limit. + +Return the proposed prompt diff and concise experiment results, including regressions and uncertainty. Raw outputs may contain conversation data and tool configuration; do not commit them by default. Updating a saved agent must be within the user's authorized task. diff --git a/third_party/phonic/skills/phonic-improvement-loop/references/mock-tools.md b/third_party/phonic/skills/phonic-improvement-loop/references/mock-tools.md new file mode 100644 index 000000000..dfa7688ef --- /dev/null +++ b/third_party/phonic/skills/phonic-improvement-loop/references/mock-tools.md @@ -0,0 +1,7 @@ +# Mock tool simulations + +For scenarios sensitive to tool results, build a mock tool server or a local fixture generator with configurable outcomes. For example, simulate A at 40%, B at 30%, C at 25%, and D at 5%. These are example weights, not defaults. Match real response schemas, include relevant error or empty-result cases, and keep business side effects simulated. + +For Responses, the test harness obtains an output from the mock and appends the matching `tool_call_output` to the history before generating the next turn. The Responses endpoint does not call the mock server for you. Replay uses recorded context; use Responses when changing historical tool outputs. To test an actual tool integration, connect the mock server to a dedicated test agent using the documented tool interface. + +Reuse a recorded output schedule or seeded fixtures for baseline and candidate comparisons. A random sample of 100 runs will not necessarily contain exactly 40/30/25/5 outcomes; use fixed counts when exact coverage matters and random sampling when testing the distribution matters. Report observed counts and agent success by outcome, including rare failures that aggregate success can hide. diff --git a/third_party/phonic/skills/phonic-improvement-loop/references/responses.md b/third_party/phonic/skills/phonic-improvement-loop/references/responses.md new file mode 100644 index 000000000..a478a2f2c --- /dev/null +++ b/third_party/phonic/skills/phonic-improvement-loop/references/responses.md @@ -0,0 +1,29 @@ +# Responses simulation requests + +Use `POST https://api.phonic.ai/v1/responses`, or the discovered MCP `create_response` tool. Read the [current API contract](https://docs.phonic.ai/api-reference/responses/create.md) before constructing requests. + +The endpoint is stateless: supply `system_prompt` and `input` on each request. It generates candidate responses without creating a conversation or executing the proposed tools/actions. It is experimental and may return 404 when not enabled for the workspace; explain the access requirement rather than retrying an unavailable feature. + +## Select inputs + +- Use this workflow for fabricated histories, edited histories, and simulated follow-up turns. +- For an existing recorded assistant turn, use `phonic-improvement-loop` or the documented conversation-item replay endpoint instead. Audio replay is a separate operation. +- Choose inputs and sample counts to match the experiment [scoped in the main skill](../SKILL.md#scope-the-experiment). Text simulation does not measure audio quality, interruptions, or live latency. + +Minimal request: + +```json +{ + "system_prompt": "You are a receptionist. Ask one concise question at a time.", + "input": [{ "role": "user", "text": "Can I book an appointment tomorrow?" }], + "num_responses": 3 +} +``` + +## Tools and multiple turns + +Define simulated custom tools inline in `tool_definitions`, using the current Phonic schema. Referenced `tools` currently support built-ins such as transfers and `choose_not_to_respond`; do not assume a stored webhook, context, MCP, or WebSocket tool can be referenced here. + +To continue a simulation, choose one returned candidate and append it as an assistant input item, then append the next user input. Do not combine multiple alternative candidates into one history. If the candidate calls tools, supply corresponding `tool_call_output` items immediately after it, preserving tool-call IDs and using the documented input schema. Use clearly labeled fixture outputs; do not execute real business side effects merely to continue a simulation. If the candidate returns an action, inspect its meaning before deciding whether the scenario should end. + +For a runnable REST example, see [Phonic examples](https://github.com/Phonic-Co/phonic-examples). diff --git a/third_party/phonic/skills/phonic-post-call/SKILL.md b/third_party/phonic/skills/phonic-post-call/SKILL.md new file mode 100644 index 000000000..9d3fa0d9d --- /dev/null +++ b/third_party/phonic/skills/phonic-post-call/SKILL.md @@ -0,0 +1,28 @@ +--- +name: phonic-post-call +description: Handle Phonic conversation webhooks, extractions, and evaluations. Use for post-call processing, analysis results, and webhook receivers. +license: MIT +--- + +# Process Phonic conversation results + +SDKs: `npm install phonic` / `pip install phonic`. Entry points: `new PhonicClient({ apiKey })` (Node), `Phonic(api_key=...)` / `AsyncPhonic(...)` (Python). REST base: `https://api.phonic.ai/v1` with `Authorization: Bearer $PHONIC_API_KEY`. Always use the `.ai` domain for API and docs URLs — `phonic.co` addresses still resolve for backward compatibility, but all new code should use `phonic.ai`. + +Fetch live docs rather than from memory: use the index at [llms.txt](https://docs.phonic.ai/llms.txt). For the REST API, see [openapi.json](https://docs.phonic.ai/openapi.json). For the WebSocket protocol, see [asyncapi.json](https://docs.phonic.ai/asyncapi.json). If an endpoint does not appear in these docs, it doesn't exist, do not invent one. + +## Webhooks and post-call steps + +Webhooks can indicate conversation progress: `conversation.ended`, `conversation.analysis`, `conversation.transferred`. A typical post-call pipeline looks like: + +1. **`conversation.ended`** fires when the call ends — log the outcome and kick off your downstream steps from here. +2. **`conversation.analysis`** follows with performance metrics: its `conversation` payload carries `latencies_ms` and `interruptions_count` (also available via `GET /v1/conversations/{id}/analysis`). It does not contain extraction results. +3. **Extractions** turn the transcript into typed fields you define. Create an extraction schema, then run it per conversation with `POST /v1/conversations/{id}/extractions` and read results with the matching GET — typically from your `conversation.ended` handler. Extractions read the transcript, so they fail if transcripts are deleted; run them promptly if you have a retention policy. +4. **Eval prompts** grade calls against plain-language criteria. Create them at the project level (`name` + a criterion like "the assistant made the required disclosure"), evaluate a conversation via the conversation-evals endpoint, and read results per conversation or listed across the project — useful for compliance checks and QA queues. + +## Webhook receivers + +- **Webhook receivers** — Phonic delivers webhooks via svix, which retries, so the same event can arrive more than once (and concurrently). Build for that: + 1. Verify the svix signature before touching the body. + 2. Durably enqueue the event before returning 2xx; process slow work with a retryable worker. + 3. Store each event id durably (DB unique constraint, not process memory) and skip events you've already processed. + 4. For external side effects, use a stable idempotency key supported by the destination and persist the result. A crash after a side effect but before marking it complete can otherwise duplicate it on retry. A local started/done marker alone does not guarantee exactly-once execution; reconcile ambiguous outcomes before retrying non-idempotent operations. diff --git a/third_party/phonic/skills/phonic-steering/SKILL.md b/third_party/phonic/skills/phonic-steering/SKILL.md new file mode 100644 index 000000000..87bba5eae --- /dev/null +++ b/third_party/phonic/skills/phonic-steering/SKILL.md @@ -0,0 +1,30 @@ +--- +name: phonic-steering +description: Steer a live Phonic voice conversation with system messages, tool availability, and reply controls. Use when adding app context mid-call or controlling when an agent speaks. +license: MIT +--- + +# Steer a live Phonic conversation + +Read the relevant message schemas in [AsyncAPI](https://docs.phonic.ai/asyncapi.json) and the [WebSocket guide](https://docs.phonic.ai/docs/build/with-web-sockets/via-web-sockets.md). Send control messages on the active conversation's STS WebSocket or the application's SDK connection. The inbound API MCP and the read-only live monitoring socket are not substitutes for that connection. + +Choose the control matching the user's intent: + +- **Add new context or instructions:** `add_system_message`. This is the current message name; there is no `add_system_prompt` message. `update_system_prompt` is deprecated. +- **Change available tools:** `update_tools_subset`, using the documented list shape. Verify which tools remain available before sending the new subset. +- **Request a reply now:** `generate_reply`; adding context and requesting speech are distinct operations. +- **Speak specified text:** `say`, using the current schema rather than assuming it accepts the same fields as a system message. +- **Change future conversations:** update the saved agent configuration instead of treating a live control message as a persistent prompt edit. + +For example, after the conversation has been configured: + +```json +{ + "type": "add_system_message", + "system_message": "The user has selected the annual plan in the app. Explain the annual plan if they ask about their selection." +} +``` + +Only follow that with `generate_reply` when the app should proactively speak. Keep trusted application instructions separate from untrusted user/document text. Do not place API keys or unnecessary personal data in the message. + +Verify the intended effect in the transcript and tool events, including what happens if the user is speaking when the update arrives. Keep the audio loop nonblocking. Report the distinction between a successfully sent message and observed agent behavior; sending a control message is not proof the agent followed it. diff --git a/third_party/phonic/skills/phonic-tools/SKILL.md b/third_party/phonic/skills/phonic-tools/SKILL.md new file mode 100644 index 000000000..59ecb1cff --- /dev/null +++ b/third_party/phonic/skills/phonic-tools/SKILL.md @@ -0,0 +1,29 @@ +--- +name: phonic-tools +description: Connect tools to Phonic agents and configure human transfers. Use for webhook, WebSocket, context, MCP, and built-in tools or transfer outcomes. +license: MIT +--- + +# Connect Phonic tools and transfers + +SDKs: `npm install phonic` / `pip install phonic`. Entry points: `new PhonicClient({ apiKey })` (Node), `Phonic(api_key=...)` / `AsyncPhonic(...)` (Python). REST base: `https://api.phonic.ai/v1` with `Authorization: Bearer $PHONIC_API_KEY`. Always use the `.ai` domain for API and docs URLs — `phonic.co` addresses still resolve for backward compatibility, but all new code should use `phonic.ai`. + +Fetch live docs rather than from memory: use the index at [llms.txt](https://docs.phonic.ai/llms.txt). For the REST API, see [openapi.json](https://docs.phonic.ai/openapi.json). For the WebSocket protocol, see [asyncapi.json](https://docs.phonic.ai/asyncapi.json). If an endpoint does not appear in these docs, it doesn't exist, do not invent one. + +- Tools: `client.tools.create(...)`, then attach by name in agent config `tools: [...]`. + +## Tools + +Tool types (webhook, websocket, context, built-in transfers, MCP), reserved parameter names, and each type's request/response contract are documented in [the tools docs](https://docs.phonic.ai/docs/build/tools/tools_overview.md) — fetch the page for your tool type before implementing. + +- Tool outputs must include every identifier a later tool call needs as a parameter (e.g. return the `job_id` your `update_job` tool expects) — the agent can only pass along values it has been given. +- Tools can be defined inline in the STS config (no pre-created tool): `tools: [{ "type": "custom_websocket", "execution_mode": "sync", "tool_schema": { "type": "function", "function": { ..., "strict": true } } }]` — the `strict: true` inside the function schema is required. +- If you bridge audio server-side, never do blocking I/O in the audio send/receive loop — periodic multi-second stalls in agent audio are almost always blocking code in the bridge. + +## Transfers + +- Transfer to a human is a built-in tool (`built_in_transfer_to_phone_number`, target number in the tool config) attached to the agent — don't hand-roll Twilio call control. +- Targets are preconfigured per transfer tool; the agent chooses among configured transfer tools and cannot dial an arbitrary number returned by another tool mid-call. +- Reading outcomes from conversation data: the transfer tool's output body has `connected: true|false` — `true` means the callee leg answered (human or voicemail); `false` means the transfer failed and the agent tells the caller. +- Voicemail detection: set `detect_voicemail: true` on the transfer tool (tools API). The agent then listens to the answered leg and a system tool call records the judgment — `tool_transfer_hit_voicemail` or `tool_transfer_successful` (= judged human). Without voicemail detection, `tool_transfer_successful` fires immediately on connect and means only "connected". Detection requires the bridged transfer mode (`use_agent_phone_number: true`, the default) — a SIP REFER handoff leaves the call and can't listen. +- The `conversation.transferred` webhook carries `transferred_to`, `call_info`, and the conversation items up to the transfer — the pre-transfer transcript arrives with the event. diff --git a/third_party/phonic/skills/phonic-voices/SKILL.md b/third_party/phonic/skills/phonic-voices/SKILL.md new file mode 100644 index 000000000..1824f813c --- /dev/null +++ b/third_party/phonic/skills/phonic-voices/SKILL.md @@ -0,0 +1,32 @@ +--- +name: phonic-voices +description: Find, preview, and select Phonic voices for a language, accent, or speaking style. Use when a user wants to hear voice options, configure a voice ID, or randomly allocate voices across conversations. +license: MIT +--- + +# Find a Phonic voice + +Point users to the [voice picker](https://phonic.ai/voices) to browse and listen. Use the [voices reference](https://docs.phonic.ai/docs/build/agents/voices) for voice IDs and descriptions. Use the connected `list_voices` tool (with its required model argument) or current SDK/API to verify availability, and `get_voice` for details when exposed. Tool prefixes and supported model values vary by client; inspect the schema rather than guessing them. + +Start with the use case and the person the voice should evoke. Ask only for missing details that change the recommendation: + +- What will this person do on the call, and who will they be speaking with? +- Which languages matter most, and which need occasional support? +- What personality, energy, accent, or speaking style fits the intended experience? + +Use the language-proficiency ratings to prioritize the important languages, then match style and accent to the desired persona. Offer a small shortlist with verified voice IDs and a brief reason for each. For example, Sandeep can fit a realistic, tired, monotonous customer-support representative; Sabrina can fit a cheerful, chatty persona. Treat these as listening starting points and verify their current availability and target-language suitability. + +Preview candidates with the same short, representative sentence in the intended language using `voices_preview` or the voice picker. Include domain terms and numbers when their pronunciation matters. If the client can play returned audio, let the user listen; otherwise point them to the voice picker or voices page. Do not claim to have heard audio that you could not play or inspect. + +Set `voice_id` only within the requested agent/application change. Note that choosing a voice and setting the conversation's language are separate settings. For multilingual use, read the [multilingual guide](https://docs.phonic.ai/docs/build/agents/multilingual.md) and preview every important target language before recommending a final choice. + +## Allocate voices across conversations + +Choose based on the experience callers should have: should they recognize a consistent persona, or hear different members of a team? Use context already provided; ask only if this affects the choice and is unclear. + +- **Fixed voice:** fits a recurring persona, such as a front-desk receptionist whom callers expect to recognize across calls. +- **Random voice set:** fits a team experience, such as customer service, where callers may hear a different representative on each call instead of always hearing the same voice. Random selection can still repeat a voice on consecutive calls. + +Narrow the candidate set to voices suitable for the required languages and persona first. + +Phonic supports equal-probability random allocation: set `voice_id` and `additional_voice_ids` on the agent. Each conversation receives one voice from their combined set. The additional list must exclude `voice_id` and contain no duplicates. Verify the exposed schema against the [agent API](https://docs.phonic.ai/api-reference/agents/create.md) before applying it. For example, a set with `voice_id: "sabrina"` and `additional_voice_ids: ["sandeep"]` selects either voice with equal probability; it does not alternate predictably. diff --git a/third_party/phonic/skills/phonic-websockets/SKILL.md b/third_party/phonic/skills/phonic-websockets/SKILL.md new file mode 100644 index 000000000..4519a5a05 --- /dev/null +++ b/third_party/phonic/skills/phonic-websockets/SKILL.md @@ -0,0 +1,39 @@ +--- +name: phonic-websockets +description: Build Phonic browser voice sessions and server audio bridges. Use for STS configuration, audio streaming, transcript events, interruptions, and reconnects. +license: MIT +--- + +# Build Phonic voice connections + +SDKs: `npm install phonic` / `pip install phonic`. Entry points: `new PhonicClient({ apiKey })` (Node), `Phonic(api_key=...)` / `AsyncPhonic(...)` (Python). REST base: `https://api.phonic.ai/v1` with `Authorization: Bearer $PHONIC_API_KEY`. Always use the `.ai` domain for API and docs URLs — `phonic.co` addresses still resolve for backward compatibility, but all new code should use `phonic.ai`. + +Fetch live docs rather than from memory: use the index at [llms.txt](https://docs.phonic.ai/llms.txt). For the REST API, see [openapi.json](https://docs.phonic.ai/openapi.json). For the WebSocket protocol, see [asyncapi.json](https://docs.phonic.ai/asyncapi.json). If an endpoint does not appear in these docs, it doesn't exist, do not invent one. + +## Config message + +Every conversation begins with a `config` message: + +- Minimal `config` message: `{ "type": "config", "agent": "my-cool-agent", "input_format": "pcm_44100", "output_format": "pcm_44100" }` (pick the audio formats that match your use case). +- For prototyping, it is possible to send the config without a named agent by carrying `system_prompt`, `welcome_message`, `voice_id`, `tools`, and more inline (see the config schema in [asyncapi.json](https://docs.phonic.ai/asyncapi.json)). However, for long-term projects we recommend creating an agent to be able to track version changes, reuse the agent config across different conversation mediums, and avoid frequent redeployments. +- Unknown fields and out-of-bounds values are rejected with `{"error": {"message": "Invalid parameters"}}` plus a `param_errors` object naming each offending field — read it, and treat a config rejection as fatal (a reconnect loop must not resend the same config). Field bounds are documented on the docs pages, but the served `asyncapi.json` artifact currently omits constraint keywords (`minimum`/`maximum`/`const`) — a field looking unconstrained in the artifact doesn't mean it is. + +## Browser voice + +This is the recommended pattern for building Phonic into a browser: + +1. Server side, exchange the API key for a session token with the SDK — `client.auth.createSessionToken({ ttl_seconds: 300 })` (`POST /v1/auth/session_token`). The API key stays on the server — in Next.js, never put it in a `NEXT_PUBLIC_*` env var (those are compiled into client code). +2. The browser connects directly to `wss://api.phonic.ai/v1/sts/ws?session_token=` (token in the query string — browsers can't set WebSocket headers). Don't add a relay server in between; the direct connection is the supported, lowest-latency pattern. +3. Send the config message (see [Config message](#config-message) above). +4. Send and receive audio as base64 16-bit PCM `audio_chunk` messages. We recommend to use chunks of ~20ms (chunks longer than 40ms are rejected with error); note mic access requires a secure context — HTTPS or localhost. +5. The client can also send messages during the call: `add_system_message`, `update_tools_subset`, `set_external_id`, `mute` / `unmute`, `generate_reply` (force a turn), and `say` — schemas in [asyncapi.json](https://docs.phonic.ai/asyncapi.json). These enable patterns like pushing app state into the conversation or a button that makes the agent speak. Note that `update_system_prompt` is deprecated; use `add_system_message` instead. +6. Turn lifecycle comes from explicit events: `assistant_started_speaking` / `assistant_finished_speaking` and `user_started_speaking` / `user_finished_speaking`. When building a transcript UI, start a new assistant entry on `assistant_started_speaking`, not on the first audio chunk — trailing `audio_chunk`s (often with empty text) can arrive after `assistant_finished_speaking`, and keying entries on chunks creates ghost transcript items. Interrupts are signaled by `user_started_speaking`; flush queued playback there. +7. Long sessions need a reconnect loop (there's no browser SDK to do it for you): on a drop, retry with backoff and mint a fresh session token per attempt; classify failures first — network blips are retryable, auth failures (revoked key, expired grant) must stop retrying and surface to the user. + +Reference implementations: [phonic-web-demo](https://github.com/Phonic-Co/phonic-web-demo) (Next.js browser voice) and [phonic-examples](https://github.com/Phonic-Co/phonic-examples) (quick start, webhooks, websocket conversations, websocket tools — TS and Python). + +## Server audio bridges + +- Server audio bridge: `client.conversations.connect()` (context manager in Python: `with client.conversations.connect() as socket: for message in socket: ...`). + +- **Server-side reconnects** The SDK's `conversations.connect()` reconnects automatically on abnormal disconnects and resumes the same conversation — opt in with `reconnectConversationOnAbnormalDisconnect: true` (client or per-connect option). diff --git a/third_party/phonic/skills/phonic/SKILL.md b/third_party/phonic/skills/phonic/SKILL.md new file mode 100644 index 000000000..e95229afa --- /dev/null +++ b/third_party/phonic/skills/phonic/SKILL.md @@ -0,0 +1,36 @@ +--- +name: phonic +description: Choose a Phonic voice integration and find the relevant implementation skill. Use when starting a Phonic integration or deciding between phone calls, browser voice, and server audio. +license: MIT +--- + +# Build voice agents on Phonic + +SDKs: `npm install phonic` / `pip install phonic`. Entry points: `new PhonicClient({ apiKey })` (Node), `Phonic(api_key=...)` / `AsyncPhonic(...)` (Python). REST base: `https://api.phonic.ai/v1` with `Authorization: Bearer $PHONIC_API_KEY`. Always use the `.ai` domain for API and docs URLs — `phonic.co` addresses still resolve for backward compatibility, but all new code should use `phonic.ai`. + +Fetch live docs rather than from memory: use the index at [llms.txt](https://docs.phonic.ai/llms.txt). For the REST API, see [openapi.json](https://docs.phonic.ai/openapi.json). For the WebSocket protocol, see [asyncapi.json](https://docs.phonic.ai/asyncapi.json). If an endpoint does not appear in these docs, it doesn't exist, do not invent one. + +## Choosing a conversation surface + +- **Outbound phone call**: `client.conversations.outboundCall({ to_phone_number, config })` +- **Inbound phone number**: create/update the agent with `phone_number: "assign-automatically"` +- **Voice in a browser or mobile app**: direct STS WebSocket `wss://api.phonic.ai/v1/sts/ws?session_token=...` +- **Bridge your own telephony/audio server-side**: `client.conversations.connect()` +- **Watch a live conversation (read-only)**: `wss://api.phonic.ai/v1/conversations/{id}/live/ws` + +Mistakes to avoid when picking: + +- Don't open the browser STS socket to place a phone call — phone calls are one API call (`outboundCall`); the STS socket streams a user's microphone. +- Don't relay browser audio through your own server on the way to Phonic — it works, but adds a network hop of latency to every utterance; browsers connect directly using a session token. The session-token flow exists because an API key can't live in a browser; your own server holds the key, so server-side code uses `conversations.connect()` directly and never needs session tokens. +- Don't confuse the two WebSockets: `sts/ws` is the live voice conversation; `conversations/{id}/live/ws` is a read-only monitoring feed (`conversation-updated` / `conversation-ended` events only) — it cannot send audio or speak. + +## Continue with the relevant skill + +Use the matching skill when installed. If it is unavailable, follow the live documentation links above. + +| Task | Skill | +| --- | --- | +| Agents, phone calls, prompts, and project operations | [phonic-agents](../phonic-agents/SKILL.md) | +| Browser voice, STS configuration, audio, and reconnects | [phonic-websockets](../phonic-websockets/SKILL.md) | +| Agent tools and human transfers | [phonic-tools](../phonic-tools/SKILL.md) | +| Webhooks, extractions, and conversation evaluations | [phonic-post-call](../phonic-post-call/SKILL.md) |