@absolutejs/voice 0.0.22-beta.467 → 0.0.22-beta.469

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.
package/README.md CHANGED
@@ -6,6 +6,61 @@ It gives your app the primitives hosted voice platforms usually keep behind thei
6
6
 
7
7
  Use it when you want Vapi/Retell/Bland-style voice-agent capability, but you want the orchestration, data, traces, storage, and UI to live inside the AbsoluteJS server you already operate.
8
8
 
9
+ ## What's new
10
+
11
+ ### 0.0.22-beta.469 · Vapi parity — RAG-as-a-tool helper
12
+
13
+ `createVoiceRAGTool(collection, options?)` wraps any `@absolutejs/rag` `RAGCollection` (or any object that satisfies `VoiceRAGCollectionLike`) into a `VoiceAgentTool` shaped like Vapi's built-in `query` / `knowledgeBaseId` flow.
14
+
15
+ ```ts
16
+ import { createInMemoryRAGStore, createRAGCollection } from "@absolutejs/rag";
17
+ import { createVoiceAssistant, createVoiceRAGTool } from "@absolutejs/voice";
18
+
19
+ const collection = createRAGCollection(
20
+ createInMemoryRAGStore(),
21
+ { embedding: openaiEmbeddings("text-embedding-3-small") },
22
+ );
23
+
24
+ const knowledgeBase = createVoiceRAGTool(collection, {
25
+ allowedFilterKeys: ["productLine"],
26
+ fixedFilter: ({ context }) => ({ tenantId: context.tenantId }),
27
+ maxChunkChars: 320,
28
+ scoreThreshold: 0.65,
29
+ });
30
+
31
+ const assistant = createVoiceAssistant({
32
+ id: "support",
33
+ model: openaiVoiceAssistantModel,
34
+ tools: [knowledgeBase],
35
+ system: "You are a support agent. Always cite the knowledge base.",
36
+ });
37
+ ```
38
+
39
+ Highlights:
40
+ - Default tool name `searchKnowledgeBase` matches the conventional Vapi knowledge-base tool name.
41
+ - Generated JSON-schema parameters expose `query` (required), `topK` (clamped to `maxTopK`), and an optional `filter` object whose keys are gated by `allowedFilterKeys`.
42
+ - `fixedFilter` can be a static record or a `(context) => filter` function (use for tenant scoping, locale, etc.). Fixed values always win over LLM-supplied filters.
43
+ - Default formatter returns numbered citations (`1. <title> (score 0.910): <chunk>`); override via `formatResult` or `resultToMessage`.
44
+ - No hard dependency on `@absolutejs/rag` — `VoiceRAGCollectionLike` is structurally compatible with `RAGCollection`, so install rag (or any duck-typed retrieval backend) as a peer.
45
+
46
+ ### 0.0.22-beta.464 → 468 · Media pipeline issue codes across product surfaces
47
+
48
+ `@absolutejs/voice` now projects compact `@absolutejs/media` reports into every buyer-facing voice surface so media health is gateable, linkable, and visible without buying a hosted dashboard.
49
+
50
+ **Proof pack (beta.464–465)** — `summarizeVoiceMediaPipelineReport(report, { artifacts })` returns a compact `VoiceMediaPipelineProofSummary` with aggregated `issueCodes` and per-section status. `writeVoiceMediaPipelineArtifacts({ dir, report, hrefBase })` persists `media-quality.{json,md}`, `media-transport.{json,md}`, and `media-processor-graph.{json,md}` into a proof-pack run directory and returns href links to wire into the summary. Together these shrink the `mediaPipelineCalibrationAssertion` summary in voice proof packs from ~35 KB to ~1.7 KB (95% reduction) and trim the total proof JSON from ~170 KB to ~114 KB.
51
+
52
+ **Production readiness (beta.464)** — `buildVoiceMediaPipelineReadinessChecks(report, { baseHref, label })` returns drop-in `VoiceProductionReadinessCheck[]` entries (overall, media quality, transport, processor graph, interruption). Spread them into `additionalChecks` for granular gating alongside the existing aggregate "Media pipeline quality" check.
53
+
54
+ **Operations record (beta.466–467)** — `VoiceOperationsRecord.mediaPipeline` is a new optional `VoiceOperationsRecordMediaPipelineSummary` (status, qualityStatus, transportStatus, processorGraphStatus, issueCodes, jitterMs, frames, surface). Populate it by passing `mediaPipeline: VoiceMediaPipelineReport` to `buildVoiceOperationsRecord`, or pass `mediaPipeline` (a report or sessionId-keyed resolver) to `createVoiceOperationsRecordRoutes` so every `/api/voice-operations/:sessionId` response carries it automatically.
55
+
56
+ **Failure replay (beta.466)** — `buildVoiceFailureReplay` reads `record.mediaPipeline` and adds `media.pipelineIssueCodes` + `media.pipelineStatus` to the report, appends `"Media pipeline issue: <code>"` entries to `summary.issues`, and demotes failure-replay status to `failed`/`degraded` when the pipeline is `fail`/`warn`. The Markdown incident artifact gets a new "Media Pipeline" section.
57
+
58
+ **Incident timeline (beta.466)** — `VoiceIncidentTimelineOptions.extraEvents` accepts any custom event source. Feed `buildVoiceMediaPipelineIncidentEvents(report, { now, source, category })` into it to surface media-pipeline issues as `category: "monitor"` entries in `/api/voice/incident-timeline` alongside operational and failure-replay events.
59
+
60
+ **Ops record renderers (beta.468)** — `renderVoiceOperationsRecordIncidentMarkdown` adds a one-line `Media pipeline` summary and an issue-code section. `renderVoiceOperationsRecordHTML` adds a nav anchor, a summary-grid card, and a `#media-pipeline` section listing surface, per-section status, frame/jitter, and codes.
61
+
62
+ Requires `@absolutejs/media@0.0.1-beta.16+`. The voice bundle externalizes `@absolutejs/media` to keep browser bundles parseable; install media as a peer dependency.
63
+
9
64
  ## Why AbsoluteJS Voice
