@absolutejs/voice 0.0.22-beta.554 → 0.0.22-beta.556
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/core/modelAdapters.d.ts +8 -0
- package/dist/index.js +84 -39
- package/dist/testing/index.js +84 -39
- package/package.json +1 -1
|
@@ -13,6 +13,14 @@ export type OpenAIVoiceAssistantModelOptions = {
|
|
|
13
13
|
model?: string;
|
|
14
14
|
onUsage?: (usage: Record<string, unknown>) => Promise<void> | void;
|
|
15
15
|
temperature?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Hard cap on a single /responses stream. Aborts via AbortController if
|
|
18
|
+
* the stream doesn't complete in time. Default 60s — generous for typical
|
|
19
|
+
* gpt-4.1-mini conversational turns (1-3s) but catches infinite hangs
|
|
20
|
+
* (server-side stream stalls observed on rare complex inputs). On timeout
|
|
21
|
+
* the agent loop falls through to default-silent-turn-ack.
|
|
22
|
+
*/
|
|
23
|
+
timeoutMs?: number;
|
|
16
24
|
};
|
|
17
25
|
export type AnthropicVoiceAssistantModelOptions = {
|
|
18
26
|
apiKey: string;
|
package/dist/index.js
CHANGED
|
@@ -5253,6 +5253,7 @@ var createVoiceSession = (options) => {
|
|
|
5253
5253
|
};
|
|
5254
5254
|
};
|
|
5255
5255
|
const completeTurn = async (session, turn) => {
|
|
5256
|
+
console.error(`[voice] completeTurn ENTER session=${options.id} turn=${turn.id} textLen=${turn.text?.length ?? 0}`);
|
|
5256
5257
|
const liveOpsControl = await options.liveOps?.getControl(options.id);
|
|
5257
5258
|
if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
|
|
5258
5259
|
await appendTrace({
|
|
@@ -5303,17 +5304,41 @@ var createVoiceSession = (options) => {
|
|
|
5303
5304
|
});
|
|
5304
5305
|
}, fillerDelayMs);
|
|
5305
5306
|
}
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5307
|
+
let committedOutput;
|
|
5308
|
+
const onTurnStartedAt = Date.now();
|
|
5309
|
+
try {
|
|
5310
|
+
committedOutput = await options.route.onTurn({
|
|
5311
|
+
api,
|
|
5312
|
+
context: options.context,
|
|
5313
|
+
liveOps: liveOpsControl ? {
|
|
5314
|
+
control: liveOpsControl,
|
|
5315
|
+
injectedInstruction
|
|
5316
|
+
} : undefined,
|
|
5317
|
+
onTextDelta: ttsStreamer?.push,
|
|
5318
|
+
session,
|
|
5319
|
+
turn
|
|
5320
|
+
});
|
|
5321
|
+
} catch (error) {
|
|
5322
|
+
const message = toError(error).message;
|
|
5323
|
+
logger.warn("voice route.onTurn failed", {
|
|
5324
|
+
elapsedMs: Date.now() - onTurnStartedAt,
|
|
5325
|
+
error: message,
|
|
5326
|
+
sessionId: options.id,
|
|
5327
|
+
turnId: turn.id
|
|
5328
|
+
});
|
|
5329
|
+
console.error(`[voice] onTurn failed for session ${options.id} turn ${turn.id} after ${Date.now() - onTurnStartedAt}ms:`, message);
|
|
5330
|
+
await appendTrace({
|
|
5331
|
+
payload: {
|
|
5332
|
+
elapsedMs: Date.now() - onTurnStartedAt,
|
|
5333
|
+
error: message,
|
|
5334
|
+
stage: "route.onTurn"
|
|
5335
|
+
},
|
|
5336
|
+
session,
|
|
5337
|
+
turnId: turn.id,
|
|
5338
|
+
type: "session.error"
|
|
5339
|
+
});
|
|
5340
|
+
committedOutput = undefined;
|
|
5341
|
+
}
|
|
5317
5342
|
const output = {
|
|
5318
5343
|
assistantText: committedOutput?.assistantText,
|
|
5319
5344
|
citations: committedOutput?.citations,
|
|
@@ -45106,41 +45131,61 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
45106
45131
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
45107
45132
|
const baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
|
|
45108
45133
|
const model = options.model ?? "gpt-4.1-mini";
|
|
45134
|
+
const timeoutMs = options.timeoutMs ?? 60000;
|
|
45109
45135
|
return {
|
|
45110
45136
|
generate: async (input) => {
|
|
45111
|
-
const
|
|
45112
|
-
|
|
45113
|
-
|
|
45114
|
-
|
|
45137
|
+
const ac = new AbortController;
|
|
45138
|
+
const timer = setTimeout(() => {
|
|
45139
|
+
ac.abort(new Error(`OpenAI /responses timed out after ${timeoutMs}ms (no completion event received)`));
|
|
45140
|
+
}, timeoutMs);
|
|
45141
|
+
let response;
|
|
45142
|
+
try {
|
|
45143
|
+
response = await fetchImpl(`${baseUrl.replace(/\/$/, "")}/responses`, {
|
|
45144
|
+
body: JSON.stringify({
|
|
45145
|
+
input: messagesToOpenAIInput(input.messages),
|
|
45146
|
+
instructions: [input.system, VOICE_SYSTEM_INSTRUCTIONS].filter(Boolean).join(`
|
|
45115
45147
|
|
|
45116
45148
|
`),
|
|
45117
|
-
|
|
45118
|
-
|
|
45119
|
-
|
|
45120
|
-
|
|
45121
|
-
|
|
45122
|
-
|
|
45123
|
-
|
|
45124
|
-
|
|
45125
|
-
|
|
45126
|
-
|
|
45127
|
-
|
|
45128
|
-
|
|
45129
|
-
|
|
45130
|
-
|
|
45131
|
-
|
|
45132
|
-
|
|
45133
|
-
|
|
45134
|
-
|
|
45135
|
-
|
|
45136
|
-
|
|
45137
|
-
|
|
45138
|
-
|
|
45139
|
-
|
|
45149
|
+
max_output_tokens: options.maxOutputTokens,
|
|
45150
|
+
model,
|
|
45151
|
+
stream: true,
|
|
45152
|
+
temperature: options.temperature,
|
|
45153
|
+
tool_choice: input.tools.length ? "auto" : "none",
|
|
45154
|
+
tools: input.tools.map((tool) => ({
|
|
45155
|
+
description: tool.description,
|
|
45156
|
+
name: tool.name,
|
|
45157
|
+
parameters: tool.parameters ?? {
|
|
45158
|
+
additionalProperties: true,
|
|
45159
|
+
type: "object"
|
|
45160
|
+
},
|
|
45161
|
+
strict: false,
|
|
45162
|
+
type: "function"
|
|
45163
|
+
}))
|
|
45164
|
+
}),
|
|
45165
|
+
headers: {
|
|
45166
|
+
accept: "text/event-stream",
|
|
45167
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
45168
|
+
"content-type": "application/json"
|
|
45169
|
+
},
|
|
45170
|
+
method: "POST",
|
|
45171
|
+
signal: ac.signal
|
|
45172
|
+
});
|
|
45173
|
+
} catch (error) {
|
|
45174
|
+
clearTimeout(timer);
|
|
45175
|
+
throw error;
|
|
45176
|
+
}
|
|
45140
45177
|
if (!response.ok) {
|
|
45178
|
+
clearTimeout(timer);
|
|
45141
45179
|
throw createHTTPError("OpenAI", response);
|
|
45142
45180
|
}
|
|
45143
|
-
|
|
45181
|
+
let assistantText;
|
|
45182
|
+
let toolCalls;
|
|
45183
|
+
let usage;
|
|
45184
|
+
try {
|
|
45185
|
+
({ assistantText, toolCalls, usage } = await consumeOpenAIResponsesStream(response, input.onTextDelta));
|
|
45186
|
+
} finally {
|
|
45187
|
+
clearTimeout(timer);
|
|
45188
|
+
}
|
|
45144
45189
|
if (usage) {
|
|
45145
45190
|
await options.onUsage?.(usage);
|
|
45146
45191
|
}
|
package/dist/testing/index.js
CHANGED
|
@@ -4600,41 +4600,61 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
4600
4600
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
4601
4601
|
const baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
|
|
4602
4602
|
const model = options.model ?? "gpt-4.1-mini";
|
|
4603
|
+
const timeoutMs = options.timeoutMs ?? 60000;
|
|
4603
4604
|
return {
|
|
4604
4605
|
generate: async (input) => {
|
|
4605
|
-
const
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4606
|
+
const ac = new AbortController;
|
|
4607
|
+
const timer = setTimeout(() => {
|
|
4608
|
+
ac.abort(new Error(`OpenAI /responses timed out after ${timeoutMs}ms (no completion event received)`));
|
|
4609
|
+
}, timeoutMs);
|
|
4610
|
+
let response;
|
|
4611
|
+
try {
|
|
4612
|
+
response = await fetchImpl(`${baseUrl.replace(/\/$/, "")}/responses`, {
|
|
4613
|
+
body: JSON.stringify({
|
|
4614
|
+
input: messagesToOpenAIInput(input.messages),
|
|
4615
|
+
instructions: [input.system, VOICE_SYSTEM_INSTRUCTIONS].filter(Boolean).join(`
|
|
4609
4616
|
|
|
4610
4617
|
`),
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4618
|
+
max_output_tokens: options.maxOutputTokens,
|
|
4619
|
+
model,
|
|
4620
|
+
stream: true,
|
|
4621
|
+
temperature: options.temperature,
|
|
4622
|
+
tool_choice: input.tools.length ? "auto" : "none",
|
|
4623
|
+
tools: input.tools.map((tool) => ({
|
|
4624
|
+
description: tool.description,
|
|
4625
|
+
name: tool.name,
|
|
4626
|
+
parameters: tool.parameters ?? {
|
|
4627
|
+
additionalProperties: true,
|
|
4628
|
+
type: "object"
|
|
4629
|
+
},
|
|
4630
|
+
strict: false,
|
|
4631
|
+
type: "function"
|
|
4632
|
+
}))
|
|
4633
|
+
}),
|
|
4634
|
+
headers: {
|
|
4635
|
+
accept: "text/event-stream",
|
|
4636
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
4637
|
+
"content-type": "application/json"
|
|
4638
|
+
},
|
|
4639
|
+
method: "POST",
|
|
4640
|
+
signal: ac.signal
|
|
4641
|
+
});
|
|
4642
|
+
} catch (error) {
|
|
4643
|
+
clearTimeout(timer);
|
|
4644
|
+
throw error;
|
|
4645
|
+
}
|
|
4634
4646
|
if (!response.ok) {
|
|
4647
|
+
clearTimeout(timer);
|
|
4635
4648
|
throw createHTTPError("OpenAI", response);
|
|
4636
4649
|
}
|
|
4637
|
-
|
|
4650
|
+
let assistantText;
|
|
4651
|
+
let toolCalls;
|
|
4652
|
+
let usage;
|
|
4653
|
+
try {
|
|
4654
|
+
({ assistantText, toolCalls, usage } = await consumeOpenAIResponsesStream(response, input.onTextDelta));
|
|
4655
|
+
} finally {
|
|
4656
|
+
clearTimeout(timer);
|
|
4657
|
+
}
|
|
4638
4658
|
if (usage) {
|
|
4639
4659
|
await options.onUsage?.(usage);
|
|
4640
4660
|
}
|
|
@@ -7070,6 +7090,7 @@ var createVoiceSession = (options) => {
|
|
|
7070
7090
|
};
|
|
7071
7091
|
};
|
|
7072
7092
|
const completeTurn = async (session, turn) => {
|
|
7093
|
+
console.error(`[voice] completeTurn ENTER session=${options.id} turn=${turn.id} textLen=${turn.text?.length ?? 0}`);
|
|
7073
7094
|
const liveOpsControl = await options.liveOps?.getControl(options.id);
|
|
7074
7095
|
if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
|
|
7075
7096
|
await appendTrace({
|
|
@@ -7120,17 +7141,41 @@ var createVoiceSession = (options) => {
|
|
|
7120
7141
|
});
|
|
7121
7142
|
}, fillerDelayMs);
|
|
7122
7143
|
}
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7144
|
+
let committedOutput;
|
|
7145
|
+
const onTurnStartedAt = Date.now();
|
|
7146
|
+
try {
|
|
7147
|
+
committedOutput = await options.route.onTurn({
|
|
7148
|
+
api,
|
|
7149
|
+
context: options.context,
|
|
7150
|
+
liveOps: liveOpsControl ? {
|
|
7151
|
+
control: liveOpsControl,
|
|
7152
|
+
injectedInstruction
|
|
7153
|
+
} : undefined,
|
|
7154
|
+
onTextDelta: ttsStreamer?.push,
|
|
7155
|
+
session,
|
|
7156
|
+
turn
|
|
7157
|
+
});
|
|
7158
|
+
} catch (error) {
|
|
7159
|
+
const message = toError(error).message;
|
|
7160
|
+
logger.warn("voice route.onTurn failed", {
|
|
7161
|
+
elapsedMs: Date.now() - onTurnStartedAt,
|
|
7162
|
+
error: message,
|
|
7163
|
+
sessionId: options.id,
|
|
7164
|
+
turnId: turn.id
|
|
7165
|
+
});
|
|
7166
|
+
console.error(`[voice] onTurn failed for session ${options.id} turn ${turn.id} after ${Date.now() - onTurnStartedAt}ms:`, message);
|
|
7167
|
+
await appendTrace({
|
|
7168
|
+
payload: {
|
|
7169
|
+
elapsedMs: Date.now() - onTurnStartedAt,
|
|
7170
|
+
error: message,
|
|
7171
|
+
stage: "route.onTurn"
|
|
7172
|
+
},
|
|
7173
|
+
session,
|
|
7174
|
+
turnId: turn.id,
|
|
7175
|
+
type: "session.error"
|
|
7176
|
+
});
|
|
7177
|
+
committedOutput = undefined;
|
|
7178
|
+
}
|
|
7134
7179
|
const output = {
|
|
7135
7180
|
assistantText: committedOutput?.assistantText,
|
|
7136
7181
|
citations: committedOutput?.citations,
|