realtime-voice-agents 2.0.0
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/LICENSE +21 -0
- package/README.md +298 -0
- package/assets/elevator-jazz.ulaw +1 -0
- package/assets/keyboard-typing.ulaw +1 -0
- package/assets/lofi.ulaw +1 -0
- package/assets/ringing.ulaw +1 -0
- package/assets/thinking-hum.ulaw +1 -0
- package/dist/BackgroundAudioPlayer-iMcivjis.mjs +282 -0
- package/dist/BackgroundAudioPlayer-jfRULWKC.cjs +313 -0
- package/dist/BaseRealtimeProvider-BQigr5mB.mjs +62 -0
- package/dist/BaseRealtimeProvider-BehPNT1r.d.cts +239 -0
- package/dist/BaseRealtimeProvider-ClP8Wx1X.d.mts +239 -0
- package/dist/BaseRealtimeProvider-DI4pKtOb.cjs +91 -0
- package/dist/GeminiLiveProvider-DvkTgzjG.d.cts +101 -0
- package/dist/GeminiLiveProvider-x2nyx5aO.d.mts +101 -0
- package/dist/InMemorySessionStore-B0_i-DOU.cjs +34 -0
- package/dist/InMemorySessionStore-B5_rq61L.d.cts +83 -0
- package/dist/InMemorySessionStore-B5_rq61L.d.mts +83 -0
- package/dist/InMemorySessionStore-DYParOJO.mjs +29 -0
- package/dist/OpenAICompatibleProvider-Bdtl-UXH.mjs +402 -0
- package/dist/OpenAICompatibleProvider-NS4cKVQj.cjs +409 -0
- package/dist/audio.cjs +21 -0
- package/dist/audio.d.cts +165 -0
- package/dist/audio.d.mts +165 -0
- package/dist/audio.mjs +4 -0
- package/dist/env-DSnGaERV.cjs +19 -0
- package/dist/env-DUwUWTsg.mjs +14 -0
- package/dist/events-BUMYdETO.d.cts +24 -0
- package/dist/events-BUMYdETO.d.mts +24 -0
- package/dist/events-BxDTIKKq.cjs +48 -0
- package/dist/events-BylBSBW-.mjs +43 -0
- package/dist/gemini.cjs +401 -0
- package/dist/gemini.d.cts +41 -0
- package/dist/gemini.d.mts +41 -0
- package/dist/gemini.mjs +395 -0
- package/dist/index.cjs +2142 -0
- package/dist/index.d.cts +920 -0
- package/dist/index.d.mts +920 -0
- package/dist/index.mjs +2113 -0
- package/dist/mulaw--cwU2c9L.mjs +64 -0
- package/dist/mulaw-DLUObjdP.cjs +117 -0
- package/dist/openai.cjs +61 -0
- package/dist/openai.d.cts +121 -0
- package/dist/openai.d.mts +121 -0
- package/dist/openai.mjs +53 -0
- package/dist/presets-Bf75YXs5.d.cts +24 -0
- package/dist/presets-Bf75YXs5.d.mts +24 -0
- package/dist/rest-BYqiVOhe.mjs +265 -0
- package/dist/rest-BvUKut_k.cjs +300 -0
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/session-config-BVLl7-ha.mjs +74 -0
- package/dist/session-config-c8sOw1XL.cjs +85 -0
- package/dist/store.cjs +3 -0
- package/dist/store.d.cts +2 -0
- package/dist/store.d.mts +2 -0
- package/dist/store.mjs +2 -0
- package/dist/testing.cjs +544 -0
- package/dist/testing.d.cts +191 -0
- package/dist/testing.d.mts +191 -0
- package/dist/testing.mjs +538 -0
- package/dist/transcode-C9aJG7_W.cjs +215 -0
- package/dist/transcode-CsAp97G9.mjs +198 -0
- package/dist/transport-B_PJFIVd.d.cts +155 -0
- package/dist/transport-CEaLFV4E.d.mts +155 -0
- package/dist/twilio.cjs +8 -0
- package/dist/twilio.d.cts +3 -0
- package/dist/twilio.d.mts +3 -0
- package/dist/twilio.mjs +2 -0
- package/dist/twiml-z9LjoF4_.d.cts +67 -0
- package/dist/twiml-z9LjoF4_.d.mts +67 -0
- package/dist/xai.cjs +92 -0
- package/dist/xai.d.cts +35 -0
- package/dist/xai.d.mts +35 -0
- package/dist/xai.mjs +86 -0
- package/package.json +183 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { a as noopLogger, t as BaseRealtimeProvider } from "./BaseRealtimeProvider-BQigr5mB.mjs";
|
|
2
|
+
import { t as buildSessionUpdate } from "./session-config-BVLl7-ha.mjs";
|
|
3
|
+
import WebSocket$1 from "ws";
|
|
4
|
+
//#region src/providers/openai-compatible/OpenAICompatibleProvider.ts
|
|
5
|
+
/**
|
|
6
|
+
* Provider for the OpenAI Realtime API (GA protocol) and API-compatible
|
|
7
|
+
* services (xAI Grok Voice Agent). Speaks `audio/pcmu` in both directions, so
|
|
8
|
+
* Twilio payloads pass through verbatim — zero transcoding on this path.
|
|
9
|
+
*
|
|
10
|
+
* Connect handshake: open WS → receive `session.created` → send
|
|
11
|
+
* `session.update` (audio/pcmu, VAD, tools, instructions) → resolve on
|
|
12
|
+
* `session.updated`. A handshake that hangs rejects after `connectTimeoutMs`
|
|
13
|
+
* so a dead upstream can't leak half-open calls.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_BASE_URL = "wss://api.openai.com/v1/realtime";
|
|
16
|
+
/** WS close codes where retrying cannot help (protocol/auth/policy failures). */
|
|
17
|
+
const NON_RETRIABLE_CLOSE_CODES = /* @__PURE__ */ new Set([
|
|
18
|
+
1002,
|
|
19
|
+
1003,
|
|
20
|
+
1007,
|
|
21
|
+
1008
|
|
22
|
+
]);
|
|
23
|
+
var OpenAICompatibleProvider = class extends BaseRealtimeProvider {
|
|
24
|
+
name;
|
|
25
|
+
capabilities;
|
|
26
|
+
config;
|
|
27
|
+
logger;
|
|
28
|
+
ws = null;
|
|
29
|
+
sessionInit = null;
|
|
30
|
+
ready = false;
|
|
31
|
+
intentionalClose = false;
|
|
32
|
+
currentResponseId = null;
|
|
33
|
+
/**
|
|
34
|
+
* response.create serialization. The GA API rejects a `response.create`
|
|
35
|
+
* issued while another response is in flight
|
|
36
|
+
* (`conversation_already_has_active_response`), so creates requested
|
|
37
|
+
* mid-response wait in a single pending slot and fire on `response.done`.
|
|
38
|
+
* Later requests coalesce into that slot — one response reads the whole
|
|
39
|
+
* conversation state, so only the instructions payload is worth keeping.
|
|
40
|
+
*/
|
|
41
|
+
responseActive = false;
|
|
42
|
+
pendingCreate = null;
|
|
43
|
+
lastCreateSent = null;
|
|
44
|
+
constructor(config, logger = noopLogger) {
|
|
45
|
+
super();
|
|
46
|
+
this.config = config;
|
|
47
|
+
this.logger = logger;
|
|
48
|
+
this.name = config.providerName ?? "openai";
|
|
49
|
+
this.capabilities = {
|
|
50
|
+
truncate: true,
|
|
51
|
+
sessionUpdate: true,
|
|
52
|
+
voiceChangeMidSession: false,
|
|
53
|
+
transcodeRequired: false,
|
|
54
|
+
resumption: false,
|
|
55
|
+
agentTranscriptDeltas: true,
|
|
56
|
+
vadInterruptControl: true,
|
|
57
|
+
...config.capabilityOverrides
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
get isConnected() {
|
|
61
|
+
return this.ready && this.ws?.readyState === WebSocket$1.OPEN;
|
|
62
|
+
}
|
|
63
|
+
async connect(init) {
|
|
64
|
+
if (this.ws) await this.close();
|
|
65
|
+
let vad = init.vad !== void 0 ? init.vad : this.config.defaultVad;
|
|
66
|
+
if (init.bridgeOwnsInterruptions && this.capabilities.vadInterruptControl && vad !== null) vad = {
|
|
67
|
+
interruptResponse: false,
|
|
68
|
+
...vad ?? { type: "server" }
|
|
69
|
+
};
|
|
70
|
+
this.sessionInit = {
|
|
71
|
+
...init,
|
|
72
|
+
vad,
|
|
73
|
+
transcription: init.transcription !== void 0 ? init.transcription : this.config.defaultTranscription
|
|
74
|
+
};
|
|
75
|
+
this.intentionalClose = false;
|
|
76
|
+
this.ready = false;
|
|
77
|
+
this.responseActive = false;
|
|
78
|
+
this.pendingCreate = null;
|
|
79
|
+
this.lastCreateSent = null;
|
|
80
|
+
this.currentResponseId = null;
|
|
81
|
+
const url = `${this.config.baseUrl ?? DEFAULT_BASE_URL}?model=${encodeURIComponent(this.config.model)}`;
|
|
82
|
+
const ws = new WebSocket$1(url, {
|
|
83
|
+
headers: {
|
|
84
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
85
|
+
...this.config.headers
|
|
86
|
+
},
|
|
87
|
+
perMessageDeflate: false
|
|
88
|
+
});
|
|
89
|
+
this.ws = ws;
|
|
90
|
+
await new Promise((resolve, reject) => {
|
|
91
|
+
const timeoutMs = this.config.connectTimeoutMs ?? 1e4;
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
fail(/* @__PURE__ */ new Error(`${this.name} session not ready within ${timeoutMs}ms`));
|
|
94
|
+
}, timeoutMs);
|
|
95
|
+
timer.unref?.();
|
|
96
|
+
let settled = false;
|
|
97
|
+
const fail = (error) => {
|
|
98
|
+
if (settled) return;
|
|
99
|
+
settled = true;
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
try {
|
|
102
|
+
ws.close();
|
|
103
|
+
} catch {}
|
|
104
|
+
reject(error);
|
|
105
|
+
};
|
|
106
|
+
ws.on("unexpected-response", (_request, response) => {
|
|
107
|
+
let body = "";
|
|
108
|
+
response.setEncoding("utf8");
|
|
109
|
+
response.on("data", (chunk) => {
|
|
110
|
+
if (body.length < 512) body += chunk;
|
|
111
|
+
});
|
|
112
|
+
response.on("end", () => {
|
|
113
|
+
const detail = body.trim().slice(0, 500);
|
|
114
|
+
this.logger.error("provider rejected the WebSocket upgrade", {
|
|
115
|
+
status: response.statusCode,
|
|
116
|
+
body: detail
|
|
117
|
+
});
|
|
118
|
+
fail(/* @__PURE__ */ new Error(`${this.name} rejected the WebSocket upgrade: HTTP ${response.statusCode}${detail ? ` — ${detail}` : ""}`));
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
ws.on("open", () => this.emit("open"));
|
|
122
|
+
ws.on("message", (raw) => {
|
|
123
|
+
const event = this.parseEvent(raw);
|
|
124
|
+
if (!event) return;
|
|
125
|
+
if (!settled) {
|
|
126
|
+
if (event.type === "session.created") {
|
|
127
|
+
this.send(this.buildSessionPayload());
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (event.type === "session.updated") {
|
|
131
|
+
settled = true;
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
this.ready = true;
|
|
134
|
+
resolve();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (event.type === "error") {
|
|
138
|
+
fail(/* @__PURE__ */ new Error(`${this.name} session setup error: ${JSON.stringify(event.error ?? event)}`));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.handleEvent(event);
|
|
143
|
+
});
|
|
144
|
+
ws.on("error", (error) => {
|
|
145
|
+
if (!settled) fail(error instanceof Error ? error : new Error(String(error)));
|
|
146
|
+
else this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
147
|
+
});
|
|
148
|
+
ws.on("close", (code, reasonBuf) => {
|
|
149
|
+
const reason = reasonBuf?.toString();
|
|
150
|
+
this.ready = false;
|
|
151
|
+
if (!settled) {
|
|
152
|
+
fail(/* @__PURE__ */ new Error(`${this.name} socket closed during setup (${code} ${reason ?? ""})`));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.emit("close", {
|
|
156
|
+
code,
|
|
157
|
+
reason,
|
|
158
|
+
retriable: !this.intentionalClose && !NON_RETRIABLE_CLOSE_CODES.has(code)
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async close() {
|
|
164
|
+
this.intentionalClose = true;
|
|
165
|
+
this.ready = false;
|
|
166
|
+
const ws = this.ws;
|
|
167
|
+
this.ws = null;
|
|
168
|
+
if (!ws) return;
|
|
169
|
+
if (ws.readyState === WebSocket$1.CLOSED) return;
|
|
170
|
+
await new Promise((resolve) => {
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
ws.terminate();
|
|
173
|
+
resolve();
|
|
174
|
+
}, 1e3);
|
|
175
|
+
timer.unref?.();
|
|
176
|
+
ws.once("close", () => {
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
resolve();
|
|
179
|
+
});
|
|
180
|
+
try {
|
|
181
|
+
ws.close(1e3);
|
|
182
|
+
} catch {
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
ws.terminate();
|
|
185
|
+
resolve();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
sendAudio(base64Mulaw) {
|
|
190
|
+
if (!this.isConnected) return;
|
|
191
|
+
this.send({
|
|
192
|
+
type: "input_audio_buffer.append",
|
|
193
|
+
audio: base64Mulaw
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
sendText(text, options = {}) {
|
|
197
|
+
const role = options.role ?? "user";
|
|
198
|
+
this.send({
|
|
199
|
+
type: "conversation.item.create",
|
|
200
|
+
item: {
|
|
201
|
+
type: "message",
|
|
202
|
+
role,
|
|
203
|
+
content: [{
|
|
204
|
+
type: role === "assistant" ? "output_text" : "input_text",
|
|
205
|
+
text
|
|
206
|
+
}]
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
if (options.triggerResponse !== false) this.createResponse();
|
|
210
|
+
}
|
|
211
|
+
sendToolResult(callId, output, options = {}) {
|
|
212
|
+
this.send({
|
|
213
|
+
type: "conversation.item.create",
|
|
214
|
+
item: {
|
|
215
|
+
type: "function_call_output",
|
|
216
|
+
call_id: callId,
|
|
217
|
+
output: typeof output === "string" ? output : JSON.stringify(output ?? null)
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
if (options.triggerResponse !== false) this.createResponse();
|
|
221
|
+
}
|
|
222
|
+
createResponse(options = {}) {
|
|
223
|
+
if (this.responseActive) {
|
|
224
|
+
this.pendingCreate = {
|
|
225
|
+
instructions: options.instructions ?? this.pendingCreate?.instructions,
|
|
226
|
+
attempts: 0
|
|
227
|
+
};
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.sendCreate({
|
|
231
|
+
instructions: options.instructions,
|
|
232
|
+
attempts: 0
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
sendCreate(create) {
|
|
236
|
+
this.lastCreateSent = create;
|
|
237
|
+
this.send({
|
|
238
|
+
type: "response.create",
|
|
239
|
+
...create.instructions ? { response: { instructions: create.instructions } } : {}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async updateSession(patch) {
|
|
243
|
+
if (!this.sessionInit) throw new Error("updateSession before connect");
|
|
244
|
+
this.sessionInit = {
|
|
245
|
+
...this.sessionInit,
|
|
246
|
+
...patch
|
|
247
|
+
};
|
|
248
|
+
this.send(this.buildSessionPayload());
|
|
249
|
+
}
|
|
250
|
+
buildSessionPayload() {
|
|
251
|
+
return (this.config.buildSession ?? buildSessionUpdate)(this.sessionInit, {
|
|
252
|
+
defaultVoice: this.config.voice,
|
|
253
|
+
extraSessionOptions: this.config.extraSessionOptions
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
cancelResponse() {
|
|
257
|
+
if (this.pendingCreate && !this.pendingCreate.instructions) this.pendingCreate = null;
|
|
258
|
+
this.send({ type: "response.cancel" });
|
|
259
|
+
}
|
|
260
|
+
truncatePlayback(itemId, audioEndMs) {
|
|
261
|
+
this.send({
|
|
262
|
+
type: "conversation.item.truncate",
|
|
263
|
+
item_id: itemId,
|
|
264
|
+
content_index: 0,
|
|
265
|
+
audio_end_ms: Math.max(0, Math.round(audioEndMs))
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
handleEvent(event) {
|
|
269
|
+
switch (event.type) {
|
|
270
|
+
case "response.created": {
|
|
271
|
+
const responseId = event.response?.id ?? `resp_${Date.now()}`;
|
|
272
|
+
this.currentResponseId = responseId;
|
|
273
|
+
this.responseActive = true;
|
|
274
|
+
this.emit("responseStarted", { responseId });
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "response.output_item.added": {
|
|
278
|
+
const itemId = event.item?.id;
|
|
279
|
+
if (itemId) this.emit("outputItemAdded", {
|
|
280
|
+
itemId,
|
|
281
|
+
responseId: event.response_id ?? this.currentResponseId ?? ""
|
|
282
|
+
});
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case "response.output_audio.delta":
|
|
286
|
+
if (typeof event.delta === "string" && event.delta.length > 0) this.emit("audio", {
|
|
287
|
+
base64Mulaw: event.delta,
|
|
288
|
+
responseId: event.response_id ?? this.currentResponseId ?? "",
|
|
289
|
+
itemId: event.item_id
|
|
290
|
+
});
|
|
291
|
+
break;
|
|
292
|
+
case "response.output_audio_transcript.delta":
|
|
293
|
+
if (typeof event.delta === "string") this.emit("agentTranscriptDelta", {
|
|
294
|
+
responseId: event.response_id ?? this.currentResponseId ?? "",
|
|
295
|
+
delta: event.delta
|
|
296
|
+
});
|
|
297
|
+
break;
|
|
298
|
+
case "response.output_audio_transcript.done":
|
|
299
|
+
if (typeof event.transcript === "string") this.emit("agentTranscript", {
|
|
300
|
+
responseId: event.response_id ?? this.currentResponseId ?? "",
|
|
301
|
+
text: event.transcript
|
|
302
|
+
});
|
|
303
|
+
break;
|
|
304
|
+
case "conversation.item.input_audio_transcription.completed":
|
|
305
|
+
if (typeof event.transcript === "string" && event.transcript.trim().length > 0) this.emit("userTranscript", { text: event.transcript.trim() });
|
|
306
|
+
break;
|
|
307
|
+
case "input_audio_buffer.speech_started":
|
|
308
|
+
if (this.pendingCreate && !this.pendingCreate.instructions) this.pendingCreate = null;
|
|
309
|
+
this.emit("userSpeechStarted");
|
|
310
|
+
break;
|
|
311
|
+
case "input_audio_buffer.speech_stopped":
|
|
312
|
+
this.emit("userSpeechStopped");
|
|
313
|
+
break;
|
|
314
|
+
case "response.done": {
|
|
315
|
+
const responseId = event.response?.id ?? this.currentResponseId ?? "";
|
|
316
|
+
const usage = normalizeUsage(event.response?.usage);
|
|
317
|
+
this.responseActive = false;
|
|
318
|
+
const pending = this.pendingCreate;
|
|
319
|
+
if (pending) {
|
|
320
|
+
this.pendingCreate = null;
|
|
321
|
+
this.sendCreate(pending);
|
|
322
|
+
}
|
|
323
|
+
if (usage) this.emit("usage", usage);
|
|
324
|
+
this.emit("responseDone", {
|
|
325
|
+
responseId,
|
|
326
|
+
usage: usage ?? void 0
|
|
327
|
+
});
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case "response.function_call_arguments.done":
|
|
331
|
+
this.emit("toolCall", {
|
|
332
|
+
id: event.call_id ?? event.item_id ?? `call_${Date.now()}`,
|
|
333
|
+
name: event.name,
|
|
334
|
+
argumentsJson: typeof event.arguments === "string" ? event.arguments : "{}",
|
|
335
|
+
responseId: event.response_id ?? this.currentResponseId ?? void 0,
|
|
336
|
+
itemId: event.item_id
|
|
337
|
+
});
|
|
338
|
+
break;
|
|
339
|
+
case "error": {
|
|
340
|
+
const code = event.error?.code;
|
|
341
|
+
if (code === "conversation_already_has_active_response") {
|
|
342
|
+
this.responseActive = true;
|
|
343
|
+
const last = this.lastCreateSent;
|
|
344
|
+
if (last && last.attempts < 2 && !this.pendingCreate) this.pendingCreate = {
|
|
345
|
+
...last,
|
|
346
|
+
attempts: last.attempts + 1
|
|
347
|
+
};
|
|
348
|
+
this.logger.warn("response.create raced an active response; deferred until it completes", { error: event.error });
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
{
|
|
352
|
+
const message = typeof event.error?.message === "string" ? event.error.message : "";
|
|
353
|
+
if (code === "response_cancel_not_active" || /cancellation failed: no active response/i.test(message)) {
|
|
354
|
+
this.logger.warn("response.cancel raced completion (ignored)", { error: event.error });
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
this.logger.warn("provider error event", { error: event.error });
|
|
359
|
+
this.emit("error", /* @__PURE__ */ new Error(`${this.name} error: ${JSON.stringify(event.error ?? event)}`));
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
parseEvent(raw) {
|
|
365
|
+
try {
|
|
366
|
+
return JSON.parse(raw.toString());
|
|
367
|
+
} catch {
|
|
368
|
+
this.logger.warn("unparseable provider frame");
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
send(payload) {
|
|
373
|
+
if (this.ws?.readyState !== WebSocket$1.OPEN) return;
|
|
374
|
+
try {
|
|
375
|
+
this.ws.send(JSON.stringify(payload));
|
|
376
|
+
} catch (error) {
|
|
377
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
function normalizeUsage(raw) {
|
|
382
|
+
if (!raw || typeof raw !== "object") return null;
|
|
383
|
+
const inputDetails = raw.input_token_details ?? raw.input_tokens_details;
|
|
384
|
+
const outputDetails = raw.output_token_details ?? raw.output_tokens_details;
|
|
385
|
+
return {
|
|
386
|
+
inputTokens: raw.input_tokens ?? 0,
|
|
387
|
+
outputTokens: raw.output_tokens ?? 0,
|
|
388
|
+
totalTokens: raw.total_tokens ?? (raw.input_tokens ?? 0) + (raw.output_tokens ?? 0),
|
|
389
|
+
...inputDetails ? { inputTokenDetails: {
|
|
390
|
+
textTokens: inputDetails.text_tokens,
|
|
391
|
+
audioTokens: inputDetails.audio_tokens,
|
|
392
|
+
cachedTokens: inputDetails.cached_tokens
|
|
393
|
+
} } : {},
|
|
394
|
+
...outputDetails ? { outputTokenDetails: {
|
|
395
|
+
textTokens: outputDetails.text_tokens,
|
|
396
|
+
audioTokens: outputDetails.audio_tokens
|
|
397
|
+
} } : {},
|
|
398
|
+
raw
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
export { OpenAICompatibleProvider as t };
|