10
65
 
11
66
  - Self-hosted by default: your app owns sessions, traces, reviews, tasks, handoffs, retention, and provider keys.
package/dist/index.d.ts CHANGED
@@ -69,6 +69,8 @@ export { assertVoiceSimulationSuiteEvidence, createVoiceSimulationSuiteRoutes, e
69
69
  export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult, } from "./workflowContract";
70
70
  export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceProviderFallbackRecovery, summarizeVoiceSessions, summarizeVoiceSessionReplay, } from "./sessionReplay";
71
71
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./agent";
72
+ export { createVoiceRAGTool } from "./ragTool";
73
+ export type { VoiceRAGCollectionLike, VoiceRAGQueryResult, VoiceRAGSearchInput, VoiceRAGToolArgs, VoiceRAGToolOptions, VoiceRAGToolResult, } from "./ragTool";
72
74
  export { assertVoiceAgentSquadContractEvidence, assertVoiceAgentSquadContract, evaluateVoiceAgentSquadContractEvidence, runVoiceAgentSquadContract, } from "./agentSquadContract";
73
75
  export { createVoiceToolIdempotencyKey, createVoiceToolRuntime, } from "./toolRuntime";
74
76
  export { assertVoiceToolContractEvidence, createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, evaluateVoiceToolContractEvidence, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract, } from "./toolContract";
package/dist/index.js CHANGED
@@ -20264,6 +20264,17 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
20264
20264
  ].filter((part) => typeof part === "string");
20265
20265
  return `- ${event.event}: ${parts.join("; ")}`;
20266
20266
  }) : ["- none recorded"];
