@absolutejs/voice 0.0.22-beta.181 → 0.0.22-beta.183
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +22 -0
- package/dist/agentSquadContract.d.ts +7 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +694 -444
- package/dist/operationsRecord.d.ts +102 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6887,6 +6887,47 @@ var resolveVoiceAgentAuditLogger = (audit) => {
|
|
|
6887
6887
|
return "append" in audit ? createVoiceAuditLogger(audit) : audit;
|
|
6888
6888
|
};
|
|
6889
6889
|
var toAuditOutcome = (status) => status === "allowed" ? "success" : status === "blocked" ? "skipped" : "error";
|
|
6890
|
+
var createVoiceAgentSquadState = (input) => {
|
|
6891
|
+
const lastHandoff = input.handoffs.at(-1);
|
|
6892
|
+
return {
|
|
6893
|
+
agentId: input.agentId,
|
|
6894
|
+
handoffCount: input.handoffs.length,
|
|
6895
|
+
handoffs: [...input.handoffs],
|
|
6896
|
+
lastHandoff,
|
|
6897
|
+
previousAgentId: lastHandoff?.status === "allowed" ? lastHandoff.fromAgentId : undefined
|
|
6898
|
+
};
|
|
6899
|
+
};
|
|
6900
|
+
var appendVoiceAgentSquadHandoff = async (input) => {
|
|
6901
|
+
const handoff = {
|
|
6902
|
+
at: Date.now(),
|
|
6903
|
+
fromAgentId: input.fromAgentId,
|
|
6904
|
+
metadata: input.metadata,
|
|
6905
|
+
originalTargetAgentId: input.originalTargetAgentId,
|
|
6906
|
+
reason: input.reason,
|
|
6907
|
+
status: input.status,
|
|
6908
|
+
summary: input.summary,
|
|
6909
|
+
targetAgentId: input.targetAgentId,
|
|
6910
|
+
turnId: input.turn.id
|
|
6911
|
+
};
|
|
6912
|
+
input.handoffs.push(handoff);
|
|
6913
|
+
await appendVoiceAgentTrace({
|
|
6914
|
+
agentId: input.agentId,
|
|
6915
|
+
event: {
|
|
6916
|
+
fromAgentId: handoff.fromAgentId,
|
|
6917
|
+
metadata: handoff.metadata,
|
|
6918
|
+
originalTargetAgentId: handoff.originalTargetAgentId,
|
|
6919
|
+
reason: handoff.reason,
|
|
6920
|
+
status: handoff.status,
|
|
6921
|
+
summary: handoff.summary,
|
|
6922
|
+
targetAgentId: handoff.targetAgentId
|
|
6923
|
+
},
|
|
6924
|
+
session: input.session,
|
|
6925
|
+
trace: input.trace,
|
|
6926
|
+
turn: input.turn,
|
|
6927
|
+
type: "agent.handoff"
|
|
6928
|
+
});
|
|
6929
|
+
return handoff;
|
|
6930
|
+
};
|
|
6890
6931
|
var createVoiceAgentTool = (tool) => tool;
|
|
6891
6932
|
var createVoiceAgent = (options) => {
|
|
6892
6933
|
const toolMap = new Map(options.tools?.map((tool) => [tool.name, tool]) ?? []);
|
|
@@ -7187,6 +7228,7 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7187
7228
|
let agent = agents.get(agentId) ?? defaultAgent;
|
|
7188
7229
|
const messages = input.messages ?? createHistoryMessages(input.session, input.turn);
|
|
7189
7230
|
const toolResults = [];
|
|
7231
|
+
const handoffs = [];
|
|
7190
7232
|
const maxHandoffs = Math.max(0, options.maxHandoffsPerTurn ?? 2);
|
|
7191
7233
|
let result = await agent.run({
|
|
7192
7234
|
...input,
|
|
@@ -7207,26 +7249,26 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7207
7249
|
const targetAgentId = normalizeText3(policy?.targetAgentId) || originalTargetAgentId;
|
|
7208
7250
|
const nextAgent = agents.get(targetAgentId);
|
|
7209
7251
|
const handoffReason = policy?.summary ?? policy?.reason ?? result.handoff.reason;
|
|
7252
|
+
const handoffSummary = policy?.summary ?? result.handoff.reason ?? policy?.reason;
|
|
7210
7253
|
const handoffMetadata = {
|
|
7211
7254
|
...result.handoff.metadata,
|
|
7212
7255
|
...policy?.metadata
|
|
7213
7256
|
};
|
|
7214
7257
|
const metadata = Object.keys(handoffMetadata).length > 0 ? handoffMetadata : undefined;
|
|
7215
7258
|
if (policy?.allow === false) {
|
|
7216
|
-
await
|
|
7259
|
+
await appendVoiceAgentSquadHandoff({
|
|
7217
7260
|
agentId: options.id,
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
status: "blocked",
|
|
7224
|
-
targetAgentId
|
|
7225
|
-
},
|
|
7261
|
+
fromAgentId: agent.id,
|
|
7262
|
+
handoffs,
|
|
7263
|
+
metadata,
|
|
7264
|
+
originalTargetAgentId: originalTargetAgentId === targetAgentId ? undefined : originalTargetAgentId,
|
|
7265
|
+
reason: handoffReason,
|
|
7226
7266
|
session: input.session,
|
|
7267
|
+
status: "blocked",
|
|
7268
|
+
summary: handoffSummary,
|
|
7269
|
+
targetAgentId,
|
|
7227
7270
|
trace: options.trace,
|
|
7228
|
-
turn: input.turn
|
|
7229
|
-
type: "agent.handoff"
|
|
7271
|
+
turn: input.turn
|
|
7230
7272
|
});
|
|
7231
7273
|
await audit?.handoff({
|
|
7232
7274
|
actor: {
|
|
@@ -7247,24 +7289,27 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7247
7289
|
reason: handoffReason ?? `Blocked handoff to ${targetAgentId}`
|
|
7248
7290
|
},
|
|
7249
7291
|
handoff: undefined,
|
|
7292
|
+
squad: createVoiceAgentSquadState({
|
|
7293
|
+
agentId: agent.id,
|
|
7294
|
+
handoffs
|
|
7295
|
+
}),
|
|
7250
7296
|
toolResults
|
|
7251
7297
|
};
|
|
7252
7298
|
}
|
|
7253
7299
|
if (!nextAgent) {
|
|
7254
|
-
await
|
|
7300
|
+
await appendVoiceAgentSquadHandoff({
|
|
7255
7301
|
agentId: options.id,
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
status: "unknown-target",
|
|
7262
|
-
targetAgentId
|
|
7263
|
-
},
|
|
7302
|
+
fromAgentId: agent.id,
|
|
7303
|
+
handoffs,
|
|
7304
|
+
metadata,
|
|
7305
|
+
originalTargetAgentId: originalTargetAgentId === targetAgentId ? undefined : originalTargetAgentId,
|
|
7306
|
+
reason: handoffReason,
|
|
7264
7307
|
session: input.session,
|
|
7308
|
+
status: "unknown-target",
|
|
7309
|
+
summary: handoffSummary,
|
|
7310
|
+
targetAgentId,
|
|
7265
7311
|
trace: options.trace,
|
|
7266
|
-
turn: input.turn
|
|
7267
|
-
type: "agent.handoff"
|
|
7312
|
+
turn: input.turn
|
|
7268
7313
|
});
|
|
7269
7314
|
await audit?.handoff({
|
|
7270
7315
|
actor: {
|
|
@@ -7285,31 +7330,36 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7285
7330
|
reason: `Unknown handoff target: ${targetAgentId}`
|
|
7286
7331
|
},
|
|
7287
7332
|
handoff: undefined,
|
|
7333
|
+
squad: createVoiceAgentSquadState({
|
|
7334
|
+
agentId: agent.id,
|
|
7335
|
+
handoffs
|
|
7336
|
+
}),
|
|
7288
7337
|
toolResults
|
|
7289
7338
|
};
|
|
7290
7339
|
}
|
|
7291
7340
|
await options.onHandoff?.({
|
|
7292
7341
|
context: input.context,
|
|
7293
7342
|
fromAgentId: agent.id,
|
|
7343
|
+
metadata,
|
|
7294
7344
|
reason: handoffReason,
|
|
7295
7345
|
session: input.session,
|
|
7346
|
+
summary: handoffSummary,
|
|
7296
7347
|
targetAgentId: nextAgent.id,
|
|
7297
7348
|
turn: input.turn
|
|
7298
7349
|
});
|
|
7299
|
-
await
|
|
7350
|
+
await appendVoiceAgentSquadHandoff({
|
|
7300
7351
|
agentId: options.id,
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
status: "allowed",
|
|
7307
|
-
targetAgentId: nextAgent.id
|
|
7308
|
-
},
|
|
7352
|
+
fromAgentId: agent.id,
|
|
7353
|
+
handoffs,
|
|
7354
|
+
metadata,
|
|
7355
|
+
originalTargetAgentId: originalTargetAgentId === nextAgent.id ? undefined : originalTargetAgentId,
|
|
7356
|
+
reason: handoffReason,
|
|
7309
7357
|
session: input.session,
|
|
7358
|
+
status: "allowed",
|
|
7359
|
+
summary: handoffSummary,
|
|
7360
|
+
targetAgentId: nextAgent.id,
|
|
7310
7361
|
trace: options.trace,
|
|
7311
|
-
turn: input.turn
|
|
7312
|
-
type: "agent.handoff"
|
|
7362
|
+
turn: input.turn
|
|
7313
7363
|
});
|
|
7314
7364
|
await audit?.handoff({
|
|
7315
7365
|
actor: {
|
|
@@ -7324,7 +7374,7 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7324
7374
|
toAgentId: nextAgent.id
|
|
7325
7375
|
});
|
|
7326
7376
|
messages.push({
|
|
7327
|
-
content: handoffReason ?? `Handoff to ${nextAgent.id}`,
|
|
7377
|
+
content: handoffSummary ?? handoffReason ?? `Handoff to ${nextAgent.id}`,
|
|
7328
7378
|
metadata,
|
|
7329
7379
|
name: nextAgent.id,
|
|
7330
7380
|
role: "system"
|
|
@@ -7338,19 +7388,18 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7338
7388
|
toolResults.push(...result.toolResults);
|
|
7339
7389
|
}
|
|
7340
7390
|
if (result.handoff) {
|
|
7341
|
-
await
|
|
7391
|
+
await appendVoiceAgentSquadHandoff({
|
|
7342
7392
|
agentId: options.id,
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
status: "max-exceeded",
|
|
7348
|
-
targetAgentId: result.handoff.targetAgentId
|
|
7349
|
-
},
|
|
7393
|
+
fromAgentId: agent.id,
|
|
7394
|
+
handoffs,
|
|
7395
|
+
metadata: result.handoff.metadata,
|
|
7396
|
+
reason: result.handoff.reason,
|
|
7350
7397
|
session: input.session,
|
|
7398
|
+
status: "max-exceeded",
|
|
7399
|
+
summary: result.handoff.reason,
|
|
7400
|
+
targetAgentId: result.handoff.targetAgentId,
|
|
7351
7401
|
trace: options.trace,
|
|
7352
|
-
turn: input.turn
|
|
7353
|
-
type: "agent.handoff"
|
|
7402
|
+
turn: input.turn
|
|
7354
7403
|
});
|
|
7355
7404
|
await audit?.handoff({
|
|
7356
7405
|
actor: {
|
|
@@ -7371,12 +7420,20 @@ var createVoiceAgentSquad = (options) => {
|
|
|
7371
7420
|
reason: `Max handoffs exceeded: ${maxHandoffs}`
|
|
7372
7421
|
},
|
|
7373
7422
|
handoff: undefined,
|
|
7423
|
+
squad: createVoiceAgentSquadState({
|
|
7424
|
+
agentId: agent.id,
|
|
7425
|
+
handoffs
|
|
7426
|
+
}),
|
|
7374
7427
|
toolResults
|
|
7375
7428
|
};
|
|
7376
7429
|
}
|
|
7377
7430
|
return {
|
|
7378
7431
|
...result,
|
|
7379
7432
|
agentId,
|
|
7433
|
+
squad: createVoiceAgentSquadState({
|
|
7434
|
+
agentId,
|
|
7435
|
+
handoffs
|
|
7436
|
+
}),
|
|
7380
7437
|
toolResults
|
|
7381
7438
|
};
|
|
7382
7439
|
};
|
|
@@ -14433,9 +14490,16 @@ var getPayloadString2 = (event, key) => {
|
|
|
14433
14490
|
const value = event.payload[key];
|
|
14434
14491
|
return typeof value === "string" ? value : undefined;
|
|
14435
14492
|
};
|
|
14493
|
+
var getPayloadRecord = (event, key) => {
|
|
14494
|
+
const value = event.payload[key];
|
|
14495
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
14496
|
+
};
|
|
14436
14497
|
var toHandoffExpectation = (event) => ({
|
|
14437
14498
|
fromAgentId: getPayloadString2(event, "fromAgentId"),
|
|
14499
|
+
metadata: getPayloadRecord(event, "metadata"),
|
|
14500
|
+
reason: getPayloadString2(event, "reason"),
|
|
14438
14501
|
status: getPayloadString2(event, "status"),
|
|
14502
|
+
summary: getPayloadString2(event, "summary"),
|
|
14439
14503
|
targetAgentId: getPayloadString2(event, "targetAgentId")
|
|
14440
14504
|
});
|
|
14441
14505
|
var createContractApi = (session) => ({
|
|
@@ -14465,6 +14529,28 @@ var appendIssue = (issues, issue, turnId) => {
|
|
|
14465
14529
|
turnId: issue.turnId ?? turnId
|
|
14466
14530
|
});
|
|
14467
14531
|
};
|
|
14532
|
+
var assertIncludes = (input) => {
|
|
14533
|
+
const actual = normalizeIncludes(input.actual?.join(" ") ?? "");
|
|
14534
|
+
for (const expected of input.expected ?? []) {
|
|
14535
|
+
if (!actual.includes(normalizeIncludes(expected))) {
|
|
14536
|
+
appendIssue(input.issues, {
|
|
14537
|
+
code: input.code,
|
|
14538
|
+
message: `Expected handoff ${input.handoffIndex + 1} ${input.label} to include: ${expected}`
|
|
14539
|
+
}, input.turnId);
|
|
14540
|
+
}
|
|
14541
|
+
}
|
|
14542
|
+
};
|
|
14543
|
+
var assertMetadata = (input) => {
|
|
14544
|
+
for (const [key, expectedValue] of Object.entries(input.expected ?? {})) {
|
|
14545
|
+
const actualValue = input.actual?.[key];
|
|
14546
|
+
if (actualValue !== expectedValue) {
|
|
14547
|
+
appendIssue(input.issues, {
|
|
14548
|
+
code: "agent_squad.handoff_metadata_mismatch",
|
|
14549
|
+
message: `Expected handoff ${input.handoffIndex + 1} metadata ${key} ${String(expectedValue)}, saw ${String(actualValue ?? "none")}.`
|
|
14550
|
+
}, input.turnId);
|
|
14551
|
+
}
|
|
14552
|
+
}
|
|
14553
|
+
};
|
|
14468
14554
|
var runVoiceAgentSquadContract = async (options) => {
|
|
14469
14555
|
const session = options.session ?? createVoiceSessionRecord(`agent-squad-contract-${options.contract.id}`, options.contract.scenarioId ?? options.contract.id);
|
|
14470
14556
|
const api = options.api ?? createContractApi(session);
|
|
@@ -14531,6 +14617,39 @@ var runVoiceAgentSquadContract = async (options) => {
|
|
|
14531
14617
|
}, turn.id);
|
|
14532
14618
|
}
|
|
14533
14619
|
}
|
|
14620
|
+
for (const key of ["reason", "summary"]) {
|
|
14621
|
+
if (expectedHandoff[key] && actual[key] !== expectedHandoff[key]) {
|
|
14622
|
+
appendIssue(turnIssues, {
|
|
14623
|
+
code: `agent_squad.handoff_${key}_mismatch`,
|
|
14624
|
+
message: `Expected handoff ${handoffIndex + 1} ${key} ${expectedHandoff[key]}, saw ${actual[key] ?? "none"}.`
|
|
14625
|
+
}, turn.id);
|
|
14626
|
+
}
|
|
14627
|
+
}
|
|
14628
|
+
assertIncludes({
|
|
14629
|
+
actual: actual.reason ? [actual.reason] : undefined,
|
|
14630
|
+
code: "agent_squad.handoff_reason_missing",
|
|
14631
|
+
expected: expectedHandoff.reasonIncludes,
|
|
14632
|
+
handoffIndex,
|
|
14633
|
+
issues: turnIssues,
|
|
14634
|
+
label: "reason",
|
|
14635
|
+
turnId: turn.id
|
|
14636
|
+
});
|
|
14637
|
+
assertIncludes({
|
|
14638
|
+
actual: actual.summary ? [actual.summary] : undefined,
|
|
14639
|
+
code: "agent_squad.handoff_summary_missing",
|
|
14640
|
+
expected: expectedHandoff.summaryIncludes,
|
|
14641
|
+
handoffIndex,
|
|
14642
|
+
issues: turnIssues,
|
|
14643
|
+
label: "summary",
|
|
14644
|
+
turnId: turn.id
|
|
14645
|
+
});
|
|
14646
|
+
assertMetadata({
|
|
14647
|
+
actual: actual.metadata,
|
|
14648
|
+
expected: expectedHandoff.metadata,
|
|
14649
|
+
handoffIndex,
|
|
14650
|
+
issues: turnIssues,
|
|
14651
|
+
turnId: turn.id
|
|
14652
|
+
});
|
|
14534
14653
|
}
|
|
14535
14654
|
for (const issue of expected?.result?.({
|
|
14536
14655
|
result: result.result,
|
|
@@ -22068,132 +22187,517 @@ var createVoiceOpsConsoleRoutes = (options) => {
|
|
|
22068
22187
|
routes.get(`${path}/json`, async () => getReport());
|
|
22069
22188
|
return routes;
|
|
22070
22189
|
};
|
|
22071
|
-
// src/
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
|
|
22076
|
-
|
|
22077
|
-
|
|
22078
|
-
|
|
22079
|
-
|
|
22080
|
-
|
|
22081
|
-
|
|
22082
|
-
|
|
22083
|
-
|
|
22084
|
-
|
|
22085
|
-
{
|
|
22086
|
-
description: "Provider routing, fallback, and resilience controls.",
|
|
22087
|
-
href: "/resilience",
|
|
22088
|
-
label: "Resilience"
|
|
22089
|
-
},
|
|
22090
|
-
{
|
|
22091
|
-
description: "One JSON/HTML production readiness rollup.",
|
|
22092
|
-
href: "/production-readiness",
|
|
22093
|
-
label: "Production Readiness",
|
|
22094
|
-
statusHref: "/api/production-readiness"
|
|
22095
|
-
},
|
|
22096
|
-
{
|
|
22097
|
-
description: "Recent sessions and replay links.",
|
|
22098
|
-
href: "/sessions",
|
|
22099
|
-
label: "Sessions"
|
|
22100
|
-
},
|
|
22101
|
-
{
|
|
22102
|
-
description: "Trace-backed phone-agent production smoke proof.",
|
|
22103
|
-
href: "/voice/phone/smoke-contract",
|
|
22104
|
-
label: "Phone Smoke",
|
|
22105
|
-
statusHref: "/api/voice/phone/smoke-contract"
|
|
22106
|
-
}
|
|
22107
|
-
];
|
|
22108
|
-
var countStatus = (statuses) => ({
|
|
22109
|
-
failed: statuses.filter((status) => status === "fail").length,
|
|
22110
|
-
passed: statuses.filter((status) => status === "pass").length,
|
|
22111
|
-
total: statuses.length
|
|
22112
|
-
});
|
|
22113
|
-
var summarizeVoiceOpsStatus = async (options) => {
|
|
22114
|
-
const include = options.include;
|
|
22115
|
-
const shouldInclude = (surface) => include?.[surface] !== false;
|
|
22116
|
-
const evals = options.evals === false ? undefined : options.evals;
|
|
22117
|
-
const events = filterVoiceTraceEvents(await options.store.list());
|
|
22118
|
-
const [quality, workflows, providers, sessions, handoffs, deliverySinks] = await Promise.all([
|
|
22119
|
-
options.quality === false || !shouldInclude("quality") ? undefined : evaluateVoiceQuality({
|
|
22120
|
-
events,
|
|
22121
|
-
thresholds: options.quality?.thresholds
|
|
22122
|
-
}),
|
|
22123
|
-
!evals || !shouldInclude("workflows") ? undefined : (async () => {
|
|
22124
|
-
const fixtureReport = await runVoiceScenarioFixtureEvals({
|
|
22125
|
-
fixtures: evals.fixtures,
|
|
22126
|
-
fixtureStore: evals.fixtureStore,
|
|
22127
|
-
scenarios: evals.scenarios
|
|
22128
|
-
});
|
|
22129
|
-
if ((options.preferFixtureWorkflows ?? true) && fixtureReport.total > 0) {
|
|
22130
|
-
return {
|
|
22131
|
-
failed: fixtureReport.failed,
|
|
22132
|
-
source: "fixtures",
|
|
22133
|
-
status: fixtureReport.status,
|
|
22134
|
-
total: fixtureReport.total
|
|
22135
|
-
};
|
|
22136
|
-
}
|
|
22137
|
-
const liveReport = await runVoiceScenarioEvals({
|
|
22138
|
-
events,
|
|
22139
|
-
scenarios: evals.scenarios
|
|
22140
|
-
});
|
|
22141
|
-
return {
|
|
22142
|
-
failed: liveReport.failed,
|
|
22143
|
-
source: "live",
|
|
22144
|
-
status: liveReport.status,
|
|
22145
|
-
total: liveReport.total
|
|
22146
|
-
};
|
|
22147
|
-
})(),
|
|
22148
|
-
!shouldInclude("providers") ? undefined : Promise.all([
|
|
22149
|
-
summarizeVoiceProviderHealth({
|
|
22150
|
-
events,
|
|
22151
|
-
providers: options.llmProviders
|
|
22152
|
-
}),
|
|
22153
|
-
summarizeVoiceProviderHealth({
|
|
22154
|
-
events: events.filter((event) => event.payload.kind === "stt"),
|
|
22155
|
-
providers: options.sttProviders
|
|
22156
|
-
}),
|
|
22157
|
-
summarizeVoiceProviderHealth({
|
|
22158
|
-
events: events.filter((event) => event.payload.kind === "tts"),
|
|
22159
|
-
providers: options.ttsProviders
|
|
22160
|
-
})
|
|
22161
|
-
]).then((groups) => groups.flat()),
|
|
22162
|
-
!shouldInclude("sessions") ? undefined : summarizeVoiceSessions({
|
|
22163
|
-
events
|
|
22164
|
-
}),
|
|
22165
|
-
!shouldInclude("handoffs") ? undefined : summarizeVoiceHandoffHealth({
|
|
22166
|
-
events
|
|
22167
|
-
}),
|
|
22168
|
-
!options.deliverySinks || !shouldInclude("deliverySinks") ? undefined : buildVoiceDeliverySinkReport(options.deliverySinks)
|
|
22169
|
-
]);
|
|
22170
|
-
const providerRecovery = shouldInclude("providerRecovery") ? summarizeVoiceProviderFallbackRecovery(events) : undefined;
|
|
22171
|
-
const surfaces = {};
|
|
22172
|
-
const statuses = [];
|
|
22173
|
-
if (quality) {
|
|
22174
|
-
surfaces.quality = { status: quality.status };
|
|
22175
|
-
statuses.push(quality.status);
|
|
22176
|
-
}
|
|
22177
|
-
if (workflows) {
|
|
22178
|
-
surfaces.workflows = workflows;
|
|
22179
|
-
statuses.push(workflows.status);
|
|
22190
|
+
// src/operationsRecord.ts
|
|
22191
|
+
import { Elysia as Elysia37 } from "elysia";
|
|
22192
|
+
|
|
22193
|
+
// src/traceTimeline.ts
|
|
22194
|
+
import { Elysia as Elysia36 } from "elysia";
|
|
22195
|
+
var escapeHtml38 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22196
|
+
var getString12 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
22197
|
+
var getNumber7 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22198
|
+
var firstString3 = (payload, keys) => {
|
|
22199
|
+
for (const key of keys) {
|
|
22200
|
+
const value = getString12(payload[key]);
|
|
22201
|
+
if (value) {
|
|
22202
|
+
return value;
|
|
22203
|
+
}
|
|
22180
22204
|
}
|
|
22181
|
-
|
|
22182
|
-
|
|
22183
|
-
|
|
22184
|
-
|
|
22185
|
-
|
|
22186
|
-
|
|
22187
|
-
|
|
22188
|
-
}
|
|
22189
|
-
statuses.push(status);
|
|
22205
|
+
return;
|
|
22206
|
+
};
|
|
22207
|
+
var firstNumber3 = (payload, keys) => {
|
|
22208
|
+
for (const key of keys) {
|
|
22209
|
+
const value = getNumber7(payload[key]);
|
|
22210
|
+
if (value !== undefined) {
|
|
22211
|
+
return value;
|
|
22212
|
+
}
|
|
22190
22213
|
}
|
|
22191
|
-
|
|
22192
|
-
|
|
22193
|
-
|
|
22214
|
+
return;
|
|
22215
|
+
};
|
|
22216
|
+
var eventProvider = (event) => firstString3(event.payload, [
|
|
22217
|
+
"provider",
|
|
22218
|
+
"selectedProvider",
|
|
22219
|
+
"fallbackProvider",
|
|
22220
|
+
"variantId"
|
|
22221
|
+
]);
|
|
22222
|
+
var eventStatus = (event) => firstString3(event.payload, [
|
|
22223
|
+
"providerStatus",
|
|
22224
|
+
"status",
|
|
22225
|
+
"disposition",
|
|
22226
|
+
"type",
|
|
22227
|
+
"reason"
|
|
22228
|
+
]);
|
|
22229
|
+
var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
|
|
22230
|
+
var timelineLabel = (event) => {
|
|
22231
|
+
switch (event.type) {
|
|
22232
|
+
case "call.lifecycle":
|
|
22233
|
+
return `Call ${eventStatus(event) ?? "lifecycle"}`;
|
|
22234
|
+
case "turn.transcript":
|
|
22235
|
+
return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
|
|
22236
|
+
case "turn.committed":
|
|
22237
|
+
return `Committed turn${getString12(event.payload.reason) ? ` (${getString12(event.payload.reason)})` : ""}`;
|
|
22238
|
+
case "turn.assistant":
|
|
22239
|
+
return "Assistant reply";
|
|
22240
|
+
case "agent.model":
|
|
22241
|
+
return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22242
|
+
case "agent.tool":
|
|
22243
|
+
return `Tool ${getString12(event.payload.toolName) ?? "call"}`;
|
|
22244
|
+
case "agent.handoff":
|
|
22245
|
+
return `Agent handoff${getString12(event.payload.targetAgentId) ? ` to ${getString12(event.payload.targetAgentId)}` : ""}`;
|
|
22246
|
+
case "assistant.run":
|
|
22247
|
+
return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22248
|
+
case "assistant.guardrail":
|
|
22249
|
+
return `Guardrail ${eventStatus(event) ?? "check"}`;
|
|
22250
|
+
case "call.handoff":
|
|
22251
|
+
return `Call handoff ${eventStatus(event) ?? ""}`.trim();
|
|
22252
|
+
case "client.live_latency":
|
|
22253
|
+
return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
|
|
22254
|
+
case "session.error":
|
|
22255
|
+
return `Error${getString12(event.payload.error) ? `: ${getString12(event.payload.error)}` : ""}`;
|
|
22256
|
+
case "turn.cost":
|
|
22257
|
+
return "Cost telemetry";
|
|
22258
|
+
case "turn_latency.stage":
|
|
22259
|
+
return `Latency ${getString12(event.payload.stage) ?? "stage"}`;
|
|
22260
|
+
case "workflow.contract":
|
|
22261
|
+
return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
|
|
22262
|
+
default:
|
|
22263
|
+
return event.type;
|
|
22194
22264
|
}
|
|
22195
|
-
|
|
22196
|
-
|
|
22265
|
+
};
|
|
22266
|
+
var summarizeProviders = (events) => {
|
|
22267
|
+
const entries = new Map;
|
|
22268
|
+
const getEntry = (provider) => {
|
|
22269
|
+
const existing = entries.get(provider);
|
|
22270
|
+
if (existing) {
|
|
22271
|
+
return existing;
|
|
22272
|
+
}
|
|
22273
|
+
const entry = {
|
|
22274
|
+
elapsed: [],
|
|
22275
|
+
errorCount: 0,
|
|
22276
|
+
eventCount: 0,
|
|
22277
|
+
fallbackCount: 0,
|
|
22278
|
+
successCount: 0,
|
|
22279
|
+
timeoutCount: 0
|
|
22280
|
+
};
|
|
22281
|
+
entries.set(provider, entry);
|
|
22282
|
+
return entry;
|
|
22283
|
+
};
|
|
22284
|
+
for (const event of events) {
|
|
22285
|
+
const provider = eventProvider(event);
|
|
22286
|
+
if (!provider) {
|
|
22287
|
+
continue;
|
|
22288
|
+
}
|
|
22289
|
+
const entry = getEntry(provider);
|
|
22290
|
+
const status = eventStatus(event);
|
|
22291
|
+
const elapsedMs = eventElapsedMs(event);
|
|
22292
|
+
entry.eventCount += 1;
|
|
22293
|
+
if (elapsedMs !== undefined) {
|
|
22294
|
+
entry.elapsed.push(elapsedMs);
|
|
22295
|
+
}
|
|
22296
|
+
if (status === "success") {
|
|
22297
|
+
entry.successCount += 1;
|
|
22298
|
+
}
|
|
22299
|
+
if (status === "fallback") {
|
|
22300
|
+
entry.fallbackCount += 1;
|
|
22301
|
+
}
|
|
22302
|
+
if (status === "error") {
|
|
22303
|
+
entry.errorCount += 1;
|
|
22304
|
+
}
|
|
22305
|
+
if (status === "timeout") {
|
|
22306
|
+
entry.timeoutCount += 1;
|
|
22307
|
+
}
|
|
22308
|
+
}
|
|
22309
|
+
return [...entries.entries()].map(([provider, entry]) => ({
|
|
22310
|
+
averageElapsedMs: entry.elapsed.length > 0 ? Math.round(entry.elapsed.reduce((total, value) => total + value, 0) / entry.elapsed.length) : undefined,
|
|
22311
|
+
errorCount: entry.errorCount,
|
|
22312
|
+
eventCount: entry.eventCount,
|
|
22313
|
+
fallbackCount: entry.fallbackCount,
|
|
22314
|
+
maxElapsedMs: entry.elapsed.length > 0 ? Math.max(...entry.elapsed) : undefined,
|
|
22315
|
+
provider,
|
|
22316
|
+
successCount: entry.successCount,
|
|
22317
|
+
timeoutCount: entry.timeoutCount
|
|
22318
|
+
})).sort((left, right) => right.eventCount - left.eventCount);
|
|
22319
|
+
};
|
|
22320
|
+
var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
22321
|
+
const source = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
22322
|
+
const grouped = new Map;
|
|
22323
|
+
for (const event of filterVoiceTraceEvents(source)) {
|
|
22324
|
+
grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
|
|
22325
|
+
}
|
|
22326
|
+
const sessions = [...grouped.entries()].map(([sessionId, sessionEvents]) => {
|
|
22327
|
+
const sorted = filterVoiceTraceEvents(sessionEvents);
|
|
22328
|
+
const summary = summarizeVoiceTrace(sorted);
|
|
22329
|
+
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
22330
|
+
const startedAt = summary.startedAt ?? sorted[0]?.at ?? 0;
|
|
22331
|
+
const status = summary.failed ? "failed" : evaluation.issues.length > 0 ? "warning" : "healthy";
|
|
22332
|
+
return {
|
|
22333
|
+
endedAt: summary.endedAt,
|
|
22334
|
+
evaluation,
|
|
22335
|
+
events: sorted.map((event) => ({
|
|
22336
|
+
at: event.at,
|
|
22337
|
+
elapsedMs: eventElapsedMs(event),
|
|
22338
|
+
id: event.id,
|
|
22339
|
+
label: timelineLabel(event),
|
|
22340
|
+
offsetMs: Math.max(0, event.at - startedAt),
|
|
22341
|
+
provider: eventProvider(event),
|
|
22342
|
+
status: eventStatus(event),
|
|
22343
|
+
turnId: event.turnId,
|
|
22344
|
+
type: event.type
|
|
22345
|
+
})),
|
|
22346
|
+
lastEventAt: sorted.at(-1)?.at,
|
|
22347
|
+
providers: summarizeProviders(sorted),
|
|
22348
|
+
sessionId,
|
|
22349
|
+
startedAt: summary.startedAt,
|
|
22350
|
+
status,
|
|
22351
|
+
summary
|
|
22352
|
+
};
|
|
22353
|
+
}).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0)).slice(0, options.limit ?? 50);
|
|
22354
|
+
return {
|
|
22355
|
+
checkedAt: Date.now(),
|
|
22356
|
+
failed: sessions.filter((session) => session.status === "failed").length,
|
|
22357
|
+
sessions,
|
|
22358
|
+
total: sessions.length,
|
|
22359
|
+
warnings: sessions.filter((session) => session.status === "warning").length
|
|
22360
|
+
};
|
|
22361
|
+
};
|
|
22362
|
+
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
22363
|
+
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${escapeHtml38(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs3(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs3(provider.maxElapsedMs)}</dd></div><div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div><div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div><div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div></dl></article>`).join("")}</div>`;
|
|
22364
|
+
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
22365
|
+
const events = session.events.map((event) => `<tr class="${escapeHtml38(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml38(event.type)}</td><td>${escapeHtml38(event.label)}</td><td>${escapeHtml38(event.provider ?? "")}</td><td>${escapeHtml38(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
22366
|
+
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml38(issue.severity)}">${escapeHtml38(issue.code)}: ${escapeHtml38(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
22367
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml38(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml38(session.sessionId)}</h1><p class="status ${escapeHtml38(session.status)}">${escapeHtml38(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
|
|
22368
|
+
};
|
|
22369
|
+
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml38(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml38(session.sessionId)}</a></td><td>${escapeHtml38(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml38(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
22370
|
+
var timelineCSS = "body{background:#0f1318;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}a{color:#fbbf24}.eyebrow{color:#fbbf24;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.5rem);line-height:.92;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.metrics,.providers{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));margin:20px 0}.metrics article,.providers article{background:#181f27;border:1px solid #2b3642;border-radius:20px;padding:16px}.metrics span,dt,.muted{color:#a8b0b8}.metrics strong{display:block;font-size:2rem}dl{display:grid;gap:8px;grid-template-columns:repeat(2,minmax(0,1fr));margin:12px 0 0}dd{font-weight:800;margin:4px 0 0}table{background:#181f27;border-collapse:collapse;border-radius:18px;overflow:hidden;width:100%}td,th{border-bottom:1px solid #2b3642;padding:12px;text-align:left}section{margin-top:28px}@media(max-width:760px){main{padding:20px}table{font-size:.9rem}}";
|
|
22371
|
+
var renderVoiceTraceTimelineHTML = (report, options = {}) => {
|
|
22372
|
+
const snippet = escapeHtml38(`const traceStore = createVoiceTraceSinkStore({
|
|
22373
|
+
store: runtimeStorage.traces,
|
|
22374
|
+
sinks: [
|
|
22375
|
+
createVoiceTraceHTTPSink({
|
|
22376
|
+
endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
|
|
22377
|
+
})
|
|
22378
|
+
]
|
|
22379
|
+
});
|
|
22380
|
+
|
|
22381
|
+
app.use(
|
|
22382
|
+
createVoiceTraceTimelineRoutes({
|
|
22383
|
+
htmlPath: '/traces',
|
|
22384
|
+
path: '/api/voice-traces',
|
|
22385
|
+
redact: {
|
|
22386
|
+
keys: ['authorization', 'apiKey', 'token']
|
|
22387
|
+
},
|
|
22388
|
+
store: traceStore
|
|
22389
|
+
})
|
|
22390
|
+
);
|
|
22391
|
+
|
|
22392
|
+
app.use(
|
|
22393
|
+
createVoiceProductionReadinessRoutes({
|
|
22394
|
+
store: traceStore,
|
|
22395
|
+
traceDeliveries: runtimeStorage.traceDeliveries
|
|
22396
|
+
})
|
|
22397
|
+
);`);
|
|
22398
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml38(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml38(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
|
|
22399
|
+
};
|
|
22400
|
+
var createVoiceTraceTimelineRoutes = (options) => {
|
|
22401
|
+
const path = options.path ?? "/api/voice-traces";
|
|
22402
|
+
const htmlPath = options.htmlPath ?? "/traces";
|
|
22403
|
+
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
22404
|
+
const routes = new Elysia36({
|
|
22405
|
+
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
22406
|
+
});
|
|
22407
|
+
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
22408
|
+
evaluation: options.evaluation,
|
|
22409
|
+
limit: options.limit,
|
|
22410
|
+
redact: options.redact
|
|
22411
|
+
});
|
|
22412
|
+
const findSession = async (sessionId) => {
|
|
22413
|
+
const report = summarizeVoiceTraceTimeline(await options.store.list({ sessionId }), {
|
|
22414
|
+
evaluation: options.evaluation,
|
|
22415
|
+
limit: 1,
|
|
22416
|
+
redact: options.redact
|
|
22417
|
+
});
|
|
22418
|
+
return report.sessions[0];
|
|
22419
|
+
};
|
|
22420
|
+
routes.get(path, async () => Response.json(await buildReport()));
|
|
22421
|
+
routes.get(`${path}/:sessionId`, async ({ params }) => {
|
|
22422
|
+
const session = await findSession(params.sessionId);
|
|
22423
|
+
return session ? Response.json(session) : Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
22424
|
+
});
|
|
22425
|
+
routes.get(htmlPath, async () => {
|
|
22426
|
+
const report = await buildReport();
|
|
22427
|
+
const body = await (options.render ?? ((input) => renderVoiceTraceTimelineHTML(input, { title })))(report);
|
|
22428
|
+
return new Response(body, {
|
|
22429
|
+
headers: {
|
|
22430
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22431
|
+
...options.headers
|
|
22432
|
+
}
|
|
22433
|
+
});
|
|
22434
|
+
});
|
|
22435
|
+
routes.get(`${htmlPath}/:sessionId`, async ({ params }) => {
|
|
22436
|
+
const session = await findSession(params.sessionId);
|
|
22437
|
+
if (!session) {
|
|
22438
|
+
return Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
22439
|
+
}
|
|
22440
|
+
const body = await (options.renderSession ?? ((input) => renderVoiceTraceTimelineSessionHTML(input, { title })))(session);
|
|
22441
|
+
return new Response(body, {
|
|
22442
|
+
headers: {
|
|
22443
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22444
|
+
...options.headers
|
|
22445
|
+
}
|
|
22446
|
+
});
|
|
22447
|
+
});
|
|
22448
|
+
return routes;
|
|
22449
|
+
};
|
|
22450
|
+
|
|
22451
|
+
// src/operationsRecord.ts
|
|
22452
|
+
var getString13 = (value) => typeof value === "string" ? value : undefined;
|
|
22453
|
+
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22454
|
+
var countOutcome = (events, outcome) => events.filter((event) => event.outcome === outcome).length;
|
|
22455
|
+
var toHandoff = (event) => ({
|
|
22456
|
+
at: event.at,
|
|
22457
|
+
fromAgentId: getString13(event.payload.fromAgentId),
|
|
22458
|
+
metadata: event.payload.metadata && typeof event.payload.metadata === "object" && !Array.isArray(event.payload.metadata) ? event.payload.metadata : undefined,
|
|
22459
|
+
reason: getString13(event.payload.reason),
|
|
22460
|
+
status: getString13(event.payload.status),
|
|
22461
|
+
summary: getString13(event.payload.summary),
|
|
22462
|
+
targetAgentId: getString13(event.payload.targetAgentId),
|
|
22463
|
+
turnId: event.turnId
|
|
22464
|
+
});
|
|
22465
|
+
var toTool = (event) => ({
|
|
22466
|
+
at: event.at,
|
|
22467
|
+
elapsedMs: getNumber8(event.payload.elapsedMs),
|
|
22468
|
+
error: getString13(event.payload.error),
|
|
22469
|
+
status: getString13(event.payload.status),
|
|
22470
|
+
toolCallId: getString13(event.payload.toolCallId),
|
|
22471
|
+
toolName: getString13(event.payload.toolName),
|
|
22472
|
+
turnId: event.turnId
|
|
22473
|
+
});
|
|
22474
|
+
var resolveOutcome4 = (events) => {
|
|
22475
|
+
const agentResults = events.filter((event) => event.type === "agent.result");
|
|
22476
|
+
return {
|
|
22477
|
+
assistantReplies: events.filter((event) => event.type === "turn.assistant").length,
|
|
22478
|
+
complete: agentResults.some((event) => event.payload.complete === true),
|
|
22479
|
+
escalated: agentResults.some((event) => event.payload.escalated === true),
|
|
22480
|
+
noAnswer: agentResults.some((event) => event.payload.noAnswer === true),
|
|
22481
|
+
transferred: agentResults.some((event) => event.payload.transferred === true),
|
|
22482
|
+
voicemail: agentResults.some((event) => event.payload.voicemail === true)
|
|
22483
|
+
};
|
|
22484
|
+
};
|
|
22485
|
+
var buildVoiceOperationsRecord = async (options) => {
|
|
22486
|
+
const sourceEvents = options.events ?? await options.store?.list({ sessionId: options.sessionId }) ?? [];
|
|
22487
|
+
const traceEvents = filterVoiceTraceEvents(sourceEvents, {
|
|
22488
|
+
sessionId: options.sessionId
|
|
22489
|
+
});
|
|
22490
|
+
const timelineReport = summarizeVoiceTraceTimeline(traceEvents, {
|
|
22491
|
+
evaluation: options.evaluation,
|
|
22492
|
+
limit: 1,
|
|
22493
|
+
redact: options.redact
|
|
22494
|
+
});
|
|
22495
|
+
const timelineSession = timelineReport.sessions[0];
|
|
22496
|
+
const replay = await summarizeVoiceSessionReplay({
|
|
22497
|
+
evaluation: options.evaluation,
|
|
22498
|
+
events: traceEvents,
|
|
22499
|
+
redact: options.redact,
|
|
22500
|
+
sessionId: options.sessionId
|
|
22501
|
+
});
|
|
22502
|
+
const auditEvents = options.audit ? filterVoiceAuditEvents(await options.audit.list({ sessionId: options.sessionId })) : undefined;
|
|
22503
|
+
return {
|
|
22504
|
+
audit: auditEvents ? {
|
|
22505
|
+
error: countOutcome(auditEvents, "error"),
|
|
22506
|
+
events: auditEvents,
|
|
22507
|
+
skipped: countOutcome(auditEvents, "skipped"),
|
|
22508
|
+
success: countOutcome(auditEvents, "success"),
|
|
22509
|
+
total: auditEvents.length
|
|
22510
|
+
} : undefined,
|
|
22511
|
+
checkedAt: Date.now(),
|
|
22512
|
+
handoffs: traceEvents.filter((event) => event.type === "agent.handoff").map(toHandoff),
|
|
22513
|
+
outcome: resolveOutcome4(traceEvents),
|
|
22514
|
+
providers: timelineSession?.providers ?? [],
|
|
22515
|
+
replay,
|
|
22516
|
+
sessionId: options.sessionId,
|
|
22517
|
+
status: timelineSession?.status ?? "healthy",
|
|
22518
|
+
summary: timelineSession?.summary ?? replay.summary,
|
|
22519
|
+
timeline: timelineSession?.events ?? [],
|
|
22520
|
+
tools: traceEvents.filter((event) => event.type === "agent.tool").map(toTool),
|
|
22521
|
+
traceEvents
|
|
22522
|
+
};
|
|
22523
|
+
};
|
|
22524
|
+
var escapeHtml39 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22525
|
+
var formatMs4 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
22526
|
+
var renderVoiceOperationsRecordHTML = (record, options = {}) => {
|
|
22527
|
+
const providers = record.providers.length ? record.providers.map((provider) => `<article><strong>${escapeHtml39(provider.provider)}</strong><span>${String(provider.eventCount)} events</span><span>${formatMs4(provider.averageElapsedMs)} avg</span><span>${String(provider.errorCount)} errors</span></article>`).join("") : '<p class="muted">No provider events recorded.</p>';
|
|
22528
|
+
const handoffs = record.handoffs.length ? record.handoffs.map((handoff) => `<li><strong>${escapeHtml39(handoff.fromAgentId ?? "unknown")}</strong> to <strong>${escapeHtml39(handoff.targetAgentId ?? "unknown")}</strong> <span>${escapeHtml39(handoff.status ?? "")}</span><p>${escapeHtml39(handoff.summary ?? handoff.reason ?? "")}</p></li>`).join("") : "<li>No agent handoffs recorded.</li>";
|
|
22529
|
+
const tools = record.tools.length ? record.tools.map((tool) => `<li><strong>${escapeHtml39(tool.toolName ?? "tool")}</strong> <span>${escapeHtml39(tool.status ?? "")}</span> ${formatMs4(tool.elapsedMs)} ${tool.error ? `<p>${escapeHtml39(tool.error)}</p>` : ""}</li>`).join("") : "<li>No tool calls recorded.</li>";
|
|
22530
|
+
const snippet = escapeHtml39(`app.use(
|
|
22531
|
+
createVoiceOperationsRecordRoutes({
|
|
22532
|
+
audit: auditStore,
|
|
22533
|
+
htmlPath: '/voice-ops/:sessionId',
|
|
22534
|
+
path: '/api/voice-ops/:sessionId',
|
|
22535
|
+
redact: {
|
|
22536
|
+
keys: ['authorization', 'apiKey', 'token']
|
|
22537
|
+
},
|
|
22538
|
+
store: traceStore
|
|
22539
|
+
})
|
|
22540
|
+
);`);
|
|
22541
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml39(options.title ?? "Voice Operations Record")}</title><style>body{background:#101417;color:#f9f4e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.eyebrow{color:#fbbf24;font-size:.8rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,4.8rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #475569;border-radius:999px;display:inline-flex;padding:8px 12px}.healthy{color:#86efac}.warning{color:#fbbf24}.failed,.error{color:#fca5a5}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin:20px 0}.card,.primitive{background:#182025;border:1px solid #2d3a43;border-radius:20px;padding:16px}.card span,.muted{color:#a9b4bd}.card strong{display:block;font-size:2rem}section{margin-top:28px}article{display:grid;gap:8px}ul{display:grid;gap:10px;list-style:none;padding:0}li{background:#182025;border:1px solid #2d3a43;border-radius:16px;padding:14px}pre{background:#080d10;border:1px solid #2d3a43;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}</style></head><body><main><p class="eyebrow">Portable production proof</p><h1>${escapeHtml39(options.title ?? "Voice Operations Record")}</h1><p class="status ${escapeHtml39(record.status)}">${escapeHtml39(record.status)}</p><section class="grid"><div class="card"><span>Events</span><strong>${String(record.summary.eventCount)}</strong></div><div class="card"><span>Turns</span><strong>${String(record.summary.turnCount)}</strong></div><div class="card"><span>Errors</span><strong>${String(record.summary.errorCount)}</strong></div><div class="card"><span>Duration</span><strong>${formatMs4(record.summary.callDurationMs)}</strong></div><div class="card"><span>Audit</span><strong>${String(record.audit?.total ?? 0)}</strong></div></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceOperationsRecordRoutes(...)</code> gives every call one debuggable object</h2><p class="muted">Use this as the support/debug payload across traces, provider routing, tools, handoffs, audit, latency, and replay.</p><pre><code>${snippet}</code></pre></section><section><h2>Providers</h2><div class="grid">${providers}</div></section><section><h2>Handoffs</h2><ul>${handoffs}</ul></section><section><h2>Tools</h2><ul>${tools}</ul></section></main></body></html>`;
|
|
22542
|
+
};
|
|
22543
|
+
var createVoiceOperationsRecordRoutes = (options) => {
|
|
22544
|
+
const path = options.path ?? "/api/voice-operations/:sessionId";
|
|
22545
|
+
const htmlPath = options.htmlPath === undefined ? "/voice-operations/:sessionId" : options.htmlPath;
|
|
22546
|
+
const routes = new Elysia37({
|
|
22547
|
+
name: options.name ?? "absolutejs-voice-operations-record"
|
|
22548
|
+
});
|
|
22549
|
+
const buildRecord = (sessionId) => buildVoiceOperationsRecord({
|
|
22550
|
+
audit: options.audit,
|
|
22551
|
+
evaluation: options.evaluation,
|
|
22552
|
+
events: options.events,
|
|
22553
|
+
redact: options.redact,
|
|
22554
|
+
sessionId,
|
|
22555
|
+
store: options.store
|
|
22556
|
+
});
|
|
22557
|
+
const getSessionId = (params) => params.sessionId ?? "";
|
|
22558
|
+
routes.get(path, async ({ params }) => Response.json(await buildRecord(getSessionId(params))));
|
|
22559
|
+
if (htmlPath) {
|
|
22560
|
+
routes.get(htmlPath, async ({ params }) => {
|
|
22561
|
+
const record = await buildRecord(getSessionId(params));
|
|
22562
|
+
const body = await (options.render ?? ((input) => renderVoiceOperationsRecordHTML(input, {
|
|
22563
|
+
title: options.title
|
|
22564
|
+
})))(record);
|
|
22565
|
+
return new Response(body, {
|
|
22566
|
+
headers: {
|
|
22567
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22568
|
+
...options.headers
|
|
22569
|
+
}
|
|
22570
|
+
});
|
|
22571
|
+
});
|
|
22572
|
+
}
|
|
22573
|
+
return routes;
|
|
22574
|
+
};
|
|
22575
|
+
// src/opsStatus.ts
|
|
22576
|
+
var DEFAULT_LINKS2 = [
|
|
22577
|
+
{
|
|
22578
|
+
description: "Production quality gates.",
|
|
22579
|
+
href: "/quality",
|
|
22580
|
+
label: "Quality",
|
|
22581
|
+
statusHref: "/quality/status"
|
|
22582
|
+
},
|
|
22583
|
+
{
|
|
22584
|
+
description: "Replay sessions against evals and workflow contracts.",
|
|
22585
|
+
href: "/evals",
|
|
22586
|
+
label: "Evals",
|
|
22587
|
+
statusHref: "/evals/status"
|
|
22588
|
+
},
|
|
22589
|
+
{
|
|
22590
|
+
description: "Provider routing, fallback, and resilience controls.",
|
|
22591
|
+
href: "/resilience",
|
|
22592
|
+
label: "Resilience"
|
|
22593
|
+
},
|
|
22594
|
+
{
|
|
22595
|
+
description: "One JSON/HTML production readiness rollup.",
|
|
22596
|
+
href: "/production-readiness",
|
|
22597
|
+
label: "Production Readiness",
|
|
22598
|
+
statusHref: "/api/production-readiness"
|
|
22599
|
+
},
|
|
22600
|
+
{
|
|
22601
|
+
description: "Recent sessions and replay links.",
|
|
22602
|
+
href: "/sessions",
|
|
22603
|
+
label: "Sessions"
|
|
22604
|
+
},
|
|
22605
|
+
{
|
|
22606
|
+
description: "Trace-backed phone-agent production smoke proof.",
|
|
22607
|
+
href: "/voice/phone/smoke-contract",
|
|
22608
|
+
label: "Phone Smoke",
|
|
22609
|
+
statusHref: "/api/voice/phone/smoke-contract"
|
|
22610
|
+
}
|
|
22611
|
+
];
|
|
22612
|
+
var countStatus = (statuses) => ({
|
|
22613
|
+
failed: statuses.filter((status) => status === "fail").length,
|
|
22614
|
+
passed: statuses.filter((status) => status === "pass").length,
|
|
22615
|
+
total: statuses.length
|
|
22616
|
+
});
|
|
22617
|
+
var summarizeVoiceOpsStatus = async (options) => {
|
|
22618
|
+
const include = options.include;
|
|
22619
|
+
const shouldInclude = (surface) => include?.[surface] !== false;
|
|
22620
|
+
const evals = options.evals === false ? undefined : options.evals;
|
|
22621
|
+
const events = filterVoiceTraceEvents(await options.store.list());
|
|
22622
|
+
const [quality, workflows, providers, sessions, handoffs, deliverySinks] = await Promise.all([
|
|
22623
|
+
options.quality === false || !shouldInclude("quality") ? undefined : evaluateVoiceQuality({
|
|
22624
|
+
events,
|
|
22625
|
+
thresholds: options.quality?.thresholds
|
|
22626
|
+
}),
|
|
22627
|
+
!evals || !shouldInclude("workflows") ? undefined : (async () => {
|
|
22628
|
+
const fixtureReport = await runVoiceScenarioFixtureEvals({
|
|
22629
|
+
fixtures: evals.fixtures,
|
|
22630
|
+
fixtureStore: evals.fixtureStore,
|
|
22631
|
+
scenarios: evals.scenarios
|
|
22632
|
+
});
|
|
22633
|
+
if ((options.preferFixtureWorkflows ?? true) && fixtureReport.total > 0) {
|
|
22634
|
+
return {
|
|
22635
|
+
failed: fixtureReport.failed,
|
|
22636
|
+
source: "fixtures",
|
|
22637
|
+
status: fixtureReport.status,
|
|
22638
|
+
total: fixtureReport.total
|
|
22639
|
+
};
|
|
22640
|
+
}
|
|
22641
|
+
const liveReport = await runVoiceScenarioEvals({
|
|
22642
|
+
events,
|
|
22643
|
+
scenarios: evals.scenarios
|
|
22644
|
+
});
|
|
22645
|
+
return {
|
|
22646
|
+
failed: liveReport.failed,
|
|
22647
|
+
source: "live",
|
|
22648
|
+
status: liveReport.status,
|
|
22649
|
+
total: liveReport.total
|
|
22650
|
+
};
|
|
22651
|
+
})(),
|
|
22652
|
+
!shouldInclude("providers") ? undefined : Promise.all([
|
|
22653
|
+
summarizeVoiceProviderHealth({
|
|
22654
|
+
events,
|
|
22655
|
+
providers: options.llmProviders
|
|
22656
|
+
}),
|
|
22657
|
+
summarizeVoiceProviderHealth({
|
|
22658
|
+
events: events.filter((event) => event.payload.kind === "stt"),
|
|
22659
|
+
providers: options.sttProviders
|
|
22660
|
+
}),
|
|
22661
|
+
summarizeVoiceProviderHealth({
|
|
22662
|
+
events: events.filter((event) => event.payload.kind === "tts"),
|
|
22663
|
+
providers: options.ttsProviders
|
|
22664
|
+
})
|
|
22665
|
+
]).then((groups) => groups.flat()),
|
|
22666
|
+
!shouldInclude("sessions") ? undefined : summarizeVoiceSessions({
|
|
22667
|
+
events
|
|
22668
|
+
}),
|
|
22669
|
+
!shouldInclude("handoffs") ? undefined : summarizeVoiceHandoffHealth({
|
|
22670
|
+
events
|
|
22671
|
+
}),
|
|
22672
|
+
!options.deliverySinks || !shouldInclude("deliverySinks") ? undefined : buildVoiceDeliverySinkReport(options.deliverySinks)
|
|
22673
|
+
]);
|
|
22674
|
+
const providerRecovery = shouldInclude("providerRecovery") ? summarizeVoiceProviderFallbackRecovery(events) : undefined;
|
|
22675
|
+
const surfaces = {};
|
|
22676
|
+
const statuses = [];
|
|
22677
|
+
if (quality) {
|
|
22678
|
+
surfaces.quality = { status: quality.status };
|
|
22679
|
+
statuses.push(quality.status);
|
|
22680
|
+
}
|
|
22681
|
+
if (workflows) {
|
|
22682
|
+
surfaces.workflows = workflows;
|
|
22683
|
+
statuses.push(workflows.status);
|
|
22684
|
+
}
|
|
22685
|
+
if (providers) {
|
|
22686
|
+
const degraded = providers.filter((provider) => provider.status === "degraded" || provider.status === "rate-limited" || provider.status === "suppressed").length;
|
|
22687
|
+
const status = degraded > 0 ? "fail" : "pass";
|
|
22688
|
+
surfaces.providers = {
|
|
22689
|
+
degraded,
|
|
22690
|
+
status,
|
|
22691
|
+
total: providers.length
|
|
22692
|
+
};
|
|
22693
|
+
statuses.push(status);
|
|
22694
|
+
}
|
|
22695
|
+
if (providerRecovery) {
|
|
22696
|
+
surfaces.providerRecovery = providerRecovery;
|
|
22697
|
+
statuses.push(providerRecovery.status);
|
|
22698
|
+
}
|
|
22699
|
+
if (sessions) {
|
|
22700
|
+
const failed = sessions.filter((session) => session.status === "failed").length;
|
|
22197
22701
|
const status = failed > 0 ? "fail" : "pass";
|
|
22198
22702
|
surfaces.sessions = {
|
|
22199
22703
|
failed,
|
|
@@ -22240,19 +22744,19 @@ var summarizeVoiceOpsStatus = async (options) => {
|
|
|
22240
22744
|
};
|
|
22241
22745
|
};
|
|
22242
22746
|
// src/opsStatusRoutes.ts
|
|
22243
|
-
import { Elysia as
|
|
22244
|
-
var
|
|
22747
|
+
import { Elysia as Elysia38 } from "elysia";
|
|
22748
|
+
var escapeHtml40 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22245
22749
|
var renderVoiceOpsStatusHTML = (report, options = {}) => {
|
|
22246
22750
|
const title = options.title ?? "AbsoluteJS Voice Ops Status";
|
|
22247
22751
|
const surfaces = Object.entries(report.surfaces).map(([key, surface]) => {
|
|
22248
22752
|
const value = "recovered" in surface ? surface.total === 0 ? "0 events" : `${surface.recovered}/${surface.total}` : ("auditTotal" in surface) ? `${surface.auditTotal + surface.traceTotal} deliveries` : ("total" in surface) ? `${Math.max(surface.total - ("failed" in surface ? surface.failed : ("degraded" in surface) ? surface.degraded : 0), 0)}/${surface.total}` : surface.status;
|
|
22249
|
-
return `<article class="surface ${
|
|
22753
|
+
return `<article class="surface ${escapeHtml40(surface.status)}"><span>${escapeHtml40(surface.status.toUpperCase())}</span><h2>${escapeHtml40(key)}</h2><strong>${escapeHtml40(value)}</strong></article>`;
|
|
22250
22754
|
}).join("");
|
|
22251
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
22755
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(title)}</title><style>body{background:#0d141b;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:980px;padding:32px}.hero{background:linear-gradient(135deg,rgba(20,184,166,.2),rgba(245,158,11,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.surfaces{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.surface{background:#151d26;border:1px solid #283544;border-radius:20px;padding:18px}.surface span{color:#aab5c0;font-size:.78rem;font-weight:900;letter-spacing:.08em}.surface strong{font-size:1.5rem}.pass{border-color:rgba(34,197,94,.55)}.fail{border-color:rgba(239,68,68,.75)}a{color:#5eead4}</style></head><body><main><section class="hero"><p class="eyebrow">Ops status</p><h1>${escapeHtml40(title)}</h1><p>Compact pass/fail status for framework widgets, demos, and small customer-facing health badges.</p><p class="status ${escapeHtml40(report.status)}">Overall: ${escapeHtml40(report.status.toUpperCase())}</p><p>${report.passed}/${report.total} checks passing</p></section><section class="surfaces">${surfaces || '<article class="surface pass"><span>PASS</span><h2>No checks configured</h2><strong>0/0</strong></article>'}</section></main></body></html>`;
|
|
22252
22756
|
};
|
|
22253
22757
|
var createVoiceOpsStatusRoutes = (options) => {
|
|
22254
22758
|
const path = options.path ?? "/api/voice/ops-status";
|
|
22255
|
-
const routes = new
|
|
22759
|
+
const routes = new Elysia38({
|
|
22256
22760
|
name: options.name ?? "absolutejs-voice-ops-status"
|
|
22257
22761
|
});
|
|
22258
22762
|
routes.get(path, async () => summarizeVoiceOpsStatus(options));
|
|
@@ -22685,10 +23189,10 @@ var createVoiceTTSProviderRouter = (options) => {
|
|
|
22685
23189
|
};
|
|
22686
23190
|
};
|
|
22687
23191
|
// src/traceDeliveryRoutes.ts
|
|
22688
|
-
import { Elysia as
|
|
22689
|
-
var
|
|
22690
|
-
var
|
|
22691
|
-
var
|
|
23192
|
+
import { Elysia as Elysia39 } from "elysia";
|
|
23193
|
+
var escapeHtml41 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
23194
|
+
var getString14 = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
23195
|
+
var getNumber9 = (value) => {
|
|
22692
23196
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
22693
23197
|
return value;
|
|
22694
23198
|
}
|
|
@@ -22699,13 +23203,13 @@ var getNumber7 = (value) => {
|
|
|
22699
23203
|
return;
|
|
22700
23204
|
};
|
|
22701
23205
|
var parseStatus2 = (value) => {
|
|
22702
|
-
const text =
|
|
23206
|
+
const text = getString14(value);
|
|
22703
23207
|
return text === "pending" || text === "delivered" || text === "failed" || text === "skipped" || text === "all" ? text : undefined;
|
|
22704
23208
|
};
|
|
22705
23209
|
var resolveVoiceTraceDeliveryFilter = (query = {}, base = {}) => ({
|
|
22706
23210
|
...base,
|
|
22707
|
-
limit:
|
|
22708
|
-
q:
|
|
23211
|
+
limit: getNumber9(query.limit) ?? base.limit,
|
|
23212
|
+
q: getString14(query.q) ?? base.q,
|
|
22709
23213
|
status: parseStatus2(query.status) ?? base.status
|
|
22710
23214
|
});
|
|
22711
23215
|
var deliverySearchText2 = (delivery) => [
|
|
@@ -22767,14 +23271,14 @@ var renderSinkResults2 = (delivery) => {
|
|
|
22767
23271
|
if (entries.length === 0) {
|
|
22768
23272
|
return "<p>No sink delivery attempts recorded yet.</p>";
|
|
22769
23273
|
}
|
|
22770
|
-
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${
|
|
23274
|
+
return `<ul>${entries.map(([sinkId, result]) => `<li><strong>${escapeHtml41(sinkId)}</strong>: ${escapeHtml41(result.status)}${result.deliveredTo ? ` to ${escapeHtml41(result.deliveredTo)}` : ""}${result.error ? ` (${escapeHtml41(result.error)})` : ""}</li>`).join("")}</ul>`;
|
|
22771
23275
|
};
|
|
22772
|
-
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${
|
|
23276
|
+
var renderEventList2 = (delivery) => delivery.events.length === 0 ? "<p>No trace events in this delivery.</p>" : `<ul>${delivery.events.map((event) => `<li>${escapeHtml41(event.type)} <small>${escapeHtml41(event.id)}</small>${event.sessionId ? ` session=${escapeHtml41(event.sessionId)}` : ""}</li>`).join("")}</ul>`;
|
|
22773
23277
|
var renderVoiceTraceDeliveryHTML = (report, options = {}) => {
|
|
22774
23278
|
const title = options.title ?? "AbsoluteJS Voice Trace Deliveries";
|
|
22775
|
-
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${
|
|
22776
|
-
const rows = report.deliveries.map((delivery) => `<article class="delivery ${
|
|
22777
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
23279
|
+
const drainAction = options.workerPath === false ? "" : `<form method="post" action="${escapeHtml41(options.workerPath ?? "/api/voice-trace-deliveries/drain")}"><button type="submit">Drain trace deliveries</button></form>`;
|
|
23280
|
+
const rows = report.deliveries.map((delivery) => `<article class="delivery ${escapeHtml41(delivery.deliveryStatus)}"><div class="head"><div><span>${escapeHtml41(delivery.deliveryStatus)}</span><h2>${escapeHtml41(delivery.id)}</h2><p>${escapeHtml41(new Date(delivery.createdAt).toLocaleString())}${delivery.deliveredAt ? ` \xB7 delivered ${escapeHtml41(new Date(delivery.deliveredAt).toLocaleString())}` : ""}</p></div><strong>${String(delivery.deliveryAttempts ?? 0)} attempt(s)</strong></div>${delivery.deliveryError ? `<p class="error">${escapeHtml41(delivery.deliveryError)}</p>` : ""}<h3>Sinks</h3>${renderSinkResults2(delivery)}<h3>Events</h3>${renderEventList2(delivery)}</article>`).join("");
|
|
23281
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml41(title)}</title><style>body{background:#0f1318;color:#f4efe1;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1120px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.16),rgba(14,165,233,.14));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#86efac;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.2rem,5vw,4.8rem);line-height:.92;margin:.2rem 0 1rem}.grid{display:grid;gap:12px;grid-template-columns:repeat(4,1fr);margin-bottom:16px}.grid article,.delivery{background:#151b22;border:1px solid #26313d;border-radius:22px;padding:18px}.grid span,.delivery span{color:#86efac;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.grid strong{display:block;font-size:2rem}.deliveries{display:grid;gap:14px}.delivery.failed{border-color:rgba(239,68,68,.75)}.delivery.pending{border-color:rgba(245,158,11,.7)}.delivery.delivered{border-color:rgba(34,197,94,.55)}.delivery.skipped{border-color:rgba(148,163,184,.6)}.head{align-items:start;display:flex;gap:14px;justify-content:space-between}.delivery h2{font-size:1.05rem;margin:.3rem 0;overflow-wrap:anywhere}.delivery h3{margin:1rem 0 .3rem}.delivery p,.delivery li{color:#c8d0d8}.error{color:#fecaca!important}button{background:#86efac;border:0;border-radius:999px;color:#07111f;cursor:pointer;font-weight:900;margin-top:12px;padding:10px 14px}@media(max-width:760px){main{padding:20px}.grid{grid-template-columns:1fr 1fr}.head{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Trace export health</p><h1>${escapeHtml41(title)}</h1><p>Checked ${escapeHtml41(new Date(report.checkedAt).toLocaleString())}. Showing ${String(report.deliveries.length)} delivery item(s).</p>${drainAction}</section>${renderMetricGrid3(report)}<section class="deliveries">${rows || "<p>No trace deliveries match this filter.</p>"}</section></main></body></html>`;
|
|
22778
23282
|
};
|
|
22779
23283
|
var createVoiceTraceDeliveryJSONHandler = (options) => async ({ query }) => buildVoiceTraceDeliveryReport(options, resolveVoiceTraceDeliveryFilter(query, options.filter));
|
|
22780
23284
|
var createVoiceTraceDeliveryHTMLHandler = (options) => async ({ query }) => {
|
|
@@ -22794,7 +23298,7 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
22794
23298
|
const path = options.path ?? "/api/voice-trace-deliveries";
|
|
22795
23299
|
const htmlPath = options.htmlPath === undefined ? "/traces/deliveries" : options.htmlPath;
|
|
22796
23300
|
const workerPath = options.workerPath === undefined ? `${path}/drain` : options.workerPath;
|
|
22797
|
-
const routes = new
|
|
23301
|
+
const routes = new Elysia39({
|
|
22798
23302
|
name: options.name ?? "absolutejs-voice-trace-deliveries"
|
|
22799
23303
|
}).get(path, createVoiceTraceDeliveryJSONHandler(options));
|
|
22800
23304
|
if (htmlPath !== false) {
|
|
@@ -22811,263 +23315,6 @@ var createVoiceTraceDeliveryRoutes = (options) => {
|
|
|
22811
23315
|
}
|
|
22812
23316
|
return routes;
|
|
22813
23317
|
};
|
|
22814
|
-
// src/traceTimeline.ts
|
|
22815
|
-
import { Elysia as Elysia38 } from "elysia";
|
|
22816
|
-
var escapeHtml40 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
22817
|
-
var getString13 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
22818
|
-
var getNumber8 = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22819
|
-
var firstString3 = (payload, keys) => {
|
|
22820
|
-
for (const key of keys) {
|
|
22821
|
-
const value = getString13(payload[key]);
|
|
22822
|
-
if (value) {
|
|
22823
|
-
return value;
|
|
22824
|
-
}
|
|
22825
|
-
}
|
|
22826
|
-
return;
|
|
22827
|
-
};
|
|
22828
|
-
var firstNumber3 = (payload, keys) => {
|
|
22829
|
-
for (const key of keys) {
|
|
22830
|
-
const value = getNumber8(payload[key]);
|
|
22831
|
-
if (value !== undefined) {
|
|
22832
|
-
return value;
|
|
22833
|
-
}
|
|
22834
|
-
}
|
|
22835
|
-
return;
|
|
22836
|
-
};
|
|
22837
|
-
var eventProvider = (event) => firstString3(event.payload, [
|
|
22838
|
-
"provider",
|
|
22839
|
-
"selectedProvider",
|
|
22840
|
-
"fallbackProvider",
|
|
22841
|
-
"variantId"
|
|
22842
|
-
]);
|
|
22843
|
-
var eventStatus = (event) => firstString3(event.payload, [
|
|
22844
|
-
"providerStatus",
|
|
22845
|
-
"status",
|
|
22846
|
-
"disposition",
|
|
22847
|
-
"type",
|
|
22848
|
-
"reason"
|
|
22849
|
-
]);
|
|
22850
|
-
var eventElapsedMs = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
|
|
22851
|
-
var timelineLabel = (event) => {
|
|
22852
|
-
switch (event.type) {
|
|
22853
|
-
case "call.lifecycle":
|
|
22854
|
-
return `Call ${eventStatus(event) ?? "lifecycle"}`;
|
|
22855
|
-
case "turn.transcript":
|
|
22856
|
-
return event.payload.isFinal === true ? "Final transcript" : "Partial transcript";
|
|
22857
|
-
case "turn.committed":
|
|
22858
|
-
return `Committed turn${getString13(event.payload.reason) ? ` (${getString13(event.payload.reason)})` : ""}`;
|
|
22859
|
-
case "turn.assistant":
|
|
22860
|
-
return "Assistant reply";
|
|
22861
|
-
case "agent.model":
|
|
22862
|
-
return `Model call${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22863
|
-
case "agent.tool":
|
|
22864
|
-
return `Tool ${getString13(event.payload.toolName) ?? "call"}`;
|
|
22865
|
-
case "agent.handoff":
|
|
22866
|
-
return `Agent handoff${getString13(event.payload.targetAgentId) ? ` to ${getString13(event.payload.targetAgentId)}` : ""}`;
|
|
22867
|
-
case "assistant.run":
|
|
22868
|
-
return `Assistant run${eventProvider(event) ? ` via ${eventProvider(event)}` : ""}`;
|
|
22869
|
-
case "assistant.guardrail":
|
|
22870
|
-
return `Guardrail ${eventStatus(event) ?? "check"}`;
|
|
22871
|
-
case "call.handoff":
|
|
22872
|
-
return `Call handoff ${eventStatus(event) ?? ""}`.trim();
|
|
22873
|
-
case "client.live_latency":
|
|
22874
|
-
return `Live latency${eventElapsedMs(event) !== undefined ? ` ${eventElapsedMs(event)}ms` : ""}`;
|
|
22875
|
-
case "session.error":
|
|
22876
|
-
return `Error${getString13(event.payload.error) ? `: ${getString13(event.payload.error)}` : ""}`;
|
|
22877
|
-
case "turn.cost":
|
|
22878
|
-
return "Cost telemetry";
|
|
22879
|
-
case "turn_latency.stage":
|
|
22880
|
-
return `Latency ${getString13(event.payload.stage) ?? "stage"}`;
|
|
22881
|
-
case "workflow.contract":
|
|
22882
|
-
return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
|
|
22883
|
-
default:
|
|
22884
|
-
return event.type;
|
|
22885
|
-
}
|
|
22886
|
-
};
|
|
22887
|
-
var summarizeProviders = (events) => {
|
|
22888
|
-
const entries = new Map;
|
|
22889
|
-
const getEntry = (provider) => {
|
|
22890
|
-
const existing = entries.get(provider);
|
|
22891
|
-
if (existing) {
|
|
22892
|
-
return existing;
|
|
22893
|
-
}
|
|
22894
|
-
const entry = {
|
|
22895
|
-
elapsed: [],
|
|
22896
|
-
errorCount: 0,
|
|
22897
|
-
eventCount: 0,
|
|
22898
|
-
fallbackCount: 0,
|
|
22899
|
-
successCount: 0,
|
|
22900
|
-
timeoutCount: 0
|
|
22901
|
-
};
|
|
22902
|
-
entries.set(provider, entry);
|
|
22903
|
-
return entry;
|
|
22904
|
-
};
|
|
22905
|
-
for (const event of events) {
|
|
22906
|
-
const provider = eventProvider(event);
|
|
22907
|
-
if (!provider) {
|
|
22908
|
-
continue;
|
|
22909
|
-
}
|
|
22910
|
-
const entry = getEntry(provider);
|
|
22911
|
-
const status = eventStatus(event);
|
|
22912
|
-
const elapsedMs = eventElapsedMs(event);
|
|
22913
|
-
entry.eventCount += 1;
|
|
22914
|
-
if (elapsedMs !== undefined) {
|
|
22915
|
-
entry.elapsed.push(elapsedMs);
|
|
22916
|
-
}
|
|
22917
|
-
if (status === "success") {
|
|
22918
|
-
entry.successCount += 1;
|
|
22919
|
-
}
|
|
22920
|
-
if (status === "fallback") {
|
|
22921
|
-
entry.fallbackCount += 1;
|
|
22922
|
-
}
|
|
22923
|
-
if (status === "error") {
|
|
22924
|
-
entry.errorCount += 1;
|
|
22925
|
-
}
|
|
22926
|
-
if (status === "timeout") {
|
|
22927
|
-
entry.timeoutCount += 1;
|
|
22928
|
-
}
|
|
22929
|
-
}
|
|
22930
|
-
return [...entries.entries()].map(([provider, entry]) => ({
|
|
22931
|
-
averageElapsedMs: entry.elapsed.length > 0 ? Math.round(entry.elapsed.reduce((total, value) => total + value, 0) / entry.elapsed.length) : undefined,
|
|
22932
|
-
errorCount: entry.errorCount,
|
|
22933
|
-
eventCount: entry.eventCount,
|
|
22934
|
-
fallbackCount: entry.fallbackCount,
|
|
22935
|
-
maxElapsedMs: entry.elapsed.length > 0 ? Math.max(...entry.elapsed) : undefined,
|
|
22936
|
-
provider,
|
|
22937
|
-
successCount: entry.successCount,
|
|
22938
|
-
timeoutCount: entry.timeoutCount
|
|
22939
|
-
})).sort((left, right) => right.eventCount - left.eventCount);
|
|
22940
|
-
};
|
|
22941
|
-
var summarizeVoiceTraceTimeline = (events, options = {}) => {
|
|
22942
|
-
const source = options.redact ? redactVoiceTraceEvents(events, options.redact) : events;
|
|
22943
|
-
const grouped = new Map;
|
|
22944
|
-
for (const event of filterVoiceTraceEvents(source)) {
|
|
22945
|
-
grouped.set(event.sessionId, [...grouped.get(event.sessionId) ?? [], event]);
|
|
22946
|
-
}
|
|
22947
|
-
const sessions = [...grouped.entries()].map(([sessionId, sessionEvents]) => {
|
|
22948
|
-
const sorted = filterVoiceTraceEvents(sessionEvents);
|
|
22949
|
-
const summary = summarizeVoiceTrace(sorted);
|
|
22950
|
-
const evaluation = evaluateVoiceTrace(sorted, options.evaluation);
|
|
22951
|
-
const startedAt = summary.startedAt ?? sorted[0]?.at ?? 0;
|
|
22952
|
-
const status = summary.failed ? "failed" : evaluation.issues.length > 0 ? "warning" : "healthy";
|
|
22953
|
-
return {
|
|
22954
|
-
endedAt: summary.endedAt,
|
|
22955
|
-
evaluation,
|
|
22956
|
-
events: sorted.map((event) => ({
|
|
22957
|
-
at: event.at,
|
|
22958
|
-
elapsedMs: eventElapsedMs(event),
|
|
22959
|
-
id: event.id,
|
|
22960
|
-
label: timelineLabel(event),
|
|
22961
|
-
offsetMs: Math.max(0, event.at - startedAt),
|
|
22962
|
-
provider: eventProvider(event),
|
|
22963
|
-
status: eventStatus(event),
|
|
22964
|
-
turnId: event.turnId,
|
|
22965
|
-
type: event.type
|
|
22966
|
-
})),
|
|
22967
|
-
lastEventAt: sorted.at(-1)?.at,
|
|
22968
|
-
providers: summarizeProviders(sorted),
|
|
22969
|
-
sessionId,
|
|
22970
|
-
startedAt: summary.startedAt,
|
|
22971
|
-
status,
|
|
22972
|
-
summary
|
|
22973
|
-
};
|
|
22974
|
-
}).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0)).slice(0, options.limit ?? 50);
|
|
22975
|
-
return {
|
|
22976
|
-
checkedAt: Date.now(),
|
|
22977
|
-
failed: sessions.filter((session) => session.status === "failed").length,
|
|
22978
|
-
sessions,
|
|
22979
|
-
total: sessions.length,
|
|
22980
|
-
warnings: sessions.filter((session) => session.status === "warning").length
|
|
22981
|
-
};
|
|
22982
|
-
};
|
|
22983
|
-
var formatMs3 = (value) => value === undefined ? "n/a" : `${String(value)}ms`;
|
|
22984
|
-
var renderProviderCards2 = (session) => session.providers.length === 0 ? '<p class="muted">No provider events recorded for this session.</p>' : `<div class="providers">${session.providers.map((provider) => `<article><strong>${escapeHtml40(provider.provider)}</strong><dl><div><dt>Events</dt><dd>${String(provider.eventCount)}</dd></div><div><dt>Avg</dt><dd>${formatMs3(provider.averageElapsedMs)}</dd></div><div><dt>Max</dt><dd>${formatMs3(provider.maxElapsedMs)}</dd></div><div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div><div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div><div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div></dl></article>`).join("")}</div>`;
|
|
22985
|
-
var renderVoiceTraceTimelineSessionHTML = (session, options = {}) => {
|
|
22986
|
-
const events = session.events.map((event) => `<tr class="${escapeHtml40(event.status ?? "")}"><td>+${String(event.offsetMs)}ms</td><td>${escapeHtml40(event.type)}</td><td>${escapeHtml40(event.label)}</td><td>${escapeHtml40(event.provider ?? "")}</td><td>${escapeHtml40(event.status ?? "")}</td><td>${formatMs3(event.elapsedMs)}</td></tr>`).join("");
|
|
22987
|
-
const issues = session.evaluation.issues.length ? session.evaluation.issues.map((issue) => `<li class="${escapeHtml40(issue.severity)}">${escapeHtml40(issue.code)}: ${escapeHtml40(issue.message)}</li>`).join("") : "<li>none</li>";
|
|
22988
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timeline")}</title><style>${timelineCSS}</style></head><body><main><a href="/traces">Back to traces</a><header><p class="eyebrow">Call timeline</p><h1>${escapeHtml40(session.sessionId)}</h1><p class="status ${escapeHtml40(session.status)}">${escapeHtml40(session.status)}</p></header><section class="metrics"><article><span>Events</span><strong>${String(session.summary.eventCount)}</strong></article><article><span>Turns</span><strong>${String(session.summary.turnCount)}</strong></article><article><span>Errors</span><strong>${String(session.summary.errorCount)}</strong></article><article><span>Duration</span><strong>${formatMs3(session.summary.callDurationMs)}</strong></article></section><section><h2>Providers</h2>${renderProviderCards2(session)}</section><section><h2>Issues</h2><ul>${issues}</ul></section><section><h2>Timeline</h2><table><thead><tr><th>Offset</th><th>Type</th><th>Event</th><th>Provider</th><th>Status</th><th>Latency</th></tr></thead><tbody>${events}</tbody></table></section></main></body></html>`;
|
|
22989
|
-
};
|
|
22990
|
-
var renderSessionRows = (report) => report.sessions.length === 0 ? '<tr><td colspan="7">No trace events recorded yet.</td></tr>' : report.sessions.map((session) => `<tr class="${escapeHtml40(session.status)}"><td><a href="/traces/${encodeURIComponent(session.sessionId)}">${escapeHtml40(session.sessionId)}</a></td><td>${escapeHtml40(session.status)}</td><td>${String(session.summary.eventCount)}</td><td>${String(session.summary.turnCount)}</td><td>${String(session.summary.errorCount)}</td><td>${formatMs3(session.summary.callDurationMs)}</td><td>${session.providers.map((provider) => escapeHtml40(provider.provider)).join(", ")}</td></tr>`).join("");
|
|
22991
|
-
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}}";
|
|
22992
|
-
var renderVoiceTraceTimelineHTML = (report, options = {}) => {
|
|
22993
|
-
const snippet = escapeHtml40(`const traceStore = createVoiceTraceSinkStore({
|
|
22994
|
-
store: runtimeStorage.traces,
|
|
22995
|
-
sinks: [
|
|
22996
|
-
createVoiceTraceHTTPSink({
|
|
22997
|
-
endpoint: process.env.VOICE_TRACE_WEBHOOK_URL
|
|
22998
|
-
})
|
|
22999
|
-
]
|
|
23000
|
-
});
|
|
23001
|
-
|
|
23002
|
-
app.use(
|
|
23003
|
-
createVoiceTraceTimelineRoutes({
|
|
23004
|
-
htmlPath: '/traces',
|
|
23005
|
-
path: '/api/voice-traces',
|
|
23006
|
-
redact: {
|
|
23007
|
-
keys: ['authorization', 'apiKey', 'token']
|
|
23008
|
-
},
|
|
23009
|
-
store: traceStore
|
|
23010
|
-
})
|
|
23011
|
-
);
|
|
23012
|
-
|
|
23013
|
-
app.use(
|
|
23014
|
-
createVoiceProductionReadinessRoutes({
|
|
23015
|
-
store: traceStore,
|
|
23016
|
-
traceDeliveries: runtimeStorage.traceDeliveries
|
|
23017
|
-
})
|
|
23018
|
-
);`);
|
|
23019
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml40(options.title ?? "Voice Trace Timelines")}</title><style>${timelineCSS}.primitive{background:#181f27;border:1px solid #334155;border-radius:20px;margin:20px 0;padding:18px}.primitive p{line-height:1.55}.primitive pre{background:#0b1118;border:1px solid #2b3642;border-radius:16px;color:#dbeafe;overflow:auto;padding:14px}.primitive code{color:#bfdbfe}</style></head><body><main><header><p class="eyebrow">Self-hosted voice debugging</p><h1>${escapeHtml40(options.title ?? "Voice Trace Timelines")}</h1><p class="muted">Per-call event timelines with provider latency, fallback, timeout, handoff, and error context.</p></header><section class="metrics"><article><span>Sessions</span><strong>${String(report.total)}</strong></article><article><span>Failed</span><strong>${String(report.failed)}</strong></article><article><span>Warnings</span><strong>${String(report.warnings)}</strong></article></section><section class="primitive"><p class="eyebrow">Copy into your app</p><h2><code>createVoiceTraceTimelineRoutes(...)</code> makes traces the proof backbone</h2><p class="muted">Mount trace timelines from the same trace store used by readiness, simulations, provider recovery, delivery sinks, and phone-agent smoke proof.</p><pre><code>${snippet}</code></pre></section><table><thead><tr><th>Session</th><th>Status</th><th>Events</th><th>Turns</th><th>Errors</th><th>Duration</th><th>Providers</th></tr></thead><tbody>${renderSessionRows(report)}</tbody></table></main></body></html>`;
|
|
23020
|
-
};
|
|
23021
|
-
var createVoiceTraceTimelineRoutes = (options) => {
|
|
23022
|
-
const path = options.path ?? "/api/voice-traces";
|
|
23023
|
-
const htmlPath = options.htmlPath ?? "/traces";
|
|
23024
|
-
const title = options.title ?? "AbsoluteJS Voice Trace Timelines";
|
|
23025
|
-
const routes = new Elysia38({
|
|
23026
|
-
name: options.name ?? "absolutejs-voice-trace-timelines"
|
|
23027
|
-
});
|
|
23028
|
-
const buildReport = async () => summarizeVoiceTraceTimeline(await options.store.list(), {
|
|
23029
|
-
evaluation: options.evaluation,
|
|
23030
|
-
limit: options.limit,
|
|
23031
|
-
redact: options.redact
|
|
23032
|
-
});
|
|
23033
|
-
const findSession = async (sessionId) => {
|
|
23034
|
-
const report = summarizeVoiceTraceTimeline(await options.store.list({ sessionId }), {
|
|
23035
|
-
evaluation: options.evaluation,
|
|
23036
|
-
limit: 1,
|
|
23037
|
-
redact: options.redact
|
|
23038
|
-
});
|
|
23039
|
-
return report.sessions[0];
|
|
23040
|
-
};
|
|
23041
|
-
routes.get(path, async () => Response.json(await buildReport()));
|
|
23042
|
-
routes.get(`${path}/:sessionId`, async ({ params }) => {
|
|
23043
|
-
const session = await findSession(params.sessionId);
|
|
23044
|
-
return session ? Response.json(session) : Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
23045
|
-
});
|
|
23046
|
-
routes.get(htmlPath, async () => {
|
|
23047
|
-
const report = await buildReport();
|
|
23048
|
-
const body = await (options.render ?? ((input) => renderVoiceTraceTimelineHTML(input, { title })))(report);
|
|
23049
|
-
return new Response(body, {
|
|
23050
|
-
headers: {
|
|
23051
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
23052
|
-
...options.headers
|
|
23053
|
-
}
|
|
23054
|
-
});
|
|
23055
|
-
});
|
|
23056
|
-
routes.get(`${htmlPath}/:sessionId`, async ({ params }) => {
|
|
23057
|
-
const session = await findSession(params.sessionId);
|
|
23058
|
-
if (!session) {
|
|
23059
|
-
return Response.json({ error: "Voice trace session not found." }, { status: 404 });
|
|
23060
|
-
}
|
|
23061
|
-
const body = await (options.renderSession ?? ((input) => renderVoiceTraceTimelineSessionHTML(input, { title })))(session);
|
|
23062
|
-
return new Response(body, {
|
|
23063
|
-
headers: {
|
|
23064
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
23065
|
-
...options.headers
|
|
23066
|
-
}
|
|
23067
|
-
});
|
|
23068
|
-
});
|
|
23069
|
-
return routes;
|
|
23070
|
-
};
|
|
23071
23318
|
// src/sqliteStore.ts
|
|
23072
23319
|
import { Database } from "bun:sqlite";
|
|
23073
23320
|
var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
|
|
@@ -23675,7 +23922,7 @@ var createVoiceMemoryStore = () => {
|
|
|
23675
23922
|
return { get, getOrCreate, list, remove, set };
|
|
23676
23923
|
};
|
|
23677
23924
|
// src/opsWebhook.ts
|
|
23678
|
-
import { Elysia as
|
|
23925
|
+
import { Elysia as Elysia40 } from "elysia";
|
|
23679
23926
|
var toHex6 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
23680
23927
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
23681
23928
|
const encoder = new TextEncoder;
|
|
@@ -23805,7 +24052,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
23805
24052
|
};
|
|
23806
24053
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
23807
24054
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
23808
|
-
return new
|
|
24055
|
+
return new Elysia40().post(path, async ({ body, request, set }) => {
|
|
23809
24056
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
23810
24057
|
if (options.signingSecret) {
|
|
23811
24058
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -24747,6 +24994,7 @@ export {
|
|
|
24747
24994
|
renderVoiceOpsStatusHTML,
|
|
24748
24995
|
renderVoiceOpsConsoleHTML,
|
|
24749
24996
|
renderVoiceOpsActionHistoryHTML,
|
|
24997
|
+
renderVoiceOperationsRecordHTML,
|
|
24750
24998
|
renderVoiceLiveLatencyHTML,
|
|
24751
24999
|
renderVoiceHandoffHealthHTML,
|
|
24752
25000
|
renderVoiceEvalHTML,
|
|
@@ -24935,6 +25183,7 @@ export {
|
|
|
24935
25183
|
createVoiceOpsRuntime,
|
|
24936
25184
|
createVoiceOpsConsoleRoutes,
|
|
24937
25185
|
createVoiceOpsActionAuditRoutes,
|
|
25186
|
+
createVoiceOperationsRecordRoutes,
|
|
24938
25187
|
createVoiceMemoryTraceSinkDeliveryStore,
|
|
24939
25188
|
createVoiceMemoryTraceEventStore,
|
|
24940
25189
|
createVoiceMemoryStore,
|
|
@@ -25059,6 +25308,7 @@ export {
|
|
|
25059
25308
|
buildVoiceOpsTaskFromReview,
|
|
25060
25309
|
buildVoiceOpsConsoleReport,
|
|
25061
25310
|
buildVoiceOpsActionHistoryReport,
|
|
25311
|
+
buildVoiceOperationsRecord,
|
|
25062
25312
|
buildVoiceDiagnosticsMarkdown,
|
|
25063
25313
|
buildVoiceDemoReadyReport,
|
|
25064
25314
|
buildVoiceDeliverySinkReport,
|