@absolutejs/voice 0.0.22-beta.67 → 0.0.22-beta.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from '.
10
10
  export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRuntime';
11
11
  export { createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract } from './toolContract';
12
12
  export { createVoiceTurnQualityHTMLHandler, createVoiceTurnQualityJSONHandler, createVoiceTurnQualityRoutes, renderVoiceTurnQualityHTML, summarizeVoiceTurnQuality } from './turnQuality';
13
+ export { createVoiceOutcomeContractHTMLHandler, createVoiceOutcomeContractJSONHandler, createVoiceOutcomeContractRoutes, renderVoiceOutcomeContractHTML, runVoiceOutcomeContractSuite } from './outcomeContract';
13
14
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
14
15
  export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
15
16
  export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
@@ -54,6 +55,7 @@ export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOpti
54
55
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
55
56
  export type { VoiceProviderCapabilityDefinition, VoiceProviderCapabilityHandlerOptions, VoiceProviderCapabilityHTMLHandlerOptions, VoiceProviderCapabilityKind, VoiceProviderCapabilityOptions, VoiceProviderCapabilityReport, VoiceProviderCapabilityRoutesOptions, VoiceProviderCapabilitySummary } from './providerCapabilities';
56
57
  export type { VoiceTurnQualityHTMLHandlerOptions, VoiceTurnQualityItem, VoiceTurnQualityOptions, VoiceTurnQualityReport, VoiceTurnQualityRoutesOptions, VoiceTurnQualityStatus } from './turnQuality';
58
+ export type { VoiceOutcomeContractDefinition, VoiceOutcomeContractHTMLHandlerOptions, VoiceOutcomeContractIssue, VoiceOutcomeContractOptions, VoiceOutcomeContractReport, VoiceOutcomeContractRoutesOptions, VoiceOutcomeContractStatus, VoiceOutcomeContractSuiteReport } from './outcomeContract';
57
59
  export type { VoiceOpsConsoleLink, VoiceOpsConsoleReport, VoiceOpsConsoleRoutesOptions } from './opsConsoleRoutes';
58
60
  export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
59
61
  export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind } from './resilienceRoutes';
package/dist/index.js CHANGED
@@ -10410,6 +10410,155 @@ var createVoiceTurnQualityRoutes = (options) => {
10410
10410
  }
10411
10411
  return routes;
10412
10412
  };