20267
+ const mediaPipelineLine = record.mediaPipeline ? [
20268
+ `status=${record.mediaPipeline.status}`,
20269
+ `surface=${record.mediaPipeline.surface}`,
20270
+ `quality=${record.mediaPipeline.qualityStatus}`,
20271
+ `transport=${record.mediaPipeline.transportStatus ?? "n/a"}`,
20272
+ `graph=${record.mediaPipeline.processorGraphStatus ?? "n/a"}`,
20273
+ `frames=${String(record.mediaPipeline.frames)}`,
20274
+ `jitter=${record.mediaPipeline.jitterMs === undefined ? "n/a" : `${String(record.mediaPipeline.jitterMs)}ms`}`,
20275
+ `issueCodes=${record.mediaPipeline.issueCodes.join(", ") || "none"}`
20276
+ ].join("; ") : "not provided";
20277
+ const mediaPipelineCodeLines = record.mediaPipeline ? record.mediaPipeline.issueCodes.length ? record.mediaPipeline.issueCodes.map((code) => `- ${code}`) : ["- none"] : ["- media pipeline report not attached to this record"];
20267
20278
  return [
20268
20279
  `# Voice incident handoff: ${record.sessionId}`,
20269
20280
  "",
@@ -20278,6 +20289,7 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
20278
20289
  `- Guardrails: ${String(record.guardrails.blocked)} blocked / ${String(record.guardrails.warned)} warned / ${String(record.guardrails.total)} decisions`,
20279
20290
  `- Provider recovery: ${providerRecoveryLine}`,
20280
20291
  `- Telephony media: ${telephonyMediaLine}`,
20292
+ `- Media pipeline: ${mediaPipelineLine}`,
20281
20293
  "",
20282
20294
  "## Provider decisions",
20283
20295
  "",
@@ -20287,6 +20299,10 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
20287
20299
  "",
20288
20300
  ...telephonyMediaLines,
20289
20301
  "",
20302
+ "## Media pipeline issue codes",
20303
+ "",
20304
+ ...mediaPipelineCodeLines,
20305
+ "",
20290
20306
  renderVoiceOperationsRecordGuardrailMarkdown(record),
20291
20307
  "",
20292
20308
  "## Next checks",
@@ -20340,6 +20356,9 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
20340
20356
  ].filter((detail) => typeof detail === "string");
20341
20357
  return `<li><strong>${escapeHtml29(event.event)}</strong> <span>${escapeHtml29(new Date(event.at).toLocaleString())}</span><p>${escapeHtml29(details.join(" \xB7 "))}</p></li>`;
20342
20358
  }).join("") : "<li>No telephony media trace events recorded.</li>";
20359
+ const mediaPipelineSection = record.mediaPipeline ? `<section id="media-pipeline"><h2>Media Pipeline</h2><p class="muted">Surface: ${escapeHtml29(record.mediaPipeline.surface)} \xB7 Status: ${escapeHtml29(record.mediaPipeline.status)} \xB7 Quality: ${escapeHtml29(record.mediaPipeline.qualityStatus)} \xB7 Transport: ${escapeHtml29(record.mediaPipeline.transportStatus ?? "n/a")} \xB7 Graph: ${escapeHtml29(record.mediaPipeline.processorGraphStatus ?? "n/a")} \xB7 Frames: ${String(record.mediaPipeline.frames)} \xB7 Jitter: ${record.mediaPipeline.jitterMs === undefined ? "n/a" : `${String(record.mediaPipeline.jitterMs)}ms`}</p><ul>${record.mediaPipeline.issueCodes.length ? record.mediaPipeline.issueCodes.map((code) => `<li><strong>${escapeHtml29(code)}</strong></li>`).join("") : "<li>No media pipeline issue codes.</li>"}</ul></section>` : "";
20360
+ const mediaPipelineCard = record.mediaPipeline ? `<div class="card"><span>Media pipeline</span><strong>${escapeHtml29(record.mediaPipeline.status)}</strong><span>${String(record.mediaPipeline.issueCodes.length)} issue code(s)</span></div>` : "";
20361
+ const mediaPipelineNavLink = record.mediaPipeline ? '<a href="#media-pipeline">Media pipeline</a>' : "";
20343
20362
  const snippet = escapeHtml29(`app.use(
20344
20363
  createVoiceOperationsRecordRoutes({
20345
20364
  audit: auditStore,
@@ -20356,7 +20375,7 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
20356
20375
  );`);
20357
20376
  const incidentMarkdown = escapeHtml29(renderVoiceOperationsRecordIncidentMarkdown(record));
20358
20377
  const incidentLink = options.incidentHref ? `<a href="${escapeHtml29(options.incidentHref)}">Download incident.md</a>` : "";
