@absolutejs/voice 0.0.22-beta.120 → 0.0.22-beta.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +42 -3
  2. package/dist/index.js +15 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -224,9 +224,9 @@ app.use(
224
224
 
225
225
  The suite rolls up session quality, scenario evals, fixture simulations, tool contracts, and outcome contracts into one pass/fail report. It is the code-owned equivalent of "test this voice flow before production" without requiring a hosted voice-agent dashboard.
226
226
 
227
- ## Phone Voice Agent Path
227
+ ## Phone Voice Agent In 20 Minutes
228
228
 
229
- Use the telephony primitives when the agent needs to answer or place calls through your own carrier account:
229
+ Use `createVoicePhoneAgent(...)` when the agent needs to answer or place calls through your own Twilio, Telnyx, or Plivo account. This is the self-hosted alternative to a hosted phone-agent dashboard: your app owns the carrier routes, stream URLs, webhooks, traces, readiness checks, and lifecycle outcomes.
230
230
 
231
231
  ```ts
232
232
  import {
@@ -242,6 +242,10 @@ const outcomePolicy = createVoiceTelephonyOutcomePolicy({
242
242
  app
243
243
  .use(
244
244
  createVoicePhoneAgent({
245
+ setup: {
246
+ path: '/api/voice/phone/setup',
247
+ title: 'Support Phone Agent'
248
+ },
245
249
  matrix: {
246
250
  path: '/api/carriers',
247
251
  title: 'AbsoluteJS Voice Carrier Matrix'
@@ -274,7 +278,42 @@ app
274
278
  );
275
279
  ```
276
280
 
277
- The wrapper mounts selected carrier routes and a readiness matrix. Telnyx and Plivo use the same wrapper with `{ provider: 'telnyx', options: ... }` or `{ provider: 'plivo', options: ... }`. The lower-level `createTwilioVoiceRoutes(...)`, `createTelnyxVoiceRoutes(...)`, and `createPlivoVoiceRoutes(...)` helpers remain available when you need carrier-specific control.
281
+ The wrapper mounts selected carrier routes plus two proof surfaces:
282
+
283
+ - `/api/voice/phone/setup`: one setup report with carrier URLs, smoke links, lifecycle stages, and readiness.
284
+ - `/api/voice/phone/setup?format=html`: copy/paste setup page for carrier dashboards.
285
+ - `/api/carriers`: carrier matrix JSON for Twilio, Telnyx, and Plivo.
286
+ - `/api/carriers?format=html`: side-by-side carrier readiness matrix.
287
+
288
+ The setup page tells you exactly what to copy into the carrier dashboard:
289
+
290
+ - Twilio: set the phone number voice webhook/TwiML URL to the reported TwiML URL, set the status callback to the reported webhook URL, and allow the reported `wss://` media stream.
291
+ - Telnyx: set the connection TeXML URL to the reported TeXML URL, set the status webhook to the reported webhook URL, and allow the reported `wss://` media stream.
292
+ - Plivo: set the answer URL to the reported answer URL, set the status callback to the reported webhook URL, and allow the reported `wss://` media stream.
293
+
294
+ Each configured carrier can also expose its own setup and smoke pages, for example:
295
+
296
+ - `/api/voice/twilio/setup?format=html`
297
+ - `/api/voice/twilio/smoke?format=html`
298
+ - `/api/voice/telnyx/setup?format=html`
299
+ - `/api/voice/telnyx/smoke?format=html`
300
+ - `/api/voice/plivo/setup?format=html`
301
+ - `/api/voice/plivo/smoke?format=html`
302
+
303
+ The phone-agent report normalizes the lifecycle schema across carriers:
304
+
305
+ - `ringing`
306
+ - `answered`
307
+ - `media-started`
308
+ - `transcript`
309
+ - `assistant-response`
310
+ - `transfer`
311
+ - `voicemail`
312
+ - `no-answer`
313
+ - `completed`
314
+ - `failed`
315
+
316
+ That is the important Vapi/Retell/Bland gap this primitive closes: a team can mount one phone-agent entrypoint, bring its own carrier account, verify readiness before live calls, and keep call traces and lifecycle outcomes inside its own AbsoluteJS app. Telnyx and Plivo use the same wrapper with `{ provider: 'telnyx', options: ... }` or `{ provider: 'plivo', options: ... }`. The lower-level `createTwilioVoiceRoutes(...)`, `createTelnyxVoiceRoutes(...)`, and `createPlivoVoiceRoutes(...)` helpers remain available when you need carrier-specific control.
278
317
 
279
318
  ## App Kit And Status Widgets
280
319
 
package/dist/index.js CHANGED
@@ -15338,9 +15338,22 @@ var loadCarrierMatrixInputs = async (input) => {
15338
15338
  return entries;
15339
15339
  };
15340
15340
  var renderVoicePhoneAgentSetupHTML = (report) => {
15341
- const carrierRows = report.carriers.map((carrier) => `<tr><td>${escapeHtml28(carrier.name ?? carrier.provider)}</td><td>${escapeHtml28(carrier.provider)}</td><td><code>${escapeHtml28(carrier.setupPath || "disabled")}</code></td><td><code>${escapeHtml28(carrier.smokePath || "disabled")}</code></td></tr>`).join("");
15341
+ const carrierRows = report.carriers.map((carrier) => {
15342
+ const entry = report.matrix?.entries.find((candidate) => candidate.provider === carrier.provider && (candidate.name === carrier.name || candidate.name === (carrier.name ?? carrier.provider)));
15343
+ const urls = entry?.setup.urls;
15344
+ const primaryUrl = carrier.provider === "plivo" ? urls?.twiml : urls?.twiml;
15345
+ return `<tr><td>${escapeHtml28(carrier.name ?? carrier.provider)}</td><td>${escapeHtml28(carrier.provider)}</td><td><code>${escapeHtml28(carrier.setupPath || "disabled")}</code></td><td><code>${escapeHtml28(carrier.smokePath || "disabled")}</code></td><td>${entry ? `<span class="${escapeHtml28(entry.status)}">${escapeHtml28(entry.status.toUpperCase())}</span>` : "unknown"}</td><td>${primaryUrl ? `<code>${escapeHtml28(primaryUrl)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.webhook ? `<code>${escapeHtml28(urls.webhook)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.stream ? `<code>${escapeHtml28(urls.stream)}</code>` : '<span class="muted">missing</span>'}</td></tr>`;
15346
+ }).join("");
15342
15347
  const stageList = report.lifecycleStages.map((stage) => `<li><code>${escapeHtml28(stage)}</code></li>`).join("");
15343
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml28(report.title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}table{background:#151d27;border:1px solid #283544;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #283544;padding:12px;text-align:left}code{color:#fde68a}ul{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));padding-left:18px}</style></head><body><main><section class="hero"><p class="eyebrow">Phone agent setup</p><h1>${escapeHtml28(report.title)}</h1><p>One self-hosted entrypoint for carrier routes, setup reports, smoke checks, and normalized call lifecycle stages.</p><p class="badge ${report.ready ? "pass" : "fail"}">Ready: ${String(report.ready)}</p>${report.matrixPath ? `<p><a href="${escapeHtml28(report.matrixPath)}?format=html">Open carrier matrix</a></p>` : ""}</section><h2>Carriers</h2><table><thead><tr><th>Name</th><th>Provider</th><th>Setup</th><th>Smoke</th></tr></thead><tbody>${carrierRows}</tbody></table><h2>Lifecycle Schema</h2><ul>${stageList}</ul></main></body></html>`;
15348
+ const checklist = report.carriers.map((carrier) => {
15349
+ const entry = report.matrix?.entries.find((candidate) => candidate.provider === carrier.provider && (candidate.name === carrier.name || candidate.name === (carrier.name ?? carrier.provider)));
15350
+ const urls = entry?.setup.urls;
15351
+ const answerLabel = carrier.provider === "telnyx" ? "TeXML URL" : carrier.provider === "plivo" ? "Answer URL" : "TwiML URL";
15352
+ const answerUrl = urls?.twiml;
15353
+ const issueList = entry?.issues.map((issue) => `<li>${escapeHtml28(issue.severity)}: ${escapeHtml28(issue.message)}</li>`).join("") ?? "";
15354
+ return `<article><h3>${escapeHtml28(carrier.name ?? carrier.provider)}</h3><ol><li>Set ${escapeHtml28(answerLabel)} to <code>${escapeHtml28(answerUrl ?? "missing")}</code>.</li><li>Set status webhook to <code>${escapeHtml28(urls?.webhook ?? "missing")}</code>.</li><li>Allow media stream URL <code>${escapeHtml28(urls?.stream ?? "missing")}</code>.</li><li>Open setup: ${carrier.setupPath ? `<a href="${escapeHtml28(carrier.setupPath)}?format=html">${escapeHtml28(carrier.setupPath)}</a>` : '<span class="muted">disabled</span>'}.</li><li>Run smoke: ${carrier.smokePath ? `<a href="${escapeHtml28(carrier.smokePath)}?format=html">${escapeHtml28(carrier.smokePath)}</a>` : '<span class="muted">disabled</span>'}.</li></ol>${issueList ? `<ul class="issues">${issueList}</ul>` : '<p class="pass">No carrier contract issues.</p>'}</article>`;
15355
+ }).join("");
15356
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml28(report.title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.warn{color:#fde68a}.muted{color:#aab5c0}table{background:#151d27;border:1px solid #283544;border-collapse:collapse;border-radius:18px;display:block;overflow:auto;width:100%}td,th{border-bottom:1px solid #283544;padding:12px;text-align:left;vertical-align:top}code{color:#fde68a;overflow-wrap:anywhere}.checklist{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));margin:18px 0}.checklist article{background:#151d27;border:1px solid #283544;border-radius:18px;padding:18px}.checklist ol{padding-left:20px}.issues{color:#fca5a5}.stages{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));padding-left:18px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Phone agent setup</p><h1>${escapeHtml28(report.title)}</h1><p>One self-hosted entrypoint for carrier routes, setup reports, smoke checks, and normalized call lifecycle stages.</p><p class="badge ${report.ready ? "pass" : "fail"}">Ready: ${String(report.ready)}</p>${report.matrixPath ? `<p><a href="${escapeHtml28(report.matrixPath)}?format=html">Open carrier matrix</a></p>` : ""}</section><h2>Carrier Setup Checklist</h2><section class="checklist">${checklist}</section><h2>Carrier URLs</h2><table><thead><tr><th>Name</th><th>Provider</th><th>Setup</th><th>Smoke</th><th>Status</th><th>Answer/TwiML/TeXML</th><th>Webhook</th><th>Stream</th></tr></thead><tbody>${carrierRows}</tbody></table><h2>Lifecycle Schema</h2><ul class="stages">${stageList}</ul></main></body></html>`;
15344
15357
  };
15345
15358
  var createVoicePhoneAgent = (options) => {
15346
15359
  const carrierSummaries = options.carriers.map((carrier) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.120",
3
+ "version": "0.0.22-beta.122",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",