@absolutejs/voice 0.0.22-beta.203 → 0.0.22-beta.204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14095,6 +14095,17 @@ import { Elysia as Elysia21 } from "elysia";
14095
14095
  // src/outcomeContract.ts
14096
14096
  import { Elysia as Elysia19 } from "elysia";
14097
14097
  var escapeHtml21 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
14098
+ var resolveSessionHref2 = (value, sessionId) => {
14099
+ if (value === false) {
14100
+ return;
14101
+ }
14102
+ const href = value ?? "/voice-operations/:sessionId";
14103
+ if (typeof href === "function") {
14104
+ return href(sessionId);
14105
+ }
14106
+ const encoded = encodeURIComponent(sessionId);
14107
+ return href.includes(":sessionId") ? href.replace(":sessionId", encoded) : `${href.replace(/\/$/, "")}/${encoded}`;
14108
+ };
14098
14109
  var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
14099
14110
  var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
14100
14111
  var hydrateSessions = async (input) => {
@@ -14118,6 +14129,7 @@ var reportContract = (input) => {
14118
14129
  const { contract } = input;
14119
14130
  const sessions = input.sessions.filter((session) => (!contract.scenarioId || session.scenarioId === contract.scenarioId) && matchesDisposition(dispositionForSession(session), contract.expectedDisposition));
14120
14131
  const sessionIds = new Set(sessions.map((session) => session.id));
14132
+ const operationsRecordHrefs = [...sessionIds].map((sessionId) => resolveSessionHref2(input.operationsRecordHref, sessionId)).filter((href) => Boolean(href));
14121
14133
  const reviews = input.reviews.filter((review) => matchesDisposition(review.summary.outcome, contract.expectedDisposition));
14122
14134
  const tasks = input.tasks.filter((task) => matchesDisposition(task.outcome, contract.expectedDisposition));
14123
14135
  const handoffs = input.handoffs.filter((handoff) => (!contract.expectedDisposition || handoff.action === contract.expectedDisposition || contract.expectedDisposition === "transferred" && handoff.action === "transfer" || contract.expectedDisposition === "escalated" && handoff.action === "escalate") && (sessionIds.size === 0 || sessionIds.has(handoff.sessionId)));
@@ -14174,7 +14186,9 @@ var reportContract = (input) => {
14174
14186
  sessions: sessions.length,
14175
14187
  tasks: tasks.length
14176
14188
  },
14177
- pass: issues.length === 0
14189
+ operationsRecordHrefs,
14190
+ pass: issues.length === 0,
14191
+ sessionIds: [...sessionIds]
14178
14192
  };
14179
14193
  };
14180
14194
  var runVoiceOutcomeContractSuite = async (options) => {
@@ -14185,7 +14199,15 @@ var runVoiceOutcomeContractSuite = async (options) => {
14185
14199
  toList(options.events),
14186
14200
  toList(options.handoffs)
14187
14201
  ]);
14188
- const contracts = options.contracts.map((contract) => reportContract({ contract, events, handoffs, reviews, sessions, tasks }));
14202
+ const contracts = options.contracts.map((contract) => reportContract({
14203
+ contract,
14204
+ events,
14205
+ handoffs,
14206
+ operationsRecordHref: options.operationsRecordHref,
14207
+ reviews,
14208
+ sessions,
14209
+ tasks
14210
+ }));
14189
14211
  const passed = contracts.filter((contract) => contract.pass).length;
14190
14212
  const failed = contracts.length - passed;
14191
14213
  return {
@@ -14199,12 +14221,15 @@ var runVoiceOutcomeContractSuite = async (options) => {
14199
14221
  };
14200
14222
  var renderVoiceOutcomeContractHTML = (report, options = {}) => {
14201
14223
  const title = options.title ?? "Voice Outcome Contracts";
14202
- const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
14224
+ const contracts = report.contracts.map((contract) => {
14225
+ const sessionLinks = contract.operationsRecordHrefs.length ? `<p>${contract.operationsRecordHrefs.map((href, index) => `<a href="${escapeHtml21(href)}">${escapeHtml21(contract.sessionIds[index] ?? href)}</a>`).join(" \xB7 ")}</p>` : "";
14226
+ return `<section class="contract ${contract.pass ? "pass" : "fail"}">
14203
14227
  <div class="contract-header">
14204
14228
  <div>
14205
14229
  <p class="eyebrow">${escapeHtml21(contract.contractId)}</p>
14206
14230
  <h2>${escapeHtml21(contract.label ?? contract.contractId)}</h2>
14207
14231
  ${contract.description ? `<p>${escapeHtml21(contract.description)}</p>` : ""}
14232
+ ${sessionLinks}
14208
14233
  </div>
14209
14234
  <strong>${contract.pass ? "pass" : "fail"}</strong>
14210
14235
  </div>
@@ -14216,7 +14241,8 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
14216
14241
  <span>events ${String(contract.matched.integrationEvents)}</span>
14217
14242
  </div>
14218
14243
  ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml21(issue.message)}</li>`).join("")}</ul>` : ""}
14219
- </section>`).join("");
14244
+ </section>`;
14245
+ }).join("");
14220
14246
  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>`;
14221
14247
  };
