@absolutejs/voice 0.0.22-beta.61 → 0.0.22-beta.63

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
@@ -1,5 +1,6 @@
1
1
  import type { VoiceOnTurnObjectHandler, VoiceRouteResult, VoiceSessionHandle, VoiceSessionRecord, VoiceTurnRecord } from './types';
2
2
  import type { VoiceTraceEventStore } from './trace';
3
+ import type { VoiceToolRuntime } from './toolRuntime';
3
4
  export type VoiceAgentMessageRole = 'assistant' | 'system' | 'tool' | 'user';
4
5
  export type VoiceAgentMessage = {
5
6
  content: string;
@@ -86,6 +87,7 @@ export type VoiceAgentOptions<TContext = unknown, TSession extends VoiceSessionR
86
87
  turn: VoiceTurnRecord;
87
88
  }) => Promise<string | undefined> | string | undefined);
88
89
  trace?: VoiceTraceEventStore;
90
+ toolRuntime?: VoiceToolRuntime<TContext, TSession, TResult>;
89
91
  tools?: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
90
92
  };
91
93
  export type VoiceAgentSquadOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export { compareVoiceEvalBaseline, createVoiceFileEvalBaselineStore, createVoice
7
7
  export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult } from './workflowContract';
8
8
  export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceSessions, summarizeVoiceSessionReplay } from './sessionReplay';
9
9
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
10
+ export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRuntime';
11
+ export { createVoiceToolContract, createVoiceToolRuntimeContractDefaults, runVoiceToolContract } from './toolContract';
10
12
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
11
13
  export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
12
14
  export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
@@ -53,6 +55,8 @@ export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQua
53
55
  export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind } from './resilienceRoutes';
54
56
  export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
55
57
  export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
58
+ export type { VoiceToolRetryDelay, VoiceToolRuntime, VoiceToolRuntimeExecuteInput, VoiceToolRuntimeOptions, VoiceToolRuntimeResult } from './toolRuntime';
59
+ export type { VoiceToolContractCase, VoiceToolContractCaseReport, VoiceToolContractDefinition, VoiceToolContractExpectation, VoiceToolContractIssue, VoiceToolContractReport } from './toolContract';
56
60
  export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
57
61
  export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
58
62
  export type { VoiceOutcomeRecipe, VoiceOutcomeRecipeName, VoiceOutcomeRecipeOptions } from './outcomeRecipes';
package/dist/index.js CHANGED
@@ -5627,16 +5627,34 @@ var createVoiceAgent = (options) => {
5627
5627
  }
5628
5628
  try {
5629
5629
  const toolStartedAt = Date.now();
5630
- const result = await tool.execute({
5630
+ const runtimeResult = options.toolRuntime ? await options.toolRuntime.execute({
5631
+ api: input.api,
5632
+ args: toolCall.args,
5633
+ context: input.context,
5634
+ session: input.session,
5635
+ tool,
5636
+ toolCallId: toolCall.id,
5637
+ turn: input.turn
5638
+ }) : undefined;
5639
+ if (runtimeResult?.status === "error") {
5640
+ throw new Error(runtimeResult.error);
5641
+ }
5642
+ const result = runtimeResult?.result ?? await tool.execute({
5631
5643
  api: input.api,
5632
5644
  args: toolCall.args,
5633
5645
  context: input.context,
5634
5646
  session: input.session,
5635
5647
  turn: input.turn
5636
5648
  });
5637
- const content = tool.resultToMessage?.(result) ?? formatToolResult(result);
5649
+ const content = runtimeResult?.content ?? tool.resultToMessage?.(result) ?? formatToolResult(result);
5638
5650
  toolResults.push({
5639
5651
  content,
5652
+ metadata: runtimeResult ? {
5653
+ attempts: runtimeResult.attempts,
5654
+ elapsedMs: runtimeResult.elapsedMs,
5655
+ idempotencyKey: runtimeResult.idempotencyKey,
5656
+ timedOut: runtimeResult.timedOut
5657
+ } : undefined,
5640
5658
  result,
5641
5659
  status: "ok",
5642
5660
  toolCallId: toolCall.id,
@@ -5646,7 +5664,11 @@ var createVoiceAgent = (options) => {
5646
5664
  agentId: options.id,
5647
5665
  event: {
5648
5666
  elapsedMs: Date.now() - toolStartedAt,
5667
+ runtimeElapsedMs: runtimeResult?.elapsedMs,
5668
+ attempts: runtimeResult?.attempts,
5669
+ idempotencyKey: runtimeResult?.idempotencyKey,
5649
5670
  status: "ok",
5671
+ timedOut: runtimeResult?.timedOut,
5650
5672
  toolCallId: toolCall.id,
5651
5673
  toolName: tool.name
5652
5674
  },
@@ -9728,6 +9750,359 @@ var createVoiceWorkflowContractHandler = (input) => {
9728
9750
  return result;
9729
9751
  };
9730
9752
  };
9753
+ // src/toolRuntime.ts
9754
+ var toErrorMessage4 = (error) => error instanceof Error ? error.message : String(error);
9755
+ var sleep4 = (ms) => new Promise((resolve2) => {
9756
+ setTimeout(resolve2, ms);
9757
+ });
9758
+ var formatToolResult2 = (result) => {
9759
+ if (typeof result === "string") {
9760
+ return result;
9761
+ }
9762
+ if (result === undefined) {
9763
+ return "";
9764
+ }
9765
+ try {
9766
+ return JSON.stringify(result);
9767
+ } catch {
9768
+ return String(result);
9769
+ }
9770
+ };
9771
+ var withTimeout = async (promise, timeoutMs) => {
9772
+ if (!timeoutMs || timeoutMs <= 0) {
9773
+ return promise;
9774
+ }
9775
+ let timer;
9776
+ try {
9777
+ return await Promise.race([
9778
+ promise,
9779
+ new Promise((_, reject) => {
9780
+ timer = setTimeout(() => {
9781
+ reject(new Error(`Voice tool timed out after ${timeoutMs}ms`));
9782
+ }, timeoutMs);
9783
+ })
9784
+ ]);
9785
+ } finally {
9786
+ if (timer) {
9787
+ clearTimeout(timer);
9788
+ }
9789
+ }
9790
+ };
9791
+ var resolveRetryDelay = (retryDelayMs, input) => typeof retryDelayMs === "function" ? retryDelayMs(input) : retryDelayMs ?? 0;
9792
+ var createVoiceToolRuntime = (options = {}) => {
9793
+ const inFlight = new Map;
9794
+ const completed = new Map;
9795
+ const execute = async (input) => {
9796
+ const idempotencyKey = options.idempotencyKey?.({
9797
+ args: input.args,
9798
+ context: input.context,
9799
+ session: input.session,
9800
+ toolCallId: input.toolCallId,
9801
+ toolName: input.tool.name,
9802
+ turn: input.turn
9803
+ });
9804
+ if (idempotencyKey) {
9805
+ const cached = completed.get(idempotencyKey);
9806
+ if (cached && cached.expiresAt > Date.now()) {
9807
+ return cached.result;
9808
+ }
9809
+ if (cached) {
9810
+ completed.delete(idempotencyKey);
9811
+ }
9812
+ const existing = inFlight.get(idempotencyKey);
9813
+ if (existing) {
9814
+ return await existing;
9815
+ }
9816
+ }
9817
+ const runPromise = (async () => {
9818
+ const startedAt = Date.now();
9819
+ const maxRetries = Math.max(0, options.maxRetries ?? 0);
9820
+ let attempts = 0;
9821
+ let lastError;
9822
+ let timedOut = false;
9823
+ for (let retry = 0;retry <= maxRetries; retry += 1) {
9824
+ attempts = retry + 1;
9825
+ try {
9826
+ const result = await withTimeout(Promise.resolve(input.tool.execute({
9827
+ api: input.api,
9828
+ args: input.args,
9829
+ context: input.context,
9830
+ session: input.session,
9831
+ turn: input.turn
9832
+ })), options.timeoutMs);
9833
+ const elapsedMs2 = Date.now() - startedAt;
9834
+ const content = input.tool.resultToMessage?.(result) ?? formatToolResult2(result);
9835
+ const runtimeResult2 = {
9836
+ attempts,
9837
+ content,
9838
+ elapsedMs: elapsedMs2,
9839
+ idempotencyKey,
9840
+ result,
9841
+ status: "ok",
9842
+ timedOut,
9843
+ toolCallId: input.toolCallId,
9844
+ toolName: input.tool.name
9845
+ };
9846
+ await options.trace?.append({
9847
+ at: Date.now(),
9848
+ payload: {
9849
+ attempts,
9850
+ elapsedMs: elapsedMs2,
9851
+ idempotencyKey,
9852
+ status: "ok",
9853
+ toolCallId: input.toolCallId,
9854
+ toolName: input.tool.name
9855
+ },
9856
+ scenarioId: input.session.scenarioId,
9857
+ sessionId: input.session.id,
9858
+ turnId: input.turn.id,
9859
+ type: "agent.tool"
9860
+ });
9861
+ return runtimeResult2;
9862
+ } catch (error) {
9863
+ lastError = error;
9864
+ timedOut ||= toErrorMessage4(error).includes("timed out");
9865
+ if (retry < maxRetries) {
9866
+ const delay = resolveRetryDelay(options.retryDelayMs, {
9867
+ attempt: attempts,
9868
+ error,
9869
+ toolName: input.tool.name
9870
+ });
9871
+ if (delay > 0) {
9872
+ await sleep4(delay);
9873
+ }
9874
+ }
9875
+ }
9876
+ }
9877
+ const elapsedMs = Date.now() - startedAt;
9878
+ const runtimeResult = {
9879
+ attempts,
9880
+ elapsedMs,
9881
+ error: toErrorMessage4(lastError),
9882
+ idempotencyKey,
9883
+ status: "error",
9884
+ timedOut,
9885
+ toolCallId: input.toolCallId,
9886
+ toolName: input.tool.name
9887
+ };
9888
+ await options.trace?.append({
9889
+ at: Date.now(),
9890
+ payload: {
9891
+ attempts,
9892
+ elapsedMs,
9893
+ error: runtimeResult.error,
9894
+ idempotencyKey,
9895
+ status: "error",
9896
+ timedOut,
9897
+ toolCallId: input.toolCallId,
9898
+ toolName: input.tool.name
9899
+ },
9900
+ scenarioId: input.session.scenarioId,
9901
+ sessionId: input.session.id,
9902
+ turnId: input.turn.id,
9903
+ type: "agent.tool"
9904
+ });
9905
+ return runtimeResult;
9906
+ })();
9907
+ if (idempotencyKey) {
9908
+ inFlight.set(idempotencyKey, runPromise);
9909
+ runPromise.then((result) => {
9910
+ if (options.idempotencyTtlMs && options.idempotencyTtlMs > 0) {
9911
+ completed.set(idempotencyKey, {
9912
+ expiresAt: Date.now() + options.idempotencyTtlMs,
9913
+ result
9914
+ });
9915
+ }
9916
+ }).finally(() => {
9917
+ inFlight.delete(idempotencyKey);
9918
+ });
9919
+ }
9920
+ return await runPromise;
9921
+ };
9922
+ return {
9923
+ execute,
9924
+ wrapTool: (tool) => ({
9925
+ ...tool,
9926
+ execute: async (input) => {
9927
+ const result = await execute({
9928
+ ...input,
9929
+ tool
9930
+ });
9931
+ if (result.status === "error") {
9932
+ throw new Error(result.error);
9933
+ }
9934
+ return result.result;
9935
+ }
9936
+ })
9937
+ };
9938
+ };
9939
+ var createVoiceToolIdempotencyKey = (input) => {
9940
+ const args = typeof input.args === "string" ? input.args : JSON.stringify(input.args ?? null);
9941
+ return [
9942
+ input.sessionId,
9943
+ input.turnId,
9944
+ input.toolCallId ?? "no-call-id",
9945
+ input.toolName,
9946
+ args
9947
+ ].join(":");
9948
+ };
9949
+ // src/toolContract.ts
9950
+ var createDefaultSession = (contractId, caseId) => createVoiceSessionRecord(`tool-contract-${contractId}-${caseId}`);
9951
+ var createDefaultTurn = (caseId) => ({
9952
+ committedAt: Date.now(),
9953
+ id: `turn-${caseId}`,
9954
+ text: `Run tool contract case ${caseId}.`,
9955
+ transcripts: []
9956
+ });
9957
+ var defaultApi = {};
9958
+ var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
9959
+ var evaluateExpectation = (input) => {
9960
+ const issues = [];
9961
+ const expect = input.expect;
9962
+ if (!expect) {
9963
+ return issues;
9964
+ }
9965
+ if (expect.expectStatus && input.status !== expect.expectStatus) {
9966
+ issues.push({
9967
+ caseId: input.caseId,
9968
+ code: "tool.status_mismatch",
9969
+ message: `Expected ${expect.expectStatus}, saw ${input.status}.`
9970
+ });
9971
+ }
9972
+ if (typeof expect.expectedAttempts === "number" && input.attempts !== expect.expectedAttempts) {
9973
+ issues.push({
9974
+ caseId: input.caseId,
9975
+ code: "tool.attempt_mismatch",
9976
+ message: `Expected ${expect.expectedAttempts} attempts, saw ${input.attempts}.`
9977
+ });
9978
+ }
9979
+ if (expect.expectedResult !== undefined && !sameJSON(input.result, expect.expectedResult)) {
9980
+ issues.push({
9981
+ caseId: input.caseId,
9982
+ code: "tool.result_mismatch",
9983
+ message: "Tool result did not match expected result."
9984
+ });
9985
+ }
9986
+ if (expect.expectedErrorIncludes && !input.error?.includes(expect.expectedErrorIncludes)) {
9987
+ issues.push({
9988
+ caseId: input.caseId,
9989
+ code: "tool.error_mismatch",
9990
+ message: `Expected error to include ${expect.expectedErrorIncludes}.`
9991
+ });
9992
+ }
9993
+ if (typeof expect.expectTimedOut === "boolean" && input.timedOut !== expect.expectTimedOut) {
9994
+ issues.push({
9995
+ caseId: input.caseId,
9996
+ code: "tool.timeout_mismatch",
9997
+ message: `Expected timedOut=${String(expect.expectTimedOut)}, saw ${String(input.timedOut)}.`
9998
+ });
9999
+ }
10000
+ if (typeof expect.maxElapsedMs === "number" && input.elapsedMs > expect.maxElapsedMs) {
10001
+ issues.push({
10002
+ caseId: input.caseId,
10003
+ code: "tool.elapsed_exceeded",
10004
+ message: `Expected elapsed <= ${expect.maxElapsedMs}ms, saw ${input.elapsedMs}ms.`
10005
+ });
10006
+ }
10007
+ return issues;
10008
+ };
10009
+ var runVoiceToolContract = async (definition) => {
10010
+ const cases = [];
10011
+ for (const testCase of definition.cases) {
10012
+ const session = testCase.session ?? createDefaultSession(definition.id, testCase.id);
10013
+ const turn = testCase.turn ?? createDefaultTurn(testCase.id);
10014
+ const context = testCase.context ?? {};
10015
+ const runtimeOptions = {
10016
+ ...definition.defaultRuntime,
10017
+ ...testCase.runtime
10018
+ };
10019
+ const runtime = createVoiceToolRuntime(runtimeOptions);
10020
+ const toolCall = {
10021
+ args: testCase.args,
10022
+ id: testCase.toolCallId ?? testCase.id,
10023
+ name: definition.tool.name
10024
+ };
10025
+ const executeOnce = () => runtime.execute({
10026
+ api: defaultApi,
10027
+ args: toolCall.args,
10028
+ context,
10029
+ session,
10030
+ tool: definition.tool,
10031
+ toolCallId: toolCall.id,
10032
+ turn
10033
+ });
10034
+ const result = await executeOnce();
10035
+ let issues2 = evaluateExpectation({
10036
+ attempts: result.attempts,
10037
+ caseId: testCase.id,
10038
+ elapsedMs: result.elapsedMs,
10039
+ error: result.error,
10040
+ expect: testCase.expect,
10041
+ result: result.result,
10042
+ status: result.status,
10043
+ timedOut: result.timedOut
10044
+ });
10045
+ if (testCase.expect?.expectIdempotent) {
10046
+ const second = await executeOnce();
10047
+ if (second.result !== result.result && !sameJSON(second.result, result.result)) {
10048
+ issues2.push({
10049
+ caseId: testCase.id,
10050
+ code: "tool.idempotency_result_mismatch",
10051
+ message: "Repeated idempotent execution returned a different result."
10052
+ });
10053
+ }
10054
+ if (second.idempotencyKey !== result.idempotencyKey) {
10055
+ issues2.push({
10056
+ caseId: testCase.id,
10057
+ code: "tool.idempotency_key_mismatch",
10058
+ message: "Repeated idempotent execution used a different idempotency key."
10059
+ });
10060
+ }
10061
+ }
10062
+ cases.push({
10063
+ attempts: result.attempts,
10064
+ caseId: testCase.id,
10065
+ elapsedMs: result.elapsedMs,
10066
+ error: result.error,
10067
+ issues: issues2,
10068
+ label: testCase.label,
10069
+ pass: issues2.length === 0,
10070
+ status: result.status,
10071
+ timedOut: result.timedOut
10072
+ });
10073
+ }
10074
+ const issues = cases.flatMap((testCase) => testCase.issues);
10075
+ return {
10076
+ cases,
10077
+ contractId: definition.id,
10078
+ issues,
10079
+ pass: issues.length === 0,
10080
+ toolName: definition.tool.name
10081
+ };
10082
+ };
10083
+ var createVoiceToolContract = (definition) => ({
10084
+ assert: async () => {
10085
+ const report = await runVoiceToolContract(definition);
10086
+ if (!report.pass) {
10087
+ throw new Error(`Voice tool contract ${definition.id} failed: ${report.issues.map((issue) => issue.message).join(" ")}`);
10088
+ }
10089
+ return report;
10090
+ },
10091
+ definition,
10092
+ run: () => runVoiceToolContract(definition)
10093
+ });
10094
+ var createVoiceToolRuntimeContractDefaults = () => ({
10095
+ idempotencyKey: ({ args, session, toolCallId, toolName, turn }) => createVoiceToolIdempotencyKey({
10096
+ args,
10097
+ sessionId: session.id,
10098
+ toolCallId,
10099
+ toolName,
10100
+ turnId: turn.id
10101
+ }),
10102
+ idempotencyTtlMs: 60000,
10103
+ maxRetries: 1,
10104
+ timeoutMs: 5000
10105
+ });
9731
10106
  // src/fileStore.ts
9732
10107
  import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "fs/promises";
9733
10108
  import { join } from "path";
@@ -10193,7 +10568,7 @@ var getMessageToolCalls = (message) => {
10193
10568
  return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
10194
10569
  };
10195
10570
  var createHTTPError = (provider, response) => new Error(`${provider} voice assistant model failed: HTTP ${response.status}`);
10196
- var sleep4 = (ms) => new Promise((resolve2) => {
10571
+ var sleep5 = (ms) => new Promise((resolve2) => {
10197
10572
  setTimeout(resolve2, ms);
10198
10573
  });
10199
10574
  var errorMessage = (error) => error instanceof Error ? error.message : String(error);
@@ -10902,7 +11277,7 @@ var createGeminiVoiceAssistantModel = (options) => {
10902
11277
  break;
10903
11278
  }
10904
11279
  const retryAfter = Number(response.headers.get("retry-after"));
10905
- await sleep4(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt);
11280
+ await sleep5(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt);
10906
11281
  }
10907
11282
  if (!response) {
10908
11283
  throw new Error("Gemini voice assistant model failed: no response");
@@ -10957,7 +11332,7 @@ var getTimeoutMs = (options, provider) => {
10957
11332
  const timeoutMs = options.providerProfiles?.[provider]?.timeoutMs ?? options.timeoutMs;
10958
11333
  return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
10959
11334
  };
10960
- var withTimeout = async (input) => {
11335
+ var withTimeout2 = async (input) => {
10961
11336
  if (!input.timeoutMs) {
10962
11337
  return input.run();
10963
11338
  }
@@ -11141,7 +11516,7 @@ var createVoiceSTTProviderRouter = (options) => {
11141
11516
  }
11142
11517
  const startedAt = Date.now();
11143
11518
  try {
11144
- const session = await withTimeout({
11519
+ const session = await withTimeout2({
11145
11520
  kind: "stt",
11146
11521
  operation: "open",
11147
11522
  provider,
@@ -11218,7 +11593,7 @@ var createVoiceTTSProviderRouter = (options) => {
11218
11593
  throw new Error(`Voice TTS provider ${provider} is not configured.`);
11219
11594
  }
11220
11595
  const startedAt = Date.now();
11221
- const session = await withTimeout({
11596
+ const session = await withTimeout2({
11222
11597
  kind: "tts",
11223
11598
  operation: "open",
11224
11599
  provider,
@@ -11297,7 +11672,7 @@ var createVoiceTTSProviderRouter = (options) => {
11297
11672
  }
11298
11673
  const startedAt = Date.now();
11299
11674
  try {
11300
- await withTimeout({
11675
+ await withTimeout2({
11301
11676
  kind: "tts",
11302
11677
  operation: "send",
11303
11678
  provider,
@@ -14116,6 +14491,7 @@ export {
14116
14491
  startVoiceOpsTask,
14117
14492
  shapeTelephonyAssistantText,
14118
14493
  selectVoiceTraceEventsForPrune,
14494
+ runVoiceToolContract,
14119
14495
  runVoiceSessionEvals,
14120
14496
  runVoiceScenarioFixtureEvals,
14121
14497
  runVoiceScenarioEvals,
@@ -14194,6 +14570,10 @@ export {
14194
14570
  createVoiceTraceHTTPSink,
14195
14571
  createVoiceTraceEventId,
14196
14572
  createVoiceTraceEvent,
14573
+ createVoiceToolRuntimeContractDefaults,
14574
+ createVoiceToolRuntime,
14575
+ createVoiceToolIdempotencyKey,
14576
+ createVoiceToolContract,
14197
14577
  createVoiceTaskUpdatedEvent,
14198
14578
  createVoiceTaskSLABreachedEvent,
14199
14579
  createVoiceTaskCreatedEvent,
@@ -0,0 +1,61 @@
1
+ import type { VoiceAgentTool } from './agent';
2
+ import { type VoiceToolRuntimeOptions } from './toolRuntime';
3
+ import type { VoiceSessionRecord, VoiceTurnRecord } from './types';
4
+ export type VoiceToolContractExpectation = {
5
+ expectedAttempts?: number;
6
+ expectedErrorIncludes?: string;
7
+ expectedResult?: unknown;
8
+ expectIdempotent?: boolean;
9
+ expectStatus?: 'error' | 'ok';
10
+ expectTimedOut?: boolean;
11
+ maxElapsedMs?: number;
12
+ };
13
+ export type VoiceToolContractCase<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown> = {
14
+ args: TArgs;
15
+ context?: TContext;
16
+ expect?: VoiceToolContractExpectation;
17
+ id: string;
18
+ label?: string;
19
+ runtime?: VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>;
20
+ session?: TSession;
21
+ toolCallId?: string;
22
+ turn?: VoiceTurnRecord;
23
+ };
24
+ export type VoiceToolContractDefinition<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown> = {
25
+ cases: Array<VoiceToolContractCase<TContext, TSession, TArgs, TToolResult, TRouteResult>>;
26
+ defaultRuntime?: VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>;
27
+ description?: string;
28
+ id: string;
29
+ label?: string;
30
+ tool: VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>;
31
+ };
32
+ export type VoiceToolContractIssue = {
33
+ caseId: string;
34
+ code: string;
35
+ message: string;
36
+ };
37
+ export type VoiceToolContractCaseReport = {
38
+ attempts: number;
39
+ caseId: string;
40
+ elapsedMs: number;
41
+ error?: string;
42
+ issues: VoiceToolContractIssue[];
43
+ label?: string;
44
+ pass: boolean;
45
+ status: 'error' | 'ok';
46
+ timedOut: boolean;
47
+ };
48
+ export type VoiceToolContractReport = {
49
+ cases: VoiceToolContractCaseReport[];
50
+ contractId: string;
51
+ issues: VoiceToolContractIssue[];
52
+ pass: boolean;
53
+ toolName: string;
54
+ };
55
+ 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>;
56
+ 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>) => {
57
+ assert: () => Promise<VoiceToolContractReport>;
58
+ definition: VoiceToolContractDefinition<TContext, TSession, TArgs, TToolResult, TRouteResult>;
59
+ run: () => Promise<VoiceToolContractReport>;
60
+ };
61
+ export declare const createVoiceToolRuntimeContractDefaults: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown>() => VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>;
@@ -0,0 +1,50 @@
1
+ import type { VoiceAgentTool, VoiceAgentToolResult } from './agent';
2
+ import type { VoiceSessionHandle, VoiceSessionRecord, VoiceTurnRecord } from './types';
3
+ import type { VoiceTraceEventStore } from './trace';
4
+ export type VoiceToolRetryDelay = number | ((input: {
5
+ attempt: number;
6
+ error: unknown;
7
+ toolName: string;
8
+ }) => number);
9
+ export type VoiceToolRuntimeOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown> = {
10
+ idempotencyKey?: (input: {
11
+ args: unknown;
12
+ context: TContext;
13
+ session: TSession;
14
+ toolCallId?: string;
15
+ toolName: string;
16
+ turn: VoiceTurnRecord;
17
+ }) => string | undefined;
18
+ idempotencyTtlMs?: number;
19
+ maxRetries?: number;
20
+ retryDelayMs?: VoiceToolRetryDelay;
21
+ timeoutMs?: number;
22
+ trace?: VoiceTraceEventStore;
23
+ };
24
+ export type VoiceToolRuntimeExecuteInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TArgs = Record<string, unknown>, TToolResult = unknown, TRouteResult = unknown> = {
25
+ api: VoiceSessionHandle<TContext, TSession, TRouteResult>;
26
+ args: TArgs;
27
+ context: TContext;
28
+ session: TSession;
29
+ tool: VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>;
30
+ toolCallId?: string;
31
+ turn: VoiceTurnRecord;
32
+ };
33
+ export type VoiceToolRuntimeResult<TToolResult = unknown> = VoiceAgentToolResult<TToolResult> & {
34
+ attempts: number;
35
+ elapsedMs: number;
36
+ idempotencyKey?: string;
37
+ timedOut: boolean;
38
+ };
39
+ export type VoiceToolRuntime<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown> = {
40
+ execute: <TArgs = Record<string, unknown>, TToolResult = unknown>(input: VoiceToolRuntimeExecuteInput<TContext, TSession, TArgs, TToolResult, TRouteResult>) => Promise<VoiceToolRuntimeResult<TToolResult>>;
41
+ wrapTool: <TArgs = Record<string, unknown>, TToolResult = unknown>(tool: VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>) => VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TRouteResult>;
42
+ };
43
+ export declare const createVoiceToolRuntime: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TRouteResult = unknown>(options?: VoiceToolRuntimeOptions<TContext, TSession, TRouteResult>) => VoiceToolRuntime<TContext, TSession, TRouteResult>;
44
+ export declare const createVoiceToolIdempotencyKey: (input: {
45
+ args: unknown;
46
+ sessionId: string;
47
+ toolCallId?: string;
48
+ toolName: string;
49
+ turnId: string;
50
+ }) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.61",
3
+ "version": "0.0.22-beta.63",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",