@absolutejs/voice 0.0.22-beta.37 → 0.0.22-beta.38
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/dist/diagnosticsRoutes.d.ts +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +215 -77
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { evaluateVoiceTrace, type StoredVoiceTraceEvent, type VoiceTraceEventFilter, type VoiceTraceEventStore, type VoiceTraceRedactionConfig } from './trace';
|
|
3
|
+
export type VoiceDiagnosticsRoutesOptions = {
|
|
4
|
+
evaluation?: Parameters<typeof evaluateVoiceTrace>[1];
|
|
5
|
+
headers?: HeadersInit;
|
|
6
|
+
name?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
redact?: VoiceTraceRedactionConfig;
|
|
9
|
+
store: VoiceTraceEventStore;
|
|
10
|
+
title?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const resolveVoiceDiagnosticsTraceFilter: (query: Record<string, unknown>) => VoiceTraceEventFilter;
|
|
13
|
+
export declare const buildVoiceDiagnosticsMarkdown: (events: StoredVoiceTraceEvent[], options?: {
|
|
14
|
+
evaluation?: Parameters<typeof evaluateVoiceTrace>[1];
|
|
15
|
+
title?: string;
|
|
16
|
+
}) => string;
|
|
17
|
+
export declare const createVoiceDiagnosticsRoutes: (options: VoiceDiagnosticsRoutesOptions) => Elysia<"", {
|
|
18
|
+
decorator: {};
|
|
19
|
+
store: {};
|
|
20
|
+
derive: {};
|
|
21
|
+
resolve: {};
|
|
22
|
+
}, {
|
|
23
|
+
typebox: {};
|
|
24
|
+
error: {};
|
|
25
|
+
}, {
|
|
26
|
+
schema: {};
|
|
27
|
+
standaloneSchema: {};
|
|
28
|
+
macro: {};
|
|
29
|
+
macroFn: {};
|
|
30
|
+
parser: {};
|
|
31
|
+
response: {};
|
|
32
|
+
}, {}, {
|
|
33
|
+
derive: {};
|
|
34
|
+
resolve: {};
|
|
35
|
+
schema: {};
|
|
36
|
+
standaloneSchema: {};
|
|
37
|
+
response: {};
|
|
38
|
+
}, {
|
|
39
|
+
derive: {};
|
|
40
|
+
resolve: {};
|
|
41
|
+
schema: {};
|
|
42
|
+
standaloneSchema: {};
|
|
43
|
+
response: {};
|
|
44
|
+
}>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { voice } from './plugin';
|
|
2
2
|
export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
|
|
3
3
|
export { createVoiceAssistantHealthHTMLHandler, createVoiceAssistantHealthJSONHandler, createVoiceAssistantHealthRoutes, renderVoiceAssistantHealthHTML, summarizeVoiceAssistantHealth } from './assistantHealth';
|
|
4
|
+
export { buildVoiceDiagnosticsMarkdown, createVoiceDiagnosticsRoutes, resolveVoiceDiagnosticsTraceFilter } from './diagnosticsRoutes';
|
|
4
5
|
export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceSessions, summarizeVoiceSessionReplay } from './sessionReplay';
|
|
5
6
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
|
|
6
7
|
export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
|
|
@@ -35,6 +36,7 @@ export { createVoiceCallReviewFromLiveTelephonyReport, createVoiceCallReviewReco
|
|
|
35
36
|
export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperiment, VoiceAssistantExperimentOptions, VoiceAssistantGuardrailInput, VoiceAssistantGuardrails, VoiceAssistantMemoryLifecycle, VoiceAssistantMemoryLifecycleInput, VoiceAssistantOptions, VoiceAssistantOutputGuardrailInput, VoiceAssistantPreset, VoiceAssistantRunsSummary, VoiceAssistantRunSummary, VoiceAssistantVariant } from './assistant';
|
|
36
37
|
export type { VoiceAssistantHealthFailure, VoiceAssistantHealthHTMLHandlerOptions, VoiceAssistantHealthRoutesOptions, VoiceAssistantHealthSummary, VoiceAssistantHealthSummaryOptions } from './assistantHealth';
|
|
37
38
|
export type { VoiceAssistantMemoryBinding, VoiceAssistantMemoryHandle, VoiceAssistantMemoryOptions, VoiceAssistantMemoryRecord, VoiceAssistantMemoryStore } from './assistantMemory';
|
|
39
|
+
export type { VoiceDiagnosticsRoutesOptions } from './diagnosticsRoutes';
|
|
38
40
|
export type { VoiceSessionListHTMLHandlerOptions, VoiceSessionListItem, VoiceSessionListOptions, VoiceSessionListRoutesOptions, VoiceSessionListStatus, VoiceSessionReplay, VoiceSessionReplayHTMLHandlerOptions, VoiceSessionReplayOptions, VoiceSessionReplayRoutesOptions, VoiceSessionReplayTurn } from './sessionReplay';
|
|
39
41
|
export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
|
|
40
42
|
export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
|
package/dist/index.js
CHANGED
|
@@ -6822,7 +6822,7 @@ var createVoiceAssistantHealthRoutes = (options) => {
|
|
|
6822
6822
|
}
|
|
6823
6823
|
return routes;
|
|
6824
6824
|
};
|
|
6825
|
-
// src/
|
|
6825
|
+
// src/diagnosticsRoutes.ts
|
|
6826
6826
|
import { Elysia as Elysia4 } from "elysia";
|
|
6827
6827
|
|
|
6828
6828
|
// src/trace.ts
|
|
@@ -7489,9 +7489,144 @@ var buildVoiceTraceReplay = (events, options = {}) => ({
|
|
|
7489
7489
|
summary: summarizeVoiceTrace(options.redact ? redactVoiceTraceEvents(events, options.redact) : events)
|
|
7490
7490
|
});
|
|
7491
7491
|
|
|
7492
|
-
// src/
|
|
7493
|
-
var getString3 = (value) => typeof value === "string" ? value : undefined;
|
|
7492
|
+
// src/diagnosticsRoutes.ts
|
|
7494
7493
|
var escapeHtml6 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
7494
|
+
var getString3 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
7495
|
+
var getNumber2 = (value) => {
|
|
7496
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : undefined;
|
|
7497
|
+
return typeof parsed === "number" && Number.isFinite(parsed) ? parsed : undefined;
|
|
7498
|
+
};
|
|
7499
|
+
var getBoolean = (value) => value === true || value === "true" || value === "1";
|
|
7500
|
+
var parseTraceTypeFilter = (value) => {
|
|
7501
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
7502
|
+
return;
|
|
7503
|
+
}
|
|
7504
|
+
const types = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
7505
|
+
return types.length <= 1 ? types[0] : types;
|
|
7506
|
+
};
|
|
7507
|
+
var resolveVoiceDiagnosticsTraceFilter = (query) => ({
|
|
7508
|
+
limit: getNumber2(query.limit),
|
|
7509
|
+
scenarioId: getString3(query.scenarioId),
|
|
7510
|
+
sessionId: getString3(query.sessionId),
|
|
7511
|
+
traceId: getString3(query.traceId),
|
|
7512
|
+
turnId: getString3(query.turnId),
|
|
7513
|
+
type: parseTraceTypeFilter(query.type)
|
|
7514
|
+
});
|
|
7515
|
+
var filterByDiagnosticsQuery = (events, query) => {
|
|
7516
|
+
const provider = getString3(query.provider);
|
|
7517
|
+
const status = getString3(query.status);
|
|
7518
|
+
const since = getNumber2(query.since);
|
|
7519
|
+
const until = getNumber2(query.until);
|
|
7520
|
+
return filterVoiceTraceEvents(events, resolveVoiceDiagnosticsTraceFilter(query)).filter((event) => (!provider || event.payload.provider === provider) && (!status || event.payload.providerStatus === status || event.payload.status === status) && (since === undefined || event.at >= since) && (until === undefined || event.at <= until));
|
|
7521
|
+
};
|
|
7522
|
+
var buildVoiceDiagnosticsMarkdown = (events, options = {}) => {
|
|
7523
|
+
const summary = summarizeVoiceTrace(events);
|
|
7524
|
+
const evaluation = evaluateVoiceTrace(events, options.evaluation);
|
|
7525
|
+
const trace = renderVoiceTraceMarkdown(events, {
|
|
7526
|
+
evaluation: options.evaluation,
|
|
7527
|
+
title: options.title ?? `Voice Diagnostics ${summary.sessionId ?? ""}`.trim()
|
|
7528
|
+
});
|
|
7529
|
+
return [
|
|
7530
|
+
`# ${options.title ?? "Voice Diagnostics Bug Report"}`,
|
|
7531
|
+
"",
|
|
7532
|
+
`Session: ${summary.sessionId ?? "unknown"}`,
|
|
7533
|
+
`Pass: ${evaluation.pass ? "yes" : "no"}`,
|
|
7534
|
+
`Events: ${summary.eventCount}`,
|
|
7535
|
+
`Turns: ${summary.turnCount}`,
|
|
7536
|
+
`Errors: ${summary.errorCount}`,
|
|
7537
|
+
`Tool errors: ${summary.toolErrorCount}`,
|
|
7538
|
+
`Estimated cost units: ${summary.cost.estimatedRelativeCostUnits}`,
|
|
7539
|
+
"",
|
|
7540
|
+
"## Issues",
|
|
7541
|
+
"",
|
|
7542
|
+
evaluation.issues.length ? evaluation.issues.map((issue) => `- [${issue.severity}] ${issue.code}: ${issue.message}`).join(`
|
|
7543
|
+
`) : "- none",
|
|
7544
|
+
"",
|
|
7545
|
+
"## Trace",
|
|
7546
|
+
"",
|
|
7547
|
+
trace
|
|
7548
|
+
].join(`
|
|
7549
|
+
`);
|
|
7550
|
+
};
|
|
7551
|
+
var renderDiagnosticsIndex = (input) => {
|
|
7552
|
+
const sessions = new Map;
|
|
7553
|
+
for (const event of input.events) {
|
|
7554
|
+
sessions.set(event.sessionId, [...sessions.get(event.sessionId) ?? [], event]);
|
|
7555
|
+
}
|
|
7556
|
+
const rows = [...sessions.entries()].sort(([, left], [, right]) => (right.at(-1)?.at ?? 0) - (left.at(-1)?.at ?? 0)).slice(0, 50).map(([sessionId, events]) => {
|
|
7557
|
+
const summary = summarizeVoiceTrace(events);
|
|
7558
|
+
const encoded = encodeURIComponent(sessionId);
|
|
7559
|
+
return `<tr><td>${escapeHtml6(sessionId)}</td><td>${summary.eventCount}</td><td>${summary.turnCount}</td><td>${summary.errorCount}</td><td><a href="${input.basePath}/html?sessionId=${encoded}&redact=true">HTML</a> \xB7 <a href="${input.basePath}/markdown?sessionId=${encoded}&redact=true">Markdown</a> \xB7 <a href="${input.basePath}/json?sessionId=${encoded}&redact=true">JSON</a></td></tr>`;
|
|
7560
|
+
}).join("");
|
|
7561
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml6(input.title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1100px;margin:auto}table{width:100%;border-collapse:collapse;background:white}td,th{border-bottom:1px solid #eee;padding:.7rem;text-align:left}a{color:#9a3412}</style></head><body><main><h1>${escapeHtml6(input.title)}</h1><p>Recent voice trace diagnostics. Exports support filters: sessionId, traceId, turnId, scenarioId, type, provider, status, since, until, limit, redact.</p><table><thead><tr><th>Session</th><th>Events</th><th>Turns</th><th>Errors</th><th>Exports</th></tr></thead><tbody>${rows}</tbody></table></main></body></html>`;
|
|
7562
|
+
};
|
|
7563
|
+
var withRedaction = (events, query, defaultRedact) => {
|
|
7564
|
+
const shouldRedact = query.redact === undefined ? defaultRedact : getBoolean(query.redact);
|
|
7565
|
+
return shouldRedact ? redactVoiceTraceEvents(events, shouldRedact) : events;
|
|
7566
|
+
};
|
|
7567
|
+
var createVoiceDiagnosticsRoutes = (options) => {
|
|
7568
|
+
const path = options.path ?? "/diagnostics";
|
|
7569
|
+
const title = options.title ?? "AbsoluteJS Voice Diagnostics";
|
|
7570
|
+
const routes = new Elysia4({
|
|
7571
|
+
name: options.name ?? "absolutejs-voice-diagnostics"
|
|
7572
|
+
});
|
|
7573
|
+
routes.get(path, async () => {
|
|
7574
|
+
const events = await options.store.list();
|
|
7575
|
+
return new Response(renderDiagnosticsIndex({ basePath: path, events, title }), {
|
|
7576
|
+
headers: {
|
|
7577
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
7578
|
+
...options.headers
|
|
7579
|
+
}
|
|
7580
|
+
});
|
|
7581
|
+
});
|
|
7582
|
+
routes.get(`${path}/json`, async ({ query }) => {
|
|
7583
|
+
const events = filterByDiagnosticsQuery(await options.store.list(), query);
|
|
7584
|
+
const redacted = withRedaction(events, query, options.redact);
|
|
7585
|
+
return Response.json({
|
|
7586
|
+
...await exportVoiceTrace({
|
|
7587
|
+
filter: resolveVoiceDiagnosticsTraceFilter(query),
|
|
7588
|
+
redact: false,
|
|
7589
|
+
store: {
|
|
7590
|
+
...options.store,
|
|
7591
|
+
list: async () => redacted
|
|
7592
|
+
}
|
|
7593
|
+
}),
|
|
7594
|
+
filteredCount: events.length,
|
|
7595
|
+
redacted: redacted !== events
|
|
7596
|
+
});
|
|
7597
|
+
});
|
|
7598
|
+
routes.get(`${path}/markdown`, async ({ query }) => {
|
|
7599
|
+
const events = withRedaction(filterByDiagnosticsQuery(await options.store.list(), query), query, options.redact ?? true);
|
|
7600
|
+
const body = buildVoiceDiagnosticsMarkdown(events, {
|
|
7601
|
+
evaluation: options.evaluation,
|
|
7602
|
+
title
|
|
7603
|
+
});
|
|
7604
|
+
return new Response(body, {
|
|
7605
|
+
headers: {
|
|
7606
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
7607
|
+
...options.headers
|
|
7608
|
+
}
|
|
7609
|
+
});
|
|
7610
|
+
});
|
|
7611
|
+
routes.get(`${path}/html`, async ({ query }) => {
|
|
7612
|
+
const events = withRedaction(filterByDiagnosticsQuery(await options.store.list(), query), query, options.redact ?? true);
|
|
7613
|
+
const body = renderVoiceTraceHTML(events, {
|
|
7614
|
+
evaluation: options.evaluation,
|
|
7615
|
+
title
|
|
7616
|
+
});
|
|
7617
|
+
return new Response(body, {
|
|
7618
|
+
headers: {
|
|
7619
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
7620
|
+
...options.headers
|
|
7621
|
+
}
|
|
7622
|
+
});
|
|
7623
|
+
});
|
|
7624
|
+
return routes;
|
|
7625
|
+
};
|
|
7626
|
+
// src/sessionReplay.ts
|
|
7627
|
+
import { Elysia as Elysia5 } from "elysia";
|
|
7628
|
+
var getString4 = (value) => typeof value === "string" ? value : undefined;
|
|
7629
|
+
var escapeHtml7 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
7495
7630
|
var increment2 = (record, key) => {
|
|
7496
7631
|
record[key] = (record[key] ?? 0) + 1;
|
|
7497
7632
|
};
|
|
@@ -7520,14 +7655,14 @@ var buildReplayTurns = (events) => {
|
|
|
7520
7655
|
case "turn.transcript":
|
|
7521
7656
|
turn.transcripts.push({
|
|
7522
7657
|
isFinal: event.payload.isFinal === true,
|
|
7523
|
-
text:
|
|
7658
|
+
text: getString4(event.payload.text)
|
|
7524
7659
|
});
|
|
7525
7660
|
break;
|
|
7526
7661
|
case "turn.committed":
|
|
7527
|
-
turn.committedText =
|
|
7662
|
+
turn.committedText = getString4(event.payload.text);
|
|
7528
7663
|
break;
|
|
7529
7664
|
case "turn.assistant": {
|
|
7530
|
-
const text =
|
|
7665
|
+
const text = getString4(event.payload.text);
|
|
7531
7666
|
if (text) {
|
|
7532
7667
|
turn.assistantReplies.push(text);
|
|
7533
7668
|
}
|
|
@@ -7596,7 +7731,7 @@ var summarizeVoiceSessions = async (options = {}) => {
|
|
|
7596
7731
|
let latestOutcome;
|
|
7597
7732
|
let errorCount = 0;
|
|
7598
7733
|
for (const event of sorted) {
|
|
7599
|
-
const provider =
|
|
7734
|
+
const provider = getString4(event.payload.provider);
|
|
7600
7735
|
if (provider) {
|
|
7601
7736
|
providers.add(provider);
|
|
7602
7737
|
}
|
|
@@ -7604,7 +7739,7 @@ var summarizeVoiceSessions = async (options = {}) => {
|
|
|
7604
7739
|
errorCount += 1;
|
|
7605
7740
|
increment2(providerErrors, provider ?? "unknown");
|
|
7606
7741
|
}
|
|
7607
|
-
const outcome =
|
|
7742
|
+
const outcome = getString4(event.payload.outcome);
|
|
7608
7743
|
if (outcome) {
|
|
7609
7744
|
latestOutcome = outcome;
|
|
7610
7745
|
}
|
|
@@ -7650,10 +7785,10 @@ var summarizeVoiceSessions = async (options = {}) => {
|
|
|
7650
7785
|
var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="voice-sessions-empty">No voice sessions found.</p>' : [
|
|
7651
7786
|
'<div class="voice-sessions-list">',
|
|
7652
7787
|
...sessions.map((session) => [
|
|
7653
|
-
`<article class="voice-session-card ${
|
|
7788
|
+
`<article class="voice-session-card ${escapeHtml7(session.status)}">`,
|
|
7654
7789
|
'<div class="voice-session-card-header">',
|
|
7655
|
-
`<strong>${
|
|
7656
|
-
`<span>${
|
|
7790
|
+
`<strong>${escapeHtml7(session.sessionId)}</strong>`,
|
|
7791
|
+
`<span>${escapeHtml7(session.status)}</span>`,
|
|
7657
7792
|
"</div>",
|
|
7658
7793
|
"<dl>",
|
|
7659
7794
|
`<div><dt>Events</dt><dd>${String(session.eventCount)}</dd></div>`,
|
|
@@ -7661,9 +7796,9 @@ var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="v
|
|
|
7661
7796
|
`<div><dt>Transcripts</dt><dd>${String(session.transcriptCount)}</dd></div>`,
|
|
7662
7797
|
`<div><dt>Errors</dt><dd>${String(session.errorCount)}</dd></div>`,
|
|
7663
7798
|
"</dl>",
|
|
7664
|
-
session.latestOutcome ? `<p>Outcome: ${
|
|
7665
|
-
session.providers.length ? `<p>Providers: ${session.providers.map(
|
|
7666
|
-
session.replayHref ? `<p><a href="${
|
|
7799
|
+
session.latestOutcome ? `<p>Outcome: ${escapeHtml7(session.latestOutcome)}</p>` : "",
|
|
7800
|
+
session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml7).join(", ")}</p>` : "",
|
|
7801
|
+
session.replayHref ? `<p><a href="${escapeHtml7(session.replayHref)}">Open replay</a></p>` : "",
|
|
7667
7802
|
"</article>"
|
|
7668
7803
|
].join("")),
|
|
7669
7804
|
"</div>"
|
|
@@ -7694,7 +7829,7 @@ var createVoiceSessionsHTMLHandler = (options = {}) => async ({ query }) => {
|
|
|
7694
7829
|
var createVoiceSessionListRoutes = (options = {}) => {
|
|
7695
7830
|
const path = options.path ?? "/api/voice-sessions";
|
|
7696
7831
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
7697
|
-
const routes = new
|
|
7832
|
+
const routes = new Elysia5({
|
|
7698
7833
|
name: options.name ?? "absolutejs-voice-session-list"
|
|
7699
7834
|
}).get(path, createVoiceSessionsJSONHandler(options));
|
|
7700
7835
|
if (htmlPath) {
|
|
@@ -7722,7 +7857,7 @@ var createVoiceSessionReplayHTMLHandler = (options) => async ({ params }) => {
|
|
|
7722
7857
|
var createVoiceSessionReplayRoutes = (options) => {
|
|
7723
7858
|
const path = options.path ?? "/api/voice-sessions/:sessionId/replay";
|
|
7724
7859
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
7725
|
-
const routes = new
|
|
7860
|
+
const routes = new Elysia5({
|
|
7726
7861
|
name: options.name ?? "absolutejs-voice-session-replay"
|
|
7727
7862
|
}).get(path, createVoiceSessionReplayJSONHandler(options));
|
|
7728
7863
|
if (htmlPath) {
|
|
@@ -8859,11 +8994,11 @@ var createGeminiVoiceAssistantModel = (options) => {
|
|
|
8859
8994
|
};
|
|
8860
8995
|
};
|
|
8861
8996
|
// src/resilienceRoutes.ts
|
|
8862
|
-
import { Elysia as
|
|
8863
|
-
var
|
|
8864
|
-
var
|
|
8865
|
-
var
|
|
8866
|
-
var
|
|
8997
|
+
import { Elysia as Elysia6 } from "elysia";
|
|
8998
|
+
var escapeHtml8 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
8999
|
+
var getString5 = (value) => typeof value === "string" ? value : undefined;
|
|
9000
|
+
var getNumber3 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
9001
|
+
var getBoolean2 = (value) => value === true;
|
|
8867
9002
|
var isProviderStatus2 = (value) => value === "error" || value === "fallback" || value === "success";
|
|
8868
9003
|
var listVoiceRoutingEvents = (events) => {
|
|
8869
9004
|
const routingEvents = [];
|
|
@@ -8871,26 +9006,26 @@ var listVoiceRoutingEvents = (events) => {
|
|
|
8871
9006
|
if (event.type !== "session.error") {
|
|
8872
9007
|
continue;
|
|
8873
9008
|
}
|
|
8874
|
-
const provider =
|
|
9009
|
+
const provider = getString5(event.payload.provider);
|
|
8875
9010
|
const providerStatus = isProviderStatus2(event.payload.providerStatus) ? event.payload.providerStatus : undefined;
|
|
8876
9011
|
if (!provider || !providerStatus) {
|
|
8877
9012
|
continue;
|
|
8878
9013
|
}
|
|
8879
|
-
const kind =
|
|
9014
|
+
const kind = getString5(event.payload.kind);
|
|
8880
9015
|
routingEvents.push({
|
|
8881
9016
|
at: event.at,
|
|
8882
|
-
attempt:
|
|
8883
|
-
elapsedMs:
|
|
8884
|
-
error:
|
|
8885
|
-
fallbackProvider:
|
|
9017
|
+
attempt: getNumber3(event.payload.attempt),
|
|
9018
|
+
elapsedMs: getNumber3(event.payload.elapsedMs),
|
|
9019
|
+
error: getString5(event.payload.error),
|
|
9020
|
+
fallbackProvider: getString5(event.payload.fallbackProvider),
|
|
8886
9021
|
kind: kind === "stt" || kind === "tts" ? kind : "llm",
|
|
8887
|
-
latencyBudgetMs:
|
|
8888
|
-
operation:
|
|
9022
|
+
latencyBudgetMs: getNumber3(event.payload.latencyBudgetMs),
|
|
9023
|
+
operation: getString5(event.payload.operation),
|
|
8889
9024
|
provider,
|
|
8890
|
-
selectedProvider:
|
|
9025
|
+
selectedProvider: getString5(event.payload.selectedProvider),
|
|
8891
9026
|
sessionId: event.sessionId,
|
|
8892
9027
|
status: providerStatus,
|
|
8893
|
-
timedOut:
|
|
9028
|
+
timedOut: getBoolean2(event.payload.timedOut),
|
|
8894
9029
|
turnId: event.turnId
|
|
8895
9030
|
});
|
|
8896
9031
|
}
|
|
@@ -8923,13 +9058,13 @@ var summarizeRoutingEvents = (events) => {
|
|
|
8923
9058
|
};
|
|
8924
9059
|
var renderProviderCards = (title, providers) => {
|
|
8925
9060
|
if (providers.length === 0) {
|
|
8926
|
-
return `<p class="muted">No ${
|
|
9061
|
+
return `<p class="muted">No ${escapeHtml8(title)} provider health yet.</p>`;
|
|
8927
9062
|
}
|
|
8928
9063
|
return `<div class="provider-grid">${providers.map((provider) => `
|
|
8929
|
-
<article class="card provider ${
|
|
9064
|
+
<article class="card provider ${escapeHtml8(provider.status)}">
|
|
8930
9065
|
<div class="card-header">
|
|
8931
|
-
<strong>${
|
|
8932
|
-
<span>${
|
|
9066
|
+
<strong>${escapeHtml8(provider.provider)}</strong>
|
|
9067
|
+
<span>${escapeHtml8(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
|
|
8933
9068
|
</div>
|
|
8934
9069
|
<dl>
|
|
8935
9070
|
<div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
|
|
@@ -8938,7 +9073,7 @@ var renderProviderCards = (title, providers) => {
|
|
|
8938
9073
|
<div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
|
|
8939
9074
|
<div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
|
|
8940
9075
|
</dl>
|
|
8941
|
-
${provider.lastError ? `<p class="muted">${
|
|
9076
|
+
${provider.lastError ? `<p class="muted">${escapeHtml8(provider.lastError)}</p>` : ""}
|
|
8942
9077
|
</article>
|
|
8943
9078
|
`).join("")}</div>`;
|
|
8944
9079
|
};
|
|
@@ -8947,24 +9082,24 @@ var renderTimeline2 = (events) => {
|
|
|
8947
9082
|
return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
|
|
8948
9083
|
}
|
|
8949
9084
|
return `<div class="timeline">${events.slice(0, 40).map((event) => `
|
|
8950
|
-
<article class="card event ${
|
|
9085
|
+
<article class="card event ${escapeHtml8(event.status ?? "unknown")}">
|
|
8951
9086
|
<div class="card-header">
|
|
8952
|
-
<strong>${
|
|
9087
|
+
<strong>${escapeHtml8(event.kind.toUpperCase())} ${escapeHtml8(event.operation ?? "generate")}</strong>
|
|
8953
9088
|
<span>${new Date(event.at).toLocaleString()}</span>
|
|
8954
9089
|
</div>
|
|
8955
9090
|
<p>
|
|
8956
|
-
<span class="pill">${
|
|
8957
|
-
<span class="pill">provider: ${
|
|
8958
|
-
${event.fallbackProvider ? `<span class="pill">fallback: ${
|
|
9091
|
+
<span class="pill">${escapeHtml8(event.status ?? "unknown")}</span>
|
|
9092
|
+
<span class="pill">provider: ${escapeHtml8(event.provider ?? "unknown")}</span>
|
|
9093
|
+
${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml8(event.fallbackProvider)}</span>` : ""}
|
|
8959
9094
|
${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
|
|
8960
9095
|
</p>
|
|
8961
9096
|
<dl>
|
|
8962
9097
|
<div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
|
|
8963
9098
|
<div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
|
|
8964
9099
|
<div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
|
|
8965
|
-
<div><dt>Session</dt><dd>${
|
|
9100
|
+
<div><dt>Session</dt><dd>${escapeHtml8(event.sessionId)}</dd></div>
|
|
8966
9101
|
</dl>
|
|
8967
|
-
${event.error ? `<p class="muted">${
|
|
9102
|
+
${event.error ? `<p class="muted">${escapeHtml8(event.error)}</p>` : ""}
|
|
8968
9103
|
</article>
|
|
8969
9104
|
`).join("")}</div>`;
|
|
8970
9105
|
};
|
|
@@ -8979,26 +9114,26 @@ var renderSimulationControls = (kind, simulation) => {
|
|
|
8979
9114
|
const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
|
|
8980
9115
|
const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
|
|
8981
9116
|
const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
|
|
8982
|
-
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${
|
|
8983
|
-
<p class="muted">${
|
|
9117
|
+
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml8(pathPrefix)}">
|
|
9118
|
+
<p class="muted">${escapeHtml8(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
|
|
8984
9119
|
<div class="simulate-actions">
|
|
8985
|
-
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${
|
|
8986
|
-
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${
|
|
9120
|
+
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml8(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml8(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
|
|
9121
|
+
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml8(provider.provider)}">Mark ${escapeHtml8(provider.provider)} recovered</button>`).join("")}
|
|
8987
9122
|
</div>
|
|
8988
|
-
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${
|
|
9123
|
+
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml8(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
|
|
8989
9124
|
<pre class="simulate-output" hidden></pre>
|
|
8990
9125
|
</div>`;
|
|
8991
9126
|
};
|
|
8992
9127
|
var renderVoiceResilienceHTML = (input) => {
|
|
8993
9128
|
const summary = summarizeRoutingEvents(input.routingEvents);
|
|
8994
|
-
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${
|
|
8995
|
-
const links = input.links?.length ? input.links.map((link) => `<a href="${
|
|
9129
|
+
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml8(kind)}: ${String(count)}</span>`).join("");
|
|
9130
|
+
const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml8(link.href)}">${escapeHtml8(link.label)}</a>`).join(" \xB7 ") : "";
|
|
8996
9131
|
return `<!doctype html>
|
|
8997
9132
|
<html lang="en">
|
|
8998
9133
|
<head>
|
|
8999
9134
|
<meta charset="utf-8" />
|
|
9000
9135
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
9001
|
-
<title>${
|
|
9136
|
+
<title>${escapeHtml8(input.title ?? "AbsoluteJS Voice Resilience")}</title>
|
|
9002
9137
|
<style>
|
|
9003
9138
|
:root { color-scheme: dark; }
|
|
9004
9139
|
body { background: radial-gradient(circle at top left, #172554, #09090b 36%, #050505); color: #f4f4f5; font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; padding: 24px; }
|
|
@@ -9134,7 +9269,7 @@ var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
|
|
|
9134
9269
|
};
|
|
9135
9270
|
var createVoiceResilienceRoutes = (options) => {
|
|
9136
9271
|
const path = options.path ?? "/resilience";
|
|
9137
|
-
const routes = new
|
|
9272
|
+
const routes = new Elysia6({
|
|
9138
9273
|
name: options.name ?? "absolutejs-voice-resilience"
|
|
9139
9274
|
}).get(path, async () => {
|
|
9140
9275
|
const events = await options.store.list();
|
|
@@ -10015,7 +10150,7 @@ var createVoiceMemoryStore = () => {
|
|
|
10015
10150
|
return { get, getOrCreate, list, remove, set };
|
|
10016
10151
|
};
|
|
10017
10152
|
// src/opsWebhook.ts
|
|
10018
|
-
import { Elysia as
|
|
10153
|
+
import { Elysia as Elysia7 } from "elysia";
|
|
10019
10154
|
var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
10020
10155
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
10021
10156
|
const encoder = new TextEncoder;
|
|
@@ -10145,7 +10280,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
10145
10280
|
};
|
|
10146
10281
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
10147
10282
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
10148
|
-
return new
|
|
10283
|
+
return new Elysia7().post(path, async ({ body, request, set }) => {
|
|
10149
10284
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
10150
10285
|
if (options.signingSecret) {
|
|
10151
10286
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -10178,9 +10313,9 @@ var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
|
10178
10313
|
});
|
|
10179
10314
|
};
|
|
10180
10315
|
// src/handoffHealth.ts
|
|
10181
|
-
import { Elysia as
|
|
10182
|
-
var
|
|
10183
|
-
var
|
|
10316
|
+
import { Elysia as Elysia8 } from "elysia";
|
|
10317
|
+
var escapeHtml9 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10318
|
+
var getString6 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
|
|
10184
10319
|
var isStatus = (value) => value === "delivered" || value === "failed" || value === "skipped";
|
|
10185
10320
|
var increment3 = (record, key) => {
|
|
10186
10321
|
record[key] = (record[key] ?? 0) + 1;
|
|
@@ -10188,11 +10323,11 @@ var increment3 = (record, key) => {
|
|
|
10188
10323
|
var normalizeDelivery = (adapterId, value) => {
|
|
10189
10324
|
const record = value && typeof value === "object" ? value : {};
|
|
10190
10325
|
return {
|
|
10191
|
-
adapterId:
|
|
10192
|
-
adapterKind:
|
|
10326
|
+
adapterId: getString6(record.adapterId) ?? adapterId,
|
|
10327
|
+
adapterKind: getString6(record.adapterKind),
|
|
10193
10328
|
deliveredAt: typeof record.deliveredAt === "number" ? record.deliveredAt : undefined,
|
|
10194
|
-
deliveredTo:
|
|
10195
|
-
error:
|
|
10329
|
+
deliveredTo: getString6(record.deliveredTo),
|
|
10330
|
+
error: getString6(record.error),
|
|
10196
10331
|
status: isStatus(record.status) ? record.status : "failed"
|
|
10197
10332
|
};
|
|
10198
10333
|
};
|
|
@@ -10226,13 +10361,13 @@ var summarizeVoiceHandoffHealth = async (options = {}) => {
|
|
|
10226
10361
|
const status = isStatus(event.payload.status) ? event.payload.status : "failed";
|
|
10227
10362
|
const deliveries = normalizeDeliveries(event.payload);
|
|
10228
10363
|
const item = {
|
|
10229
|
-
action:
|
|
10364
|
+
action: getString6(event.payload.action),
|
|
10230
10365
|
at: event.at,
|
|
10231
10366
|
deliveries,
|
|
10232
|
-
reason:
|
|
10367
|
+
reason: getString6(event.payload.reason),
|
|
10233
10368
|
sessionId: event.sessionId,
|
|
10234
10369
|
status,
|
|
10235
|
-
target:
|
|
10370
|
+
target: getString6(event.payload.target)
|
|
10236
10371
|
};
|
|
10237
10372
|
return {
|
|
10238
10373
|
...item,
|
|
@@ -10297,10 +10432,10 @@ var renderActionSummary = (summary) => {
|
|
|
10297
10432
|
return [
|
|
10298
10433
|
'<section class="voice-handoff-health-columns">',
|
|
10299
10434
|
"<article><h3>Actions</h3>",
|
|
10300
|
-
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${
|
|
10435
|
+
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${escapeHtml9(action)}: ${String(count)}</li>`).join("")}</ul>`,
|
|
10301
10436
|
"</article>",
|
|
10302
10437
|
"<article><h3>Adapters</h3>",
|
|
10303
|
-
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${
|
|
10438
|
+
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${escapeHtml9(adapterId)}: ${String(counts.delivered)} delivered / ${String(counts.failed)} failed / ${String(counts.skipped)} skipped</li>`).join("")}</ul>`,
|
|
10304
10439
|
"</article>",
|
|
10305
10440
|
"</section>"
|
|
10306
10441
|
].join("");
|
|
@@ -10314,22 +10449,22 @@ var renderVoiceHandoffHealthHTML = (summary) => [
|
|
|
10314
10449
|
summary.events.length === 0 ? '<p class="voice-handoff-health-empty">No handoffs found.</p>' : [
|
|
10315
10450
|
'<div class="voice-handoff-health-events">',
|
|
10316
10451
|
...summary.events.map((event) => [
|
|
10317
|
-
`<article class="${
|
|
10452
|
+
`<article class="${escapeHtml9(event.status)}">`,
|
|
10318
10453
|
'<div class="voice-handoff-health-event-header">',
|
|
10319
|
-
`<strong>${
|
|
10320
|
-
`<span>${
|
|
10454
|
+
`<strong>${escapeHtml9(event.action ?? "handoff")}</strong>`,
|
|
10455
|
+
`<span>${escapeHtml9(event.status)}</span>`,
|
|
10321
10456
|
"</div>",
|
|
10322
|
-
`<p><small>${
|
|
10323
|
-
event.target ? `<p>Target: ${
|
|
10324
|
-
event.reason ? `<p>Reason: ${
|
|
10457
|
+
`<p><small>${escapeHtml9(event.sessionId)}</small></p>`,
|
|
10458
|
+
event.target ? `<p>Target: ${escapeHtml9(event.target)}</p>` : "",
|
|
10459
|
+
event.reason ? `<p>Reason: ${escapeHtml9(event.reason)}</p>` : "",
|
|
10325
10460
|
event.deliveries.length ? `<ul>${event.deliveries.map((delivery) => [
|
|
10326
10461
|
"<li>",
|
|
10327
|
-
`${
|
|
10328
|
-
delivery.deliveredTo ? ` to ${
|
|
10329
|
-
delivery.error ? ` (${
|
|
10462
|
+
`${escapeHtml9(delivery.adapterId)}: ${escapeHtml9(delivery.status)}`,
|
|
10463
|
+
delivery.deliveredTo ? ` to ${escapeHtml9(delivery.deliveredTo)}` : "",
|
|
10464
|
+
delivery.error ? ` (${escapeHtml9(delivery.error)})` : "",
|
|
10330
10465
|
"</li>"
|
|
10331
10466
|
].join("")).join("")}</ul>` : "",
|
|
10332
|
-
event.replayHref ? `<p><a href="${
|
|
10467
|
+
event.replayHref ? `<p><a href="${escapeHtml9(event.replayHref)}">Open replay</a></p>` : "",
|
|
10333
10468
|
"</article>"
|
|
10334
10469
|
].join("")),
|
|
10335
10470
|
"</div>"
|
|
@@ -10361,7 +10496,7 @@ var createVoiceHandoffHealthHTMLHandler = (options = {}) => async ({ query }) =>
|
|
|
10361
10496
|
var createVoiceHandoffHealthRoutes = (options = {}) => {
|
|
10362
10497
|
const path = options.path ?? "/api/voice-handoffs";
|
|
10363
10498
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10364
|
-
const routes = new
|
|
10499
|
+
const routes = new Elysia8({
|
|
10365
10500
|
name: options.name ?? "absolutejs-voice-handoff-health"
|
|
10366
10501
|
}).get(path, createVoiceHandoffHealthJSONHandler(options));
|
|
10367
10502
|
if (htmlPath) {
|
|
@@ -12507,6 +12642,7 @@ export {
|
|
|
12507
12642
|
resolveVoiceOpsTaskAssignment,
|
|
12508
12643
|
resolveVoiceOpsTaskAgeBucket,
|
|
12509
12644
|
resolveVoiceOpsPreset,
|
|
12645
|
+
resolveVoiceDiagnosticsTraceFilter,
|
|
12510
12646
|
resolveVoiceAssistantMemoryNamespace,
|
|
12511
12647
|
resolveTurnDetectionConfig,
|
|
12512
12648
|
resolveAudioConditioningConfig,
|
|
@@ -12640,6 +12776,7 @@ export {
|
|
|
12640
12776
|
createVoiceExternalObjectMapId,
|
|
12641
12777
|
createVoiceExternalObjectMap,
|
|
12642
12778
|
createVoiceExperiment,
|
|
12779
|
+
createVoiceDiagnosticsRoutes,
|
|
12643
12780
|
createVoiceCallReviewRecorder,
|
|
12644
12781
|
createVoiceCallReviewFromSession,
|
|
12645
12782
|
createVoiceCallReviewFromLiveTelephonyReport,
|
|
@@ -12675,6 +12812,7 @@ export {
|
|
|
12675
12812
|
buildVoiceTraceReplay,
|
|
12676
12813
|
buildVoiceOpsTaskFromSLABreach,
|
|
12677
12814
|
buildVoiceOpsTaskFromReview,
|
|
12815
|
+
buildVoiceDiagnosticsMarkdown,
|
|
12678
12816
|
assignVoiceOpsTask,
|
|
12679
12817
|
applyVoiceOpsTaskPolicy,
|
|
12680
12818
|
applyVoiceOpsTaskAssignmentRule,
|