@absolutejs/voice 0.0.22-beta.181 → 0.0.22-beta.182

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 CHANGED
@@ -61,10 +61,30 @@ export type VoiceAgentModelOutput<TResult = unknown> = VoiceRouteResult<TResult>
61
61
  export type VoiceAgentModel<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
62
62
  generate: (input: VoiceAgentModelInput<TContext, TSession>) => Promise<VoiceAgentModelOutput<TResult>> | VoiceAgentModelOutput<TResult>;
63
63
  };
64
+ export type VoiceAgentSquadHandoffStatus = 'allowed' | 'blocked' | 'max-exceeded' | 'unknown-target';
65
+ export type VoiceAgentSquadStateHandoff = {
66
+ at: number;
67
+ fromAgentId: string;
68
+ metadata?: Record<string, unknown>;
69
+ originalTargetAgentId?: string;
70
+ reason?: string;
71
+ status: VoiceAgentSquadHandoffStatus;
72
+ summary?: string;
73
+ targetAgentId: string;
74
+ turnId: string;
75
+ };
76
+ export type VoiceAgentSquadState = {
77
+ agentId: string;
78
+ handoffCount: number;
79
+ handoffs: VoiceAgentSquadStateHandoff[];
80
+ lastHandoff?: VoiceAgentSquadStateHandoff;
81
+ previousAgentId?: string;
82
+ };
64
83
  export type VoiceAgentRunResult<TResult = unknown> = VoiceRouteResult<TResult> & {
65
84
  agentId: string;
66
85
  handoff?: VoiceAgentModelOutput<TResult>['handoff'];
67
86
  messages: VoiceAgentMessage[];
87
+ squad?: VoiceAgentSquadState;
68
88
  toolResults: VoiceAgentToolResult[];
69
89
  };
70
90
  export type VoiceAgentSquadHandoffPolicyResult<TResult = unknown> = {
@@ -120,8 +140,10 @@ export type VoiceAgentSquadOptions<TContext = unknown, TSession extends VoiceSes
120
140
  onHandoff?: (input: {
121
141
  context: TContext;
122
142
  fromAgentId: string;
143
+ metadata?: Record<string, unknown>;
123
144
  reason?: string;
124
145
  session: TSession;
146
+ summary?: string;
125
147
  targetAgentId: string;
126
148
  turn: VoiceTurnRecord;
127
149
  }) => Promise<void> | void;
@@ -1,10 +1,15 @@
1
- import type { VoiceAgent, VoiceAgentRunResult } from './agent';
1
+ import type { VoiceAgent, VoiceAgentRunResult, VoiceAgentSquadHandoffStatus } from './agent';
2
2
  import type { VoiceTraceEventStore } from './trace';
3
3
  import type { VoiceRouteResult, VoiceSessionHandle, VoiceSessionRecord } from './types';
4
4
  export type VoiceAgentSquadContractOutcome = 'assistant' | 'complete' | 'escalate' | 'no-answer' | 'transfer' | 'voicemail';
5
5
  export type VoiceAgentSquadHandoffExpectation = {
6
6
  fromAgentId?: string;
7
- status?: 'allowed' | 'blocked' | 'max-exceeded' | 'unknown-target';
7
+ metadata?: Record<string, unknown>;
8
+ reason?: string;
9
+ reasonIncludes?: string[];
10
+ status?: VoiceAgentSquadHandoffStatus;
11
+ summary?: string;
12
+ summaryIncludes?: string[];
8
13
  targetAgentId?: string;
9
14
  };
10
15
  export type VoiceAgentSquadTurnExpectation<TResult = unknown> = {
package/dist/index.d.ts CHANGED
@@ -110,7 +110,7 @@ export type { VoiceProviderStackChoice, VoiceProviderStackCapabilities, VoicePro
110
110
  export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
111
111
  export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingKindSummary, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind, VoiceRoutingSessionSummary, VoiceRoutingSessionSummaryOptions } from './resilienceRoutes';
112
112
  export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
113
- export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadHandoffPolicyResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
113
+ export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadHandoffPolicyResult, VoiceAgentSquadHandoffStatus, VoiceAgentSquadOptions, VoiceAgentSquadState, VoiceAgentSquadStateHandoff, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
114
114
  export type { VoiceAgentSquadContractDefinition, VoiceAgentSquadContractIssue, VoiceAgentSquadContractOutcome, VoiceAgentSquadContractReport, VoiceAgentSquadContractRunOptions, VoiceAgentSquadContractTurn, VoiceAgentSquadContractTurnReport, VoiceAgentSquadHandoffExpectation, VoiceAgentSquadTurnExpectation } from './agentSquadContract';
115
115
  export type { VoiceToolRetryDelay, VoiceToolRuntime, VoiceToolRuntimeExecuteInput, VoiceToolRuntimeOptions, VoiceToolRuntimeResult } from './toolRuntime';
116
116
  export type { VoiceToolContractCase, VoiceToolContractCaseReport, VoiceToolContractDefinition, VoiceToolContractExpectation, VoiceToolContractHandlerOptions, VoiceToolContractHTMLHandlerOptions, VoiceToolContractIssue, VoiceToolContractReport, VoiceToolContractRoutesOptions, VoiceToolContractSuiteReport } from './toolContract';
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 appendVoiceAgentTrace({
7259
+ await appendVoiceAgentSquadHandoff({
7217
7260
  agentId: options.id,
7218
- event: {
7219
- fromAgentId: agent.id,
7220
- metadata,
7221
- originalTargetAgentId,
7222
- reason: handoffReason,
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 appendVoiceAgentTrace({
7300
+ await appendVoiceAgentSquadHandoff({
7255
7301
  agentId: options.id,
7256
- event: {
7257
- fromAgentId: agent.id,
7258
- metadata,
7259
- originalTargetAgentId,
7260
- reason: handoffReason,
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 appendVoiceAgentTrace({
7350
+ await appendVoiceAgentSquadHandoff({
7300
7351
  agentId: options.id,
7301
- event: {
7302
- fromAgentId: agent.id,
7303
- metadata,
7304
- originalTargetAgentId: originalTargetAgentId === nextAgent.id ? undefined : originalTargetAgentId,
7305
- reason: handoffReason,
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 appendVoiceAgentTrace({
7391
+ await appendVoiceAgentSquadHandoff({
7342
7392
  agentId: options.id,
7343
- event: {
7344
- fromAgentId: agent.id,
7345
- metadata: result.handoff.metadata,
7346
- reason: result.handoff.reason,
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.181",
3
+ "version": "0.0.22-beta.182",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",