@absolutejs/voice 0.0.22-beta.123 → 0.0.22-beta.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/agentSquadContract.d.ts +64 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +193 -2
- package/dist/productionReadiness.d.ts +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -593,6 +593,54 @@ voice({
|
|
|
593
593
|
|
|
594
594
|
For production call centers, pass `handoffPolicy` to keep routing code-owned instead of dashboard-owned. The policy can allow a handoff, reroute it to a different specialist, merge handoff metadata, summarize the reason for the target agent, or block the handoff and return an escalation. Squad traces mark each handoff as `allowed`, `blocked`, `unknown-target`, or `max-exceeded`, so support teams can audit why a caller moved between specialists.
|
|
595
595
|
|
|
596
|
+
Use `runVoiceAgentSquadContract(...)` in tests or readiness checks when you need proof that a specialist graph still routes correctly:
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
import {
|
|
600
|
+
createVoiceMemoryTraceEventStore,
|
|
601
|
+
runVoiceAgentSquadContract
|
|
602
|
+
} from '@absolutejs/voice';
|
|
603
|
+
|
|
604
|
+
const trace = createVoiceMemoryTraceEventStore();
|
|
605
|
+
const frontDesk = createVoiceAgentSquad({
|
|
606
|
+
id: 'front-desk',
|
|
607
|
+
defaultAgentId: 'support',
|
|
608
|
+
agents: [supportAgent, billingAgent],
|
|
609
|
+
trace
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const report = await runVoiceAgentSquadContract({
|
|
613
|
+
context: {},
|
|
614
|
+
squad: frontDesk,
|
|
615
|
+
trace,
|
|
616
|
+
contract: {
|
|
617
|
+
id: 'billing-route',
|
|
618
|
+
scenarioId: 'billing-route',
|
|
619
|
+
turns: [
|
|
620
|
+
{
|
|
621
|
+
text: 'I have a billing question.',
|
|
622
|
+
expect: {
|
|
623
|
+
finalAgentId: 'billing',
|
|
624
|
+
outcome: 'assistant',
|
|
625
|
+
assistantIncludes: ['billing'],
|
|
626
|
+
handoffs: [
|
|
627
|
+
{
|
|
628
|
+
fromAgentId: 'support',
|
|
629
|
+
targetAgentId: 'billing',
|
|
630
|
+
status: 'allowed'
|
|
631
|
+
}
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
]
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
if (!report.pass) {
|
|
640
|
+
throw new Error(report.issues.map((issue) => issue.message).join('\n'));
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
596
644
|
## Traces And Replay
|
|
597
645
|
|
|
598
646
|
Use trace stores when you want every call to be inspectable outside a hosted platform. Trace events are append-only records for model passes, tool calls, handoffs, agent results, call lifecycle, turn timing, errors, and cost telemetry.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { VoiceAgent, VoiceAgentRunResult } from './agent';
|
|
2
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
3
|
+
import type { VoiceRouteResult, VoiceSessionHandle, VoiceSessionRecord } from './types';
|
|
4
|
+
export type VoiceAgentSquadContractOutcome = 'assistant' | 'complete' | 'escalate' | 'no-answer' | 'transfer' | 'voicemail';
|
|
5
|
+
export type VoiceAgentSquadHandoffExpectation = {
|
|
6
|
+
fromAgentId?: string;
|
|
7
|
+
status?: 'allowed' | 'blocked' | 'max-exceeded' | 'unknown-target';
|
|
8
|
+
targetAgentId?: string;
|
|
9
|
+
};
|
|
10
|
+
export type VoiceAgentSquadTurnExpectation<TResult = unknown> = {
|
|
11
|
+
assistantIncludes?: string[];
|
|
12
|
+
finalAgentId?: string;
|
|
13
|
+
handoffs?: VoiceAgentSquadHandoffExpectation[];
|
|
14
|
+
outcome?: VoiceAgentSquadContractOutcome;
|
|
15
|
+
result?: (input: {
|
|
16
|
+
result: TResult | undefined;
|
|
17
|
+
routeResult: VoiceRouteResult<TResult>;
|
|
18
|
+
}) => VoiceAgentSquadContractIssue[];
|
|
19
|
+
transferTarget?: string;
|
|
20
|
+
};
|
|
21
|
+
export type VoiceAgentSquadContractTurn<TResult = unknown> = {
|
|
22
|
+
expect?: VoiceAgentSquadTurnExpectation<TResult>;
|
|
23
|
+
id?: string;
|
|
24
|
+
text: string;
|
|
25
|
+
};
|
|
26
|
+
export type VoiceAgentSquadContractDefinition<TResult = unknown> = {
|
|
27
|
+
description?: string;
|
|
28
|
+
id: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
scenarioId?: string;
|
|
31
|
+
turns: Array<VoiceAgentSquadContractTurn<TResult>>;
|
|
32
|
+
};
|
|
33
|
+
export type VoiceAgentSquadContractIssue = {
|
|
34
|
+
code: string;
|
|
35
|
+
message: string;
|
|
36
|
+
turnId?: string;
|
|
37
|
+
};
|
|
38
|
+
export type VoiceAgentSquadContractTurnReport<TResult = unknown> = {
|
|
39
|
+
agentId: string;
|
|
40
|
+
handoffs: VoiceAgentSquadHandoffExpectation[];
|
|
41
|
+
issues: VoiceAgentSquadContractIssue[];
|
|
42
|
+
outcome?: VoiceAgentSquadContractOutcome;
|
|
43
|
+
pass: boolean;
|
|
44
|
+
result: VoiceAgentRunResult<TResult>;
|
|
45
|
+
turnId: string;
|
|
46
|
+
};
|
|
47
|
+
export type VoiceAgentSquadContractReport<TResult = unknown> = {
|
|
48
|
+
contractId: string;
|
|
49
|
+
issues: VoiceAgentSquadContractIssue[];
|
|
50
|
+
pass: boolean;
|
|
51
|
+
scenarioId?: string;
|
|
52
|
+
sessionId: string;
|
|
53
|
+
turns: Array<VoiceAgentSquadContractTurnReport<TResult>>;
|
|
54
|
+
};
|
|
55
|
+
export type VoiceAgentSquadContractRunOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
56
|
+
api?: VoiceSessionHandle<TContext, TSession, TResult>;
|
|
57
|
+
context: TContext;
|
|
58
|
+
contract: VoiceAgentSquadContractDefinition<TResult>;
|
|
59
|
+
session?: TSession;
|
|
60
|
+
squad: VoiceAgent<TContext, TSession, TResult>;
|
|
61
|
+
trace?: VoiceTraceEventStore;
|
|
62
|
+
};
|
|
63
|
+
export declare const runVoiceAgentSquadContract: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceAgentSquadContractRunOptions<TContext, TSession, TResult>) => Promise<VoiceAgentSquadContractReport<TResult>>;
|
|
64
|
+
export declare const assertVoiceAgentSquadContract: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceAgentSquadContractRunOptions<TContext, TSession, TResult>) => Promise<VoiceAgentSquadContractReport<TResult>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { createVoiceSimulationSuiteRoutes, renderVoiceSimulationSuiteHTML, runVo
|
|
|
11
11
|
export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult } from './workflowContract';
|
|
12
12
|
export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceSessions, summarizeVoiceSessionReplay } from './sessionReplay';
|
|
13
13
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
|
|
14
|
+
export { assertVoiceAgentSquadContract, runVoiceAgentSquadContract } from './agentSquadContract';
|
|
14
15
|
export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRuntime';
|
|
15
16
|
export { createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract } from './toolContract';
|
|
16
17
|
export { createVoiceTurnLatencyHTMLHandler, createVoiceTurnLatencyJSONHandler, createVoiceTurnLatencyRoutes, renderVoiceTurnLatencyHTML, summarizeVoiceTurnLatency } from './turnLatency';
|
|
@@ -82,6 +83,7 @@ export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQua
|
|
|
82
83
|
export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingKindSummary, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind, VoiceRoutingSessionSummary, VoiceRoutingSessionSummaryOptions } from './resilienceRoutes';
|
|
83
84
|
export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
|
|
84
85
|
export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadHandoffPolicyResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
|
|
86
|
+
export type { VoiceAgentSquadContractDefinition, VoiceAgentSquadContractIssue, VoiceAgentSquadContractOutcome, VoiceAgentSquadContractReport, VoiceAgentSquadContractRunOptions, VoiceAgentSquadContractTurn, VoiceAgentSquadContractTurnReport, VoiceAgentSquadHandoffExpectation, VoiceAgentSquadTurnExpectation } from './agentSquadContract';
|
|
85
87
|
export type { VoiceToolRetryDelay, VoiceToolRuntime, VoiceToolRuntimeExecuteInput, VoiceToolRuntimeOptions, VoiceToolRuntimeResult } from './toolRuntime';
|
|
86
88
|
export type { VoiceToolContractCase, VoiceToolContractCaseReport, VoiceToolContractDefinition, VoiceToolContractExpectation, VoiceToolContractHandlerOptions, VoiceToolContractHTMLHandlerOptions, VoiceToolContractIssue, VoiceToolContractReport, VoiceToolContractRoutesOptions, VoiceToolContractSuiteReport } from './toolContract';
|
|
87
89
|
export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
|
package/dist/index.js
CHANGED
|
@@ -9831,6 +9831,12 @@ var resolveCarriers = async (options, input) => {
|
|
|
9831
9831
|
providers: [...providers]
|
|
9832
9832
|
});
|
|
9833
9833
|
};
|
|
9834
|
+
var resolveAgentSquadContracts = async (options, input) => {
|
|
9835
|
+
if (options.agentSquadContracts === false || options.agentSquadContracts === undefined) {
|
|
9836
|
+
return;
|
|
9837
|
+
}
|
|
9838
|
+
return typeof options.agentSquadContracts === "function" ? await options.agentSquadContracts(input) : options.agentSquadContracts;
|
|
9839
|
+
};
|
|
9834
9840
|
var summarizeLiveLatency = (events, options) => {
|
|
9835
9841
|
const warnAfterMs = options.liveLatencyWarnAfterMs ?? 1800;
|
|
9836
9842
|
const failAfterMs = options.liveLatencyFailAfterMs ?? 3200;
|
|
@@ -9853,7 +9859,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
9853
9859
|
const routingEvents = listVoiceRoutingEvents(events);
|
|
9854
9860
|
const routingSessions = summarizeVoiceRoutingSessions(routingEvents);
|
|
9855
9861
|
const liveLatency = summarizeLiveLatency(events, options);
|
|
9856
|
-
const [quality, providers, sessions, handoffs, carriers] = await Promise.all([
|
|
9862
|
+
const [quality, providers, sessions, handoffs, carriers, agentSquadContracts] = await Promise.all([
|
|
9857
9863
|
evaluateVoiceQuality({ events }),
|
|
9858
9864
|
Promise.all([
|
|
9859
9865
|
summarizeVoiceProviderHealth({
|
|
@@ -9871,7 +9877,8 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
9871
9877
|
]).then((groups) => groups.flat()),
|
|
9872
9878
|
summarizeVoiceSessions({ events, status: "all" }),
|
|
9873
9879
|
summarizeVoiceHandoffHealth({ events }),
|
|
9874
|
-
resolveCarriers(options, { query, request })
|
|
9880
|
+
resolveCarriers(options, { query, request }),
|
|
9881
|
+
resolveAgentSquadContracts(options, { query, request })
|
|
9875
9882
|
]);
|
|
9876
9883
|
const degradedProviders = providers.filter((provider) => provider.status === "degraded" || provider.status === "rate-limited" || provider.status === "suppressed").length;
|
|
9877
9884
|
const failedSessions = sessions.filter((session) => session.status === "failed").length;
|
|
@@ -9980,6 +9987,28 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
9980
9987
|
status: carrierStatus(carriers),
|
|
9981
9988
|
warnings: carriers.summary.warnings
|
|
9982
9989
|
} : undefined;
|
|
9990
|
+
const agentSquadContractSummary = agentSquadContracts ? {
|
|
9991
|
+
failed: agentSquadContracts.filter((report) => !report.pass).length,
|
|
9992
|
+
passed: agentSquadContracts.filter((report) => report.pass).length,
|
|
9993
|
+
status: agentSquadContracts.some((report) => !report.pass) ? "fail" : agentSquadContracts.length === 0 ? "warn" : "pass",
|
|
9994
|
+
total: agentSquadContracts.length
|
|
9995
|
+
} : undefined;
|
|
9996
|
+
if (agentSquadContractSummary) {
|
|
9997
|
+
checks.push({
|
|
9998
|
+
detail: agentSquadContractSummary.status === "pass" ? `${agentSquadContractSummary.passed} agent squad contract(s) are passing.` : agentSquadContractSummary.total === 0 ? "No agent squad contracts are configured." : `${agentSquadContractSummary.failed} agent squad contract(s) failed.`,
|
|
9999
|
+
href: options.links?.agentSquadContracts ?? "/agent-squad-contract",
|
|
10000
|
+
label: "Agent squad contracts",
|
|
10001
|
+
status: agentSquadContractSummary.status,
|
|
10002
|
+
value: `${agentSquadContractSummary.passed}/${agentSquadContractSummary.total}`,
|
|
10003
|
+
actions: agentSquadContractSummary.status === "pass" ? [] : [
|
|
10004
|
+
{
|
|
10005
|
+
description: "Open the specialist routing contract report and inspect failing handoff paths.",
|
|
10006
|
+
href: options.links?.agentSquadContracts ?? "/agent-squad-contract",
|
|
10007
|
+
label: "Open squad contracts"
|
|
10008
|
+
}
|
|
10009
|
+
]
|
|
10010
|
+
});
|
|
10011
|
+
}
|
|
9983
10012
|
if (carriers && carrierSummary) {
|
|
9984
10013
|
checks.push({
|
|
9985
10014
|
detail: carrierSummary.status === "pass" ? "Configured carrier setup and contract checks are passing." : `${carrierSummary.failing} carrier(s) failing, ${carrierSummary.warnings} warning(s).`,
|
|
@@ -10000,6 +10029,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
10000
10029
|
checkedAt: Date.now(),
|
|
10001
10030
|
checks,
|
|
10002
10031
|
links: {
|
|
10032
|
+
agentSquadContracts: "/agent-squad-contract",
|
|
10003
10033
|
carriers: "/carriers",
|
|
10004
10034
|
handoffs: "/handoffs",
|
|
10005
10035
|
handoffRetry: "/api/voice-handoffs/retry",
|
|
@@ -10011,6 +10041,7 @@ var buildVoiceProductionReadinessReport = async (options, input = {}) => {
|
|
|
10011
10041
|
},
|
|
10012
10042
|
status: rollupStatus(checks),
|
|
10013
10043
|
summary: {
|
|
10044
|
+
agentSquadContracts: agentSquadContractSummary,
|
|
10014
10045
|
carriers: carrierSummary,
|
|
10015
10046
|
handoffs: {
|
|
10016
10047
|
failed: handoffs.failed,
|
|
@@ -12733,6 +12764,164 @@ var createVoiceWorkflowContractHandler = (input) => {
|
|
|
12733
12764
|
return result;
|
|
12734
12765
|
};
|
|
12735
12766
|
};
|
|
12767
|
+
// src/agentSquadContract.ts
|
|
12768
|
+
var normalizeIncludes = (value) => value.trim().toLowerCase();
|
|
12769
|
+
var resolveOutcome3 = (result) => {
|
|
12770
|
+
if (result.complete)
|
|
12771
|
+
return "complete";
|
|
12772
|
+
if (result.transfer)
|
|
12773
|
+
return "transfer";
|
|
12774
|
+
if (result.escalate)
|
|
12775
|
+
return "escalate";
|
|
12776
|
+
if (result.voicemail)
|
|
12777
|
+
return "voicemail";
|
|
12778
|
+
if (result.noAnswer)
|
|
12779
|
+
return "no-answer";
|
|
12780
|
+
if (result.assistantText?.trim())
|
|
12781
|
+
return "assistant";
|
|
12782
|
+
return;
|
|
12783
|
+
};
|
|
12784
|
+
var getPayloadString2 = (event, key) => {
|
|
12785
|
+
const value = event.payload[key];
|
|
12786
|
+
return typeof value === "string" ? value : undefined;
|
|
12787
|
+
};
|
|
12788
|
+
var toHandoffExpectation = (event) => ({
|
|
12789
|
+
fromAgentId: getPayloadString2(event, "fromAgentId"),
|
|
12790
|
+
status: getPayloadString2(event, "status"),
|
|
12791
|
+
targetAgentId: getPayloadString2(event, "targetAgentId")
|
|
12792
|
+
});
|
|
12793
|
+
var createContractApi = (session) => ({
|
|
12794
|
+
close: async () => {},
|
|
12795
|
+
commitTurn: async () => {},
|
|
12796
|
+
complete: async () => {},
|
|
12797
|
+
connect: async () => {},
|
|
12798
|
+
disconnect: async () => {},
|
|
12799
|
+
escalate: async () => {},
|
|
12800
|
+
fail: async () => {},
|
|
12801
|
+
id: session.id,
|
|
12802
|
+
markNoAnswer: async () => {},
|
|
12803
|
+
markVoicemail: async () => {},
|
|
12804
|
+
receiveAudio: async () => {},
|
|
12805
|
+
snapshot: async () => session,
|
|
12806
|
+
transfer: async () => {}
|
|
12807
|
+
});
|
|
12808
|
+
var createContractTurn = (turn, index) => ({
|
|
12809
|
+
committedAt: Date.now(),
|
|
12810
|
+
id: turn.id ?? `turn-${index + 1}`,
|
|
12811
|
+
text: turn.text,
|
|
12812
|
+
transcripts: []
|
|
12813
|
+
});
|
|
12814
|
+
var appendIssue = (issues, issue, turnId) => {
|
|
12815
|
+
issues.push({
|
|
12816
|
+
...issue,
|
|
12817
|
+
turnId: issue.turnId ?? turnId
|
|
12818
|
+
});
|
|
12819
|
+
};
|
|
12820
|
+
var runVoiceAgentSquadContract = async (options) => {
|
|
12821
|
+
const session = options.session ?? createVoiceSessionRecord(`agent-squad-contract-${options.contract.id}`, options.contract.scenarioId ?? options.contract.id);
|
|
12822
|
+
const api = options.api ?? createContractApi(session);
|
|
12823
|
+
const turnReports = [];
|
|
12824
|
+
const issues = [];
|
|
12825
|
+
for (const [index, contractTurn] of options.contract.turns.entries()) {
|
|
12826
|
+
const turn = createContractTurn(contractTurn, index);
|
|
12827
|
+
const result = await options.squad.run({
|
|
12828
|
+
api,
|
|
12829
|
+
context: options.context,
|
|
12830
|
+
session,
|
|
12831
|
+
turn
|
|
12832
|
+
});
|
|
12833
|
+
const handoffEvents = await options.trace?.list({
|
|
12834
|
+
sessionId: session.id,
|
|
12835
|
+
turnId: turn.id,
|
|
12836
|
+
type: "agent.handoff"
|
|
12837
|
+
}) ?? [];
|
|
12838
|
+
const handoffs = handoffEvents.map(toHandoffExpectation);
|
|
12839
|
+
const turnIssues = [];
|
|
12840
|
+
const expected = contractTurn.expect;
|
|
12841
|
+
const outcome = resolveOutcome3(result);
|
|
12842
|
+
if (expected?.finalAgentId && result.agentId !== expected.finalAgentId) {
|
|
12843
|
+
appendIssue(turnIssues, {
|
|
12844
|
+
code: "agent_squad.final_agent_mismatch",
|
|
12845
|
+
message: `Expected final agent ${expected.finalAgentId}, saw ${result.agentId}.`
|
|
12846
|
+
}, turn.id);
|
|
12847
|
+
}
|
|
12848
|
+
if (expected?.outcome && outcome !== expected.outcome) {
|
|
12849
|
+
appendIssue(turnIssues, {
|
|
12850
|
+
code: "agent_squad.outcome_mismatch",
|
|
12851
|
+
message: `Expected outcome ${expected.outcome}, saw ${outcome ?? "none"}.`
|
|
12852
|
+
}, turn.id);
|
|
12853
|
+
}
|
|
12854
|
+
if (expected?.transferTarget && result.transfer?.target !== expected.transferTarget) {
|
|
12855
|
+
appendIssue(turnIssues, {
|
|
12856
|
+
code: "agent_squad.transfer_target_mismatch",
|
|
12857
|
+
message: `Expected transfer target ${expected.transferTarget}, saw ${result.transfer?.target ?? "none"}.`
|
|
12858
|
+
}, turn.id);
|
|
12859
|
+
}
|
|
12860
|
+
const assistantText = normalizeIncludes(result.assistantText ?? "");
|
|
12861
|
+
for (const expectedText of expected?.assistantIncludes ?? []) {
|
|
12862
|
+
if (!assistantText.includes(normalizeIncludes(expectedText))) {
|
|
12863
|
+
appendIssue(turnIssues, {
|
|
12864
|
+
code: "agent_squad.assistant_text_missing",
|
|
12865
|
+
message: `Expected assistant text to include: ${expectedText}`
|
|
12866
|
+
}, turn.id);
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
for (const [handoffIndex, expectedHandoff] of (expected?.handoffs ?? []).entries()) {
|
|
12870
|
+
const actual = handoffs[handoffIndex];
|
|
12871
|
+
if (!actual) {
|
|
12872
|
+
appendIssue(turnIssues, {
|
|
12873
|
+
code: "agent_squad.handoff_missing",
|
|
12874
|
+
message: `Expected handoff ${handoffIndex + 1}, but no trace event was recorded.`
|
|
12875
|
+
}, turn.id);
|
|
12876
|
+
continue;
|
|
12877
|
+
}
|
|
12878
|
+
for (const key of ["fromAgentId", "status", "targetAgentId"]) {
|
|
12879
|
+
if (expectedHandoff[key] && actual[key] !== expectedHandoff[key]) {
|
|
12880
|
+
appendIssue(turnIssues, {
|
|
12881
|
+
code: "agent_squad.handoff_mismatch",
|
|
12882
|
+
message: `Expected handoff ${handoffIndex + 1} ${key} ${expectedHandoff[key]}, saw ${actual[key] ?? "none"}.`
|
|
12883
|
+
}, turn.id);
|
|
12884
|
+
}
|
|
12885
|
+
}
|
|
12886
|
+
}
|
|
12887
|
+
for (const issue of expected?.result?.({
|
|
12888
|
+
result: result.result,
|
|
12889
|
+
routeResult: result
|
|
12890
|
+
}) ?? []) {
|
|
12891
|
+
appendIssue(turnIssues, issue, turn.id);
|
|
12892
|
+
}
|
|
12893
|
+
issues.push(...turnIssues);
|
|
12894
|
+
turnReports.push({
|
|
12895
|
+
agentId: result.agentId,
|
|
12896
|
+
handoffs,
|
|
12897
|
+
issues: turnIssues,
|
|
12898
|
+
outcome,
|
|
12899
|
+
pass: turnIssues.length === 0,
|
|
12900
|
+
result,
|
|
12901
|
+
turnId: turn.id
|
|
12902
|
+
});
|
|
12903
|
+
session.turns.push({
|
|
12904
|
+
...turn,
|
|
12905
|
+
assistantText: result.assistantText,
|
|
12906
|
+
result: result.result
|
|
12907
|
+
});
|
|
12908
|
+
}
|
|
12909
|
+
return {
|
|
12910
|
+
contractId: options.contract.id,
|
|
12911
|
+
issues,
|
|
12912
|
+
pass: issues.length === 0,
|
|
12913
|
+
scenarioId: options.contract.scenarioId,
|
|
12914
|
+
sessionId: session.id,
|
|
12915
|
+
turns: turnReports
|
|
12916
|
+
};
|
|
12917
|
+
};
|
|
12918
|
+
var assertVoiceAgentSquadContract = async (options) => {
|
|
12919
|
+
const report = await runVoiceAgentSquadContract(options);
|
|
12920
|
+
if (!report.pass) {
|
|
12921
|
+
throw new Error(`Voice agent squad contract ${report.contractId} failed: ${report.issues.map((issue) => issue.message).join(" ")}`);
|
|
12922
|
+
}
|
|
12923
|
+
return report;
|
|
12924
|
+
};
|
|
12736
12925
|
// src/turnLatency.ts
|
|
12737
12926
|
import { Elysia as Elysia21 } from "elysia";
|
|
12738
12927
|
var DEFAULT_WARN_AFTER_MS = 1800;
|
|
@@ -19779,6 +19968,7 @@ export {
|
|
|
19779
19968
|
runVoiceOutcomeContractSuite,
|
|
19780
19969
|
runVoiceCampaignProof,
|
|
19781
19970
|
runVoiceCampaignDialerProof,
|
|
19971
|
+
runVoiceAgentSquadContract,
|
|
19782
19972
|
resolveVoiceTraceRedactionOptions,
|
|
19783
19973
|
resolveVoiceTelephonyOutcome,
|
|
19784
19974
|
resolveVoiceSTTRoutingStrategy,
|
|
@@ -20056,6 +20246,7 @@ export {
|
|
|
20056
20246
|
buildVoiceDiagnosticsMarkdown,
|
|
20057
20247
|
buildVoiceCampaignObservabilityReport,
|
|
20058
20248
|
assignVoiceOpsTask,
|
|
20249
|
+
assertVoiceAgentSquadContract,
|
|
20059
20250
|
applyVoiceTelephonyOutcome,
|
|
20060
20251
|
applyVoiceOpsTaskPolicy,
|
|
20061
20252
|
applyVoiceOpsTaskAssignmentRule,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import { type VoiceTelephonyCarrierMatrixInput } from './telephony/matrix';
|
|
3
3
|
import type { VoiceTraceEventStore } from './trace';
|
|
4
|
+
import type { VoiceAgentSquadContractReport } from './agentSquadContract';
|
|
4
5
|
export type VoiceProductionReadinessStatus = 'fail' | 'pass' | 'warn';
|
|
5
6
|
export type VoiceProductionReadinessAction = {
|
|
6
7
|
description?: string;
|
|
@@ -20,6 +21,7 @@ export type VoiceProductionReadinessReport = {
|
|
|
20
21
|
checkedAt: number;
|
|
21
22
|
checks: VoiceProductionReadinessCheck[];
|
|
22
23
|
links: {
|
|
24
|
+
agentSquadContracts?: string;
|
|
23
25
|
carriers?: string;
|
|
24
26
|
handoffs?: string;
|
|
25
27
|
handoffRetry?: string;
|
|
@@ -30,6 +32,12 @@ export type VoiceProductionReadinessReport = {
|
|
|
30
32
|
};
|
|
31
33
|
status: VoiceProductionReadinessStatus;
|
|
32
34
|
summary: {
|
|
35
|
+
agentSquadContracts?: {
|
|
36
|
+
failed: number;
|
|
37
|
+
passed: number;
|
|
38
|
+
status: VoiceProductionReadinessStatus;
|
|
39
|
+
total: number;
|
|
40
|
+
};
|
|
33
41
|
carriers?: {
|
|
34
42
|
failing: number;
|
|
35
43
|
providers: number;
|
|
@@ -66,6 +74,10 @@ export type VoiceProductionReadinessReport = {
|
|
|
66
74
|
};
|
|
67
75
|
};
|
|
68
76
|
export type VoiceProductionReadinessRoutesOptions = {
|
|
77
|
+
agentSquadContracts?: false | readonly VoiceAgentSquadContractReport[] | ((input: {
|
|
78
|
+
query: Record<string, unknown>;
|
|
79
|
+
request: Request;
|
|
80
|
+
}) => Promise<readonly VoiceAgentSquadContractReport[]> | readonly VoiceAgentSquadContractReport[]);
|
|
69
81
|
carriers?: false | readonly VoiceTelephonyCarrierMatrixInput[] | ((input: {
|
|
70
82
|
query: Record<string, unknown>;
|
|
71
83
|
request: Request;
|