14222
14248
  var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
@@ -14453,6 +14479,17 @@ var createDefaultTurn = (caseId) => ({
14453
14479
  var defaultApi = {};
14454
14480
  var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
14455
14481
  var escapeHtml22 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
14482
+ var resolveSessionHref3 = (value, sessionId) => {
14483
+ if (value === false) {
14484
+ return;
14485
+ }
14486
+ const href = value ?? "/voice-operations/:sessionId";
14487
+ if (typeof href === "function") {
14488
+ return href(sessionId);
14489
+ }
14490
+ const encoded = encodeURIComponent(sessionId);
14491
+ return href.includes(":sessionId") ? href.replace(":sessionId", encoded) : `${href.replace(/\/$/, "")}/${encoded}`;
14492
+ };
14456
14493
  var evaluateExpectation = (input) => {
14457
14494
  const issues = [];
14458
14495
  const expect = input.expect;
@@ -14503,7 +14540,7 @@ var evaluateExpectation = (input) => {
14503
14540
  }
14504
14541
  return issues;
14505
14542
  };
14506
- var runVoiceToolContract = async (definition) => {
14543
+ var runVoiceToolContract = async (definition, options = {}) => {
14507
14544
  const cases = [];
14508
14545
  for (const testCase of definition.cases) {
14509
14546
  const session = testCase.session ?? createDefaultSession(definition.id, testCase.id);
@@ -14563,7 +14600,9 @@ var runVoiceToolContract = async (definition) => {
14563
14600
  error: result.error,
14564
14601
  issues: issues2,
14565
14602
  label: testCase.label,
14603
+ operationsRecordHref: resolveSessionHref3(options.operationsRecordHref, session.id),
14566
14604
  pass: issues2.length === 0,
14605
+ sessionId: session.id,
14567
14606
  status: result.status,
14568
14607
  timedOut: result.timedOut
14569
14608
  });
@@ -14602,7 +14641,7 @@ var createVoiceToolRuntimeContractDefaults = () => ({
14602
14641
  timeoutMs: 5000
14603
14642
  });
14604
14643
  var runVoiceToolContractSuite = async (options) => {
14605
- const contracts = await Promise.all(options.contracts.map((contract) => runVoiceToolContract(contract)));
14644
+ const contracts = await Promise.all(options.contracts.map((contract) => runVoiceToolContract(contract, options)));
14606
14645
  const passed = contracts.filter((contract) => contract.pass).length;
14607
14646
  const failed = contracts.length - passed;
14608
14647
  return {
@@ -14642,9 +14681,10 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
14642
14681
  );`);
14643
14682
  const contracts = report.contracts.map((contract) => {
14644
14683
  const cases = contract.cases.map((testCase) => `<tr>
14645
- <td>${escapeHtml22(testCase.label ?? testCase.caseId)}</td>
14684
+ <td>${testCase.operationsRecordHref ? `<a href="${escapeHtml22(testCase.operationsRecordHref)}">${escapeHtml22(testCase.label ?? testCase.caseId)}</a>` : escapeHtml22(testCase.label ?? testCase.caseId)}</td>
14646
14685
  <td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
14647
14686
  <td>${escapeHtml22(testCase.status)}</td>
14687
+ <td>${escapeHtml22(testCase.sessionId)}</td>
14648
14688
  <td>${String(testCase.attempts)}</td>
14649
14689
  <td>${String(testCase.elapsedMs)}ms</td>
14650
14690
  <td>${testCase.timedOut ? "yes" : "no"}</td>
@@ -14659,7 +14699,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
14659
14699
  <strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
14660
14700
  </div>
14661
14701
  <table>
14662
- <thead><tr><th>Case</th><th>Status</th><th>Result</th><th>Attempts</th><th>Elapsed</th><th>Timed out</th><th>Issues</th></tr></thead>
14702
+ <thead><tr><th>Case</th><th>Status</th><th>Result</th><th>Session</th><th>Attempts</th><th>Elapsed</th><th>Timed out</th><th>Issues</th></tr></thead>
14663
14703
  <tbody>${cases}</tbody>
14664
14704
  </table>
14665
14705
  </section>`;
@@ -15196,7 +15236,7 @@ var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&l
15196
15236
  var increment4 = (record, key) => {
15197
15237
  record[key] = (record[key] ?? 0) + 1;
15198
15238
  };
15199
- var resolveSessionHref2 = (value, session) => {
15239
+ var resolveSessionHref4 = (value, session) => {
15200
15240
  if (value === false) {
15201
15241
  return;
15202
15242
  }
@@ -15360,7 +15400,7 @@ var summarizeVoiceSessions = async (options = {}) => {
15360
15400
  const replayHref = options.replayHref === false ? "" : typeof options.replayHref === "function" ? options.replayHref(item) : `${options.replayHref ?? "/api/voice-sessions"}/${encodeURIComponent(sessionId)}/replay/htmx`;
15361
15401
  return {
15362
15402
  ...item,
15363
- operationsRecordHref: resolveSessionHref2(options.operationsRecordHref, item),
15403
+ operationsRecordHref: resolveSessionHref4(options.operationsRecordHref, item),
15364
15404
  replayHref
15365
15405
  };
15366
15406
  });
@@ -23985,7 +24025,7 @@ var eventStatus = (event) => firstString3(event.payload, [
23985
24025
  "reason"
23986
24026
  ]);
23987
24027
  var eventElapsedMs2 = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
23988
- var resolveSessionHref3 = (value, sessionId) => {
24028
+ var resolveSessionHref5 = (value, sessionId) => {
23989
24029
  if (value === false) {
23990
24030
  return;
23991
24031
  }
@@ -24115,7 +24155,7 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
24115
24155
  type: event.type
24116
24156
  })),
24117
24157
  lastEventAt: sorted.at(-1)?.at,
24118
- operationsRecordHref: resolveSessionHref3(options.operationsRecordHref, sessionId),
24158
+ operationsRecordHref: resolveSessionHref5(options.operationsRecordHref, sessionId),
24119
24159
  providers: summarizeProviders(sorted),
24120
24160
  sessionId,
24121
24161
  startedAt: summary.startedAt,
@@ -32,7 +32,9 @@ export type VoiceOutcomeContractReport = {
32
32
  sessions: number;
33
33
  tasks: number;
34
34
  };
35
+ operationsRecordHrefs: string[];
35
36
  pass: boolean;
37
+ sessionIds: string[];
36
38
  };
37
39
  export type VoiceOutcomeContractSuiteReport = {
38
40
  checkedAt: number;
@@ -49,6 +51,7 @@ export type VoiceOutcomeContractOptions<TSession extends VoiceSessionRecord = Vo
49
51
  contracts: VoiceOutcomeContractDefinition[];
50
52
  events?: StoredVoiceIntegrationEvent[] | ListStore<StoredVoiceIntegrationEvent>;
51
53
  handoffs?: StoredVoiceHandoffDelivery[] | VoiceHandoffDeliveryStore;
54
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
52
55
  reviews?: StoredVoiceCallReviewArtifact[] | ListStore<StoredVoiceCallReviewArtifact>;
53
56
  sessions?: TSession[] | VoiceSessionStore<TSession>;
54
57
  tasks?: StoredVoiceOpsTask[] | ListStore<StoredVoiceOpsTask>;
@@ -42,7 +42,9 @@ export type VoiceToolContractCaseReport = {
42
42
  error?: string;
43
43
  issues: VoiceToolContractIssue[];
44
44
  label?: string;
45
+ operationsRecordHref?: string;
45
46
  pass: boolean;
47
+ sessionId: string;
46
48
  status: 'error' | 'ok';
47
49
  timedOut: boolean;
48
50
  };
@@ -64,6 +66,7 @@ export type VoiceToolContractSuiteReport = {
64
66
  };
65
67
  export type VoiceToolContractHandlerOptions = {
66
68
  contracts: VoiceToolContractDefinition[];
69
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
67
70
  };
68
71
  export type VoiceToolContractHTMLHandlerOptions = VoiceToolContractHandlerOptions & {
69
72
  headers?: HeadersInit;
@@ -75,7 +78,7 @@ export type VoiceToolContractRoutesOptions = VoiceToolContractHTMLHandlerOptions
75
78
  name?: string;
76
79
  path?: string;
77
80
  };
78
- export declare const runVoiceToolContract: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown>(definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>) => Promise<VoiceToolContractReport>;
81
+ export declare const runVoiceToolContract: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown>(definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>, options?: Pick<VoiceToolContractHandlerOptions, "operationsRecordHref">) => Promise<VoiceToolContractReport>;
79
82
  export declare const createVoiceToolContract: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown>(definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>) => {
80
83
  assert: () => Promise<VoiceToolContractReport>;
81
84
  definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.203",
3
+ "version": "0.0.22-beta.204",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",