@absolutejs/voice 0.0.22-beta.202 → 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>`;
@@ -14705,7 +14745,7 @@ var collectSimulationActions = (input) => {
14705
14745
  const firstFailed = input.sessions.sessions.find((session) => session.status === "fail");
14706
14746
  actions.push({
14707
14747
  description: firstFailed ? `Inspect session ${firstFailed.sessionId}; at least one quality metric is outside threshold.` : "Inspect failing session quality reports.",
14708
- href: input.links?.sessions,
14748
+ href: firstFailed?.operationsRecordHref ?? input.links?.sessions,
14709
14749
  label: "Review failing session quality",
14710
14750
  section: "sessions",
14711
14751
  severity: "error"
@@ -14716,9 +14756,10 @@ var collectSimulationActions = (input) => {
14716
14756
  continue;
14717
14757
  }
14718
14758
  const issue = scenario.issues[0] ?? scenario.sessions.find((session) => session.issues.length > 0)?.issues[0] ?? "Scenario did not meet its expected trace conditions.";
14759
+ const failedSession = scenario.sessions.find((session) => session.status === "fail");
14719
14760
  actions.push({
14720
14761
  description: `${scenario.label}: ${issue}`,
14721
- href: input.links?.scenarios,
14762
+ href: failedSession?.operationsRecordHref ?? input.links?.scenarios,
14722
14763
  label: `Fix scenario ${scenario.label}`,
14723
14764
  section: "scenarios",
14724
14765
  severity: "error"
@@ -14729,9 +14770,10 @@ var collectSimulationActions = (input) => {
14729
14770
  continue;
14730
14771
  }
14731
14772
  const failedScenario = fixture.report.scenarios.find((scenario) => scenario.status === "fail");
14773
+ const failedSession = failedScenario?.sessions.find((session) => session.status === "fail");
14732
14774
  actions.push({
14733
14775
  description: failedScenario ? `${fixture.label}: ${failedScenario.label} failed.` : `${fixture.label}: fixture simulation failed.`,
14734
- href: input.links?.fixtures,
14776
+ href: failedSession?.operationsRecordHref ?? input.links?.fixtures,
14735
14777
  label: `Update fixture ${fixture.label}`,
14736
14778
  section: "fixtures",
14737
14779
  severity: "error"
@@ -14774,16 +14816,19 @@ var runVoiceSimulationSuite = async (options) => {
14774
14816
  const [sessions, scenarios, fixtures, tools, outcomes] = await Promise.all([
14775
14817
  shouldRunSessions ? runVoiceSessionEvals({
14776
14818
  limit: options.limit,
14819
+ operationsRecordHref: options.operationsRecordHref,
14777
14820
  store: options.store,
14778
14821
  thresholds: options.thresholds
14779
14822
  }) : undefined,
14780
14823
  shouldRunScenarios ? runVoiceScenarioEvals({
14824
+ operationsRecordHref: options.operationsRecordHref,
14781
14825
  scenarios: options.scenarios,
14782
14826
  store: options.store
14783
14827
  }) : undefined,
14784
14828
  shouldRunFixtures ? runVoiceScenarioFixtureEvals({
14785
14829
  fixtures: options.fixtures,
14786
14830
  fixtureStore: options.fixtureStore,
14831
+ operationsRecordHref: options.operationsRecordHref,
14787
14832
  scenarios: options.scenarios
14788
14833
  }) : undefined,
14789
14834
  shouldRunTools ? runVoiceToolContractSuite({
@@ -15191,7 +15236,7 @@ var escapeHtml24 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&l
15191
15236
  var increment4 = (record, key) => {
15192
15237
  record[key] = (record[key] ?? 0) + 1;
15193
15238
  };
15194
- var resolveSessionHref2 = (value, session) => {
15239
+ var resolveSessionHref4 = (value, session) => {
15195
15240
  if (value === false) {
15196
15241
  return;
15197
15242
  }
@@ -15355,7 +15400,7 @@ var summarizeVoiceSessions = async (options = {}) => {
15355
15400
  const replayHref = options.replayHref === false ? "" : typeof options.replayHref === "function" ? options.replayHref(item) : `${options.replayHref ?? "/api/voice-sessions"}/${encodeURIComponent(sessionId)}/replay/htmx`;
15356
15401
  return {
15357
15402
  ...item,
15358
- operationsRecordHref: resolveSessionHref2(options.operationsRecordHref, item),
15403
+ operationsRecordHref: resolveSessionHref4(options.operationsRecordHref, item),
15359
15404
  replayHref
15360
15405
  };
15361
15406
  });
@@ -23980,7 +24025,7 @@ var eventStatus = (event) => firstString3(event.payload, [
23980
24025
  "reason"
23981
24026
  ]);
23982
24027
  var eventElapsedMs2 = (event) => firstNumber3(event.payload, ["elapsedMs", "latencyMs", "durationMs"]);
23983
- var resolveSessionHref3 = (value, sessionId) => {
24028
+ var resolveSessionHref5 = (value, sessionId) => {
23984
24029
  if (value === false) {
23985
24030
  return;
23986
24031
  }
@@ -24110,7 +24155,7 @@ var summarizeVoiceTraceTimeline = (events, options = {}) => {
24110
24155
  type: event.type
24111
24156
  })),
24112
24157
  lastEventAt: sorted.at(-1)?.at,
24113
- operationsRecordHref: resolveSessionHref3(options.operationsRecordHref, sessionId),
24158
+ operationsRecordHref: resolveSessionHref5(options.operationsRecordHref, sessionId),
24114
24159
  providers: summarizeProviders(sorted),
24115
24160
  sessionId,
24116
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>;
@@ -57,6 +57,7 @@ export type VoiceSimulationSuiteOptions<TSession extends VoiceSessionRecord = Vo
57
57
  tools?: boolean;
58
58
  };
59
59
  limit?: number;
60
+ operationsRecordHref?: false | string | ((sessionId: string) => string);
60
61
  outcomes?: Omit<VoiceOutcomeContractOptions<TSession>, 'contracts'> & {
61
62
  contracts: VoiceOutcomeContractDefinition[];
62
63
  };
@@ -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.202",
3
+ "version": "0.0.22-beta.204",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",