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