10413
+ // src/outcomeContract.ts
10414
+ import { Elysia as Elysia15 } from "elysia";
10415
+ var escapeHtml16 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
10416
+ var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
10417
+ var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
10418
+ var hydrateSessions = async (input) => {
10419
+ if (!input)
10420
+ return [];
10421
+ if (Array.isArray(input))
10422
+ return input;
10423
+ const summaries = await input.list();
10424
+ const sessions = await Promise.all(summaries.map((summary) => input.get(summary.id)));
10425
+ const hydrated = [];
10426
+ for (const session of sessions) {
10427
+ if (session) {
10428
+ hydrated.push(session);
10429
+ }
10430
+ }
10431
+ return hydrated;
10432
+ };
10433
+ var dispositionForSession = (session) => session.call?.disposition ?? (session.status === "completed" ? "completed" : undefined);
10434
+ var matchesDisposition = (disposition, expected) => expected === undefined || disposition === expected;
10435
+ var reportContract = (input) => {
10436
+ const { contract } = input;
10437
+ const sessions = input.sessions.filter((session) => (!contract.scenarioId || session.scenarioId === contract.scenarioId) && matchesDisposition(dispositionForSession(session), contract.expectedDisposition));
10438
+ const sessionIds = new Set(sessions.map((session) => session.id));
10439
+ const reviews = input.reviews.filter((review) => matchesDisposition(review.summary.outcome, contract.expectedDisposition));
10440
+ const tasks = input.tasks.filter((task) => matchesDisposition(task.outcome, contract.expectedDisposition));
10441
+ 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)));
10442
+ const events = input.events.filter((event) => {
10443
+ const eventSessionId = getPayloadString(event, "sessionId");
10444
+ const eventOutcome = getPayloadString(event, "outcome") ?? getPayloadString(event, "disposition");
10445
+ return (sessionIds.size === 0 || !eventSessionId || sessionIds.has(eventSessionId)) && (!contract.expectedDisposition || eventOutcome === contract.expectedDisposition);
10446
+ });
10447
+ const issues = [];
10448
+ const minSessions = contract.minSessions ?? 1;
10449
+ if (sessions.length < minSessions) {
10450
+ issues.push({
10451
+ code: "outcome.sessions_missing",
10452
+ message: `Expected at least ${minSessions} matching session(s), saw ${sessions.length}.`
10453
+ });
10454
+ }
10455
+ if (contract.requireReview !== false && reviews.length === 0) {
10456
+ issues.push({
10457
+ code: "outcome.review_missing",
10458
+ message: "Expected at least one matching review artifact."
10459
+ });
10460
+ }
10461
+ if (contract.requireTask && tasks.length < (contract.minTasks ?? 1)) {
10462
+ issues.push({
10463
+ code: "outcome.task_missing",
10464
+ message: `Expected at least ${contract.minTasks ?? 1} matching task(s), saw ${tasks.length}.`
10465
+ });
10466
+ }
10467
+ for (const action of contract.requireHandoffActions ?? []) {
10468
+ if (!handoffs.some((handoff) => handoff.action === action)) {
10469
+ issues.push({
10470
+ code: "outcome.handoff_missing",
10471
+ message: `Expected handoff action ${action}.`
10472
+ });
10473
+ }
10474
+ }
10475
+ for (const type of contract.requireIntegrationEvents ?? []) {
10476
+ if (!events.some((event) => event.type === type)) {
10477
+ issues.push({
10478
+ code: "outcome.integration_event_missing",
10479
+ message: `Expected integration event ${type}.`
10480
+ });
10481
+ }
10482
+ }
10483
+ return {
10484
+ contractId: contract.id,
10485
+ description: contract.description,
10486
+ issues,
10487
+ label: contract.label,
10488
+ matched: {
10489
+ handoffs: handoffs.length,
10490
+ integrationEvents: events.length,
10491
+ reviews: reviews.length,
10492
+ sessions: sessions.length,
10493
+ tasks: tasks.length
10494
+ },
10495
+ pass: issues.length === 0
10496
+ };
10497
+ };
10498
+ var runVoiceOutcomeContractSuite = async (options) => {
10499
+ const [sessions, reviews, tasks, events, handoffs] = await Promise.all([
10500
+ hydrateSessions(options.sessions),
10501
+ toList(options.reviews),
10502
+ toList(options.tasks),
10503
+ toList(options.events),
10504
+ toList(options.handoffs)
10505
+ ]);
10506
+ const contracts = options.contracts.map((contract) => reportContract({ contract, events, handoffs, reviews, sessions, tasks }));
10507
+ const passed = contracts.filter((contract) => contract.pass).length;
10508
+ const failed = contracts.length - passed;
10509
+ return {
10510
+ checkedAt: Date.now(),
10511
+ contracts,
10512
+ failed,
10513
+ passed,
10514
+ status: failed > 0 ? "fail" : "pass",
10515
+ total: contracts.length
10516
+ };
10517
+ };
10518
+ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
10519
+ const title = options.title ?? "Voice Outcome Contracts";
10520
+ const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
10521
+ <div class="contract-header">
10522
+ <div>
10523
+ <p class="eyebrow">${escapeHtml16(contract.contractId)}</p>
10524
+ <h2>${escapeHtml16(contract.label ?? contract.contractId)}</h2>
10525
+ ${contract.description ? `<p>${escapeHtml16(contract.description)}</p>` : ""}
10526
+ </div>
10527
+ <strong>${contract.pass ? "pass" : "fail"}</strong>
10528
+ </div>
10529
+ <div class="grid">
10530
+ <span>sessions ${String(contract.matched.sessions)}</span>
10531
+ <span>reviews ${String(contract.matched.reviews)}</span>
10532
+ <span>tasks ${String(contract.matched.tasks)}</span>
10533
+ <span>handoffs ${String(contract.matched.handoffs)}</span>
10534
+ <span>events ${String(contract.matched.integrationEvents)}</span>
10535
+ </div>
10536
+ ${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml16(issue.message)}</li>`).join("")}</ul>` : ""}
10537
+ </section>`).join("");
10538
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml16(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>${escapeHtml16(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>`;
10539
+ };
10540
+ var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
10541
+ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
10542
+ const report = await runVoiceOutcomeContractSuite(options);
10543
+ const render = options.render ?? ((input) => renderVoiceOutcomeContractHTML(input, options));
10544
+ return new Response(await render(report), {
10545
+ headers: {
10546
+ "Content-Type": "text/html; charset=utf-8",
10547
+ ...options.headers
10548
+ }
10549
+ });
10550
+ };
10551
+ var createVoiceOutcomeContractRoutes = (options) => {
10552
+ const path = options.path ?? "/api/outcome-contracts";
10553
+ const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
10554
+ const routes = new Elysia15({
10555
+ name: options.name ?? "absolutejs-voice-outcome-contracts"
10556
+ }).get(path, createVoiceOutcomeContractJSONHandler(options));
10557
+ if (htmlPath) {
10558
+ routes.get(htmlPath, createVoiceOutcomeContractHTMLHandler(options));
10559
+ }
10560
+ return routes;
10561
+ };
10413
10562
  // src/fileStore.ts
