@absolutejs/voice 0.0.22-beta.182 → 0.0.22-beta.183
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/index.d.ts +2 -0
- package/dist/index.js +408 -277
- package/dist/operationsRecord.d.ts +102 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export { buildVoiceProductionReadinessGate, buildVoiceProductionReadinessReport,
|
|
|
48
48
|
export { createVoiceReadinessProfile, recommendVoiceReadinessProfile } from './readinessProfiles';
|
|
49
49
|
export { buildVoiceProviderContractMatrix, createVoiceProviderContractMatrixHTMLHandler, createVoiceProviderContractMatrixJSONHandler, createVoiceProviderContractMatrixPreset, createVoiceProviderContractMatrixRoutes, evaluateVoiceProviderStackGaps, renderVoiceProviderContractMatrixHTML, recommendVoiceProviderStack } from './providerStackRecommendations';
|
|
50
50
|
export { buildVoiceOpsConsoleReport, createVoiceOpsConsoleRoutes, renderVoiceOpsConsoleHTML } from './opsConsoleRoutes';
|
|
51
|
+
export { buildVoiceOperationsRecord, createVoiceOperationsRecordRoutes, renderVoiceOperationsRecordHTML } from './operationsRecord';
|
|
51
52
|
export { summarizeVoiceOpsStatus } from './opsStatus';
|
|
52
53
|
export { createVoiceOpsStatusRoutes, renderVoiceOpsStatusHTML } from './opsStatusRoutes';
|
|
53
54
|
export { createVoiceQualityRoutes, evaluateVoiceQuality, renderVoiceQualityHTML } from './qualityRoutes';
|
|
@@ -107,6 +108,7 @@ export type { VoiceOpsStatus, VoiceOpsStatusLink, VoiceOpsStatusOptions, VoiceOp
|
|
|
107
108
|
export type { VoiceProductionReadinessAction, VoiceProductionReadinessAuditOptions, VoiceProductionReadinessAuditRequirement, VoiceProductionReadinessAuditSummary, VoiceProductionReadinessCheck, VoiceProductionReadinessGateIssue, VoiceProductionReadinessGateOptions, VoiceProductionReadinessGateProfile, VoiceProductionReadinessGateProfileSurface, VoiceProductionReadinessGateReport, VoiceProductionReadinessOpsActionHistoryOptions, VoiceProductionReadinessOpsActionHistorySummary, VoiceProductionReadinessProfileExplanation, VoiceProductionReadinessProfileSurface, VoiceProductionReadinessProofSource, VoiceProductionReadinessReport, VoiceProductionReadinessRoutesOptions, VoiceProductionReadinessTraceDeliverySummary, VoiceProductionReadinessAuditDeliveryOptions, VoiceProductionReadinessAuditDeliverySummary, VoiceProductionReadinessTraceDeliveryOptions, VoiceProductionReadinessStatus } from './productionReadiness';
|
|
108
109
|
export type { VoiceReadinessProfileName, VoiceReadinessProfileOptions, VoiceReadinessProfileRecommendation, VoiceReadinessProfileRecommendationScore, VoiceReadinessProfileRoutesOptions } from './readinessProfiles';
|
|
109
110
|
export type { VoiceProviderStackChoice, VoiceProviderStackCapabilities, VoiceProviderStackCapabilityGap, VoiceProviderStackCapabilityGapInput, VoiceProviderStackCapabilityGapReport, VoiceProviderContractCheck, VoiceProviderContractCheckStatus, VoiceProviderContractDefinition, VoiceProviderContractMatrixHandlerOptions, VoiceProviderContractMatrixHTMLHandlerOptions, VoiceProviderContractMatrixInput, VoiceProviderContractMatrixPresetOptions, VoiceProviderContractMatrixReport, VoiceProviderContractMatrixRoutesOptions, VoiceProviderContractMatrixRow, VoiceProviderStackInput, VoiceProviderStackKind, VoiceProviderStackRecommendation } from './providerStackRecommendations';
|
|
111
|
+
export type { VoiceOperationsRecord, VoiceOperationsRecordAgentHandoff, VoiceOperationsRecordAuditSummary, VoiceOperationsRecordOptions, VoiceOperationsRecordOutcome, VoiceOperationsRecordRoutesOptions, VoiceOperationsRecordStatus, VoiceOperationsRecordTool } from './operationsRecord';
|
|
110
112
|
export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
|
|
111
113
|
export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingKindSummary, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind, VoiceRoutingSessionSummary, VoiceRoutingSessionSummaryOptions } from './resilienceRoutes';
|
|
112
114
|
export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
|
package/dist/index.js
CHANGED
|
@@ -22187,6 +22187,391 @@ var createVoiceOpsConsoleRoutes = (options) => {
|
|
|
22187
22187
|
routes.get(`${path}/json`, async () => getReport());
|
|
22188
22188
|
return routes;
|
|
22189
22189
|
};
|
|
22190
|
+
// src/operationsRecord.ts
|
|
22191
|
+
import { Elysia as Elysia37 } from "elysia";
|
|
22192
|
+
|
|
22193
|
+
// src/traceTimeline.ts
|
|
22194
|
+
import { Elysia as Elysia36 } from "elysia";
|
|
22195
|
+
var escapeHtml38 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22196
|
+
var getString12 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
22197
|
+
var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22198
|
+
var firstString3 = (payload, keys) => {
|
|
22199
|
+
for (const key of keys) {
|
|
22200
|
+
const value = getString12(payload[key]);
|
|
22201
|
+
if (value) {
|
|
22202
|
+
return value;
|
|
22203
|
+
}
|
|
22204
|
+
}
|
|
22205
|
+
return;
|
|
22206
|
+
};
|
|
22207
|
+
var firstNumber3 = (payload, keys) => {
|
|
22208
|
+
for (const key of keys) {
|
|
22209
|
+
const value = getNumber7(payload[key]);
|
|
22210
|
+
if (value !== undefined) {
|
|
22211
|
+
return value;
|
|
22212
|
+
}
|
|
22213
|
+
}
|
|
22214
|
+
return;
|
|
22215
|
+
};
|
|
22216
|
+
var eventProvider = (event) => firstString3(event.payload, [
|
|
22217
|
+
"provider",
|
|
22218
|
+
"selectedProvider",
|
|
22219
|
+
"fallbackProvider",
|
|
22220
|
+
"variantId"
|
|
22221
|
+
]);
|
|
22222
|
+
var eventStatus = (event) => firstString3(event.payload, [
|
|
22223
|
+
"providerStatus",
|
|
22224
|
+
"status",
|
|
22225
|
+
"disposition",
|
|
22226
|
+
"type",
|
|
22227
|
+
"reason"
|
|
22228
|
+
]);
|
|
22229
|
+
var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
|
|
22230
|
+
var timelineLabel = (event) => {
|
|
22231
|
+
switch (event.type) {
|
|
22232
|
+
case "call.lifecycle":
|
|
22233
|
+
return `Call ${eventStatus(event) ?? "lifecycle"}`;
|
|
22234
|
+
case "turn.transcript":
|
|
22235
|
+
return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
|
|
22236
|
+
case "turn.committed":
|
|
22237
|
+
return `Committed turn${getString12(event.payload.reason) ? ` (${getString12(event.payload.reason)})` : ""}`;
|
|
22238
|
+
case "turn.assistant":
|
|
22239
|
+
return "Assistant reply";
|
|
22240
|
+
case "agent.model":
|
|
22241
|
+
return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22242
|
+
case "agent.tool":
|
|
22243
|
+
return `Tool ${getString12(event.payload.toolName) ?? "call"}`;
|
|
22244
|
+
case "agent.handoff":
|
|
22245
|
+
return `Agent handoff${getString12(event.payload.targetAgentId) ? ` to ${getString12(event.payload.targetAgentId)}` : ""}`;
|
|
22246
|
+
case "assistant.run":
|
|
22247
|
+
return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22248
|
+
case "assistant.guardrail":
|
|
22249
|
+
return `Guardrail ${eventStatus(event) ?? "check"}`;
|
|
22250
|
+
case "call.handoff":
|
|
22251
|
+
return `Call handoff ${eventStatus(event) ?? ""}`.trim();
|
|
22252
|
+
case "client.live_latency":
|
|
22253
|
+
return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
|
|
22254
|
+
case "session.error":
|
|
22255
|
+
return `Error${getString12(event.payload.error) ? `: ${getString12(event.payload.error)}` : ""}`;
|
|
22256
|
+
case "turn.cost":
|
|
22257
|
+
return "Cost telemetry";
|
|
22258
|
+
case "turn_latency.stage":
|
|
22259
|
+
return `Latency ${getString12(event.payload.stage) ?? "stage"}`;
|
|
22260
|
+
case "workflow.contract":
|
|
22261
|
+
return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
|
|
22262
|
+
default:
|
|
22263
|
+
return event.type;
|
|
22264
|
+
}
|
|
22265
|
+
};
|
|
22266
|
+
var summarizeProviders = (events) => {
|
|
22267
|
+
const entries = new Map;
|
|
22268
|
+
const getEntry = (provider) => {
|
|
22269
|
+
const existing = entries.get(provider);
|
|
22270
|
+
if (existing) {
|
|
22271
|
+
return existing;
|
|
22272
|
+
}
|
|
22273
|
+
const entry = {
|
|
22274
|
+
elapsed: [],
|
|
22275
|
+
errorCount: 0,
|
|
22276
|
+
eventCount: 0,
|
|
22277
|
+
fallbackCount: 0,
|
|
22278
|
+
successCount: 0,
|
|
22279
|
+
timeoutCount: 0
|
|
22280
|
+
};
|
|
22281
|
+
entries.set(provider, entry);
|
|
22282
|
+
return entry;
|
|
22283
|
+
};
|
|
22284
|
+
for (const event of events) {
|
|
22285
|
+
const provider = eventProvider(event);
|
|
22286
|
+
if (!provider) {
|
|
22287
|
+
continue;
|
|
22288
|
+
}
|
|
22289
|
+
const entry = getEntry(provider);
|
|
22290
|
+
const status = eventStatus(event);
|
|
22291
|
+
const elapsedMs = eventElapsedMs(event);
|
|
22292
|
+
entry.eventCount += 1;
|
|
22293
|
+
if (elapsedMs !== undefined) {
|
|
22294
|
+
entry.elapsed.push(elapsedMs);
|
|
22295
|
+
}
|
|
22296
|
+
if (status === "success") {
|
|
22297
|
+
entry.successCount += 1;
|
|
22298
|
+
}
|
|
22299
|
+
if (status === "fallback") {
|
|
22300
|
+
entry.fallbackCount += 1;
|
|
22301
|
+
}
|
|
22302
|
+
if (status === "error") {
|
|
22303
|
+
entry.errorCount += 1;
|
|
22304
|
+
}
|
|
22305
|
+
if (status === "timeout") {
|
|
22306
|
+
entry.timeoutCount += 1;
|
|
22307
|
+
}
|
|
22308
|
+
}
|
|
22309
|
+
return [...entries.entries()].map(([provider, entry]) => ({
|
|
22310
|
+
averageElapsedMs: entry.elapsed.length > 0 ? Math.round(entry.elapsed.reduce((total, value) => total + value, 0) / entry.elapsed.length) : undefined,
|
|
22311
|
+
errorCount: entry.errorCount,
|
|
22312
|
+
eventCount: entry.eventCount,
|
|
22313
|
+
fallbackCount: entry.fallbackCount,
|
|
22314
|
+
maxElapsedMs: entry.elapsed.length > 0 ? Math.max(...entry.elapsed) : undefined,
|
|
22315
|
+
provider,
|
|
22316
|
+
successCount: entry.successCount,
|
|
22317
|
+
timeoutCount: entry.timeoutCount
|
|
22318
|
+
})).sort((left, right) => right.eventCount - left.eventCount);
|
|
22319
|
+
};
|
|
22320
|
+
var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
22321
|
+
const source = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
22322
|
+
const grouped = new Map;
|
|
22323
|
+
for (const event of filterVoiceTraceEvents(source)) {
|
|
22324
|
+
grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
|
|
22325
|
+
}
|
|
22326
|
+
const sessions = [...grouped.entries()].map(([sessionId, sessionEvents]) => {
|
|
22327
|
+
const sorted = filterVoiceTraceEvents(sessionEvents);
|
|
22328
|
+
const summary = summarizeVoiceTrace(sorted);
|
|
22329
|
+
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
22330
|
+
const startedAt = summary.startedAt ?? sorted[0]?.at ?? 0;
|
|
22331
|
+
const status = summary.failed ? "failed" : evaluation.issues.length > 0 ? "warning" : "healthy";
|
|
22332
|
+
return {
|
|
22333
|
+
endedAt: summary.endedAt,
|
|
22334
|
+
evaluation,
|
|
22335
|
+
events: sorted.map((event) => ({
|
|
22336
|
+
at: event.at,
|
|
22337
|
+
elapsedMs: eventElapsedMs(event),
|
|
22338
|
+
id: event.id,
|
|
22339
|
+
label: timelineLabel(event),
|
|
22340
|
+
offsetMs: Math.max(0, event.at - startedAt),
|
|
22341
|
+
provider: eventProvider(event),
|
|
22342
|
+
status: eventStatus(event),
|
|
22343
|
+
turnId: event.turnId,
|
|
22344
|
+
type: event.type
|
|
22345
|
+
})),
|
|
22346
|
+
lastEventAt: sorted.at(-1)?.at,
|
|
22347
|
+
providers: summarizeProviders(sorted),
|
|
22348
|
+
sessionId,
|
|
22349
|
+
startedAt: summary.startedAt,
|
|
22350
|
+
status,
|
|
22351
|
+
summary
|
|
22352
|
+
};
|
|
22353
|
+
}).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0)).slice(0, options.limit ?? 50);
|
|
22354
|
+
return {
|
|
22355
|
+
checkedAt: Date.now(),
|
|
22356
|
+
failed: sessions.filter((session) => session.status === "failed").length,
|
|
22357
|
+
sessions,
|
|
22358
|
+
total: sessions.length,
|
|
22359
|
+
warnings: sessions.filter((session) => session.status === "warning").length
|
|
22360
|
+
};
|
|
22361
|
+
};
|
|
22362
|
+
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
22363
|
+
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${escapeHtml38(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs3(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs3(provider.maxElapsedMs)}</dd></div><div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div><div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div><div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div></dl></article>`).join("")}</div>`;
|
|
22364
|
+
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
22365
|
+
const events = session.events.map((event) => `<tr class="${escapeHtml38(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml38(event.type)}</td><td>${escapeHtml38(event.label)}</td><td>${escapeHtml38(event.provider ?? "")}</td><td>${escapeHtml38(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
22366
|
+
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml38(issue.severity)}">${escapeHtml38(issue.code)}: ${escapeHtml38(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
22367
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml38(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml38(session.sessionId)}</h1><p class="status ${escapeHtml38(session.status)}">${escapeHtml38(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
|
|
22368
|
+
};
|
|
22369
|
+
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml38(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml38(session.sessionId)}</a></td><td>${escapeHtml38(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml38(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
22370
|
+
var timelineCSS = "body{background:#0f1318;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}a{color:#fbbf24}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;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}.metrics,.providers{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article,.providers article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span,dt,.muted{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:12px 0 0}dd{font-weight:800;margin:4px 0 0}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}section{margin-top:28px}@media(max-width:760px){main{padding:20px}table{font-size:.9rem}}";
|
|
22371
|
+
var renderVoiceTraceTimelineHTML = (report, options = {}) => {
|
|
22372
|
+
const snippet = escapeHtml38(`const traceStore = createVoiceTraceSinkStore({
|
|
22373
|
+
store: runtimeStorage.traces,
|
|
22374
|
+
sinks: [
|
|
22375
|
+
createVoiceTraceHTTPSink({
|
|
22376
|
+
endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
|
|
22377
|
+
})
|
|
22378
|
+
]
|
|
22379
|
+
});
|
|
22380
|
+
|
|
22381
|
+
app.use(
|
|
22382
|
+
createVoiceTraceTimelineRoutes({
|
|
22383
|
+
htmlPath: '/traces',
|
|
22384
|
+
path: '/api/voice-traces',
|
|
22385
|
+
redact: {
|
|
22386
|
+
keys: ['authorization', 'apiKey', 'token']
|
|
22387
|
+
},
|
|
22388
|
+
store: traceStore
|
|
22389
|
+
})
|
|
22390
|
+
);
|
|
22391
|
+
|
|
22392
|
+
app.use(
|
|
22393
|
+
createVoiceProductionReadinessRoutes({
|
|
22394
|
+
store: traceStore,
|
|
22395
|
+
traceDeliveries: runtimeStorage.traceDeliveries
|
|
22396
|
+
})
|
|
22397
|
+
);`);
|
|
22398
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml38(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml38(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
|
|
22399
|
+
};
|
|
22400
|
+
var createVoiceTraceTimelineRoutes = (options) => {
|
|
22401
|
+
const path = options.path ?? "/api/voice-traces";
|
|
22402
|
+
const htmlPath = options.htmlPath ?? "/traces";
|
|
22403
|
+
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
22404
|
+
const routes = new Elysia36({
|
|
22405
|
+
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
22406
|
+
});
|
|
22407
|
+
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
22408
|
+
evaluation: options.evaluation,
|
|
22409
|
+
limit: options.limit,
|
|
22410
|
+
redact: options.redact
|
|
22411
|
+
});
|
|
22412
|
+
const findSession = async (sessionId) => {
|
|
22413
|
+
const report = summarizeVoiceTraceTimeline(await options.store.list({ sessionId }), {
|
|
22414
|
+
evaluation: options.evaluation,
|
|
22415
|
+
limit: 1,
|
|
22416
|
+
redact: options.redact
|
|
22417
|
+
});
|
|
22418
|
+
return report.sessions[0];
|
|
22419
|
+
};
|
|
22420
|
+
routes.get(path, async () => Response.json(await buildReport()));
|
|
22421
|
+
routes.get(`${path}/:sessionId`, async ({ params }) => {
|
|
22422
|
+
const session = await findSession(params.sessionId);
|
|
22423
|
+
return session ? Response.json(session) : Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
22424
|
+
});
|
|
22425
|
+
routes.get(htmlPath, async () => {
|
|
22426
|
+
const report = await buildReport();
|
|
22427
|
+
const body = await (options.render ?? ((input) => renderVoiceTraceTimelineHTML(input, { title })))(report);
|
|
22428
|
+
return new Response(body, {
|
|
22429
|
+
headers: {
|
|
22430
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22431
|
+
...options.headers
|
|
22432
|
+
}
|
|
22433
|
+
});
|
|
22434
|
+
});
|
|
22435
|
+
routes.get(`${htmlPath}/:sessionId`, async ({ params }) => {
|
|
22436
|
+
const session = await findSession(params.sessionId);
|
|
22437
|
+
if (!session) {
|
|
22438
|
+
return Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
22439
|
+
}
|
|
22440
|
+
const body = await (options.renderSession ?? ((input) => renderVoiceTraceTimelineSessionHTML(input, { title })))(session);
|
|
22441
|
+
return new Response(body, {
|
|
22442
|
+
headers: {
|
|
22443
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22444
|
+
...options.headers
|
|
22445
|
+
}
|
|
22446
|
+
});
|
|
22447
|
+
});
|
|
22448
|
+
return routes;
|
|
22449
|
+
};
|
|
22450
|
+
|
|
22451
|
+
// src/operationsRecord.ts
|
|
22452
|
+
var getString13 = (value) => typeof value === "string" ? value : undefined;
|
|
22453
|
+
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22454
|
+
var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
|
|
22455
|
+
var toHandoff = (event) => ({
|
|
22456
|
+
at: event.at,
|
|
22457
|
+
fromAgentId: getString13(event.payload.fromAgentId),
|
|
22458
|
+
metadata: event.payload.metadata && typeof event.payload.metadata === "object" && !Array.isArray(event.payload.metadata) ? event.payload.metadata : undefined,
|
|
22459
|
+
reason: getString13(event.payload.reason),
|
|
22460
|
+
status: getString13(event.payload.status),
|
|
22461
|
+
summary: getString13(event.payload.summary),
|
|
22462
|
+
targetAgentId: getString13(event.payload.targetAgentId),
|
|
22463
|
+
turnId: event.turnId
|
|
22464
|
+
});
|
|
22465
|
+
var toTool = (event) => ({
|
|
22466
|
+
at: event.at,
|
|
22467
|
+
elapsedMs: getNumber8(event.payload.elapsedMs),
|
|
22468
|
+
error: getString13(event.payload.error),
|
|
22469
|
+
status: getString13(event.payload.status),
|
|
22470
|
+
toolCallId: getString13(event.payload.toolCallId),
|
|
22471
|
+
toolName: getString13(event.payload.toolName),
|
|
22472
|
+
turnId: event.turnId
|
|
22473
|
+
});
|
|
22474
|
+
var resolveOutcome4 = (events) => {
|
|
22475
|
+
const agentResults = events.filter((event) => event.type === "agent.result");
|
|
22476
|
+
return {
|
|
22477
|
+
assistantReplies: events.filter((event) => event.type === "turn.assistant").length,
|
|
22478
|
+
complete: agentResults.some((event) => event.payload.complete === true),
|
|
22479
|
+
escalated: agentResults.some((event) => event.payload.escalated === true),
|
|
22480
|
+
noAnswer: agentResults.some((event) => event.payload.noAnswer === true),
|
|
22481
|
+
transferred: agentResults.some((event) => event.payload.transferred === true),
|
|
22482
|
+
voicemail: agentResults.some((event) => event.payload.voicemail === true)
|
|
22483
|
+
};
|
|
22484
|
+
};
|
|
22485
|
+
var buildVoiceOperationsRecord = async (options) => {
|
|
22486
|
+
const sourceEvents = options.events ?? await options.store?.list({ sessionId: options.sessionId }) ?? [];
|
|
22487
|
+
const traceEvents = filterVoiceTraceEvents(sourceEvents, {
|
|
22488
|
+
sessionId: options.sessionId
|
|
22489
|
+
});
|
|
22490
|
+
const timelineReport = summarizeVoiceTraceTimeline(traceEvents, {
|
|
22491
|
+
evaluation: options.evaluation,
|
|
22492
|
+
limit: 1,
|
|
22493
|
+
redact: options.redact
|
|
22494
|
+
});
|
|
22495
|
+
const timelineSession = timelineReport.sessions[0];
|
|
22496
|
+
const replay = await summarizeVoiceSessionReplay({
|
|
22497
|
+
evaluation: options.evaluation,
|
|
22498
|
+
events: traceEvents,
|
|
22499
|
+
redact: options.redact,
|
|
22500
|
+
sessionId: options.sessionId
|
|
22501
|
+
});
|
|
22502
|
+
const auditEvents = options.audit ? filterVoiceAuditEvents(await options.audit.list({ sessionId: options.sessionId })) : undefined;
|
|
22503
|
+
return {
|
|
22504
|
+
audit: auditEvents ? {
|
|
22505
|
+
error: countOutcome(auditEvents, "error"),
|
|
22506
|
+
events: auditEvents,
|
|
22507
|
+
skipped: countOutcome(auditEvents, "skipped"),
|
|
22508
|
+
success: countOutcome(auditEvents, "success"),
|
|
22509
|
+
total: auditEvents.length
|
|
22510
|
+
} : undefined,
|
|
22511
|
+
checkedAt: Date.now(),
|
|
22512
|
+
handoffs: traceEvents.filter((event) => event.type === "agent.handoff").map(toHandoff),
|
|
22513
|
+
outcome: resolveOutcome4(traceEvents),
|
|
22514
|
+
providers: timelineSession?.providers ?? [],
|
|
22515
|
+
replay,
|
|
22516
|
+
sessionId: options.sessionId,
|
|
22517
|
+
status: timelineSession?.status ?? "healthy",
|
|
22518
|
+
summary: timelineSession?.summary ?? replay.summary,
|
|
22519
|
+
timeline: timelineSession?.events ?? [],
|
|
22520
|
+
tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
|
|
22521
|
+
traceEvents
|
|
22522
|
+
};
|
|
22523
|
+
};
|
|
22524
|
+
var escapeHtml39 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22525
|
+
var formatMs4 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
22526
|
+
var renderVoiceOperationsRecordHTML = (record, options = {}) => {
|
|
22527
|
+
const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml39(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs4(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
|
|
22528
|
+
const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml39(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml39(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml39(handoff.status ?? "")}</span><p>${escapeHtml39(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
|
|
22529
|
+
const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml39(tool.toolName ?? "tool")}</strong> <span>${escapeHtml39(tool.status ?? "")}</span> ${formatMs4(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml39(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
|
|
22530
|
+
const snippet = escapeHtml39(`app.use(
|
|
22531
|
+
createVoiceOperationsRecordRoutes({
|
|
22532
|
+
audit: auditStore,
|
|
22533
|
+
htmlPath: '/voice-ops/:sessionId',
|
|
22534
|
+
path: '/api/voice-ops/:sessionId',
|
|
22535
|
+
redact: {
|
|
22536
|
+
keys: ['authorization', 'apiKey', 'token']
|
|
22537
|
+
},
|
|
22538
|
+
store: traceStore
|
|
22539
|
+
})
|
|
22540
|
+
);`);
|
|
22541
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml39(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{color:#a9b4bd}.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}</style></head><body><main><p class="eyebrow">Portable production proof</p><h1>${escapeHtml39(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml39(record.status)}">${escapeHtml39(record.status)}</p><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>${formatMs4(record.summary.callDurationMs)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div></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, audit, latency, and replay.</p><pre><code>${snippet}</code></pre></section><section><h2>Providers</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section></main></body></html>`;
|
|
22542
|
+
};
|
|
22543
|
+
var createVoiceOperationsRecordRoutes = (options) => {
|
|
22544
|
+
const path = options.path ?? "/api/voice-operations/:sessionId";
|
|
22545
|
+
const htmlPath = options.htmlPath === undefined ? "/voice-operations/:sessionId" : options.htmlPath;
|
|
22546
|
+
const routes = new Elysia37({
|
|
22547
|
+
name: options.name ?? "absolutejs-voice-operations-record"
|
|
22548
|
+
});
|
|
22549
|
+
const buildRecord = (sessionId) => buildVoiceOperationsRecord({
|
|
22550
|
+
audit: options.audit,
|
|
22551
|
+
evaluation: options.evaluation,
|
|
22552
|
+
events: options.events,
|
|
22553
|
+
redact: options.redact,
|
|
22554
|
+
sessionId,
|
|
22555
|
+
store: options.store
|
|
22556
|
+
});
|
|
22557
|
+
const getSessionId = (params) => params.sessionId ?? "";
|
|
22558
|
+
routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
|
|
22559
|
+
if (htmlPath) {
|
|
22560
|
+
routes.get(htmlPath, async ({ params }) => {
|
|
22561
|
+
const record = await buildRecord(getSessionId(params));
|
|
22562
|
+
const body = await (options.render ?? ((input) => renderVoiceOperationsRecordHTML(input, {
|
|
22563
|
+
title: options.title
|
|
22564
|
+
})))(record);
|
|
22565
|
+
return new Response(body, {
|
|
22566
|
+
headers: {
|
|
22567
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22568
|
+
...options.headers
|
|
22569
|
+
}
|
|
22570
|
+
});
|
|
22571
|
+
});
|
|
22572
|
+
}
|
|
22573
|
+
return routes;
|
|
22574
|
+
};
|
|
22190
22575
|
// src/opsStatus.ts
|
|
22191
22576
|
var DEFAULT_LINKS2 = [
|
|
22192
22577
|
{
|
|
@@ -22359,19 +22744,19 @@ var summarizeVoiceOpsStatus = async (options) => {
|
|
|
22359
22744
|
};
|
|
22360
22745
|
};
|
|
22361
22746
|
// src/opsStatusRoutes.ts
|
|
22362
|
-
import { Elysia as
|
|
22363
|
-
var
|
|
22747
|
+
import { Elysia as Elysia38 } from "elysia";
|
|
22748
|
+
var escapeHtml40 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22364
22749
|
var renderVoiceOpsStatusHTML = (report, options = {}) => {
|
|
22365
22750
|
const title = options.title ?? "AbsoluteJS Voice Ops Status";
|
|
22366
22751
|
const surfaces = Object.entries(report.surfaces).map(([key, surface]) => {
|
|
22367
22752
|
const value = "recovered" in surface ? surface.total === 0 ? "0 events" : `${surface.recovered}/${surface.total}` : ("auditTotal" in surface) ? `${surface.auditTotal + surface.traceTotal} deliveries` : ("total" in surface) ? `${Math.max(surface.total - ("failed" in surface ? surface.failed : ("degraded" in surface) ? surface.degraded : 0), 0)}/${surface.total}` : surface.status;
|
|
22368
|
-
return `<article class="surface ${
|
|
22753
|
+
return `<article class="surface ${escapeHtml40(surface.status)}"><span>${escapeHtml40(surface.status.toUpperCase())}</span><h2>${escapeHtml40(key)}</h2><strong>${escapeHtml40(value)}</strong></article>`;
|
|
22369
22754
|
}).join("");
|
|
22370
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
22755
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(title)}</title><style>body{background:#0d141b;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:980px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.surfaces{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.surface{background:#151d26;border:1px solid #283544;border-radius:20px;padding:18px}.surface span{color:#aab5c0;font-size:.78rem;font-weight:900;letter-spacing:.08em}.surface strong{font-size:1.5rem}.pass{border-color:rgba(34,197,94,.55)}.fail{border-color:rgba(239,68,68,.75)}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Ops status</p><h1>${escapeHtml40(title)}</h1><p>Compact pass/fail status for framework widgets, demos, and small customer-facing health badges.</p><p class="status ${escapeHtml40(report.status)}">Overall: ${escapeHtml40(report.status.toUpperCase())}</p><p>${report.passed}/${report.total} checks passing</p></section><section class="surfaces">${surfaces || '<article class="surface pass"><span>PASS</span><h2>No checks configured</h2><strong>0/0</strong></article>'}</section></main></body></html>`;
|
|
22371
22756
|
};
|
|
22372
22757
|
var createVoiceOpsStatusRoutes = (options) => {
|
|
22373
22758
|
const path = options.path ?? "/api/voice/ops-status";
|
|
22374
|
-
const routes = new
|
|
22759
|
+
const routes = new Elysia38({
|
|
22375
22760
|
name: options.name ?? "absolutejs-voice-ops-status"
|
|
22376
22761
|
});
|
|
22377
22762
|
routes.get(path, async () => summarizeVoiceOpsStatus(options));
|
|
@@ -22804,10 +23189,10 @@ var createVoiceTTSProviderRouter = (options) => {
|
|
|
22804
23189
|
};
|
|
22805
23190
|
};
|
|
22806
23191
|
// src/traceDeliveryRoutes.ts
|
|
22807
|
-
import { Elysia as
|
|
22808
|
-
var
|
|
22809
|
-
var
|
|
22810
|
-
var
|
|
23192
|
+
import { Elysia as Elysia39 } from "elysia";
|
|
23193
|
+
var escapeHtml41 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23194
|
+
var getString14 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
23195
|
+
var getNumber9 = (value) => {
|
|
22811
23196
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
22812
23197
|
return value;
|
|
22813
23198
|
}
|
|
@@ -22818,13 +23203,13 @@ var getNumber7 = (value) => {
|
|
|
22818
23203
|
return;
|
|
22819
23204
|
};
|
|
22820
23205
|
var parseStatus2 = (value) => {
|
|
22821
|
-
const text =
|
|
23206
|
+
const text = getString14(value);
|
|
22822
23207
|
return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
|
|
22823
23208
|
};
|
|
22824
23209
|
var resolveVoiceTraceDeliveryFilter = (query = {}, base = {}) => ({
|
|
22825
23210
|
...base,
|
|
22826
|
-
limit:
|
|
22827
|
-
q:
|
|
23211
|
+
limit: getNumber9(query.limit) ?? base.limit,
|
|
23212
|
+
q: getString14(query.q) ?? base.q,
|
|
22828
23213
|
status: parseStatus2(query.status) ?? base.status
|
|
22829
23214
|
});
|
|
22830
23215
|
var deliverySearchText2 = (delivery) => [
|
|
@@ -22886,14 +23271,14 @@ var renderSinkResults2 = (delivery) => {
|
|
|
22886
23271
|
if (entries.length === 0) {
|
|
22887
23272
|
return "<p>No sink delivery attempts recorded yet.</p>";
|
|
22888
23273
|
}
|
|
22889
|
-
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${
|
|
23274
|
+
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${escapeHtml41(sinkId)}</strong>: ${escapeHtml41(result.status)}${result.deliveredTo ? ` to ${escapeHtml41(result.deliveredTo)}` : ""}${result.error ? ` (${escapeHtml41(result.error)})` : ""}</li>`).join("")}</ul>`;
|
|
22890
23275
|
};
|
|
22891
|
-
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${
|
|
23276
|
+
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${escapeHtml41(event.type)} <small>${escapeHtml41(event.id)}</small>${event.sessionId ? ` session=${escapeHtml41(event.sessionId)}` : ""}</li>`).join("")}</ul>`;
|
|
22892
23277
|
var renderVoiceTraceDeliveryHTML = (report, options = {}) => {
|
|
22893
23278
|
const title = options.title ?? "AbsoluteJS Voice Trace Deliveries";
|
|
22894
|
-
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${
|
|
22895
|
-
const rows = report.deliveries.map((delivery) => `<article class="delivery ${
|
|
22896
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23279
|
+
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${escapeHtml41(options.workerPath ?? "/api/voice-trace-deliveries/drain")}"><button type="submit">Drain trace deliveries</button></form>`;
|
|
23280
|
+
const rows = report.deliveries.map((delivery) => `<article class="delivery ${escapeHtml41(delivery.deliveryStatus)}"><div class="head"><div><span>${escapeHtml41(delivery.deliveryStatus)}</span><h2>${escapeHtml41(delivery.id)}</h2><p>${escapeHtml41(new Date(delivery.createdAt).toLocaleString())}${delivery.deliveredAt ? ` \xB7 delivered ${escapeHtml41(new Date(delivery.deliveredAt).toLocaleString())}` : ""}</p></div><strong>${String(delivery.deliveryAttempts ?? 0)} attempt(s)</strong></div>${delivery.deliveryError ? `<p class="error">${escapeHtml41(delivery.deliveryError)}</p>` : ""}<h3>Sinks</h3>${renderSinkResults2(delivery)}<h3>Events</h3>${renderEventList2(delivery)}</article>`).join("");
|
|
23281
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml41(title)}</title><style>body{background:#0f1318;color:#f4efe1;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.16),rgba(14,165,233,.14));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#86efac;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.2rem,5vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.grid{display:grid;gap:12px;grid-template-columns:repeat(4,1fr);margin-bottom:16px}.grid article,.delivery{background:#151b22;border:1px solid #26313d;border-radius:22px;padding:18px}.grid span,.delivery span{color:#86efac;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.grid strong{display:block;font-size:2rem}.deliveries{display:grid;gap:14px}.delivery.failed{border-color:rgba(239,68,68,.75)}.delivery.pending{border-color:rgba(245,158,11,.7)}.delivery.delivered{border-color:rgba(34,197,94,.55)}.delivery.skipped{border-color:rgba(148,163,184,.6)}.head{align-items:start;display:flex;gap:14px;justify-content:space-between}.delivery h2{font-size:1.05rem;margin:.3rem 0;overflow-wrap:anywhere}.delivery h3{margin:1rem 0 .3rem}.delivery p,.delivery li{color:#c8d0d8}.error{color:#fecaca!important}button{background:#86efac;border:0;border-radius:999px;color:#07111f;cursor:pointer;font-weight:900;margin-top:12px;padding:10px 14px}@media(max-width:760px){main{padding:20px}.grid{grid-template-columns:1fr 1fr}.head{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Trace export health</p><h1>${escapeHtml41(title)}</h1><p>Checked ${escapeHtml41(new Date(report.checkedAt).toLocaleString())}. Showing ${String(report.deliveries.length)} delivery item(s).</p>${drainAction}</section>${renderMetricGrid3(report)}<section class="deliveries">${rows || "<p>No trace deliveries match this filter.</p>"}</section></main></body></html>`;
|
|
22897
23282
|
};
|
|
22898
23283
|
var createVoiceTraceDeliveryJSONHandler = (options) => async ({ query }) => buildVoiceTraceDeliveryReport(options, resolveVoiceTraceDeliveryFilter(query, options.filter));
|
|
22899
23284
|
var createVoiceTraceDeliveryHTMLHandler = (options) => async ({ query }) => {
|
|
@@ -22913,7 +23298,7 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
22913
23298
|
const path = options.path ?? "/api/voice-trace-deliveries";
|
|
22914
23299
|
const htmlPath = options.htmlPath === undefined ? "/traces/deliveries" : options.htmlPath;
|
|
22915
23300
|
const workerPath = options.workerPath === undefined ? `${path}/drain` : options.workerPath;
|
|
22916
|
-
const routes = new
|
|
23301
|
+
const routes = new Elysia39({
|
|
22917
23302
|
name: options.name ?? "absolutejs-voice-trace-deliveries"
|
|
22918
23303
|
}).get(path, createVoiceTraceDeliveryJSONHandler(options));
|
|
22919
23304
|
if (htmlPath !== false) {
|
|
@@ -22930,263 +23315,6 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
22930
23315
|
}
|
|
22931
23316
|
return routes;
|
|
22932
23317
|
};
|
|
22933
|
-
// src/traceTimeline.ts
|
|
22934
|
-
import { Elysia as Elysia38 } from "elysia";
|
|
22935
|
-
var escapeHtml40 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22936
|
-
var getString13 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
22937
|
-
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22938
|
-
var firstString3 = (payload, keys) => {
|
|
22939
|
-
for (const key of keys) {
|
|
22940
|
-
const value = getString13(payload[key]);
|
|
22941
|
-
if (value) {
|
|
22942
|
-
return value;
|
|
22943
|
-
}
|
|
22944
|
-
}
|
|
22945
|
-
return;
|
|
22946
|
-
};
|
|
22947
|
-
var firstNumber3 = (payload, keys) => {
|
|
22948
|
-
for (const key of keys) {
|
|
22949
|
-
const value = getNumber8(payload[key]);
|
|
22950
|
-
if (value !== undefined) {
|
|
22951
|
-
return value;
|
|
22952
|
-
}
|
|
22953
|
-
}
|
|
22954
|
-
return;
|
|
22955
|
-
};
|
|
22956
|
-
var eventProvider = (event) => firstString3(event.payload, [
|
|
22957
|
-
"provider",
|
|
22958
|
-
"selectedProvider",
|
|
22959
|
-
"fallbackProvider",
|
|
22960
|
-
"variantId"
|
|
22961
|
-
]);
|
|
22962
|
-
var eventStatus = (event) => firstString3(event.payload, [
|
|
22963
|
-
"providerStatus",
|
|
22964
|
-
"status",
|
|
22965
|
-
"disposition",
|
|
22966
|
-
"type",
|
|
22967
|
-
"reason"
|
|
22968
|
-
]);
|
|
22969
|
-
var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
|
|
22970
|
-
var timelineLabel = (event) => {
|
|
22971
|
-
switch (event.type) {
|
|
22972
|
-
case "call.lifecycle":
|
|
22973
|
-
return `Call ${eventStatus(event) ?? "lifecycle"}`;
|
|
22974
|
-
case "turn.transcript":
|
|
22975
|
-
return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
|
|
22976
|
-
case "turn.committed":
|
|
22977
|
-
return `Committed turn${getString13(event.payload.reason) ? ` (${getString13(event.payload.reason)})` : ""}`;
|
|
22978
|
-
case "turn.assistant":
|
|
22979
|
-
return "Assistant reply";
|
|
22980
|
-
case "agent.model":
|
|
22981
|
-
return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22982
|
-
case "agent.tool":
|
|
22983
|
-
return `Tool ${getString13(event.payload.toolName) ?? "call"}`;
|
|
22984
|
-
case "agent.handoff":
|
|
22985
|
-
return `Agent handoff${getString13(event.payload.targetAgentId) ? ` to ${getString13(event.payload.targetAgentId)}` : ""}`;
|
|
22986
|
-
case "assistant.run":
|
|
22987
|
-
return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22988
|
-
case "assistant.guardrail":
|
|
22989
|
-
return `Guardrail ${eventStatus(event) ?? "check"}`;
|
|
22990
|
-
case "call.handoff":
|
|
22991
|
-
return `Call handoff ${eventStatus(event) ?? ""}`.trim();
|
|
22992
|
-
case "client.live_latency":
|
|
22993
|
-
return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
|
|
22994
|
-
case "session.error":
|
|
22995
|
-
return `Error${getString13(event.payload.error) ? `: ${getString13(event.payload.error)}` : ""}`;
|
|
22996
|
-
case "turn.cost":
|
|
22997
|
-
return "Cost telemetry";
|
|
22998
|
-
case "turn_latency.stage":
|
|
22999
|
-
return `Latency ${getString13(event.payload.stage) ?? "stage"}`;
|
|
23000
|
-
case "workflow.contract":
|
|
23001
|
-
return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
|
|
23002
|
-
default:
|
|
23003
|
-
return event.type;
|
|
23004
|
-
}
|
|
23005
|
-
};
|
|
23006
|
-
var summarizeProviders = (events) => {
|
|
23007
|
-
const entries = new Map;
|
|
23008
|
-
const getEntry = (provider) => {
|
|
23009
|
-
const existing = entries.get(provider);
|
|
23010
|
-
if (existing) {
|
|
23011
|
-
return existing;
|
|
23012
|
-
}
|
|
23013
|
-
const entry = {
|
|
23014
|
-
elapsed: [],
|
|
23015
|
-
errorCount: 0,
|
|
23016
|
-
eventCount: 0,
|
|
23017
|
-
fallbackCount: 0,
|
|
23018
|
-
successCount: 0,
|
|
23019
|
-
timeoutCount: 0
|
|
23020
|
-
};
|
|
23021
|
-
entries.set(provider, entry);
|
|
23022
|
-
return entry;
|
|
23023
|
-
};
|
|
23024
|
-
for (const event of events) {
|
|
23025
|
-
const provider = eventProvider(event);
|
|
23026
|
-
if (!provider) {
|
|
23027
|
-
continue;
|
|
23028
|
-
}
|
|
23029
|
-
const entry = getEntry(provider);
|
|
23030
|
-
const status = eventStatus(event);
|
|
23031
|
-
const elapsedMs = eventElapsedMs(event);
|
|
23032
|
-
entry.eventCount += 1;
|
|
23033
|
-
if (elapsedMs !== undefined) {
|
|
23034
|
-
entry.elapsed.push(elapsedMs);
|
|
23035
|
-
}
|
|
23036
|
-
if (status === "success") {
|
|
23037
|
-
entry.successCount += 1;
|
|
23038
|
-
}
|
|
23039
|
-
if (status === "fallback") {
|
|
23040
|
-
entry.fallbackCount += 1;
|
|
23041
|
-
}
|
|
23042
|
-
if (status === "error") {
|
|
23043
|
-
entry.errorCount += 1;
|
|
23044
|
-
}
|
|
23045
|
-
if (status === "timeout") {
|
|
23046
|
-
entry.timeoutCount += 1;
|
|
23047
|
-
}
|
|
23048
|
-
}
|
|
23049
|
-
return [...entries.entries()].map(([provider, entry]) => ({
|
|
23050
|
-
averageElapsedMs: entry.elapsed.length > 0 ? Math.round(entry.elapsed.reduce((total, value) => total + value, 0) / entry.elapsed.length) : undefined,
|
|
23051
|
-
errorCount: entry.errorCount,
|
|
23052
|
-
eventCount: entry.eventCount,
|
|
23053
|
-
fallbackCount: entry.fallbackCount,
|
|
23054
|
-
maxElapsedMs: entry.elapsed.length > 0 ? Math.max(...entry.elapsed) : undefined,
|
|
23055
|
-
provider,
|
|
23056
|
-
successCount: entry.successCount,
|
|
23057
|
-
timeoutCount: entry.timeoutCount
|
|
23058
|
-
})).sort((left, right) => right.eventCount - left.eventCount);
|
|
23059
|
-
};
|
|
23060
|
-
var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
23061
|
-
const source = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
23062
|
-
const grouped = new Map;
|
|
23063
|
-
for (const event of filterVoiceTraceEvents(source)) {
|
|
23064
|
-
grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
|
|
23065
|
-
}
|
|
23066
|
-
const sessions = [...grouped.entries()].map(([sessionId, sessionEvents]) => {
|
|
23067
|
-
const sorted = filterVoiceTraceEvents(sessionEvents);
|
|
23068
|
-
const summary = summarizeVoiceTrace(sorted);
|
|
23069
|
-
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
23070
|
-
const startedAt = summary.startedAt ?? sorted[0]?.at ?? 0;
|
|
23071
|
-
const status = summary.failed ? "failed" : evaluation.issues.length > 0 ? "warning" : "healthy";
|
|
23072
|
-
return {
|
|
23073
|
-
endedAt: summary.endedAt,
|
|
23074
|
-
evaluation,
|
|
23075
|
-
events: sorted.map((event) => ({
|
|
23076
|
-
at: event.at,
|
|
23077
|
-
elapsedMs: eventElapsedMs(event),
|
|
23078
|
-
id: event.id,
|
|
23079
|
-
label: timelineLabel(event),
|
|
23080
|
-
offsetMs: Math.max(0, event.at - startedAt),
|
|
23081
|
-
provider: eventProvider(event),
|
|
23082
|
-
status: eventStatus(event),
|
|
23083
|
-
turnId: event.turnId,
|
|
23084
|
-
type: event.type
|
|
23085
|
-
})),
|
|
23086
|
-
lastEventAt: sorted.at(-1)?.at,
|
|
23087
|
-
providers: summarizeProviders(sorted),
|
|
23088
|
-
sessionId,
|
|
23089
|
-
startedAt: summary.startedAt,
|
|
23090
|
-
status,
|
|
23091
|
-
summary
|
|
23092
|
-
};
|
|
23093
|
-
}).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0)).slice(0, options.limit ?? 50);
|
|
23094
|
-
return {
|
|
23095
|
-
checkedAt: Date.now(),
|
|
23096
|
-
failed: sessions.filter((session) => session.status === "failed").length,
|
|
23097
|
-
sessions,
|
|
23098
|
-
total: sessions.length,
|
|
23099
|
-
warnings: sessions.filter((session) => session.status === "warning").length
|
|
23100
|
-
};
|
|
23101
|
-
};
|
|
23102
|
-
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
23103
|
-
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${escapeHtml40(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs3(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs3(provider.maxElapsedMs)}</dd></div><div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div><div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div><div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div></dl></article>`).join("")}</div>`;
|
|
23104
|
-
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
23105
|
-
const events = session.events.map((event) => `<tr class="${escapeHtml40(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml40(event.type)}</td><td>${escapeHtml40(event.label)}</td><td>${escapeHtml40(event.provider ?? "")}</td><td>${escapeHtml40(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
23106
|
-
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml40(issue.severity)}">${escapeHtml40(issue.code)}: ${escapeHtml40(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
23107
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml40(session.sessionId)}</h1><p class="status ${escapeHtml40(session.status)}">${escapeHtml40(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
|
|
23108
|
-
};
|
|
23109
|
-
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml40(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml40(session.sessionId)}</a></td><td>${escapeHtml40(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml40(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
23110
|
-
var timelineCSS = "body{background:#0f1318;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}a{color:#fbbf24}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;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}.metrics,.providers{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article,.providers article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span,dt,.muted{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:12px 0 0}dd{font-weight:800;margin:4px 0 0}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}section{margin-top:28px}@media(max-width:760px){main{padding:20px}table{font-size:.9rem}}";
|
|
23111
|
-
var renderVoiceTraceTimelineHTML = (report, options = {}) => {
|
|
23112
|
-
const snippet = escapeHtml40(`const traceStore = createVoiceTraceSinkStore({
|
|
23113
|
-
store: runtimeStorage.traces,
|
|
23114
|
-
sinks: [
|
|
23115
|
-
createVoiceTraceHTTPSink({
|
|
23116
|
-
endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
|
|
23117
|
-
})
|
|
23118
|
-
]
|
|
23119
|
-
});
|
|
23120
|
-
|
|
23121
|
-
app.use(
|
|
23122
|
-
createVoiceTraceTimelineRoutes({
|
|
23123
|
-
htmlPath: '/traces',
|
|
23124
|
-
path: '/api/voice-traces',
|
|
23125
|
-
redact: {
|
|
23126
|
-
keys: ['authorization', 'apiKey', 'token']
|
|
23127
|
-
},
|
|
23128
|
-
store: traceStore
|
|
23129
|
-
})
|
|
23130
|
-
);
|
|
23131
|
-
|
|
23132
|
-
app.use(
|
|
23133
|
-
createVoiceProductionReadinessRoutes({
|
|
23134
|
-
store: traceStore,
|
|
23135
|
-
traceDeliveries: runtimeStorage.traceDeliveries
|
|
23136
|
-
})
|
|
23137
|
-
);`);
|
|
23138
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml40(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
|
|
23139
|
-
};
|
|
23140
|
-
var createVoiceTraceTimelineRoutes = (options) => {
|
|
23141
|
-
const path = options.path ?? "/api/voice-traces";
|
|
23142
|
-
const htmlPath = options.htmlPath ?? "/traces";
|
|
23143
|
-
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
23144
|
-
const routes = new Elysia38({
|
|
23145
|
-
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
23146
|
-
});
|
|
23147
|
-
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
23148
|
-
evaluation: options.evaluation,
|
|
23149
|
-
limit: options.limit,
|
|
23150
|
-
redact: options.redact
|
|
23151
|
-
});
|
|
23152
|
-
const findSession = async (sessionId) => {
|
|
23153
|
-
const report = summarizeVoiceTraceTimeline(await options.store.list({ sessionId }), {
|
|
23154
|
-
evaluation: options.evaluation,
|
|
23155
|
-
limit: 1,
|
|
23156
|
-
redact: options.redact
|
|
23157
|
-
});
|
|
23158
|
-
return report.sessions[0];
|
|
23159
|
-
};
|
|
23160
|
-
routes.get(path, async () => Response.json(await buildReport()));
|
|
23161
|
-
routes.get(`${path}/:sessionId`, async ({ params }) => {
|
|
23162
|
-
const session = await findSession(params.sessionId);
|
|
23163
|
-
return session ? Response.json(session) : Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
23164
|
-
});
|
|
23165
|
-
routes.get(htmlPath, async () => {
|
|
23166
|
-
const report = await buildReport();
|
|
23167
|
-
const body = await (options.render ?? ((input) => renderVoiceTraceTimelineHTML(input, { title })))(report);
|
|
23168
|
-
return new Response(body, {
|
|
23169
|
-
headers: {
|
|
23170
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
23171
|
-
...options.headers
|
|
23172
|
-
}
|
|
23173
|
-
});
|
|
23174
|
-
});
|
|
23175
|
-
routes.get(`${htmlPath}/:sessionId`, async ({ params }) => {
|
|
23176
|
-
const session = await findSession(params.sessionId);
|
|
23177
|
-
if (!session) {
|
|
23178
|
-
return Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
23179
|
-
}
|
|
23180
|
-
const body = await (options.renderSession ?? ((input) => renderVoiceTraceTimelineSessionHTML(input, { title })))(session);
|
|
23181
|
-
return new Response(body, {
|
|
23182
|
-
headers: {
|
|
23183
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
23184
|
-
...options.headers
|
|
23185
|
-
}
|
|
23186
|
-
});
|
|
23187
|
-
});
|
|
23188
|
-
return routes;
|
|
23189
|
-
};
|
|
23190
23318
|
// src/sqliteStore.ts
|
|
23191
23319
|
import { Database } from "bun:sqlite";
|
|
23192
23320
|
var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
|
|
@@ -23794,7 +23922,7 @@ var createVoiceMemoryStore = () => {
|
|
|
23794
23922
|
return { get, getOrCreate, list, remove, set };
|
|
23795
23923
|
};
|
|
23796
23924
|
// src/opsWebhook.ts
|
|
23797
|
-
import { Elysia as
|
|
23925
|
+
import { Elysia as Elysia40 } from "elysia";
|
|
23798
23926
|
var toHex6 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
23799
23927
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
23800
23928
|
const encoder = new TextEncoder;
|
|
@@ -23924,7 +24052,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
23924
24052
|
};
|
|
23925
24053
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
23926
24054
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
23927
|
-
return new
|
|
24055
|
+
return new Elysia40().post(path, async ({ body, request, set }) => {
|
|
23928
24056
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
23929
24057
|
if (options.signingSecret) {
|
|
23930
24058
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -24866,6 +24994,7 @@ export {
|
|
|
24866
24994
|
renderVoiceOpsStatusHTML,
|
|
24867
24995
|
renderVoiceOpsConsoleHTML,
|
|
24868
24996
|
renderVoiceOpsActionHistoryHTML,
|
|
24997
|
+
renderVoiceOperationsRecordHTML,
|
|
24869
24998
|
renderVoiceLiveLatencyHTML,
|
|
24870
24999
|
renderVoiceHandoffHealthHTML,
|
|
24871
25000
|
renderVoiceEvalHTML,
|
|
@@ -25054,6 +25183,7 @@ export {
|
|
|
25054
25183
|
createVoiceOpsRuntime,
|
|
25055
25184
|
createVoiceOpsConsoleRoutes,
|
|
25056
25185
|
createVoiceOpsActionAuditRoutes,
|
|
25186
|
+
createVoiceOperationsRecordRoutes,
|
|
25057
25187
|
createVoiceMemoryTraceSinkDeliveryStore,
|
|
25058
25188
|
createVoiceMemoryTraceEventStore,
|
|
25059
25189
|
createVoiceMemoryStore,
|
|
@@ -25178,6 +25308,7 @@ export {
|
|
|
25178
25308
|
buildVoiceOpsTaskFromReview,
|
|
25179
25309
|
buildVoiceOpsConsoleReport,
|
|
25180
25310
|
buildVoiceOpsActionHistoryReport,
|
|
25311
|
+
buildVoiceOperationsRecord,
|
|
25181
25312
|
buildVoiceDiagnosticsMarkdown,
|
|
25182
25313
|
buildVoiceDemoReadyReport,
|
|
25183
25314
|
buildVoiceDeliverySinkReport,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { type StoredVoiceAuditEvent, type VoiceAuditEventStore } from './audit';
|
|
3
|
+
import { type VoiceSessionReplay } from './sessionReplay';
|
|
4
|
+
import { type VoiceTraceTimelineEvent, type VoiceTraceTimelineProviderSummary } from './traceTimeline';
|
|
5
|
+
import { type StoredVoiceTraceEvent, type VoiceTraceEvaluationOptions, type VoiceTraceEventStore, type VoiceTraceRedactionConfig, type VoiceTraceSummary } from './trace';
|
|
6
|
+
export type VoiceOperationsRecordStatus = 'failed' | 'healthy' | 'warning';
|
|
7
|
+
export type VoiceOperationsRecordOutcome = {
|
|
8
|
+
assistantReplies: number;
|
|
9
|
+
complete: boolean;
|
|
10
|
+
escalated: boolean;
|
|
11
|
+
noAnswer: boolean;
|
|
12
|
+
transferred: boolean;
|
|
13
|
+
voicemail: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type VoiceOperationsRecordAgentHandoff = {
|
|
16
|
+
at: number;
|
|
17
|
+
fromAgentId?: string;
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
reason?: string;
|
|
20
|
+
status?: string;
|
|
21
|
+
summary?: string;
|
|
22
|
+
targetAgentId?: string;
|
|
23
|
+
turnId?: string;
|
|
24
|
+
};
|
|
25
|
+
export type VoiceOperationsRecordTool = {
|
|
26
|
+
at: number;
|
|
27
|
+
elapsedMs?: number;
|
|
28
|
+
error?: string;
|
|
29
|
+
status?: string;
|
|
30
|
+
toolCallId?: string;
|
|
31
|
+
toolName?: string;
|
|
32
|
+
turnId?: string;
|
|
33
|
+
};
|
|
34
|
+
export type VoiceOperationsRecordAuditSummary = {
|
|
35
|
+
error: number;
|
|
36
|
+
events: StoredVoiceAuditEvent[];
|
|
37
|
+
skipped: number;
|
|
38
|
+
success: number;
|
|
39
|
+
total: number;
|
|
40
|
+
};
|
|
41
|
+
export type VoiceOperationsRecord = {
|
|
42
|
+
audit?: VoiceOperationsRecordAuditSummary;
|
|
43
|
+
checkedAt: number;
|
|
44
|
+
handoffs: VoiceOperationsRecordAgentHandoff[];
|
|
45
|
+
outcome: VoiceOperationsRecordOutcome;
|
|
46
|
+
providers: VoiceTraceTimelineProviderSummary[];
|
|
47
|
+
replay: VoiceSessionReplay;
|
|
48
|
+
sessionId: string;
|
|
49
|
+
status: VoiceOperationsRecordStatus;
|
|
50
|
+
summary: VoiceTraceSummary;
|
|
51
|
+
timeline: VoiceTraceTimelineEvent[];
|
|
52
|
+
tools: VoiceOperationsRecordTool[];
|
|
53
|
+
traceEvents: StoredVoiceTraceEvent[];
|
|
54
|
+
};
|
|
55
|
+
export type VoiceOperationsRecordOptions = {
|
|
56
|
+
audit?: VoiceAuditEventStore;
|
|
57
|
+
evaluation?: VoiceTraceEvaluationOptions;
|
|
58
|
+
events?: StoredVoiceTraceEvent[];
|
|
59
|
+
redact?: VoiceTraceRedactionConfig;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
store?: VoiceTraceEventStore;
|
|
62
|
+
};
|
|
63
|
+
export type VoiceOperationsRecordRoutesOptions = Omit<VoiceOperationsRecordOptions, 'sessionId'> & {
|
|
64
|
+
headers?: HeadersInit;
|
|
65
|
+
htmlPath?: false | string;
|
|
66
|
+
name?: string;
|
|
67
|
+
path?: string;
|
|
68
|
+
render?: (record: VoiceOperationsRecord) => string | Promise<string>;
|
|
69
|
+
title?: string;
|
|
70
|
+
};
|
|
71
|
+
export declare const buildVoiceOperationsRecord: (options: VoiceOperationsRecordOptions) => Promise<VoiceOperationsRecord>;
|
|
72
|
+
export declare const renderVoiceOperationsRecordHTML: (record: VoiceOperationsRecord, options?: {
|
|
73
|
+
title?: string;
|
|
74
|
+
}) => string;
|
|
75
|
+
export declare const createVoiceOperationsRecordRoutes: (options: VoiceOperationsRecordRoutesOptions) => Elysia<"", {
|
|
76
|
+
decorator: {};
|
|
77
|
+
store: {};
|
|
78
|
+
derive: {};
|
|
79
|
+
resolve: {};
|
|
80
|
+
}, {
|
|
81
|
+
typebox: {};
|
|
82
|
+
error: {};
|
|
83
|
+
}, {
|
|
84
|
+
schema: {};
|
|
85
|
+
standaloneSchema: {};
|
|
86
|
+
macro: {};
|
|
87
|
+
macroFn: {};
|
|
88
|
+
parser: {};
|
|
89
|
+
response: {};
|
|
90
|
+
}, {}, {
|
|
91
|
+
derive: {};
|
|
92
|
+
resolve: {};
|
|
93
|
+
schema: {};
|
|
94
|
+
standaloneSchema: {};
|
|
95
|
+
response: {};
|
|
96
|
+
}, {
|
|
97
|
+
derive: {};
|
|
98
|
+
resolve: {};
|
|
99
|
+
schema: {};
|
|
100
|
+
standaloneSchema: {};
|
|
101
|
+
response: {};
|
|
102
|
+
}>;
|