@absolutejs/voice 0.0.22-beta.677 → 0.0.22-beta.678
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/CHANGELOG.md +6 -0
- package/changelog.json +17 -0
- package/dist/client/htmxBootstrap.js +1 -1
- package/dist/core/hardenedFetch.d.ts +9 -1
- package/dist/core/modelAdapters.d.ts +3 -1
- package/dist/embed/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +31 -9
- package/dist/testing/index.js +30 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.0.22-beta.678 — 2026-09-27
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Classify exhausted fetch header deadlines as timeouts with per-attempt metadata, preserve caller cancellation, and expose a configurable header deadline without changing the six-second default.** (`hardenFetch`, `VoiceFetchHeadersTimeoutError`, `HardenFetchOptions`, `createVoiceProviderRouter`, `VoiceProviderRouterEvent`)
|
|
14
|
+
|
|
9
15
|
## 0.0.22-beta.677 — 2026-09-16
|
|
10
16
|
|
|
11
17
|
### Fixed
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/voice",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "fixed",
|
|
9
|
+
"summary": "Classify exhausted fetch header deadlines as timeouts with per-attempt metadata, preserve caller cancellation, and expose a configurable header deadline without changing the six-second default.",
|
|
10
|
+
"symbols": [
|
|
11
|
+
"hardenFetch",
|
|
12
|
+
"VoiceFetchHeadersTimeoutError",
|
|
13
|
+
"HardenFetchOptions",
|
|
14
|
+
"createVoiceProviderRouter",
|
|
15
|
+
"VoiceProviderRouterEvent"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"date": "2026-09-27",
|
|
20
|
+
"version": "0.0.22-beta.678"
|
|
21
|
+
},
|
|
5
22
|
{
|
|
6
23
|
"changes": [
|
|
7
24
|
{
|
|
@@ -316,7 +316,7 @@ var serverMessageToAction = (message) => {
|
|
|
316
316
|
}
|
|
317
317
|
};
|
|
318
318
|
|
|
319
|
-
//
|
|
319
|
+
// node_modules/@absolutejs/media/dist/index.js
|
|
320
320
|
var TAU = Math.PI * 2;
|
|
321
321
|
var pushIssue = (issues, severity, code, message) => {
|
|
322
322
|
issues.push({ code, message, severity });
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare class VoiceFetchHeadersTimeoutError extends Error {
|
|
2
|
+
readonly timeoutMs: number;
|
|
3
|
+
readonly attempt: number;
|
|
4
|
+
constructor(timeoutMs: number, attempt: number);
|
|
5
|
+
}
|
|
6
|
+
export type HardenFetchOptions = {
|
|
7
|
+
attemptTimeoutMs?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare const hardenFetch: (baseFetch?: typeof fetch, options?: HardenFetchOptions) => (input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]) => Promise<Response>;
|
|
@@ -64,7 +64,9 @@ export type VoiceProviderRouterEvent<TProvider extends string = string> = {
|
|
|
64
64
|
suppressedUntil?: number;
|
|
65
65
|
status: "error" | "fallback" | "success";
|
|
66
66
|
timedOut?: boolean;
|
|
67
|
-
timeoutKind?: "latency-budget" | "stream-inactivity";
|
|
67
|
+
timeoutKind?: "latency-budget" | "stream-inactivity" | "response-headers";
|
|
68
|
+
/** Fetch attempt number within this provider attempt. */
|
|
69
|
+
fetchAttempt?: number;
|
|
68
70
|
timeoutMs?: number;
|
|
69
71
|
totalElapsedMs?: number;
|
|
70
72
|
};
|
package/dist/embed/index.js
CHANGED
|
@@ -313,7 +313,7 @@ var serverMessageToAction = (message) => {
|
|
|
313
313
|
}
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
-
//
|
|
316
|
+
// node_modules/@absolutejs/media/dist/index.js
|
|
317
317
|
var TAU = Math.PI * 2;
|
|
318
318
|
var pushIssue = (issues, severity, code, message) => {
|
|
319
319
|
issues.push({ code, message, severity });
|
package/dist/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "
|
|
|
72
72
|
export { createPersonaVoiceCaller, createScriptedVoiceCaller, renderVoiceSimulationTranscript, runVoiceConversationSimulation, } from "./core/conversationSimulator";
|
|
73
73
|
export type { RunVoiceConversationSimulationInput, VoiceConversationSimulationEndedReason, VoiceConversationSimulationResult, VoicePersonaCallerCompletion, VoiceScriptedCallerStep, VoiceSimulatedSpeaker, VoiceSimulatedTurn, VoiceSimulatorCaller, VoiceSimulatorCallerModel, VoiceSimulatorCallerReply, } from "./core/conversationSimulator";
|
|
74
74
|
export { logVoiceTiming, startVoiceTimer, voiceTimingEnabled, } from "./core/debugTiming";
|
|
75
|
-
export { hardenFetch } from "./core/hardenedFetch";
|
|
75
|
+
export { hardenFetch, VoiceFetchHeadersTimeoutError, type HardenFetchOptions, } from "./core/hardenedFetch";
|
|
76
76
|
export { createVoiceMCPToolset } from "./core/mcpToolset";
|
|
77
77
|
export type { CreateVoiceMCPToolsetOptions, MCPClientLike, MCPToolCallResult, MCPToolContentBlock, MCPToolDefinition, VoiceMCPToolResult, } from "./core/mcpToolset";
|
|
78
78
|
export { createAIVoiceModel } from "./core/aiVoiceModel";
|
package/dist/index.js
CHANGED
|
@@ -42225,8 +42225,19 @@ Respond with only your spoken line. When your goal is met or you want to hang up
|
|
|
42225
42225
|
};
|
|
42226
42226
|
// src/core/hardenedFetch.ts
|
|
42227
42227
|
var ATTEMPT_TIMEOUT_MS = 6000;
|
|
42228
|
+
|
|
42229
|
+
class VoiceFetchHeadersTimeoutError extends Error {
|
|
42230
|
+
timeoutMs;
|
|
42231
|
+
attempt;
|
|
42232
|
+
constructor(timeoutMs, attempt) {
|
|
42233
|
+
super(`fetch attempt ${attempt} exceeded ${timeoutMs}ms before response headers`);
|
|
42234
|
+
this.name = "VoiceFetchHeadersTimeoutError";
|
|
42235
|
+
this.timeoutMs = timeoutMs;
|
|
42236
|
+
this.attempt = attempt;
|
|
42237
|
+
}
|
|
42238
|
+
}
|
|
42228
42239
|
var isBun = "Bun" in globalThis;
|
|
42229
|
-
var oneAttempt = async (baseFetch, input, init) => {
|
|
42240
|
+
var oneAttempt = async (baseFetch, input, init, timeoutMs, attempt) => {
|
|
42230
42241
|
const controller = new AbortController;
|
|
42231
42242
|
const callerSignal = init?.signal ?? undefined;
|
|
42232
42243
|
const onCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
@@ -42235,8 +42246,8 @@ var oneAttempt = async (baseFetch, input, init) => {
|
|
|
42235
42246
|
else
|
|
42236
42247
|
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
42237
42248
|
const timer = setTimeout(() => {
|
|
42238
|
-
controller.abort(new
|
|
42239
|
-
},
|
|
42249
|
+
controller.abort(new VoiceFetchHeadersTimeoutError(timeoutMs, attempt));
|
|
42250
|
+
}, timeoutMs);
|
|
42240
42251
|
const headers = new Headers(init?.headers);
|
|
42241
42252
|
if (isBun)
|
|
42242
42253
|
headers.set("Connection", "close");
|
|
@@ -42246,20 +42257,29 @@ var oneAttempt = async (baseFetch, input, init) => {
|
|
|
42246
42257
|
headers,
|
|
42247
42258
|
signal: controller.signal
|
|
42248
42259
|
});
|
|
42260
|
+
} catch (error) {
|
|
42261
|
+
if (callerSignal?.aborted)
|
|
42262
|
+
throw callerSignal.reason;
|
|
42263
|
+
if (controller.signal.reason instanceof VoiceFetchHeadersTimeoutError)
|
|
42264
|
+
throw controller.signal.reason;
|
|
42265
|
+
throw error;
|
|
42249
42266
|
} finally {
|
|
42250
42267
|
clearTimeout(timer);
|
|
42251
42268
|
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
42252
42269
|
}
|
|
42253
42270
|
};
|
|
42254
|
-
var hardenFetch = (baseFetch = globalThis.fetch) => {
|
|
42271
|
+
var hardenFetch = (baseFetch = globalThis.fetch, options = {}) => {
|
|
42272
|
+
const timeoutMs = options.attemptTimeoutMs ?? ATTEMPT_TIMEOUT_MS;
|
|
42273
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
42274
|
+
throw new RangeError("attemptTimeoutMs must be finite and positive");
|
|
42255
42275
|
const hardenedFetch = async (input, init) => {
|
|
42256
42276
|
try {
|
|
42257
|
-
return await oneAttempt(baseFetch, input, init);
|
|
42277
|
+
return await oneAttempt(baseFetch, input, init, timeoutMs, 1);
|
|
42258
42278
|
} catch (error) {
|
|
42259
42279
|
if (init?.signal?.aborted)
|
|
42260
42280
|
throw error;
|
|
42261
42281
|
console.warn(`[voice] hardened fetch retrying on a fresh connection: ${error instanceof Error ? error.message : String(error)}`);
|
|
42262
|
-
return oneAttempt(baseFetch, input, init);
|
|
42282
|
+
return oneAttempt(baseFetch, input, init, timeoutMs, 2);
|
|
42263
42283
|
}
|
|
42264
42284
|
};
|
|
42265
42285
|
return typeof baseFetch.preconnect === "function" ? Object.assign(hardenedFetch, {
|
|
@@ -46725,7 +46745,7 @@ var createVoiceProviderRouter = (options) => {
|
|
|
46725
46745
|
lastError = error;
|
|
46726
46746
|
const hasNextProvider = index < order.length - 1;
|
|
46727
46747
|
const isProviderError = options.isProviderError?.(error, provider) ?? true;
|
|
46728
|
-
const timedOut = options.isTimeoutError?.(error, provider) ?? (error instanceof VoiceProviderTimeoutError || error instanceof VoiceProviderStreamInactivityError);
|
|
46748
|
+
const timedOut = options.isTimeoutError?.(error, provider) ?? (error instanceof VoiceProviderTimeoutError || error instanceof VoiceProviderStreamInactivityError || error instanceof VoiceFetchHeadersTimeoutError);
|
|
46729
46749
|
const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
|
|
46730
46750
|
const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
|
|
46731
46751
|
const providerHealth = recordProviderError(provider, isProviderError, rateLimited);
|
|
@@ -46745,8 +46765,9 @@ var createVoiceProviderRouter = (options) => {
|
|
|
46745
46765
|
suppressedUntil: providerHealth?.suppressedUntil,
|
|
46746
46766
|
status: "error",
|
|
46747
46767
|
timedOut,
|
|
46748
|
-
|
|
46749
|
-
|
|
46768
|
+
fetchAttempt: error instanceof VoiceFetchHeadersTimeoutError ? error.attempt : undefined,
|
|
46769
|
+
timeoutKind: error instanceof VoiceFetchHeadersTimeoutError ? "response-headers" : error instanceof VoiceProviderStreamInactivityError ? "stream-inactivity" : error instanceof VoiceProviderTimeoutError ? "latency-budget" : undefined,
|
|
46770
|
+
timeoutMs: error instanceof VoiceFetchHeadersTimeoutError ? error.timeoutMs : error instanceof VoiceProviderStreamInactivityError ? error.inactivityMs : error instanceof VoiceProviderTimeoutError ? error.timeoutMs : undefined,
|
|
46750
46771
|
totalElapsedMs: Date.now() - routingStartedAt
|
|
46751
46772
|
}, input);
|
|
46752
46773
|
if (!hasNextProvider || !shouldFallback) {
|
|
@@ -54471,6 +54492,7 @@ export {
|
|
|
54471
54492
|
VOICE_WEBHOOK_SIGNATURE_HEADER,
|
|
54472
54493
|
VOICE_WEBHOOK_TIMESTAMP_HEADER,
|
|
54473
54494
|
VOICE_ZERO_DATA_RETENTION_REDACTION_MARKER,
|
|
54495
|
+
VoiceFetchHeadersTimeoutError,
|
|
54474
54496
|
acknowledgeVoiceMonitorIssue,
|
|
54475
54497
|
aggregateVoiceTurnLatencySpans,
|
|
54476
54498
|
appendVoiceIOProviderRouterTraceEvent,
|
package/dist/testing/index.js
CHANGED
|
@@ -5121,8 +5121,19 @@ var voiceTimingEnabled = () => timingEnabled();
|
|
|
5121
5121
|
|
|
5122
5122
|
// src/core/hardenedFetch.ts
|
|
5123
5123
|
var ATTEMPT_TIMEOUT_MS = 6000;
|
|
5124
|
+
|
|
5125
|
+
class VoiceFetchHeadersTimeoutError extends Error {
|
|
5126
|
+
timeoutMs;
|
|
5127
|
+
attempt;
|
|
5128
|
+
constructor(timeoutMs, attempt) {
|
|
5129
|
+
super(`fetch attempt ${attempt} exceeded ${timeoutMs}ms before response headers`);
|
|
5130
|
+
this.name = "VoiceFetchHeadersTimeoutError";
|
|
5131
|
+
this.timeoutMs = timeoutMs;
|
|
5132
|
+
this.attempt = attempt;
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5124
5135
|
var isBun = "Bun" in globalThis;
|
|
5125
|
-
var oneAttempt = async (baseFetch, input, init) => {
|
|
5136
|
+
var oneAttempt = async (baseFetch, input, init, timeoutMs, attempt) => {
|
|
5126
5137
|
const controller = new AbortController;
|
|
5127
5138
|
const callerSignal = init?.signal ?? undefined;
|
|
5128
5139
|
const onCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
@@ -5131,8 +5142,8 @@ var oneAttempt = async (baseFetch, input, init) => {
|
|
|
5131
5142
|
else
|
|
5132
5143
|
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
5133
5144
|
const timer = setTimeout(() => {
|
|
5134
|
-
controller.abort(new
|
|
5135
|
-
},
|
|
5145
|
+
controller.abort(new VoiceFetchHeadersTimeoutError(timeoutMs, attempt));
|
|
5146
|
+
}, timeoutMs);
|
|
5136
5147
|
const headers = new Headers(init?.headers);
|
|
5137
5148
|
if (isBun)
|
|
5138
5149
|
headers.set("Connection", "close");
|
|
@@ -5142,20 +5153,29 @@ var oneAttempt = async (baseFetch, input, init) => {
|
|
|
5142
5153
|
headers,
|
|
5143
5154
|
signal: controller.signal
|
|
5144
5155
|
});
|
|
5156
|
+
} catch (error) {
|
|
5157
|
+
if (callerSignal?.aborted)
|
|
5158
|
+
throw callerSignal.reason;
|
|
5159
|
+
if (controller.signal.reason instanceof VoiceFetchHeadersTimeoutError)
|
|
5160
|
+
throw controller.signal.reason;
|
|
5161
|
+
throw error;
|
|
5145
5162
|
} finally {
|
|
5146
5163
|
clearTimeout(timer);
|
|
5147
5164
|
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
5148
5165
|
}
|
|
5149
5166
|
};
|
|
5150
|
-
var hardenFetch = (baseFetch = globalThis.fetch) => {
|
|
5167
|
+
var hardenFetch = (baseFetch = globalThis.fetch, options = {}) => {
|
|
5168
|
+
const timeoutMs = options.attemptTimeoutMs ?? ATTEMPT_TIMEOUT_MS;
|
|
5169
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
5170
|
+
throw new RangeError("attemptTimeoutMs must be finite and positive");
|
|
5151
5171
|
const hardenedFetch = async (input, init) => {
|
|
5152
5172
|
try {
|
|
5153
|
-
return await oneAttempt(baseFetch, input, init);
|
|
5173
|
+
return await oneAttempt(baseFetch, input, init, timeoutMs, 1);
|
|
5154
5174
|
} catch (error) {
|
|
5155
5175
|
if (init?.signal?.aborted)
|
|
5156
5176
|
throw error;
|
|
5157
5177
|
console.warn(`[voice] hardened fetch retrying on a fresh connection: ${error instanceof Error ? error.message : String(error)}`);
|
|
5158
|
-
return oneAttempt(baseFetch, input, init);
|
|
5178
|
+
return oneAttempt(baseFetch, input, init, timeoutMs, 2);
|
|
5159
5179
|
}
|
|
5160
5180
|
};
|
|
5161
5181
|
return typeof baseFetch.preconnect === "function" ? Object.assign(hardenedFetch, {
|
|
@@ -5612,7 +5632,7 @@ var createVoiceProviderRouter = (options) => {
|
|
|
5612
5632
|
lastError = error;
|
|
5613
5633
|
const hasNextProvider = index < order.length - 1;
|
|
5614
5634
|
const isProviderError = options.isProviderError?.(error, provider) ?? true;
|
|
5615
|
-
const timedOut = options.isTimeoutError?.(error, provider) ?? (error instanceof VoiceProviderTimeoutError || error instanceof VoiceProviderStreamInactivityError);
|
|
5635
|
+
const timedOut = options.isTimeoutError?.(error, provider) ?? (error instanceof VoiceProviderTimeoutError || error instanceof VoiceProviderStreamInactivityError || error instanceof VoiceFetchHeadersTimeoutError);
|
|
5616
5636
|
const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
|
|
5617
5637
|
const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
|
|
5618
5638
|
const providerHealth = recordProviderError(provider, isProviderError, rateLimited);
|
|
@@ -5632,8 +5652,9 @@ var createVoiceProviderRouter = (options) => {
|
|
|
5632
5652
|
suppressedUntil: providerHealth?.suppressedUntil,
|
|
5633
5653
|
status: "error",
|
|
5634
5654
|
timedOut,
|
|
5635
|
-
|
|
5636
|
-
|
|
5655
|
+
fetchAttempt: error instanceof VoiceFetchHeadersTimeoutError ? error.attempt : undefined,
|
|
5656
|
+
timeoutKind: error instanceof VoiceFetchHeadersTimeoutError ? "response-headers" : error instanceof VoiceProviderStreamInactivityError ? "stream-inactivity" : error instanceof VoiceProviderTimeoutError ? "latency-budget" : undefined,
|
|
5657
|
+
timeoutMs: error instanceof VoiceFetchHeadersTimeoutError ? error.timeoutMs : error instanceof VoiceProviderStreamInactivityError ? error.inactivityMs : error instanceof VoiceProviderTimeoutError ? error.timeoutMs : undefined,
|
|
5637
5658
|
totalElapsedMs: Date.now() - routingStartedAt
|
|
5638
5659
|
}, input);
|
|
5639
5660
|
if (!hasNextProvider || !shouldFallback) {
|