10414
10563
  import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "fs/promises";
10415
10564
  import { join } from "path";
@@ -12503,7 +12652,7 @@ var createVoiceMemoryStore = () => {
12503
12652
  return { get, getOrCreate, list, remove, set };
12504
12653
  };
12505
12654
  // src/opsWebhook.ts
12506
- import { Elysia as Elysia15 } from "elysia";
12655
+ import { Elysia as Elysia16 } from "elysia";
12507
12656
  var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
12508
12657
  var signVoiceOpsWebhookBody = async (input) => {
12509
12658
  const encoder = new TextEncoder;
@@ -12633,7 +12782,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
12633
12782
  };
12634
12783
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
12635
12784
  const path = options.path ?? "/api/voice-ops/webhook";
12636
- return new Elysia15().post(path, async ({ body, request, set }) => {
12785
+ return new Elysia16().post(path, async ({ body, request, set }) => {
12637
12786
  const bodyText = typeof body === "string" ? body : JSON.stringify(body);
12638
12787
  if (options.signingSecret) {
12639
12788
  const verification = await verifyVoiceOpsWebhookSignature({
@@ -14805,6 +14954,7 @@ export {
14805
14954
  runVoiceSessionEvals,
14806
14955
  runVoiceScenarioFixtureEvals,
14807
14956
  runVoiceScenarioEvals,
14957
+ runVoiceOutcomeContractSuite,
14808
14958
  resolveVoiceTraceRedactionOptions,
14809
14959
  resolveVoiceSTTRoutingStrategy,
14810
14960
  resolveVoiceRuntimePreset,
@@ -14831,6 +14981,7 @@ export {
14831
14981
  renderVoiceQualityHTML,
14832
14982
  renderVoiceProviderHealthHTML,
14833
14983
  renderVoiceProviderCapabilityHTML,
14984
+ renderVoiceOutcomeContractHTML,
14834
14985
  renderVoiceOpsConsoleHTML,
14835
14986
  renderVoiceHandoffHealthHTML,
14836
14987
  renderVoiceEvalHTML,
@@ -14937,6 +15088,9 @@ export {
14937
15088
  createVoicePostgresReviewStore,
14938
15089
  createVoicePostgresIntegrationEventStore,
14939
15090
  createVoicePostgresExternalObjectMapStore,
15091
+ createVoiceOutcomeContractRoutes,
15092
+ createVoiceOutcomeContractJSONHandler,
15093
+ createVoiceOutcomeContractHTMLHandler,
14940
15094
  createVoiceOpsWebhookSink,
14941
15095
  createVoiceOpsWebhookReceiverRoutes,
14942
15096
  createVoiceOpsWebhookEnvelope,
@@ -0,0 +1,112 @@
1
+ import { Elysia } from 'elysia';
2
+ import type { StoredVoiceHandoffDelivery, VoiceCallDisposition, VoiceHandoffAction, VoiceHandoffDeliveryStore, VoiceSessionRecord, VoiceSessionStore } from './types';
3
+ import type { StoredVoiceIntegrationEvent, StoredVoiceOpsTask, VoiceIntegrationEventType } from './ops';
4
+ import type { StoredVoiceCallReviewArtifact } from './testing/review';
5
+ export type VoiceOutcomeContractStatus = 'pass' | 'fail';
6
+ export type VoiceOutcomeContractDefinition = {
7
+ description?: string;
8
+ expectedDisposition?: VoiceCallDisposition;
9
+ id: string;
10
+ label?: string;
11
+ minSessions?: number;
12
+ minTasks?: number;
13
+ requireHandoffActions?: VoiceHandoffAction[];
14
+ requireIntegrationEvents?: VoiceIntegrationEventType[];
15
+ requireReview?: boolean;
16
+ requireTask?: boolean;
17
+ scenarioId?: string;
18
+ };
19
+ export type VoiceOutcomeContractIssue = {
20
+ code: string;
21
+ message: string;
22
+ };
23
+ export type VoiceOutcomeContractReport = {
24
+ contractId: string;
25
+ description?: string;
26
+ issues: VoiceOutcomeContractIssue[];
27
+ label?: string;
28
+ matched: {
29
+ handoffs: number;
30
+ integrationEvents: number;
31
+ reviews: number;
32
+ sessions: number;
33
+ tasks: number;
34
+ };
35
+ pass: boolean;
36
+ };
37
+ export type VoiceOutcomeContractSuiteReport = {
38
+ checkedAt: number;
39
+ contracts: VoiceOutcomeContractReport[];
40
+ failed: number;
41
+ passed: number;
42
+ status: VoiceOutcomeContractStatus;
43
+ total: number;
44
+ };
45
+ type ListStore<T> = {
46
+ list: () => Promise<T[]> | T[];
47
+ };
48
+ export type VoiceOutcomeContractOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = {
49
+ contracts: VoiceOutcomeContractDefinition[];
50
+ events?: StoredVoiceIntegrationEvent[] | ListStore<StoredVoiceIntegrationEvent>;
51
+ handoffs?: StoredVoiceHandoffDelivery[] | VoiceHandoffDeliveryStore;
52
+ reviews?: StoredVoiceCallReviewArtifact[] | ListStore<StoredVoiceCallReviewArtifact>;
53
+ sessions?: TSession[] | VoiceSessionStore<TSession>;
54
+ tasks?: StoredVoiceOpsTask[] | ListStore<StoredVoiceOpsTask>;
55
+ };
56
+ export type VoiceOutcomeContractHTMLHandlerOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = VoiceOutcomeContractOptions<TSession> & {
57
+ headers?: HeadersInit;
58
+ render?: (report: VoiceOutcomeContractSuiteReport) => string | Promise<string>;
59
+ title?: string;
60
+ };
61
+ export type VoiceOutcomeContractRoutesOptions<TSession extends VoiceSessionRecord = VoiceSessionRecord> = VoiceOutcomeContractHTMLHandlerOptions<TSession> & {
62
+ htmlPath?: false | string;
63
+ name?: string;
64
+ path?: string;
65
+ };
66
+ export declare const runVoiceOutcomeContractSuite: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractOptions<TSession>) => Promise<VoiceOutcomeContractSuiteReport>;
67
+ export declare const renderVoiceOutcomeContractHTML: (report: VoiceOutcomeContractSuiteReport, options?: {
68
+ title?: string;
69
+ }) => string;
70
+ export declare const createVoiceOutcomeContractJSONHandler: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractOptions<TSession>) => () => Promise<VoiceOutcomeContractSuiteReport>;
71
+ export declare const createVoiceOutcomeContractHTMLHandler: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractHTMLHandlerOptions<TSession>) => () => Promise<Response>;
72
+ export declare const createVoiceOutcomeContractRoutes: <TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: VoiceOutcomeContractRoutesOptions<TSession>) => Elysia<"", {
73
+ decorator: {};
74
+ store: {};
75
+ derive: {};
76
+ resolve: {};
77
+ }, {
78
+ typebox: {};
79
+ error: {};
80
+ }, {
81
+ schema: {};
82
+ standaloneSchema: {};
83
+ macro: {};
84
+ macroFn: {};
85
+ parser: {};
86
+ response: {};
87
+ }, {
88
+ [x: string]: {
89
+ get: {
90
+ body: unknown;
91
+ params: {};
92
+ query: unknown;
93
+ headers: unknown;
94
+ response: {
95
+ 200: VoiceOutcomeContractSuiteReport;
96
+ };
97
+ };
98
+ };
99
+ }, {
100
+ derive: {};
101
+ resolve: {};
102
+ schema: {};
103
+ standaloneSchema: {};
104
+ response: {};
105
+ }, {
106
+ derive: {};
107
+ resolve: {};
108
+ schema: {};
109
+ standaloneSchema: {};
110
+ response: {};
111
+ }>;
112
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.67",
3
+ "version": "0.0.22-beta.68",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",