@yourgpt/llm-sdk 2.6.0-beta.0 → 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 +189 -0
- package/dist/adapters/index.d.mts +11 -13
- package/dist/adapters/index.d.ts +11 -13
- package/dist/adapters/index.js +610 -186
- package/dist/adapters/index.mjs +610 -186
- package/dist/audio-BBSwnjo8.d.mts +92 -0
- package/dist/audio-BBSwnjo8.d.ts +92 -0
- package/dist/{base-tNgbBaSo.d.mts → base-BICaQaRS.d.mts} +8 -3
- package/dist/{base-C58Dsr9p.d.ts → base-CpRPB8u8.d.ts} +8 -3
- package/dist/fallback/index.d.mts +4 -4
- package/dist/fallback/index.d.ts +4 -4
- package/dist/fallback/index.js +75 -39
- package/dist/fallback/index.mjs +75 -39
- package/dist/index.d.mts +18 -35
- package/dist/index.d.ts +18 -35
- package/dist/index.js +304 -88
- package/dist/index.mjs +302 -89
- 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 +559 -178
- package/dist/providers/fireworks/index.mjs +559 -178
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +559 -178
- package/dist/providers/google/index.mjs +559 -178
- 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 -23
- package/dist/providers/openai/index.d.ts +25 -23
- package/dist/providers/openai/index.js +1397 -481
- package/dist/providers/openai/index.mjs +1396 -482
- package/dist/providers/openrouter/index.d.mts +3 -3
- package/dist/providers/openrouter/index.d.ts +3 -3
- package/dist/providers/openrouter/index.js +559 -178
- package/dist/providers/openrouter/index.mjs +559 -178
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +559 -178
- package/dist/providers/togetherai/index.mjs +559 -178
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +559 -178
- package/dist/providers/xai/index.mjs +559 -178
- package/dist/{types-B6dhnguR.d.mts → types-C3RgRbEC.d.mts} +11 -4
- package/dist/{types-CCxPmkmK.d.ts → types-DCFqkJAy.d.ts} +11 -4
- package/dist/{types-BQ31QIsA.d.ts → types-DHQEMFHA.d.mts} +3 -11
- package/dist/{types-BkQCSiIt.d.mts → types-Dg1Bcg1k.d.mts} +127 -3
- package/dist/{types-BkQCSiIt.d.ts → types-Dg1Bcg1k.d.ts} +127 -3
- package/dist/{types-BSSiJW2o.d.mts → types-Dikj3En4.d.ts} +3 -11
- 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 +117 -49
- package/LICENSE +0 -21
|
@@ -1,3 +1,257 @@
|
|
|
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
|
+
|
|
104
|
+
// src/core/openai-parameters.ts
|
|
105
|
+
function capabilities(model) {
|
|
106
|
+
const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
|
|
107
|
+
const major = Number(gpt?.[1] ?? 0);
|
|
108
|
+
const minor = Number(gpt?.[2] ?? 0);
|
|
109
|
+
const chat = gpt?.[3]?.startsWith("chat") ?? false;
|
|
110
|
+
const reasoning = /^o\d+(?:-|$)/i.test(model) || major >= 5 && !chat;
|
|
111
|
+
const supportsNone = major === 5 && minor >= 1 && !chat || /^gpt-6-(?:sol|luna)(?:-\d{4}-\d{2}-\d{2})?$/i.test(model);
|
|
112
|
+
return { major, minor, reasoning, supportsNone };
|
|
113
|
+
}
|
|
114
|
+
function requiresOpenAIResponses(model, baseUrl, options = {}) {
|
|
115
|
+
if (options.parameterPolicy === "passthrough") return false;
|
|
116
|
+
const c = capabilities(model);
|
|
117
|
+
if (!c.reasoning || options.reasoningEffort === "none") return false;
|
|
118
|
+
if (!(c.major >= 6 || c.major === 5 && c.minor >= 4)) return false;
|
|
119
|
+
if (!baseUrl) return true;
|
|
120
|
+
try {
|
|
121
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function buildOpenAIParameters(model, maxTokens, temperature, options = {}, api = "chat") {
|
|
127
|
+
const c = capabilities(model ?? "");
|
|
128
|
+
const normalize = options.parameterPolicy !== "passthrough";
|
|
129
|
+
const effort = options.reasoningEffort;
|
|
130
|
+
if (normalize && c.reasoning && effort === "none" && !c.supportsNone) {
|
|
131
|
+
throw new Error(`${model} does not support reasoningEffort: none`);
|
|
132
|
+
}
|
|
133
|
+
if (normalize && c.major >= 6 && effort === "minimal") {
|
|
134
|
+
throw new Error(`${model} does not support reasoningEffort: minimal`);
|
|
135
|
+
}
|
|
136
|
+
const result = {
|
|
137
|
+
[api === "responses" ? "max_output_tokens" : normalize && c.reasoning ? "max_completion_tokens" : "max_tokens"]: maxTokens
|
|
138
|
+
};
|
|
139
|
+
if (effort !== void 0) {
|
|
140
|
+
if (api === "responses") result.reasoning = { effort };
|
|
141
|
+
else result.reasoning_effort = effort;
|
|
142
|
+
}
|
|
143
|
+
if (temperature !== void 0) {
|
|
144
|
+
if (!normalize || !c.reasoning || effort === "none" && c.supportsNone) {
|
|
145
|
+
result.temperature = temperature;
|
|
146
|
+
} else {
|
|
147
|
+
(options.onWarning ?? console.warn)(
|
|
148
|
+
`[llm-sdk] ${model}: omitted temperature because reasoning is enabled.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
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
|
+
|
|
213
|
+
// src/core/provider-error.ts
|
|
214
|
+
function providerErrorEvent(error, code) {
|
|
215
|
+
const details = error ?? {};
|
|
216
|
+
const event = {
|
|
217
|
+
type: "error",
|
|
218
|
+
message: error instanceof Error ? error.message : String(error),
|
|
219
|
+
code,
|
|
220
|
+
...typeof details.status === "number" ? { status: details.status } : {},
|
|
221
|
+
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
222
|
+
...typeof details.param === "string" ? { param: details.param } : {},
|
|
223
|
+
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
224
|
+
};
|
|
225
|
+
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
226
|
+
return event;
|
|
227
|
+
}
|
|
228
|
+
function errorFromEvent(event) {
|
|
229
|
+
if (event.cause instanceof Error) return event.cause;
|
|
230
|
+
return Object.assign(new Error(event.message), {
|
|
231
|
+
status: event.status,
|
|
232
|
+
code: event.providerCode ?? event.code,
|
|
233
|
+
param: event.param,
|
|
234
|
+
request_id: event.requestId
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function throwIfAborted(signal) {
|
|
238
|
+
if (!signal?.aborted) return;
|
|
239
|
+
const error = new Error("The operation was aborted");
|
|
240
|
+
error.name = "AbortError";
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/core/utils.ts
|
|
245
|
+
function generateId(prefix = "id") {
|
|
246
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
247
|
+
}
|
|
248
|
+
function generateMessageId() {
|
|
249
|
+
return generateId("msg");
|
|
250
|
+
}
|
|
251
|
+
function generateToolCallId() {
|
|
252
|
+
return generateId("call");
|
|
253
|
+
}
|
|
254
|
+
|
|
1
255
|
// src/adapters/base.ts
|
|
2
256
|
function stringifyForDebug(value) {
|
|
3
257
|
return JSON.stringify(
|
|
@@ -104,16 +358,6 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
104
358
|
}
|
|
105
359
|
return normalized;
|
|
106
360
|
}
|
|
107
|
-
function isOpenAIReasoningModel(modelId) {
|
|
108
|
-
if (!modelId) return false;
|
|
109
|
-
return /^(o1|o3|o4|gpt-5)/i.test(modelId);
|
|
110
|
-
}
|
|
111
|
-
function buildOpenAITokenParams(modelId, maxTokens, temperature) {
|
|
112
|
-
if (isOpenAIReasoningModel(modelId)) {
|
|
113
|
-
return { max_completion_tokens: maxTokens };
|
|
114
|
-
}
|
|
115
|
-
return { max_tokens: maxTokens, temperature };
|
|
116
|
-
}
|
|
117
361
|
function toOpenAIResponseFormat(rf) {
|
|
118
362
|
if (!rf) return void 0;
|
|
119
363
|
if (rf.type === "json_object") return { type: "json_object" };
|
|
@@ -198,7 +442,7 @@ function messageToOpenAIContent(message) {
|
|
|
198
442
|
}
|
|
199
443
|
return blocks;
|
|
200
444
|
}
|
|
201
|
-
function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
445
|
+
function formatMessagesForOpenAI(messages, systemPrompt, preserveProviderMetadata = false) {
|
|
202
446
|
const formatted = [];
|
|
203
447
|
if (systemPrompt) {
|
|
204
448
|
formatted.push({ role: "system", content: systemPrompt });
|
|
@@ -216,7 +460,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
216
460
|
const assistantMsg = {
|
|
217
461
|
role: "assistant",
|
|
218
462
|
// Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
|
|
219
|
-
content: hasToolCalls ? msg.content || null : msg.content
|
|
463
|
+
content: hasToolCalls ? msg.content || null : msg.content,
|
|
464
|
+
...preserveProviderMetadata && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
|
|
220
465
|
};
|
|
221
466
|
if (hasToolCalls) {
|
|
222
467
|
assistantMsg.tool_calls = msg.tool_calls;
|
|
@@ -225,7 +470,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
225
470
|
} else if (msg.role === "tool" && msg.tool_call_id) {
|
|
226
471
|
formatted.push({
|
|
227
472
|
role: "tool",
|
|
228
|
-
content: msg.content ?? "",
|
|
473
|
+
content: preserveProviderMetadata ? msg.content ?? "" : textToolOutput(msg.modelOutput, msg.content ?? ""),
|
|
474
|
+
...preserveProviderMetadata && msg.modelOutput ? { modelOutput: msg.modelOutput } : {},
|
|
229
475
|
tool_call_id: msg.tool_call_id
|
|
230
476
|
});
|
|
231
477
|
}
|
|
@@ -233,344 +479,61 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
233
479
|
return formatted;
|
|
234
480
|
}
|
|
235
481
|
|
|
236
|
-
// src/
|
|
237
|
-
var
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
vision: true,
|
|
254
|
-
tools: true,
|
|
255
|
-
jsonMode: true,
|
|
256
|
-
maxTokens: 128e3
|
|
257
|
-
},
|
|
258
|
-
// GPT-4 Turbo
|
|
259
|
-
"gpt-4-turbo": {
|
|
260
|
-
vision: true,
|
|
261
|
-
tools: true,
|
|
262
|
-
jsonMode: true,
|
|
263
|
-
maxTokens: 128e3
|
|
264
|
-
},
|
|
265
|
-
"gpt-4-turbo-preview": {
|
|
266
|
-
vision: false,
|
|
267
|
-
tools: true,
|
|
268
|
-
jsonMode: true,
|
|
269
|
-
maxTokens: 128e3
|
|
270
|
-
},
|
|
271
|
-
// GPT-4
|
|
272
|
-
"gpt-4": { vision: false, tools: true, jsonMode: false, maxTokens: 8192 },
|
|
273
|
-
"gpt-4-32k": {
|
|
274
|
-
vision: false,
|
|
275
|
-
tools: true,
|
|
276
|
-
jsonMode: false,
|
|
277
|
-
maxTokens: 32768
|
|
278
|
-
},
|
|
279
|
-
// GPT-3.5
|
|
280
|
-
"gpt-3.5-turbo": {
|
|
281
|
-
vision: false,
|
|
282
|
-
tools: true,
|
|
283
|
-
jsonMode: true,
|
|
284
|
-
maxTokens: 16385
|
|
285
|
-
},
|
|
286
|
-
// O1 series (reasoning)
|
|
287
|
-
o1: { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
288
|
-
"o1-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
289
|
-
"o1-preview": {
|
|
290
|
-
vision: true,
|
|
291
|
-
tools: false,
|
|
292
|
-
jsonMode: false,
|
|
293
|
-
maxTokens: 128e3
|
|
294
|
-
},
|
|
295
|
-
// O3 series
|
|
296
|
-
"o3-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 }
|
|
297
|
-
};
|
|
298
|
-
function openai(modelId, options = {}) {
|
|
299
|
-
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
300
|
-
const baseURL = options.baseURL ?? "https://api.openai.com/v1";
|
|
301
|
-
let client = null;
|
|
302
|
-
async function getClient() {
|
|
303
|
-
if (!client) {
|
|
482
|
+
// src/adapters/openai.ts
|
|
483
|
+
var OpenAIAdapter = class _OpenAIAdapter {
|
|
484
|
+
constructor(config) {
|
|
485
|
+
this.config = config;
|
|
486
|
+
this.model = config.model || "gpt-4o";
|
|
487
|
+
this.provider = _OpenAIAdapter.resolveProviderName(config.baseUrl);
|
|
488
|
+
}
|
|
489
|
+
static resolveProviderName(baseUrl) {
|
|
490
|
+
if (!baseUrl) return "openai";
|
|
491
|
+
if (baseUrl.includes("generativelanguage.googleapis.com")) return "google";
|
|
492
|
+
if (baseUrl.includes("x.ai")) return "xai";
|
|
493
|
+
if (baseUrl.includes("azure")) return "azure";
|
|
494
|
+
if (baseUrl.includes("fireworks.ai")) return "fireworks";
|
|
495
|
+
return "openai";
|
|
496
|
+
}
|
|
497
|
+
async getClient() {
|
|
498
|
+
if (!this.client) {
|
|
304
499
|
const { default: OpenAI } = await import('openai');
|
|
305
|
-
client = new OpenAI({
|
|
306
|
-
apiKey,
|
|
307
|
-
baseURL,
|
|
308
|
-
organization:
|
|
309
|
-
defaultHeaders:
|
|
500
|
+
this.client = new OpenAI({
|
|
501
|
+
apiKey: this.config.apiKey,
|
|
502
|
+
baseURL: this.config.baseUrl,
|
|
503
|
+
organization: this.config.organization,
|
|
504
|
+
defaultHeaders: this.config.headers
|
|
310
505
|
});
|
|
311
506
|
}
|
|
312
|
-
return client;
|
|
507
|
+
return this.client;
|
|
313
508
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
supportsVision: modelConfig.vision,
|
|
320
|
-
supportsTools: modelConfig.tools,
|
|
321
|
-
supportsStreaming: true,
|
|
322
|
-
supportsJsonMode: modelConfig.jsonMode,
|
|
323
|
-
supportsThinking: false,
|
|
324
|
-
supportsPDF: false,
|
|
325
|
-
maxTokens: modelConfig.maxTokens,
|
|
326
|
-
supportedImageTypes: modelConfig.vision ? ["image/png", "image/jpeg", "image/gif", "image/webp"] : []
|
|
327
|
-
},
|
|
328
|
-
async doGenerate(params) {
|
|
329
|
-
const client2 = await getClient();
|
|
330
|
-
const messages = formatMessagesForOpenAI2(params.messages);
|
|
331
|
-
const response = await client2.chat.completions.create({
|
|
332
|
-
model: modelId,
|
|
333
|
-
messages,
|
|
334
|
-
tools: params.tools,
|
|
335
|
-
...buildOpenAITokenParams(
|
|
336
|
-
modelId,
|
|
337
|
-
params.maxTokens,
|
|
338
|
-
params.temperature
|
|
339
|
-
),
|
|
340
|
-
response_format: toOpenAIResponseFormat(params.responseFormat)
|
|
341
|
-
});
|
|
342
|
-
const choice = response.choices[0];
|
|
343
|
-
const message = choice.message;
|
|
344
|
-
const toolCalls = (message.tool_calls ?? []).map(
|
|
345
|
-
(tc) => ({
|
|
346
|
-
id: tc.id,
|
|
347
|
-
name: tc.function.name,
|
|
348
|
-
args: JSON.parse(tc.function.arguments || "{}")
|
|
349
|
-
})
|
|
350
|
-
);
|
|
351
|
-
return {
|
|
352
|
-
text: message.content ?? "",
|
|
353
|
-
toolCalls,
|
|
354
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
355
|
-
usage: {
|
|
356
|
-
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
357
|
-
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
358
|
-
totalTokens: response.usage?.total_tokens ?? 0
|
|
359
|
-
},
|
|
360
|
-
rawResponse: response
|
|
361
|
-
};
|
|
362
|
-
},
|
|
363
|
-
async *doStream(params) {
|
|
364
|
-
const client2 = await getClient();
|
|
365
|
-
const messages = formatMessagesForOpenAI2(params.messages);
|
|
366
|
-
const stream = await client2.chat.completions.create({
|
|
367
|
-
model: modelId,
|
|
368
|
-
messages,
|
|
369
|
-
tools: params.tools,
|
|
370
|
-
...buildOpenAITokenParams(
|
|
371
|
-
modelId,
|
|
372
|
-
params.maxTokens,
|
|
373
|
-
params.temperature
|
|
374
|
-
),
|
|
375
|
-
response_format: toOpenAIResponseFormat(params.responseFormat),
|
|
376
|
-
stream: true
|
|
377
|
-
});
|
|
378
|
-
let currentToolCall = null;
|
|
379
|
-
let totalPromptTokens = 0;
|
|
380
|
-
let totalCompletionTokens = 0;
|
|
381
|
-
for await (const chunk of stream) {
|
|
382
|
-
if (params.signal?.aborted) {
|
|
383
|
-
yield { type: "error", error: new Error("Aborted") };
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
const choice = chunk.choices[0];
|
|
387
|
-
const delta = choice?.delta;
|
|
388
|
-
if (delta?.content) {
|
|
389
|
-
yield { type: "text-delta", text: delta.content };
|
|
390
|
-
}
|
|
391
|
-
if (delta?.tool_calls) {
|
|
392
|
-
for (const tc of delta.tool_calls) {
|
|
393
|
-
if (tc.id) {
|
|
394
|
-
if (currentToolCall) {
|
|
395
|
-
yield {
|
|
396
|
-
type: "tool-call",
|
|
397
|
-
toolCall: {
|
|
398
|
-
id: currentToolCall.id,
|
|
399
|
-
name: currentToolCall.name,
|
|
400
|
-
args: JSON.parse(currentToolCall.arguments || "{}")
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
currentToolCall = {
|
|
405
|
-
id: tc.id,
|
|
406
|
-
name: tc.function?.name ?? "",
|
|
407
|
-
arguments: tc.function?.arguments ?? ""
|
|
408
|
-
};
|
|
409
|
-
} else if (currentToolCall && tc.function?.arguments) {
|
|
410
|
-
currentToolCall.arguments += tc.function.arguments;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
if (choice?.finish_reason) {
|
|
415
|
-
if (currentToolCall) {
|
|
416
|
-
yield {
|
|
417
|
-
type: "tool-call",
|
|
418
|
-
toolCall: {
|
|
419
|
-
id: currentToolCall.id,
|
|
420
|
-
name: currentToolCall.name,
|
|
421
|
-
args: JSON.parse(currentToolCall.arguments || "{}")
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
currentToolCall = null;
|
|
425
|
-
}
|
|
426
|
-
if (chunk.usage) {
|
|
427
|
-
totalPromptTokens = chunk.usage.prompt_tokens;
|
|
428
|
-
totalCompletionTokens = chunk.usage.completion_tokens;
|
|
429
|
-
}
|
|
430
|
-
yield {
|
|
431
|
-
type: "finish",
|
|
432
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
433
|
-
usage: {
|
|
434
|
-
promptTokens: totalPromptTokens,
|
|
435
|
-
completionTokens: totalCompletionTokens,
|
|
436
|
-
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
function mapFinishReason(reason) {
|
|
445
|
-
switch (reason) {
|
|
446
|
-
case "stop":
|
|
447
|
-
return "stop";
|
|
448
|
-
case "length":
|
|
449
|
-
return "length";
|
|
450
|
-
case "tool_calls":
|
|
451
|
-
case "function_call":
|
|
452
|
-
return "tool-calls";
|
|
453
|
-
case "content_filter":
|
|
454
|
-
return "content-filter";
|
|
455
|
-
default:
|
|
456
|
-
return "unknown";
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
function formatMessagesForOpenAI2(messages) {
|
|
460
|
-
return messages.map((msg) => {
|
|
461
|
-
switch (msg.role) {
|
|
462
|
-
case "system":
|
|
463
|
-
return { role: "system", content: msg.content };
|
|
464
|
-
case "user":
|
|
465
|
-
if (typeof msg.content === "string") {
|
|
466
|
-
return { role: "user", content: msg.content };
|
|
467
|
-
}
|
|
468
|
-
return {
|
|
469
|
-
role: "user",
|
|
470
|
-
content: msg.content.map((part) => {
|
|
471
|
-
if (part.type === "text") {
|
|
472
|
-
return { type: "text", text: part.text };
|
|
473
|
-
}
|
|
474
|
-
if (part.type === "image") {
|
|
475
|
-
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
476
|
-
const url = imageData.startsWith("data:") ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
477
|
-
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
478
|
-
}
|
|
479
|
-
return { type: "text", text: "" };
|
|
480
|
-
})
|
|
481
|
-
};
|
|
482
|
-
case "assistant":
|
|
483
|
-
const assistantMsg = {
|
|
484
|
-
role: "assistant",
|
|
485
|
-
content: msg.content
|
|
486
|
-
};
|
|
487
|
-
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
488
|
-
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
489
|
-
id: tc.id,
|
|
490
|
-
type: "function",
|
|
491
|
-
function: {
|
|
492
|
-
name: tc.name,
|
|
493
|
-
arguments: JSON.stringify(tc.args)
|
|
494
|
-
}
|
|
495
|
-
}));
|
|
496
|
-
}
|
|
497
|
-
return assistantMsg;
|
|
498
|
-
case "tool":
|
|
499
|
-
return {
|
|
500
|
-
role: "tool",
|
|
501
|
-
tool_call_id: msg.toolCallId,
|
|
502
|
-
content: msg.content
|
|
503
|
-
};
|
|
504
|
-
default:
|
|
505
|
-
return msg;
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// src/core/utils.ts
|
|
511
|
-
function generateId(prefix = "id") {
|
|
512
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
513
|
-
}
|
|
514
|
-
function generateMessageId() {
|
|
515
|
-
return generateId("msg");
|
|
516
|
-
}
|
|
517
|
-
function generateToolCallId() {
|
|
518
|
-
return generateId("call");
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// src/adapters/openai.ts
|
|
522
|
-
var OpenAIAdapter = class _OpenAIAdapter {
|
|
523
|
-
constructor(config) {
|
|
524
|
-
this.config = config;
|
|
525
|
-
this.model = config.model || "gpt-4o";
|
|
526
|
-
this.provider = _OpenAIAdapter.resolveProviderName(config.baseUrl);
|
|
527
|
-
}
|
|
528
|
-
static resolveProviderName(baseUrl) {
|
|
529
|
-
if (!baseUrl) return "openai";
|
|
530
|
-
if (baseUrl.includes("generativelanguage.googleapis.com")) return "google";
|
|
531
|
-
if (baseUrl.includes("x.ai")) return "xai";
|
|
532
|
-
if (baseUrl.includes("azure")) return "azure";
|
|
533
|
-
if (baseUrl.includes("fireworks.ai")) return "fireworks";
|
|
534
|
-
return "openai";
|
|
509
|
+
parameterOptions(request) {
|
|
510
|
+
return {
|
|
511
|
+
...this.config,
|
|
512
|
+
reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
|
|
513
|
+
};
|
|
535
514
|
}
|
|
536
|
-
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
return this.client;
|
|
515
|
+
toolDefinitions(request) {
|
|
516
|
+
if (request.toolDefinitions?.length) return request.toolDefinitions;
|
|
517
|
+
return formatTools(request.actions ?? []).map((tool) => ({
|
|
518
|
+
name: tool.function.name,
|
|
519
|
+
description: tool.function.description ?? "",
|
|
520
|
+
location: "server",
|
|
521
|
+
inputSchema: tool.function.parameters
|
|
522
|
+
}));
|
|
545
523
|
}
|
|
546
524
|
shouldUseResponsesApi(request) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
request.
|
|
525
|
+
return useOpenAIResponses(
|
|
526
|
+
request.config?.model || this.model,
|
|
527
|
+
this.config.baseUrl,
|
|
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
|
|
554
535
|
);
|
|
555
536
|
}
|
|
556
|
-
/**
|
|
557
|
-
* Whether this model rejects function tools on /v1/chat/completions.
|
|
558
|
-
*
|
|
559
|
-
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
560
|
-
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
561
|
-
* restriction and may not implement /v1/responses at all.
|
|
562
|
-
*/
|
|
563
|
-
requiresResponsesApiForTools(modelId) {
|
|
564
|
-
if (!modelId) return false;
|
|
565
|
-
if (!/^gpt-5\.(?:[4-9]|\d{2,})/i.test(modelId)) return false;
|
|
566
|
-
const baseUrl = this.config.baseUrl;
|
|
567
|
-
if (!baseUrl) return true;
|
|
568
|
-
try {
|
|
569
|
-
return new URL(baseUrl).hostname === "api.openai.com";
|
|
570
|
-
} catch {
|
|
571
|
-
return false;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
537
|
/**
|
|
575
538
|
* Convert one message's content to Responses content parts.
|
|
576
539
|
*
|
|
@@ -601,11 +564,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
601
564
|
});
|
|
602
565
|
}
|
|
603
566
|
buildResponsesInput(request) {
|
|
604
|
-
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? 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);
|
|
605
568
|
const input = [];
|
|
606
569
|
for (const message of sourceMessages) {
|
|
607
570
|
if (message.role === "system") {
|
|
608
|
-
if (
|
|
571
|
+
if (message.content && message.content !== request.systemPrompt) {
|
|
609
572
|
input.push({
|
|
610
573
|
type: "message",
|
|
611
574
|
role: "developer",
|
|
@@ -615,6 +578,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
615
578
|
continue;
|
|
616
579
|
}
|
|
617
580
|
if (message.role === "assistant") {
|
|
581
|
+
const replay = getResponseReplay(message.providerMetadata);
|
|
582
|
+
if (replay) {
|
|
583
|
+
input.push(...replay);
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
618
586
|
const content = this.toResponsesContent(message.content, "assistant");
|
|
619
587
|
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
620
588
|
input.push({
|
|
@@ -638,7 +606,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
638
606
|
input.push({
|
|
639
607
|
type: "function_call_output",
|
|
640
608
|
call_id: message.tool_call_id,
|
|
641
|
-
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)
|
|
642
610
|
});
|
|
643
611
|
continue;
|
|
644
612
|
}
|
|
@@ -651,6 +619,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
651
619
|
}
|
|
652
620
|
return input;
|
|
653
621
|
}
|
|
622
|
+
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
623
|
+
formatRawMessages(messages, preserveProviderMetadata = false) {
|
|
624
|
+
return messages.map((message) => {
|
|
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;
|
|
629
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
|
|
630
|
+
content = null;
|
|
631
|
+
}
|
|
632
|
+
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
633
|
+
const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
|
|
634
|
+
(attachment) => attachmentToOpenAIImage(attachment)
|
|
635
|
+
).filter((image) => image !== null);
|
|
636
|
+
if (images.length > 0) {
|
|
637
|
+
const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
|
|
638
|
+
content = [...parts, ...images];
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
...rest,
|
|
643
|
+
content,
|
|
644
|
+
...preserveProviderMetadata && providerMetadata ? { providerMetadata } : {}
|
|
645
|
+
};
|
|
646
|
+
});
|
|
647
|
+
}
|
|
654
648
|
/**
|
|
655
649
|
* Build the web_search tool for the Responses API, or undefined when web
|
|
656
650
|
* search is off.
|
|
@@ -729,20 +723,25 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
729
723
|
).map((part) => part.text) : []
|
|
730
724
|
).join("") : "";
|
|
731
725
|
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
name
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
+
}) : [];
|
|
743
741
|
return {
|
|
744
742
|
content,
|
|
745
743
|
toolCalls,
|
|
744
|
+
providerMetadata: responseMetadata(response),
|
|
746
745
|
usage: response?.usage ? {
|
|
747
746
|
promptTokens: response.usage.input_tokens ?? 0,
|
|
748
747
|
completionTokens: response.usage.output_tokens ?? 0,
|
|
@@ -753,6 +752,17 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
753
752
|
}
|
|
754
753
|
buildResponsesPayload(request, stream) {
|
|
755
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
|
+
);
|
|
756
766
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
757
767
|
request.config?.responseFormat
|
|
758
768
|
);
|
|
@@ -762,18 +772,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
762
772
|
instructions: request.systemPrompt,
|
|
763
773
|
input: this.buildResponsesInput(request),
|
|
764
774
|
tools: this.buildResponsesTools(
|
|
765
|
-
|
|
775
|
+
this.toolDefinitions(request),
|
|
766
776
|
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
767
777
|
this.buildResponsesWebSearchTool(request)
|
|
768
778
|
),
|
|
769
|
-
tool_choice: openaiToolOptions?.toolChoice
|
|
779
|
+
tool_choice: openaiToolOptions?.toolChoice,
|
|
770
780
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
+
} : {},
|
|
777
801
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
778
802
|
stream
|
|
779
803
|
};
|
|
@@ -781,10 +805,26 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
781
805
|
}
|
|
782
806
|
async completeWithResponses(request) {
|
|
783
807
|
const client = await this.getClient();
|
|
808
|
+
throwIfAborted(request.signal);
|
|
784
809
|
const payload = this.buildResponsesPayload(request, false);
|
|
785
810
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
786
|
-
|
|
811
|
+
if (!client.responses?.create)
|
|
812
|
+
throw new Error(
|
|
813
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
814
|
+
);
|
|
815
|
+
const response = await client.responses.create(payload, {
|
|
816
|
+
signal: request.signal
|
|
817
|
+
});
|
|
818
|
+
throwIfAborted(request.signal);
|
|
787
819
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
820
|
+
if (response.status && response.status !== "completed") {
|
|
821
|
+
throw Object.assign(
|
|
822
|
+
new Error(
|
|
823
|
+
response.error?.message ?? `Responses request ${response.status}`
|
|
824
|
+
),
|
|
825
|
+
response.error
|
|
826
|
+
);
|
|
827
|
+
}
|
|
788
828
|
return this.parseResponsesResult(response);
|
|
789
829
|
}
|
|
790
830
|
/**
|
|
@@ -802,13 +842,30 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
802
842
|
*/
|
|
803
843
|
async *streamWithResponses(request) {
|
|
804
844
|
const client = await this.getClient();
|
|
845
|
+
throwIfAborted(request.signal);
|
|
805
846
|
const payload = this.buildResponsesPayload(request, true);
|
|
806
847
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
807
|
-
|
|
848
|
+
if (!client.responses?.create)
|
|
849
|
+
throw new Error(
|
|
850
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
851
|
+
);
|
|
852
|
+
const stream = await client.responses.create(payload, {
|
|
853
|
+
signal: request.signal
|
|
854
|
+
});
|
|
808
855
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
809
856
|
let usage;
|
|
810
857
|
const citations = [];
|
|
858
|
+
let completed = false;
|
|
859
|
+
let terminalResponse;
|
|
860
|
+
let responseIdentity = {};
|
|
861
|
+
const outputItems = /* @__PURE__ */ new Map();
|
|
862
|
+
let streamedText = "";
|
|
811
863
|
function* flushToolCalls() {
|
|
864
|
+
for (const call of toolCalls.values()) {
|
|
865
|
+
if (!call.name)
|
|
866
|
+
throw new Error("Responses tool call is missing its name");
|
|
867
|
+
JSON.parse(call.arguments || "{}");
|
|
868
|
+
}
|
|
812
869
|
for (const call of toolCalls.values()) {
|
|
813
870
|
yield { type: "action:start", id: call.id, name: call.name };
|
|
814
871
|
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
@@ -817,19 +874,38 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
817
874
|
toolCalls.clear();
|
|
818
875
|
}
|
|
819
876
|
for await (const event of stream) {
|
|
820
|
-
|
|
877
|
+
throwIfAborted(request.signal);
|
|
821
878
|
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
879
|
+
if (completed)
|
|
880
|
+
throw new Error("Responses stream emitted an event after completion");
|
|
822
881
|
const index = event?.output_index ?? 0;
|
|
823
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;
|
|
824
899
|
case "response.output_text.delta":
|
|
825
900
|
if (event.delta) {
|
|
901
|
+
streamedText += event.delta;
|
|
826
902
|
yield { type: "message:delta", content: event.delta };
|
|
827
903
|
}
|
|
828
904
|
break;
|
|
829
905
|
case "response.output_item.added":
|
|
830
906
|
if (event.item?.type === "function_call") {
|
|
831
907
|
toolCalls.set(index, {
|
|
832
|
-
id: event.item.call_id ??
|
|
908
|
+
id: event.item.call_id ?? "",
|
|
833
909
|
name: event.item.name ?? "",
|
|
834
910
|
arguments: event.item.arguments ?? ""
|
|
835
911
|
});
|
|
@@ -847,8 +923,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
847
923
|
}
|
|
848
924
|
break;
|
|
849
925
|
}
|
|
850
|
-
case "response.completed":
|
|
851
|
-
|
|
926
|
+
case "response.completed": {
|
|
927
|
+
if (event.response?.status && event.response.status !== "completed")
|
|
928
|
+
throw new Error(`Responses stream ${event.response.status}`);
|
|
929
|
+
completed = true;
|
|
930
|
+
terminalResponse = { ...responseIdentity, ...event.response };
|
|
852
931
|
const found = this.collectResponsesCitations(
|
|
853
932
|
event.response?.output,
|
|
854
933
|
citations.length
|
|
@@ -864,21 +943,88 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
864
943
|
}
|
|
865
944
|
break;
|
|
866
945
|
}
|
|
867
|
-
case "
|
|
868
|
-
case "response.failed":
|
|
946
|
+
case "response.incomplete":
|
|
869
947
|
throw new Error(
|
|
870
|
-
|
|
948
|
+
`Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
|
|
949
|
+
);
|
|
950
|
+
case "error":
|
|
951
|
+
case "response.failed": {
|
|
952
|
+
const details = event.response?.error ?? event;
|
|
953
|
+
throw Object.assign(
|
|
954
|
+
new Error(details.message ?? "Responses stream failed"),
|
|
955
|
+
{
|
|
956
|
+
code: details.code,
|
|
957
|
+
param: details.param,
|
|
958
|
+
status: details.status
|
|
959
|
+
}
|
|
871
960
|
);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
throwIfAborted(request.signal);
|
|
965
|
+
if (!completed)
|
|
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
|
+
});
|
|
872
977
|
}
|
|
873
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
|
+
}
|
|
874
1012
|
yield* flushToolCalls();
|
|
875
1013
|
if (citations.length > 0) {
|
|
876
1014
|
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
877
1015
|
}
|
|
878
|
-
yield {
|
|
879
|
-
|
|
1016
|
+
yield {
|
|
1017
|
+
type: "message:end",
|
|
1018
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1019
|
+
};
|
|
1020
|
+
yield {
|
|
1021
|
+
type: "done",
|
|
1022
|
+
usage,
|
|
1023
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1024
|
+
};
|
|
880
1025
|
}
|
|
881
1026
|
async *stream(request) {
|
|
1027
|
+
throwIfAborted(request.signal);
|
|
882
1028
|
if (this.shouldUseResponsesApi(request)) {
|
|
883
1029
|
const messageId2 = generateMessageId();
|
|
884
1030
|
yield { type: "message:start", id: messageId2 };
|
|
@@ -886,47 +1032,14 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
886
1032
|
yield* this.streamWithResponses(request);
|
|
887
1033
|
return;
|
|
888
1034
|
} catch (error) {
|
|
889
|
-
yield
|
|
890
|
-
type: "error",
|
|
891
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
892
|
-
code: "OPENAI_RESPONSES_ERROR"
|
|
893
|
-
};
|
|
1035
|
+
yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
|
|
894
1036
|
return;
|
|
895
1037
|
}
|
|
896
1038
|
}
|
|
897
1039
|
const client = await this.getClient();
|
|
898
1040
|
let messages;
|
|
899
1041
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
900
|
-
const processedMessages = request.rawMessages
|
|
901
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
902
|
-
return { ...msg, content: null };
|
|
903
|
-
}
|
|
904
|
-
const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
|
|
905
|
-
if (hasAttachments) {
|
|
906
|
-
const content = [];
|
|
907
|
-
if (msg.content) {
|
|
908
|
-
content.push({ type: "text", text: msg.content });
|
|
909
|
-
}
|
|
910
|
-
for (const attachment of msg.attachments) {
|
|
911
|
-
if (attachment.type === "image") {
|
|
912
|
-
let imageUrl;
|
|
913
|
-
if (attachment.url) {
|
|
914
|
-
imageUrl = attachment.url;
|
|
915
|
-
} else if (attachment.data) {
|
|
916
|
-
imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
|
|
917
|
-
} else {
|
|
918
|
-
continue;
|
|
919
|
-
}
|
|
920
|
-
content.push({
|
|
921
|
-
type: "image_url",
|
|
922
|
-
image_url: { url: imageUrl, detail: "auto" }
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
return { ...msg, content, attachments: void 0 };
|
|
927
|
-
}
|
|
928
|
-
return msg;
|
|
929
|
-
});
|
|
1042
|
+
const processedMessages = this.formatRawMessages(request.rawMessages);
|
|
930
1043
|
if (request.systemPrompt) {
|
|
931
1044
|
const hasSystem = processedMessages.some((m) => m.role === "system");
|
|
932
1045
|
if (!hasSystem) {
|
|
@@ -946,7 +1059,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
946
1059
|
request.systemPrompt
|
|
947
1060
|
);
|
|
948
1061
|
}
|
|
949
|
-
const tools = request.
|
|
1062
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1063
|
+
type: "function",
|
|
1064
|
+
function: {
|
|
1065
|
+
name: tool.name,
|
|
1066
|
+
description: tool.description,
|
|
1067
|
+
parameters: normalizeObjectJsonSchema(
|
|
1068
|
+
tool.inputSchema
|
|
1069
|
+
)
|
|
1070
|
+
}
|
|
1071
|
+
})) : formatTools(request.actions ?? []);
|
|
950
1072
|
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
951
1073
|
if (webSearchConfig) {
|
|
952
1074
|
const webSearchTool = {
|
|
@@ -975,26 +1097,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
975
1097
|
tools: tools.length > 0 ? tools : void 0,
|
|
976
1098
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
977
1099
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
978
|
-
...
|
|
1100
|
+
...buildOpenAIParameters(
|
|
979
1101
|
modelIdForPayload,
|
|
980
1102
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
981
|
-
request.config?.temperature ?? this.config.temperature
|
|
1103
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1104
|
+
this.parameterOptions(request)
|
|
982
1105
|
),
|
|
983
1106
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
984
1107
|
stream: true,
|
|
985
1108
|
stream_options: { include_usage: true }
|
|
986
1109
|
};
|
|
987
1110
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
988
|
-
const stream = await client.chat.completions.create(payload
|
|
989
|
-
|
|
1111
|
+
const stream = await client.chat.completions.create(payload, {
|
|
1112
|
+
signal: request.signal
|
|
1113
|
+
});
|
|
1114
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1115
|
+
let finishReason;
|
|
990
1116
|
const collectedCitations = [];
|
|
991
1117
|
let citationIndex = 0;
|
|
992
1118
|
let usage;
|
|
993
1119
|
for await (const chunk of stream) {
|
|
994
1120
|
logProviderPayload("openai", "stream chunk", chunk, request.debug);
|
|
995
|
-
|
|
996
|
-
break;
|
|
997
|
-
}
|
|
1121
|
+
throwIfAborted(request.signal);
|
|
998
1122
|
const delta = chunk.choices[0]?.delta;
|
|
999
1123
|
const choice = chunk.choices[0];
|
|
1000
1124
|
if (delta?.content) {
|
|
@@ -1017,38 +1141,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1017
1141
|
}
|
|
1018
1142
|
}
|
|
1019
1143
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
const tcExtraContent = toolCall.extra_content;
|
|
1036
|
-
currentToolCall = {
|
|
1037
|
-
id: toolCall.id,
|
|
1038
|
-
name: toolCall.function?.name || "",
|
|
1039
|
-
arguments: toolCall.function?.arguments || "",
|
|
1040
|
-
...tcExtraContent ? { extra_content: tcExtraContent } : {}
|
|
1041
|
-
};
|
|
1042
|
-
yield {
|
|
1043
|
-
type: "action:start",
|
|
1044
|
-
id: currentToolCall.id,
|
|
1045
|
-
name: currentToolCall.name,
|
|
1046
|
-
...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
|
|
1047
|
-
};
|
|
1048
|
-
} else if (currentToolCall && toolCall.function?.arguments) {
|
|
1049
|
-
currentToolCall.arguments += toolCall.function.arguments;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1144
|
+
for (const part of delta?.tool_calls ?? []) {
|
|
1145
|
+
const index = part.index ?? 0;
|
|
1146
|
+
const call = toolCalls.get(index) ?? {
|
|
1147
|
+
id: "",
|
|
1148
|
+
name: "",
|
|
1149
|
+
arguments: ""
|
|
1150
|
+
};
|
|
1151
|
+
if (part.id) call.id = part.id;
|
|
1152
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1153
|
+
if (part.function?.arguments)
|
|
1154
|
+
call.arguments += part.function.arguments;
|
|
1155
|
+
if (part.extra_content) call.extra_content = part.extra_content;
|
|
1156
|
+
toolCalls.set(index, call);
|
|
1052
1157
|
}
|
|
1053
1158
|
if (chunk.usage) {
|
|
1054
1159
|
usage = {
|
|
@@ -1057,21 +1162,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1057
1162
|
total_tokens: chunk.usage.total_tokens
|
|
1058
1163
|
};
|
|
1059
1164
|
}
|
|
1060
|
-
if (choice?.finish_reason)
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1165
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1166
|
+
}
|
|
1167
|
+
throwIfAborted(request.signal);
|
|
1168
|
+
if (!finishReason)
|
|
1169
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1170
|
+
if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
|
|
1171
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1172
|
+
}
|
|
1173
|
+
for (const call of toolCalls.values()) {
|
|
1174
|
+
if (!call.id || !call.name)
|
|
1175
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1176
|
+
JSON.parse(call.arguments || "{}");
|
|
1177
|
+
}
|
|
1178
|
+
for (const call of toolCalls.values()) {
|
|
1179
|
+
yield {
|
|
1180
|
+
type: "action:start",
|
|
1181
|
+
id: call.id,
|
|
1182
|
+
name: call.name,
|
|
1183
|
+
...call.extra_content ? { extra_content: call.extra_content } : {}
|
|
1184
|
+
};
|
|
1185
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
1186
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
1075
1187
|
}
|
|
1076
1188
|
if (collectedCitations.length > 0) {
|
|
1077
1189
|
const uniqueCitations = deduplicateCitations(collectedCitations);
|
|
@@ -1080,26 +1192,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1080
1192
|
yield { type: "message:end" };
|
|
1081
1193
|
yield { type: "done", usage };
|
|
1082
1194
|
} catch (error) {
|
|
1083
|
-
yield {
|
|
1084
|
-
type: "error",
|
|
1085
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
1086
|
-
code: `${this.provider.toUpperCase()}_ERROR`
|
|
1087
|
-
};
|
|
1195
|
+
yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
|
|
1088
1196
|
}
|
|
1089
1197
|
}
|
|
1090
1198
|
async complete(request) {
|
|
1199
|
+
throwIfAborted(request.signal);
|
|
1091
1200
|
if (this.shouldUseResponsesApi(request)) {
|
|
1092
1201
|
return this.completeWithResponses(request);
|
|
1093
1202
|
}
|
|
1094
1203
|
const client = await this.getClient();
|
|
1095
1204
|
let messages;
|
|
1096
1205
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
1097
|
-
const sanitized = request.rawMessages
|
|
1098
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
1099
|
-
return { ...msg, content: null };
|
|
1100
|
-
}
|
|
1101
|
-
return msg;
|
|
1102
|
-
});
|
|
1206
|
+
const sanitized = this.formatRawMessages(request.rawMessages);
|
|
1103
1207
|
if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
|
|
1104
1208
|
messages = [
|
|
1105
1209
|
{ role: "system", content: request.systemPrompt },
|
|
@@ -1114,7 +1218,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1114
1218
|
request.systemPrompt
|
|
1115
1219
|
);
|
|
1116
1220
|
}
|
|
1117
|
-
const tools = request.
|
|
1221
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1222
|
+
type: "function",
|
|
1223
|
+
function: {
|
|
1224
|
+
name: tool.name,
|
|
1225
|
+
description: tool.description,
|
|
1226
|
+
parameters: normalizeObjectJsonSchema(
|
|
1227
|
+
tool.inputSchema
|
|
1228
|
+
)
|
|
1229
|
+
}
|
|
1230
|
+
})) : formatTools(request.actions ?? []);
|
|
1118
1231
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
1119
1232
|
const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
|
|
1120
1233
|
type: "function",
|
|
@@ -1129,16 +1242,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1129
1242
|
tools: tools.length > 0 ? tools : void 0,
|
|
1130
1243
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
1131
1244
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
1132
|
-
...
|
|
1245
|
+
...buildOpenAIParameters(
|
|
1133
1246
|
modelIdForCompletePayload,
|
|
1134
1247
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
1135
|
-
request.config?.temperature ?? this.config.temperature
|
|
1248
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1249
|
+
this.parameterOptions(request)
|
|
1136
1250
|
),
|
|
1137
1251
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
1138
1252
|
stream: false
|
|
1139
1253
|
};
|
|
1140
1254
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
1141
|
-
const response = await client.chat.completions.create(payload
|
|
1255
|
+
const response = await client.chat.completions.create(payload, {
|
|
1256
|
+
signal: request.signal
|
|
1257
|
+
});
|
|
1142
1258
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
1143
1259
|
const choice = response.choices?.[0];
|
|
1144
1260
|
const message = choice?.message;
|
|
@@ -1188,6 +1304,411 @@ function createOpenAIAdapter(config) {
|
|
|
1188
1304
|
return new OpenAIAdapter(config);
|
|
1189
1305
|
}
|
|
1190
1306
|
|
|
1307
|
+
// src/providers/openai/provider.ts
|
|
1308
|
+
var OPENAI_MODELS = {
|
|
1309
|
+
// GPT-4o series
|
|
1310
|
+
"gpt-4o": { vision: true, tools: true, jsonMode: true, maxTokens: 128e3 },
|
|
1311
|
+
"gpt-5.2": {
|
|
1312
|
+
vision: true,
|
|
1313
|
+
tools: true,
|
|
1314
|
+
jsonMode: true,
|
|
1315
|
+
maxTokens: 128e3
|
|
1316
|
+
},
|
|
1317
|
+
"gpt-4o-2024-11-20": {
|
|
1318
|
+
vision: true,
|
|
1319
|
+
tools: true,
|
|
1320
|
+
jsonMode: true,
|
|
1321
|
+
maxTokens: 128e3
|
|
1322
|
+
},
|
|
1323
|
+
"gpt-4o-2024-08-06": {
|
|
1324
|
+
vision: true,
|
|
1325
|
+
tools: true,
|
|
1326
|
+
jsonMode: true,
|
|
1327
|
+
maxTokens: 128e3
|
|
1328
|
+
},
|
|
1329
|
+
// GPT-4 Turbo
|
|
1330
|
+
"gpt-4-turbo": {
|
|
1331
|
+
vision: true,
|
|
1332
|
+
tools: true,
|
|
1333
|
+
jsonMode: true,
|
|
1334
|
+
maxTokens: 128e3
|
|
1335
|
+
},
|
|
1336
|
+
"gpt-4-turbo-preview": {
|
|
1337
|
+
vision: false,
|
|
1338
|
+
tools: true,
|
|
1339
|
+
jsonMode: true,
|
|
1340
|
+
maxTokens: 128e3
|
|
1341
|
+
},
|
|
1342
|
+
// GPT-4
|
|
1343
|
+
"gpt-4": { vision: false, tools: true, jsonMode: false, maxTokens: 8192 },
|
|
1344
|
+
"gpt-4-32k": {
|
|
1345
|
+
vision: false,
|
|
1346
|
+
tools: true,
|
|
1347
|
+
jsonMode: false,
|
|
1348
|
+
maxTokens: 32768
|
|
1349
|
+
},
|
|
1350
|
+
// GPT-3.5
|
|
1351
|
+
"gpt-3.5-turbo": {
|
|
1352
|
+
vision: false,
|
|
1353
|
+
tools: true,
|
|
1354
|
+
jsonMode: true,
|
|
1355
|
+
maxTokens: 16385
|
|
1356
|
+
},
|
|
1357
|
+
// O1 series (reasoning)
|
|
1358
|
+
o1: { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
1359
|
+
"o1-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
1360
|
+
"o1-preview": {
|
|
1361
|
+
vision: true,
|
|
1362
|
+
tools: false,
|
|
1363
|
+
jsonMode: false,
|
|
1364
|
+
maxTokens: 128e3
|
|
1365
|
+
},
|
|
1366
|
+
// O3 series
|
|
1367
|
+
"o3-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 }
|
|
1368
|
+
};
|
|
1369
|
+
function openai(modelId, options = {}) {
|
|
1370
|
+
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
1371
|
+
const baseURL = options.baseURL ?? "https://api.openai.com/v1";
|
|
1372
|
+
let client = null;
|
|
1373
|
+
async function getClient() {
|
|
1374
|
+
if (!client) {
|
|
1375
|
+
const { default: OpenAI } = await import('openai');
|
|
1376
|
+
client = new OpenAI({
|
|
1377
|
+
apiKey,
|
|
1378
|
+
baseURL,
|
|
1379
|
+
organization: options.organization,
|
|
1380
|
+
defaultHeaders: options.headers
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
return client;
|
|
1384
|
+
}
|
|
1385
|
+
function parameterOptions(params) {
|
|
1386
|
+
return {
|
|
1387
|
+
...options,
|
|
1388
|
+
reasoningEffort: params.reasoningEffort ?? options.reasoningEffort
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
function useResponses(params) {
|
|
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
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
const responsesAdapter = new OpenAIAdapter({
|
|
1404
|
+
...options,
|
|
1405
|
+
apiKey: apiKey ?? "",
|
|
1406
|
+
model: modelId,
|
|
1407
|
+
baseUrl: baseURL
|
|
1408
|
+
});
|
|
1409
|
+
function responsesRequest(params) {
|
|
1410
|
+
return {
|
|
1411
|
+
messages: [],
|
|
1412
|
+
rawMessages: formatMessagesForOpenAI2(params.messages, true),
|
|
1413
|
+
toolDefinitions: params.tools?.map((raw) => {
|
|
1414
|
+
const tool = raw;
|
|
1415
|
+
if (tool.type !== "function")
|
|
1416
|
+
throw new Error(
|
|
1417
|
+
"Only function tools are supported by this OpenAI generation path"
|
|
1418
|
+
);
|
|
1419
|
+
return {
|
|
1420
|
+
name: tool.function.name,
|
|
1421
|
+
description: tool.function.description ?? "",
|
|
1422
|
+
location: "server",
|
|
1423
|
+
inputSchema: tool.function.parameters
|
|
1424
|
+
};
|
|
1425
|
+
}),
|
|
1426
|
+
config: {
|
|
1427
|
+
openai: {
|
|
1428
|
+
...resolveOpenAITransport(options, params.openai),
|
|
1429
|
+
api: "responses"
|
|
1430
|
+
},
|
|
1431
|
+
temperature: params.temperature,
|
|
1432
|
+
maxTokens: params.maxTokens,
|
|
1433
|
+
responseFormat: params.responseFormat,
|
|
1434
|
+
reasoningEffort: parameterOptions(params).reasoningEffort
|
|
1435
|
+
},
|
|
1436
|
+
signal: params.signal
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
const modelConfig = OPENAI_MODELS[modelId] ?? OPENAI_MODELS["gpt-4o"];
|
|
1440
|
+
return {
|
|
1441
|
+
provider: "openai",
|
|
1442
|
+
modelId,
|
|
1443
|
+
capabilities: {
|
|
1444
|
+
supportsVision: modelConfig.vision,
|
|
1445
|
+
supportsTools: modelConfig.tools,
|
|
1446
|
+
supportsStreaming: true,
|
|
1447
|
+
supportsJsonMode: modelConfig.jsonMode,
|
|
1448
|
+
supportsThinking: false,
|
|
1449
|
+
supportsPDF: false,
|
|
1450
|
+
maxTokens: modelConfig.maxTokens,
|
|
1451
|
+
supportedImageTypes: modelConfig.vision ? ["image/png", "image/jpeg", "image/gif", "image/webp"] : []
|
|
1452
|
+
},
|
|
1453
|
+
async doGenerate(params) {
|
|
1454
|
+
throwIfAborted(params.signal);
|
|
1455
|
+
if (useResponses(params)) {
|
|
1456
|
+
const result = await responsesAdapter.complete(
|
|
1457
|
+
responsesRequest(params)
|
|
1458
|
+
);
|
|
1459
|
+
return {
|
|
1460
|
+
text: result.content,
|
|
1461
|
+
toolCalls: result.toolCalls,
|
|
1462
|
+
finishReason: result.toolCalls.length ? "tool-calls" : result.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
|
|
1463
|
+
usage: result.usage ?? {
|
|
1464
|
+
promptTokens: 0,
|
|
1465
|
+
completionTokens: 0,
|
|
1466
|
+
totalTokens: 0
|
|
1467
|
+
},
|
|
1468
|
+
rawResponse: result.rawResponse,
|
|
1469
|
+
providerMetadata: result.providerMetadata
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
const client2 = await getClient();
|
|
1473
|
+
const messages = formatMessagesForOpenAI2(params.messages);
|
|
1474
|
+
const response = await client2.chat.completions.create(
|
|
1475
|
+
{
|
|
1476
|
+
model: modelId,
|
|
1477
|
+
messages,
|
|
1478
|
+
tools: params.tools,
|
|
1479
|
+
...buildOpenAIParameters(
|
|
1480
|
+
modelId,
|
|
1481
|
+
params.maxTokens,
|
|
1482
|
+
params.temperature,
|
|
1483
|
+
parameterOptions(params)
|
|
1484
|
+
),
|
|
1485
|
+
response_format: toOpenAIResponseFormat(params.responseFormat)
|
|
1486
|
+
},
|
|
1487
|
+
{ signal: params.signal }
|
|
1488
|
+
);
|
|
1489
|
+
const choice = response.choices[0];
|
|
1490
|
+
const message = choice.message;
|
|
1491
|
+
const toolCalls = (message.tool_calls ?? []).map(
|
|
1492
|
+
(tc) => ({
|
|
1493
|
+
id: tc.id,
|
|
1494
|
+
name: tc.function.name,
|
|
1495
|
+
args: JSON.parse(tc.function.arguments || "{}")
|
|
1496
|
+
})
|
|
1497
|
+
);
|
|
1498
|
+
return {
|
|
1499
|
+
text: message.content ?? "",
|
|
1500
|
+
toolCalls,
|
|
1501
|
+
finishReason: mapFinishReason(choice.finish_reason),
|
|
1502
|
+
usage: {
|
|
1503
|
+
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
1504
|
+
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
1505
|
+
totalTokens: response.usage?.total_tokens ?? 0
|
|
1506
|
+
},
|
|
1507
|
+
rawResponse: response
|
|
1508
|
+
};
|
|
1509
|
+
},
|
|
1510
|
+
async *doStream(params) {
|
|
1511
|
+
throwIfAborted(params.signal);
|
|
1512
|
+
if (useResponses(params)) {
|
|
1513
|
+
const calls2 = /* @__PURE__ */ new Map();
|
|
1514
|
+
let hasTools = false;
|
|
1515
|
+
for await (const event of responsesAdapter.stream(
|
|
1516
|
+
responsesRequest(params)
|
|
1517
|
+
)) {
|
|
1518
|
+
switch (event.type) {
|
|
1519
|
+
case "message:delta":
|
|
1520
|
+
yield { type: "text-delta", text: event.content };
|
|
1521
|
+
break;
|
|
1522
|
+
case "action:start":
|
|
1523
|
+
calls2.set(event.id, { name: event.name, args: "" });
|
|
1524
|
+
break;
|
|
1525
|
+
case "action:args": {
|
|
1526
|
+
const call = calls2.get(event.id);
|
|
1527
|
+
if (call) call.args += event.args;
|
|
1528
|
+
break;
|
|
1529
|
+
}
|
|
1530
|
+
case "action:end": {
|
|
1531
|
+
const call = calls2.get(event.id);
|
|
1532
|
+
if (!call)
|
|
1533
|
+
throw new Error("Responses tool call has no matching start");
|
|
1534
|
+
hasTools = true;
|
|
1535
|
+
yield {
|
|
1536
|
+
type: "tool-call",
|
|
1537
|
+
toolCall: {
|
|
1538
|
+
id: event.id,
|
|
1539
|
+
name: call.name,
|
|
1540
|
+
args: JSON.parse(call.args || "{}")
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
calls2.delete(event.id);
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
case "error":
|
|
1547
|
+
throw errorFromEvent(event);
|
|
1548
|
+
case "done":
|
|
1549
|
+
yield {
|
|
1550
|
+
type: "finish",
|
|
1551
|
+
finishReason: hasTools ? "tool-calls" : event.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
|
|
1552
|
+
providerMetadata: event.providerMetadata,
|
|
1553
|
+
usage: {
|
|
1554
|
+
promptTokens: event.usage?.prompt_tokens ?? 0,
|
|
1555
|
+
completionTokens: event.usage?.completion_tokens ?? 0,
|
|
1556
|
+
totalTokens: event.usage?.total_tokens ?? 0
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const client2 = await getClient();
|
|
1565
|
+
const messages = formatMessagesForOpenAI2(params.messages);
|
|
1566
|
+
const stream = await client2.chat.completions.create(
|
|
1567
|
+
{
|
|
1568
|
+
model: modelId,
|
|
1569
|
+
messages,
|
|
1570
|
+
tools: params.tools,
|
|
1571
|
+
...buildOpenAIParameters(
|
|
1572
|
+
modelId,
|
|
1573
|
+
params.maxTokens,
|
|
1574
|
+
params.temperature,
|
|
1575
|
+
parameterOptions(params)
|
|
1576
|
+
),
|
|
1577
|
+
response_format: toOpenAIResponseFormat(params.responseFormat),
|
|
1578
|
+
stream: true,
|
|
1579
|
+
stream_options: { include_usage: true }
|
|
1580
|
+
},
|
|
1581
|
+
{ signal: params.signal }
|
|
1582
|
+
);
|
|
1583
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1584
|
+
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1585
|
+
let finishReason;
|
|
1586
|
+
for await (const chunk of stream) {
|
|
1587
|
+
throwIfAborted(params.signal);
|
|
1588
|
+
if (chunk.usage) {
|
|
1589
|
+
usage = {
|
|
1590
|
+
promptTokens: chunk.usage.prompt_tokens ?? 0,
|
|
1591
|
+
completionTokens: chunk.usage.completion_tokens ?? 0,
|
|
1592
|
+
totalTokens: chunk.usage.total_tokens ?? (chunk.usage.prompt_tokens ?? 0) + (chunk.usage.completion_tokens ?? 0)
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
const choice = chunk.choices?.[0];
|
|
1596
|
+
if (choice?.delta?.content)
|
|
1597
|
+
yield { type: "text-delta", text: choice.delta.content };
|
|
1598
|
+
for (const part of choice?.delta?.tool_calls ?? []) {
|
|
1599
|
+
const index = part.index ?? 0;
|
|
1600
|
+
const call = toolCalls.get(index) ?? {
|
|
1601
|
+
id: "",
|
|
1602
|
+
name: "",
|
|
1603
|
+
arguments: ""
|
|
1604
|
+
};
|
|
1605
|
+
if (part.id) call.id = part.id;
|
|
1606
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1607
|
+
if (part.function?.arguments)
|
|
1608
|
+
call.arguments += part.function.arguments;
|
|
1609
|
+
toolCalls.set(index, call);
|
|
1610
|
+
}
|
|
1611
|
+
if (choice?.finish_reason)
|
|
1612
|
+
finishReason = mapFinishReason(choice.finish_reason);
|
|
1613
|
+
}
|
|
1614
|
+
throwIfAborted(params.signal);
|
|
1615
|
+
if (!finishReason)
|
|
1616
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1617
|
+
const calls = [...toolCalls.values()].map((call) => {
|
|
1618
|
+
if (!call.id || !call.name)
|
|
1619
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1620
|
+
return {
|
|
1621
|
+
id: call.id,
|
|
1622
|
+
name: call.name,
|
|
1623
|
+
args: JSON.parse(call.arguments || "{}")
|
|
1624
|
+
};
|
|
1625
|
+
});
|
|
1626
|
+
if (calls.length && finishReason !== "tool-calls")
|
|
1627
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1628
|
+
for (const toolCall of calls) yield { type: "tool-call", toolCall };
|
|
1629
|
+
yield { type: "finish", finishReason, usage };
|
|
1630
|
+
}
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
function mapFinishReason(reason) {
|
|
1634
|
+
switch (reason) {
|
|
1635
|
+
case "stop":
|
|
1636
|
+
return "stop";
|
|
1637
|
+
case "length":
|
|
1638
|
+
return "length";
|
|
1639
|
+
case "tool_calls":
|
|
1640
|
+
case "function_call":
|
|
1641
|
+
return "tool-calls";
|
|
1642
|
+
case "content_filter":
|
|
1643
|
+
return "content-filter";
|
|
1644
|
+
default:
|
|
1645
|
+
return "unknown";
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
function formatMessagesForOpenAI2(messages, responses = false) {
|
|
1649
|
+
return messages.map((msg) => {
|
|
1650
|
+
switch (msg.role) {
|
|
1651
|
+
case "system":
|
|
1652
|
+
return { role: "system", content: msg.content };
|
|
1653
|
+
case "user":
|
|
1654
|
+
if (typeof msg.content === "string") {
|
|
1655
|
+
return { role: "user", content: msg.content };
|
|
1656
|
+
}
|
|
1657
|
+
return {
|
|
1658
|
+
role: "user",
|
|
1659
|
+
content: msg.content.map((part) => {
|
|
1660
|
+
if (part.type === "text") {
|
|
1661
|
+
return { type: "text", text: part.text };
|
|
1662
|
+
}
|
|
1663
|
+
if (part.type === "image") {
|
|
1664
|
+
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
1665
|
+
const url = /^(data:|https?:\/\/)/.test(imageData) ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
1666
|
+
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
1667
|
+
}
|
|
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");
|
|
1680
|
+
})
|
|
1681
|
+
};
|
|
1682
|
+
case "assistant":
|
|
1683
|
+
const assistantMsg = {
|
|
1684
|
+
role: "assistant",
|
|
1685
|
+
content: msg.content,
|
|
1686
|
+
...responses && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
|
|
1687
|
+
};
|
|
1688
|
+
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
1689
|
+
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
1690
|
+
id: tc.id,
|
|
1691
|
+
type: "function",
|
|
1692
|
+
function: {
|
|
1693
|
+
name: tc.name,
|
|
1694
|
+
arguments: JSON.stringify(tc.args)
|
|
1695
|
+
}
|
|
1696
|
+
}));
|
|
1697
|
+
}
|
|
1698
|
+
return assistantMsg;
|
|
1699
|
+
case "tool":
|
|
1700
|
+
return {
|
|
1701
|
+
role: "tool",
|
|
1702
|
+
tool_call_id: msg.toolCallId,
|
|
1703
|
+
content: responses ? msg.content : textToolOutput(msg.modelOutput, msg.content),
|
|
1704
|
+
...responses && msg.modelOutput ? { modelOutput: msg.modelOutput } : {}
|
|
1705
|
+
};
|
|
1706
|
+
default:
|
|
1707
|
+
return msg;
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1191
1712
|
// src/providers/types.ts
|
|
1192
1713
|
function createCallableProvider(providerFn, properties) {
|
|
1193
1714
|
Object.defineProperty(providerFn, "name", {
|
|
@@ -1204,6 +1725,394 @@ function createCallableProvider(providerFn, properties) {
|
|
|
1204
1725
|
return providerFn;
|
|
1205
1726
|
}
|
|
1206
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
|
+
|
|
1207
2116
|
// src/providers/openai/index.ts
|
|
1208
2117
|
var OPENAI_MODELS2 = {
|
|
1209
2118
|
// GPT-4o series
|
|
@@ -1318,7 +2227,12 @@ function createOpenAI(config = {}) {
|
|
|
1318
2227
|
return createOpenAIAdapter({
|
|
1319
2228
|
apiKey,
|
|
1320
2229
|
model: modelId,
|
|
1321
|
-
baseUrl: config.baseUrl
|
|
2230
|
+
baseUrl: config.baseUrl,
|
|
2231
|
+
reasoningEffort: config.reasoningEffort,
|
|
2232
|
+
api: config.api,
|
|
2233
|
+
responses: config.responses,
|
|
2234
|
+
parameterPolicy: config.parameterPolicy,
|
|
2235
|
+
onWarning: config.onWarning
|
|
1322
2236
|
});
|
|
1323
2237
|
};
|
|
1324
2238
|
const getCapabilities = (modelId) => {
|
|
@@ -1346,6 +2260,6 @@ function createOpenAI(config = {}) {
|
|
|
1346
2260
|
}
|
|
1347
2261
|
var createOpenAIProvider = createOpenAI;
|
|
1348
2262
|
|
|
1349
|
-
export { createOpenAI, openai as createOpenAIModel, createOpenAIProvider, openai };
|
|
2263
|
+
export { createOpenAI, openai as createOpenAIModel, createOpenAIProvider, openai, openaiSpeech, openaiTranscription };
|
|
1350
2264
|
//# sourceMappingURL=index.mjs.map
|
|
1351
2265
|
//# sourceMappingURL=index.mjs.map
|