@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 +55 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +151 -1
- package/dist/ragTool.d.ts +47 -0
- package/dist/testing/index.js +20 -1
- package/package.json +1 -1
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
|
|
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>;
|
package/dist/testing/index.js
CHANGED
|
@@ -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
|
|
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";
|