@absolutejs/voice 0.0.22-beta.193 → 0.0.22-beta.195
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/agent.d.ts +15 -0
- package/dist/client/index.js +2 -0
- package/dist/dataControl.d.ts +92 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +591 -280
- package/dist/svelte/index.js +2 -0
- package/dist/trace.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7756,7 +7756,7 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7756
7756
|
targetAgentId: nextAgent.id,
|
|
7757
7757
|
turn: input.turn
|
|
7758
7758
|
});
|
|
7759
|
-
await appendVoiceAgentSquadHandoff({
|
|
7759
|
+
const handoff = await appendVoiceAgentSquadHandoff({
|
|
7760
7760
|
agentId: options.id,
|
|
7761
7761
|
fromAgentId: agent.id,
|
|
7762
7762
|
handoffs,
|
|
@@ -7782,17 +7782,54 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7782
7782
|
sessionId: input.session.id,
|
|
7783
7783
|
toAgentId: nextAgent.id
|
|
7784
7784
|
});
|
|
7785
|
-
|
|
7785
|
+
const summaryMessage = {
|
|
7786
7786
|
content: handoffSummary ?? handoffReason ?? `Handoff to ${nextAgent.id}`,
|
|
7787
7787
|
metadata,
|
|
7788
7788
|
name: nextAgent.id,
|
|
7789
7789
|
role: "system"
|
|
7790
|
+
};
|
|
7791
|
+
messages.push(summaryMessage);
|
|
7792
|
+
const contextPolicy = await options.contextPolicy?.({
|
|
7793
|
+
context: input.context,
|
|
7794
|
+
fromAgentId: agent.id,
|
|
7795
|
+
handoff,
|
|
7796
|
+
messages,
|
|
7797
|
+
session: input.session,
|
|
7798
|
+
summaryMessage,
|
|
7799
|
+
targetAgent: nextAgent,
|
|
7800
|
+
turn: input.turn
|
|
7801
|
+
});
|
|
7802
|
+
if (contextPolicy?.metadata && Object.keys(contextPolicy.metadata).length > 0) {
|
|
7803
|
+
handoff.metadata = {
|
|
7804
|
+
...handoff.metadata,
|
|
7805
|
+
...contextPolicy.metadata
|
|
7806
|
+
};
|
|
7807
|
+
const latest = handoffs.at(-1);
|
|
7808
|
+
if (latest === handoff) {
|
|
7809
|
+
latest.metadata = handoff.metadata;
|
|
7810
|
+
}
|
|
7811
|
+
}
|
|
7812
|
+
await appendVoiceAgentTrace({
|
|
7813
|
+
agentId: options.id,
|
|
7814
|
+
event: {
|
|
7815
|
+
fromAgentId: handoff.fromAgentId,
|
|
7816
|
+
messageCount: messages.length,
|
|
7817
|
+
nextMessageCount: contextPolicy?.messages?.length ?? messages.length,
|
|
7818
|
+
status: contextPolicy ? "applied" : "default",
|
|
7819
|
+
summaryIncluded: (contextPolicy?.messages ?? messages).some((message) => message === summaryMessage),
|
|
7820
|
+
targetAgentId: nextAgent.id
|
|
7821
|
+
},
|
|
7822
|
+
session: input.session,
|
|
7823
|
+
trace: options.trace,
|
|
7824
|
+
turn: input.turn,
|
|
7825
|
+
type: "agent.context"
|
|
7790
7826
|
});
|
|
7791
7827
|
agent = nextAgent;
|
|
7792
7828
|
agentId = nextAgent.id;
|
|
7793
7829
|
result = await agent.run({
|
|
7794
7830
|
...input,
|
|
7795
|
-
messages
|
|
7831
|
+
messages: contextPolicy?.messages ?? messages,
|
|
7832
|
+
system: contextPolicy?.system ?? input.system
|
|
7796
7833
|
});
|
|
7797
7834
|
toolResults.push(...result.toolResults);
|
|
7798
7835
|
}
|
|
@@ -9503,6 +9540,8 @@ var renderTraceEventMarkdown = (event, startedAt) => {
|
|
|
9503
9540
|
return event.payload.text ? `${label} assistant "${formatTraceValue(event.payload.text)}"` : `${label} ${formatTraceValue(event.payload.status)}`;
|
|
9504
9541
|
case "agent.tool":
|
|
9505
9542
|
return `${label} ${formatTraceValue(event.payload.toolName)} ${formatTraceValue(event.payload.status)}`;
|
|
9543
|
+
case "agent.context":
|
|
9544
|
+
return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)} ${formatTraceValue(event.payload.status)}`;
|
|
9506
9545
|
case "agent.handoff":
|
|
9507
9546
|
return `${label} ${formatTraceValue(event.payload.fromAgentId)} -> ${formatTraceValue(event.payload.targetAgentId)}`;
|
|
9508
9547
|
case "session.error":
|
|
@@ -12673,6 +12712,20 @@ var createVoiceDeliveryRuntimeRoutes = (options) => {
|
|
|
12673
12712
|
return routes;
|
|
12674
12713
|
};
|
|
12675
12714
|
// src/dataControl.ts
|
|
12715
|
+
import { Elysia as Elysia15 } from "elysia";
|
|
12716
|
+
var voiceComplianceRedactionDefaults = {
|
|
12717
|
+
keys: [
|
|
12718
|
+
"apiKey",
|
|
12719
|
+
"authorization",
|
|
12720
|
+
"email",
|
|
12721
|
+
"password",
|
|
12722
|
+
"phone",
|
|
12723
|
+
"secret",
|
|
12724
|
+
"token"
|
|
12725
|
+
],
|
|
12726
|
+
redactEmails: true,
|
|
12727
|
+
redactPhoneNumbers: true
|
|
12728
|
+
};
|
|
12676
12729
|
var allRetentionScopes = [
|
|
12677
12730
|
"auditDeliveries",
|
|
12678
12731
|
"campaigns",
|
|
@@ -12886,17 +12939,269 @@ var applyVoiceDataRetentionPolicy = async (options) => {
|
|
|
12886
12939
|
return report;
|
|
12887
12940
|
};
|
|
12888
12941
|
var buildVoiceDataRetentionPlan = (options) => applyVoiceDataRetentionPolicy({ ...options, dryRun: true });
|
|
12942
|
+
var getBooleanQuery = (value) => value === true || value === "true" || value === "1";
|
|
12943
|
+
var getNumberQuery = (value) => {
|
|
12944
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : undefined;
|
|
12945
|
+
return typeof parsed === "number" && Number.isFinite(parsed) ? parsed : undefined;
|
|
12946
|
+
};
|
|
12947
|
+
var parseRetentionScopes = (value) => {
|
|
12948
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
12949
|
+
return;
|
|
12950
|
+
}
|
|
12951
|
+
const allowed = new Set(allRetentionScopes);
|
|
12952
|
+
return value.split(",").map((entry) => entry.trim()).filter((entry) => allowed.has(entry));
|
|
12953
|
+
};
|
|
12954
|
+
var escapeHtml17 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
12955
|
+
var buildStorageSurfaces = (options) => [
|
|
12956
|
+
{
|
|
12957
|
+
configured: Boolean(options.session ?? options.sessions),
|
|
12958
|
+
control: "session",
|
|
12959
|
+
name: "Sessions",
|
|
12960
|
+
selfHosted: true
|
|
12961
|
+
},
|
|
12962
|
+
{
|
|
12963
|
+
configured: Boolean(options.traces),
|
|
12964
|
+
control: "audit",
|
|
12965
|
+
name: "Trace events",
|
|
12966
|
+
selfHosted: true
|
|
12967
|
+
},
|
|
12968
|
+
{
|
|
12969
|
+
configured: Boolean(options.audit),
|
|
12970
|
+
control: "audit",
|
|
12971
|
+
name: "Audit events",
|
|
12972
|
+
selfHosted: true
|
|
12973
|
+
},
|
|
12974
|
+
{
|
|
12975
|
+
configured: Boolean(options.reviews),
|
|
12976
|
+
control: "artifact",
|
|
12977
|
+
name: "Call reviews",
|
|
12978
|
+
selfHosted: true
|
|
12979
|
+
},
|
|
12980
|
+
{
|
|
12981
|
+
configured: Boolean(options.tasks),
|
|
12982
|
+
control: "workflow",
|
|
12983
|
+
name: "Ops tasks",
|
|
12984
|
+
selfHosted: true
|
|
12985
|
+
},
|
|
12986
|
+
{
|
|
12987
|
+
configured: Boolean(options.events),
|
|
12988
|
+
control: "workflow",
|
|
12989
|
+
name: "Integration events",
|
|
12990
|
+
selfHosted: true
|
|
12991
|
+
},
|
|
12992
|
+
{
|
|
12993
|
+
configured: Boolean(options.campaigns),
|
|
12994
|
+
control: "workflow",
|
|
12995
|
+
name: "Campaign records",
|
|
12996
|
+
selfHosted: true
|
|
12997
|
+
},
|
|
12998
|
+
{
|
|
12999
|
+
configured: Boolean(options.auditDeliveries ?? options.traceDeliveries),
|
|
13000
|
+
control: "queue",
|
|
13001
|
+
name: "Audit/trace delivery queues",
|
|
13002
|
+
selfHosted: true
|
|
13003
|
+
},
|
|
13004
|
+
{
|
|
13005
|
+
configured: Boolean(options.incidentBundles),
|
|
13006
|
+
control: "artifact",
|
|
13007
|
+
name: "Incident bundles",
|
|
13008
|
+
selfHosted: true
|
|
13009
|
+
}
|
|
13010
|
+
];
|
|
13011
|
+
var defaultProviderKeys = [
|
|
13012
|
+
{
|
|
13013
|
+
env: "OPENAI_API_KEY",
|
|
13014
|
+
name: "OpenAI",
|
|
13015
|
+
recommendation: "Keep provider keys server-side, scoped per environment, and never expose them to browser voice clients.",
|
|
13016
|
+
required: false
|
|
13017
|
+
},
|
|
13018
|
+
{
|
|
13019
|
+
env: "ANTHROPIC_API_KEY",
|
|
13020
|
+
name: "Anthropic",
|
|
13021
|
+
recommendation: "Use least-privilege project keys and route all requests through your AbsoluteJS server.",
|
|
13022
|
+
required: false
|
|
13023
|
+
},
|
|
13024
|
+
{
|
|
13025
|
+
env: "GEMINI_API_KEY",
|
|
13026
|
+
name: "Gemini",
|
|
13027
|
+
recommendation: "Use server-owned credentials and redact prompts/tool payloads before support export.",
|
|
13028
|
+
required: false
|
|
13029
|
+
},
|
|
13030
|
+
{
|
|
13031
|
+
env: "DEEPGRAM_API_KEY",
|
|
13032
|
+
name: "Deepgram",
|
|
13033
|
+
recommendation: "Keep STT credentials server-side and pair transcript exports with PII redaction.",
|
|
13034
|
+
required: false
|
|
13035
|
+
}
|
|
13036
|
+
];
|
|
13037
|
+
var resolveDataControlRedaction = (redact) => redact === false ? undefined : redact === true || redact === undefined ? voiceComplianceRedactionDefaults : redact;
|
|
13038
|
+
var createVoiceZeroRetentionPolicy = (options) => ({
|
|
13039
|
+
...options,
|
|
13040
|
+
beforeOrAt: options.beforeOrAt ?? Date.now(),
|
|
13041
|
+
dryRun: options.dryRun ?? true,
|
|
13042
|
+
scopes: options.scopes ?? allRetentionScopes
|
|
13043
|
+
});
|
|
13044
|
+
var buildVoiceDataControlReport = async (options) => {
|
|
13045
|
+
const redaction = resolveDataControlRedaction(options.redact);
|
|
13046
|
+
const retentionPlan = await buildVoiceDataRetentionPlan({
|
|
13047
|
+
...options,
|
|
13048
|
+
...options.retention ?? {},
|
|
13049
|
+
auditDeliveries: options.auditDeliveries,
|
|
13050
|
+
traceDeliveries: options.traceDeliveries
|
|
13051
|
+
});
|
|
13052
|
+
const auditExport = options.audit ? await exportVoiceAuditTrail({
|
|
13053
|
+
filter: options.auditFilter,
|
|
13054
|
+
redact: redaction,
|
|
13055
|
+
store: options.audit
|
|
13056
|
+
}) : undefined;
|
|
13057
|
+
return {
|
|
13058
|
+
auditExport,
|
|
13059
|
+
checkedAt: Date.now(),
|
|
13060
|
+
redaction: {
|
|
13061
|
+
defaults: voiceComplianceRedactionDefaults,
|
|
13062
|
+
enabled: Boolean(redaction)
|
|
13063
|
+
},
|
|
13064
|
+
retentionPlan,
|
|
13065
|
+
storage: buildStorageSurfaces(options),
|
|
13066
|
+
providerKeys: options.providerKeys ?? defaultProviderKeys,
|
|
13067
|
+
zeroRetentionAvailable: true
|
|
13068
|
+
};
|
|
13069
|
+
};
|
|
13070
|
+
var renderDataRetentionReportRows = (report) => report.scopes.map((scope) => `<tr><td>${escapeHtml17(scope.scope)}</td><td>${scope.scannedCount}</td><td>${scope.deletedCount}</td><td>${escapeHtml17(scope.skippedReason ?? "")}</td><td><code>${escapeHtml17(scope.deletedIds.join(", "))}</code></td></tr>`).join("");
|
|
13071
|
+
var renderVoiceDataControlHTML = (report, options = {}) => {
|
|
13072
|
+
const title = options.title ?? "Voice Data Control";
|
|
13073
|
+
const storageRows = report.storage.map((surface) => `<tr><td>${escapeHtml17(surface.name)}</td><td>${surface.configured ? "Configured" : "Missing"}</td><td>${escapeHtml17(surface.control)}</td><td>${surface.selfHosted ? "Yes" : "No"}</td></tr>`).join("");
|
|
13074
|
+
const keyRows = report.providerKeys.map((key) => `<tr><td>${escapeHtml17(key.name)}</td><td><code>${escapeHtml17(key.env ?? "n/a")}</code></td><td>${key.required ? "Required" : "Optional"}</td><td>${escapeHtml17(key.recommendation)}</td></tr>`).join("");
|
|
13075
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>${escapeHtml17(title)}</title><style>body{background:#f8f7f2;color:#181713;font-family:ui-sans-serif,system-ui,sans-serif;line-height:1.45;margin:2rem}main{max-width:1120px;margin:auto}.summary{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:1rem 0}.card,table{background:white;border:1px solid #ddd6c8;border-radius:14px}.card{padding:1rem}table{border-collapse:collapse;width:100%;overflow:hidden}td,th{border-bottom:1px solid #eee8dc;padding:.7rem;text-align:left;vertical-align:top}code{white-space:pre-wrap;word-break:break-word}a{color:#9a3412}</style></head><body><main><h1>${escapeHtml17(title)}</h1><p>Self-hosted data-control proof for retention, redaction, audit export, deletion planning, customer-owned storage, and provider key handling.</p><section class="summary"><div class="card"><strong>Redaction</strong><br>${report.redaction.enabled ? "enabled" : "disabled"}</div><div class="card"><strong>Retention dry-run deletes</strong><br>${report.retentionPlan.deletedCount}</div><div class="card"><strong>Audit export events</strong><br>${report.auditExport?.events.length ?? 0}</div><div class="card"><strong>Zero retention recipe</strong><br>${report.zeroRetentionAvailable ? "available" : "missing"}</div></section><h2>Customer-Owned Storage</h2><table><thead><tr><th>Surface</th><th>Status</th><th>Control</th><th>Self-hosted</th></tr></thead><tbody>${storageRows}</tbody></table><h2>Retention Plan</h2><table><thead><tr><th>Scope</th><th>Scanned</th><th>Would delete</th><th>Skipped</th><th>Ids</th></tr></thead><tbody>${renderDataRetentionReportRows(report.retentionPlan)}</tbody></table><h2>Provider Keys</h2><table><thead><tr><th>Provider</th><th>Env</th><th>Required</th><th>Recommendation</th></tr></thead><tbody>${keyRows}</tbody></table><p><a href="./data-control/audit.md">Redacted audit Markdown</a> \xB7 <a href="./data-control/audit.html">Redacted audit HTML</a></p></main></body></html>`;
|
|
13076
|
+
};
|
|
13077
|
+
var renderVoiceDataControlMarkdown = (report, options = {}) => [
|
|
13078
|
+
`# ${options.title ?? "Voice Data Control"}`,
|
|
13079
|
+
"",
|
|
13080
|
+
`Checked: ${new Date(report.checkedAt).toISOString()}`,
|
|
13081
|
+
`Redaction: ${report.redaction.enabled ? "enabled" : "disabled"}`,
|
|
13082
|
+
`Retention dry-run deletes: ${report.retentionPlan.deletedCount}`,
|
|
13083
|
+
`Audit export events: ${report.auditExport?.events.length ?? 0}`,
|
|
13084
|
+
"",
|
|
13085
|
+
"## Customer-Owned Storage",
|
|
13086
|
+
"",
|
|
13087
|
+
...report.storage.map((surface) => `- ${surface.name}: ${surface.configured ? "configured" : "missing"} (${surface.control})`),
|
|
13088
|
+
"",
|
|
13089
|
+
"## Retention Plan",
|
|
13090
|
+
"",
|
|
13091
|
+
...report.retentionPlan.scopes.map((scope) => `- ${scope.scope}: scanned ${scope.scannedCount}, would delete ${scope.deletedCount}${scope.skippedReason ? `, skipped=${scope.skippedReason}` : ""}`),
|
|
13092
|
+
"",
|
|
13093
|
+
"## Provider Keys",
|
|
13094
|
+
"",
|
|
13095
|
+
...report.providerKeys.map((key) => `- ${key.name}${key.env ? ` (${key.env})` : ""}: ${key.recommendation}`)
|
|
13096
|
+
].join(`
|
|
13097
|
+
`);
|
|
13098
|
+
var buildRetentionPolicyFromQuery = (query, options) => ({
|
|
13099
|
+
...options,
|
|
13100
|
+
before: getNumberQuery(query.before),
|
|
13101
|
+
beforeOrAt: getNumberQuery(query.beforeOrAt),
|
|
13102
|
+
dryRun: !getBooleanQuery(query.apply),
|
|
13103
|
+
limit: getNumberQuery(query.limit),
|
|
13104
|
+
scopes: parseRetentionScopes(query.scopes),
|
|
13105
|
+
traceFilter: typeof query.sessionId === "string" && query.sessionId.trim() ? { sessionId: query.sessionId } : undefined
|
|
13106
|
+
});
|
|
13107
|
+
var parseRetentionPolicyBody = (body, options, dryRun) => {
|
|
13108
|
+
const input = body && typeof body === "object" ? body : {};
|
|
13109
|
+
return {
|
|
13110
|
+
...options,
|
|
13111
|
+
before: getNumberQuery(input.before),
|
|
13112
|
+
beforeOrAt: getNumberQuery(input.beforeOrAt),
|
|
13113
|
+
dryRun,
|
|
13114
|
+
limit: getNumberQuery(input.limit),
|
|
13115
|
+
scopes: parseRetentionScopes(input.scopes),
|
|
13116
|
+
traceFilter: typeof input.sessionId === "string" && input.sessionId.trim() ? { sessionId: input.sessionId } : undefined
|
|
13117
|
+
};
|
|
13118
|
+
};
|
|
13119
|
+
var createVoiceDataControlRoutes = (options) => {
|
|
13120
|
+
const path = options.path ?? "/data-control";
|
|
13121
|
+
const title = options.title ?? "AbsoluteJS Voice Data Control";
|
|
13122
|
+
const routes = new Elysia15({
|
|
13123
|
+
name: options.name ?? "absolutejs-voice-data-control"
|
|
13124
|
+
});
|
|
13125
|
+
routes.get(path, async ({ query }) => {
|
|
13126
|
+
const report = await buildVoiceDataControlReport({
|
|
13127
|
+
...options,
|
|
13128
|
+
retention: buildRetentionPolicyFromQuery(query, options)
|
|
13129
|
+
});
|
|
13130
|
+
return new Response(renderVoiceDataControlHTML(report, { title }), {
|
|
13131
|
+
headers: {
|
|
13132
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
13133
|
+
...options.headers
|
|
13134
|
+
}
|
|
13135
|
+
});
|
|
13136
|
+
});
|
|
13137
|
+
routes.get(`${path}.json`, async ({ query }) => buildVoiceDataControlReport({
|
|
13138
|
+
...options,
|
|
13139
|
+
retention: buildRetentionPolicyFromQuery(query, options)
|
|
13140
|
+
}));
|
|
13141
|
+
routes.get(`${path}.md`, async ({ query }) => {
|
|
13142
|
+
const report = await buildVoiceDataControlReport({
|
|
13143
|
+
...options,
|
|
13144
|
+
retention: buildRetentionPolicyFromQuery(query, options)
|
|
13145
|
+
});
|
|
13146
|
+
return new Response(renderVoiceDataControlMarkdown(report, { title }), {
|
|
13147
|
+
headers: {
|
|
13148
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
13149
|
+
...options.headers
|
|
13150
|
+
}
|
|
13151
|
+
});
|
|
13152
|
+
});
|
|
13153
|
+
routes.post(`${path}/retention/plan`, async ({ body }) => buildVoiceDataRetentionPlan(parseRetentionPolicyBody(body, options, true)));
|
|
13154
|
+
routes.post(`${path}/retention/apply`, async ({ body, set }) => {
|
|
13155
|
+
const input = body && typeof body === "object" ? body : {};
|
|
13156
|
+
if (input.confirm !== "apply-retention-policy") {
|
|
13157
|
+
set.status = 400;
|
|
13158
|
+
return {
|
|
13159
|
+
error: 'Refusing to apply retention without confirm="apply-retention-policy". Use /retention/plan first.'
|
|
13160
|
+
};
|
|
13161
|
+
}
|
|
13162
|
+
return applyVoiceDataRetentionPolicy(parseRetentionPolicyBody(body, options, false));
|
|
13163
|
+
});
|
|
13164
|
+
routes.get(`${path}/audit.json`, async () => options.audit ? exportVoiceAuditTrail({
|
|
13165
|
+
redact: resolveDataControlRedaction(options.redact),
|
|
13166
|
+
store: options.audit
|
|
13167
|
+
}) : { events: [], exportedAt: Date.now(), redacted: false });
|
|
13168
|
+
routes.get(`${path}/audit.md`, async () => {
|
|
13169
|
+
const events = options.audit ? await options.audit.list() : [];
|
|
13170
|
+
return new Response(renderVoiceAuditMarkdown(events, {
|
|
13171
|
+
redact: resolveDataControlRedaction(options.redact),
|
|
13172
|
+
title: `${title} Audit Export`
|
|
13173
|
+
}), {
|
|
13174
|
+
headers: {
|
|
13175
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
13176
|
+
...options.headers
|
|
13177
|
+
}
|
|
13178
|
+
});
|
|
13179
|
+
});
|
|
13180
|
+
routes.get(`${path}/audit.html`, async () => {
|
|
13181
|
+
const events = options.audit ? await options.audit.list() : [];
|
|
13182
|
+
return new Response(renderVoiceAuditHTML(events, {
|
|
13183
|
+
redact: resolveDataControlRedaction(options.redact),
|
|
13184
|
+
title: `${title} Audit Export`
|
|
13185
|
+
}), {
|
|
13186
|
+
headers: {
|
|
13187
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
13188
|
+
...options.headers
|
|
13189
|
+
}
|
|
13190
|
+
});
|
|
13191
|
+
});
|
|
13192
|
+
return routes;
|
|
13193
|
+
};
|
|
12889
13194
|
// src/evalRoutes.ts
|
|
12890
|
-
import { Elysia as
|
|
13195
|
+
import { Elysia as Elysia18 } from "elysia";
|
|
12891
13196
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
12892
13197
|
import { dirname as dirname2 } from "path";
|
|
12893
13198
|
|
|
12894
13199
|
// src/qualityRoutes.ts
|
|
12895
|
-
import { Elysia as
|
|
13200
|
+
import { Elysia as Elysia17 } from "elysia";
|
|
12896
13201
|
|
|
12897
13202
|
// src/handoffHealth.ts
|
|
12898
|
-
import { Elysia as
|
|
12899
|
-
var
|
|
13203
|
+
import { Elysia as Elysia16 } from "elysia";
|
|
13204
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
12900
13205
|
var getString6 = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
|
|
12901
13206
|
var isStatus = (value) => value === "delivered" || value === "failed" || value === "skipped";
|
|
12902
13207
|
var increment3 = (record, key) => {
|
|
@@ -13014,10 +13319,10 @@ var renderActionSummary = (summary) => {
|
|
|
13014
13319
|
return [
|
|
13015
13320
|
'<section class="voice-handoff-health-columns">',
|
|
13016
13321
|
"<article><h3>Actions</h3>",
|
|
13017
|
-
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${
|
|
13322
|
+
actions.length === 0 ? "<p>No handoff actions yet.</p>" : `<ul>${actions.map(([action, count]) => `<li>${escapeHtml18(action)}: ${String(count)}</li>`).join("")}</ul>`,
|
|
13018
13323
|
"</article>",
|
|
13019
13324
|
"<article><h3>Adapters</h3>",
|
|
13020
|
-
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${
|
|
13325
|
+
adapters.length === 0 ? "<p>No adapter deliveries yet.</p>" : `<ul>${adapters.map(([adapterId, counts]) => `<li>${escapeHtml18(adapterId)}: ${String(counts.delivered)} delivered / ${String(counts.failed)} failed / ${String(counts.skipped)} skipped</li>`).join("")}</ul>`,
|
|
13021
13326
|
"</article>",
|
|
13022
13327
|
"</section>"
|
|
13023
13328
|
].join("");
|
|
@@ -13031,22 +13336,22 @@ var renderVoiceHandoffHealthHTML = (summary) => [
|
|
|
13031
13336
|
summary.events.length === 0 ? '<p class="voice-handoff-health-empty">No handoffs found.</p>' : [
|
|
13032
13337
|
'<div class="voice-handoff-health-events">',
|
|
13033
13338
|
...summary.events.map((event) => [
|
|
13034
|
-
`<article class="${
|
|
13339
|
+
`<article class="${escapeHtml18(event.status)}">`,
|
|
13035
13340
|
'<div class="voice-handoff-health-event-header">',
|
|
13036
|
-
`<strong>${
|
|
13037
|
-
`<span>${
|
|
13341
|
+
`<strong>${escapeHtml18(event.action ?? "handoff")}</strong>`,
|
|
13342
|
+
`<span>${escapeHtml18(event.status)}</span>`,
|
|
13038
13343
|
"</div>",
|
|
13039
|
-
`<p><small>${
|
|
13040
|
-
event.target ? `<p>Target: ${
|
|
13041
|
-
event.reason ? `<p>Reason: ${
|
|
13344
|
+
`<p><small>${escapeHtml18(event.sessionId)}</small></p>`,
|
|
13345
|
+
event.target ? `<p>Target: ${escapeHtml18(event.target)}</p>` : "",
|
|
13346
|
+
event.reason ? `<p>Reason: ${escapeHtml18(event.reason)}</p>` : "",
|
|
13042
13347
|
event.deliveries.length ? `<ul>${event.deliveries.map((delivery) => [
|
|
13043
13348
|
"<li>",
|
|
13044
|
-
`${
|
|
13045
|
-
delivery.deliveredTo ? ` to ${
|
|
13046
|
-
delivery.error ? ` (${
|
|
13349
|
+
`${escapeHtml18(delivery.adapterId)}: ${escapeHtml18(delivery.status)}`,
|
|
13350
|
+
delivery.deliveredTo ? ` to ${escapeHtml18(delivery.deliveredTo)}` : "",
|
|
13351
|
+
delivery.error ? ` (${escapeHtml18(delivery.error)})` : "",
|
|
13047
13352
|
"</li>"
|
|
13048
13353
|
].join("")).join("")}</ul>` : "",
|
|
13049
|
-
event.replayHref ? `<p><a href="${
|
|
13354
|
+
event.replayHref ? `<p><a href="${escapeHtml18(event.replayHref)}">Open replay</a></p>` : "",
|
|
13050
13355
|
"</article>"
|
|
13051
13356
|
].join("")),
|
|
13052
13357
|
"</div>"
|
|
@@ -13078,7 +13383,7 @@ var createVoiceHandoffHealthHTMLHandler = (options = {}) => async ({ query }) =>
|
|
|
13078
13383
|
var createVoiceHandoffHealthRoutes = (options = {}) => {
|
|
13079
13384
|
const path = options.path ?? "/api/voice-handoffs";
|
|
13080
13385
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
13081
|
-
const routes = new
|
|
13386
|
+
const routes = new Elysia16({
|
|
13082
13387
|
name: options.name ?? "absolutejs-voice-handoff-health"
|
|
13083
13388
|
}).get(path, createVoiceHandoffHealthJSONHandler(options));
|
|
13084
13389
|
if (htmlPath) {
|
|
@@ -13199,17 +13504,17 @@ var evaluateVoiceQuality = async (input) => {
|
|
|
13199
13504
|
thresholds
|
|
13200
13505
|
};
|
|
13201
13506
|
};
|
|
13202
|
-
var
|
|
13507
|
+
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
13203
13508
|
var formatMetricValue = (metric) => metric.unit === "rate" ? `${(metric.actual * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.actual)}ms` : String(metric.actual);
|
|
13204
13509
|
var formatThreshold = (metric) => metric.unit === "rate" ? `${(metric.threshold * 100).toFixed(2)}%` : metric.unit === "ms" ? `${Math.round(metric.threshold)}ms` : String(metric.threshold);
|
|
13205
13510
|
var renderVoiceQualityHTML = (report, options = {}) => {
|
|
13206
|
-
const rows = Object.entries(report.metrics).map(([key, metric]) => `<tr class="${metric.pass ? "pass" : "fail"}"><td>${
|
|
13207
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
13511
|
+
const rows = Object.entries(report.metrics).map(([key, metric]) => `<tr class="${metric.pass ? "pass" : "fail"}"><td>${escapeHtml19(metric.label)}</td><td>${escapeHtml19(formatMetricValue(metric))}</td><td>${escapeHtml19(formatThreshold(metric))}</td><td>${metric.pass ? "pass" : "fail"}</td><td><code>${escapeHtml19(key)}</code></td></tr>`).join("");
|
|
13512
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml19(link.href)}">${escapeHtml19(link.label)}</a>`).join("")}</nav>` : "";
|
|
13208
13513
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>AbsoluteJS Voice Quality</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1100px;margin:auto}nav{display:flex;flex-wrap:wrap;gap:.5rem;margin:0 0 1.25rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;padding:.35rem .75rem;font-weight:800}.status.pass{background:#dcfce7;color:#166534}.status.fail{background:#fee2e2;color:#991b1b}table{border-collapse:collapse;width:100%;background:white;margin-top:1rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}.pass td{border-left:4px solid #16a34a}.fail td{border-left:4px solid #dc2626}code{background:#f3f4f6;padding:.15rem .3rem;border-radius:.3rem}</style></head><body><main>${links}<h1>Voice quality gates</h1><p class="status ${report.status}">${report.status}</p><p>${report.eventCount} event(s) checked.</p><table><thead><tr><th>Metric</th><th>Actual</th><th>Threshold</th><th>Status</th><th>Key</th></tr></thead><tbody>${rows}</tbody></table></main></body></html>`;
|
|
13209
13514
|
};
|
|
13210
13515
|
var createVoiceQualityRoutes = (options) => {
|
|
13211
13516
|
const path = options.path ?? "/quality";
|
|
13212
|
-
const routes = new
|
|
13517
|
+
const routes = new Elysia17({
|
|
13213
13518
|
name: options.name ?? "absolutejs-voice-quality"
|
|
13214
13519
|
});
|
|
13215
13520
|
const getReport = () => evaluateVoiceQuality({
|
|
@@ -13238,7 +13543,7 @@ var createVoiceQualityRoutes = (options) => {
|
|
|
13238
13543
|
};
|
|
13239
13544
|
|
|
13240
13545
|
// src/evalRoutes.ts
|
|
13241
|
-
var
|
|
13546
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
13242
13547
|
var rate2 = (count, total) => count / Math.max(1, total);
|
|
13243
13548
|
var normalizeSearchText = (value) => value.trim().toLowerCase();
|
|
13244
13549
|
var getString8 = (value) => typeof value === "string" ? value : undefined;
|
|
@@ -13546,7 +13851,7 @@ var createVoiceFileScenarioFixtureStore = (filePath) => ({
|
|
|
13546
13851
|
var formatTime = (value) => value === undefined ? "unknown" : new Date(value).toLocaleString();
|
|
13547
13852
|
var formatPercent = (value) => `${(value * 100).toFixed(2)}%`;
|
|
13548
13853
|
var renderVoiceEvalPrimitiveCopy = () => {
|
|
13549
|
-
const snippet =
|
|
13854
|
+
const snippet = escapeHtml20(`app.use(
|
|
13550
13855
|
createVoiceEvalRoutes({
|
|
13551
13856
|
path: '/evals',
|
|
13552
13857
|
store: traceStore,
|
|
@@ -13567,44 +13872,44 @@ var renderVoiceEvalPrimitiveCopy = () => {
|
|
|
13567
13872
|
};
|
|
13568
13873
|
var renderVoiceEvalHTML = (report, options = {}) => {
|
|
13569
13874
|
const title = options.title ?? "AbsoluteJS Voice Evals";
|
|
13570
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
13571
|
-
const trend = report.trend.length ? report.trend.map((bucket) => `<tr><td>${
|
|
13875
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml20(link.href)}">${escapeHtml20(link.label)}</a>`).join("")}</nav>` : "";
|
|
13876
|
+
const trend = report.trend.length ? report.trend.map((bucket) => `<tr><td>${escapeHtml20(bucket.key)}</td><td>${bucket.total}</td><td>${bucket.passed}</td><td>${bucket.failed}</td></tr>`).join("") : '<tr><td colspan="4">No eval buckets yet.</td></tr>';
|
|
13572
13877
|
const sessions = report.sessions.length ? report.sessions.map((session) => {
|
|
13573
13878
|
const failedMetrics = Object.entries(session.quality.metrics).filter(([, metric]) => !metric.pass).map(([, metric]) => metric.label).join(", ");
|
|
13574
|
-
return `<tr class="${session.status}"><td>${
|
|
13879
|
+
return `<tr class="${session.status}"><td>${escapeHtml20(session.sessionId)}</td><td>${escapeHtml20(session.status)}</td><td>${session.eventCount}</td><td>${session.summary.turnCount}</td><td>${session.summary.errorCount}</td><td>${escapeHtml20(formatTime(session.endedAt))}</td><td>${escapeHtml20(failedMetrics || "none")}</td></tr>`;
|
|
13575
13880
|
}).join("") : '<tr><td colspan="7">No sessions found.</td></tr>';
|
|
13576
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
13881
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1180px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.eyebrow{font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.pass{color:#166534}.fail{color:#991b1b}.status.pass{background:#dcfce7}.status.fail{background:#fee2e2}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card,.primitive{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.primitive{background:#fffdf7;border-color:#d6c7a3;margin:1rem 0}.primitive p{line-height:1.55}.primitive pre{background:#181713;border-radius:.85rem;color:#fef3c7;overflow:auto;padding:1rem}.primitive code{color:#fef3c7}.card strong{display:block;font-size:2rem}table{border-collapse:collapse;background:white;width:100%;margin:1rem 0 2rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}tr.fail td{border-left:4px solid #dc2626}tr.pass td{border-left:4px solid #16a34a}</style></head><body><main>${links}<h1>${escapeHtml20(title)}</h1><p class="status ${report.status}">${report.status}</p><div class="grid"><article class="card"><span>Total</span><strong>${report.total}</strong></article><article class="card"><span>Passed</span><strong>${report.passed}</strong></article><article class="card"><span>Failed</span><strong>${report.failed}</strong></article></div>${renderVoiceEvalPrimitiveCopy()}<h2>Trend</h2><table><thead><tr><th>Day</th><th>Total</th><th>Passed</th><th>Failed</th></tr></thead><tbody>${trend}</tbody></table><h2>Session Eval Results</h2><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Last event</th><th>Failed metrics</th></tr></thead><tbody>${sessions}</tbody></table></main></body></html>`;
|
|
13577
13882
|
};
|
|
13578
13883
|
var renderVoiceEvalBaselineHTML = (comparison, options = {}) => {
|
|
13579
13884
|
const title = options.title ?? "AbsoluteJS Voice Eval Baseline";
|
|
13580
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
13581
|
-
const reasons = comparison.reasons.length ? comparison.reasons.map((reason) => `<li>${
|
|
13582
|
-
const newFailures = comparison.newFailedSessionIds.length ? comparison.newFailedSessionIds.map((id) => `<li>${
|
|
13583
|
-
const recovered = comparison.recoveredSessionIds.length ? comparison.recoveredSessionIds.map((id) => `<li>${
|
|
13584
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
13885
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml20(link.href)}">${escapeHtml20(link.label)}</a>`).join("")}</nav>` : "";
|
|
13886
|
+
const reasons = comparison.reasons.length ? comparison.reasons.map((reason) => `<li>${escapeHtml20(reason)}</li>`).join("") : "<li>No baseline regressions detected.</li>";
|
|
13887
|
+
const newFailures = comparison.newFailedSessionIds.length ? comparison.newFailedSessionIds.map((id) => `<li>${escapeHtml20(id)}</li>`).join("") : "<li>none</li>";
|
|
13888
|
+
const recovered = comparison.recoveredSessionIds.length ? comparison.recoveredSessionIds.map((id) => `<li>${escapeHtml20(id)}</li>`).join("") : "<li>none</li>";
|
|
13889
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1000px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.pass{background:#dcfce7;color:#166534}.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:1rem 0}.card{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.card strong{display:block;font-size:2rem}section{background:white;border:1px solid #e7e5e4;border-radius:1rem;margin:1rem 0;padding:1rem}</style></head><body><main>${links}<h1>${escapeHtml20(title)}</h1><p class="status ${comparison.status}">${comparison.status}</p><div class="grid"><article class="card"><span>Baseline pass rate</span><strong>${escapeHtml20(formatPercent(comparison.baseline.passRate))}</strong></article><article class="card"><span>Current pass rate</span><strong>${escapeHtml20(formatPercent(comparison.current.passRate))}</strong></article><article class="card"><span>Failed delta</span><strong>${comparison.deltas.failed}</strong></article><article class="card"><span>Pass rate delta</span><strong>${escapeHtml20(formatPercent(comparison.deltas.passRate))}</strong></article></div><section><h2>Regression Reasons</h2><ul>${reasons}</ul></section><section><h2>New Failed Sessions</h2><ul>${newFailures}</ul></section><section><h2>Recovered Sessions</h2><ul>${recovered}</ul></section></main></body></html>`;
|
|
13585
13890
|
};
|
|
13586
13891
|
var renderVoiceScenarioEvalHTML = (report, options = {}) => {
|
|
13587
13892
|
const title = options.title ?? "AbsoluteJS Voice Scenario Evals";
|
|
13588
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
13893
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml20(link.href)}">${escapeHtml20(link.label)}</a>`).join("")}</nav>` : "";
|
|
13589
13894
|
const scenarios = report.scenarios.length ? report.scenarios.map((scenario) => {
|
|
13590
|
-
const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${
|
|
13591
|
-
const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${
|
|
13592
|
-
return `<section class="scenario ${scenario.status}"><h2>${
|
|
13895
|
+
const scenarioIssues = scenario.issues.length ? `<ul>${scenario.issues.map((issue) => `<li>${escapeHtml20(issue)}</li>`).join("")}</ul>` : "";
|
|
13896
|
+
const sessions = scenario.sessions.length ? scenario.sessions.map((session) => `<tr class="${session.status}"><td>${escapeHtml20(session.sessionId)}</td><td>${escapeHtml20(session.status)}</td><td>${session.eventCount}</td><td>${escapeHtml20(session.issues.join(", ") || "none")}</td></tr>`).join("") : '<tr><td colspan="4">No matching sessions.</td></tr>';
|
|
13897
|
+
return `<section class="scenario ${scenario.status}"><h2>${escapeHtml20(scenario.label)}</h2>${scenario.description ? `<p>${escapeHtml20(scenario.description)}</p>` : ""}<p class="status ${scenario.status}">${scenario.status}</p><p>${scenario.passed} passed, ${scenario.failed} failed, ${scenario.matchedSessions} matched.</p>${scenarioIssues}<table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Issues</th></tr></thead><tbody>${sessions}</tbody></table></section>`;
|
|
13593
13898
|
}).join("") : "<section><p>No scenarios configured.</p></section>";
|
|
13594
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
13899
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1180px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.eyebrow{font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.status.pass{background:#dcfce7;color:#166534}.status.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card,section{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.primitive{background:#fffdf7;border-color:#d6c7a3}.primitive p{line-height:1.55}.primitive pre{background:#181713;border-radius:.85rem;color:#fef3c7;overflow:auto;padding:1rem}.primitive code{color:#fef3c7}.card strong{display:block;font-size:2rem}section{margin:1rem 0}table{border-collapse:collapse;width:100%;margin-top:1rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}tr.fail td{border-left:4px solid #dc2626}tr.pass td{border-left:4px solid #16a34a}</style></head><body><main>${links}<h1>${escapeHtml20(title)}</h1><p class="status ${report.status}">${report.status}</p><div class="grid"><article class="card"><span>Total</span><strong>${report.total}</strong></article><article class="card"><span>Passed</span><strong>${report.passed}</strong></article><article class="card"><span>Failed</span><strong>${report.failed}</strong></article></div>${renderVoiceEvalPrimitiveCopy()}${scenarios}</main></body></html>`;
|
|
13595
13900
|
};
|
|
13596
13901
|
var renderVoiceScenarioFixtureEvalHTML = (report, options = {}) => {
|
|
13597
13902
|
const title = options.title ?? "AbsoluteJS Voice Fixture Evals";
|
|
13598
|
-
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${
|
|
13903
|
+
const links = options.links?.length ? `<nav>${options.links.map((link) => `<a href="${escapeHtml20(link.href)}">${escapeHtml20(link.label)}</a>`).join("")}</nav>` : "";
|
|
13599
13904
|
const fixtures = report.fixtures.length ? report.fixtures.map((fixture) => {
|
|
13600
|
-
const scenarios = fixture.report.scenarios.map((scenario) => `<tr class="${scenario.status}"><td>${
|
|
13601
|
-
return `<section class="${fixture.status}"><h2>${
|
|
13905
|
+
const scenarios = fixture.report.scenarios.map((scenario) => `<tr class="${scenario.status}"><td>${escapeHtml20(scenario.label)}</td><td>${escapeHtml20(scenario.status)}</td><td>${scenario.matchedSessions}</td><td>${escapeHtml20([...scenario.issues, ...scenario.sessions.flatMap((session) => session.issues)].join(", ") || "none")}</td></tr>`).join("");
|
|
13906
|
+
return `<section class="${fixture.status}"><h2>${escapeHtml20(fixture.label)}</h2>${fixture.description ? `<p>${escapeHtml20(fixture.description)}</p>` : ""}<p class="status ${fixture.status}">${fixture.status}</p><table><thead><tr><th>Scenario</th><th>Status</th><th>Sessions</th><th>Issues</th></tr></thead><tbody>${scenarios}</tbody></table></section>`;
|
|
13602
13907
|
}).join("") : "<section><p>No scenario fixtures configured.</p></section>";
|
|
13603
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
13908
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;margin:2rem;background:#f8f7f2;color:#181713}main{max-width:1180px;margin:auto}nav{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}nav a{background:#181713;border-radius:999px;color:white;padding:.35rem .7rem;text-decoration:none}.eyebrow{font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.status{border-radius:999px;display:inline-flex;font-weight:800;padding:.35rem .75rem}.status.pass{background:#dcfce7;color:#166534}.status.fail{background:#fee2e2;color:#991b1b}.grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:1rem 0}.card,section{background:white;border:1px solid #e7e5e4;border-radius:1rem;padding:1rem}.primitive{background:#fffdf7;border-color:#d6c7a3}.primitive p{line-height:1.55}.primitive pre{background:#181713;border-radius:.85rem;color:#fef3c7;overflow:auto;padding:1rem}.primitive code{color:#fef3c7}.card strong{display:block;font-size:2rem}section{margin:1rem 0}table{border-collapse:collapse;width:100%;margin-top:1rem}td,th{border-bottom:1px solid #eee;padding:.75rem;text-align:left}tr.fail td{border-left:4px solid #dc2626}tr.pass td{border-left:4px solid #16a34a}</style></head><body><main>${links}<h1>${escapeHtml20(title)}</h1><p class="status ${report.status}">${report.status}</p><div class="grid"><article class="card"><span>Total</span><strong>${report.total}</strong></article><article class="card"><span>Passed</span><strong>${report.passed}</strong></article><article class="card"><span>Failed</span><strong>${report.failed}</strong></article></div>${renderVoiceEvalPrimitiveCopy()}${fixtures}</main></body></html>`;
|
|
13604
13909
|
};
|
|
13605
13910
|
var createVoiceEvalRoutes = (options) => {
|
|
13606
13911
|
const path = options.path ?? "/evals";
|
|
13607
|
-
const routes = new
|
|
13912
|
+
const routes = new Elysia18({
|
|
13608
13913
|
name: options.name ?? "absolutejs-voice-evals"
|
|
13609
13914
|
});
|
|
13610
13915
|
const getReport = () => runVoiceSessionEvals({
|
|
@@ -13738,11 +14043,11 @@ var createVoiceEvalRoutes = (options) => {
|
|
|
13738
14043
|
return routes;
|
|
13739
14044
|
};
|
|
13740
14045
|
// src/simulationSuite.ts
|
|
13741
|
-
import { Elysia as
|
|
14046
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
13742
14047
|
|
|
13743
14048
|
// src/outcomeContract.ts
|
|
13744
|
-
import { Elysia as
|
|
13745
|
-
var
|
|
14049
|
+
import { Elysia as Elysia19 } from "elysia";
|
|
14050
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
13746
14051
|
var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
|
|
13747
14052
|
var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
|
|
13748
14053
|
var hydrateSessions = async (input) => {
|
|
@@ -13850,9 +14155,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
13850
14155
|
const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
13851
14156
|
<div class="contract-header">
|
|
13852
14157
|
<div>
|
|
13853
|
-
<p class="eyebrow">${
|
|
13854
|
-
<h2>${
|
|
13855
|
-
${contract.description ? `<p>${
|
|
14158
|
+
<p class="eyebrow">${escapeHtml21(contract.contractId)}</p>
|
|
14159
|
+
<h2>${escapeHtml21(contract.label ?? contract.contractId)}</h2>
|
|
14160
|
+
${contract.description ? `<p>${escapeHtml21(contract.description)}</p>` : ""}
|
|
13856
14161
|
</div>
|
|
13857
14162
|
<strong>${contract.pass ? "pass" : "fail"}</strong>
|
|
13858
14163
|
</div>
|
|
@@ -13863,9 +14168,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
13863
14168
|
<span>handoffs ${String(contract.matched.handoffs)}</span>
|
|
13864
14169
|
<span>events ${String(contract.matched.integrationEvents)}</span>
|
|
13865
14170
|
</div>
|
|
13866
|
-
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${
|
|
14171
|
+
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml21(issue.message)}</li>`).join("")}</ul>` : ""}
|
|
13867
14172
|
</section>`).join("");
|
|
13868
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
14173
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml21(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
|
|
13869
14174
|
};
|
|
13870
14175
|
var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
|
|
13871
14176
|
var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
@@ -13881,7 +14186,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
|
13881
14186
|
var createVoiceOutcomeContractRoutes = (options) => {
|
|
13882
14187
|
const path = options.path ?? "/api/outcome-contracts";
|
|
13883
14188
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
13884
|
-
const routes = new
|
|
14189
|
+
const routes = new Elysia19({
|
|
13885
14190
|
name: options.name ?? "absolutejs-voice-outcome-contracts"
|
|
13886
14191
|
}).get(path, createVoiceOutcomeContractJSONHandler(options));
|
|
13887
14192
|
if (htmlPath) {
|
|
@@ -13891,7 +14196,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
|
|
|
13891
14196
|
};
|
|
13892
14197
|
|
|
13893
14198
|
// src/toolContract.ts
|
|
13894
|
-
import { Elysia as
|
|
14199
|
+
import { Elysia as Elysia20 } from "elysia";
|
|
13895
14200
|
|
|
13896
14201
|
// src/toolRuntime.ts
|
|
13897
14202
|
var toErrorMessage4 = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -14100,7 +14405,7 @@ var createDefaultTurn = (caseId) => ({
|
|
|
14100
14405
|
});
|
|
14101
14406
|
var defaultApi = {};
|
|
14102
14407
|
var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
14103
|
-
var
|
|
14408
|
+
var escapeHtml22 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
14104
14409
|
var evaluateExpectation = (input) => {
|
|
14105
14410
|
const issues = [];
|
|
14106
14411
|
const expect = input.expect;
|
|
@@ -14264,7 +14569,7 @@ var runVoiceToolContractSuite = async (options) => {
|
|
|
14264
14569
|
};
|
|
14265
14570
|
var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
14266
14571
|
const title = options.title ?? "Voice Tool Contracts";
|
|
14267
|
-
const snippet =
|
|
14572
|
+
const snippet = escapeHtml22(`app.use(
|
|
14268
14573
|
createVoiceToolContractRoutes({
|
|
14269
14574
|
htmlPath: '/tool-contracts',
|
|
14270
14575
|
path: '/api/tool-contracts',
|
|
@@ -14290,19 +14595,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
14290
14595
|
);`);
|
|
14291
14596
|
const contracts = report.contracts.map((contract) => {
|
|
14292
14597
|
const cases = contract.cases.map((testCase) => `<tr>
|
|
14293
|
-
<td>${
|
|
14598
|
+
<td>${escapeHtml22(testCase.label ?? testCase.caseId)}</td>
|
|
14294
14599
|
<td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
|
|
14295
|
-
<td>${
|
|
14600
|
+
<td>${escapeHtml22(testCase.status)}</td>
|
|
14296
14601
|
<td>${String(testCase.attempts)}</td>
|
|
14297
14602
|
<td>${String(testCase.elapsedMs)}ms</td>
|
|
14298
14603
|
<td>${testCase.timedOut ? "yes" : "no"}</td>
|
|
14299
|
-
<td>${
|
|
14604
|
+
<td>${escapeHtml22(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
|
|
14300
14605
|
</tr>`).join("");
|
|
14301
14606
|
return `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
14302
14607
|
<div class="contract-header">
|
|
14303
14608
|
<div>
|
|
14304
|
-
<p class="eyebrow">${
|
|
14305
|
-
<h2>${
|
|
14609
|
+
<p class="eyebrow">${escapeHtml22(contract.toolName)}</p>
|
|
14610
|
+
<h2>${escapeHtml22(contract.label ?? contract.contractId)}</h2>
|
|
14306
14611
|
</div>
|
|
14307
14612
|
<strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
|
|
14308
14613
|
</div>
|
|
@@ -14312,7 +14617,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
14312
14617
|
</table>
|
|
14313
14618
|
</section>`;
|
|
14314
14619
|
}).join("");
|
|
14315
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
14620
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml22(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.primitive,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(245,158,11,.12))}.primitive{background:#151b20;border-color:#5a4421}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}h2{margin:.2rem 0 1rem}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}.primitive p{color:#d8dee6;line-height:1.55}.primitive pre{background:#0f1217;border:1px solid #2a323a;border-radius:16px;color:#fef3c7;overflow:auto;padding:14px}.primitive code{color:#fef3c7}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left;vertical-align:top}th{color:#a8b0b8;font-size:.82rem}@media(max-width:800px){main{padding:18px}table{display:block;overflow:auto}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Tool Reliability</p><h1>${escapeHtml22(title)}</h1><div class="summary"><span class="pill ${report.status === "pass" ? "pass" : "fail"}">${escapeHtml22(report.status)}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceToolContractRoutes(...)</code> certifies tool behavior</h2><p>Define deterministic tool cases for retries, idempotency, timeouts, result shape, and error handling so assistant tools fail in pre-production instead of live calls.</p><pre><code>${snippet}</code></pre></section>${contracts || '<section class="contract"><p>No tool contracts configured.</p></section>'}</main></body></html>`;
|
|
14316
14621
|
};
|
|
14317
14622
|
var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
|
|
14318
14623
|
var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
@@ -14329,7 +14634,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
|
14329
14634
|
var createVoiceToolContractRoutes = (options) => {
|
|
14330
14635
|
const path = options.path ?? "/api/tool-contracts";
|
|
14331
14636
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
14332
|
-
const routes = new
|
|
14637
|
+
const routes = new Elysia20({
|
|
14333
14638
|
name: options.name ?? "absolutejs-voice-tool-contracts"
|
|
14334
14639
|
}).get(path, createVoiceToolContractJSONHandler(options));
|
|
14335
14640
|
if (htmlPath) {
|
|
@@ -14339,7 +14644,7 @@ var createVoiceToolContractRoutes = (options) => {
|
|
|
14339
14644
|
};
|
|
14340
14645
|
|
|
14341
14646
|
// src/simulationSuite.ts
|
|
14342
|
-
var
|
|
14647
|
+
var escapeHtml23 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
14343
14648
|
var summarizeSection = (report) => ({
|
|
14344
14649
|
failed: report.failed,
|
|
14345
14650
|
passed: report.passed,
|
|
@@ -14475,15 +14780,15 @@ var renderSection = (label, summary) => {
|
|
|
14475
14780
|
if (!summary) {
|
|
14476
14781
|
return "";
|
|
14477
14782
|
}
|
|
14478
|
-
return `<article class="${
|
|
14783
|
+
return `<article class="${escapeHtml23(summary.status)}"><span>${escapeHtml23(label)}</span><strong>${escapeHtml23(summary.status)}</strong><p>${summary.passed}/${summary.total} passed, ${summary.failed} failed.</p></article>`;
|
|
14479
14784
|
};
|
|
14480
14785
|
var renderAction = (action) => {
|
|
14481
|
-
const content = `<strong>${
|
|
14482
|
-
return action.href ? `<a class="action" href="${
|
|
14786
|
+
const content = `<strong>${escapeHtml23(action.label)}</strong><p>${escapeHtml23(action.description)}</p><span>${escapeHtml23(action.section)} / ${escapeHtml23(action.severity)}</span>`;
|
|
14787
|
+
return action.href ? `<a class="action" href="${escapeHtml23(action.href)}">${content}</a>` : `<article class="action">${content}</article>`;
|
|
14483
14788
|
};
|
|
14484
14789
|
var renderVoiceSimulationSuiteHTML = (report, options = {}) => {
|
|
14485
14790
|
const title = options.title ?? "Voice Simulation Suite";
|
|
14486
|
-
const snippet =
|
|
14791
|
+
const snippet = escapeHtml23(`app.use(
|
|
14487
14792
|
createVoiceSimulationSuiteRoutes({
|
|
14488
14793
|
htmlPath: '/voice/simulations',
|
|
14489
14794
|
path: '/api/voice/simulations',
|
|
@@ -14516,12 +14821,12 @@ app.use(
|
|
|
14516
14821
|
store: traceStore
|
|
14517
14822
|
})
|
|
14518
14823
|
);`);
|
|
14519
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
14824
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml23(title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero,.primitive{background:linear-gradient(135deg,rgba(34,197,94,.18),rgba(59,130,246,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.primitive{background:#151d27;border-color:#355078}.eyebrow{color:#93c5fd;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.grid,.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin:18px 0}.grid article,.action{background:#151d27;border:1px solid #283544;border-radius:18px;color:inherit;padding:16px;text-decoration:none}.grid span,.action span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0;text-transform:uppercase}.action strong{display:block;color:#f8f3e7;margin-bottom:.35rem}.action p,.primitive p{color:#d8dee6;line-height:1.55;margin:.3rem 0 .6rem}pre{background:#151d27;border:1px solid #283544;border-radius:18px;overflow:auto;padding:16px}.primitive pre{background:#0b1118;color:#dbeafe}.primitive code{color:#bfdbfe}</style></head><body><main><section class="hero"><p class="eyebrow">Pre-production proof</p><h1>${escapeHtml23(title)}</h1><p>One report for session quality, scenario evals, fixture simulations, tool contracts, and outcome contracts.</p><p class="badge ${escapeHtml23(report.status)}">Status: ${escapeHtml23(report.status)}</p><section class="grid">${renderSection("Sessions", report.summary.sessions)}${renderSection("Scenarios", report.summary.scenarios)}${renderSection("Fixtures", report.summary.fixtures)}${renderSection("Tools", report.summary.tools)}${renderSection("Outcomes", report.summary.outcomes)}</section></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceSimulationSuiteRoutes(...)</code> builds this pre-production proof surface</h2><p>Run session quality checks, scenario evals, fixture-backed simulations, tool contracts, and outcome contracts from one route group before live traffic sees a regression.</p><pre><code>${snippet}</code></pre></section><h2>Actions</h2><section class="actions">${report.actions.length > 0 ? report.actions.map(renderAction).join("") : '<article class="action"><strong>No action required</strong><p>All enabled simulation sections are passing.</p></article>'}</section><pre>${escapeHtml23(JSON.stringify({ summary: report.summary, actions: report.actions }, null, 2))}</pre></main></body></html>`;
|
|
14520
14825
|
};
|
|
14521
14826
|
var createVoiceSimulationSuiteRoutes = (options) => {
|
|
14522
14827
|
const path = options.path ?? "/api/voice/simulations";
|
|
14523
14828
|
const htmlPath = options.htmlPath === undefined ? "/voice/simulations" : options.htmlPath;
|
|
14524
|
-
const app = new
|
|
14829
|
+
const app = new Elysia21({
|
|
14525
14830
|
name: options.name ?? "absolutejs-voice-simulation-suite"
|
|
14526
14831
|
}).get(path, () => runVoiceSimulationSuite(options));
|
|
14527
14832
|
if (htmlPath) {
|
|
@@ -14833,9 +15138,9 @@ var createVoiceWorkflowContractHandler = (input) => {
|
|
|
14833
15138
|
};
|
|
14834
15139
|
};
|
|
14835
15140
|
// src/sessionReplay.ts
|
|
14836
|
-
import { Elysia as
|
|
15141
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
14837
15142
|
var getString9 = (value) => typeof value === "string" ? value : undefined;
|
|
14838
|
-
var
|
|
15143
|
+
var escapeHtml24 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
14839
15144
|
var increment4 = (record, key) => {
|
|
14840
15145
|
record[key] = (record[key] ?? 0) + 1;
|
|
14841
15146
|
};
|
|
@@ -15015,10 +15320,10 @@ var summarizeVoiceSessions = async (options = {}) => {
|
|
|
15015
15320
|
var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="voice-sessions-empty">No voice sessions found.</p>' : [
|
|
15016
15321
|
'<div class="voice-sessions-list">',
|
|
15017
15322
|
...sessions.map((session) => [
|
|
15018
|
-
`<article class="voice-session-card ${
|
|
15323
|
+
`<article class="voice-session-card ${escapeHtml24(session.status)}">`,
|
|
15019
15324
|
'<div class="voice-session-card-header">',
|
|
15020
|
-
`<strong>${
|
|
15021
|
-
`<span>${
|
|
15325
|
+
`<strong>${escapeHtml24(session.sessionId)}</strong>`,
|
|
15326
|
+
`<span>${escapeHtml24(session.status)}</span>`,
|
|
15022
15327
|
"</div>",
|
|
15023
15328
|
"<dl>",
|
|
15024
15329
|
`<div><dt>Events</dt><dd>${String(session.eventCount)}</dd></div>`,
|
|
@@ -15026,9 +15331,9 @@ var renderVoiceSessionsHTML = (sessions) => sessions.length === 0 ? '<p class="v
|
|
|
15026
15331
|
`<div><dt>Transcripts</dt><dd>${String(session.transcriptCount)}</dd></div>`,
|
|
15027
15332
|
`<div><dt>Errors</dt><dd>${String(session.errorCount)}</dd></div>`,
|
|
15028
15333
|
"</dl>",
|
|
15029
|
-
session.latestOutcome ? `<p>Outcome: ${
|
|
15030
|
-
session.providers.length ? `<p>Providers: ${session.providers.map(
|
|
15031
|
-
session.replayHref ? `<p><a href="${
|
|
15334
|
+
session.latestOutcome ? `<p>Outcome: ${escapeHtml24(session.latestOutcome)}</p>` : "",
|
|
15335
|
+
session.providers.length ? `<p>Providers: ${session.providers.map(escapeHtml24).join(", ")}</p>` : "",
|
|
15336
|
+
session.replayHref ? `<p><a href="${escapeHtml24(session.replayHref)}">Open replay</a></p>` : "",
|
|
15032
15337
|
"</article>"
|
|
15033
15338
|
].join("")),
|
|
15034
15339
|
"</div>"
|
|
@@ -15059,7 +15364,7 @@ var createVoiceSessionsHTMLHandler = (options = {}) => async ({ query }) => {
|
|
|
15059
15364
|
var createVoiceSessionListRoutes = (options = {}) => {
|
|
15060
15365
|
const path = options.path ?? "/api/voice-sessions";
|
|
15061
15366
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
15062
|
-
const routes = new
|
|
15367
|
+
const routes = new Elysia22({
|
|
15063
15368
|
name: options.name ?? "absolutejs-voice-session-list"
|
|
15064
15369
|
}).get(path, createVoiceSessionsJSONHandler(options));
|
|
15065
15370
|
if (htmlPath) {
|
|
@@ -15087,7 +15392,7 @@ var createVoiceSessionReplayHTMLHandler = (options) => async ({ params }) => {
|
|
|
15087
15392
|
var createVoiceSessionReplayRoutes = (options) => {
|
|
15088
15393
|
const path = options.path ?? "/api/voice-sessions/:sessionId/replay";
|
|
15089
15394
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
15090
|
-
const routes = new
|
|
15395
|
+
const routes = new Elysia22({
|
|
15091
15396
|
name: options.name ?? "absolutejs-voice-session-replay"
|
|
15092
15397
|
}).get(path, createVoiceSessionReplayJSONHandler(options));
|
|
15093
15398
|
if (htmlPath) {
|
|
@@ -15316,10 +15621,10 @@ var assertVoiceAgentSquadContract = async (options) => {
|
|
|
15316
15621
|
return report;
|
|
15317
15622
|
};
|
|
15318
15623
|
// src/turnLatency.ts
|
|
15319
|
-
import { Elysia as
|
|
15624
|
+
import { Elysia as Elysia23 } from "elysia";
|
|
15320
15625
|
var DEFAULT_WARN_AFTER_MS = 1800;
|
|
15321
15626
|
var DEFAULT_FAIL_AFTER_MS = 3200;
|
|
15322
|
-
var
|
|
15627
|
+
var escapeHtml25 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
15323
15628
|
var firstNumber = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
15324
15629
|
var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
15325
15630
|
var createTraceStageIndex = (events) => {
|
|
@@ -15451,11 +15756,11 @@ await traceStore.append({
|
|
|
15451
15756
|
turnId,
|
|
15452
15757
|
type: 'turn_latency.stage'
|
|
15453
15758
|
});`;
|
|
15454
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
15455
|
-
<header><div><p class="eyebrow">${
|
|
15456
|
-
<dl>${turn.stages.map((stage) => `<div><dt>${
|
|
15759
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml25(turn.status)}">
|
|
15760
|
+
<header><div><p class="eyebrow">${escapeHtml25(turn.sessionId)} \xB7 ${escapeHtml25(turn.turnId)}</p><h2>${escapeHtml25(turn.text || "Empty turn")}</h2></div><strong>${escapeHtml25(turn.status)}</strong></header>
|
|
15761
|
+
<dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml25(stage.label)}</dt><dd>${escapeHtml25(formatMs(stage.valueMs))}</dd></div>`).join("")}</dl>
|
|
15457
15762
|
</article>`).join("");
|
|
15458
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
15763
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml25(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn,.primitive{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(251,191,36,.1))}.eyebrow{color:#5eead4;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.primitive p{color:#cbd5e1}.primitive pre{background:#0a0d10;border:1px solid #2a323a;border-radius:16px;color:#d9fff7;overflow:auto;padding:16px}.turn header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.empty{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{font-weight:900;margin:0}@media(max-width:800px){main{padding:18px}.turn header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">End-to-end responsiveness</p><h1>${escapeHtml25(title)}</h1><div class="summary"><span class="pill ${escapeHtml25(report.status)}">${escapeHtml25(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">avg ${escapeHtml25(formatMs(report.averageTotalMs))}</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTurnLatencyRoutes(...)</code> exposes the full turn waterfall</h2><p>Attach stage traces for speech detection, commit, model response, TTS send, and first audio so teams can prove where latency actually comes from.</p><pre><code>${escapeHtml25(snippet)}</code></pre></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
15459
15764
|
};
|
|
15460
15765
|
var createVoiceTurnLatencyJSONHandler = (options) => async () => summarizeVoiceTurnLatency(options);
|
|
15461
15766
|
var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
|
|
@@ -15472,7 +15777,7 @@ var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
|
|
|
15472
15777
|
var createVoiceTurnLatencyRoutes = (options) => {
|
|
15473
15778
|
const path = options.path ?? "/api/turn-latency";
|
|
15474
15779
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
15475
|
-
const routes = new
|
|
15780
|
+
const routes = new Elysia23({
|
|
15476
15781
|
name: options.name ?? "absolutejs-voice-turn-latency"
|
|
15477
15782
|
}).get(path, createVoiceTurnLatencyJSONHandler(options));
|
|
15478
15783
|
if (htmlPath) {
|
|
@@ -15481,8 +15786,8 @@ var createVoiceTurnLatencyRoutes = (options) => {
|
|
|
15481
15786
|
return routes;
|
|
15482
15787
|
};
|
|
15483
15788
|
// src/liveLatency.ts
|
|
15484
|
-
import { Elysia as
|
|
15485
|
-
var
|
|
15789
|
+
import { Elysia as Elysia24 } from "elysia";
|
|
15790
|
+
var escapeHtml26 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
15486
15791
|
var percentile = (values, percentileValue) => {
|
|
15487
15792
|
if (values.length === 0) {
|
|
15488
15793
|
return;
|
|
@@ -15549,13 +15854,13 @@ await traceStore.append({
|
|
|
15549
15854
|
sessionId,
|
|
15550
15855
|
type: 'client.live_latency'
|
|
15551
15856
|
});`;
|
|
15552
|
-
const rows = report.recent.map((sample) => `<tr><td>${
|
|
15553
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
15857
|
+
const rows = report.recent.map((sample) => `<tr><td>${escapeHtml26(sample.sessionId)}</td><td>${escapeHtml26(formatMs2(sample.latencyMs))}</td><td>${escapeHtml26(sample.status ?? "unknown")}</td><td>${escapeHtml26(new Date(sample.at).toLocaleString())}</td></tr>`).join("");
|
|
15858
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml26(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(245,158,11,.1));border:1px solid #26313d;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;padding:8px 12px}.pass{color:#86efac}.warn,.empty{color:#fbbf24}.fail{color:#fca5a5}.metrics{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.metrics article,table,.primitive{background:#141922;border:1px solid #26313d;border-radius:18px}.metrics article,.primitive{padding:16px}.metrics span{color:#a8b0b8}.metrics strong{display:block;font-size:2rem;margin-top:.25rem}.primitive{margin:0 0 18px}.primitive h2{margin:.2rem 0 .5rem}.primitive p{color:#cbd5e1}.primitive pre{background:#080b10;border:1px solid #26313d;border-radius:16px;color:#d9fff7;overflow:auto;padding:16px}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #26313d;padding:12px;text-align:left}@media(max-width:760px){main{padding:20px}}</style></head><body><main><section class="hero"><p class="eyebrow">Browser proof</p><h1>${escapeHtml26(title)}</h1><p>Recent real browser speech-to-assistant response measurements from persisted <code>client.live_latency</code> traces.</p><p class="status ${escapeHtml26(report.status)}">Status: ${escapeHtml26(report.status)}</p><section class="metrics"><article><span>p50</span><strong>${escapeHtml26(formatMs2(report.p50LatencyMs))}</strong></article><article><span>p95</span><strong>${escapeHtml26(formatMs2(report.p95LatencyMs))}</strong></article><article><span>Average</span><strong>${escapeHtml26(formatMs2(report.averageLatencyMs))}</strong></article><article><span>Samples</span><strong>${String(report.total)}</strong></article></section></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceLiveLatencyRoutes(...)</code> turns real browser timing into a release gate</h2><p>Persist live timing samples into the trace store so readiness, simulations, and trace timelines all point at the same self-hosted proof.</p><pre><code>${escapeHtml26(snippet)}</code></pre></section><table><thead><tr><th>Session</th><th>Latency</th><th>Status</th><th>Measured</th></tr></thead><tbody>${rows || '<tr><td colspan="4">No live latency samples yet.</td></tr>'}</tbody></table></main></body></html>`;
|
|
15554
15859
|
};
|
|
15555
15860
|
var createVoiceLiveLatencyRoutes = (options) => {
|
|
15556
15861
|
const path = options.path ?? "/api/live-latency";
|
|
15557
15862
|
const htmlPath = options.htmlPath === undefined ? "/live-latency" : options.htmlPath;
|
|
15558
|
-
const routes = new
|
|
15863
|
+
const routes = new Elysia24({
|
|
15559
15864
|
name: options.name ?? "absolutejs-voice-live-latency"
|
|
15560
15865
|
}).get(path, () => summarizeVoiceLiveLatency(options));
|
|
15561
15866
|
if (htmlPath) {
|
|
@@ -15874,9 +16179,9 @@ None.
|
|
|
15874
16179
|
`}`;
|
|
15875
16180
|
};
|
|
15876
16181
|
// src/turnQuality.ts
|
|
15877
|
-
import { Elysia as
|
|
16182
|
+
import { Elysia as Elysia25 } from "elysia";
|
|
15878
16183
|
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
15879
|
-
var
|
|
16184
|
+
var escapeHtml27 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
15880
16185
|
var getTurnLatencyMs = (turn) => {
|
|
15881
16186
|
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
15882
16187
|
if (firstTranscriptAt === undefined) {
|
|
@@ -15947,24 +16252,24 @@ var summarizeVoiceTurnQuality = async (options) => {
|
|
|
15947
16252
|
};
|
|
15948
16253
|
var renderVoiceTurnQualityHTML = (report, options = {}) => {
|
|
15949
16254
|
const title = options.title ?? "Voice Turn Quality";
|
|
15950
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
16255
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml27(turn.status)}">
|
|
15951
16256
|
<div class="turn-header">
|
|
15952
16257
|
<div>
|
|
15953
|
-
<p class="eyebrow">${
|
|
15954
|
-
<h2>${
|
|
16258
|
+
<p class="eyebrow">${escapeHtml27(turn.sessionId)} \xB7 ${escapeHtml27(turn.turnId)}</p>
|
|
16259
|
+
<h2>${escapeHtml27(turn.text || "Empty turn")}</h2>
|
|
15955
16260
|
</div>
|
|
15956
|
-
<strong>${
|
|
16261
|
+
<strong>${escapeHtml27(turn.status)}</strong>
|
|
15957
16262
|
</div>
|
|
15958
16263
|
<dl>
|
|
15959
|
-
<div><dt>Source</dt><dd>${
|
|
16264
|
+
<div><dt>Source</dt><dd>${escapeHtml27(turn.source ?? "unknown")}</dd></div>
|
|
15960
16265
|
<div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
|
|
15961
|
-
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${
|
|
15962
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
16266
|
+
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml27(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
|
|
16267
|
+
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml27(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
|
|
15963
16268
|
<div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
|
|
15964
16269
|
<div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
|
|
15965
16270
|
</dl>
|
|
15966
16271
|
</article>`).join("");
|
|
15967
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
16272
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml27(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml27(title)}</h1><div class="summary"><span class="pill ${escapeHtml27(report.status)}">${escapeHtml27(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
15968
16273
|
};
|
|
15969
16274
|
var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
|
|
15970
16275
|
var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
@@ -15981,7 +16286,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
|
15981
16286
|
var createVoiceTurnQualityRoutes = (options) => {
|
|
15982
16287
|
const path = options.path ?? "/api/turn-quality";
|
|
15983
16288
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
15984
|
-
const routes = new
|
|
16289
|
+
const routes = new Elysia25({
|
|
15985
16290
|
name: options.name ?? "absolutejs-voice-turn-quality"
|
|
15986
16291
|
}).get(path, createVoiceTurnQualityJSONHandler(options));
|
|
15987
16292
|
if (htmlPath) {
|
|
@@ -15990,7 +16295,7 @@ var createVoiceTurnQualityRoutes = (options) => {
|
|
|
15990
16295
|
return routes;
|
|
15991
16296
|
};
|
|
15992
16297
|
// src/telephonyOutcome.ts
|
|
15993
|
-
import { Elysia as
|
|
16298
|
+
import { Elysia as Elysia26 } from "elysia";
|
|
15994
16299
|
var DEFAULT_COMPLETED_STATUSES = [
|
|
15995
16300
|
"answered",
|
|
15996
16301
|
"completed",
|
|
@@ -16640,7 +16945,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
16640
16945
|
var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
16641
16946
|
const path = options.path ?? "/api/voice/telephony/webhook";
|
|
16642
16947
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
16643
|
-
return new
|
|
16948
|
+
return new Elysia26({
|
|
16644
16949
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
16645
16950
|
}).post(path, async ({ query, request }) => {
|
|
16646
16951
|
try {
|
|
@@ -16661,11 +16966,11 @@ var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
|
16661
16966
|
});
|
|
16662
16967
|
};
|
|
16663
16968
|
// src/phoneAgent.ts
|
|
16664
|
-
import { Elysia as
|
|
16969
|
+
import { Elysia as Elysia32 } from "elysia";
|
|
16665
16970
|
|
|
16666
16971
|
// src/telephony/plivo.ts
|
|
16667
16972
|
import { Buffer as Buffer5 } from "buffer";
|
|
16668
|
-
import { Elysia as
|
|
16973
|
+
import { Elysia as Elysia28 } from "elysia";
|
|
16669
16974
|
|
|
16670
16975
|
// src/telephony/contract.ts
|
|
16671
16976
|
var DEFAULT_REQUIREMENTS = [
|
|
@@ -16749,7 +17054,7 @@ var evaluateVoiceTelephonyContract = (input) => {
|
|
|
16749
17054
|
|
|
16750
17055
|
// src/telephony/twilio.ts
|
|
16751
17056
|
import { Buffer as Buffer4 } from "buffer";
|
|
16752
|
-
import { Elysia as
|
|
17057
|
+
import { Elysia as Elysia27 } from "elysia";
|
|
16753
17058
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
16754
17059
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
16755
17060
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -16779,7 +17084,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
|
|
|
16779
17084
|
return parameters;
|
|
16780
17085
|
};
|
|
16781
17086
|
var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
16782
|
-
var
|
|
17087
|
+
var escapeHtml28 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
16783
17088
|
var getWebhookVerificationUrl = (webhook, input) => {
|
|
16784
17089
|
if (!webhook?.verificationUrl) {
|
|
16785
17090
|
return;
|
|
@@ -16822,23 +17127,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
|
|
|
16822
17127
|
};
|
|
16823
17128
|
var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16824
17129
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
|
|
16825
|
-
<h1>${
|
|
17130
|
+
<h1>${escapeHtml28(title)}</h1>
|
|
16826
17131
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
16827
17132
|
<section>
|
|
16828
17133
|
<h2>URLs</h2>
|
|
16829
17134
|
<ul>
|
|
16830
|
-
<li><strong>TwiML:</strong> <code>${
|
|
16831
|
-
<li><strong>Media stream:</strong> <code>${
|
|
16832
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
17135
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml28(status.urls.twiml)}</code></li>
|
|
17136
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml28(status.urls.stream)}</code></li>
|
|
17137
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml28(status.urls.webhook)}</code></li>
|
|
16833
17138
|
</ul>
|
|
16834
17139
|
</section>
|
|
16835
17140
|
<section>
|
|
16836
17141
|
<h2>Signing</h2>
|
|
16837
17142
|
<p>Mode: <code>${status.signing.mode}</code></p>
|
|
16838
|
-
${status.signing.verificationUrl ? `<p>Verification URL: <code>${
|
|
17143
|
+
${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml28(status.signing.verificationUrl)}</code></p>` : ""}
|
|
16839
17144
|
</section>
|
|
16840
|
-
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
16841
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
17145
|
+
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml28(name)}</code></li>`).join("")}</ul></section>` : ""}
|
|
17146
|
+
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml28(warning)}</li>`).join("")}</ul></section>` : ""}
|
|
16842
17147
|
</main>`;
|
|
16843
17148
|
var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&", "&");
|
|
16844
17149
|
var createSmokeCheck = (name, status, message, details) => ({
|
|
@@ -16849,20 +17154,20 @@ var createSmokeCheck = (name, status, message, details) => ({
|
|
|
16849
17154
|
});
|
|
16850
17155
|
var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
16851
17156
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
|
|
16852
|
-
<h1>${
|
|
17157
|
+
<h1>${escapeHtml28(title)}</h1>
|
|
16853
17158
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
16854
17159
|
<section>
|
|
16855
17160
|
<h2>Checks</h2>
|
|
16856
17161
|
<ul>
|
|
16857
|
-
${report.checks.map((check) => `<li><strong>${
|
|
17162
|
+
${report.checks.map((check) => `<li><strong>${escapeHtml28(check.name)}</strong>: ${escapeHtml28(check.status)}${check.message ? ` - ${escapeHtml28(check.message)}` : ""}</li>`).join("")}
|
|
16858
17163
|
</ul>
|
|
16859
17164
|
</section>
|
|
16860
17165
|
<section>
|
|
16861
17166
|
<h2>Observed URLs</h2>
|
|
16862
17167
|
<ul>
|
|
16863
|
-
<li><strong>TwiML:</strong> <code>${
|
|
16864
|
-
<li><strong>Stream:</strong> <code>${
|
|
16865
|
-
<li><strong>Webhook:</strong> <code>${
|
|
17168
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml28(report.setup.urls.twiml)}</code></li>
|
|
17169
|
+
<li><strong>Stream:</strong> <code>${escapeHtml28(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
|
|
17170
|
+
<li><strong>Webhook:</strong> <code>${escapeHtml28(report.setup.urls.webhook)}</code></li>
|
|
16866
17171
|
</ul>
|
|
16867
17172
|
</section>
|
|
16868
17173
|
</main>`;
|
|
@@ -17322,7 +17627,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
17322
17627
|
const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
|
|
17323
17628
|
const bridges = new WeakMap;
|
|
17324
17629
|
const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
|
|
17325
|
-
const app = new
|
|
17630
|
+
const app = new Elysia27({
|
|
17326
17631
|
name: options.name ?? "absolutejs-voice-twilio"
|
|
17327
17632
|
}).get(twimlPath, async ({ query, request }) => {
|
|
17328
17633
|
const streamUrl = await resolveTwilioStreamUrl(options, {
|
|
@@ -17459,7 +17764,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
17459
17764
|
|
|
17460
17765
|
// src/telephony/plivo.ts
|
|
17461
17766
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17462
|
-
var
|
|
17767
|
+
var escapeHtml29 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17463
17768
|
var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
17464
17769
|
var resolveRequestOrigin2 = (request) => {
|
|
17465
17770
|
const url = new URL(request.url);
|
|
@@ -17710,21 +18015,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
|
|
|
17710
18015
|
};
|
|
17711
18016
|
var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17712
18017
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
|
|
17713
|
-
<h1>${
|
|
18018
|
+
<h1>${escapeHtml29(title)}</h1>
|
|
17714
18019
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
17715
18020
|
<ul>
|
|
17716
|
-
<li><strong>Answer XML:</strong> <code>${
|
|
17717
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
17718
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
18021
|
+
<li><strong>Answer XML:</strong> <code>${escapeHtml29(status.urls.answer)}</code></li>
|
|
18022
|
+
<li><strong>Audio stream:</strong> <code>${escapeHtml29(status.urls.stream)}</code></li>
|
|
18023
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml29(status.urls.webhook)}</code></li>
|
|
17719
18024
|
</ul>
|
|
17720
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
17721
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
18025
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml29(name)}</code></li>`).join("")}</ul>` : ""}
|
|
18026
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml29(warning)}</li>`).join("")}</ul>` : ""}
|
|
17722
18027
|
</main>`;
|
|
17723
18028
|
var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
17724
18029
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
|
|
17725
|
-
<h1>${
|
|
18030
|
+
<h1>${escapeHtml29(title)}</h1>
|
|
17726
18031
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
17727
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
18032
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml29(check.name)}</strong>: ${escapeHtml29(check.status)}${check.message ? ` - ${escapeHtml29(check.message)}` : ""}</li>`).join("")}</ul>
|
|
17728
18033
|
</main>`;
|
|
17729
18034
|
var runPlivoSmokeTest = async (input) => {
|
|
17730
18035
|
const setup = await buildPlivoVoiceSetupStatus(input.options, input);
|
|
@@ -17819,7 +18124,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
17819
18124
|
request: input.request
|
|
17820
18125
|
}) : verificationUrl ?? input.request.url
|
|
17821
18126
|
}) : undefined);
|
|
17822
|
-
const app = new
|
|
18127
|
+
const app = new Elysia28({
|
|
17823
18128
|
name: options.name ?? "absolutejs-voice-plivo"
|
|
17824
18129
|
}).get(answerPath, async ({ query, request }) => {
|
|
17825
18130
|
const streamUrl = await resolvePlivoStreamUrl(options, {
|
|
@@ -17930,9 +18235,9 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
17930
18235
|
|
|
17931
18236
|
// src/telephony/telnyx.ts
|
|
17932
18237
|
import { Buffer as Buffer6 } from "buffer";
|
|
17933
|
-
import { Elysia as
|
|
18238
|
+
import { Elysia as Elysia29 } from "elysia";
|
|
17934
18239
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17935
|
-
var
|
|
18240
|
+
var escapeHtml30 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
17936
18241
|
var joinUrlPath4 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
17937
18242
|
var resolveRequestOrigin3 = (request) => {
|
|
17938
18243
|
const url = new URL(request.url);
|
|
@@ -18133,21 +18438,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
|
|
|
18133
18438
|
};
|
|
18134
18439
|
var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
18135
18440
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
|
|
18136
|
-
<h1>${
|
|
18441
|
+
<h1>${escapeHtml30(title)}</h1>
|
|
18137
18442
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
18138
18443
|
<ul>
|
|
18139
|
-
<li><strong>TeXML:</strong> <code>${
|
|
18140
|
-
<li><strong>Media stream:</strong> <code>${
|
|
18141
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
18444
|
+
<li><strong>TeXML:</strong> <code>${escapeHtml30(status.urls.texml)}</code></li>
|
|
18445
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml30(status.urls.stream)}</code></li>
|
|
18446
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml30(status.urls.webhook)}</code></li>
|
|
18142
18447
|
</ul>
|
|
18143
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
18144
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
18448
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml30(name)}</code></li>`).join("")}</ul>` : ""}
|
|
18449
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml30(warning)}</li>`).join("")}</ul>` : ""}
|
|
18145
18450
|
</main>`;
|
|
18146
18451
|
var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
18147
18452
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
|
|
18148
|
-
<h1>${
|
|
18453
|
+
<h1>${escapeHtml30(title)}</h1>
|
|
18149
18454
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
18150
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
18455
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml30(check.name)}</strong>: ${escapeHtml30(check.status)}${check.message ? ` - ${escapeHtml30(check.message)}` : ""}</li>`).join("")}</ul>
|
|
18151
18456
|
</main>`;
|
|
18152
18457
|
var runTelnyxSmokeTest = async (input) => {
|
|
18153
18458
|
const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
|
|
@@ -18241,7 +18546,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
18241
18546
|
publicKey: options.webhook?.publicKey,
|
|
18242
18547
|
toleranceSeconds: options.webhook?.toleranceSeconds
|
|
18243
18548
|
}) : undefined);
|
|
18244
|
-
const app = new
|
|
18549
|
+
const app = new Elysia29({
|
|
18245
18550
|
name: options.name ?? "absolutejs-voice-telnyx"
|
|
18246
18551
|
}).get(texmlPath, async ({ query, request }) => {
|
|
18247
18552
|
const streamUrl = await resolveTelnyxStreamUrl(options, {
|
|
@@ -18351,8 +18656,8 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
18351
18656
|
};
|
|
18352
18657
|
|
|
18353
18658
|
// src/telephony/matrix.ts
|
|
18354
|
-
import { Elysia as
|
|
18355
|
-
var
|
|
18659
|
+
import { Elysia as Elysia30 } from "elysia";
|
|
18660
|
+
var escapeHtml31 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
18356
18661
|
var labelForProvider = (provider) => provider.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
18357
18662
|
var resolveEntryStatus = (contract, setup, smoke) => {
|
|
18358
18663
|
if (!contract.pass || !setup.ready || smoke?.pass === false) {
|
|
@@ -18413,13 +18718,13 @@ var badgeStyles = {
|
|
|
18413
18718
|
};
|
|
18414
18719
|
var renderVoiceTelephonyCarrierMatrixHTML = (matrix, options = {}) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 1040px; margin: 40px auto; padding: 0 20px; color: #172033;">
|
|
18415
18720
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Carrier matrix</p>
|
|
18416
|
-
<h1 style="font-size: 34px; margin: 0 0 8px;">${
|
|
18721
|
+
<h1 style="font-size: 34px; margin: 0 0 8px;">${escapeHtml31(options.title ?? "AbsoluteJS Voice Carrier Matrix")}</h1>
|
|
18417
18722
|
<p style="color:#52606d; margin: 0 0 24px;">${matrix.summary.ready}/${matrix.summary.providers} ready, ${matrix.summary.contractsPassing}/${matrix.summary.providers} contract passing, ${matrix.summary.smokePassing}/${matrix.summary.providers} smoke passing.</p>
|
|
18418
18723
|
<section style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px;">
|
|
18419
18724
|
${matrix.entries.map((entry) => `<article style="border:1px solid #d9e2ec; border-radius:18px; padding:18px; background:#fff; box-shadow:0 18px 48px rgba(15,23,42,.08);">
|
|
18420
18725
|
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center;">
|
|
18421
|
-
<h2 style="margin:0; font-size:20px;">${
|
|
18422
|
-
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${
|
|
18726
|
+
<h2 style="margin:0; font-size:20px;">${escapeHtml31(entry.name)}</h2>
|
|
18727
|
+
<span style="border:1px solid; border-radius:999px; padding:4px 10px; font-size:12px; font-weight:700; ${badgeStyles[entry.status]}">${escapeHtml31(entry.status.toUpperCase())}</span>
|
|
18423
18728
|
</div>
|
|
18424
18729
|
<dl style="display:grid; grid-template-columns: 1fr 1fr; gap:8px 12px; margin:16px 0;">
|
|
18425
18730
|
<dt style="color:#64748b;">Setup</dt><dd style="margin:0; font-weight:700;">${entry.ready ? "Ready" : "Needs attention"}</dd>
|
|
@@ -18427,15 +18732,15 @@ ${matrix.entries.map((entry) => `<article style="border:1px solid #d9e2ec; borde
|
|
|
18427
18732
|
<dt style="color:#64748b;">Smoke</dt><dd style="margin:0; font-weight:700;">${entry.smoke ? entry.smoke.pass ? "Pass" : "Fail" : "Missing"}</dd>
|
|
18428
18733
|
<dt style="color:#64748b;">Contract</dt><dd style="margin:0; font-weight:700;">${entry.contract.pass ? "Pass" : "Fail"}</dd>
|
|
18429
18734
|
</dl>
|
|
18430
|
-
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${
|
|
18431
|
-
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${
|
|
18432
|
-
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${
|
|
18735
|
+
<p style="margin:0 0 8px; color:#475569;"><strong>Stream:</strong> <code>${escapeHtml31(entry.setup.urls.stream || "missing")}</code></p>
|
|
18736
|
+
<p style="margin:0 0 12px; color:#475569;"><strong>Webhook:</strong> <code>${escapeHtml31(entry.setup.urls.webhook || "missing")}</code></p>
|
|
18737
|
+
${entry.issues.length ? `<ul style="margin:12px 0 0; padding-left:18px;">${entry.issues.map((issue) => `<li>${escapeHtml31(issue.severity)}: ${escapeHtml31(issue.message)}</li>`).join("")}</ul>` : '<p style="margin:12px 0 0; color:#166534;">No contract issues.</p>'}
|
|
18433
18738
|
</article>`).join("")}
|
|
18434
18739
|
</section>
|
|
18435
18740
|
</main>`;
|
|
18436
18741
|
var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
18437
18742
|
const path = options.path ?? "/api/voice/telephony/carriers";
|
|
18438
|
-
return new
|
|
18743
|
+
return new Elysia30({
|
|
18439
18744
|
name: options.name ?? "absolutejs-voice-telephony-carrier-matrix"
|
|
18440
18745
|
}).get(path, async ({ query, request }) => {
|
|
18441
18746
|
const providers = await options.load({ query, request });
|
|
@@ -18457,7 +18762,7 @@ var createVoiceTelephonyCarrierMatrixRoutes = (options) => {
|
|
|
18457
18762
|
};
|
|
18458
18763
|
|
|
18459
18764
|
// src/phoneAgentProductionSmoke.ts
|
|
18460
|
-
import { Elysia as
|
|
18765
|
+
import { Elysia as Elysia31 } from "elysia";
|
|
18461
18766
|
var defaultRequirements = [
|
|
18462
18767
|
"media-started",
|
|
18463
18768
|
"transcript",
|
|
@@ -18465,7 +18770,7 @@ var defaultRequirements = [
|
|
|
18465
18770
|
"lifecycle-outcome",
|
|
18466
18771
|
"no-session-error"
|
|
18467
18772
|
];
|
|
18468
|
-
var
|
|
18773
|
+
var escapeHtml32 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
18469
18774
|
var payloadType = (event) => typeof event.payload.type === "string" ? event.payload.type : undefined;
|
|
18470
18775
|
var hasTextPayload = (event) => ["text", "assistantText", "transcript"].some((key) => {
|
|
18471
18776
|
const value = event.payload[key];
|
|
@@ -18574,10 +18879,10 @@ var resolveHandlerOptions = async (options, input) => ({
|
|
|
18574
18879
|
});
|
|
18575
18880
|
var renderVoicePhoneAgentProductionSmokeHTML = (report, options = {}) => {
|
|
18576
18881
|
const title = options.title ?? "AbsoluteJS Voice Phone Smoke Contract";
|
|
18577
|
-
const issues = report.issues.map((issue) => `<li><strong>${
|
|
18578
|
-
const outcomes = report.observed.lifecycleOutcomes.map((outcome) => `<span class="pill">${
|
|
18579
|
-
const requirements = report.required.map((requirement) => `<span class="pill">${
|
|
18580
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
18882
|
+
const issues = report.issues.map((issue) => `<li><strong>${escapeHtml32(issue.requirement)}</strong>: ${escapeHtml32(issue.message)}</li>`).join("");
|
|
18883
|
+
const outcomes = report.observed.lifecycleOutcomes.map((outcome) => `<span class="pill">${escapeHtml32(outcome)}</span>`).join("");
|
|
18884
|
+
const requirements = report.required.map((requirement) => `<span class="pill">${escapeHtml32(requirement)}</span>`).join("");
|
|
18885
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml32(title)}</title><style>body{background:#0e141b;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1050px;padding:32px}.hero,.panel{background:#151d26;border:1px solid #283544;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12))}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.2rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.metric{background:#0f151d;border:1px solid #283544;border-radius:16px;padding:14px}.metric strong{display:block;font-size:1.8rem}.pill{background:#0f151d;border:1px solid #3f3f46;border-radius:999px;display:inline-flex;margin:4px;padding:7px 10px}.issues{color:#fca5a5}code{color:#fde68a}@media(max-width:720px){main{padding:18px}}</style></head><body><main><section class="hero"><p class="eyebrow">Phone agent production smoke</p><h1>${escapeHtml32(title)}</h1><p class="status ${report.pass ? "pass" : "fail"}">${report.pass ? "PASS" : "FAIL"}</p><p>Contract <code>${escapeHtml32(report.contractId)}</code>${report.provider ? ` for <code>${escapeHtml32(report.provider)}</code>` : ""}${report.sessionId ? ` on session <code>${escapeHtml32(report.sessionId)}</code>` : ""}.</p></section><section class="panel"><h2>Observed Trace Evidence</h2><div class="grid"><div class="metric"><span>Media starts</span><strong>${String(report.observed.mediaStarts)}</strong></div><div class="metric"><span>Transcripts</span><strong>${String(report.observed.transcripts)}</strong></div><div class="metric"><span>Assistant responses</span><strong>${String(report.observed.assistantResponses)}</strong></div><div class="metric"><span>Session errors</span><strong>${String(report.observed.sessionErrors)}</strong></div></div><p>${outcomes || '<span class="pill">No lifecycle outcome</span>'}</p></section><section class="panel"><h2>Requirements</h2><p>${requirements}</p>${issues ? `<ul class="issues">${issues}</ul>` : '<p class="pass">All required phone-agent smoke evidence is present.</p>'}</section></main></body></html>`;
|
|
18581
18886
|
};
|
|
18582
18887
|
var createVoicePhoneAgentProductionSmokeJSONHandler = (options) => async ({
|
|
18583
18888
|
query,
|
|
@@ -18600,7 +18905,7 @@ var createVoicePhoneAgentProductionSmokeHTMLHandler = (options) => async ({
|
|
|
18600
18905
|
var createVoicePhoneAgentProductionSmokeRoutes = (options) => {
|
|
18601
18906
|
const path = options.path ?? "/api/voice/phone/smoke-contract";
|
|
18602
18907
|
const htmlPath = options.htmlPath === undefined ? "/voice/phone/smoke-contract" : options.htmlPath;
|
|
18603
|
-
const routes = new
|
|
18908
|
+
const routes = new Elysia31({
|
|
18604
18909
|
name: options.name ?? "absolutejs-voice-phone-smoke-contract"
|
|
18605
18910
|
}).get(path, createVoicePhoneAgentProductionSmokeJSONHandler(options));
|
|
18606
18911
|
if (htmlPath) {
|
|
@@ -18643,7 +18948,7 @@ var PHONE_AGENT_LIFECYCLE_STAGES = [
|
|
|
18643
18948
|
"completed",
|
|
18644
18949
|
"failed"
|
|
18645
18950
|
];
|
|
18646
|
-
var
|
|
18951
|
+
var escapeHtml33 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
18647
18952
|
var loadRouteJson = async (input) => {
|
|
18648
18953
|
const response = await input.app.handle(new Request(new URL(input.path, input.origin).toString(), {
|
|
18649
18954
|
headers: {
|
|
@@ -18703,10 +19008,10 @@ var renderVoicePhoneAgentSetupHTML = (report) => {
|
|
|
18703
19008
|
const entry = report.matrix?.entries.find((candidate) => candidate.provider === carrier.provider && (candidate.name === carrier.name || candidate.name === (carrier.name ?? carrier.provider)));
|
|
18704
19009
|
const urls = entry?.setup.urls;
|
|
18705
19010
|
const primaryUrl = carrier.provider === "plivo" ? urls?.twiml : urls?.twiml;
|
|
18706
|
-
return `<tr><td>${
|
|
19011
|
+
return `<tr><td>${escapeHtml33(carrier.name ?? carrier.provider)}</td><td>${escapeHtml33(carrier.provider)}</td><td><code>${escapeHtml33(carrier.setupPath || "disabled")}</code></td><td><code>${escapeHtml33(carrier.smokePath || "disabled")}</code></td><td>${entry ? `<span class="${escapeHtml33(entry.status)}">${escapeHtml33(entry.status.toUpperCase())}</span>` : "unknown"}</td><td>${primaryUrl ? `<code>${escapeHtml33(primaryUrl)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.webhook ? `<code>${escapeHtml33(urls.webhook)}</code>` : '<span class="muted">missing</span>'}</td><td>${urls?.stream ? `<code>${escapeHtml33(urls.stream)}</code>` : '<span class="muted">missing</span>'}</td></tr>`;
|
|
18707
19012
|
}).join("");
|
|
18708
|
-
const stageList = report.lifecycleStages.map((stage) => `<li><code>${
|
|
18709
|
-
const snippet =
|
|
19013
|
+
const stageList = report.lifecycleStages.map((stage) => `<li><code>${escapeHtml33(stage)}</code></li>`).join("");
|
|
19014
|
+
const snippet = escapeHtml33(`const phoneAgent = createVoicePhoneAgent({
|
|
18710
19015
|
carriers: [
|
|
18711
19016
|
{
|
|
18712
19017
|
provider: 'twilio',
|
|
@@ -18743,10 +19048,10 @@ app.use(
|
|
|
18743
19048
|
const urls = entry?.setup.urls;
|
|
18744
19049
|
const answerLabel = carrier.provider === "telnyx" ? "TeXML URL" : carrier.provider === "plivo" ? "Answer URL" : "TwiML URL";
|
|
18745
19050
|
const answerUrl = urls?.twiml;
|
|
18746
|
-
const issueList = entry?.issues.map((issue) => `<li>${
|
|
18747
|
-
return `<article><h3>${
|
|
19051
|
+
const issueList = entry?.issues.map((issue) => `<li>${escapeHtml33(issue.severity)}: ${escapeHtml33(issue.message)}</li>`).join("") ?? "";
|
|
19052
|
+
return `<article><h3>${escapeHtml33(carrier.name ?? carrier.provider)}</h3><ol><li>Set ${escapeHtml33(answerLabel)} to <code>${escapeHtml33(answerUrl ?? "missing")}</code>.</li><li>Set status webhook to <code>${escapeHtml33(urls?.webhook ?? "missing")}</code>.</li><li>Allow media stream URL <code>${escapeHtml33(urls?.stream ?? "missing")}</code>.</li><li>Open setup: ${carrier.setupPath ? `<a href="${escapeHtml33(carrier.setupPath)}?format=html">${escapeHtml33(carrier.setupPath)}</a>` : '<span class="muted">disabled</span>'}.</li><li>Run smoke: ${carrier.smokePath ? `<a href="${escapeHtml33(carrier.smokePath)}?format=html">${escapeHtml33(carrier.smokePath)}</a>` : '<span class="muted">disabled</span>'}.</li>${report.productionSmokePath ? `<li>Certify production smoke traces: <a href="${escapeHtml33(report.productionSmokePath.replace("/api/", "/"))}?sessionId=">${escapeHtml33(report.productionSmokePath)}</a>.</li>` : ""}</ol>${issueList ? `<ul class="issues">${issueList}</ul>` : '<p class="pass">No carrier contract issues.</p>'}</article>`;
|
|
18748
19053
|
}).join("");
|
|
18749
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
19054
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml33(report.title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.primitive{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}.primitive{background:#151d27;border-color:#365a60}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.warn{color:#fde68a}.muted{color:#aab5c0}table{background:#151d27;border:1px solid #283544;border-collapse:collapse;border-radius:18px;display:block;overflow:auto;width:100%}td,th{border-bottom:1px solid #283544;padding:12px;text-align:left;vertical-align:top}code{color:#fde68a;overflow-wrap:anywhere}.primitive p{color:#cbd5de;line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #283544;border-radius:18px;color:#fef3c7;overflow:auto;padding:16px}.checklist{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));margin:18px 0}.checklist article{background:#151d27;border:1px solid #283544;border-radius:18px;padding:18px}.checklist ol{padding-left:20px}.issues{color:#fca5a5}.stages{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));padding-left:18px}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Phone agent setup</p><h1>${escapeHtml33(report.title)}</h1><p>One self-hosted entrypoint for carrier routes, setup reports, smoke checks, and normalized call lifecycle stages.</p><p class="badge ${report.ready ? "pass" : "fail"}">Ready: ${String(report.ready)}</p>${report.matrixPath ? `<p><a href="${escapeHtml33(report.matrixPath)}?format=html">Open carrier matrix</a></p>` : ""}</section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoicePhoneAgent(...)</code> builds this carrier control plane</h2><p>Mount carrier routes once, expose setup and smoke proof, then feed the same carrier matrix and phone-agent smoke reports into production readiness so carrier regressions block deploys.</p><pre><code>${snippet}</code></pre></section><h2>Carrier Setup Checklist</h2><section class="checklist">${checklist}</section><h2>Carrier URLs</h2><table><thead><tr><th>Name</th><th>Provider</th><th>Setup</th><th>Smoke</th><th>Status</th><th>Answer/TwiML/TeXML</th><th>Webhook</th><th>Stream</th></tr></thead><tbody>${carrierRows}</tbody></table><h2>Lifecycle Schema</h2><ul class="stages">${stageList}</ul></main></body></html>`;
|
|
18750
19055
|
};
|
|
18751
19056
|
var createVoicePhoneAgent = (options) => {
|
|
18752
19057
|
const carrierSummaries = options.carriers.map((carrier) => ({
|
|
@@ -18755,7 +19060,7 @@ var createVoicePhoneAgent = (options) => {
|
|
|
18755
19060
|
setupPath: resolveSetupPath(carrier),
|
|
18756
19061
|
smokePath: resolveSmokePath(carrier)
|
|
18757
19062
|
}));
|
|
18758
|
-
const app = new
|
|
19063
|
+
const app = new Elysia32({
|
|
18759
19064
|
name: options.name ?? "absolutejs-voice-phone-agent"
|
|
18760
19065
|
});
|
|
18761
19066
|
for (const carrier of options.carriers) {
|
|
@@ -20809,8 +21114,8 @@ var createOpenAIVoiceTTS = (options) => {
|
|
|
20809
21114
|
};
|
|
20810
21115
|
};
|
|
20811
21116
|
// src/providerCapabilities.ts
|
|
20812
|
-
import { Elysia as
|
|
20813
|
-
var
|
|
21117
|
+
import { Elysia as Elysia33 } from "elysia";
|
|
21118
|
+
var escapeHtml34 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20814
21119
|
var fromProviderList = (kind, providers, options) => (providers ?? []).map((provider) => ({
|
|
20815
21120
|
configured: true,
|
|
20816
21121
|
features: options.features?.[provider],
|
|
@@ -20873,27 +21178,27 @@ var summarizeVoiceProviderCapabilities = async (options) => {
|
|
|
20873
21178
|
var renderVoiceProviderCapabilityHTML = (report, options = {}) => {
|
|
20874
21179
|
const title = options.title ?? "Voice Provider Capabilities";
|
|
20875
21180
|
const cards = report.capabilities.map((capability) => {
|
|
20876
|
-
const features = (capability.features ?? []).map((feature) => `<span class="pill">${
|
|
20877
|
-
return `<article class="card ${
|
|
21181
|
+
const features = (capability.features ?? []).map((feature) => `<span class="pill">${escapeHtml34(feature)}</span>`).join("");
|
|
21182
|
+
return `<article class="card ${escapeHtml34(capability.status)}">
|
|
20878
21183
|
<div class="card-header">
|
|
20879
21184
|
<div>
|
|
20880
|
-
<p class="eyebrow">${
|
|
20881
|
-
<h2>${
|
|
21185
|
+
<p class="eyebrow">${escapeHtml34(capability.kind)}</p>
|
|
21186
|
+
<h2>${escapeHtml34(capability.label ?? capability.provider)}</h2>
|
|
20882
21187
|
</div>
|
|
20883
|
-
<strong>${
|
|
21188
|
+
<strong>${escapeHtml34(capability.status)}</strong>
|
|
20884
21189
|
</div>
|
|
20885
|
-
${capability.description ? `<p>${
|
|
21190
|
+
${capability.description ? `<p>${escapeHtml34(capability.description)}</p>` : ""}
|
|
20886
21191
|
<dl>
|
|
20887
21192
|
<div><dt>Configured</dt><dd>${capability.configured ? "yes" : "no"}</dd></div>
|
|
20888
21193
|
<div><dt>Selected</dt><dd>${capability.selected ? "yes" : "no"}</dd></div>
|
|
20889
|
-
<div><dt>Model</dt><dd>${
|
|
21194
|
+
<div><dt>Model</dt><dd>${escapeHtml34(capability.model ?? "default")}</dd></div>
|
|
20890
21195
|
<div><dt>Runs</dt><dd>${String(capability.health?.runCount ?? 0)}</dd></div>
|
|
20891
21196
|
<div><dt>Errors</dt><dd>${String(capability.health?.errorCount ?? 0)}</dd></div>
|
|
20892
21197
|
</dl>
|
|
20893
21198
|
${features ? `<div class="features">${features}</div>` : ""}
|
|
20894
21199
|
</article>`;
|
|
20895
21200
|
}).join("");
|
|
20896
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
21201
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml34(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.card{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(14,165,233,.16),rgba(34,197,94,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary,.features{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.card-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.selected,.healthy{color:#86efac}.unconfigured,.degraded,.rate-limited,.suppressed{color:#fca5a5}.idle,.recoverable{color:#fde68a}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.card-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Provider Discovery</p><h1>${escapeHtml34(title)}</h1><div class="summary"><span class="pill">${String(report.configured)} configured</span><span class="pill">${String(report.selected)} selected</span><span class="pill">${String(report.unconfigured)} missing</span><span class="pill">${String(report.total)} total</span></div></section><section class="grid">${cards || '<article class="card"><p>No provider capabilities configured.</p></article>'}</section></main></body></html>`;
|
|
20897
21202
|
};
|
|
20898
21203
|
var createVoiceProviderCapabilityJSONHandler = (options) => async () => summarizeVoiceProviderCapabilities(options);
|
|
20899
21204
|
var createVoiceProviderCapabilityHTMLHandler = (options) => async () => {
|
|
@@ -20910,7 +21215,7 @@ var createVoiceProviderCapabilityHTMLHandler = (options) => async () => {
|
|
|
20910
21215
|
var createVoiceProviderCapabilityRoutes = (options) => {
|
|
20911
21216
|
const path = options.path ?? "/api/provider-capabilities";
|
|
20912
21217
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
20913
|
-
const routes = new
|
|
21218
|
+
const routes = new Elysia33({
|
|
20914
21219
|
name: options.name ?? "absolutejs-voice-provider-capabilities"
|
|
20915
21220
|
}).get(path, createVoiceProviderCapabilityJSONHandler(options));
|
|
20916
21221
|
if (htmlPath) {
|
|
@@ -20919,8 +21224,8 @@ var createVoiceProviderCapabilityRoutes = (options) => {
|
|
|
20919
21224
|
return routes;
|
|
20920
21225
|
};
|
|
20921
21226
|
// src/resilienceRoutes.ts
|
|
20922
|
-
import { Elysia as
|
|
20923
|
-
var
|
|
21227
|
+
import { Elysia as Elysia34 } from "elysia";
|
|
21228
|
+
var escapeHtml35 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
20924
21229
|
var getString12 = (value) => typeof value === "string" ? value : undefined;
|
|
20925
21230
|
var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20926
21231
|
var getBoolean2 = (value) => value === true;
|
|
@@ -21067,13 +21372,13 @@ var summarizeRoutingEvents = (events) => {
|
|
|
21067
21372
|
};
|
|
21068
21373
|
var renderProviderCards = (title, providers) => {
|
|
21069
21374
|
if (providers.length === 0) {
|
|
21070
|
-
return `<p class="muted">No ${
|
|
21375
|
+
return `<p class="muted">No ${escapeHtml35(title)} provider health yet.</p>`;
|
|
21071
21376
|
}
|
|
21072
21377
|
return `<div class="provider-grid">${providers.map((provider) => `
|
|
21073
|
-
<article class="card provider ${
|
|
21378
|
+
<article class="card provider ${escapeHtml35(provider.status)}">
|
|
21074
21379
|
<div class="card-header">
|
|
21075
|
-
<strong>${
|
|
21076
|
-
<span>${
|
|
21380
|
+
<strong>${escapeHtml35(provider.provider)}</strong>
|
|
21381
|
+
<span>${escapeHtml35(provider.status)}${provider.recommended ? " \xB7 recommended" : ""}</span>
|
|
21077
21382
|
</div>
|
|
21078
21383
|
<dl>
|
|
21079
21384
|
<div><dt>Runs</dt><dd>${provider.runCount}</dd></div>
|
|
@@ -21082,7 +21387,7 @@ var renderProviderCards = (title, providers) => {
|
|
|
21082
21387
|
<div><dt>Timeouts</dt><dd>${provider.timeoutCount}</dd></div>
|
|
21083
21388
|
<div><dt>Fallbacks</dt><dd>${provider.fallbackCount}</dd></div>
|
|
21084
21389
|
</dl>
|
|
21085
|
-
${provider.lastError ? `<p class="muted">${
|
|
21390
|
+
${provider.lastError ? `<p class="muted">${escapeHtml35(provider.lastError)}</p>` : ""}
|
|
21086
21391
|
</article>
|
|
21087
21392
|
`).join("")}</div>`;
|
|
21088
21393
|
};
|
|
@@ -21091,24 +21396,24 @@ var renderTimeline2 = (events) => {
|
|
|
21091
21396
|
return '<p class="muted">No provider routing events yet. Run the app or simulate provider failover.</p>';
|
|
21092
21397
|
}
|
|
21093
21398
|
return `<div class="timeline">${events.slice(0, 40).map((event) => `
|
|
21094
|
-
<article class="card event ${
|
|
21399
|
+
<article class="card event ${escapeHtml35(event.status ?? "unknown")}">
|
|
21095
21400
|
<div class="card-header">
|
|
21096
|
-
<strong>${
|
|
21401
|
+
<strong>${escapeHtml35(event.kind.toUpperCase())} ${escapeHtml35(event.operation ?? "generate")}</strong>
|
|
21097
21402
|
<span>${new Date(event.at).toLocaleString()}</span>
|
|
21098
21403
|
</div>
|
|
21099
21404
|
<p>
|
|
21100
|
-
<span class="pill">${
|
|
21101
|
-
<span class="pill">provider: ${
|
|
21102
|
-
${event.fallbackProvider ? `<span class="pill">fallback: ${
|
|
21405
|
+
<span class="pill">${escapeHtml35(event.status ?? "unknown")}</span>
|
|
21406
|
+
<span class="pill">provider: ${escapeHtml35(event.provider ?? "unknown")}</span>
|
|
21407
|
+
${event.fallbackProvider ? `<span class="pill">fallback: ${escapeHtml35(event.fallbackProvider)}</span>` : ""}
|
|
21103
21408
|
${event.timedOut ? '<span class="pill danger">timed out</span>' : ""}
|
|
21104
21409
|
</p>
|
|
21105
21410
|
<dl>
|
|
21106
21411
|
<div><dt>Attempt</dt><dd>${event.attempt ?? 0}</dd></div>
|
|
21107
21412
|
<div><dt>Elapsed</dt><dd>${event.elapsedMs ?? 0}ms</dd></div>
|
|
21108
21413
|
<div><dt>Budget</dt><dd>${event.latencyBudgetMs ?? 0}ms</dd></div>
|
|
21109
|
-
<div><dt>Session</dt><dd>${
|
|
21414
|
+
<div><dt>Session</dt><dd>${escapeHtml35(event.sessionId)}</dd></div>
|
|
21110
21415
|
</dl>
|
|
21111
|
-
${event.error ? `<p class="muted">${
|
|
21416
|
+
${event.error ? `<p class="muted">${escapeHtml35(event.error)}</p>` : ""}
|
|
21112
21417
|
</article>
|
|
21113
21418
|
`).join("")}</div>`;
|
|
21114
21419
|
};
|
|
@@ -21118,9 +21423,9 @@ var renderSessionKind = (kind, summary) => {
|
|
|
21118
21423
|
const status = latest?.status ?? "idle";
|
|
21119
21424
|
const fallback = latest?.fallbackProvider && latest.fallbackProvider !== provider ? ` -> ${latest.fallbackProvider}` : "";
|
|
21120
21425
|
return `<div>
|
|
21121
|
-
<dt>${
|
|
21122
|
-
<dd>${
|
|
21123
|
-
<small>${
|
|
21426
|
+
<dt>${escapeHtml35(kind.toUpperCase())}</dt>
|
|
21427
|
+
<dd>${escapeHtml35(provider)}${escapeHtml35(fallback)}</dd>
|
|
21428
|
+
<small>${escapeHtml35(status)} \xB7 ${summary.runCount} event${summary.runCount === 1 ? "" : "s"} \xB7 ${summary.errorCount} error${summary.errorCount === 1 ? "" : "s"} \xB7 ${summary.fallbackCount} fallback${summary.fallbackCount === 1 ? "" : "s"}</small>
|
|
21124
21429
|
</div>`;
|
|
21125
21430
|
};
|
|
21126
21431
|
var renderSessionSummaries = (sessions) => {
|
|
@@ -21128,10 +21433,10 @@ var renderSessionSummaries = (sessions) => {
|
|
|
21128
21433
|
return '<p class="muted">No call-level routing summaries yet. Run a voice session or provider simulation.</p>';
|
|
21129
21434
|
}
|
|
21130
21435
|
return `<div class="session-grid">${sessions.slice(0, 12).map((session) => `
|
|
21131
|
-
<article class="card session ${
|
|
21436
|
+
<article class="card session ${escapeHtml35(session.status)}">
|
|
21132
21437
|
<div class="card-header">
|
|
21133
|
-
<strong>${
|
|
21134
|
-
<span>${
|
|
21438
|
+
<strong>${escapeHtml35(session.sessionId)}</strong>
|
|
21439
|
+
<span>${escapeHtml35(session.status)}</span>
|
|
21135
21440
|
</div>
|
|
21136
21441
|
<p>
|
|
21137
21442
|
<span class="pill">${session.eventCount} routing events</span>
|
|
@@ -21158,21 +21463,21 @@ var renderSimulationControls = (kind, simulation) => {
|
|
|
21158
21463
|
const pathPrefix = simulation.pathPrefix ?? `/api/${kind}-simulate`;
|
|
21159
21464
|
const failureProviders = simulation.failureProviders ?? configuredProviders.map(({ provider }) => provider);
|
|
21160
21465
|
const canFail = (provider) => configuredProviders.some((entry) => entry.provider === provider) && (!simulation.fallbackRequiredProvider || configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider));
|
|
21161
|
-
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${
|
|
21162
|
-
<p class="muted">${
|
|
21466
|
+
return `<div class="simulate-panel" data-sim-kind="${kind}" data-sim-prefix="${escapeHtml35(pathPrefix)}">
|
|
21467
|
+
<p class="muted">${escapeHtml35(simulation.failureMessage ?? `Simulate ${kind.toUpperCase()} provider failure without changing provider credentials.`)}</p>
|
|
21163
21468
|
<div class="simulate-actions">
|
|
21164
|
-
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${
|
|
21165
|
-
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${
|
|
21469
|
+
${failureProviders.map((provider) => `<button type="button" data-provider-fail="${escapeHtml35(provider)}"${canFail(provider) ? "" : " disabled"}>Simulate ${escapeHtml35(provider)} ${kind.toUpperCase()} failure</button>`).join("")}
|
|
21470
|
+
${configuredProviders.map((provider) => `<button type="button" data-provider-recover="${escapeHtml35(provider.provider)}">Mark ${escapeHtml35(provider.provider)} recovered</button>`).join("")}
|
|
21166
21471
|
</div>
|
|
21167
|
-
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${
|
|
21472
|
+
${simulation.fallbackRequiredProvider && !configuredProviders.some((entry) => entry.provider === simulation.fallbackRequiredProvider) ? `<p class="muted">${escapeHtml35(simulation.fallbackRequiredMessage ?? `Configure ${simulation.fallbackRequiredProvider} to enable fallback simulation.`)}</p>` : ""}
|
|
21168
21473
|
<pre class="simulate-output" hidden></pre>
|
|
21169
21474
|
</div>`;
|
|
21170
21475
|
};
|
|
21171
21476
|
var renderVoiceResilienceHTML = (input) => {
|
|
21172
21477
|
const summary = summarizeRoutingEvents(input.routingEvents);
|
|
21173
|
-
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${
|
|
21174
|
-
const links = input.links?.length ? input.links.map((link) => `<a href="${
|
|
21175
|
-
const snippet =
|
|
21478
|
+
const kindCounts = [...summary.byKind.entries()].map(([kind, count]) => `<span class="pill">${escapeHtml35(kind)}: ${String(count)}</span>`).join("");
|
|
21479
|
+
const links = input.links?.length ? input.links.map((link) => `<a href="${escapeHtml35(link.href)}">${escapeHtml35(link.label)}</a>`).join(" \xB7 ") : "";
|
|
21480
|
+
const snippet = escapeHtml35(`const sttSimulator = createVoiceIOProviderFailureSimulator({
|
|
21176
21481
|
kind: 'stt',
|
|
21177
21482
|
providers: ['deepgram', 'assemblyai'],
|
|
21178
21483
|
fallback: ['deepgram', 'assemblyai'],
|
|
@@ -21210,7 +21515,7 @@ app.use(
|
|
|
21210
21515
|
<head>
|
|
21211
21516
|
<meta charset="utf-8" />
|
|
21212
21517
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
21213
|
-
<title>${
|
|
21518
|
+
<title>${escapeHtml35(input.title ?? "AbsoluteJS Voice Resilience")}</title>
|
|
21214
21519
|
<style>
|
|
21215
21520
|
:root { color-scheme: dark; }
|
|
21216
21521
|
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; }
|
|
@@ -21362,7 +21667,7 @@ var registerSimulationRoutes = (routes, simulation, defaultPathPrefix) => {
|
|
|
21362
21667
|
};
|
|
21363
21668
|
var createVoiceResilienceRoutes = (options) => {
|
|
21364
21669
|
const path = options.path ?? "/resilience";
|
|
21365
|
-
const routes = new
|
|
21670
|
+
const routes = new Elysia34({
|
|
21366
21671
|
name: options.name ?? "absolutejs-voice-resilience"
|
|
21367
21672
|
}).get(path, async () => {
|
|
21368
21673
|
const events = await options.store.list();
|
|
@@ -21440,8 +21745,8 @@ var assertVoiceProviderRoutingContract = async (options) => {
|
|
|
21440
21745
|
return report;
|
|
21441
21746
|
};
|
|
21442
21747
|
// src/productionReadiness.ts
|
|
21443
|
-
import { Elysia as
|
|
21444
|
-
var
|
|
21748
|
+
import { Elysia as Elysia35 } from "elysia";
|
|
21749
|
+
var escapeHtml36 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
21445
21750
|
var rollupStatus2 = (checks) => checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "pass";
|
|
21446
21751
|
var readinessGateCodes = {
|
|
21447
21752
|
"Agent squad contracts": "voice.readiness.agent_squad_contracts",
|
|
@@ -22392,22 +22697,22 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
22392
22697
|
var buildVoiceProductionReadinessGate = async (options, input = {}) => summarizeVoiceProductionReadinessGate(await buildVoiceProductionReadinessReport(options, input), options.gate || undefined);
|
|
22393
22698
|
var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
22394
22699
|
const title = options.title ?? "AbsoluteJS Voice Production Readiness";
|
|
22395
|
-
const profile = report.profile ? `<section class="profile"><p class="eyebrow">Readiness profile</p><h2>${
|
|
22700
|
+
const profile = report.profile ? `<section class="profile"><p class="eyebrow">Readiness profile</p><h2>${escapeHtml36(report.profile.name)}</h2><p>${escapeHtml36(report.profile.description)}</p><p>${escapeHtml36(report.profile.purpose)}</p><div class="profile-surfaces">${report.profile.surfaces.map((surface) => `<article class="${surface.configured ? "pass" : "warn"}"><span>${surface.configured ? "CONFIGURED" : "EXPECTED"}</span><strong>${surface.href ? `<a href="${escapeHtml36(surface.href)}">${escapeHtml36(surface.label)}</a>` : escapeHtml36(surface.label)}</strong></article>`).join("")}</div></section>` : "";
|
|
22396
22701
|
const checks = report.checks.map((check, index) => {
|
|
22397
|
-
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${
|
|
22398
|
-
return `<article class="check ${
|
|
22702
|
+
const actions = (check.actions ?? []).map((action) => action.method === "POST" ? `<button type="button" data-readiness-action="${index}" data-action-url="${escapeHtml36(action.href)}">${escapeHtml36(action.label)}</button>` : `<a href="${escapeHtml36(action.href)}">${escapeHtml36(action.label)}</a>`).join("");
|
|
22703
|
+
return `<article class="check ${escapeHtml36(check.status)}">
|
|
22399
22704
|
<div>
|
|
22400
|
-
<span>${
|
|
22401
|
-
<h2>${
|
|
22402
|
-
${check.detail ? `<p>${
|
|
22403
|
-
${check.proofSource ? `<p class="proof-source">Proof source: ${check.proofSource.href ? `<a href="${
|
|
22705
|
+
<span>${escapeHtml36(check.status.toUpperCase())}</span>
|
|
22706
|
+
<h2>${escapeHtml36(check.label)}</h2>
|
|
22707
|
+
${check.detail ? `<p>${escapeHtml36(check.detail)}</p>` : ""}
|
|
22708
|
+
${check.proofSource ? `<p class="proof-source">Proof source: ${check.proofSource.href ? `<a href="${escapeHtml36(check.proofSource.href)}">${escapeHtml36(check.proofSource.sourceLabel)}</a>` : escapeHtml36(check.proofSource.sourceLabel)}${check.proofSource.detail ? ` \xB7 ${escapeHtml36(check.proofSource.detail)}` : ""}</p>` : ""}
|
|
22404
22709
|
${actions ? `<p class="actions">${actions}</p>` : ""}
|
|
22405
22710
|
</div>
|
|
22406
|
-
<strong>${
|
|
22407
|
-
${check.href ? `<a href="${
|
|
22711
|
+
<strong>${escapeHtml36(String(check.value ?? check.status))}</strong>
|
|
22712
|
+
${check.href ? `<a href="${escapeHtml36(check.href)}">Open surface</a>` : ""}
|
|
22408
22713
|
</article>`;
|
|
22409
22714
|
}).join("");
|
|
22410
|
-
const snippet =
|
|
22715
|
+
const snippet = escapeHtml36(`createVoiceProductionReadinessRoutes({
|
|
22411
22716
|
htmlPath: '/production-readiness',
|
|
22412
22717
|
path: '/api/production-readiness',
|
|
22413
22718
|
gatePath: '/api/production-readiness/gate',
|
|
@@ -22423,13 +22728,13 @@ var renderVoiceProductionReadinessHTML = (report, options = {}) => {
|
|
|
22423
22728
|
providerRoutingContracts: loadProviderRoutingContracts,
|
|
22424
22729
|
store: traceStore
|
|
22425
22730
|
});`);
|
|
22426
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
22731
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml36(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero,.primitive,.profile{background:linear-gradient(135deg,rgba(20,184,166,.18),rgba(245,158,11,.12));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.primitive,.profile{background:#111722}.primitive{border-color:#3a3f2d}.eyebrow{color:#fbbf24;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{display:inline-flex;border:1px solid #3f3f46;border-radius:999px;padding:8px 12px}.primitive code{color:#fde68a}.primitive p{color:#c8ccd3;line-height:1.55;margin:.45rem 0 0}.primitive pre{background:#0b0f16;border:1px solid #2c3440;border-radius:18px;color:#fef3c7;margin:16px 0 0;overflow:auto;padding:16px}.status.pass,.check.pass,.profile-surfaces .pass{border-color:rgba(34,197,94,.55)}.status.warn,.check.warn,.profile-surfaces .warn{border-color:rgba(245,158,11,.65)}.status.fail,.check.fail{border-color:rgba(239,68,68,.75)}.checks{display:grid;gap:14px}.check{align-items:center;background:#141922;border:1px solid #26313d;border-radius:22px;display:grid;gap:16px;grid-template-columns:1fr auto auto;padding:18px}.check span,.profile-surfaces span{color:#a8b0b8;font-size:.78rem;font-weight:900;letter-spacing:.08em}.check h2{margin:.2rem 0}.check p,.profile p{color:#b9c0c8;margin:.2rem 0 0}.check .proof-source{color:#f9d77e;font-weight:800}.check strong{font-size:1.5rem}.profile-surfaces{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin-top:16px}.profile-surfaces article{background:#141922;border:1px solid #26313d;border-radius:16px;padding:14px}.profile-surfaces strong{display:block;margin-top:6px}.actions{display:flex;flex-wrap:wrap;gap:10px}.check a,a{color:#fbbf24}button{background:#fbbf24;border:0;border-radius:999px;color:#111827;cursor:pointer;font-weight:800;padding:9px 12px}button:disabled{cursor:wait;opacity:.65}@media(max-width:760px){main{padding:20px}.check{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted readiness</p><h1>${escapeHtml36(title)}</h1><p>One deployable pass/fail report for quality gates, provider failover, session health, handoffs, routing evidence, and optional carrier readiness.</p><p class="status ${escapeHtml36(report.status)}">Overall: ${escapeHtml36(report.status.toUpperCase())}</p><p>Checked ${escapeHtml36(new Date(report.checkedAt).toLocaleString())}</p></section>${profile}<section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceProductionReadinessRoutes(...)</code> builds this deploy gate</h2><p>Mount one package primitive to expose JSON readiness, HTML readiness, and a machine-readable gate route. Feed it the proof stores and contract reports your app already owns.</p><pre><code>${snippet}</code></pre></section><section class="checks">${checks}</section></main><script>document.querySelectorAll("[data-readiness-action]").forEach((button)=>{button.addEventListener("click",async()=>{const url=button.getAttribute("data-action-url");if(!url)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch(url,{method:"POST"});button.textContent=response.ok?"Done. Reloading...":"Failed";if(response.ok)setTimeout(()=>location.reload(),500)}catch{button.textContent="Failed"}finally{setTimeout(()=>{button.disabled=false;button.textContent=original},1500)}})});</script></body></html>`;
|
|
22427
22732
|
};
|
|
22428
22733
|
var createVoiceProductionReadinessRoutes = (options) => {
|
|
22429
22734
|
const path = options.path ?? "/api/production-readiness";
|
|
22430
22735
|
const gatePath = options.gatePath === undefined ? "/api/production-readiness/gate" : options.gatePath;
|
|
22431
22736
|
const htmlPath = options.htmlPath ?? "/production-readiness";
|
|
22432
|
-
const routes = new
|
|
22737
|
+
const routes = new Elysia35({
|
|
22433
22738
|
name: options.name ?? "absolutejs-voice-production-readiness"
|
|
22434
22739
|
});
|
|
22435
22740
|
routes.get(path, async ({ query, request }) => buildVoiceProductionReadinessReport(options, { query, request }));
|
|
@@ -22793,8 +23098,8 @@ var recommendVoiceReadinessProfile = (options) => {
|
|
|
22793
23098
|
};
|
|
22794
23099
|
};
|
|
22795
23100
|
// src/providerStackRecommendations.ts
|
|
22796
|
-
import { Elysia as
|
|
22797
|
-
var
|
|
23101
|
+
import { Elysia as Elysia36 } from "elysia";
|
|
23102
|
+
var escapeHtml37 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22798
23103
|
var profileProviderPriorities = {
|
|
22799
23104
|
"meeting-recorder": {
|
|
22800
23105
|
llm: ["openai", "anthropic", "gemini"],
|
|
@@ -23037,17 +23342,17 @@ var resolveProviderContractMatrixInput = async (matrix) => typeof matrix === "fu
|
|
|
23037
23342
|
var renderVoiceProviderContractMatrixHTML = (report, options = {}) => {
|
|
23038
23343
|
const title = options.title ?? "Voice Provider Contract Matrix";
|
|
23039
23344
|
const rows = report.rows.map((row) => {
|
|
23040
|
-
const checks = row.checks.map((check) => `<li class="${
|
|
23041
|
-
return `<article class="row ${
|
|
23345
|
+
const checks = row.checks.map((check) => `<li class="${escapeHtml37(check.status)}"><strong>${escapeHtml37(check.label)}</strong><span>${escapeHtml37(check.detail ?? check.status)}</span>${check.remediation ? `<em>${check.remediation.href ? `<a href="${escapeHtml37(check.remediation.href)}">${escapeHtml37(check.remediation.label)}</a>` : escapeHtml37(check.remediation.label)}: ${escapeHtml37(check.remediation.detail)}</em>` : ""}</li>`).join("");
|
|
23346
|
+
return `<article class="row ${escapeHtml37(row.status)}">
|
|
23042
23347
|
<div>
|
|
23043
|
-
<p class="eyebrow">${
|
|
23044
|
-
<h2>${
|
|
23045
|
-
<p class="status ${
|
|
23348
|
+
<p class="eyebrow">${escapeHtml37(row.kind)}${row.selected ? " \xB7 selected" : ""}</p>
|
|
23349
|
+
<h2>${escapeHtml37(row.provider)}</h2>
|
|
23350
|
+
<p class="status ${escapeHtml37(row.status)}">${escapeHtml37(row.status.toUpperCase())}</p>
|
|
23046
23351
|
</div>
|
|
23047
23352
|
<ul>${checks}</ul>
|
|
23048
23353
|
</article>`;
|
|
23049
23354
|
}).join("");
|
|
23050
|
-
const snippet =
|
|
23355
|
+
const snippet = escapeHtml37(`const providerContracts = () =>
|
|
23051
23356
|
createVoiceProviderContractMatrixPreset('phone-agent', {
|
|
23052
23357
|
env: process.env,
|
|
23053
23358
|
providers: {
|
|
@@ -23068,7 +23373,7 @@ createVoiceProductionReadinessRoutes({
|
|
|
23068
23373
|
providerContractMatrix: () =>
|
|
23069
23374
|
buildVoiceProviderContractMatrix(providerContracts())
|
|
23070
23375
|
});`);
|
|
23071
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23376
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml37(title)}</title><style>body{background:#0f1412;color:#f7f3e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.primitive,.row{background:#17201b;border:1px solid #2d3b32;border-radius:24px;margin-bottom:16px;padding:22px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.16),rgba(125,211,252,.12))}.primitive{background:#111814;border-color:#41604a}.eyebrow{color:#86efac;font-size:.78rem;font-weight:900;letter-spacing:.1em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill,.status{border:1px solid #3f4f45;border-radius:999px;display:inline-flex;padding:8px 12px}.primitive code{color:#bbf7d0}.primitive p{color:#c8d8ca;line-height:1.55;margin:.45rem 0 0}.primitive pre{background:#08110d;border:1px solid #294132;border-radius:18px;color:#d9f99d;margin:16px 0 0;overflow:auto;padding:16px}.status.pass,.row.pass,.pass{border-color:rgba(34,197,94,.65)}.status.warn,.row.warn,.warn{border-color:rgba(245,158,11,.7)}.status.fail,.row.fail,.fail{border-color:rgba(239,68,68,.75)}.row{display:grid;gap:20px;grid-template-columns:minmax(180px,.45fr) 1fr}.row ul{display:grid;gap:10px;list-style:none;margin:0;padding:0}.row li{background:#111814;border:1px solid #2d3b32;border-radius:16px;display:grid;gap:4px;padding:12px}.row li span{color:#b8c2ba}.row li em{color:#f9d77e;font-style:normal}.row li a{color:#86efac}@media(max-width:760px){main{padding:18px}.row{grid-template-columns:1fr}}</style></head><body><main><section class="hero"><p class="eyebrow">Provider contracts</p><h1>${escapeHtml37(title)}</h1><p>Self-hosted provider proof for configured state, required env, latency budgets, fallback, streaming, and declared capabilities.</p><div class="summary"><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.warned)} warning</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} total</span></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceProviderContractMatrixPreset(...)</code> builds this matrix</h2><p>Give AbsoluteJS your configured LLM, STT, and TTS providers once. It turns them into deploy-checkable proof for env, fallback, streaming, latency budgets, selected providers, and profile-required capabilities without a hosted dashboard.</p><pre><code>${snippet}</code></pre></section>${rows || '<article class="row"><p>No provider contracts configured.</p></article>'}</main></body></html>`;
|
|
23072
23377
|
};
|
|
23073
23378
|
var createVoiceProviderContractMatrixJSONHandler = (matrix) => async () => buildVoiceProviderContractMatrix(await resolveProviderContractMatrixInput(matrix));
|
|
23074
23379
|
var createVoiceProviderContractMatrixHTMLHandler = (options) => async () => {
|
|
@@ -23083,7 +23388,7 @@ var createVoiceProviderContractMatrixHTMLHandler = (options) => async () => {
|
|
|
23083
23388
|
var createVoiceProviderContractMatrixRoutes = (options) => {
|
|
23084
23389
|
const path = options.path ?? "/api/provider-contracts";
|
|
23085
23390
|
const htmlPath = options.htmlPath ?? "/provider-contracts";
|
|
23086
|
-
const routes = new
|
|
23391
|
+
const routes = new Elysia36({
|
|
23087
23392
|
name: options.name ?? "absolutejs-voice-provider-contract-matrix"
|
|
23088
23393
|
});
|
|
23089
23394
|
const jsonHandler = createVoiceProviderContractMatrixJSONHandler(options.matrix);
|
|
@@ -23142,7 +23447,7 @@ var evaluateVoiceProviderStackGaps = (input) => {
|
|
|
23142
23447
|
};
|
|
23143
23448
|
};
|
|
23144
23449
|
// src/opsConsoleRoutes.ts
|
|
23145
|
-
import { Elysia as
|
|
23450
|
+
import { Elysia as Elysia37 } from "elysia";
|
|
23146
23451
|
var DEFAULT_LINKS = [
|
|
23147
23452
|
{
|
|
23148
23453
|
description: "Quality gates for CI, deploy checks, and production readiness.",
|
|
@@ -23177,7 +23482,7 @@ var DEFAULT_LINKS = [
|
|
|
23177
23482
|
label: "Handoffs"
|
|
23178
23483
|
}
|
|
23179
23484
|
];
|
|
23180
|
-
var
|
|
23485
|
+
var escapeHtml38 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23181
23486
|
var countProviderStatuses = (providers) => {
|
|
23182
23487
|
const degradedStatuses = new Set(["degraded", "rate-limited", "suppressed"]);
|
|
23183
23488
|
const healthy = providers.filter((provider) => provider.status === "healthy").length;
|
|
@@ -23246,20 +23551,20 @@ var buildVoiceOpsConsoleReport = async (options) => {
|
|
|
23246
23551
|
trace
|
|
23247
23552
|
};
|
|
23248
23553
|
};
|
|
23249
|
-
var renderMetricCard = (input) => `<article class="metric"><span>${
|
|
23554
|
+
var renderMetricCard = (input) => `<article class="metric"><span>${escapeHtml38(input.label)}</span><strong>${escapeHtml38(String(input.value))}</strong>${input.status ? `<p class="${escapeHtml38(input.status)}">${escapeHtml38(input.status)}</p>` : ""}${input.href ? `<a href="${escapeHtml38(input.href)}">Open</a>` : ""}</article>`;
|
|
23250
23555
|
var renderVoiceOpsConsoleHTML = (report, options = {}) => {
|
|
23251
23556
|
const links = report.links.map((link) => `<article class="surface">
|
|
23252
|
-
<div><h2>${
|
|
23253
|
-
<p><a href="${
|
|
23557
|
+
<div><h2>${escapeHtml38(link.label)}</h2>${link.description ? `<p>${escapeHtml38(link.description)}</p>` : ""}</div>
|
|
23558
|
+
<p><a href="${escapeHtml38(link.href)}">Open ${escapeHtml38(link.label)}</a>${link.statusHref ? ` \xB7 <a href="${escapeHtml38(link.statusHref)}">Status</a>` : ""}</p>
|
|
23254
23559
|
</article>`).join("");
|
|
23255
|
-
const sessions = report.recentSessions.length ? report.recentSessions.map((session) => `<tr><td>${
|
|
23256
|
-
const routing = report.recentRoutingEvents.length ? report.recentRoutingEvents.map((event) => `<tr><td>${
|
|
23560
|
+
const sessions = report.recentSessions.length ? report.recentSessions.map((session) => `<tr><td>${escapeHtml38(session.sessionId)}</td><td>${escapeHtml38(session.status)}</td><td>${session.turnCount}</td><td>${session.errorCount}</td><td>${session.replayHref ? `<a href="${escapeHtml38(session.replayHref)}">Replay</a>` : ""}</td></tr>`).join("") : '<tr><td colspan="5">No sessions yet.</td></tr>';
|
|
23561
|
+
const routing = report.recentRoutingEvents.length ? report.recentRoutingEvents.map((event) => `<tr><td>${escapeHtml38(event.kind)}</td><td>${escapeHtml38(event.provider ?? "unknown")}</td><td>${escapeHtml38(event.status ?? "unknown")}</td><td>${event.elapsedMs ?? 0}ms</td><td>${escapeHtml38(event.sessionId)}</td></tr>`).join("") : '<tr><td colspan="5">No provider routing events yet.</td></tr>';
|
|
23257
23562
|
const title = options.title ?? "AbsoluteJS Voice Ops Console";
|
|
23258
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23563
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml38(title)}</title><style>body{font-family:ui-sans-serif,system-ui,sans-serif;background:#101316;color:#f6f2e8;margin:0}main{max-width:1180px;margin:auto;padding:32px}a{color:#fbbf24}header{display:flex;justify-content:space-between;gap:24px;align-items:flex-start;margin-bottom:24px}.eyebrow{color:#fbbf24;font-weight:800;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.2rem,5vw,4.5rem);line-height:.95;margin:.2rem 0 1rem}.muted{color:#a8b0b8}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.metric,.surface{background:#181d22;border:1px solid #2a323a;border-radius:20px;padding:18px}.metric strong{display:block;font-size:2.2rem;margin:.25rem 0}.pass,.healthy{color:#86efac}.fail,.failed,.degraded{color:#fca5a5}.surfaces{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin:24px 0}table{width:100%;border-collapse:collapse;background:#181d22;border-radius:16px;overflow:hidden;margin:12px 0 28px}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left}section{margin-top:30px}@media(max-width:700px){main{padding:20px}header{display:block}}</style></head><body><main><header><div><p class="eyebrow">Self-hosted voice operations</p><h1>${escapeHtml38(title)}</h1><p class="muted">One deployable control plane for quality gates, failover, traces, sessions, handoffs, and provider health.</p></div><p class="muted">Checked ${escapeHtml38(new Date(report.checkedAt).toLocaleString())}</p></header><div class="grid">${renderMetricCard({ label: "Quality", value: report.quality.status, status: report.quality.status, href: "/quality" })}${renderMetricCard({ label: "Events", value: report.eventCount, href: "/diagnostics" })}${renderMetricCard({ label: "Sessions", value: report.sessions.total, status: report.sessions.failed > 0 ? "failed" : "healthy", href: "/sessions" })}${renderMetricCard({ label: "Handoffs failed", value: report.handoffs.failed, status: report.handoffs.failed > 0 ? "failed" : "healthy", href: "/handoffs" })}${renderMetricCard({ label: "Providers degraded", value: report.providers.degraded, status: report.providers.degraded > 0 ? "degraded" : "healthy", href: "/resilience" })}</div><section><h2>Operational Surfaces</h2><div class="surfaces">${links}</div></section><section><h2>Recent Sessions</h2><table><thead><tr><th>Session</th><th>Status</th><th>Turns</th><th>Errors</th><th>Replay</th></tr></thead><tbody>${sessions}</tbody></table></section><section><h2>Recent Provider Routing</h2><table><thead><tr><th>Kind</th><th>Provider</th><th>Status</th><th>Elapsed</th><th>Session</th></tr></thead><tbody>${routing}</tbody></table></section></main></body></html>`;
|
|
23259
23564
|
};
|
|
23260
23565
|
var createVoiceOpsConsoleRoutes = (options) => {
|
|
23261
23566
|
const path = options.path ?? "/ops-console";
|
|
23262
|
-
const routes = new
|
|
23567
|
+
const routes = new Elysia37({
|
|
23263
23568
|
name: options.name ?? "absolutejs-voice-ops-console"
|
|
23264
23569
|
});
|
|
23265
23570
|
const getReport = () => buildVoiceOpsConsoleReport(options);
|
|
@@ -23276,11 +23581,11 @@ var createVoiceOpsConsoleRoutes = (options) => {
|
|
|
23276
23581
|
return routes;
|
|
23277
23582
|
};
|
|
23278
23583
|
// src/operationsRecord.ts
|
|
23279
|
-
import { Elysia as
|
|
23584
|
+
import { Elysia as Elysia39 } from "elysia";
|
|
23280
23585
|
|
|
23281
23586
|
// src/traceTimeline.ts
|
|
23282
|
-
import { Elysia as
|
|
23283
|
-
var
|
|
23587
|
+
import { Elysia as Elysia38 } from "elysia";
|
|
23588
|
+
var escapeHtml39 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23284
23589
|
var getString14 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
23285
23590
|
var getNumber9 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
23286
23591
|
var firstString3 = (payload, keys) => {
|
|
@@ -23448,16 +23753,16 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
|
23448
23753
|
};
|
|
23449
23754
|
};
|
|
23450
23755
|
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
23451
|
-
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>${
|
|
23756
|
+
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>${escapeHtml39(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>`;
|
|
23452
23757
|
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
23453
|
-
const events = session.events.map((event) => `<tr class="${
|
|
23454
|
-
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${
|
|
23455
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23758
|
+
const events = session.events.map((event) => `<tr class="${escapeHtml39(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml39(event.type)}</td><td>${escapeHtml39(event.label)}</td><td>${escapeHtml39(event.provider ?? "")}</td><td>${escapeHtml39(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
23759
|
+
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml39(issue.severity)}">${escapeHtml39(issue.code)}: ${escapeHtml39(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
23760
|
+
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 Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml39(session.sessionId)}</h1><p class="status ${escapeHtml39(session.status)}">${escapeHtml39(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>`;
|
|
23456
23761
|
};
|
|
23457
|
-
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${
|
|
23762
|
+
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml39(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml39(session.sessionId)}</a></td><td>${escapeHtml39(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) => escapeHtml39(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
23458
23763
|
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}}";
|
|
23459
23764
|
var renderVoiceTraceTimelineHTML = (report, options = {}) => {
|
|
23460
|
-
const snippet =
|
|
23765
|
+
const snippet = escapeHtml39(`const traceStore = createVoiceTraceSinkStore({
|
|
23461
23766
|
store: runtimeStorage.traces,
|
|
23462
23767
|
sinks: [
|
|
23463
23768
|
createVoiceTraceHTTPSink({
|
|
@@ -23483,13 +23788,13 @@ app.use(
|
|
|
23483
23788
|
traceDeliveries: runtimeStorage.traceDeliveries
|
|
23484
23789
|
})
|
|
23485
23790
|
);`);
|
|
23486
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23791
|
+
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 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>${escapeHtml39(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>`;
|
|
23487
23792
|
};
|
|
23488
23793
|
var createVoiceTraceTimelineRoutes = (options) => {
|
|
23489
23794
|
const path = options.path ?? "/api/voice-traces";
|
|
23490
23795
|
const htmlPath = options.htmlPath ?? "/traces";
|
|
23491
23796
|
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
23492
|
-
const routes = new
|
|
23797
|
+
const routes = new Elysia38({
|
|
23493
23798
|
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
23494
23799
|
});
|
|
23495
23800
|
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
@@ -23643,16 +23948,16 @@ var buildVoiceOperationsRecord = async (options) => {
|
|
|
23643
23948
|
traceEvents
|
|
23644
23949
|
};
|
|
23645
23950
|
};
|
|
23646
|
-
var
|
|
23951
|
+
var escapeHtml40 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23647
23952
|
var formatMs4 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
23648
23953
|
var renderVoiceOperationsRecordHTML = (record, options = {}) => {
|
|
23649
|
-
const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${
|
|
23650
|
-
const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${
|
|
23651
|
-
const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${
|
|
23652
|
-
const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${
|
|
23653
|
-
const tasks = record.tasks?.tasks.length ? record.tasks.tasks.map((task) => `<li><strong>${
|
|
23654
|
-
const integrationEvents = record.integrationEvents?.events.length ? record.integrationEvents.events.map((event) => `<li><strong>${
|
|
23655
|
-
const snippet =
|
|
23954
|
+
const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml40(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>';
|
|
23955
|
+
const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml40(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml40(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml40(handoff.status ?? "")}</span><p>${escapeHtml40(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
|
|
23956
|
+
const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml40(tool.toolName ?? "tool")}</strong> <span>${escapeHtml40(tool.status ?? "")}</span> ${formatMs4(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml40(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
|
|
23957
|
+
const reviews = record.reviews?.reviews.length ? record.reviews.reviews.map((review) => `<li><strong>${escapeHtml40(review.title)}</strong> <span>${escapeHtml40(review.summary.outcome ?? "")}</span><p>${escapeHtml40(review.postCall?.summary ?? review.transcript.actual)}</p></li>`).join("") : "<li>No call reviews recorded.</li>";
|
|
23958
|
+
const tasks = record.tasks?.tasks.length ? record.tasks.tasks.map((task) => `<li><strong>${escapeHtml40(task.title)}</strong> <span>${escapeHtml40(task.status)}</span><p>${escapeHtml40(task.recommendedAction)}</p></li>`).join("") : "<li>No ops tasks recorded.</li>";
|
|
23959
|
+
const integrationEvents = record.integrationEvents?.events.length ? record.integrationEvents.events.map((event) => `<li><strong>${escapeHtml40(event.type)}</strong> <span>${escapeHtml40(event.deliveryStatus ?? "local")}</span><p>${escapeHtml40(event.deliveryError ?? event.deliveredTo ?? "")}</p></li>`).join("") : "<li>No integration events recorded.</li>";
|
|
23960
|
+
const snippet = escapeHtml40(`app.use(
|
|
23656
23961
|
createVoiceOperationsRecordRoutes({
|
|
23657
23962
|
audit: auditStore,
|
|
23658
23963
|
integrationEvents: opsEvents,
|
|
@@ -23666,12 +23971,12 @@ var renderVoiceOperationsRecordHTML = (record, options = {}) => {
|
|
|
23666
23971
|
tasks: opsTasks
|
|
23667
23972
|
})
|
|
23668
23973
|
);`);
|
|
23669
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23974
|
+
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 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>${escapeHtml40(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml40(record.status)}">${escapeHtml40(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><div class="card"><span>Reviews</span><strong>${String(record.reviews?.total ?? 0)}</strong></div><div class="card"><span>Tasks</span><strong>${String(record.tasks?.total ?? 0)}</strong></div><div class="card"><span>Integrations</span><strong>${String(record.integrationEvents?.total ?? 0)}</strong></div></section><section class="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, replay, reviews, tasks, and webhook delivery.</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><section><h2>Reviews</h2><ul>${reviews}</ul></section><section><h2>Tasks</h2><ul>${tasks}</ul></section><section><h2>Integration Events</h2><ul>${integrationEvents}</ul></section></main></body></html>`;
|
|
23670
23975
|
};
|
|
23671
23976
|
var createVoiceOperationsRecordRoutes = (options) => {
|
|
23672
23977
|
const path = options.path ?? "/api/voice-operations/:sessionId";
|
|
23673
23978
|
const htmlPath = options.htmlPath === undefined ? "/voice-operations/:sessionId" : options.htmlPath;
|
|
23674
|
-
const routes = new
|
|
23979
|
+
const routes = new Elysia39({
|
|
23675
23980
|
name: options.name ?? "absolutejs-voice-operations-record"
|
|
23676
23981
|
});
|
|
23677
23982
|
const buildRecord = (sessionId) => buildVoiceOperationsRecord({
|
|
@@ -23704,7 +24009,7 @@ var createVoiceOperationsRecordRoutes = (options) => {
|
|
|
23704
24009
|
return routes;
|
|
23705
24010
|
};
|
|
23706
24011
|
// src/incidentBundle.ts
|
|
23707
|
-
import { Elysia as
|
|
24012
|
+
import { Elysia as Elysia40 } from "elysia";
|
|
23708
24013
|
var filterIncidentBundleArtifacts = (artifacts, filter = {}) => artifacts.filter((artifact) => {
|
|
23709
24014
|
if (filter.sessionId && artifact.sessionId !== filter.sessionId) {
|
|
23710
24015
|
return false;
|
|
@@ -23903,7 +24208,7 @@ var buildVoiceIncidentBundle = async (options) => {
|
|
|
23903
24208
|
var createVoiceIncidentBundleRoutes = (options) => {
|
|
23904
24209
|
const path = options.path ?? "/api/voice-incidents/:sessionId";
|
|
23905
24210
|
const markdownPath = options.markdownPath === undefined ? "/voice-incidents/:sessionId/markdown" : options.markdownPath;
|
|
23906
|
-
const routes = new
|
|
24211
|
+
const routes = new Elysia40({
|
|
23907
24212
|
name: options.name ?? "absolutejs-voice-incident-bundle"
|
|
23908
24213
|
});
|
|
23909
24214
|
const getSessionId = (params) => params.sessionId ?? "";
|
|
@@ -24104,19 +24409,19 @@ var summarizeVoiceOpsStatus = async (options) => {
|
|
|
24104
24409
|
};
|
|
24105
24410
|
};
|
|
24106
24411
|
// src/opsStatusRoutes.ts
|
|
24107
|
-
import { Elysia as
|
|
24108
|
-
var
|
|
24412
|
+
import { Elysia as Elysia41 } from "elysia";
|
|
24413
|
+
var escapeHtml41 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
24109
24414
|
var renderVoiceOpsStatusHTML = (report, options = {}) => {
|
|
24110
24415
|
const title = options.title ?? "AbsoluteJS Voice Ops Status";
|
|
24111
24416
|
const surfaces = Object.entries(report.surfaces).map(([key, surface]) => {
|
|
24112
24417
|
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;
|
|
24113
|
-
return `<article class="surface ${
|
|
24418
|
+
return `<article class="surface ${escapeHtml41(surface.status)}"><span>${escapeHtml41(surface.status.toUpperCase())}</span><h2>${escapeHtml41(key)}</h2><strong>${escapeHtml41(value)}</strong></article>`;
|
|
24114
24419
|
}).join("");
|
|
24115
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
24420
|
+
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:#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>${escapeHtml41(title)}</h1><p>Compact pass/fail status for framework widgets, demos, and small customer-facing health badges.</p><p class="status ${escapeHtml41(report.status)}">Overall: ${escapeHtml41(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>`;
|
|
24116
24421
|
};
|
|
24117
24422
|
var createVoiceOpsStatusRoutes = (options) => {
|
|
24118
24423
|
const path = options.path ?? "/api/voice/ops-status";
|
|
24119
|
-
const routes = new
|
|
24424
|
+
const routes = new Elysia41({
|
|
24120
24425
|
name: options.name ?? "absolutejs-voice-ops-status"
|
|
24121
24426
|
});
|
|
24122
24427
|
routes.get(path, async () => summarizeVoiceOpsStatus(options));
|
|
@@ -24549,8 +24854,8 @@ var createVoiceTTSProviderRouter = (options) => {
|
|
|
24549
24854
|
};
|
|
24550
24855
|
};
|
|
24551
24856
|
// src/traceDeliveryRoutes.ts
|
|
24552
|
-
import { Elysia as
|
|
24553
|
-
var
|
|
24857
|
+
import { Elysia as Elysia42 } from "elysia";
|
|
24858
|
+
var escapeHtml42 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
24554
24859
|
var getString16 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
24555
24860
|
var getNumber11 = (value) => {
|
|
24556
24861
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -24631,14 +24936,14 @@ var renderSinkResults2 = (delivery) => {
|
|
|
24631
24936
|
if (entries.length === 0) {
|
|
24632
24937
|
return "<p>No sink delivery attempts recorded yet.</p>";
|
|
24633
24938
|
}
|
|
24634
|
-
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${
|
|
24939
|
+
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${escapeHtml42(sinkId)}</strong>: ${escapeHtml42(result.status)}${result.deliveredTo ? ` to ${escapeHtml42(result.deliveredTo)}` : ""}${result.error ? ` (${escapeHtml42(result.error)})` : ""}</li>`).join("")}</ul>`;
|
|
24635
24940
|
};
|
|
24636
|
-
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${
|
|
24941
|
+
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${escapeHtml42(event.type)} <small>${escapeHtml42(event.id)}</small>${event.sessionId ? ` session=${escapeHtml42(event.sessionId)}` : ""}</li>`).join("")}</ul>`;
|
|
24637
24942
|
var renderVoiceTraceDeliveryHTML = (report, options = {}) => {
|
|
24638
24943
|
const title = options.title ?? "AbsoluteJS Voice Trace Deliveries";
|
|
24639
|
-
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${
|
|
24640
|
-
const rows = report.deliveries.map((delivery) => `<article class="delivery ${
|
|
24641
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
24944
|
+
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${escapeHtml42(options.workerPath ?? "/api/voice-trace-deliveries/drain")}"><button type="submit">Drain trace deliveries</button></form>`;
|
|
24945
|
+
const rows = report.deliveries.map((delivery) => `<article class="delivery ${escapeHtml42(delivery.deliveryStatus)}"><div class="head"><div><span>${escapeHtml42(delivery.deliveryStatus)}</span><h2>${escapeHtml42(delivery.id)}</h2><p>${escapeHtml42(new Date(delivery.createdAt).toLocaleString())}${delivery.deliveredAt ? ` \xB7 delivered ${escapeHtml42(new Date(delivery.deliveredAt).toLocaleString())}` : ""}</p></div><strong>${String(delivery.deliveryAttempts ?? 0)} attempt(s)</strong></div>${delivery.deliveryError ? `<p class="error">${escapeHtml42(delivery.deliveryError)}</p>` : ""}<h3>Sinks</h3>${renderSinkResults2(delivery)}<h3>Events</h3>${renderEventList2(delivery)}</article>`).join("");
|
|
24946
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml42(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>${escapeHtml42(title)}</h1><p>Checked ${escapeHtml42(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>`;
|
|
24642
24947
|
};
|
|
24643
24948
|
var createVoiceTraceDeliveryJSONHandler = (options) => async ({ query }) => buildVoiceTraceDeliveryReport(options, resolveVoiceTraceDeliveryFilter(query, options.filter));
|
|
24644
24949
|
var createVoiceTraceDeliveryHTMLHandler = (options) => async ({ query }) => {
|
|
@@ -24658,7 +24963,7 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
24658
24963
|
const path = options.path ?? "/api/voice-trace-deliveries";
|
|
24659
24964
|
const htmlPath = options.htmlPath === undefined ? "/traces/deliveries" : options.htmlPath;
|
|
24660
24965
|
const workerPath = options.workerPath === undefined ? `${path}/drain` : options.workerPath;
|
|
24661
|
-
const routes = new
|
|
24966
|
+
const routes = new Elysia42({
|
|
24662
24967
|
name: options.name ?? "absolutejs-voice-trace-deliveries"
|
|
24663
24968
|
}).get(path, createVoiceTraceDeliveryJSONHandler(options));
|
|
24664
24969
|
if (htmlPath !== false) {
|
|
@@ -25282,7 +25587,7 @@ var createVoiceMemoryStore = () => {
|
|
|
25282
25587
|
return { get, getOrCreate, list, remove, set };
|
|
25283
25588
|
};
|
|
25284
25589
|
// src/opsWebhook.ts
|
|
25285
|
-
import { Elysia as
|
|
25590
|
+
import { Elysia as Elysia43 } from "elysia";
|
|
25286
25591
|
var toHex6 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
25287
25592
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
25288
25593
|
const encoder = new TextEncoder;
|
|
@@ -25412,7 +25717,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
25412
25717
|
};
|
|
25413
25718
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
25414
25719
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
25415
|
-
return new
|
|
25720
|
+
return new Elysia43().post(path, async ({ body, request, set }) => {
|
|
25416
25721
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
25417
25722
|
if (options.signingSecret) {
|
|
25418
25723
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -26256,6 +26561,7 @@ export {
|
|
|
26256
26561
|
withVoiceOpsTaskId,
|
|
26257
26562
|
withVoiceIntegrationEventId,
|
|
26258
26563
|
voiceTelephonyOutcomeToRouteResult,
|
|
26564
|
+
voiceComplianceRedactionDefaults,
|
|
26259
26565
|
voice,
|
|
26260
26566
|
verifyVoiceTwilioWebhookSignature,
|
|
26261
26567
|
verifyVoiceTelnyxWebhookSignature,
|
|
@@ -26365,6 +26671,8 @@ export {
|
|
|
26365
26671
|
renderVoiceDemoReadyHTML,
|
|
26366
26672
|
renderVoiceDeliverySinkHTML,
|
|
26367
26673
|
renderVoiceDeliveryRuntimeHTML,
|
|
26674
|
+
renderVoiceDataControlMarkdown,
|
|
26675
|
+
renderVoiceDataControlHTML,
|
|
26368
26676
|
renderVoiceCampaignsHTML,
|
|
26369
26677
|
renderVoiceCampaignObservabilityHTML,
|
|
26370
26678
|
renderVoiceCallReviewMarkdown,
|
|
@@ -26422,6 +26730,7 @@ export {
|
|
|
26422
26730
|
deliverVoiceAuditEventsToSinks,
|
|
26423
26731
|
decodeTwilioMulawBase64,
|
|
26424
26732
|
deadLetterVoiceOpsTask,
|
|
26733
|
+
createVoiceZeroRetentionPolicy,
|
|
26425
26734
|
createVoiceZendeskTicketUpdateSink,
|
|
26426
26735
|
createVoiceZendeskTicketSyncSinks,
|
|
26427
26736
|
createVoiceZendeskTicketSink,
|
|
@@ -26609,6 +26918,7 @@ export {
|
|
|
26609
26918
|
createVoiceDeliveryRuntimeRoutes,
|
|
26610
26919
|
createVoiceDeliveryRuntimePresetConfig,
|
|
26611
26920
|
createVoiceDeliveryRuntime,
|
|
26921
|
+
createVoiceDataControlRoutes,
|
|
26612
26922
|
createVoiceCampaignWorkerLoop,
|
|
26613
26923
|
createVoiceCampaignWorker,
|
|
26614
26924
|
createVoiceCampaignTelephonyOutcomeHandler,
|
|
@@ -26690,6 +27000,7 @@ export {
|
|
|
26690
27000
|
buildVoiceDeliverySinkReport,
|
|
26691
27001
|
buildVoiceDeliveryRuntimeReport,
|
|
26692
27002
|
buildVoiceDataRetentionPlan,
|
|
27003
|
+
buildVoiceDataControlReport,
|
|
26693
27004
|
buildVoiceCampaignObservabilityReport,
|
|
26694
27005
|
buildVoiceAuditTrailReport,
|
|
26695
27006
|
buildVoiceAuditExport,
|