@yourgpt/llm-sdk 2.6.0-beta.1 → 2.6.0-beta.2
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/README.md +112 -0
- package/dist/adapters/index.d.mts +5 -5
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +372 -39
- package/dist/adapters/index.mjs +372 -39
- package/dist/audio-BBSwnjo8.d.mts +92 -0
- package/dist/audio-BBSwnjo8.d.ts +92 -0
- package/dist/{base-CkeT7u-O.d.mts → base-BICaQaRS.d.mts} +7 -3
- package/dist/{base-BdTpUAWI.d.ts → base-CpRPB8u8.d.ts} +7 -3
- package/dist/fallback/index.d.mts +4 -4
- package/dist/fallback/index.d.ts +4 -4
- package/dist/index.d.mts +17 -35
- package/dist/index.d.ts +17 -35
- package/dist/index.js +229 -60
- package/dist/index.mjs +227 -61
- package/dist/providers/anthropic/index.d.mts +3 -3
- package/dist/providers/anthropic/index.d.ts +3 -3
- package/dist/providers/anthropic/index.js +123 -7
- package/dist/providers/anthropic/index.mjs +123 -7
- package/dist/providers/azure/index.d.mts +3 -3
- package/dist/providers/azure/index.d.ts +3 -3
- package/dist/providers/azure/index.js +101 -4
- package/dist/providers/azure/index.mjs +101 -4
- package/dist/providers/fireworks/index.d.mts +3 -3
- package/dist/providers/fireworks/index.d.ts +3 -3
- package/dist/providers/fireworks/index.js +321 -31
- package/dist/providers/fireworks/index.mjs +321 -31
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +321 -31
- package/dist/providers/google/index.mjs +321 -31
- package/dist/providers/ollama/index.d.mts +4 -4
- package/dist/providers/ollama/index.d.ts +4 -4
- package/dist/providers/ollama/index.js +87 -2
- package/dist/providers/ollama/index.mjs +87 -2
- package/dist/providers/openai/index.d.mts +25 -6
- package/dist/providers/openai/index.d.ts +25 -6
- package/dist/providers/openai/index.js +750 -40
- package/dist/providers/openai/index.mjs +749 -41
- package/dist/providers/openrouter/index.d.mts +3 -3
- package/dist/providers/openrouter/index.d.ts +3 -3
- package/dist/providers/openrouter/index.js +321 -31
- package/dist/providers/openrouter/index.mjs +321 -31
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +321 -31
- package/dist/providers/togetherai/index.mjs +321 -31
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +321 -31
- package/dist/providers/xai/index.mjs +321 -31
- package/dist/{types-BWRYIvl_.d.mts → types-C3RgRbEC.d.mts} +1 -1
- package/dist/{types-CpXcT-tY.d.ts → types-DCFqkJAy.d.ts} +1 -1
- package/dist/{types-NOswlhm9.d.mts → types-DHQEMFHA.d.mts} +3 -3
- package/dist/{types-BW3csYo5.d.mts → types-Dg1Bcg1k.d.mts} +106 -3
- package/dist/{types-BW3csYo5.d.ts → types-Dg1Bcg1k.d.ts} +106 -3
- package/dist/{types-DA54bSmX.d.ts → types-Dikj3En4.d.ts} +3 -3
- package/dist/yourgpt/index.d.mts +1 -1
- package/dist/yourgpt/index.d.ts +1 -1
- package/docs/multimodal-and-audio.md +170 -0
- package/package.json +3 -2
|
@@ -1,3 +1,106 @@
|
|
|
1
|
+
// src/core/tool-output.ts
|
|
2
|
+
var imageTypes = /* @__PURE__ */ new Set([
|
|
3
|
+
"image/png",
|
|
4
|
+
"image/jpeg",
|
|
5
|
+
"image/webp",
|
|
6
|
+
"image/gif"
|
|
7
|
+
]);
|
|
8
|
+
function normalizeToolOutput(value) {
|
|
9
|
+
const output = value;
|
|
10
|
+
if (output?.type !== "content" || !Array.isArray(output.content) || !output.content.length) {
|
|
11
|
+
throw new Error("Tool model output must contain a nonempty content array");
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
type: "content",
|
|
15
|
+
content: output.content.map((part) => {
|
|
16
|
+
if (part?.type === "text" && typeof part.text === "string")
|
|
17
|
+
return { type: "text", text: part.text };
|
|
18
|
+
if (part?.type !== "image")
|
|
19
|
+
throw new Error("Unsupported tool output content type");
|
|
20
|
+
const sources = ["data", "url", "fileId"].filter((key) => key in part);
|
|
21
|
+
if (sources.length !== 1)
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Tool image requires exactly one of data, url, or fileId"
|
|
24
|
+
);
|
|
25
|
+
if ("fileId" in part) {
|
|
26
|
+
if (part.provider !== "openai" || typeof part.fileId !== "string" || !part.fileId.trim())
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Tool image file references must name the openai provider and fileId"
|
|
29
|
+
);
|
|
30
|
+
return { type: "image", fileId: part.fileId, provider: "openai" };
|
|
31
|
+
}
|
|
32
|
+
if ("url" in part) {
|
|
33
|
+
let url;
|
|
34
|
+
try {
|
|
35
|
+
url = new URL(part.url);
|
|
36
|
+
} catch {
|
|
37
|
+
throw new Error("Invalid tool image URL");
|
|
38
|
+
}
|
|
39
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password)
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Tool image URL must use HTTP(S) without credentials"
|
|
42
|
+
);
|
|
43
|
+
return { type: "image", url: url.href };
|
|
44
|
+
}
|
|
45
|
+
if (!("data" in part) || !imageTypes.has(part.mediaType))
|
|
46
|
+
throw new Error("Unsupported tool image media type");
|
|
47
|
+
let data = part.data instanceof Uint8Array ? Buffer.from(part.data).toString("base64") : part.data;
|
|
48
|
+
if (typeof data !== "string")
|
|
49
|
+
throw new Error("Tool image data must be base64 or bytes");
|
|
50
|
+
if (data.startsWith("data:")) {
|
|
51
|
+
const match = /^data:([^;,]+);base64,(.+)$/.exec(data);
|
|
52
|
+
if (!match || match[1] !== part.mediaType)
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Tool image data URL media type does not match mediaType"
|
|
55
|
+
);
|
|
56
|
+
data = match[2];
|
|
57
|
+
}
|
|
58
|
+
if (!data.length || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(data))
|
|
59
|
+
throw new Error("Invalid tool image base64 data");
|
|
60
|
+
if (Buffer.byteLength(data, "base64") > 20 * 1024 * 1024)
|
|
61
|
+
throw new Error("Tool image exceeds 20 MiB");
|
|
62
|
+
if (Buffer.from(data, "base64").toString("base64") !== data)
|
|
63
|
+
throw new Error("Invalid tool image base64 data");
|
|
64
|
+
return { type: "image", data, mediaType: part.mediaType };
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function toolOutputForProvider(value, provider) {
|
|
69
|
+
const output = normalizeToolOutput(value);
|
|
70
|
+
return output.content.map((part) => {
|
|
71
|
+
if (part.type === "text")
|
|
72
|
+
return {
|
|
73
|
+
type: provider === "responses" ? "input_text" : "text",
|
|
74
|
+
text: part.text
|
|
75
|
+
};
|
|
76
|
+
if (provider === "unsupported")
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Image tool results require OpenAI Responses or Anthropic; this endpoint is not supported"
|
|
79
|
+
);
|
|
80
|
+
if ("fileId" in part) {
|
|
81
|
+
if (provider !== "responses")
|
|
82
|
+
throw new Error(
|
|
83
|
+
"OpenAI image file references cannot be sent to another provider"
|
|
84
|
+
);
|
|
85
|
+
return { type: "input_image", file_id: part.fileId, detail: "auto" };
|
|
86
|
+
}
|
|
87
|
+
if (provider === "responses")
|
|
88
|
+
return {
|
|
89
|
+
type: "input_image",
|
|
90
|
+
image_url: "url" in part ? part.url : `data:${part.mediaType};base64,${part.data}`,
|
|
91
|
+
detail: "auto"
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
type: "image",
|
|
95
|
+
source: "url" in part ? { type: "url", url: part.url } : { type: "base64", media_type: part.mediaType, data: part.data }
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function textToolOutput(value, fallback) {
|
|
100
|
+
if (value === void 0) return fallback;
|
|
101
|
+
return toolOutputForProvider(value, "unsupported").map((part) => part.text).join("\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
1
104
|
// src/core/openai-parameters.ts
|
|
2
105
|
function capabilities(model) {
|
|
3
106
|
const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
|
|
@@ -49,6 +152,64 @@ function buildOpenAIParameters(model, maxTokens, temperature, options = {}, api
|
|
|
49
152
|
return result;
|
|
50
153
|
}
|
|
51
154
|
|
|
155
|
+
// src/core/openai-responses.ts
|
|
156
|
+
function resolveOpenAITransport(defaults, overrides) {
|
|
157
|
+
return {
|
|
158
|
+
api: overrides?.api ?? defaults.api ?? "auto",
|
|
159
|
+
responses: defaults.responses !== void 0 || overrides?.responses !== void 0 ? { ...defaults.responses, ...overrides?.responses } : void 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function useOpenAIResponses(model, baseURL, parameters, transport2, hasTools, hasReplay, nativeToolSearch = false) {
|
|
163
|
+
const api = transport2.api ?? "auto";
|
|
164
|
+
if (!["auto", "chat", "responses"].includes(api)) {
|
|
165
|
+
throw new Error(`Unknown OpenAI API selection: ${api}`);
|
|
166
|
+
}
|
|
167
|
+
if (api === "chat") {
|
|
168
|
+
if (transport2.responses !== void 0 || nativeToolSearch) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"Responses options and native tool search require the Responses API"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
let officialEndpoint = !baseURL;
|
|
176
|
+
try {
|
|
177
|
+
officialEndpoint = !baseURL || new URL(baseURL).hostname === "api.openai.com";
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
return api === "responses" || transport2.responses !== void 0 || hasReplay && officialEndpoint || nativeToolSearch || hasTools && requiresOpenAIResponses(model, baseURL, parameters);
|
|
181
|
+
}
|
|
182
|
+
function responseMetadata(response) {
|
|
183
|
+
const output = Array.isArray(response?.output) ? response.output : [];
|
|
184
|
+
return {
|
|
185
|
+
openai: {
|
|
186
|
+
response: {
|
|
187
|
+
id: response?.id,
|
|
188
|
+
model: response?.model,
|
|
189
|
+
status: response?.status,
|
|
190
|
+
output,
|
|
191
|
+
usage: response?.usage,
|
|
192
|
+
store: response?.store,
|
|
193
|
+
refusals: output.flatMap(
|
|
194
|
+
(item) => item?.type === "message" && Array.isArray(item.content) ? item.content.filter(
|
|
195
|
+
(part) => part?.type === "refusal" && typeof part.refusal === "string"
|
|
196
|
+
).map((part) => part.refusal) : []
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function getResponseReplay(metadata) {
|
|
203
|
+
const response = metadata?.openai?.response;
|
|
204
|
+
if (response === void 0) return void 0;
|
|
205
|
+
if (!Array.isArray(response.output) || response.output.some((item) => !item || typeof item.type !== "string")) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"Invalid OpenAI Responses replay metadata: output must contain typed items"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return response.output;
|
|
211
|
+
}
|
|
212
|
+
|
|
52
213
|
// src/core/provider-error.ts
|
|
53
214
|
function providerErrorEvent(error, code) {
|
|
54
215
|
const details = error ?? {};
|
|
@@ -281,7 +442,7 @@ function messageToOpenAIContent(message) {
|
|
|
281
442
|
}
|
|
282
443
|
return blocks;
|
|
283
444
|
}
|
|
284
|
-
function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
445
|
+
function formatMessagesForOpenAI(messages, systemPrompt, preserveProviderMetadata = false) {
|
|
285
446
|
const formatted = [];
|
|
286
447
|
if (systemPrompt) {
|
|
287
448
|
formatted.push({ role: "system", content: systemPrompt });
|
|
@@ -299,7 +460,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
299
460
|
const assistantMsg = {
|
|
300
461
|
role: "assistant",
|
|
301
462
|
// Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
|
|
302
|
-
content: hasToolCalls ? msg.content || null : msg.content
|
|
463
|
+
content: hasToolCalls ? msg.content || null : msg.content,
|
|
464
|
+
...preserveProviderMetadata && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
|
|
303
465
|
};
|
|
304
466
|
if (hasToolCalls) {
|
|
305
467
|
assistantMsg.tool_calls = msg.tool_calls;
|
|
@@ -308,7 +470,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
308
470
|
} else if (msg.role === "tool" && msg.tool_call_id) {
|
|
309
471
|
formatted.push({
|
|
310
472
|
role: "tool",
|
|
311
|
-
content: msg.content ?? "",
|
|
473
|
+
content: preserveProviderMetadata ? msg.content ?? "" : textToolOutput(msg.modelOutput, msg.content ?? ""),
|
|
474
|
+
...preserveProviderMetadata && msg.modelOutput ? { modelOutput: msg.modelOutput } : {},
|
|
312
475
|
tool_call_id: msg.tool_call_id
|
|
313
476
|
});
|
|
314
477
|
}
|
|
@@ -359,13 +522,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
359
522
|
}));
|
|
360
523
|
}
|
|
361
524
|
shouldUseResponsesApi(request) {
|
|
362
|
-
|
|
363
|
-
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
|
|
364
|
-
return true;
|
|
365
|
-
return requiresOpenAIResponses(
|
|
525
|
+
return useOpenAIResponses(
|
|
366
526
|
request.config?.model || this.model,
|
|
367
527
|
this.config.baseUrl,
|
|
368
|
-
this.parameterOptions(request)
|
|
528
|
+
this.parameterOptions(request),
|
|
529
|
+
resolveOpenAITransport(this.config, request.config?.openai),
|
|
530
|
+
this.toolDefinitions(request).length > 0,
|
|
531
|
+
!!(request.rawMessages ?? request.messages)?.some(
|
|
532
|
+
(message) => message.providerMetadata?.openai?.response
|
|
533
|
+
),
|
|
534
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false
|
|
369
535
|
);
|
|
370
536
|
}
|
|
371
537
|
/**
|
|
@@ -398,11 +564,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
398
564
|
});
|
|
399
565
|
}
|
|
400
566
|
buildResponsesInput(request) {
|
|
401
|
-
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
|
|
567
|
+
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
|
|
402
568
|
const input = [];
|
|
403
569
|
for (const message of sourceMessages) {
|
|
404
570
|
if (message.role === "system") {
|
|
405
|
-
if (
|
|
571
|
+
if (message.content && message.content !== request.systemPrompt) {
|
|
406
572
|
input.push({
|
|
407
573
|
type: "message",
|
|
408
574
|
role: "developer",
|
|
@@ -412,6 +578,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
412
578
|
continue;
|
|
413
579
|
}
|
|
414
580
|
if (message.role === "assistant") {
|
|
581
|
+
const replay = getResponseReplay(message.providerMetadata);
|
|
582
|
+
if (replay) {
|
|
583
|
+
input.push(...replay);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
415
586
|
const content = this.toResponsesContent(message.content, "assistant");
|
|
416
587
|
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
417
588
|
input.push({
|
|
@@ -435,7 +606,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
435
606
|
input.push({
|
|
436
607
|
type: "function_call_output",
|
|
437
608
|
call_id: message.tool_call_id,
|
|
438
|
-
output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
609
|
+
output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
439
610
|
});
|
|
440
611
|
continue;
|
|
441
612
|
}
|
|
@@ -449,10 +620,12 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
449
620
|
return input;
|
|
450
621
|
}
|
|
451
622
|
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
452
|
-
formatRawMessages(messages) {
|
|
623
|
+
formatRawMessages(messages, preserveProviderMetadata = false) {
|
|
453
624
|
return messages.map((message) => {
|
|
454
|
-
const { attachments, ...rest } = message;
|
|
455
|
-
let content = message.content;
|
|
625
|
+
const { attachments, providerMetadata, modelOutput, ...rest } = message;
|
|
626
|
+
let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
|
|
627
|
+
if (preserveProviderMetadata && modelOutput)
|
|
628
|
+
rest.modelOutput = modelOutput;
|
|
456
629
|
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
|
|
457
630
|
content = null;
|
|
458
631
|
}
|
|
@@ -465,7 +638,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
465
638
|
content = [...parts, ...images];
|
|
466
639
|
}
|
|
467
640
|
}
|
|
468
|
-
return {
|
|
641
|
+
return {
|
|
642
|
+
...rest,
|
|
643
|
+
content,
|
|
644
|
+
...preserveProviderMetadata && providerMetadata ? { providerMetadata } : {}
|
|
645
|
+
};
|
|
469
646
|
});
|
|
470
647
|
}
|
|
471
648
|
/**
|
|
@@ -546,14 +723,25 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
546
723
|
).map((part) => part.text) : []
|
|
547
724
|
).join("") : "";
|
|
548
725
|
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
name
|
|
552
|
-
|
|
553
|
-
|
|
726
|
+
const seenCallIds = /* @__PURE__ */ new Set();
|
|
727
|
+
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
|
|
728
|
+
if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
|
|
729
|
+
throw new Error("Responses contains an incomplete function call");
|
|
730
|
+
}
|
|
731
|
+
if (seenCallIds.has(item.call_id))
|
|
732
|
+
throw new Error("Responses contains duplicate function call IDs");
|
|
733
|
+
seenCallIds.add(item.call_id);
|
|
734
|
+
const args = JSON.parse(item.arguments);
|
|
735
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
736
|
+
throw new Error(
|
|
737
|
+
"Responses function arguments must be a JSON object"
|
|
738
|
+
);
|
|
739
|
+
return { id: item.call_id, name: item.name, args };
|
|
740
|
+
}) : [];
|
|
554
741
|
return {
|
|
555
742
|
content,
|
|
556
743
|
toolCalls,
|
|
744
|
+
providerMetadata: responseMetadata(response),
|
|
557
745
|
usage: response?.usage ? {
|
|
558
746
|
promptTokens: response.usage.input_tokens ?? 0,
|
|
559
747
|
completionTokens: response.usage.output_tokens ?? 0,
|
|
@@ -564,6 +752,17 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
564
752
|
}
|
|
565
753
|
buildResponsesPayload(request, stream) {
|
|
566
754
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
755
|
+
const options = resolveOpenAITransport(
|
|
756
|
+
this.config,
|
|
757
|
+
request.config?.openai
|
|
758
|
+
).responses;
|
|
759
|
+
const parameters = buildOpenAIParameters(
|
|
760
|
+
request.config?.model || this.model,
|
|
761
|
+
request.config?.maxTokens ?? this.config.maxTokens,
|
|
762
|
+
request.config?.temperature ?? this.config.temperature,
|
|
763
|
+
this.parameterOptions(request),
|
|
764
|
+
"responses"
|
|
765
|
+
);
|
|
567
766
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
568
767
|
request.config?.responseFormat
|
|
569
768
|
);
|
|
@@ -579,13 +778,26 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
579
778
|
),
|
|
580
779
|
tool_choice: openaiToolOptions?.toolChoice,
|
|
581
780
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
582
|
-
...
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
781
|
+
...parameters,
|
|
782
|
+
...options?.reasoningSummary ? {
|
|
783
|
+
reasoning: {
|
|
784
|
+
...parameters.reasoning,
|
|
785
|
+
summary: options.reasoningSummary
|
|
786
|
+
}
|
|
787
|
+
} : {},
|
|
788
|
+
...options?.store !== void 0 ? { store: options.store } : {},
|
|
789
|
+
...options?.previousResponseId !== void 0 ? { previous_response_id: options.previousResponseId } : {},
|
|
790
|
+
...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
|
|
791
|
+
...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
|
|
792
|
+
...options?.promptCacheRetention !== void 0 ? { prompt_cache_retention: options.promptCacheRetention } : {},
|
|
793
|
+
...options?.store === false || options?.include ? {
|
|
794
|
+
include: [
|
|
795
|
+
.../* @__PURE__ */ new Set([
|
|
796
|
+
...options?.include ?? [],
|
|
797
|
+
...options?.store === false ? ["reasoning.encrypted_content"] : []
|
|
798
|
+
])
|
|
799
|
+
]
|
|
800
|
+
} : {},
|
|
589
801
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
590
802
|
stream
|
|
591
803
|
};
|
|
@@ -644,6 +856,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
644
856
|
let usage;
|
|
645
857
|
const citations = [];
|
|
646
858
|
let completed = false;
|
|
859
|
+
let terminalResponse;
|
|
860
|
+
let responseIdentity = {};
|
|
861
|
+
const outputItems = /* @__PURE__ */ new Map();
|
|
862
|
+
let streamedText = "";
|
|
647
863
|
function* flushToolCalls() {
|
|
648
864
|
for (const call of toolCalls.values()) {
|
|
649
865
|
if (!call.name)
|
|
@@ -660,17 +876,36 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
660
876
|
for await (const event of stream) {
|
|
661
877
|
throwIfAborted(request.signal);
|
|
662
878
|
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
879
|
+
if (completed)
|
|
880
|
+
throw new Error("Responses stream emitted an event after completion");
|
|
663
881
|
const index = event?.output_index ?? 0;
|
|
664
882
|
switch (event?.type) {
|
|
883
|
+
case "response.created":
|
|
884
|
+
responseIdentity = {
|
|
885
|
+
id: event.response?.id,
|
|
886
|
+
model: event.response?.model
|
|
887
|
+
};
|
|
888
|
+
break;
|
|
889
|
+
case "response.output_item.done":
|
|
890
|
+
if (event.item) outputItems.set(index, event.item);
|
|
891
|
+
if (event.item?.type === "function_call") {
|
|
892
|
+
toolCalls.set(index, {
|
|
893
|
+
id: event.item.call_id,
|
|
894
|
+
name: event.item.name,
|
|
895
|
+
arguments: event.item.arguments
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
break;
|
|
665
899
|
case "response.output_text.delta":
|
|
666
900
|
if (event.delta) {
|
|
901
|
+
streamedText += event.delta;
|
|
667
902
|
yield { type: "message:delta", content: event.delta };
|
|
668
903
|
}
|
|
669
904
|
break;
|
|
670
905
|
case "response.output_item.added":
|
|
671
906
|
if (event.item?.type === "function_call") {
|
|
672
907
|
toolCalls.set(index, {
|
|
673
|
-
id: event.item.call_id ??
|
|
908
|
+
id: event.item.call_id ?? "",
|
|
674
909
|
name: event.item.name ?? "",
|
|
675
910
|
arguments: event.item.arguments ?? ""
|
|
676
911
|
});
|
|
@@ -689,7 +924,10 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
689
924
|
break;
|
|
690
925
|
}
|
|
691
926
|
case "response.completed": {
|
|
927
|
+
if (event.response?.status && event.response.status !== "completed")
|
|
928
|
+
throw new Error(`Responses stream ${event.response.status}`);
|
|
692
929
|
completed = true;
|
|
930
|
+
terminalResponse = { ...responseIdentity, ...event.response };
|
|
693
931
|
const found = this.collectResponsesCitations(
|
|
694
932
|
event.response?.output,
|
|
695
933
|
citations.length
|
|
@@ -726,12 +964,64 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
726
964
|
throwIfAborted(request.signal);
|
|
727
965
|
if (!completed)
|
|
728
966
|
throw new Error("Responses stream ended without a completed response");
|
|
967
|
+
const output = terminalResponse.output?.length ? terminalResponse.output : [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
|
|
968
|
+
if (output.some((item) => item.type === "function_call"))
|
|
969
|
+
toolCalls.clear();
|
|
970
|
+
for (const [index, item] of output.entries()) {
|
|
971
|
+
if (item.type === "function_call") {
|
|
972
|
+
toolCalls.set(index, {
|
|
973
|
+
id: item.call_id,
|
|
974
|
+
name: item.name,
|
|
975
|
+
arguments: item.arguments
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
const uniqueCalls = /* @__PURE__ */ new Map();
|
|
980
|
+
for (const call of toolCalls.values()) {
|
|
981
|
+
if (!call.id || !call.name)
|
|
982
|
+
throw new Error("Responses stream contains an incomplete tool call");
|
|
983
|
+
uniqueCalls.set(call.id, call);
|
|
984
|
+
}
|
|
985
|
+
toolCalls.clear();
|
|
986
|
+
[...uniqueCalls.values()].forEach(
|
|
987
|
+
(call, index) => toolCalls.set(index, call)
|
|
988
|
+
);
|
|
989
|
+
if (!output.length && toolCalls.size) {
|
|
990
|
+
for (const call of toolCalls.values())
|
|
991
|
+
output.push({
|
|
992
|
+
type: "function_call",
|
|
993
|
+
call_id: call.id,
|
|
994
|
+
name: call.name,
|
|
995
|
+
arguments: call.arguments
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
if (!output.length && streamedText)
|
|
999
|
+
output.push({
|
|
1000
|
+
type: "message",
|
|
1001
|
+
role: "assistant",
|
|
1002
|
+
content: [{ type: "output_text", text: streamedText, annotations: [] }]
|
|
1003
|
+
});
|
|
1004
|
+
terminalResponse.output = output;
|
|
1005
|
+
const finalText = this.parseResponsesResult(terminalResponse).content;
|
|
1006
|
+
if (finalText.startsWith(streamedText) && finalText.length > streamedText.length) {
|
|
1007
|
+
yield {
|
|
1008
|
+
type: "message:delta",
|
|
1009
|
+
content: finalText.slice(streamedText.length)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
729
1012
|
yield* flushToolCalls();
|
|
730
1013
|
if (citations.length > 0) {
|
|
731
1014
|
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
732
1015
|
}
|
|
733
|
-
yield {
|
|
734
|
-
|
|
1016
|
+
yield {
|
|
1017
|
+
type: "message:end",
|
|
1018
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1019
|
+
};
|
|
1020
|
+
yield {
|
|
1021
|
+
type: "done",
|
|
1022
|
+
usage,
|
|
1023
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1024
|
+
};
|
|
735
1025
|
}
|
|
736
1026
|
async *stream(request) {
|
|
737
1027
|
throwIfAborted(request.signal);
|
|
@@ -1099,7 +1389,16 @@ function openai(modelId, options = {}) {
|
|
|
1099
1389
|
};
|
|
1100
1390
|
}
|
|
1101
1391
|
function useResponses(params) {
|
|
1102
|
-
return
|
|
1392
|
+
return useOpenAIResponses(
|
|
1393
|
+
modelId,
|
|
1394
|
+
baseURL,
|
|
1395
|
+
parameterOptions(params),
|
|
1396
|
+
resolveOpenAITransport(options, params.openai),
|
|
1397
|
+
!!params.tools?.length,
|
|
1398
|
+
params.messages.some(
|
|
1399
|
+
(message) => message.role === "assistant" && !!message.providerMetadata?.openai?.response
|
|
1400
|
+
)
|
|
1401
|
+
);
|
|
1103
1402
|
}
|
|
1104
1403
|
const responsesAdapter = new OpenAIAdapter({
|
|
1105
1404
|
...options,
|
|
@@ -1110,7 +1409,7 @@ function openai(modelId, options = {}) {
|
|
|
1110
1409
|
function responsesRequest(params) {
|
|
1111
1410
|
return {
|
|
1112
1411
|
messages: [],
|
|
1113
|
-
rawMessages: formatMessagesForOpenAI2(params.messages),
|
|
1412
|
+
rawMessages: formatMessagesForOpenAI2(params.messages, true),
|
|
1114
1413
|
toolDefinitions: params.tools?.map((raw) => {
|
|
1115
1414
|
const tool = raw;
|
|
1116
1415
|
if (tool.type !== "function")
|
|
@@ -1125,6 +1424,10 @@ function openai(modelId, options = {}) {
|
|
|
1125
1424
|
};
|
|
1126
1425
|
}),
|
|
1127
1426
|
config: {
|
|
1427
|
+
openai: {
|
|
1428
|
+
...resolveOpenAITransport(options, params.openai),
|
|
1429
|
+
api: "responses"
|
|
1430
|
+
},
|
|
1128
1431
|
temperature: params.temperature,
|
|
1129
1432
|
maxTokens: params.maxTokens,
|
|
1130
1433
|
responseFormat: params.responseFormat,
|
|
@@ -1156,13 +1459,14 @@ function openai(modelId, options = {}) {
|
|
|
1156
1459
|
return {
|
|
1157
1460
|
text: result.content,
|
|
1158
1461
|
toolCalls: result.toolCalls,
|
|
1159
|
-
finishReason: result.toolCalls.length ? "tool-calls" : "stop",
|
|
1462
|
+
finishReason: result.toolCalls.length ? "tool-calls" : result.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
|
|
1160
1463
|
usage: result.usage ?? {
|
|
1161
1464
|
promptTokens: 0,
|
|
1162
1465
|
completionTokens: 0,
|
|
1163
1466
|
totalTokens: 0
|
|
1164
1467
|
},
|
|
1165
|
-
rawResponse: result.rawResponse
|
|
1468
|
+
rawResponse: result.rawResponse,
|
|
1469
|
+
providerMetadata: result.providerMetadata
|
|
1166
1470
|
};
|
|
1167
1471
|
}
|
|
1168
1472
|
const client2 = await getClient();
|
|
@@ -1244,7 +1548,8 @@ function openai(modelId, options = {}) {
|
|
|
1244
1548
|
case "done":
|
|
1245
1549
|
yield {
|
|
1246
1550
|
type: "finish",
|
|
1247
|
-
finishReason: hasTools ? "tool-calls" : "stop",
|
|
1551
|
+
finishReason: hasTools ? "tool-calls" : event.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
|
|
1552
|
+
providerMetadata: event.providerMetadata,
|
|
1248
1553
|
usage: {
|
|
1249
1554
|
promptTokens: event.usage?.prompt_tokens ?? 0,
|
|
1250
1555
|
completionTokens: event.usage?.completion_tokens ?? 0,
|
|
@@ -1340,7 +1645,7 @@ function mapFinishReason(reason) {
|
|
|
1340
1645
|
return "unknown";
|
|
1341
1646
|
}
|
|
1342
1647
|
}
|
|
1343
|
-
function formatMessagesForOpenAI2(messages) {
|
|
1648
|
+
function formatMessagesForOpenAI2(messages, responses = false) {
|
|
1344
1649
|
return messages.map((msg) => {
|
|
1345
1650
|
switch (msg.role) {
|
|
1346
1651
|
case "system":
|
|
@@ -1357,16 +1662,28 @@ function formatMessagesForOpenAI2(messages) {
|
|
|
1357
1662
|
}
|
|
1358
1663
|
if (part.type === "image") {
|
|
1359
1664
|
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
1360
|
-
const url =
|
|
1665
|
+
const url = /^(data:|https?:\/\/)/.test(imageData) ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
1361
1666
|
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
1362
1667
|
}
|
|
1363
|
-
|
|
1668
|
+
if (part.type === "file") {
|
|
1669
|
+
if (!responses)
|
|
1670
|
+
throw new Error(
|
|
1671
|
+
"File inputs require the Responses API on this OpenAI path"
|
|
1672
|
+
);
|
|
1673
|
+
return /^https?:\/\//.test(part.data) ? { type: "input_file", file_url: part.data } : {
|
|
1674
|
+
type: "input_file",
|
|
1675
|
+
filename: part.filename ?? "document.pdf",
|
|
1676
|
+
file_data: part.data.startsWith("data:") ? part.data : `data:${part.mimeType};base64,${part.data}`
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
throw new Error("Unsupported OpenAI message content");
|
|
1364
1680
|
})
|
|
1365
1681
|
};
|
|
1366
1682
|
case "assistant":
|
|
1367
1683
|
const assistantMsg = {
|
|
1368
1684
|
role: "assistant",
|
|
1369
|
-
content: msg.content
|
|
1685
|
+
content: msg.content,
|
|
1686
|
+
...responses && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
|
|
1370
1687
|
};
|
|
1371
1688
|
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
1372
1689
|
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
@@ -1383,7 +1700,8 @@ function formatMessagesForOpenAI2(messages) {
|
|
|
1383
1700
|
return {
|
|
1384
1701
|
role: "tool",
|
|
1385
1702
|
tool_call_id: msg.toolCallId,
|
|
1386
|
-
content: msg.content
|
|
1703
|
+
content: responses ? msg.content : textToolOutput(msg.modelOutput, msg.content),
|
|
1704
|
+
...responses && msg.modelOutput ? { modelOutput: msg.modelOutput } : {}
|
|
1387
1705
|
};
|
|
1388
1706
|
default:
|
|
1389
1707
|
return msg;
|
|
@@ -1407,6 +1725,394 @@ function createCallableProvider(providerFn, properties) {
|
|
|
1407
1725
|
return providerFn;
|
|
1408
1726
|
}
|
|
1409
1727
|
|
|
1728
|
+
// src/core/audio.ts
|
|
1729
|
+
var audioErrorMarker = /* @__PURE__ */ Symbol.for("@yourgpt/llm-sdk/AudioAPIError");
|
|
1730
|
+
var _a;
|
|
1731
|
+
var AudioAPIError = class extends Error {
|
|
1732
|
+
constructor(message, code, status, requestId) {
|
|
1733
|
+
super(message);
|
|
1734
|
+
this.code = code;
|
|
1735
|
+
this.status = status;
|
|
1736
|
+
this.requestId = requestId;
|
|
1737
|
+
this[_a] = true;
|
|
1738
|
+
this.name = "AudioAPIError";
|
|
1739
|
+
}
|
|
1740
|
+
static [(_a = audioErrorMarker, Symbol.hasInstance)](value) {
|
|
1741
|
+
return !!value && typeof value === "object" && value[audioErrorMarker] === true;
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
|
|
1745
|
+
// src/providers/openai/audio.ts
|
|
1746
|
+
var formats = {
|
|
1747
|
+
mp3: "audio/mpeg",
|
|
1748
|
+
opus: "audio/ogg",
|
|
1749
|
+
aac: "audio/aac",
|
|
1750
|
+
flac: "audio/flac",
|
|
1751
|
+
wav: "audio/wav",
|
|
1752
|
+
pcm: "audio/pcm"
|
|
1753
|
+
};
|
|
1754
|
+
var extensions = {
|
|
1755
|
+
"audio/mpeg": "mp3",
|
|
1756
|
+
"audio/mp3": "mp3",
|
|
1757
|
+
"audio/mp4": "m4a",
|
|
1758
|
+
"video/mp4": "mp4",
|
|
1759
|
+
"audio/wav": "wav",
|
|
1760
|
+
"audio/x-wav": "wav",
|
|
1761
|
+
"audio/webm": "webm",
|
|
1762
|
+
"video/webm": "webm",
|
|
1763
|
+
"audio/ogg": "ogg",
|
|
1764
|
+
"audio/flac": "flac",
|
|
1765
|
+
"audio/x-flac": "flac"
|
|
1766
|
+
};
|
|
1767
|
+
var MAX_AUDIO = 25e6;
|
|
1768
|
+
function hasAudioHeader(data, format) {
|
|
1769
|
+
const head = Buffer.from(data.subarray(0, 12)).toString("latin1");
|
|
1770
|
+
if (format === "wav")
|
|
1771
|
+
return head.startsWith("RIFF") && head.slice(8, 12) === "WAVE";
|
|
1772
|
+
if (format === "mp3" || format === "mpeg" || format === "mpga")
|
|
1773
|
+
return head.startsWith("ID3") || data[0] === 255 && (data[1] & 224) === 224;
|
|
1774
|
+
if (format === "opus" || format === "ogg") return head.startsWith("OggS");
|
|
1775
|
+
if (format === "flac") return head.startsWith("fLaC");
|
|
1776
|
+
if (format === "aac") return data[0] === 255 && (data[1] & 246) === 240;
|
|
1777
|
+
if (format === "webm")
|
|
1778
|
+
return data[0] === 26 && data[1] === 69 && data[2] === 223 && data[3] === 163;
|
|
1779
|
+
if (format === "m4a" || format === "mp4") return head.slice(4, 8) === "ftyp";
|
|
1780
|
+
return format === "pcm" && data.length > 0 && data.length % 2 === 0;
|
|
1781
|
+
}
|
|
1782
|
+
function invalid(message) {
|
|
1783
|
+
throw new AudioAPIError(message, "invalid_input");
|
|
1784
|
+
}
|
|
1785
|
+
function unsupported(message) {
|
|
1786
|
+
throw new AudioAPIError(message, "unsupported_option");
|
|
1787
|
+
}
|
|
1788
|
+
function providerOptions(value, allowed) {
|
|
1789
|
+
for (const [provider, options] of Object.entries(value ?? {})) {
|
|
1790
|
+
if (provider !== "openai")
|
|
1791
|
+
unsupported(
|
|
1792
|
+
`Provider options for ${provider} cannot be used with OpenAI audio`
|
|
1793
|
+
);
|
|
1794
|
+
for (const key of Object.keys(options))
|
|
1795
|
+
if (!allowed.includes(key))
|
|
1796
|
+
unsupported(`Unsupported OpenAI audio option: ${key}`);
|
|
1797
|
+
}
|
|
1798
|
+
return value?.openai ?? {};
|
|
1799
|
+
}
|
|
1800
|
+
async function readBody(response, limit) {
|
|
1801
|
+
if (Number(response.headers.get("content-length")) > limit) {
|
|
1802
|
+
await response.body?.cancel();
|
|
1803
|
+
throw new AudioAPIError(
|
|
1804
|
+
"Audio API response exceeds the size limit",
|
|
1805
|
+
"invalid_response"
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
const reader = response.body?.getReader();
|
|
1809
|
+
if (!reader) return new Uint8Array();
|
|
1810
|
+
const chunks = [];
|
|
1811
|
+
let length = 0;
|
|
1812
|
+
try {
|
|
1813
|
+
while (true) {
|
|
1814
|
+
const { done, value } = await reader.read();
|
|
1815
|
+
if (done) break;
|
|
1816
|
+
length += value.length;
|
|
1817
|
+
if (length > limit) {
|
|
1818
|
+
await reader.cancel();
|
|
1819
|
+
throw new AudioAPIError(
|
|
1820
|
+
"Audio API response exceeds the size limit",
|
|
1821
|
+
"invalid_response"
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
chunks.push(value);
|
|
1825
|
+
}
|
|
1826
|
+
} finally {
|
|
1827
|
+
reader.releaseLock();
|
|
1828
|
+
}
|
|
1829
|
+
const data = new Uint8Array(length);
|
|
1830
|
+
let offset = 0;
|
|
1831
|
+
for (const chunk of chunks) {
|
|
1832
|
+
data.set(chunk, offset);
|
|
1833
|
+
offset += chunk.length;
|
|
1834
|
+
}
|
|
1835
|
+
return data;
|
|
1836
|
+
}
|
|
1837
|
+
function transport(config) {
|
|
1838
|
+
return async (path, body, signal) => {
|
|
1839
|
+
signal?.throwIfAborted();
|
|
1840
|
+
const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
|
|
1841
|
+
if (!apiKey) invalid("OpenAI audio requires an API key on the server");
|
|
1842
|
+
const headers = {
|
|
1843
|
+
Authorization: `Bearer ${apiKey}`
|
|
1844
|
+
};
|
|
1845
|
+
if (typeof body === "string") headers["Content-Type"] = "application/json";
|
|
1846
|
+
if (config.organization)
|
|
1847
|
+
headers["OpenAI-Organization"] = config.organization;
|
|
1848
|
+
if (config.project) headers["OpenAI-Project"] = config.project;
|
|
1849
|
+
let response;
|
|
1850
|
+
try {
|
|
1851
|
+
response = await (config.fetch ?? globalThis.fetch)(
|
|
1852
|
+
`${(config.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "")}${path}`,
|
|
1853
|
+
{ method: "POST", headers, body, signal, redirect: "error" }
|
|
1854
|
+
);
|
|
1855
|
+
} catch {
|
|
1856
|
+
signal?.throwIfAborted();
|
|
1857
|
+
throw new AudioAPIError("OpenAI audio request failed", "network_error");
|
|
1858
|
+
}
|
|
1859
|
+
if (!response.ok) {
|
|
1860
|
+
await response.body?.cancel();
|
|
1861
|
+
throw new AudioAPIError(
|
|
1862
|
+
`OpenAI audio request failed (HTTP ${response.status})`,
|
|
1863
|
+
"provider_error",
|
|
1864
|
+
response.status,
|
|
1865
|
+
response.headers.get("x-request-id") ?? void 0
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
return response;
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
function openaiSpeech(modelId = "gpt-4o-mini-tts", config = {}) {
|
|
1872
|
+
const request = transport(config);
|
|
1873
|
+
return {
|
|
1874
|
+
provider: "openai",
|
|
1875
|
+
modelId,
|
|
1876
|
+
async doGenerate(options) {
|
|
1877
|
+
const {
|
|
1878
|
+
text,
|
|
1879
|
+
voice = "alloy",
|
|
1880
|
+
outputFormat = "mp3",
|
|
1881
|
+
speed,
|
|
1882
|
+
instructions,
|
|
1883
|
+
signal
|
|
1884
|
+
} = options;
|
|
1885
|
+
signal?.throwIfAborted();
|
|
1886
|
+
providerOptions(options.providerOptions, []);
|
|
1887
|
+
if (typeof text !== "string" || !text.trim() || text.length > 4096)
|
|
1888
|
+
invalid("Speech text must contain 1\u20134096 characters");
|
|
1889
|
+
if (!Object.prototype.hasOwnProperty.call(formats, outputFormat))
|
|
1890
|
+
unsupported("Unsupported OpenAI speech output format");
|
|
1891
|
+
if (typeof voice !== "string" || !voice.trim())
|
|
1892
|
+
invalid("Speech voice must not be empty");
|
|
1893
|
+
if (speed !== void 0 && (!Number.isFinite(speed) || speed < 0.25 || speed > 4))
|
|
1894
|
+
invalid("Speech speed must be between 0.25 and 4");
|
|
1895
|
+
if (instructions !== void 0) {
|
|
1896
|
+
if (typeof instructions !== "string" || instructions.length > 4096)
|
|
1897
|
+
invalid(
|
|
1898
|
+
"Speech instructions must be a string of at most 4096 characters"
|
|
1899
|
+
);
|
|
1900
|
+
if (modelId === "tts-1" || modelId === "tts-1-hd")
|
|
1901
|
+
unsupported("Instructions are not supported by tts-1 or tts-1-hd");
|
|
1902
|
+
}
|
|
1903
|
+
const response = await request(
|
|
1904
|
+
"/audio/speech",
|
|
1905
|
+
JSON.stringify({
|
|
1906
|
+
model: modelId,
|
|
1907
|
+
input: text,
|
|
1908
|
+
voice,
|
|
1909
|
+
response_format: outputFormat,
|
|
1910
|
+
speed,
|
|
1911
|
+
instructions
|
|
1912
|
+
}),
|
|
1913
|
+
signal
|
|
1914
|
+
);
|
|
1915
|
+
const mediaType = response.headers.get("content-type")?.split(";")[0].trim();
|
|
1916
|
+
if (mediaType && !mediaType.startsWith("audio/") && mediaType !== "application/octet-stream") {
|
|
1917
|
+
await response.body?.cancel();
|
|
1918
|
+
throw new AudioAPIError(
|
|
1919
|
+
"Speech endpoint returned a non-audio response",
|
|
1920
|
+
"invalid_response"
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
const data = await readBody(response, MAX_AUDIO);
|
|
1924
|
+
signal?.throwIfAborted();
|
|
1925
|
+
if (!data.length || !hasAudioHeader(data, outputFormat))
|
|
1926
|
+
throw new AudioAPIError(
|
|
1927
|
+
"Speech endpoint returned empty or invalid audio",
|
|
1928
|
+
"invalid_response"
|
|
1929
|
+
);
|
|
1930
|
+
return {
|
|
1931
|
+
audio: {
|
|
1932
|
+
uint8Array: data,
|
|
1933
|
+
get base64() {
|
|
1934
|
+
return Buffer.from(data).toString("base64");
|
|
1935
|
+
},
|
|
1936
|
+
mediaType: formats[outputFormat],
|
|
1937
|
+
format: outputFormat
|
|
1938
|
+
},
|
|
1939
|
+
warnings: [],
|
|
1940
|
+
response: {
|
|
1941
|
+
provider: "openai",
|
|
1942
|
+
modelId,
|
|
1943
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
1944
|
+
status: response.status
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
async function audioBytes(options) {
|
|
1951
|
+
const blob = typeof Blob !== "undefined" && options.audio instanceof Blob ? options.audio : void 0;
|
|
1952
|
+
if (blob && blob.size > MAX_AUDIO)
|
|
1953
|
+
invalid("Transcription file exceeds 25 MB");
|
|
1954
|
+
const declared = options.mediaType?.split(";")[0].trim();
|
|
1955
|
+
const blobType = blob?.type.split(";")[0].trim();
|
|
1956
|
+
if (declared && blobType && declared !== blobType)
|
|
1957
|
+
invalid("Transcription mediaType does not match the Blob type");
|
|
1958
|
+
const mediaType = declared || blobType || "";
|
|
1959
|
+
const extension = extensions[mediaType];
|
|
1960
|
+
if (!extension)
|
|
1961
|
+
unsupported(
|
|
1962
|
+
"Unsupported transcription media type; provide an encoded audio file"
|
|
1963
|
+
);
|
|
1964
|
+
let bytes;
|
|
1965
|
+
if (blob) bytes = new Uint8Array(await blob.arrayBuffer());
|
|
1966
|
+
else if (options.audio instanceof Uint8Array) bytes = options.audio;
|
|
1967
|
+
else if (options.audio instanceof ArrayBuffer)
|
|
1968
|
+
bytes = new Uint8Array(options.audio);
|
|
1969
|
+
else if (typeof options.audio === "string") {
|
|
1970
|
+
const base64 = options.audio;
|
|
1971
|
+
if (base64.length > MAX_AUDIO * 4 / 3 + 4)
|
|
1972
|
+
invalid("Transcription file exceeds 25 MB");
|
|
1973
|
+
if (!base64 || base64.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64))
|
|
1974
|
+
invalid(
|
|
1975
|
+
"Audio must be bytes, a Blob, or valid base64; URLs are not fetched"
|
|
1976
|
+
);
|
|
1977
|
+
bytes = Buffer.from(base64, "base64");
|
|
1978
|
+
if (Buffer.from(bytes).toString("base64") !== base64)
|
|
1979
|
+
invalid("Invalid audio base64");
|
|
1980
|
+
} else invalid("Audio must be bytes, a Blob, or base64");
|
|
1981
|
+
if (!bytes.length || bytes.length > MAX_AUDIO)
|
|
1982
|
+
invalid("Transcription file must contain 1 byte to 25 MB");
|
|
1983
|
+
if (!hasAudioHeader(bytes, extension))
|
|
1984
|
+
invalid("Audio header does not match the declared media type");
|
|
1985
|
+
return { bytes, mediaType, extension };
|
|
1986
|
+
}
|
|
1987
|
+
function openaiTranscription(modelId = "gpt-4o-mini-transcribe", config = {}) {
|
|
1988
|
+
const request = transport(config);
|
|
1989
|
+
return {
|
|
1990
|
+
provider: "openai",
|
|
1991
|
+
modelId,
|
|
1992
|
+
async doTranscribe(options) {
|
|
1993
|
+
options.signal?.throwIfAborted();
|
|
1994
|
+
if (modelId.includes("realtime"))
|
|
1995
|
+
unsupported(
|
|
1996
|
+
"Realtime models require a realtime session; use a file transcription model"
|
|
1997
|
+
);
|
|
1998
|
+
const opts = providerOptions(options.providerOptions, [
|
|
1999
|
+
"timestampGranularities",
|
|
2000
|
+
"includeLogprobs",
|
|
2001
|
+
"chunkingStrategy"
|
|
2002
|
+
]);
|
|
2003
|
+
const timestamps = opts.timestampGranularities;
|
|
2004
|
+
if (timestamps !== void 0 && (!Array.isArray(timestamps) || !timestamps.length || timestamps.some((x) => x !== "word" && x !== "segment")))
|
|
2005
|
+
invalid("timestampGranularities must contain word or segment");
|
|
2006
|
+
if (timestamps !== void 0 && modelId !== "whisper-1")
|
|
2007
|
+
unsupported("Timestamp granularities require whisper-1");
|
|
2008
|
+
if (opts.includeLogprobs !== void 0 && typeof opts.includeLogprobs !== "boolean")
|
|
2009
|
+
invalid("includeLogprobs must be boolean");
|
|
2010
|
+
if (opts.includeLogprobs && !/^gpt-4o-(mini-)?transcribe(?:-\d{4}-\d{2}-\d{2})?$/.test(modelId))
|
|
2011
|
+
unsupported(
|
|
2012
|
+
"Log probabilities require a supported gpt-4o transcription model"
|
|
2013
|
+
);
|
|
2014
|
+
const diarize = modelId === "gpt-4o-transcribe-diarize";
|
|
2015
|
+
if (opts.chunkingStrategy !== void 0 && opts.chunkingStrategy !== "auto")
|
|
2016
|
+
unsupported("Only automatic audio chunking is supported");
|
|
2017
|
+
if (diarize && options.prompt !== void 0)
|
|
2018
|
+
unsupported("Prompts are not supported by the diarization model");
|
|
2019
|
+
if (options.language !== void 0 && !/^[a-z]{2}$/.test(options.language))
|
|
2020
|
+
invalid("language must be a two-letter ISO-639-1 code");
|
|
2021
|
+
if (options.prompt !== void 0 && typeof options.prompt !== "string")
|
|
2022
|
+
invalid("Transcription prompt must be a string");
|
|
2023
|
+
const { bytes, mediaType, extension } = await audioBytes(options);
|
|
2024
|
+
options.signal?.throwIfAborted();
|
|
2025
|
+
const fileName = options.filename ?? `audio.${extension}`;
|
|
2026
|
+
if (!/^[^/\\\r\n\x00]+\.[a-z0-9]+$/i.test(fileName))
|
|
2027
|
+
invalid("Transcription filename must be a filename with an extension");
|
|
2028
|
+
const allowedExt = mediaType === "audio/mpeg" ? ["mp3", "mpeg", "mpga"] : mediaType === "audio/mp4" ? ["m4a", "mp4"] : [extension];
|
|
2029
|
+
if (!allowedExt.includes(fileName.split(".").at(-1).toLowerCase()))
|
|
2030
|
+
invalid("Transcription filename extension does not match mediaType");
|
|
2031
|
+
const form = new FormData();
|
|
2032
|
+
form.append(
|
|
2033
|
+
"file",
|
|
2034
|
+
new Blob([new Uint8Array(bytes)], { type: mediaType }),
|
|
2035
|
+
fileName
|
|
2036
|
+
);
|
|
2037
|
+
form.append("model", modelId);
|
|
2038
|
+
form.append(
|
|
2039
|
+
"response_format",
|
|
2040
|
+
diarize ? "diarized_json" : timestamps ? "verbose_json" : "json"
|
|
2041
|
+
);
|
|
2042
|
+
if (options.language) form.append("language", options.language);
|
|
2043
|
+
if (options.prompt) form.append("prompt", options.prompt);
|
|
2044
|
+
if (timestamps)
|
|
2045
|
+
for (const value2 of timestamps)
|
|
2046
|
+
form.append("timestamp_granularities[]", value2);
|
|
2047
|
+
if (opts.includeLogprobs) form.append("include[]", "logprobs");
|
|
2048
|
+
if (diarize || opts.chunkingStrategy)
|
|
2049
|
+
form.append("chunking_strategy", "auto");
|
|
2050
|
+
const response = await request(
|
|
2051
|
+
"/audio/transcriptions",
|
|
2052
|
+
form,
|
|
2053
|
+
options.signal
|
|
2054
|
+
);
|
|
2055
|
+
let value;
|
|
2056
|
+
try {
|
|
2057
|
+
value = JSON.parse(
|
|
2058
|
+
Buffer.from(await readBody(response, 5 * 1024 * 1024)).toString(
|
|
2059
|
+
"utf8"
|
|
2060
|
+
)
|
|
2061
|
+
);
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
options.signal?.throwIfAborted();
|
|
2064
|
+
if (error instanceof AudioAPIError) throw error;
|
|
2065
|
+
throw new AudioAPIError(
|
|
2066
|
+
"Transcription endpoint returned invalid JSON",
|
|
2067
|
+
"invalid_response"
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
options.signal?.throwIfAborted();
|
|
2071
|
+
if (!value || typeof value.text !== "string")
|
|
2072
|
+
throw new AudioAPIError(
|
|
2073
|
+
"Transcription response is missing text",
|
|
2074
|
+
"invalid_response"
|
|
2075
|
+
);
|
|
2076
|
+
function timed(items, word = false) {
|
|
2077
|
+
if (items === void 0) return void 0;
|
|
2078
|
+
if (!Array.isArray(items) || items.some(
|
|
2079
|
+
(x) => !x || typeof x[word ? "word" : "text"] !== "string" || !Number.isFinite(x.start) || !Number.isFinite(x.end) || x.start < 0 || x.end < x.start
|
|
2080
|
+
))
|
|
2081
|
+
throw new AudioAPIError(
|
|
2082
|
+
"Invalid transcription timestamps",
|
|
2083
|
+
"invalid_response"
|
|
2084
|
+
);
|
|
2085
|
+
return items.map(
|
|
2086
|
+
(x) => word ? { word: x.word, start: x.start, end: x.end } : {
|
|
2087
|
+
text: x.text,
|
|
2088
|
+
start: x.start,
|
|
2089
|
+
end: x.end,
|
|
2090
|
+
...typeof x.speaker === "string" ? { speaker: x.speaker } : {}
|
|
2091
|
+
}
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
return {
|
|
2095
|
+
text: value.text,
|
|
2096
|
+
language: typeof value.language === "string" ? value.language : void 0,
|
|
2097
|
+
durationSeconds: typeof value.duration === "number" ? value.duration : void 0,
|
|
2098
|
+
segments: timed(value.segments),
|
|
2099
|
+
words: timed(value.words, true),
|
|
2100
|
+
usage: value.usage,
|
|
2101
|
+
providerMetadata: {
|
|
2102
|
+
openai: { ...value.logprobs ? { logprobs: value.logprobs } : {} }
|
|
2103
|
+
},
|
|
2104
|
+
warnings: [],
|
|
2105
|
+
response: {
|
|
2106
|
+
provider: "openai",
|
|
2107
|
+
modelId,
|
|
2108
|
+
status: response.status,
|
|
2109
|
+
requestId: response.headers.get("x-request-id") ?? void 0
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
|
|
1410
2116
|
// src/providers/openai/index.ts
|
|
1411
2117
|
var OPENAI_MODELS2 = {
|
|
1412
2118
|
// GPT-4o series
|
|
@@ -1523,6 +2229,8 @@ function createOpenAI(config = {}) {
|
|
|
1523
2229
|
model: modelId,
|
|
1524
2230
|
baseUrl: config.baseUrl,
|
|
1525
2231
|
reasoningEffort: config.reasoningEffort,
|
|
2232
|
+
api: config.api,
|
|
2233
|
+
responses: config.responses,
|
|
1526
2234
|
parameterPolicy: config.parameterPolicy,
|
|
1527
2235
|
onWarning: config.onWarning
|
|
1528
2236
|
});
|
|
@@ -1552,6 +2260,6 @@ function createOpenAI(config = {}) {
|
|
|
1552
2260
|
}
|
|
1553
2261
|
var createOpenAIProvider = createOpenAI;
|
|
1554
2262
|
|
|
1555
|
-
export { createOpenAI, openai as createOpenAIModel, createOpenAIProvider, openai };
|
|
2263
|
+
export { createOpenAI, openai as createOpenAIModel, createOpenAIProvider, openai, openaiSpeech, openaiTranscription };
|
|
1556
2264
|
//# sourceMappingURL=index.mjs.map
|
|
1557
2265
|
//# sourceMappingURL=index.mjs.map
|