@absolutejs/voice 0.0.22-beta.670 → 0.0.22-beta.671

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.
@@ -13,6 +13,8 @@ export type OpenAIVoiceAssistantModelOptions = {
13
13
  model?: string;
14
14
  onUsage?: (usage: Record<string, unknown>) => Promise<void> | void;
15
15
  temperature?: number;
16
+ /** Maximum silence between SSE body chunks. Default 10 seconds. */
17
+ streamInactivityMs?: number;
16
18
  /**
17
19
  * Hard cap on a single /responses stream. Aborts via AbortController if
18
20
  * the stream doesn't complete in time. Default 60s — generous for typical
@@ -62,6 +64,9 @@ export type VoiceProviderRouterEvent<TProvider extends string = string> = {
62
64
  suppressedUntil?: number;
63
65
  status: "error" | "fallback" | "success";
64
66
  timedOut?: boolean;
67
+ timeoutKind?: "latency-budget" | "stream-inactivity";
68
+ timeoutMs?: number;
69
+ totalElapsedMs?: number;
65
70
  };
66
71
  export type VoiceProviderRouterFallbackMode = "never" | "provider-error" | "rate-limit";
67
72
  export type VoiceProviderRouterStrategy = "balanced" | "ordered" | "prefer-cheapest" | "prefer-fastest" | "prefer-selected" | "quality-first";
package/dist/index.js CHANGED
@@ -41730,13 +41730,14 @@ var buildVoiceIOProviderRouterTraceEvent = (options) => ({
41730
41730
  metadata: options.metadata,
41731
41731
  payload: {
41732
41732
  ...options.event,
41733
+ recoverable: options.event.status === "error" && options.event.fallbackProvider !== undefined,
41733
41734
  ...options.payload ?? {},
41734
41735
  providerStatus: options.event.status
41735
41736
  },
41736
41737
  scenarioId: options.scenarioId,
41737
41738
  sessionId: options.sessionId,
41738
41739
  turnId: options.turnId,
41739
- type: options.type ?? "session.error"
41740
+ type: options.type ?? (options.event.status === "error" ? "session.error" : "provider.decision")
41740
41741
  });
41741
41742
  var buildVoiceProviderRouterTraceEvent = (options) => ({
41742
41743
  at: options.at ?? options.event.at,
@@ -41744,13 +41745,14 @@ var buildVoiceProviderRouterTraceEvent = (options) => ({
41744
41745
  metadata: options.metadata,
41745
41746
  payload: {
41746
41747
  ...options.event,
41748
+ recoverable: options.event.status === "error" && options.event.fallbackProvider !== undefined,
41747
41749
  ...options.payload ?? {},
41748
41750
  providerStatus: options.event.status
41749
41751
  },
41750
41752
  scenarioId: options.scenarioId,
41751
41753
  sessionId: options.sessionId,
41752
41754
  turnId: options.turnId,
41753
- type: options.type ?? "session.error"
41755
+ type: options.type ?? (options.event.status === "error" ? "session.error" : "provider.decision")
41754
41756
  });
41755
41757
  // src/core/workflowContract.ts
41756
41758
  var getObject2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
@@ -46363,6 +46365,15 @@ class VoiceProviderTimeoutError extends Error {
46363
46365
  this.timeoutMs = timeoutMs;
46364
46366
  }
46365
46367
  }
46368
+
46369
+ class VoiceProviderStreamInactivityError extends Error {
46370
+ inactivityMs;
46371
+ constructor(inactivityMs) {
46372
+ super(`SSE read inactivity timeout (${inactivityMs}ms with no chunk)`);
46373
+ this.name = "VoiceProviderStreamInactivityError";
46374
+ this.inactivityMs = inactivityMs;
46375
+ }
46376
+ }
46366
46377
  var getMessageToolCalls = (message) => {
46367
46378
  const toolCalls = message.metadata?.toolCalls;
46368
46379
  return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
@@ -46609,7 +46620,7 @@ var createVoiceProviderRouter = (options) => {
46609
46620
  }
46610
46621
  }
46611
46622
  };
46612
- const runProviderWithEmptyRetry = async (provider, model, input, index, selectedProvider, startedAt) => {
46623
+ const runProviderWithEmptyRetry = async (provider, model, input, index, selectedProvider, startedAt, routingStartedAt) => {
46613
46624
  let output = await runProvider(provider, model, input);
46614
46625
  let emptyAttempt = 0;
46615
46626
  while (emptyAttempt < emptyCompletionRetries && isEmptyModelOutput(output)) {
@@ -46621,7 +46632,8 @@ var createVoiceProviderRouter = (options) => {
46621
46632
  emptyCompletion: true,
46622
46633
  provider,
46623
46634
  selectedProvider,
46624
- status: "success"
46635
+ status: "success",
46636
+ totalElapsedMs: Date.now() - routingStartedAt
46625
46637
  }, input);
46626
46638
  output = await runProvider(provider, model, input);
46627
46639
  }
@@ -46633,6 +46645,7 @@ var createVoiceProviderRouter = (options) => {
46633
46645
  if (!selectedProvider || order.length === 0) {
46634
46646
  throw new Error("Voice provider router has no available providers.");
46635
46647
  }
46648
+ const routingStartedAt = Date.now();
46636
46649
  let lastError;
46637
46650
  for (const [index, provider] of order.entries()) {
46638
46651
  const model = options.providers[provider];
@@ -46641,7 +46654,7 @@ var createVoiceProviderRouter = (options) => {
46641
46654
  }
46642
46655
  const startedAt = Date.now();
46643
46656
  try {
46644
- const output = await runProviderWithEmptyRetry(provider, model, input, index, selectedProvider, startedAt);
46657
+ const output = await runProviderWithEmptyRetry(provider, model, input, index, selectedProvider, startedAt, routingStartedAt);
46645
46658
  const providerHealth = recordProviderSuccess(provider);
46646
46659
  await emit({
46647
46660
  at: Date.now(),
@@ -46653,14 +46666,15 @@ var createVoiceProviderRouter = (options) => {
46653
46666
  providerHealth,
46654
46667
  recovered: provider !== selectedProvider,
46655
46668
  selectedProvider,
46656
- status: provider === selectedProvider ? "success" : "fallback"
46669
+ status: provider === selectedProvider ? "success" : "fallback",
46670
+ totalElapsedMs: Date.now() - routingStartedAt
46657
46671
  }, input);
46658
46672
  return output;
46659
46673
  } catch (error) {
46660
46674
  lastError = error;
46661
46675
  const hasNextProvider = index < order.length - 1;
46662
46676
  const isProviderError = options.isProviderError?.(error, provider) ?? true;
46663
- const timedOut = options.isTimeoutError?.(error, provider) ?? error instanceof VoiceProviderTimeoutError;
46677
+ const timedOut = options.isTimeoutError?.(error, provider) ?? (error instanceof VoiceProviderTimeoutError || error instanceof VoiceProviderStreamInactivityError);
46664
46678
  const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
46665
46679
  const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
46666
46680
  const providerHealth = recordProviderError(provider, isProviderError, rateLimited);
@@ -46679,7 +46693,10 @@ var createVoiceProviderRouter = (options) => {
46679
46693
  suppressionRemainingMs: getSuppressionRemainingMs(provider),
46680
46694
  suppressedUntil: providerHealth?.suppressedUntil,
46681
46695
  status: "error",
46682
- timedOut
46696
+ timedOut,
46697
+ timeoutKind: error instanceof VoiceProviderStreamInactivityError ? "stream-inactivity" : error instanceof VoiceProviderTimeoutError ? "latency-budget" : undefined,
46698
+ timeoutMs: error instanceof VoiceProviderStreamInactivityError ? error.inactivityMs : error instanceof VoiceProviderTimeoutError ? error.timeoutMs : undefined,
46699
+ totalElapsedMs: Date.now() - routingStartedAt
46683
46700
  }, input);
46684
46701
  if (!hasNextProvider || !shouldFallback) {
46685
46702
  throw error;
@@ -46882,7 +46899,7 @@ var readServerSentEvents = async (response, onEvent, abortOptions) => {
46882
46899
  return new Promise((resolve2, reject) => {
46883
46900
  inactivityTimer = setTimeout(() => {
46884
46901
  reader.cancel().catch(() => {});
46885
- reject(new Error(`SSE read inactivity timeout (${inactivityMs}ms with no chunk)`));
46902
+ reject(new VoiceProviderStreamInactivityError(inactivityMs));
46886
46903
  }, inactivityMs);
46887
46904
  if (signal) {
46888
46905
  onAbort = () => {
@@ -47071,7 +47088,7 @@ var createOpenAIVoiceAssistantModel = (options) => {
47071
47088
  outputItems
47072
47089
  } = await consumeOpenAIResponsesStream(response, onTextDelta, {
47073
47090
  signal: ac.signal,
47074
- inactivityMs: 1e4
47091
+ inactivityMs: options.streamInactivityMs ?? 1e4
47075
47092
  }));
47076
47093
  stamp("openai.stream-done", {
47077
47094
  textChars: assistantText?.length ?? 0,
@@ -5248,6 +5248,15 @@ class VoiceProviderTimeoutError extends Error {
5248
5248
  this.timeoutMs = timeoutMs;
5249
5249
  }
5250
5250
  }
5251
+
5252
+ class VoiceProviderStreamInactivityError extends Error {
5253
+ inactivityMs;
5254
+ constructor(inactivityMs) {
5255
+ super(`SSE read inactivity timeout (${inactivityMs}ms with no chunk)`);
5256
+ this.name = "VoiceProviderStreamInactivityError";
5257
+ this.inactivityMs = inactivityMs;
5258
+ }
5259
+ }
5251
5260
  var getMessageToolCalls = (message) => {
5252
5261
  const toolCalls = message.metadata?.toolCalls;
5253
5262
  return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
@@ -5494,7 +5503,7 @@ var createVoiceProviderRouter = (options) => {
5494
5503
  }
5495
5504
  }
5496
5505
  };
5497
- const runProviderWithEmptyRetry = async (provider, model, input, index, selectedProvider, startedAt) => {
5506
+ const runProviderWithEmptyRetry = async (provider, model, input, index, selectedProvider, startedAt, routingStartedAt) => {
5498
5507
  let output = await runProvider(provider, model, input);
5499
5508
  let emptyAttempt = 0;
5500
5509
  while (emptyAttempt < emptyCompletionRetries && isEmptyModelOutput(output)) {
@@ -5506,7 +5515,8 @@ var createVoiceProviderRouter = (options) => {
5506
5515
  emptyCompletion: true,
5507
5516
  provider,
5508
5517
  selectedProvider,
5509
- status: "success"
5518
+ status: "success",
5519
+ totalElapsedMs: Date.now() - routingStartedAt
5510
5520
  }, input);
5511
5521
  output = await runProvider(provider, model, input);
5512
5522
  }
@@ -5518,6 +5528,7 @@ var createVoiceProviderRouter = (options) => {
5518
5528
  if (!selectedProvider || order.length === 0) {
5519
5529
  throw new Error("Voice provider router has no available providers.");
5520
5530
  }
5531
+ const routingStartedAt = Date.now();
5521
5532
  let lastError;
5522
5533
  for (const [index, provider] of order.entries()) {
5523
5534
  const model = options.providers[provider];
@@ -5526,7 +5537,7 @@ var createVoiceProviderRouter = (options) => {
5526
5537
  }
5527
5538
  const startedAt = Date.now();
5528
5539
  try {
5529
- const output = await runProviderWithEmptyRetry(provider, model, input, index, selectedProvider, startedAt);
5540
+ const output = await runProviderWithEmptyRetry(provider, model, input, index, selectedProvider, startedAt, routingStartedAt);
5530
5541
  const providerHealth = recordProviderSuccess(provider);
5531
5542
  await emit({
5532
5543
  at: Date.now(),
@@ -5538,14 +5549,15 @@ var createVoiceProviderRouter = (options) => {
5538
5549
  providerHealth,
5539
5550
  recovered: provider !== selectedProvider,
5540
5551
  selectedProvider,
5541
- status: provider === selectedProvider ? "success" : "fallback"
5552
+ status: provider === selectedProvider ? "success" : "fallback",
5553
+ totalElapsedMs: Date.now() - routingStartedAt
5542
5554
  }, input);
5543
5555
  return output;
5544
5556
  } catch (error) {
5545
5557
  lastError = error;
5546
5558
  const hasNextProvider = index < order.length - 1;
5547
5559
  const isProviderError = options.isProviderError?.(error, provider) ?? true;
5548
- const timedOut = options.isTimeoutError?.(error, provider) ?? error instanceof VoiceProviderTimeoutError;
5560
+ const timedOut = options.isTimeoutError?.(error, provider) ?? (error instanceof VoiceProviderTimeoutError || error instanceof VoiceProviderStreamInactivityError);
5549
5561
  const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
5550
5562
  const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
5551
5563
  const providerHealth = recordProviderError(provider, isProviderError, rateLimited);
@@ -5564,7 +5576,10 @@ var createVoiceProviderRouter = (options) => {
5564
5576
  suppressionRemainingMs: getSuppressionRemainingMs(provider),
5565
5577
  suppressedUntil: providerHealth?.suppressedUntil,
5566
5578
  status: "error",
5567
- timedOut
5579
+ timedOut,
5580
+ timeoutKind: error instanceof VoiceProviderStreamInactivityError ? "stream-inactivity" : error instanceof VoiceProviderTimeoutError ? "latency-budget" : undefined,
5581
+ timeoutMs: error instanceof VoiceProviderStreamInactivityError ? error.inactivityMs : error instanceof VoiceProviderTimeoutError ? error.timeoutMs : undefined,
5582
+ totalElapsedMs: Date.now() - routingStartedAt
5568
5583
  }, input);
5569
5584
  if (!hasNextProvider || !shouldFallback) {
5570
5585
  throw error;
@@ -5767,7 +5782,7 @@ var readServerSentEvents = async (response, onEvent, abortOptions) => {
5767
5782
  return new Promise((resolve2, reject) => {
5768
5783
  inactivityTimer = setTimeout(() => {
5769
5784
  reader.cancel().catch(() => {});
5770
- reject(new Error(`SSE read inactivity timeout (${inactivityMs}ms with no chunk)`));
5785
+ reject(new VoiceProviderStreamInactivityError(inactivityMs));
5771
5786
  }, inactivityMs);
5772
5787
  if (signal) {
5773
5788
  onAbort = () => {
@@ -5956,7 +5971,7 @@ var createOpenAIVoiceAssistantModel = (options) => {
5956
5971
  outputItems
5957
5972
  } = await consumeOpenAIResponsesStream(response, onTextDelta, {
5958
5973
  signal: ac.signal,
5959
- inactivityMs: 1e4
5974
+ inactivityMs: options.streamInactivityMs ?? 1e4
5960
5975
  }));
5961
5976
  stamp("openai.stream-done", {
5962
5977
  textChars: assistantText?.length ?? 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.670",
3
+ "version": "0.0.22-beta.671",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",