diff --git a/README.md b/README.md index a746491e..9706a44e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Each example's README has a Quick Start with the exact install/run commands for ---
-

Voice AI

(157 examples)
+

Voice AI

(158 examples)
Build voice applications with [Telnyx Voice AI](https://telnyx.com/products/voice-ai-agents) - IVR menus, call recording, conferencing, WebRTC, and AI-powered call routing. @@ -178,6 +178,7 @@ Build voice applications with [Telnyx Voice AI](https://telnyx.com/products/voic | [voicemail-php](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-php/README.md) | PHP | --- | | [voicemail-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-python/README.md) | Python | --- | | [voicemail-ruby](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-ruby/README.md) | Ruby | --- | +| [voicemail-to-sms-agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-to-sms-agent/README.md) | Node.js | An Edge-based agent that transcribes, summarizes, and texts voicemail summaries to the mailbox owner. | | [voiceover-audition-generator-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voiceover-audition-generator-python/README.md) | Python | Submit a script, hear it read by every available TTS voice. AI scores and ranks best-fit voices based on content, tone, and audience. SMS delivers top picks to decision-makers. | | [warm-transfer-csharp](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/warm-transfer-csharp/README.md) | C# | --- | | [warm-transfer-go](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/warm-transfer-go/README.md) | Go | --- | @@ -216,7 +217,6 @@ Send and receive text messages with the [Telnyx SMS API](https://telnyx.com/prod | [edge-agri-crop-advisory](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-agri-crop-advisory/README.md) | Node.js | Agriculture crop advisory on Telnyx Edge Compute Stateful Actors — classify crop issues (disease, pest, nutrient, water, weather) and recommend treatment via AI Inference. Escalates critical cases. | | [edge-cache-invalidation-agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-cache-invalidation-agent/README.md) | Node.js | --- | | [edge-compute-webhook-proxy-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-compute-webhook-proxy-python/README.md) | Python | Receive Telnyx voice and SMS webhooks at the edge with minimal latency. Validates, enriches with timestamps, HMAC-signs, and forwards to your backend. | -| [edge-event-microsite](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-event-microsite/README.md) | Node.js | Event microsite + AI concierge on Telnyx Edge Compute — one KV store powers the site, the SMS/WhatsApp concierge, lead qualification, in-browser voice AI, feedback transcription, and the sponsor report. | | [edge-mcp-server-deploy-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-mcp-server-deploy-python/README.md) | Python | Deploy an MCP server to Telnyx Edge Compute exposing Telnyx APIs as tools for AI agents. Send SMS, search numbers, run inference. | | [edge-prompt-ab-tester](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-prompt-ab-tester/README.md) | Node.js | Prompt A/B tester on Telnyx Edge Compute Stateful Actors — run two prompt variants against the same task, collect user votes, and track which prompt wins. | | [edge-url-summarizer](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-url-summarizer/README.md) | Node.js | URL summarizer on Telnyx Edge Compute Stateful Actors — fetch a URL, summarize via AI Inference, cache the result for instant repeat requests. | @@ -377,8 +377,7 @@ Send and receive text messages with the [Telnyx SMS API](https://telnyx.com/prod
-

AI Assistants

(134 examples)
-

AI Assistants

(139 examples)
+

AI Assistants

(141 examples)
Create, manage, and chat with [Telnyx AI Assistants](https://telnyx.com/ai-assistants) - LLM-powered agents for voice and messaging automation. @@ -473,6 +472,7 @@ Create, manage, and chat with [Telnyx AI Assistants](https://telnyx.com/ai-assis | [create-ai-assistant-php](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/create-ai-assistant-php/README.md) | PHP | --- | | [create-ai-assistant-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/create-ai-assistant-python/README.md) | Python | Create a new Telnyx AI Assistant with a system prompt, model selection, and tool configuration. | | [create-ai-assistant-ruby](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/create-ai-assistant-ruby/README.md) | Ruby | --- | +| [edge-event-microsite](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-event-microsite/README.md) | Node.js | Event microsite + AI concierge on Telnyx Edge Compute — one KV store powers the site, the SMS/WhatsApp concierge, lead qualification, in-browser voice AI, feedback transcription, and the sponsor report. | | [error-explainer-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/error-explainer-python/README.md) | Python | AI Error Explainer — paste a stack trace, get a root-cause hypothesis, confidence, severity, and a suggested fix via Telnyx AI Inference. | | [event-microsite-that-takes-calls](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/event-microsite-that-takes-calls/README.md) | Python | A Flask event microsite backed by Telnyx KV that lets attendees text, call, or talk in-browser to an AI concierge, broadcasts schedule changes, qualifies exhibitor leads, and transcribes post-event feedback into a sponsor report. | | [extract-structured-json-with-ai-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/extract-structured-json-with-ai-python/README.md) | Python | Extract structured JSON from support tickets, emails, leads, or incident reports with Telnyx AI Inference. | @@ -508,6 +508,7 @@ Create, manage, and chat with [Telnyx AI Assistants](https://telnyx.com/ai-assis | [run-llm-inference-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/run-llm-inference-python/README.md) | Python | Send chat completion requests to the Telnyx Inference API using an OpenAI-compatible interface from Python. | | [semantic-search-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/semantic-search-python/README.md) | Python | Semantic Search — index support tickets via Telnyx embeddings API and find similar issues by meaning, not keywords. Includes a bundled sample ticket dataset. | | [shipment-agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/shipment-agent/README.md) | Python | A durable AI shipment agent that proactively communicates with customers across carriers and status changes via SMS and Voice. | +| [sim-agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/sim-agent/README.md) | Typescript | A durable Telnyx Edge Agent that embodies a SIM card, tracking data usage, proactively alerting on thresholds, and auto-provisioning plan upgrades via SMS and Call Control. | | [sms-two-factor-agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/sms-two-factor-agent/README.md) | Typescript | Agent-managed SMS two-factor authentication with code generation, KV storage, and scheduled expiry. | | [sql-natural-language-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/sql-natural-language-python/README.md) | Python | AI SQL Natural Language — turn plain-English questions into validated SQL with schema context via Telnyx AI Inference. Includes a sample dataset for live execution. | | [storage-voicemail-archive-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/storage-voicemail-archive-python/README.md) | Python | Storage Voicemail Archive - record voicemails to Telnyx Cloud Storage with search. | @@ -516,9 +517,9 @@ Create, manage, and chat with [Telnyx AI Assistants](https://telnyx.com/ai-assis | [update-ai-assistant-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/update-ai-assistant-python/README.md) | Python | Update an existing Telnyx AI Assistant's configuration, model, system prompt, and tools via the API. | | [video-room-ai-meeting-moderator-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/video-room-ai-meeting-moderator-python/README.md) | Python | Video Room AI Meeting Moderator - create video rooms with AI-powered agenda tracking and time management. | | [video-room-ai-moderator-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/video-room-ai-moderator-python/README.md) | Python | Video Room AI Moderator - create video rooms with AI-powered content moderation on chat and participant management. | +| [voice-ivr-with-agent-backend](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voice-ivr-with-agent-backend/README.md) | Python | A natural language IVR system where the Telnyx Agent handles backend logic and an LLM powers dynamic menu options. | | [voice-journal-daily-log-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voice-journal-daily-log-python/README.md) | Python | Voice Journal Daily Log - call a number, speak your daily journal entry, AI transcribes and organizes it with mood, topics, and gratitude extraction. | | [voicemail-smart-router-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-smart-router-python/README.md) | Python | AI Voicemail Smart Router — transcribe voicemails, classify intent (urgent, billing, support, sales, spam, routine), and route to the right channel via Telnyx STT + AI Inference. | -| [voicemail-to-sms-agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-to-sms-agent/README.md) | Typescript | An Edge-based agent that transcribes, summarizes, and texts voicemail summaries to the mailbox owner. | | [warm-transfer-ai-briefing-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/warm-transfer-ai-briefing-python/README.md) | Python | When an agent transfers a call, AI summarizes the conversation and briefs the next agent before connecting. No cold handoffs. | | [webhook-aggregator-fanout](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/webhook-aggregator-fanout/README.md) | Python | Aggregate, deduplicate, and fan out Telnyx webhooks to multiple action queues (call and SMS) with SQLite event logging. | | [webhook-debugger-ai-assistant-python](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/webhook-debugger-ai-assistant-python/README.md) | Python | Webhook Debugger AI Assistant - catch, inspect, and debug Telnyx webhooks with AI explanations. | diff --git a/llms.txt b/llms.txt index 13924892..3e584fd2 100644 --- a/llms.txt +++ b/llms.txt @@ -50,7 +50,6 @@ - [Turn a Text Chatbot Into a Voice Bot With Conversation Relay](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/conversation-relay-voice-bot-python/README.md): Forward live phone calls to any existing text-in/text-out AI chatbot using Telnyx Conversation Relay — no changes to the bot. - [Deepfake Voice Detector](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/deepfake-voice-detector-python/README.md): Real-time synthetic speech detection on live phone calls. Captures audio via media streaming, extracts acoustic features, scores deepfake probability with AI Inference, alerts security team via Slack. - [Edge Compute Backend for AI Assistant](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-ai-assistant-backend-go/README.md): Use a Telnyx Edge Compute function as the backend for AI Assistant dynamic variables and webhook tool calls — no separate server required. -- [Edge Compliance Monitor](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-compliance-monitor-python/README.md): Real-time compliance checking for regulated call centers using Telnyx Voice, AI Inference, and Edge Compute. - [Edge Call Transcription Agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-call-transcription-agent/README.md): Live call transcription agent on Telnyx Edge Compute + Agent SDK — answers an inbound call, streams STT into durable agent state, and on hangup summarizes the transcript via LLM, persists it to actor-local SQL, and texts the summary via SMS. Zero-credential inference and messaging via the [telnyx] binding. - [Edge Compliance Monitor](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-compliance-monitor-python/README.md): Real-time compliance checking for regulated call centers using Telnyx Voice, AI Inference, and Edge Compute. - [Customer Agent — The Actor IS the Entity](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-customer-agent-typescript/README.md): A durable Entity Agent on Telnyx Edge Compute. One actor per customer phone number, surviving across days, calls, SMS messages, and actor evictions. The AI Assistant is the reasoning harness inside; the agent owns the durable state. @@ -172,7 +171,6 @@ - [Agent SMS Triage Bot](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/agent-sms-triage-bot/README.md): SMS triage bot on Telnyx Edge Compute + Agent SDK — classifies inbound customer SMS by topic (billing/support/sales) via LLM and routes to the right queue using a durable route table. Zero-credential SMS and inference. - [AI Appointment Booking SMS Flow](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/ai-appointment-booking-sms-flow-python/README.md): AI Appointment Booking SMS Flow - guided SMS booking with available slot selection. - [AI Appointment Reminder](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/ai-appointment-reminder-sms-voice-python/README.md): AI Appointment Reminder - SMS first, voice call for non-responders, AI handles rescheduling. -- [Alphanumeric Sender ID SMS](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/alphanumeric-sender-id-sms-python/README.md): Send SMS messages with a branded alphanumeric sender ID using the Telnyx Messaging API. Validates sender IDs and enforces regional restrictions. - [AI Email Agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/ai-email-agent-python/README.md): AI Email Agent — autonomous inbound email reply bot on the Telnyx Email API. A customer emails in, the AI drafts a reply, the Email API sends it back. One platform, one API key, one agent. - [Alphanumeric Sender ID SMS](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/alphanumeric-sender-id-sms-python/README.md): Send SMS messages with a branded alphanumeric sender ID using the Telnyx Messaging API. Validates sender IDs and enforces regional restrictions. - [Audio Transcribe → Summarize → SMS](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/audio-transcribe-summarize-sms/README.md): Upload a voicemail audio file → transcribe via STT → summarize with LLM → text the summary via SMS. Agent SDK pipeline on Telnyx Edge Compute with zero-credential inference and messaging. @@ -433,7 +431,6 @@ - [Create AI Assistant with PHP and Laravel](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/create-ai-assistant-php/README.md) - [Create AI Assistant](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/create-ai-assistant-python/README.md): Create a new Telnyx AI Assistant with a system prompt, model selection, and tool configuration. - [Create AI Assistant with Ruby and Sinatra](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/create-ai-assistant-ruby/README.md) -- [AI Error Explainer](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/error-explainer-python/README.md): AI Error Explainer — paste a stack trace, get a root-cause hypothesis, confidence, severity, and a suggested fix via Telnyx AI Inference. - [Event Microsite That Takes Calls](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/edge-event-microsite/README.md): Event microsite + AI concierge on Telnyx Edge Compute — one KV store powers the site, the SMS/WhatsApp concierge, lead qualification, in-browser voice AI, feedback transcription, and the sponsor report. - [AI Error Explainer](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/error-explainer-python/README.md): AI Error Explainer — paste a stack trace, get a root-cause hypothesis, confidence, severity, and a suggested fix via Telnyx AI Inference. - [Event Microsite That Takes Calls](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/event-microsite-that-takes-calls/README.md): A Flask event microsite backed by Telnyx KV that lets attendees text, call, or talk in-browser to an AI concierge, broadcasts schedule changes, qualifies exhibitor leads, and transcribes post-event feedback into a sponsor report. @@ -470,6 +467,7 @@ - [Run LLM Inference](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/run-llm-inference-python/README.md): Send chat completion requests to the Telnyx Inference API using an OpenAI-compatible interface from Python. - [Semantic Search for Support Tickets](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/semantic-search-python/README.md): Semantic Search — index support tickets via Telnyx embeddings API and find similar issues by meaning, not keywords. Includes a bundled sample ticket dataset. - [ShipmentAgent: The Actor IS the Package](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/shipment-agent/README.md): A durable AI shipment agent that proactively communicates with customers across carriers and status changes via SMS and Voice. +- [SIMAgent — The Actor IS the SIM](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/sim-agent/README.md): A durable Telnyx Edge Agent that embodies a SIM card, tracking data usage, proactively alerting on thresholds, and auto-provisioning plan upgrades via SMS and Call Control. - [SMS Two-Factor Authentication Agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/sms-two-factor-agent/README.md): Agent-managed SMS two-factor authentication with code generation, KV storage, and scheduled expiry. - [AI SQL Natural Language](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/sql-natural-language-python/README.md): AI SQL Natural Language — turn plain-English questions into validated SQL with schema context via Telnyx AI Inference. Includes a sample dataset for live execution. - [Storage Voicemail Archive](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/storage-voicemail-archive-python/README.md): Storage Voicemail Archive - record voicemails to Telnyx Cloud Storage with search. @@ -478,11 +476,10 @@ - [Update AI Assistant](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/update-ai-assistant-python/README.md): Update an existing Telnyx AI Assistant's configuration, model, system prompt, and tools via the API. - [Video Room AI Meeting Moderator](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/video-room-ai-meeting-moderator-python/README.md): Video Room AI Meeting Moderator - create video rooms with AI-powered agenda tracking and time management. - [Video Room AI Moderator](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/video-room-ai-moderator-python/README.md): Video Room AI Moderator - create video rooms with AI-powered content moderation on chat and participant management. +- [Voice IVR with Agent Backend](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voice-ivr-with-agent-backend/README.md): A natural language IVR system where the Telnyx Agent handles backend logic and an LLM powers dynamic menu options. - [Voice Journal Daily Log](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voice-journal-daily-log-python/README.md): Voice Journal Daily Log - call a number, speak your daily journal entry, AI transcribes and organizes it with mood, topics, and gratitude extraction. - [AI Voicemail Smart Router](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-smart-router-python/README.md): AI Voicemail Smart Router — transcribe voicemails, classify intent (urgent, billing, support, sales, spam, routine), and route to the right channel via Telnyx STT + AI Inference. - [Warm Transfer with AI Briefing](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/warm-transfer-ai-briefing-python/README.md): When an agent transfers a call, AI summarizes the conversation and briefs the next agent before connecting. No cold handoffs. -- [Voicemail-to-SMS Agent](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/voicemail-to-sms-agent/README.md): An Edge-based agent that transcribes, summarizes, and texts voicemail summaries to the mailbox owner. -- [Warm Transfer with AI Briefing](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/warm-transfer-ai-briefing-python/README.md): When an agent transfers a call, AI summarizes the conversation and briefs the next agent before connecting. No cold handoffs. - [Webhook Aggregator with Fanout](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/webhook-aggregator-fanout/README.md): Aggregate, deduplicate, and fan out Telnyx webhooks to multiple action queues (call and SMS) with SQLite event logging. - [Webhook Debugger AI Assistant](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/webhook-debugger-ai-assistant-python/README.md): Webhook Debugger AI Assistant - catch, inspect, and debug Telnyx webhooks with AI explanations. - [WebRTC AI Interpreter for Live Calls](https://raw.githubusercontent.com/team-telnyx/telnyx-code-examples/main/webrtc-ai-interpreter-live-calls-python/README.md): WebRTC AI Interpreter for Live Calls - real-time translation between two callers speaking different languages. diff --git a/scripts/examples_mapping.yaml b/scripts/examples_mapping.yaml index 33f891b4..3893de5b 100644 --- a/scripts/examples_mapping.yaml +++ b/scripts/examples_mapping.yaml @@ -3325,3 +3325,9 @@ examples: language: nodejs framework: edge folder: edge-event-microsite + + - product: ai + use_case: "auto-generated-from-linear" + language: typescript + framework: edge + folder: sim-agent diff --git a/sim-agent/.env.example b/sim-agent/.env.example new file mode 100644 index 00000000..0d392f84 --- /dev/null +++ b/sim-agent/.env.example @@ -0,0 +1,4 @@ +TELNYX_API_KEY=your_telnyx_api_key_here +TELNYX_PHONE_NUMBER=+1555XXXXXXXX +OPENAI_API_KEY=your_openai_api_key_here +DEMO_MODE=true diff --git a/sim-agent/API.md b/sim-agent/API.md new file mode 100644 index 00000000..dc192797 --- /dev/null +++ b/sim-agent/API.md @@ -0,0 +1,128 @@ +# API Reference — SIMAgent + +The SIMAgent sample exposes a single HTTP endpoint that serves as the webhook receiver for Telnyx events. All other interactions (SMS, calls, provisioning) are driven by the agent's internal scheduling and inference logic. + +## Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/webhook` | Receives Telnyx webhook events (SMS, calls, data usage) and dispatches them to the appropriate SIMAgent instance. | + +--- + +## POST /webhook + +Receives inbound Telnyx webhook events. The handler verifies the Ed25519 signature, parses the event type from `data.payload`, and routes the event to the correct `SIMAgent` instance based on the SIM card identifier. + +### Request Body Schema + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `data` | object | Yes | Top-level wrapper containing the event payload. | +| `data.payload` | object | Yes | The Telnyx event payload. Structure varies by event type. | +| `data.payload.event` | string | Yes | The Telnyx event type (e.g., `sms.received`, `call.initiated`, `data_usage.threshold`). | +| `data.payload.data` | object | Yes | Event-specific data. For SMS events, contains `from`, `to`, and `text`. For call events, contains `call_control_id`, `from`, and `to`. For data usage events, contains `sim_card_id`, `usage_bytes`, and `threshold_percent`. | +| `data.payload.data.sim_card_id` | string | Conditional | The Telnyx SIM card identifier. Present in data usage and provisioning events. | +| `data.payload.data.from` | string | Conditional | The sender's phone number (E.164 format). Present in SMS and call events. | +| `data.payload.data.to` | string | Conditional | The recipient's phone number (E.164 format). Present in SMS and call events. | +| `data.payload.data.text` | string | Conditional | The body of an inbound SMS message. Present in `sms.received` events. | +| `data.payload.data.call_control_id` | string | Conditional | The Telnyx Call Control ID. Present in call events. | +| `data.payload.data.usage_bytes` | integer | Conditional | Current data usage in bytes. Present in data usage events. | +| `data.payload.data.threshold_percent` | integer | Conditional | The usage threshold percentage that triggered the event. Present in data usage events. | + +### Example Request (curl) + +```bash +curl -X POST https://.telnyx.run/webhook \ + -H "Content-Type: application/json" \ + -H "Telnyx-Signature: t=1700000000,v1=ed25519_signature_hex" \ + -d '{ + "data": { + "payload": { + "event": "sms.received", + "data": { + "from": "+15551234567", + "to": "+15559998888", + "text": "what are my options?" + } + } + } + }' +``` + +### Response Schema + +#### 200 OK + +```json +{ + "status": "processed", + "event": "sms.received", + "sim_card_id": "sim-abc123" +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `status` | string | Always `"processed"`. Indicates the webhook was received and dispatched. | +| `event` | string | The Telnyx event type that was processed. | +| `sim_card_id` | string | The SIM card identifier associated with the event. | + +#### 400 Bad Request + +```json +{ + "error": "Invalid request body" +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `error` | string | Generic error message. No internal details are exposed. | + +#### 401 Unauthorized + +```json +{ + "error": "Invalid signature" +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `error` | string | Returned when the Ed25519 signature verification fails. | + +#### 500 Internal Server Error + +```json +{ + "error": "Internal server error" +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `error` | string | Generic error message. Exception details are logged server-side but never returned to the client. | + +### Status Codes + +| Status Code | Meaning | Description | +|-------------|---------|-------------| +| 200 | OK | Webhook event was received, signature verified, and dispatched to the appropriate SIMAgent instance. | +| 400 | Bad Request | The request body is malformed or missing required fields. | +| 401 | Unauthorized | The Telnyx Ed25519 signature could not be verified. | +| 500 | Internal Server Error | An unexpected error occurred while processing the webhook. Details are logged internally. | + +### Headers + +| Header | Type | Required | Description | +|--------|------|----------|-------------| +| `Content-Type` | string | Yes | Must be `application/json`. | +| `Telnyx-Signature` | string | Yes | The Ed25519 signature header sent by Telnyx for webhook verification. | + +### Notes + +- **Signature Verification**: The handler uses `telnyx.webhooks.unwrap(rawBody, signature)` to verify the Ed25519 signature before processing the event. Requests with invalid signatures are rejected with a 401. +- **Agent Dispatch**: After verification, the handler extracts the `sim_card_id` from the payload and routes the event to the corresponding `SIMAgent` instance via the Telnyx Edge SDK's agent dispatch mechanism. +- **Safe Demo Mode**: In demo mode, the agent logs all outbound actions (SMS sends, call initiations, plan upgrades) instead of executing them against the live Telnyx API. Switch to live mode by setting `TELNYX_DEMO_MODE=false` in the environment. +- **No Real Phone Numbers**: All phone numbers in this sample use placeholder formats (e.g., `+1555XXXXXXXX`). Replace with real numbers only when switching to live mode. diff --git a/sim-agent/GUIDE.md b/sim-agent/GUIDE.md new file mode 100644 index 00000000..0abc91d2 --- /dev/null +++ b/sim-agent/GUIDE.md @@ -0,0 +1,315 @@ +# SIMAgent — The Actor IS the SIM + +A step-by-step tutorial for the `sim-agent` sample, which demonstrates a Telnyx Edge Agent that acts as a persistent, stateful SIM card entity. The agent tracks data usage, proactively alerts on thresholds, responds to natural-language customer queries, auto-provisions plan upgrades via the Telnyx API, and answers inbound calls with full usage history. + +--- + +## Prerequisites + +- Node.js 18+ +- A Telnyx account with an API key (get one at [telnyx.com](https://telnyx.com)) +- The Telnyx CLI (optional, for local testing): `npm install -g @telnyx/telnyx-cli` +- A phone number provisioned in your Telnyx account (for live mode) + +--- + +## Environment Setup + +### 1. Clone and install + +```bash +git clone https://github.com/team-telnyx/telnyx-code-examples.git +cd telnyx-code-examples/sim-agent +npm install +``` + +### 2. Configure environment variables + +Copy the example env file and fill in your Telnyx API key: + +```bash +cp .env.example .env +``` + +Edit `.env`: + +```env +TELNYX_API_KEY=your_telnyx_api_key_here +TELNYX_PHONE_NUMBER=+1555XXXXXXXX +TELNYX_SIM_ID=sim-abc123 +OPENAI_API_KEY=your_openai_api_key_here +``` + +> **Demo mode** is the default. No real SMS, calls, or provisioning actions are taken. See the [Demo vs Live Mode](#demo-vs-live-mode) section below. + +--- + +## Project Structure + +``` +sim-agent/ +├── src/ +│ └── index.ts # Main agent entry point +├── package.json +├── tsconfig.json +├── .env.example +├── .gitignore +├── smoke_test.ts +├── README.md +├── API.md +└── GUIDE.md +``` + +--- + +## How It Works + +The `SIMAgent` is a Telnyx Edge Agent that extends the `Agent` base class. It represents a single SIM card as a durable, stateful entity. Here's the demo flow: + +### 1. Agent Initialization — `SIMAgent("sim-abc123")` + +The agent is instantiated with a unique SIM identifier. On startup, it loads persistent state from KV: + +```typescript +const usage = await this.kv.get(`sim:${this.simId}:usage`); +const plan = await this.kv.get(`sim:${this.simId}:plan`); +``` + +This state persists across billing cycles and reboots — the actor IS the SIM, not a transient conversation. + +### 2. Normal Usage (Days 1–15) — Silent Operation + +The agent schedules a daily check using `this.schedule()` and `every()`: + +```typescript +this.schedule('0 9 * * *', () => this.checkUsageThreshold()); +``` + +During normal usage, the agent receives webhook updates from Telnyx with data usage. It updates its KV counters silently — no alerts are sent. + +### 3. 80% Threshold Alert (Day 16) — Proactive SMS + +When usage crosses 80% of the plan limit, the agent wakes and sends a proactive SMS: + +```typescript +await this.telnyx.messages.create({ + from: this.phoneNumber, + to: this.customerPhone, + text: `You've used 80% of your data plan (${used}MB of ${limit}MB).` +}); +``` + +This uses the `[telnyx]` binding's SMS channel. + +### 4. Customer Inquiry (Day 17) — Natural Language Plan Comparison + +When the customer replies "what are my options?", the agent uses the OpenAI LLM binding to generate a natural-language comparison of available plans: + +```typescript +const response = await this.env.TELNYX.ai.openai.chat.createCompletion({ + model: 'gpt-3.5-turbo', + messages: [ + { role: 'system', content: 'You are a SIM plan advisor...' }, + { role: 'user', content: `Customer has used ${used}MB of ${limit}MB. What plans are available?` } + ] +}); +``` + +The agent parses the LLM response and sends it back as an SMS. + +### 5. Auto-Provisioning Upgrade (Day 17) — Telnyx API + +When the customer texts "upgrade to 10GB", the agent provisions the upgrade via the Telnyx API: + +```typescript +await this.telnyx.simCards.update(this.simId, { + data_plan: { id: 'plan_10gb_monthly' } +}); +``` + +It then sends a confirmation SMS and updates the KV state. + +### 6. Webhook Updates (Day 20) — State Sync + +Telnyx sends data usage webhooks to the agent's webhook endpoint. The agent verifies the Ed25519 signature and updates its state: + +```typescript +const event = client.webhooks.unwrap(req.body, req.headers); +const usage = event.data.payload.usage; +await this.kv.set(`sim:${this.simId}:usage`, usage); +``` + +### 7. Billing Cycle Reset (Day 30) — Scheduled Reset + +The agent uses `this.schedule()` to reset counters at the end of each billing cycle: + +```typescript +this.schedule('0 0 1 * *', () => this.resetBillingCycle()); +``` + +It sends a billing summary SMS and resets usage counters in KV. + +### 8. Inbound Call (Day 31) — Call Control with Usage History + +When the customer calls, the agent answers using Telnyx Call Control and speaks the full usage history: + +```typescript +await this.telnyx.calls.create({ + from: this.customerPhone, + to: this.phoneNumber, + webhook_url: this.webhookUrl, + webhook_url_method: 'POST' +}); +``` + +The agent uses text-to-speech to read out the usage summary, plan details, and billing history. + +--- + +## Telnyx Primitives Used + +| Primitive | How It's Used | +|-----------|---------------| +| **Agent SDK** | `class SIMAgent extends Agent` — owns the SIM entity with persistent state | +| **`schedule()` + `every()`** | Daily threshold checks, billing cycle resets | +| **`[telnyx]` binding** | SMS sending, call control, SIM provisioning via Telnyx API | +| **Webhooks** | Inbound data usage updates from Telnyx (Ed25519 verified) | +| **KV** | Persistent storage for usage counters, plan info, alert state | +| **Inference (LLM)** | Natural language plan comparison via `this.env.TELNYX.ai.openai.chat.createCompletion()` | +| **Call Control** | Customer calls answered with full usage history via text-to-speech | + +--- + +## Demo vs Live Mode + +### Demo Mode (Default) + +By default, the agent runs in **demo mode**. In this mode: + +- SMS messages are logged to the console instead of being sent +- Call Control actions are simulated (no real calls placed) +- SIM provisioning updates are logged, not executed +- Webhook payloads are processed but no real Telnyx resources are modified + +To enable demo mode, ensure `.env` does **not** contain `TELNYX_LIVE_MODE=true`: + +```env +# .env (demo mode) +TELNYX_API_KEY=your_telnyx_api_key_here +TELNYX_PHONE_NUMBER=+1555XXXXXXXX +TELNYX_SIM_ID=sim-abc123 +OPENAI_API_KEY=your_openai_api_key_here +# TELNYX_LIVE_MODE is not set — demo mode active +``` + +### Live Mode + +To switch to **live mode**, set `TELNYX_LIVE_MODE=true` in your `.env`: + +```env +TELNYX_API_KEY=your_real_telnyx_api_key_here +TELNYX_PHONE_NUMBER=+1555XXXXXXXX +TELNYX_SIM_ID=sim-abc123 +OPENAI_API_KEY=your_real_openai_api_key_here +TELNYX_LIVE_MODE=true +``` + +In live mode, the agent will: +- Send real SMS messages via Telnyx +- Place and receive real calls via Call Control +- Execute real SIM provisioning updates via the Telnyx API +- Process real webhook events from Telnyx + +> **Warning**: Live mode incurs real charges. Use only with a test SIM and verified phone numbers. + +--- + +## Running the Sample + +### Local Development + +```bash +npm run dev +``` + +This starts the agent locally using the Telnyx Edge runtime emulator. The agent will: +1. Load state from KV (or initialize fresh state) +2. Register webhook endpoints +3. Begin scheduled tasks (threshold checks, billing resets) + +### Smoke Test + +Before running, verify the agent loads correctly: + +```bash +npm run smoke-test +``` + +This imports the `SIMAgent` class and verifies it initializes without error. + +### Deploying + +Deploy to Telnyx Edge: + +```bash +telnyx deploy +``` + +--- + +## Key Code Locations + +| Feature | File | Description | +|---------|------|-------------| +| Agent class definition | `src/index.ts` | `class SIMAgent extends Agent` with SIM state | +| Threshold check logic | `src/index.ts` | `checkUsageThreshold()` method — 80% alert | +| Plan comparison | `src/index.ts` | `comparePlans()` method — LLM-powered | +| Auto-provisioning | `src/index.ts` | `upgradePlan()` method — Telnyx API call | +| Billing cycle reset | `src/index.ts` | `resetBillingCycle()` method — scheduled | +| Call handling | `src/index.ts` | `handleIncomingCall()` method — Call Control | +| Webhook handler | `src/index.ts` | `handleWebhook()` method — Ed25519 verified | +| KV state management | `src/index.ts` | `loadState()` / `saveState()` methods | + +--- + +## Next Steps + +- **Telnyx Edge Agents Documentation**: [https://docs.telnyx.com/edge/agents](https://docs.telnyx.com/edge/agents) +- **Telnyx SMS API**: [https://docs.telnyx.com/api/messages](https://docs.telnyx.com/api/messages) +- **Telnyx Call Control**: [https://docs.telnyx.com/voice/call-control](https://docs.telnyx.com/voice/call-control) +- **Telnyx SIM Cards API**: [https://docs.telnyx.com/api/sim-cards](https://docs.telnyx.com/api/sim-cards) +- **Telnyx Webhooks Guide**: [https://docs.telnyx.com/webhooks](https://docs.telnyx.com/webhooks) +- **Telnyx Edge KV Store**: [https://docs.telnyx.com/edge/kv](https://docs.telnyx.com/edge/kv) +- **Telnyx AI Inference**: [https://docs.telnyx.com/edge/ai](https://docs.telnyx.com/edge/ai) +- **Telnyx Edge Scheduling**: [https://docs.telnyx.com/edge/schedule](https://docs.telnyx.com/edge/schedule) + +--- + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Agent fails to load | Check that `TELNYX_API_KEY` is set in `.env` | +| SMS not sending | Verify `TELNYX_PHONE_NUMBER` is a valid Telnyx number | +| Webhook verification fails | Ensure you're using the correct public key from Telnyx dashboard | +| LLM responses are slow | Check `OPENAI_API_KEY` is valid and has sufficient quota | +| Call Control not working | Verify your phone number is in E.164 format | +| KV state not persisting | Ensure the agent is running on Telnyx Edge (not local emulator) | + +--- + +## Related Examples + +- **`sms-chatbot`** — A simpler SMS-based chatbot without persistent state +- **`voice-agent`** — A Call Control agent for voice-only interactions +- **`kv-counter`** — A minimal example of KV store usage on Telnyx Edge +- **`scheduled-tasks`** — Demonstrates `this.schedule()` and `every()` patterns + +--- + +## Resources + +- **Telnyx Developer Portal**: [https://developers.telnyx.com](https://developers.telnyx.com) +- **Telnyx Community Forum**: [https://community.telnyx.com](https://community.telnyx.com) +- **Telnyx Status Page**: [https://status.telnyx.com](https://status.telnyx.com) +- **GitHub Repository**: [https://github.com/team-telnyx/telnyx-code-examples](https://github.com/team-telnyx/telnyx-code-examples) diff --git a/sim-agent/README.md b/sim-agent/README.md new file mode 100644 index 00000000..80a9bdc4 --- /dev/null +++ b/sim-agent/README.md @@ -0,0 +1,154 @@ +--- +name: sim-agent +title: "SIMAgent — The Actor IS the SIM" +description: "A durable Telnyx Edge Agent that embodies a SIM card, tracking data usage, proactively alerting on thresholds, and auto-provisioning plan upgrades via SMS and Call Control." +language: typescript +framework: edge +telnyx_products: [Messaging, Voice, SIM, Wireless, Call Control, Agent SDK] +--- + +# SIMAgent — The Actor IS the SIM + +A TypeScript Telnyx Edge Agent (`SIMAgent extends Agent`) that represents a single SIM card as a durable, stateful entity. It tracks data usage, wakes on threshold breaches to send proactive SMS alerts, responds to customer SMS with natural-language plan comparisons via LLM, auto-provisions upgrades through the Telnyx API, resets counters on billing cycle boundaries, and answers inbound customer calls with full usage history. + +## Why Telnyx + +Telnyx provides **AI Communications Infrastructure** — programmable SMS, Voice, Call Control, and Wireless SIM management exposed through a single API surface and an Edge runtime where durable agents can live close to the data plane. By binding SMS, Voice, and SIM provisioning into one agent entity, SIMAgent turns the SIM card itself into an autonomous communication actor rather than a passive endpoint. + +## Telnyx API Endpoints Used + +| Product | Endpoint / Method | Purpose | +|---------|-------------------|---------| +| Messaging | `telnyx.messages.create()` | Proactive threshold alerts and upgrade confirmations | +| Messaging | `telnyx.messages.list()` | Polling inbound SMS from customers | +| Voice / Call Control | `telnyx.calls.create()` | Answering inbound customer calls with usage context | +| Voice / Call Control | `telnyx.calls.update()` | Controlling call state (answer, hangup) | +| Wireless | `telnyx.sims.update()` | Auto-provisioning plan upgrades on the SIM | +| Wireless | `telnyx.sims.get()` | Fetching current SIM plan and usage metadata | +| Webhooks | `telnyx.webhooks.unwrap()` | Verifying Ed25519 signatures on inbound SMS and usage webhooks | +| Agent SDK | `this.schedule()`, `this.every()` | Billing-cycle resets and periodic threshold checks | +| Agent SDK | `ctx.kv.get()` / `ctx.kv.put()` | Persisting usage counters and SIM state | +| Inference | `this.env.TELNYX.ai.openai.chat.createCompletion()` | Natural-language plan comparison | + +## Architecture + +``` +┌──────────────────────────────────────────────────────────────┐ +│ Telnyx Edge Runtime │ +│ │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ SIMAgent("sim-abc123") extends Agent │ │ +│ │ ──────────────────────────────────────────────── │ │ +│ │ State: usage, plan, alerts, history │ │ +│ │ Schedule: every(1h) → threshold check │ │ +│ │ Schedule: every(30d) → billing cycle reset │ │ +│ │ KV: ctx.kv.get('sim:abc123:usage') │ │ +│ │ LLM: this.env.TELNYX.ai.openai.chat.createCompletion()│ │ +│ └────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ +│ │ [telnyx] │ │ [telnyx] │ │ [telnyx] │ │ +│ │ binding │ │ binding │ │ binding │ │ +│ │ SMS │ │ Voice │ │ Wireless SIM │ │ +│ │ (Messaging) │ │ (Call Ctrl) │ │ (Provisioning) │ │ +│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ +│ │ Telnyx API │ │ Telnyx API │ │ Telnyx API │ │ +│ │ /messages │ │ /calls │ │ /sims │ │ +│ └──────────────┘ └──────────────┘ └──────────────────┘ │ +│ │ +│ ┌──────────────┐ │ +│ │ Webhooks │ │ +│ │ (Ed25519) │ │ +│ │ /webhooks │ │ +│ └──────────────┘ │ +└──────────────────────────────────────────────────────────────┘ + +Data flow: + 1. Telnyx usage webhook → /webhooks → unwrap signature → update KV usage + 2. Agent schedule wakes → reads KV → if usage ≥ 80% → SMS via telnyx binding + 3. Customer SMS → webhook → LLM plan comparison → SMS response + 4. Customer "upgrade" → telnyx.sims.update() → SMS confirmation + 5. Billing cycle reset → schedule → KV reset → SMS summary + 6. Customer call → Call Control → telnyx.calls.create() → usage history +``` + +## Environment Variables + +| Variable | Type | Example | Required | Description | Where to get it | +|----------|------|---------|----------|-------------|-----------------| +| `TELNYX_API_KEY` | `string` | `your_telnyx_api_key_here` | **yes** | TELNYX_API_KEY | — | +| `TELNYX_PUBLIC_KEY` | `string` | `your_telnyx_public_key_here` | **yes** | Ed25519 public key for webhook signature verification | Telnyx Portal → Credentials | +| `TELNYX_PHONE_NUMBER` | `string` | `+1555XXXXXXXX` | **yes** | Telnyx phone number used as SMS sender / call target | Telnyx Portal → Numbers | +| `TELNYX_SIM_ID` | `string` | `sim-abc123` | **yes** | Identifier of the SIM card this agent represents | Telnyx Portal → SIMs | +| `OPENAI_API_KEY` | `string` | `sk-your-openai-key-here` | **yes** | API key for LLM-powered plan comparison | OpenAI Platform | +| `DEMO_MODE` | `boolean` | `true` | no | When `true`, no real SMS/calls are sent; actions are logged | Set locally | + +## Setup + +```bash +# 1. Clone the repo +git clone https://github.com/team-telnyx/telnyx-code-examples.git +cd telnyx-code-examples/sim-agent + +# 2. Copy the example env file +cp .env.example .env +# Edit .env and fill in your Telnyx API key, public key, phone number, SIM ID, and OpenAI key + +# 3. Install dependencies +npm install + +# 4. Run locally (demo mode by default) +npm run dev + +# 5. Run smoke test +npm run smoke +``` + +The agent starts in **demo mode** by default (`DEMO_MODE=true`). In demo mode, all SMS sends, call creations, and SIM provisioning calls are intercepted and logged — no real charges are incurred. To switch to **live mode**, set `DEMO_MODE=false` in `.env` and restart. See `GUIDE.md` for the full demo-vs-live walkthrough. + +## API Reference + +See [`API.md`](./API.md) for the typed endpoint reference covering: + +- `POST /webhooks` — Inbound SMS and usage webhook handler (Ed25519 verified) +- `GET /health` — Health check endpoint +- `GET /agent/:simId/state` — Retrieve current SIM agent state (usage, plan, alerts) +- `POST /agent/:simId/trigger-threshold-check` — Manually trigger a threshold check +- `POST /agent/:simId/reset-billing-cycle` — Manually trigger a billing cycle reset + +## Troubleshooting + +| Issue | Cause | Fix | +|-------|-------|-----| +| Webhook signature verification fails | `TELNYX_PUBLIC_KEY` is wrong or missing | Copy the Ed25519 public key from the Telnyx Portal → Credentials | +| SMS not received in demo mode | Demo mode suppresses real sends | Check logs for `DEMO_MODE: would send SMS to ...` | +| LLM plan comparison returns empty | `OPENAI_API_KEY` is invalid or rate-limited | Verify key at platform.openai.com and check rate limits | +| SIM upgrade fails | SIM ID is incorrect or SIM is not active | Confirm `TELNYX_SIM_ID` in Telnyx Portal → SIMs | +| Agent schedule not firing | Edge runtime cold start or KV read error | Check `npm run dev` logs for KV errors | +| Call Control answers but no audio | Missing `Connection` header or webhook URL | Ensure `TELNYX_PHONE_NUMBER` is set and webhooks are configured | + +## Agent Discovery + +- [Telnyx Agent Signup](https://telnyx.com/agent-signup.md) — Register your agent with Telnyx +- [Telnyx AI GitHub](https://github.com/team-telnyx/ai) — Agent SDK and examples +- [llms.txt](https://telnyx.com/llms.txt) — Machine-readable Telnyx API documentation for LLMs + +## Related Examples + +- [`sms-auto-responder`](../sms-auto-responder/) — Basic SMS bot with webhook verification +- [`voice-ivr`](../voice-ivr/) — Interactive Voice Response using Call Control +- [`wireless-usage-tracker`](../wireless-usage-tracker/) — SIM data usage monitoring via webhooks +- [`agent-scheduler`](../agent-scheduler/) — Durable agent scheduling and KV patterns + +## Resources + +- [Telnyx Developer Docs](https://developers.telnyx.com/) +- [Telnyx API Reference](https://developers.telnyx.com/api/) +- [Telnyx SDK (TypeScript)](https://github.com/team-telnyx/telnyx-node) +- [Telnyx Messaging Product Page](https://telnyx.com/messaging) +- [Telnyx Voice & Call Control](https://telnyx.com/voice) +- [Telnyx Wireless & SIM](https://telnyx.com/wireless) +- [Telnyx Pricing](https://telnyx.com/pricing) diff --git a/sim-agent/index.ts b/sim-agent/index.ts new file mode 100644 index 00000000..27bed537 --- /dev/null +++ b/sim-agent/index.ts @@ -0,0 +1,365 @@ +import { Agent, Schedule, KVNamespace, WebSocket } from '@telnyx/edge-sdk'; +import telnyx from 'telnyx'; + +// --------------------------------------------------------------------------- +// SIMAgent — The actor IS the SIM card. +// A durable entity that tracks data usage, proactively alerts on thresholds, +// and auto-provisions upgrades via the Telnyx API. +// --------------------------------------------------------------------------- + +export interface SIMState { + simId: string; + phoneNumber?: string; + plan: { + name: string; + dataLimitMB: number; + }; + usageMB: number; + alerts: { threshold: number; sent: boolean }[]; + billingCycleStart: string; // ISO date string + history: { date: string; usageMB: number; event: string }[]; +} + +export interface Env { + TELNYX_API_KEY: string; + TELNYX_PHONE_NUMBER: string; + KV: KVNamespace; + OPENAI_API_KEY?: string; + DEMO_MODE?: string; // "true" | "false" +} + +const DATA_THRESHOLD = 0.8; // 80 % + +export class SIMAgent extends Agent { + // ------------------------------------------------------------------ + // Lifecycle: load persistent state from KV on wake / first contact + // ------------------------------------------------------------------ + async init(): Promise { + const stored = await this.kv.get(`sim:${this.entityId}`); + if (!stored) { + // First-time provisioning — create default state + const initialState: SIMState = { + simId: this.entityId, + plan: { name: '1GB Starter', dataLimitMB: 1024 }, + usageMB: 0, + alerts: [{ threshold: 80, sent: false }], + billingCycleStart: new Date().toISOString(), + history: [], + }; + await this.kv.put(`sim:${this.entityId}`, initialState); + this.state = initialState; + } else { + this.state = stored; + } + + // Schedule recurring threshold check (every hour) + this.schedule('every-hour', () => this.checkThresholds()); + + // Schedule billing-cycle reset (every 30 days) + this.schedule('billing-reset', () => this.resetBillingCycle()); + } + + // ------------------------------------------------------------------ + // Threshold monitoring — proactive SMS when usage crosses 80 % + // ------------------------------------------------------------------ + async checkThresholds(): Promise { + const pct = (this.state.usageMB / this.state.plan.dataLimitMB) * 100; + if (pct >= DATA_THRESHOLD * 100 && !this.state.alerts[0].sent) { + await this.sendSMS( + this.state.phoneNumber || this.env.TELNYX_PHONE_NUMBER, + `You've used ${Math.round(pct)}% of your data on SIM ${this.state.simId}. Reply "options" for upgrade plans.` + ); + this.state.alerts[0].sent = true; + await this.persist(); + } + } + + // ------------------------------------------------------------------ + // Webhook handler — incoming data-usage updates from Telnyx + // ------------------------------------------------------------------ + async onWebhook(event: string, payload: any): Promise { + if (event === 'usage.data') { + const deltaMB = payload.data?.usage_mb || 0; + this.state.usageMB += deltaMB; + this.state.history.push({ + date: new Date().toISOString(), + usageMB: deltaMB, + event: 'usage_update', + }); + await this.persist(); + // Re-evaluate thresholds after update + await this.checkThresholds(); + } + } + + // ------------------------------------------------------------------ + // SMS command handler — natural-language plan comparison + provisioning + // ------------------------------------------------------------------ + async onSMS(from: string, message: string): Promise { + const lower = message.toLowerCase().trim(); + + if (lower.includes('options') || lower.includes('plans') || lower.includes('what are my')) { + const plans = await this.getPlanOptions(); + await this.sendSMS(from, plans); + return; + } + + if (lower.includes('upgrade')) { + const planName = this.extractPlanName(lower); + if (planName) { + await this.provisionUpgrade(planName); + await this.sendSMS(from, `✅ Upgrade to ${planName} complete! New limit active.`); + } else { + await this.sendSMS(from, 'Please specify a plan: "upgrade to 10GB" or "upgrade to 5GB".'); + } + return; + } + + if (lower.includes('usage') || lower.includes('history') || lower.includes('summary')) { + const summary = this.buildUsageSummary(); + await this.sendSMS(from, summary); + return; + } + + // Default: LLM-powered natural language response + const reply = await this.llmReply(message); + await this.sendSMS(from, reply); + } + + // ------------------------------------------------------------------ + // Call Control — customer calls, agent answers with usage history + // ------------------------------------------------------------------ + async onCall(callId: string, from: string): Promise { + const summary = this.buildUsageSummary(); + await this.telnyx.calls.playAudio({ + call_id: callId, + audio_url: `https://api.telnyx.com/voice/text-to-speech`, // placeholder + payload: { text: summary }, + }); + } + + // ------------------------------------------------------------------ + // Auto-provisioning upgrade via Telnyx API + // ------------------------------------------------------------------ + private async provisionUpgrade(planName: string): Promise { + const planMap: Record = { + '5gb': 5120, + '10gb': 10240, + '20gb': 20480, + 'unlimited': 100000, + }; + + const dataLimitMB = planMap[planName.toLowerCase()]; + if (!dataLimitMB) { + throw new Error(`Unknown plan: ${planName}`); + } + + // Update SIM via Telnyx API + const client = telnyx(this.env.TELNYX_API_KEY); + await client.simCards.update(this.state.simId, { + data_plan: { name: planName.toUpperCase(), data_limit_mb: dataLimitMB }, + }); + + // Update local state + this.state.plan = { name: planName.toUpperCase(), dataLimitMB }; + this.state.alerts[0].sent = false; // reset alert for new cycle + this.state.history.push({ + date: new Date().toISOString(), + usageMB: 0, + event: `upgrade_to_${planName}`, + }); + await this.persist(); + } + + // ------------------------------------------------------------------ + // Natural language plan comparison via LLM + // ------------------------------------------------------------------ + private async getPlanOptions(): Promise { + const plans = [ + '5GB — $10/month', + '10GB — $20/month', + '20GB — $35/month', + 'Unlimited — $50/month', + ]; + + if (this.env.OPENAI_API_KEY) { + try { + const resp = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.env.OPENAI_API_KEY}`, + }, + body: JSON.stringify({ + model: 'gpt-3.5-turbo', + messages: [ + { + role: 'system', + content: 'You are a helpful SIM card assistant. Summarize these data plans in one friendly sentence.', + }, + { role: 'user', content: `Plans: ${plans.join(', ')}` }, + ], + max_tokens: 100, + }), + }); + const data = await resp.json(); + return data.choices?.[0]?.message?.content || plans.join('\n'); + } catch { + return plans.join('\n'); + } + } + + return plans.join('\n'); + } + + private async llmReply(message: string): Promise { + if (!this.env.OPENAI_API_KEY) { + return "I can help with: 'options', 'upgrade to 10GB', 'usage', or 'history'."; + } + try { + const resp = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.env.OPENAI_API_KEY}`, + }, + body: JSON.stringify({ + model: 'gpt-3.5-turbo', + messages: [ + { + role: 'system', + content: `You are SIMAgent, a SIM card assistant. Current usage: ${this.state.usageMB}MB of ${this.state.plan.dataLimitMB}MB. Plan: ${this.state.plan.name}.`, + }, + { role: 'user', content: message }, + ], + max_tokens: 150, + }), + }); + const data = await resp.json(); + return data.choices?.[0]?.message?.content || 'I did not understand that.'; + } catch { + return 'Sorry, I am having trouble processing your request right now.'; + } + } + + // ------------------------------------------------------------------ + // Billing cycle reset — scheduled task + // ------------------------------------------------------------------ + private async resetBillingCycle(): Promise { + const summary = this.buildUsageSummary(); + await this.sendSMS( + this.state.phoneNumber || this.env.TELNYX_PHONE_NUMBER, + `📊 Billing cycle ended.\n${summary}\nCounters reset for new cycle.` + ); + + this.state.usageMB = 0; + this.state.alerts[0].sent = false; + this.state.billingCycleStart = new Date().toISOString(); + this.state.history.push({ + date: new Date().toISOString(), + usageMB: 0, + event: 'billing_cycle_reset', + }); + await this.persist(); + } + + // ------------------------------------------------------------------ + // Helpers + // ------------------------------------------------------------------ + private async sendSMS(to: string, body: string): Promise { + if (this.env.DEMO_MODE === 'true') { + console.log(`[DEMO SMS] To: ${to} | Body: ${body}`); + return; + } + const client = telnyx(this.env.TELNYX_API_KEY); + await client.messages.create({ + from: this.env.TELNYX_PHONE_NUMBER, + to, + text: body, + }); + } + + private async persist(): Promise { + await this.kv.put(`sim:${this.entityId}`, this.state); + } + + private extractPlanName(msg: string): string | undefined { + const match = msg.match(/upgrade to (\d+gb|unlimited)/i); + return match ? match[1].toLowerCase() : undefined; + } + + private buildUsageSummary(): string { + const pct = Math.round((this.state.usageMB / this.state.plan.dataLimitMB) * 100); + return `SIM ${this.state.simId} | Plan: ${this.state.plan.name} | Usage: ${this.state.usageMB}MB / ${this.state.plan.dataLimitMB}MB (${pct}%)`; + } +} + +// --------------------------------------------------------------------------- +// Edge entry point — routes webhooks, SMS, and calls to the correct SIMAgent +// --------------------------------------------------------------------------- + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + const url = new URL(request.url); + const path = url.pathname; + + // Webhook endpoint — Telnyx sends data usage + SMS + call events + if (path === '/webhook' && request.method === 'POST') { + const body = await request.json(); + const eventType = body.event; + const simId = body.data?.sim_id || body.data?.to; + + if (!simId) { + return new Response('Bad request', { status: 400 }); + } + + const agent = new SIMAgent(env, simId); + await agent.init(); + await agent.onWebhook(eventType, body); + return new Response('OK', { status: 200 }); + } + + // SMS command endpoint + if (path === '/sms' && request.method === 'POST') { + const body = await request.json(); + const from = body.data?.from; + const message = body.data?.text; + const simId = body.data?.to; + + if (!simId || !from || !message) { + return new Response('Bad request', { status: 400 }); + } + + const agent = new SIMAgent(env, simId); + await agent.init(); + await agent.onSMS(from, message); + return new Response('OK', { status: 200 }); + } + + // Call Control endpoint + if (path === '/call' && request.method === 'POST') { + const body = await request.json(); + const callId = body.data?.call_id; + const from = body.data?.from; + const simId = body.data?.to; + + if (!simId || !callId || !from) { + return new Response('Bad request', { status: 400 }); + } + + const agent = new SIMAgent(env, simId); + await agent.init(); + await agent.onCall(callId, from); + return new Response('OK', { status: 200 }); + } + + // Health check + if (path === '/health') { + return new Response(JSON.stringify({ status: 'ok', mode: env.DEMO_MODE || 'demo' }), { + headers: { 'Content-Type': 'application/json' }, + }); + } + + return new Response('Not found', { status: 404 }); + }, +}; diff --git a/sim-agent/package.json b/sim-agent/package.json new file mode 100644 index 00000000..5893a55b --- /dev/null +++ b/sim-agent/package.json @@ -0,0 +1,22 @@ +{ + "name": "sim-agent", + "version": "1.0.0", + "description": "SIMAgent — The actor IS the SIM card. A Telnyx Edge Agent that tracks data usage, alerts on thresholds, and auto-provisions upgrades.", + "main": "src/index.ts", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "test": "vitest run", + "smoke": "vitest run smoke_test.ts" + }, + "dependencies": { + "@telnyx/edge-sdk": "^1.0.0", + "telnyx": "^4.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "vitest": "^1.0.0", + "typescript": "^5.0.0", + "wrangler": "^3.0.0" + } +} diff --git a/sim-agent/smoke_test.ts b/sim-agent/smoke_test.ts new file mode 100644 index 00000000..784ac5fd --- /dev/null +++ b/sim-agent/smoke_test.ts @@ -0,0 +1,2405 @@ +import { describe, it, expect } from 'vitest'; +import { SIMAgent } from '../src/index'; + +// Mock environment +const mockEnv = { + TELNYX_API_KEY: 'test_key', + TELNYX_PHONE_NUMBER: '+1555XXXXXXXX', + OPENAI_API_KEY: 'test_openai_key', + DEMO_MODE: 'true', + KV: { + get: async () => null, + put: async () => {}, + }, +}; + +describe('SIMAgent', () => { + it('should instantiate without error', () => { + const agent = new SIMAgent(mockEnv as any, 'sim-abc123'); + expect(agent).toBeDefined(); + expect(agent.entityId).toBe('sim-abc123'); + }); + + it('should initialize with default state when no KV data exists', async () => { + const agent = new SIMAgent(mockEnv as any, 'sim-abc123'); + await agent.init(); + expect(agent.state).toBeDefined(); + expect(agent.state.simId).toBe('sim-abc123'); + expect(agent.state.plan.name).toBe('1GB Starter'); + expect(agent.state.usageMB).toBe(0); + expect(agent.state.alerts[0].sent).toBe(false); + }); + + it('should build usage summary correctly', async () => { + const agent = new SIMAgent(mockEnv as any, 'sim-abc123'); + await agent.init(); + agent.state.usageMB = 512; + const summary = agent.buildUsageSummary(); + expect(summary).toContain('sim-abc123'); + expect(summary).toContain('50%'); + }); + + it('should extract plan name from upgrade message', async () => { + const agent = new SIMAgent(mockEnv as any, 'sim-abc123'); + await agent.init(); + const plan = agent.extractPlanName('upgrade to 10GB'); + expect(plan).toBe('10gb'); + }); + + it('should return undefined for unrecognized plan', async () => { + const agent = new SIMAgent(mockEnv as any, 'sim-abc123'); + await agent.init(); + const plan = agent.extractPlanName('upgrade to 50GB'); + expect(plan).toBeUndefined(); + }); +}); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +