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

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,7 @@ 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';
10
11
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
11
12
  export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
12
13
  export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
@@ -53,6 +54,7 @@ export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQua
53
54
  export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind } from './resilienceRoutes';
54
55
  export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
55
56
  export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
57
+ export type { VoiceToolRetryDelay, VoiceToolRuntime, VoiceToolRuntimeExecuteInput, VoiceToolRuntimeOptions, VoiceToolRuntimeResult } from './toolRuntime';
56
58
  export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
57
59
  export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
58
60
  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,202 @@ 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
+ };
9731
9949
  // src/fileStore.ts
9732
9950
  import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "fs/promises";
9733
9951
  import { join } from "path";
@@ -10193,7 +10411,7 @@ var getMessageToolCalls = (message) => {
10193
10411
  return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
10194
10412
  };
10195
10413
  var createHTTPError = (provider, response) => new Error(`${provider} voice assistant model failed: HTTP ${response.status}`);
10196
- var sleep4 = (ms) => new Promise((resolve2) => {
10414
+ var sleep5 = (ms) => new Promise((resolve2) => {
10197
10415
  setTimeout(resolve2, ms);
10198
10416
  });
10199
10417
  var errorMessage = (error) => error instanceof Error ? error.message : String(error);
@@ -10902,7 +11120,7 @@ var createGeminiVoiceAssistantModel = (options) => {
10902
11120
  break;
10903
11121
  }
10904
11122
  const retryAfter = Number(response.headers.get("retry-after"));
10905
- await sleep4(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt);
11123
+ await sleep5(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt);
10906
11124
  }
10907
11125
  if (!response) {
10908
11126
  throw new Error("Gemini voice assistant model failed: no response");
@@ -10957,7 +11175,7 @@ var getTimeoutMs = (options, provider) => {
10957
11175
  const timeoutMs = options.providerProfiles?.[provider]?.timeoutMs ?? options.timeoutMs;
10958
11176
  return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
10959
11177
  };
10960
- var withTimeout = async (input) => {
11178
+ var withTimeout2 = async (input) => {
10961
11179
  if (!input.timeoutMs) {
10962
11180
  return input.run();
10963
11181
  }
@@ -11141,7 +11359,7 @@ var createVoiceSTTProviderRouter = (options) => {
11141
11359
  }
11142
11360
  const startedAt = Date.now();
11143
11361
  try {
11144
- const session = await withTimeout({
11362
+ const session = await withTimeout2({
11145
11363
  kind: "stt",
11146
11364
  operation: "open",
11147
11365
  provider,
@@ -11218,7 +11436,7 @@ var createVoiceTTSProviderRouter = (options) => {
11218
11436
  throw new Error(`Voice TTS provider ${provider} is not configured.`);
11219
11437
  }
11220
11438
  const startedAt = Date.now();
11221
- const session = await withTimeout({
11439
+ const session = await withTimeout2({
11222
11440
  kind: "tts",
11223
11441
  operation: "open",
11224
11442
  provider,
@@ -11297,7 +11515,7 @@ var createVoiceTTSProviderRouter = (options) => {
11297
11515
  }
11298
11516
  const startedAt = Date.now();
11299
11517
  try {
11300
- await withTimeout({
11518
+ await withTimeout2({
11301
11519
  kind: "tts",
11302
11520
  operation: "send",
11303
11521
  provider,
@@ -14194,6 +14412,8 @@ export {
14194
14412
  createVoiceTraceHTTPSink,
14195
14413
  createVoiceTraceEventId,
14196
14414
  createVoiceTraceEvent,
14415
+ createVoiceToolRuntime,
14416
+ createVoiceToolIdempotencyKey,
14197
14417
  createVoiceTaskUpdatedEvent,
14198
14418
  createVoiceTaskSLABreachedEvent,
14199
14419
  createVoiceTaskCreatedEvent,
@@ -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.62",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",