20359
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml29(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml29(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml29(record.status)}">${escapeHtml29(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#telephony-media">Telephony media</a><a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs2(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml29(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Telephony media</span><strong>${String(record.telephonyMedia.media)}</strong><span>${String(record.telephonyMedia.inbound)} inbound / ${String(record.telephonyMedia.outbound)} outbound / ${String(record.telephonyMedia.clears)} clears</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="telephony-media"><h2>Telephony Media</h2><p class="muted">Live <code>client.telephony_media</code> stream lifecycle evidence attached to this session. Carriers: ${escapeHtml29(record.telephonyMedia.carriers.join(", ") || "none")}. Streams: ${escapeHtml29(record.telephonyMedia.streamIds.join(", ") || "none")}. Inbound: ${String(record.telephonyMedia.inbound)}. Outbound: ${String(record.telephonyMedia.outbound)}. Marks: ${String(record.telephonyMedia.marks)}. Clears: ${String(record.telephonyMedia.clears)}.</p><ul>${telephonyMedia}</ul></section><section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, media streams, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
20378
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml29(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml29(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml29(record.status)}">${escapeHtml29(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#telephony-media">Telephony media</a>${mediaPipelineNavLink}<a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs2(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml29(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Telephony media</span><strong>${String(record.telephonyMedia.media)}</strong><span>${String(record.telephonyMedia.inbound)} inbound / ${String(record.telephonyMedia.outbound)} outbound / ${String(record.telephonyMedia.clears)} clears</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div>${mediaPipelineCard}</section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="telephony-media"><h2>Telephony Media</h2><p class="muted">Live <code>client.telephony_media</code> stream lifecycle evidence attached to this session. Carriers: ${escapeHtml29(record.telephonyMedia.carriers.join(", ") || "none")}. Streams: ${escapeHtml29(record.telephonyMedia.streamIds.join(", ") || "none")}. Inbound: ${String(record.telephonyMedia.inbound)}. Outbound: ${String(record.telephonyMedia.outbound)}. Marks: ${String(record.telephonyMedia.marks)}. Clears: ${String(record.telephonyMedia.clears)}.</p><ul>${telephonyMedia}</ul></section>${mediaPipelineSection}<section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, media streams, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
20360
20379
  };
20361
20380
  var createVoiceOperationsRecordRoutes = (options) => {
20362
20381
  const path = options.path ?? "/api/voice-operations/:sessionId";
@@ -34480,6 +34499,136 @@ var createVoiceWorkflowContractHandler = (input) => {
34480
34499
  return result;
34481
34500
  };
34482
34501
  };
34502
+ // src/ragTool.ts
34503
+ var DEFAULT_TOOL_NAME = "searchKnowledgeBase";
34504
+ var DEFAULT_DESCRIPTION = "Search the knowledge base and return short grounded citations. Use this whenever the caller asks a question that may be answered by indexed reference material.";
34505
+ var DEFAULT_TOP_K = 6;
34506
+ var DEFAULT_MAX_TOP_K = 20;
34507
+ var DEFAULT_MAX_CHUNK_CHARS = 320;
34508
+ var truncate = (value, limit) => {
34509
+ if (limit <= 0 || value.length <= limit)
34510
+ return value;
34511
+ return `${value.slice(0, Math.max(0, limit - 1)).trimEnd()}\u2026`;
34512
+ };
34513
+ var formatScore = (score) => {
34514
+ if (!Number.isFinite(score))
34515
+ return "n/a";
34516
+ return score.toFixed(3);
34517
+ };
34518
+ var buildDefaultCitationMessage = (citations, args, maxChunkChars) => {
34519
+ if (citations.length === 0) {
34520
+ return `No knowledge base results for "${args.query}".`;
34521
+ }
34522
+ const lines = citations.map((citation, index) => {
34523
+ const label = citation.title ?? citation.source ?? citation.chunkId;
34524
+ const text = truncate(citation.chunkText, maxChunkChars);
34525
+ return `${String(index + 1)}. ${label} (score ${formatScore(citation.score)}): ${text}`;
34526
+ });
34527
+ return [
34528
+ `Knowledge base results for "${args.query}":`,
34529
+ ...lines
34530
+ ].join(`
34531
+ `);
34532
+ };
34533
+ var filterAllowedFilterKeys = (filter, allowedKeys) => {
34534
+ if (!filter)
34535
+ return;
34536
+ if (!allowedKeys)
34537
+ return filter;
34538
+ const allowed = new Set(allowedKeys);
34539
+ const entries = Object.entries(filter).filter(([key]) => allowed.has(key));
34540
+ if (entries.length === 0)
34541
+ return;
34542
+ return Object.fromEntries(entries);
34543
+ };
34544
+ var mergeFilters = (...filters) => {
34545
+ const present = filters.filter((entry) => entry !== undefined);
34546
+ if (present.length === 0)
34547
+ return;
34548
+ return Object.assign({}, ...present);
34549
+ };
34550
+ var buildVoiceRAGToolParameters = (options) => {
34551
+ if (options.parameters)
34552
+ return options.parameters;
34553
+ const defaultTopK = options.topK ?? DEFAULT_TOP_K;
34554
+ const maxTopK = options.maxTopK ?? DEFAULT_MAX_TOP_K;
34555
+ const properties = {
34556
+ query: {
34557
+ description: "Natural-language question to look up in the knowledge base.",
34558
+ type: "string"
34559
+ },
34560
+ topK: {
34561
+ default: defaultTopK,
34562
+ description: `How many citations to return (1-${String(maxTopK)}).`,
34563
+ maximum: maxTopK,
34564
+ minimum: 1,
34565
+ type: "integer"
34566
+ }
34567
+ };
34568
+ if (options.allowedFilterKeys && options.allowedFilterKeys.length > 0) {
34569
+ properties.filter = {
34570
+ additionalProperties: false,
34571
+ description: "Optional metadata filter. Only keys listed here are honored: " + options.allowedFilterKeys.join(", "),
34572
+ properties: Object.fromEntries(options.allowedFilterKeys.map((key) => [key, {}])),
34573
+ type: "object"
34574
+ };
34575
+ }
34576
+ return {
34577
+ additionalProperties: false,
34578
+ properties,
34579
+ required: ["query"],
34580
+ type: "object"
34581
+ };
34582
+ };
34583
+ var createVoiceRAGTool = (collection, options = {}) => {
34584
+ const name = options.name ?? DEFAULT_TOOL_NAME;
34585
+ const description = options.description ?? DEFAULT_DESCRIPTION;
34586
+ const defaultTopK = options.topK ?? DEFAULT_TOP_K;
34587
+ const maxTopK = options.maxTopK ?? DEFAULT_MAX_TOP_K;
34588
+ const maxChunkChars = options.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;
34589
+ const parameters = buildVoiceRAGToolParameters(options);
34590
+ return createVoiceAgentTool({
34591
+ description,
34592
+ execute: async ({ args, context }) => {
34593
+ const query = typeof args?.query === "string" ? args.query.trim() : "";
34594
+ if (query.length === 0) {
34595
+ const empty = {
34596
+ citations: [],
34597
+ message: "Knowledge base search requires a non-empty query.",
34598
+ query: "",
34599
+ topK: 0
34600
+ };
34601
+ return empty;
34602
+ }
34603
+ const requestedTopK = typeof args?.topK === "number" && Number.isFinite(args.topK) ? Math.min(maxTopK, Math.max(1, Math.floor(args.topK))) : defaultTopK;
34604
+ const llmFilter = filterAllowedFilterKeys(args?.filter, options.allowedFilterKeys);
34605
+ const fixedFilter = typeof options.fixedFilter === "function" ? options.fixedFilter({ context }) : options.fixedFilter;
34606
+ const filter = mergeFilters(fixedFilter, llmFilter);
34607
+ const rawResults = await collection.search({
34608
+ filter,
34609
+ query,
34610
+ scoreThreshold: options.scoreThreshold,
34611
+ topK: requestedTopK
34612
+ });
34613
+ const citations = Array.from(rawResults).slice(0, requestedTopK);
34614
+ const formatter = options.formatResult ? options.formatResult : (entries, innerArgs) => buildDefaultCitationMessage(entries, innerArgs, maxChunkChars);
34615
+ const message = formatter(citations, {
34616
+ filter,
34617
+ query,
34618
+ topK: requestedTopK
34619
+ });
34620
+ return {
34621
+ citations,
34622
+ message,
34623
+ query,
34624
+ topK: requestedTopK
34625
+ };
34626
+ },
34627
+ name,
34628
+ parameters,
34629
+ resultToMessage: options.resultToMessage ?? ((result) => result.message)
34630
+ });
34631
+ };
34483
34632
  // src/agentSquadContract.ts
34484
34633
  var normalizeIncludes = (value) => value.trim().toLowerCase();
34485
34634
  var resolveOutcome4 = (result) => {
@@ -43137,6 +43286,7 @@ export {
43137
43286
  createVoiceRealCallEvidenceRuntimeRoutes,
43138
43287
  createVoiceRealCallEvidenceRuntime,
43139
43288
  createVoiceReadinessProfile,
43289
+ createVoiceRAGTool,
43140
43290
  createVoiceQualityRoutes,
43141
43291
  createVoiceProviderSloRoutes,
43142
43292
  createVoiceProviderRouter,
@@ -0,0 +1,47 @@
1
+ import { type VoiceAgentTool } from "./agent";
2
+ import type { VoiceSessionRecord } from "./types";
3
+ export type VoiceRAGQueryResult = {
4
+ chunkId: string;
5
+ chunkText: string;
6
+ score: number;
7
+ source?: string;
8
+ title?: string;
9
+ metadata?: Record<string, unknown>;
10
+ };
11
+ export type VoiceRAGSearchInput = {
12
+ filter?: Record<string, unknown>;
13
+ query: string;
14
+ scoreThreshold?: number;
15
+ signal?: AbortSignal;
16
+ topK?: number;
17
+ };
18
+ export type VoiceRAGCollectionLike = {
19
+ search: (input: VoiceRAGSearchInput) => Promise<readonly VoiceRAGQueryResult[]> | readonly VoiceRAGQueryResult[];
20
+ };
21
+ export type VoiceRAGToolArgs = {
22
+ filter?: Record<string, unknown>;
23
+ query: string;
24
+ topK?: number;
25
+ };
26
+ export type VoiceRAGToolResult = {
27
+ citations: VoiceRAGQueryResult[];
28
+ message: string;
29
+ query: string;
30
+ topK: number;
31
+ };
32
+ export type VoiceRAGToolOptions<TContext = unknown> = {
33
+ allowedFilterKeys?: readonly string[];
34
+ description?: string;
35
+ fixedFilter?: Record<string, unknown> | ((input: {
36
+ context: TContext;
37
+ }) => Record<string, unknown> | undefined);
38
+ formatResult?: (citations: readonly VoiceRAGQueryResult[], args: VoiceRAGToolArgs) => string;
39
+ maxChunkChars?: number;
40
+ maxTopK?: number;
41
+ name?: string;
42
+ parameters?: Record<string, unknown>;
43
+ resultToMessage?: (result: VoiceRAGToolResult) => string;
44
+ scoreThreshold?: number;
45
+ topK?: number;
46
+ };
47
+ export declare const createVoiceRAGTool: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord>(collection: VoiceRAGCollectionLike, options?: VoiceRAGToolOptions<TContext>) => VoiceAgentTool<TContext, TSession, VoiceRAGToolArgs, VoiceRAGToolResult>;
@@ -10958,6 +10958,17 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
10958
10958
  ].filter((part) => typeof part === "string");
10959
10959
  return `- ${event.event}: ${parts.join("; ")}`;
10960
10960
  }) : ["- none recorded"];
10961
+ const mediaPipelineLine = record.mediaPipeline ? [
10962
+ `status=${record.mediaPipeline.status}`,
10963
+ `surface=${record.mediaPipeline.surface}`,
10964
+ `quality=${record.mediaPipeline.qualityStatus}`,
10965
+ `transport=${record.mediaPipeline.transportStatus ?? "n/a"}`,
10966
+ `graph=${record.mediaPipeline.processorGraphStatus ?? "n/a"}`,
10967
+ `frames=${String(record.mediaPipeline.frames)}`,
10968
+ `jitter=${record.mediaPipeline.jitterMs === undefined ? "n/a" : `${String(record.mediaPipeline.jitterMs)}ms`}`,
10969
+ `issueCodes=${record.mediaPipeline.issueCodes.join(", ") || "none"}`
10970
+ ].join("; ") : "not provided";
10971
+ const mediaPipelineCodeLines = record.mediaPipeline ? record.mediaPipeline.issueCodes.length ? record.mediaPipeline.issueCodes.map((code) => `- ${code}`) : ["- none"] : ["- media pipeline report not attached to this record"];
10961
10972
  return [
10962
10973
  `# Voice incident handoff: ${record.sessionId}`,
10963
10974
  "",
@@ -10972,6 +10983,7 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
10972
10983
  `- Guardrails: ${String(record.guardrails.blocked)} blocked / ${String(record.guardrails.warned)} warned / ${String(record.guardrails.total)} decisions`,
10973
10984
  `- Provider recovery: ${providerRecoveryLine}`,
10974
10985
  `- Telephony media: ${telephonyMediaLine}`,
10986
+ `- Media pipeline: ${mediaPipelineLine}`,
10975
10987
  "",
10976
10988
  "## Provider decisions",
10977
10989
  "",
@@ -10981,6 +10993,10 @@ var renderVoiceOperationsRecordIncidentMarkdown = (record) => {
10981
10993
  "",
10982
10994
  ...telephonyMediaLines,
10983
10995
  "",
10996
+ "## Media pipeline issue codes",
10997
+ "",
10998
+ ...mediaPipelineCodeLines,
10999
+ "",
10984
11000
  renderVoiceOperationsRecordGuardrailMarkdown(record),
10985
11001
  "",
10986
11002
  "## Next checks",
@@ -11034,6 +11050,9 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
11034
11050
  ].filter((detail) => typeof detail === "string");
11035
11051
  return `<li><strong>${escapeHtml7(event.event)}</strong> <span>${escapeHtml7(new Date(event.at).toLocaleString())}</span><p>${escapeHtml7(details.join(" \xB7 "))}</p></li>`;
11036
11052
  }).join("") : "<li>No telephony media trace events recorded.</li>";
11053
+ const mediaPipelineSection = record.mediaPipeline ? `<section id="media-pipeline"><h2>Media Pipeline</h2><p class="muted">Surface: ${escapeHtml7(record.mediaPipeline.surface)} \xB7 Status: ${escapeHtml7(record.mediaPipeline.status)} \xB7 Quality: ${escapeHtml7(record.mediaPipeline.qualityStatus)} \xB7 Transport: ${escapeHtml7(record.mediaPipeline.transportStatus ?? "n/a")} \xB7 Graph: ${escapeHtml7(record.mediaPipeline.processorGraphStatus ?? "n/a")} \xB7 Frames: ${String(record.mediaPipeline.frames)} \xB7 Jitter: ${record.mediaPipeline.jitterMs === undefined ? "n/a" : `${String(record.mediaPipeline.jitterMs)}ms`}</p><ul>${record.mediaPipeline.issueCodes.length ? record.mediaPipeline.issueCodes.map((code) => `<li><strong>${escapeHtml7(code)}</strong></li>`).join("") : "<li>No media pipeline issue codes.</li>"}</ul></section>` : "";
11054
+ const mediaPipelineCard = record.mediaPipeline ? `<div class="card"><span>Media pipeline</span><strong>${escapeHtml7(record.mediaPipeline.status)}</strong><span>${String(record.mediaPipeline.issueCodes.length)} issue code(s)</span></div>` : "";
11055
+ const mediaPipelineNavLink = record.mediaPipeline ? '<a href="#media-pipeline">Media pipeline</a>' : "";
11037
11056
  const snippet = escapeHtml7(`app.use(
11038
11057
  createVoiceOperationsRecordRoutes({
11039
11058
  audit: auditStore,
@@ -11050,7 +11069,7 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
11050
11069
  );`);
11051
11070
  const incidentMarkdown = escapeHtml7(renderVoiceOperationsRecordIncidentMarkdown(record));
11052
11071
  const incidentLink = options.incidentHref ? `<a href="${escapeHtml7(options.incidentHref)}">Download incident.md</a>` : "";
11053
- return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml7(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml7(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml7(record.status)}">${escapeHtml7(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#telephony-media">Telephony media</a><a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs2(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml7(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Telephony media</span><strong>${String(record.telephonyMedia.media)}</strong><span>${String(record.telephonyMedia.inbound)} inbound / ${String(record.telephonyMedia.outbound)} outbound / ${String(record.telephonyMedia.clears)} clears</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="telephony-media"><h2>Telephony Media</h2><p class="muted">Live <code>client.telephony_media</code> stream lifecycle evidence attached to this session. Carriers: ${escapeHtml7(record.telephonyMedia.carriers.join(", ") || "none")}. Streams: ${escapeHtml7(record.telephonyMedia.streamIds.join(", ") || "none")}. Inbound: ${String(record.telephonyMedia.inbound)}. Outbound: ${String(record.telephonyMedia.outbound)}. Marks: ${String(record.telephonyMedia.marks)}. Clears: ${String(record.telephonyMedia.clears)}.</p><ul>${telephonyMedia}</ul></section><section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, media streams, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
11072
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml7(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted,.label{color:#a9b4bd}.label{display:block;font-size:.72rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.hero-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.hero-actions a{background:#fbbf24;border-radius:999px;color:#111827;font-weight:900;padding:10px 14px;text-decoration:none}.two-column{display:grid;gap:18px;grid-template-columns:minmax(0,1.15fr) minmax(280px,.85fr)}@media(max-width:860px){main{padding:20px}.two-column{grid-template-columns:1fr}}</style></head><body><main><p class="eyebrow">Call log replacement</p><h1>${escapeHtml7(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml7(record.status)}">${escapeHtml7(record.status)}</p><div class="hero-actions"><a href="#transcript">Transcript</a><a href="#provider-decisions">Provider decisions</a><a href="#telephony-media">Telephony media</a>${mediaPipelineNavLink}<a href="#guardrails">Guardrails</a><a href="#incident-handoff">Incident handoff</a>${incidentLink}</div><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs2(record.summary.callDurationMs)}</strong></div><div class="card"><span>Provider recovery</span><strong>${escapeHtml7(providerDecisionSummary.recoveryStatus)}</strong><span>${String(providerDecisionSummary.fallbacks)} fallback / ${String(providerDecisionSummary.degraded)} degraded / ${String(providerDecisionSummary.errors)} errors</span></div><div class="card"><span>Telephony media</span><strong>${String(record.telephonyMedia.media)}</strong><span>${String(record.telephonyMedia.inbound)} inbound / ${String(record.telephonyMedia.outbound)} outbound / ${String(record.telephonyMedia.clears)} clears</span></div><div class="card"><span>Guardrails</span><strong>${String(record.guardrails.blocked)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div>${mediaPipelineCard}</section><section class="two-column"><div><h2 id="transcript">Transcript</h2><ul>${transcript}</ul></div><div><h2 id="provider-decisions">Provider Decisions</h2><ul>${providerDecisions}</ul></div></section><section id="telephony-media"><h2>Telephony Media</h2><p class="muted">Live <code>client.telephony_media</code> stream lifecycle evidence attached to this session. Carriers: ${escapeHtml7(record.telephonyMedia.carriers.join(", ") || "none")}. Streams: ${escapeHtml7(record.telephonyMedia.streamIds.join(", ") || "none")}. Inbound: ${String(record.telephonyMedia.inbound)}. Outbound: ${String(record.telephonyMedia.outbound)}. Marks: ${String(record.telephonyMedia.marks)}. Clears: ${String(record.telephonyMedia.clears)}.</p><ul>${telephonyMedia}</ul></section>${mediaPipelineSection}<section id="guardrails"><h2>Guardrail Evidence</h2><p class="muted">Live <code>assistant.guardrail</code> decisions attached to this session.</p><ul>${guardrails}</ul></section><section id="incident-handoff"><h2>Copyable Incident Handoff</h2><p class="muted">Paste this into Slack, Linear, Zendesk, or an incident review. ${incidentLink}</p><pre><code>${incidentMarkdown}</code></pre></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, guardrails, audit, latency, replay, reviews, tasks, media streams, and webhook delivery.</p><pre><code>${snippet}</code></pre></section><section><h2>Provider Summary</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
11054
11073
  };
11055
11074
  var createVoiceOperationsRecordRoutes = (options) => {
11056
11075
  const path = options.path ?? "/api/voice-operations/:sessionId";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.467",
3
+ "version": "0.0.22-beta.469",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",