@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,5 +1,259 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/core/tool-output.ts
|
|
4
|
+
var imageTypes = /* @__PURE__ */ new Set([
|
|
5
|
+
"image/png",
|
|
6
|
+
"image/jpeg",
|
|
7
|
+
"image/webp",
|
|
8
|
+
"image/gif"
|
|
9
|
+
]);
|
|
10
|
+
function normalizeToolOutput(value) {
|
|
11
|
+
const output = value;
|
|
12
|
+
if (output?.type !== "content" || !Array.isArray(output.content) || !output.content.length) {
|
|
13
|
+
throw new Error("Tool model output must contain a nonempty content array");
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
type: "content",
|
|
17
|
+
content: output.content.map((part) => {
|
|
18
|
+
if (part?.type === "text" && typeof part.text === "string")
|
|
19
|
+
return { type: "text", text: part.text };
|
|
20
|
+
if (part?.type !== "image")
|
|
21
|
+
throw new Error("Unsupported tool output content type");
|
|
22
|
+
const sources = ["data", "url", "fileId"].filter((key) => key in part);
|
|
23
|
+
if (sources.length !== 1)
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Tool image requires exactly one of data, url, or fileId"
|
|
26
|
+
);
|
|
27
|
+
if ("fileId" in part) {
|
|
28
|
+
if (part.provider !== "openai" || typeof part.fileId !== "string" || !part.fileId.trim())
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Tool image file references must name the openai provider and fileId"
|
|
31
|
+
);
|
|
32
|
+
return { type: "image", fileId: part.fileId, provider: "openai" };
|
|
33
|
+
}
|
|
34
|
+
if ("url" in part) {
|
|
35
|
+
let url;
|
|
36
|
+
try {
|
|
37
|
+
url = new URL(part.url);
|
|
38
|
+
} catch {
|
|
39
|
+
throw new Error("Invalid tool image URL");
|
|
40
|
+
}
|
|
41
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password)
|
|
42
|
+
throw new Error(
|
|
43
|
+
"Tool image URL must use HTTP(S) without credentials"
|
|
44
|
+
);
|
|
45
|
+
return { type: "image", url: url.href };
|
|
46
|
+
}
|
|
47
|
+
if (!("data" in part) || !imageTypes.has(part.mediaType))
|
|
48
|
+
throw new Error("Unsupported tool image media type");
|
|
49
|
+
let data = part.data instanceof Uint8Array ? Buffer.from(part.data).toString("base64") : part.data;
|
|
50
|
+
if (typeof data !== "string")
|
|
51
|
+
throw new Error("Tool image data must be base64 or bytes");
|
|
52
|
+
if (data.startsWith("data:")) {
|
|
53
|
+
const match = /^data:([^;,]+);base64,(.+)$/.exec(data);
|
|
54
|
+
if (!match || match[1] !== part.mediaType)
|
|
55
|
+
throw new Error(
|
|
56
|
+
"Tool image data URL media type does not match mediaType"
|
|
57
|
+
);
|
|
58
|
+
data = match[2];
|
|
59
|
+
}
|
|
60
|
+
if (!data.length || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(data))
|
|
61
|
+
throw new Error("Invalid tool image base64 data");
|
|
62
|
+
if (Buffer.byteLength(data, "base64") > 20 * 1024 * 1024)
|
|
63
|
+
throw new Error("Tool image exceeds 20 MiB");
|
|
64
|
+
if (Buffer.from(data, "base64").toString("base64") !== data)
|
|
65
|
+
throw new Error("Invalid tool image base64 data");
|
|
66
|
+
return { type: "image", data, mediaType: part.mediaType };
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function toolOutputForProvider(value, provider) {
|
|
71
|
+
const output = normalizeToolOutput(value);
|
|
72
|
+
return output.content.map((part) => {
|
|
73
|
+
if (part.type === "text")
|
|
74
|
+
return {
|
|
75
|
+
type: provider === "responses" ? "input_text" : "text",
|
|
76
|
+
text: part.text
|
|
77
|
+
};
|
|
78
|
+
if (provider === "unsupported")
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Image tool results require OpenAI Responses or Anthropic; this endpoint is not supported"
|
|
81
|
+
);
|
|
82
|
+
if ("fileId" in part) {
|
|
83
|
+
if (provider !== "responses")
|
|
84
|
+
throw new Error(
|
|
85
|
+
"OpenAI image file references cannot be sent to another provider"
|
|
86
|
+
);
|
|
87
|
+
return { type: "input_image", file_id: part.fileId, detail: "auto" };
|
|
88
|
+
}
|
|
89
|
+
if (provider === "responses")
|
|
90
|
+
return {
|
|
91
|
+
type: "input_image",
|
|
92
|
+
image_url: "url" in part ? part.url : `data:${part.mediaType};base64,${part.data}`,
|
|
93
|
+
detail: "auto"
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
type: "image",
|
|
97
|
+
source: "url" in part ? { type: "url", url: part.url } : { type: "base64", media_type: part.mediaType, data: part.data }
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function textToolOutput(value, fallback) {
|
|
102
|
+
if (value === void 0) return fallback;
|
|
103
|
+
return toolOutputForProvider(value, "unsupported").map((part) => part.text).join("\n");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/core/openai-parameters.ts
|
|
107
|
+
function capabilities(model) {
|
|
108
|
+
const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
|
|
109
|
+
const major = Number(gpt?.[1] ?? 0);
|
|
110
|
+
const minor = Number(gpt?.[2] ?? 0);
|
|
111
|
+
const chat = gpt?.[3]?.startsWith("chat") ?? false;
|
|
112
|
+
const reasoning = /^o\d+(?:-|$)/i.test(model) || major >= 5 && !chat;
|
|
113
|
+
const supportsNone = major === 5 && minor >= 1 && !chat || /^gpt-6-(?:sol|luna)(?:-\d{4}-\d{2}-\d{2})?$/i.test(model);
|
|
114
|
+
return { major, minor, reasoning, supportsNone };
|
|
115
|
+
}
|
|
116
|
+
function requiresOpenAIResponses(model, baseUrl, options = {}) {
|
|
117
|
+
if (options.parameterPolicy === "passthrough") return false;
|
|
118
|
+
const c = capabilities(model);
|
|
119
|
+
if (!c.reasoning || options.reasoningEffort === "none") return false;
|
|
120
|
+
if (!(c.major >= 6 || c.major === 5 && c.minor >= 4)) return false;
|
|
121
|
+
if (!baseUrl) return true;
|
|
122
|
+
try {
|
|
123
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function buildOpenAIParameters(model, maxTokens, temperature, options = {}, api = "chat") {
|
|
129
|
+
const c = capabilities(model ?? "");
|
|
130
|
+
const normalize = options.parameterPolicy !== "passthrough";
|
|
131
|
+
const effort = options.reasoningEffort;
|
|
132
|
+
if (normalize && c.reasoning && effort === "none" && !c.supportsNone) {
|
|
133
|
+
throw new Error(`${model} does not support reasoningEffort: none`);
|
|
134
|
+
}
|
|
135
|
+
if (normalize && c.major >= 6 && effort === "minimal") {
|
|
136
|
+
throw new Error(`${model} does not support reasoningEffort: minimal`);
|
|
137
|
+
}
|
|
138
|
+
const result = {
|
|
139
|
+
[api === "responses" ? "max_output_tokens" : normalize && c.reasoning ? "max_completion_tokens" : "max_tokens"]: maxTokens
|
|
140
|
+
};
|
|
141
|
+
if (effort !== void 0) {
|
|
142
|
+
if (api === "responses") result.reasoning = { effort };
|
|
143
|
+
else result.reasoning_effort = effort;
|
|
144
|
+
}
|
|
145
|
+
if (temperature !== void 0) {
|
|
146
|
+
if (!normalize || !c.reasoning || effort === "none" && c.supportsNone) {
|
|
147
|
+
result.temperature = temperature;
|
|
148
|
+
} else {
|
|
149
|
+
(options.onWarning ?? console.warn)(
|
|
150
|
+
`[llm-sdk] ${model}: omitted temperature because reasoning is enabled.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/core/openai-responses.ts
|
|
158
|
+
function resolveOpenAITransport(defaults, overrides) {
|
|
159
|
+
return {
|
|
160
|
+
api: overrides?.api ?? defaults.api ?? "auto",
|
|
161
|
+
responses: defaults.responses !== void 0 || overrides?.responses !== void 0 ? { ...defaults.responses, ...overrides?.responses } : void 0
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function useOpenAIResponses(model, baseURL, parameters, transport2, hasTools, hasReplay, nativeToolSearch = false) {
|
|
165
|
+
const api = transport2.api ?? "auto";
|
|
166
|
+
if (!["auto", "chat", "responses"].includes(api)) {
|
|
167
|
+
throw new Error(`Unknown OpenAI API selection: ${api}`);
|
|
168
|
+
}
|
|
169
|
+
if (api === "chat") {
|
|
170
|
+
if (transport2.responses !== void 0 || nativeToolSearch) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Responses options and native tool search require the Responses API"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
let officialEndpoint = !baseURL;
|
|
178
|
+
try {
|
|
179
|
+
officialEndpoint = !baseURL || new URL(baseURL).hostname === "api.openai.com";
|
|
180
|
+
} catch {
|
|
181
|
+
}
|
|
182
|
+
return api === "responses" || transport2.responses !== void 0 || hasReplay && officialEndpoint || nativeToolSearch || hasTools && requiresOpenAIResponses(model, baseURL, parameters);
|
|
183
|
+
}
|
|
184
|
+
function responseMetadata(response) {
|
|
185
|
+
const output = Array.isArray(response?.output) ? response.output : [];
|
|
186
|
+
return {
|
|
187
|
+
openai: {
|
|
188
|
+
response: {
|
|
189
|
+
id: response?.id,
|
|
190
|
+
model: response?.model,
|
|
191
|
+
status: response?.status,
|
|
192
|
+
output,
|
|
193
|
+
usage: response?.usage,
|
|
194
|
+
store: response?.store,
|
|
195
|
+
refusals: output.flatMap(
|
|
196
|
+
(item) => item?.type === "message" && Array.isArray(item.content) ? item.content.filter(
|
|
197
|
+
(part) => part?.type === "refusal" && typeof part.refusal === "string"
|
|
198
|
+
).map((part) => part.refusal) : []
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function getResponseReplay(metadata) {
|
|
205
|
+
const response = metadata?.openai?.response;
|
|
206
|
+
if (response === void 0) return void 0;
|
|
207
|
+
if (!Array.isArray(response.output) || response.output.some((item) => !item || typeof item.type !== "string")) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"Invalid OpenAI Responses replay metadata: output must contain typed items"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return response.output;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/core/provider-error.ts
|
|
216
|
+
function providerErrorEvent(error, code) {
|
|
217
|
+
const details = error ?? {};
|
|
218
|
+
const event = {
|
|
219
|
+
type: "error",
|
|
220
|
+
message: error instanceof Error ? error.message : String(error),
|
|
221
|
+
code,
|
|
222
|
+
...typeof details.status === "number" ? { status: details.status } : {},
|
|
223
|
+
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
224
|
+
...typeof details.param === "string" ? { param: details.param } : {},
|
|
225
|
+
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
226
|
+
};
|
|
227
|
+
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
228
|
+
return event;
|
|
229
|
+
}
|
|
230
|
+
function errorFromEvent(event) {
|
|
231
|
+
if (event.cause instanceof Error) return event.cause;
|
|
232
|
+
return Object.assign(new Error(event.message), {
|
|
233
|
+
status: event.status,
|
|
234
|
+
code: event.providerCode ?? event.code,
|
|
235
|
+
param: event.param,
|
|
236
|
+
request_id: event.requestId
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function throwIfAborted(signal) {
|
|
240
|
+
if (!signal?.aborted) return;
|
|
241
|
+
const error = new Error("The operation was aborted");
|
|
242
|
+
error.name = "AbortError";
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/core/utils.ts
|
|
247
|
+
function generateId(prefix = "id") {
|
|
248
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
249
|
+
}
|
|
250
|
+
function generateMessageId() {
|
|
251
|
+
return generateId("msg");
|
|
252
|
+
}
|
|
253
|
+
function generateToolCallId() {
|
|
254
|
+
return generateId("call");
|
|
255
|
+
}
|
|
256
|
+
|
|
3
257
|
// src/adapters/base.ts
|
|
4
258
|
function stringifyForDebug(value) {
|
|
5
259
|
return JSON.stringify(
|
|
@@ -106,16 +360,6 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
106
360
|
}
|
|
107
361
|
return normalized;
|
|
108
362
|
}
|
|
109
|
-
function isOpenAIReasoningModel(modelId) {
|
|
110
|
-
if (!modelId) return false;
|
|
111
|
-
return /^(o1|o3|o4|gpt-5)/i.test(modelId);
|
|
112
|
-
}
|
|
113
|
-
function buildOpenAITokenParams(modelId, maxTokens, temperature) {
|
|
114
|
-
if (isOpenAIReasoningModel(modelId)) {
|
|
115
|
-
return { max_completion_tokens: maxTokens };
|
|
116
|
-
}
|
|
117
|
-
return { max_tokens: maxTokens, temperature };
|
|
118
|
-
}
|
|
119
363
|
function toOpenAIResponseFormat(rf) {
|
|
120
364
|
if (!rf) return void 0;
|
|
121
365
|
if (rf.type === "json_object") return { type: "json_object" };
|
|
@@ -200,7 +444,7 @@ function messageToOpenAIContent(message) {
|
|
|
200
444
|
}
|
|
201
445
|
return blocks;
|
|
202
446
|
}
|
|
203
|
-
function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
447
|
+
function formatMessagesForOpenAI(messages, systemPrompt, preserveProviderMetadata = false) {
|
|
204
448
|
const formatted = [];
|
|
205
449
|
if (systemPrompt) {
|
|
206
450
|
formatted.push({ role: "system", content: systemPrompt });
|
|
@@ -218,7 +462,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
218
462
|
const assistantMsg = {
|
|
219
463
|
role: "assistant",
|
|
220
464
|
// Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
|
|
221
|
-
content: hasToolCalls ? msg.content || null : msg.content
|
|
465
|
+
content: hasToolCalls ? msg.content || null : msg.content,
|
|
466
|
+
...preserveProviderMetadata && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
|
|
222
467
|
};
|
|
223
468
|
if (hasToolCalls) {
|
|
224
469
|
assistantMsg.tool_calls = msg.tool_calls;
|
|
@@ -227,7 +472,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
227
472
|
} else if (msg.role === "tool" && msg.tool_call_id) {
|
|
228
473
|
formatted.push({
|
|
229
474
|
role: "tool",
|
|
230
|
-
content: msg.content ?? "",
|
|
475
|
+
content: preserveProviderMetadata ? msg.content ?? "" : textToolOutput(msg.modelOutput, msg.content ?? ""),
|
|
476
|
+
...preserveProviderMetadata && msg.modelOutput ? { modelOutput: msg.modelOutput } : {},
|
|
231
477
|
tool_call_id: msg.tool_call_id
|
|
232
478
|
});
|
|
233
479
|
}
|
|
@@ -235,344 +481,61 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
235
481
|
return formatted;
|
|
236
482
|
}
|
|
237
483
|
|
|
238
|
-
// src/
|
|
239
|
-
var
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
vision: true,
|
|
256
|
-
tools: true,
|
|
257
|
-
jsonMode: true,
|
|
258
|
-
maxTokens: 128e3
|
|
259
|
-
},
|
|
260
|
-
// GPT-4 Turbo
|
|
261
|
-
"gpt-4-turbo": {
|
|
262
|
-
vision: true,
|
|
263
|
-
tools: true,
|
|
264
|
-
jsonMode: true,
|
|
265
|
-
maxTokens: 128e3
|
|
266
|
-
},
|
|
267
|
-
"gpt-4-turbo-preview": {
|
|
268
|
-
vision: false,
|
|
269
|
-
tools: true,
|
|
270
|
-
jsonMode: true,
|
|
271
|
-
maxTokens: 128e3
|
|
272
|
-
},
|
|
273
|
-
// GPT-4
|
|
274
|
-
"gpt-4": { vision: false, tools: true, jsonMode: false, maxTokens: 8192 },
|
|
275
|
-
"gpt-4-32k": {
|
|
276
|
-
vision: false,
|
|
277
|
-
tools: true,
|
|
278
|
-
jsonMode: false,
|
|
279
|
-
maxTokens: 32768
|
|
280
|
-
},
|
|
281
|
-
// GPT-3.5
|
|
282
|
-
"gpt-3.5-turbo": {
|
|
283
|
-
vision: false,
|
|
284
|
-
tools: true,
|
|
285
|
-
jsonMode: true,
|
|
286
|
-
maxTokens: 16385
|
|
287
|
-
},
|
|
288
|
-
// O1 series (reasoning)
|
|
289
|
-
o1: { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
290
|
-
"o1-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
291
|
-
"o1-preview": {
|
|
292
|
-
vision: true,
|
|
293
|
-
tools: false,
|
|
294
|
-
jsonMode: false,
|
|
295
|
-
maxTokens: 128e3
|
|
296
|
-
},
|
|
297
|
-
// O3 series
|
|
298
|
-
"o3-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 }
|
|
299
|
-
};
|
|
300
|
-
function openai(modelId, options = {}) {
|
|
301
|
-
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
302
|
-
const baseURL = options.baseURL ?? "https://api.openai.com/v1";
|
|
303
|
-
let client = null;
|
|
304
|
-
async function getClient() {
|
|
305
|
-
if (!client) {
|
|
484
|
+
// src/adapters/openai.ts
|
|
485
|
+
var OpenAIAdapter = class _OpenAIAdapter {
|
|
486
|
+
constructor(config) {
|
|
487
|
+
this.config = config;
|
|
488
|
+
this.model = config.model || "gpt-4o";
|
|
489
|
+
this.provider = _OpenAIAdapter.resolveProviderName(config.baseUrl);
|
|
490
|
+
}
|
|
491
|
+
static resolveProviderName(baseUrl) {
|
|
492
|
+
if (!baseUrl) return "openai";
|
|
493
|
+
if (baseUrl.includes("generativelanguage.googleapis.com")) return "google";
|
|
494
|
+
if (baseUrl.includes("x.ai")) return "xai";
|
|
495
|
+
if (baseUrl.includes("azure")) return "azure";
|
|
496
|
+
if (baseUrl.includes("fireworks.ai")) return "fireworks";
|
|
497
|
+
return "openai";
|
|
498
|
+
}
|
|
499
|
+
async getClient() {
|
|
500
|
+
if (!this.client) {
|
|
306
501
|
const { default: OpenAI } = await import('openai');
|
|
307
|
-
client = new OpenAI({
|
|
308
|
-
apiKey,
|
|
309
|
-
baseURL,
|
|
310
|
-
organization:
|
|
311
|
-
defaultHeaders:
|
|
502
|
+
this.client = new OpenAI({
|
|
503
|
+
apiKey: this.config.apiKey,
|
|
504
|
+
baseURL: this.config.baseUrl,
|
|
505
|
+
organization: this.config.organization,
|
|
506
|
+
defaultHeaders: this.config.headers
|
|
312
507
|
});
|
|
313
508
|
}
|
|
314
|
-
return client;
|
|
509
|
+
return this.client;
|
|
315
510
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
supportsVision: modelConfig.vision,
|
|
322
|
-
supportsTools: modelConfig.tools,
|
|
323
|
-
supportsStreaming: true,
|
|
324
|
-
supportsJsonMode: modelConfig.jsonMode,
|
|
325
|
-
supportsThinking: false,
|
|
326
|
-
supportsPDF: false,
|
|
327
|
-
maxTokens: modelConfig.maxTokens,
|
|
328
|
-
supportedImageTypes: modelConfig.vision ? ["image/png", "image/jpeg", "image/gif", "image/webp"] : []
|
|
329
|
-
},
|
|
330
|
-
async doGenerate(params) {
|
|
331
|
-
const client2 = await getClient();
|
|
332
|
-
const messages = formatMessagesForOpenAI2(params.messages);
|
|
333
|
-
const response = await client2.chat.completions.create({
|
|
334
|
-
model: modelId,
|
|
335
|
-
messages,
|
|
336
|
-
tools: params.tools,
|
|
337
|
-
...buildOpenAITokenParams(
|
|
338
|
-
modelId,
|
|
339
|
-
params.maxTokens,
|
|
340
|
-
params.temperature
|
|
341
|
-
),
|
|
342
|
-
response_format: toOpenAIResponseFormat(params.responseFormat)
|
|
343
|
-
});
|
|
344
|
-
const choice = response.choices[0];
|
|
345
|
-
const message = choice.message;
|
|
346
|
-
const toolCalls = (message.tool_calls ?? []).map(
|
|
347
|
-
(tc) => ({
|
|
348
|
-
id: tc.id,
|
|
349
|
-
name: tc.function.name,
|
|
350
|
-
args: JSON.parse(tc.function.arguments || "{}")
|
|
351
|
-
})
|
|
352
|
-
);
|
|
353
|
-
return {
|
|
354
|
-
text: message.content ?? "",
|
|
355
|
-
toolCalls,
|
|
356
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
357
|
-
usage: {
|
|
358
|
-
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
359
|
-
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
360
|
-
totalTokens: response.usage?.total_tokens ?? 0
|
|
361
|
-
},
|
|
362
|
-
rawResponse: response
|
|
363
|
-
};
|
|
364
|
-
},
|
|
365
|
-
async *doStream(params) {
|
|
366
|
-
const client2 = await getClient();
|
|
367
|
-
const messages = formatMessagesForOpenAI2(params.messages);
|
|
368
|
-
const stream = await client2.chat.completions.create({
|
|
369
|
-
model: modelId,
|
|
370
|
-
messages,
|
|
371
|
-
tools: params.tools,
|
|
372
|
-
...buildOpenAITokenParams(
|
|
373
|
-
modelId,
|
|
374
|
-
params.maxTokens,
|
|
375
|
-
params.temperature
|
|
376
|
-
),
|
|
377
|
-
response_format: toOpenAIResponseFormat(params.responseFormat),
|
|
378
|
-
stream: true
|
|
379
|
-
});
|
|
380
|
-
let currentToolCall = null;
|
|
381
|
-
let totalPromptTokens = 0;
|
|
382
|
-
let totalCompletionTokens = 0;
|
|
383
|
-
for await (const chunk of stream) {
|
|
384
|
-
if (params.signal?.aborted) {
|
|
385
|
-
yield { type: "error", error: new Error("Aborted") };
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const choice = chunk.choices[0];
|
|
389
|
-
const delta = choice?.delta;
|
|
390
|
-
if (delta?.content) {
|
|
391
|
-
yield { type: "text-delta", text: delta.content };
|
|
392
|
-
}
|
|
393
|
-
if (delta?.tool_calls) {
|
|
394
|
-
for (const tc of delta.tool_calls) {
|
|
395
|
-
if (tc.id) {
|
|
396
|
-
if (currentToolCall) {
|
|
397
|
-
yield {
|
|
398
|
-
type: "tool-call",
|
|
399
|
-
toolCall: {
|
|
400
|
-
id: currentToolCall.id,
|
|
401
|
-
name: currentToolCall.name,
|
|
402
|
-
args: JSON.parse(currentToolCall.arguments || "{}")
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
currentToolCall = {
|
|
407
|
-
id: tc.id,
|
|
408
|
-
name: tc.function?.name ?? "",
|
|
409
|
-
arguments: tc.function?.arguments ?? ""
|
|
410
|
-
};
|
|
411
|
-
} else if (currentToolCall && tc.function?.arguments) {
|
|
412
|
-
currentToolCall.arguments += tc.function.arguments;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (choice?.finish_reason) {
|
|
417
|
-
if (currentToolCall) {
|
|
418
|
-
yield {
|
|
419
|
-
type: "tool-call",
|
|
420
|
-
toolCall: {
|
|
421
|
-
id: currentToolCall.id,
|
|
422
|
-
name: currentToolCall.name,
|
|
423
|
-
args: JSON.parse(currentToolCall.arguments || "{}")
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
currentToolCall = null;
|
|
427
|
-
}
|
|
428
|
-
if (chunk.usage) {
|
|
429
|
-
totalPromptTokens = chunk.usage.prompt_tokens;
|
|
430
|
-
totalCompletionTokens = chunk.usage.completion_tokens;
|
|
431
|
-
}
|
|
432
|
-
yield {
|
|
433
|
-
type: "finish",
|
|
434
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
435
|
-
usage: {
|
|
436
|
-
promptTokens: totalPromptTokens,
|
|
437
|
-
completionTokens: totalCompletionTokens,
|
|
438
|
-
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
function mapFinishReason(reason) {
|
|
447
|
-
switch (reason) {
|
|
448
|
-
case "stop":
|
|
449
|
-
return "stop";
|
|
450
|
-
case "length":
|
|
451
|
-
return "length";
|
|
452
|
-
case "tool_calls":
|
|
453
|
-
case "function_call":
|
|
454
|
-
return "tool-calls";
|
|
455
|
-
case "content_filter":
|
|
456
|
-
return "content-filter";
|
|
457
|
-
default:
|
|
458
|
-
return "unknown";
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
function formatMessagesForOpenAI2(messages) {
|
|
462
|
-
return messages.map((msg) => {
|
|
463
|
-
switch (msg.role) {
|
|
464
|
-
case "system":
|
|
465
|
-
return { role: "system", content: msg.content };
|
|
466
|
-
case "user":
|
|
467
|
-
if (typeof msg.content === "string") {
|
|
468
|
-
return { role: "user", content: msg.content };
|
|
469
|
-
}
|
|
470
|
-
return {
|
|
471
|
-
role: "user",
|
|
472
|
-
content: msg.content.map((part) => {
|
|
473
|
-
if (part.type === "text") {
|
|
474
|
-
return { type: "text", text: part.text };
|
|
475
|
-
}
|
|
476
|
-
if (part.type === "image") {
|
|
477
|
-
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
478
|
-
const url = imageData.startsWith("data:") ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
479
|
-
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
480
|
-
}
|
|
481
|
-
return { type: "text", text: "" };
|
|
482
|
-
})
|
|
483
|
-
};
|
|
484
|
-
case "assistant":
|
|
485
|
-
const assistantMsg = {
|
|
486
|
-
role: "assistant",
|
|
487
|
-
content: msg.content
|
|
488
|
-
};
|
|
489
|
-
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
490
|
-
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
491
|
-
id: tc.id,
|
|
492
|
-
type: "function",
|
|
493
|
-
function: {
|
|
494
|
-
name: tc.name,
|
|
495
|
-
arguments: JSON.stringify(tc.args)
|
|
496
|
-
}
|
|
497
|
-
}));
|
|
498
|
-
}
|
|
499
|
-
return assistantMsg;
|
|
500
|
-
case "tool":
|
|
501
|
-
return {
|
|
502
|
-
role: "tool",
|
|
503
|
-
tool_call_id: msg.toolCallId,
|
|
504
|
-
content: msg.content
|
|
505
|
-
};
|
|
506
|
-
default:
|
|
507
|
-
return msg;
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// src/core/utils.ts
|
|
513
|
-
function generateId(prefix = "id") {
|
|
514
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
515
|
-
}
|
|
516
|
-
function generateMessageId() {
|
|
517
|
-
return generateId("msg");
|
|
518
|
-
}
|
|
519
|
-
function generateToolCallId() {
|
|
520
|
-
return generateId("call");
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// src/adapters/openai.ts
|
|
524
|
-
var OpenAIAdapter = class _OpenAIAdapter {
|
|
525
|
-
constructor(config) {
|
|
526
|
-
this.config = config;
|
|
527
|
-
this.model = config.model || "gpt-4o";
|
|
528
|
-
this.provider = _OpenAIAdapter.resolveProviderName(config.baseUrl);
|
|
529
|
-
}
|
|
530
|
-
static resolveProviderName(baseUrl) {
|
|
531
|
-
if (!baseUrl) return "openai";
|
|
532
|
-
if (baseUrl.includes("generativelanguage.googleapis.com")) return "google";
|
|
533
|
-
if (baseUrl.includes("x.ai")) return "xai";
|
|
534
|
-
if (baseUrl.includes("azure")) return "azure";
|
|
535
|
-
if (baseUrl.includes("fireworks.ai")) return "fireworks";
|
|
536
|
-
return "openai";
|
|
511
|
+
parameterOptions(request) {
|
|
512
|
+
return {
|
|
513
|
+
...this.config,
|
|
514
|
+
reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
|
|
515
|
+
};
|
|
537
516
|
}
|
|
538
|
-
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
return this.client;
|
|
517
|
+
toolDefinitions(request) {
|
|
518
|
+
if (request.toolDefinitions?.length) return request.toolDefinitions;
|
|
519
|
+
return formatTools(request.actions ?? []).map((tool) => ({
|
|
520
|
+
name: tool.function.name,
|
|
521
|
+
description: tool.function.description ?? "",
|
|
522
|
+
location: "server",
|
|
523
|
+
inputSchema: tool.function.parameters
|
|
524
|
+
}));
|
|
547
525
|
}
|
|
548
526
|
shouldUseResponsesApi(request) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
request.
|
|
527
|
+
return useOpenAIResponses(
|
|
528
|
+
request.config?.model || this.model,
|
|
529
|
+
this.config.baseUrl,
|
|
530
|
+
this.parameterOptions(request),
|
|
531
|
+
resolveOpenAITransport(this.config, request.config?.openai),
|
|
532
|
+
this.toolDefinitions(request).length > 0,
|
|
533
|
+
!!(request.rawMessages ?? request.messages)?.some(
|
|
534
|
+
(message) => message.providerMetadata?.openai?.response
|
|
535
|
+
),
|
|
536
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false
|
|
556
537
|
);
|
|
557
538
|
}
|
|
558
|
-
/**
|
|
559
|
-
* Whether this model rejects function tools on /v1/chat/completions.
|
|
560
|
-
*
|
|
561
|
-
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
562
|
-
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
563
|
-
* restriction and may not implement /v1/responses at all.
|
|
564
|
-
*/
|
|
565
|
-
requiresResponsesApiForTools(modelId) {
|
|
566
|
-
if (!modelId) return false;
|
|
567
|
-
if (!/^gpt-5\.(?:[4-9]|\d{2,})/i.test(modelId)) return false;
|
|
568
|
-
const baseUrl = this.config.baseUrl;
|
|
569
|
-
if (!baseUrl) return true;
|
|
570
|
-
try {
|
|
571
|
-
return new URL(baseUrl).hostname === "api.openai.com";
|
|
572
|
-
} catch {
|
|
573
|
-
return false;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
539
|
/**
|
|
577
540
|
* Convert one message's content to Responses content parts.
|
|
578
541
|
*
|
|
@@ -603,11 +566,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
603
566
|
});
|
|
604
567
|
}
|
|
605
568
|
buildResponsesInput(request) {
|
|
606
|
-
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
569
|
+
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
|
|
607
570
|
const input = [];
|
|
608
571
|
for (const message of sourceMessages) {
|
|
609
572
|
if (message.role === "system") {
|
|
610
|
-
if (
|
|
573
|
+
if (message.content && message.content !== request.systemPrompt) {
|
|
611
574
|
input.push({
|
|
612
575
|
type: "message",
|
|
613
576
|
role: "developer",
|
|
@@ -617,6 +580,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
617
580
|
continue;
|
|
618
581
|
}
|
|
619
582
|
if (message.role === "assistant") {
|
|
583
|
+
const replay = getResponseReplay(message.providerMetadata);
|
|
584
|
+
if (replay) {
|
|
585
|
+
input.push(...replay);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
620
588
|
const content = this.toResponsesContent(message.content, "assistant");
|
|
621
589
|
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
622
590
|
input.push({
|
|
@@ -640,7 +608,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
640
608
|
input.push({
|
|
641
609
|
type: "function_call_output",
|
|
642
610
|
call_id: message.tool_call_id,
|
|
643
|
-
output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
611
|
+
output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
644
612
|
});
|
|
645
613
|
continue;
|
|
646
614
|
}
|
|
@@ -653,6 +621,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
653
621
|
}
|
|
654
622
|
return input;
|
|
655
623
|
}
|
|
624
|
+
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
625
|
+
formatRawMessages(messages, preserveProviderMetadata = false) {
|
|
626
|
+
return messages.map((message) => {
|
|
627
|
+
const { attachments, providerMetadata, modelOutput, ...rest } = message;
|
|
628
|
+
let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
|
|
629
|
+
if (preserveProviderMetadata && modelOutput)
|
|
630
|
+
rest.modelOutput = modelOutput;
|
|
631
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
|
|
632
|
+
content = null;
|
|
633
|
+
}
|
|
634
|
+
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
635
|
+
const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
|
|
636
|
+
(attachment) => attachmentToOpenAIImage(attachment)
|
|
637
|
+
).filter((image) => image !== null);
|
|
638
|
+
if (images.length > 0) {
|
|
639
|
+
const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
|
|
640
|
+
content = [...parts, ...images];
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
...rest,
|
|
645
|
+
content,
|
|
646
|
+
...preserveProviderMetadata && providerMetadata ? { providerMetadata } : {}
|
|
647
|
+
};
|
|
648
|
+
});
|
|
649
|
+
}
|
|
656
650
|
/**
|
|
657
651
|
* Build the web_search tool for the Responses API, or undefined when web
|
|
658
652
|
* search is off.
|
|
@@ -731,20 +725,25 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
731
725
|
).map((part) => part.text) : []
|
|
732
726
|
).join("") : "";
|
|
733
727
|
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
name
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
728
|
+
const seenCallIds = /* @__PURE__ */ new Set();
|
|
729
|
+
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
|
|
730
|
+
if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
|
|
731
|
+
throw new Error("Responses contains an incomplete function call");
|
|
732
|
+
}
|
|
733
|
+
if (seenCallIds.has(item.call_id))
|
|
734
|
+
throw new Error("Responses contains duplicate function call IDs");
|
|
735
|
+
seenCallIds.add(item.call_id);
|
|
736
|
+
const args = JSON.parse(item.arguments);
|
|
737
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
738
|
+
throw new Error(
|
|
739
|
+
"Responses function arguments must be a JSON object"
|
|
740
|
+
);
|
|
741
|
+
return { id: item.call_id, name: item.name, args };
|
|
742
|
+
}) : [];
|
|
745
743
|
return {
|
|
746
744
|
content,
|
|
747
745
|
toolCalls,
|
|
746
|
+
providerMetadata: responseMetadata(response),
|
|
748
747
|
usage: response?.usage ? {
|
|
749
748
|
promptTokens: response.usage.input_tokens ?? 0,
|
|
750
749
|
completionTokens: response.usage.output_tokens ?? 0,
|
|
@@ -755,6 +754,17 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
755
754
|
}
|
|
756
755
|
buildResponsesPayload(request, stream) {
|
|
757
756
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
757
|
+
const options = resolveOpenAITransport(
|
|
758
|
+
this.config,
|
|
759
|
+
request.config?.openai
|
|
760
|
+
).responses;
|
|
761
|
+
const parameters = buildOpenAIParameters(
|
|
762
|
+
request.config?.model || this.model,
|
|
763
|
+
request.config?.maxTokens ?? this.config.maxTokens,
|
|
764
|
+
request.config?.temperature ?? this.config.temperature,
|
|
765
|
+
this.parameterOptions(request),
|
|
766
|
+
"responses"
|
|
767
|
+
);
|
|
758
768
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
759
769
|
request.config?.responseFormat
|
|
760
770
|
);
|
|
@@ -764,18 +774,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
764
774
|
instructions: request.systemPrompt,
|
|
765
775
|
input: this.buildResponsesInput(request),
|
|
766
776
|
tools: this.buildResponsesTools(
|
|
767
|
-
|
|
777
|
+
this.toolDefinitions(request),
|
|
768
778
|
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
769
779
|
this.buildResponsesWebSearchTool(request)
|
|
770
780
|
),
|
|
771
|
-
tool_choice: openaiToolOptions?.toolChoice
|
|
781
|
+
tool_choice: openaiToolOptions?.toolChoice,
|
|
772
782
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
783
|
+
...parameters,
|
|
784
|
+
...options?.reasoningSummary ? {
|
|
785
|
+
reasoning: {
|
|
786
|
+
...parameters.reasoning,
|
|
787
|
+
summary: options.reasoningSummary
|
|
788
|
+
}
|
|
789
|
+
} : {},
|
|
790
|
+
...options?.store !== void 0 ? { store: options.store } : {},
|
|
791
|
+
...options?.previousResponseId !== void 0 ? { previous_response_id: options.previousResponseId } : {},
|
|
792
|
+
...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
|
|
793
|
+
...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
|
|
794
|
+
...options?.promptCacheRetention !== void 0 ? { prompt_cache_retention: options.promptCacheRetention } : {},
|
|
795
|
+
...options?.store === false || options?.include ? {
|
|
796
|
+
include: [
|
|
797
|
+
.../* @__PURE__ */ new Set([
|
|
798
|
+
...options?.include ?? [],
|
|
799
|
+
...options?.store === false ? ["reasoning.encrypted_content"] : []
|
|
800
|
+
])
|
|
801
|
+
]
|
|
802
|
+
} : {},
|
|
779
803
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
780
804
|
stream
|
|
781
805
|
};
|
|
@@ -783,10 +807,26 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
783
807
|
}
|
|
784
808
|
async completeWithResponses(request) {
|
|
785
809
|
const client = await this.getClient();
|
|
810
|
+
throwIfAborted(request.signal);
|
|
786
811
|
const payload = this.buildResponsesPayload(request, false);
|
|
787
812
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
788
|
-
|
|
813
|
+
if (!client.responses?.create)
|
|
814
|
+
throw new Error(
|
|
815
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
816
|
+
);
|
|
817
|
+
const response = await client.responses.create(payload, {
|
|
818
|
+
signal: request.signal
|
|
819
|
+
});
|
|
820
|
+
throwIfAborted(request.signal);
|
|
789
821
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
822
|
+
if (response.status && response.status !== "completed") {
|
|
823
|
+
throw Object.assign(
|
|
824
|
+
new Error(
|
|
825
|
+
response.error?.message ?? `Responses request ${response.status}`
|
|
826
|
+
),
|
|
827
|
+
response.error
|
|
828
|
+
);
|
|
829
|
+
}
|
|
790
830
|
return this.parseResponsesResult(response);
|
|
791
831
|
}
|
|
792
832
|
/**
|
|
@@ -804,13 +844,30 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
804
844
|
*/
|
|
805
845
|
async *streamWithResponses(request) {
|
|
806
846
|
const client = await this.getClient();
|
|
847
|
+
throwIfAborted(request.signal);
|
|
807
848
|
const payload = this.buildResponsesPayload(request, true);
|
|
808
849
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
809
|
-
|
|
850
|
+
if (!client.responses?.create)
|
|
851
|
+
throw new Error(
|
|
852
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
853
|
+
);
|
|
854
|
+
const stream = await client.responses.create(payload, {
|
|
855
|
+
signal: request.signal
|
|
856
|
+
});
|
|
810
857
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
811
858
|
let usage;
|
|
812
859
|
const citations = [];
|
|
860
|
+
let completed = false;
|
|
861
|
+
let terminalResponse;
|
|
862
|
+
let responseIdentity = {};
|
|
863
|
+
const outputItems = /* @__PURE__ */ new Map();
|
|
864
|
+
let streamedText = "";
|
|
813
865
|
function* flushToolCalls() {
|
|
866
|
+
for (const call of toolCalls.values()) {
|
|
867
|
+
if (!call.name)
|
|
868
|
+
throw new Error("Responses tool call is missing its name");
|
|
869
|
+
JSON.parse(call.arguments || "{}");
|
|
870
|
+
}
|
|
814
871
|
for (const call of toolCalls.values()) {
|
|
815
872
|
yield { type: "action:start", id: call.id, name: call.name };
|
|
816
873
|
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
@@ -819,19 +876,38 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
819
876
|
toolCalls.clear();
|
|
820
877
|
}
|
|
821
878
|
for await (const event of stream) {
|
|
822
|
-
|
|
879
|
+
throwIfAborted(request.signal);
|
|
823
880
|
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
881
|
+
if (completed)
|
|
882
|
+
throw new Error("Responses stream emitted an event after completion");
|
|
824
883
|
const index = event?.output_index ?? 0;
|
|
825
884
|
switch (event?.type) {
|
|
885
|
+
case "response.created":
|
|
886
|
+
responseIdentity = {
|
|
887
|
+
id: event.response?.id,
|
|
888
|
+
model: event.response?.model
|
|
889
|
+
};
|
|
890
|
+
break;
|
|
891
|
+
case "response.output_item.done":
|
|
892
|
+
if (event.item) outputItems.set(index, event.item);
|
|
893
|
+
if (event.item?.type === "function_call") {
|
|
894
|
+
toolCalls.set(index, {
|
|
895
|
+
id: event.item.call_id,
|
|
896
|
+
name: event.item.name,
|
|
897
|
+
arguments: event.item.arguments
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
break;
|
|
826
901
|
case "response.output_text.delta":
|
|
827
902
|
if (event.delta) {
|
|
903
|
+
streamedText += event.delta;
|
|
828
904
|
yield { type: "message:delta", content: event.delta };
|
|
829
905
|
}
|
|
830
906
|
break;
|
|
831
907
|
case "response.output_item.added":
|
|
832
908
|
if (event.item?.type === "function_call") {
|
|
833
909
|
toolCalls.set(index, {
|
|
834
|
-
id: event.item.call_id ??
|
|
910
|
+
id: event.item.call_id ?? "",
|
|
835
911
|
name: event.item.name ?? "",
|
|
836
912
|
arguments: event.item.arguments ?? ""
|
|
837
913
|
});
|
|
@@ -849,8 +925,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
849
925
|
}
|
|
850
926
|
break;
|
|
851
927
|
}
|
|
852
|
-
case "response.completed":
|
|
853
|
-
|
|
928
|
+
case "response.completed": {
|
|
929
|
+
if (event.response?.status && event.response.status !== "completed")
|
|
930
|
+
throw new Error(`Responses stream ${event.response.status}`);
|
|
931
|
+
completed = true;
|
|
932
|
+
terminalResponse = { ...responseIdentity, ...event.response };
|
|
854
933
|
const found = this.collectResponsesCitations(
|
|
855
934
|
event.response?.output,
|
|
856
935
|
citations.length
|
|
@@ -866,21 +945,88 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
866
945
|
}
|
|
867
946
|
break;
|
|
868
947
|
}
|
|
869
|
-
case "
|
|
870
|
-
case "response.failed":
|
|
948
|
+
case "response.incomplete":
|
|
871
949
|
throw new Error(
|
|
872
|
-
|
|
950
|
+
`Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
|
|
951
|
+
);
|
|
952
|
+
case "error":
|
|
953
|
+
case "response.failed": {
|
|
954
|
+
const details = event.response?.error ?? event;
|
|
955
|
+
throw Object.assign(
|
|
956
|
+
new Error(details.message ?? "Responses stream failed"),
|
|
957
|
+
{
|
|
958
|
+
code: details.code,
|
|
959
|
+
param: details.param,
|
|
960
|
+
status: details.status
|
|
961
|
+
}
|
|
873
962
|
);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
throwIfAborted(request.signal);
|
|
967
|
+
if (!completed)
|
|
968
|
+
throw new Error("Responses stream ended without a completed response");
|
|
969
|
+
const output = terminalResponse.output?.length ? terminalResponse.output : [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
|
|
970
|
+
if (output.some((item) => item.type === "function_call"))
|
|
971
|
+
toolCalls.clear();
|
|
972
|
+
for (const [index, item] of output.entries()) {
|
|
973
|
+
if (item.type === "function_call") {
|
|
974
|
+
toolCalls.set(index, {
|
|
975
|
+
id: item.call_id,
|
|
976
|
+
name: item.name,
|
|
977
|
+
arguments: item.arguments
|
|
978
|
+
});
|
|
874
979
|
}
|
|
875
980
|
}
|
|
981
|
+
const uniqueCalls = /* @__PURE__ */ new Map();
|
|
982
|
+
for (const call of toolCalls.values()) {
|
|
983
|
+
if (!call.id || !call.name)
|
|
984
|
+
throw new Error("Responses stream contains an incomplete tool call");
|
|
985
|
+
uniqueCalls.set(call.id, call);
|
|
986
|
+
}
|
|
987
|
+
toolCalls.clear();
|
|
988
|
+
[...uniqueCalls.values()].forEach(
|
|
989
|
+
(call, index) => toolCalls.set(index, call)
|
|
990
|
+
);
|
|
991
|
+
if (!output.length && toolCalls.size) {
|
|
992
|
+
for (const call of toolCalls.values())
|
|
993
|
+
output.push({
|
|
994
|
+
type: "function_call",
|
|
995
|
+
call_id: call.id,
|
|
996
|
+
name: call.name,
|
|
997
|
+
arguments: call.arguments
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
if (!output.length && streamedText)
|
|
1001
|
+
output.push({
|
|
1002
|
+
type: "message",
|
|
1003
|
+
role: "assistant",
|
|
1004
|
+
content: [{ type: "output_text", text: streamedText, annotations: [] }]
|
|
1005
|
+
});
|
|
1006
|
+
terminalResponse.output = output;
|
|
1007
|
+
const finalText = this.parseResponsesResult(terminalResponse).content;
|
|
1008
|
+
if (finalText.startsWith(streamedText) && finalText.length > streamedText.length) {
|
|
1009
|
+
yield {
|
|
1010
|
+
type: "message:delta",
|
|
1011
|
+
content: finalText.slice(streamedText.length)
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
876
1014
|
yield* flushToolCalls();
|
|
877
1015
|
if (citations.length > 0) {
|
|
878
1016
|
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
879
1017
|
}
|
|
880
|
-
yield {
|
|
881
|
-
|
|
1018
|
+
yield {
|
|
1019
|
+
type: "message:end",
|
|
1020
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1021
|
+
};
|
|
1022
|
+
yield {
|
|
1023
|
+
type: "done",
|
|
1024
|
+
usage,
|
|
1025
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1026
|
+
};
|
|
882
1027
|
}
|
|
883
1028
|
async *stream(request) {
|
|
1029
|
+
throwIfAborted(request.signal);
|
|
884
1030
|
if (this.shouldUseResponsesApi(request)) {
|
|
885
1031
|
const messageId2 = generateMessageId();
|
|
886
1032
|
yield { type: "message:start", id: messageId2 };
|
|
@@ -888,47 +1034,14 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
888
1034
|
yield* this.streamWithResponses(request);
|
|
889
1035
|
return;
|
|
890
1036
|
} catch (error) {
|
|
891
|
-
yield
|
|
892
|
-
type: "error",
|
|
893
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
894
|
-
code: "OPENAI_RESPONSES_ERROR"
|
|
895
|
-
};
|
|
1037
|
+
yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
|
|
896
1038
|
return;
|
|
897
1039
|
}
|
|
898
1040
|
}
|
|
899
1041
|
const client = await this.getClient();
|
|
900
1042
|
let messages;
|
|
901
1043
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
902
|
-
const processedMessages = request.rawMessages
|
|
903
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
904
|
-
return { ...msg, content: null };
|
|
905
|
-
}
|
|
906
|
-
const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
|
|
907
|
-
if (hasAttachments) {
|
|
908
|
-
const content = [];
|
|
909
|
-
if (msg.content) {
|
|
910
|
-
content.push({ type: "text", text: msg.content });
|
|
911
|
-
}
|
|
912
|
-
for (const attachment of msg.attachments) {
|
|
913
|
-
if (attachment.type === "image") {
|
|
914
|
-
let imageUrl;
|
|
915
|
-
if (attachment.url) {
|
|
916
|
-
imageUrl = attachment.url;
|
|
917
|
-
} else if (attachment.data) {
|
|
918
|
-
imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
|
|
919
|
-
} else {
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
|
-
content.push({
|
|
923
|
-
type: "image_url",
|
|
924
|
-
image_url: { url: imageUrl, detail: "auto" }
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
return { ...msg, content, attachments: void 0 };
|
|
929
|
-
}
|
|
930
|
-
return msg;
|
|
931
|
-
});
|
|
1044
|
+
const processedMessages = this.formatRawMessages(request.rawMessages);
|
|
932
1045
|
if (request.systemPrompt) {
|
|
933
1046
|
const hasSystem = processedMessages.some((m) => m.role === "system");
|
|
934
1047
|
if (!hasSystem) {
|
|
@@ -948,7 +1061,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
948
1061
|
request.systemPrompt
|
|
949
1062
|
);
|
|
950
1063
|
}
|
|
951
|
-
const tools = request.
|
|
1064
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1065
|
+
type: "function",
|
|
1066
|
+
function: {
|
|
1067
|
+
name: tool.name,
|
|
1068
|
+
description: tool.description,
|
|
1069
|
+
parameters: normalizeObjectJsonSchema(
|
|
1070
|
+
tool.inputSchema
|
|
1071
|
+
)
|
|
1072
|
+
}
|
|
1073
|
+
})) : formatTools(request.actions ?? []);
|
|
952
1074
|
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
953
1075
|
if (webSearchConfig) {
|
|
954
1076
|
const webSearchTool = {
|
|
@@ -977,26 +1099,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
977
1099
|
tools: tools.length > 0 ? tools : void 0,
|
|
978
1100
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
979
1101
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
980
|
-
...
|
|
1102
|
+
...buildOpenAIParameters(
|
|
981
1103
|
modelIdForPayload,
|
|
982
1104
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
983
|
-
request.config?.temperature ?? this.config.temperature
|
|
1105
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1106
|
+
this.parameterOptions(request)
|
|
984
1107
|
),
|
|
985
1108
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
986
1109
|
stream: true,
|
|
987
1110
|
stream_options: { include_usage: true }
|
|
988
1111
|
};
|
|
989
1112
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
990
|
-
const stream = await client.chat.completions.create(payload
|
|
991
|
-
|
|
1113
|
+
const stream = await client.chat.completions.create(payload, {
|
|
1114
|
+
signal: request.signal
|
|
1115
|
+
});
|
|
1116
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1117
|
+
let finishReason;
|
|
992
1118
|
const collectedCitations = [];
|
|
993
1119
|
let citationIndex = 0;
|
|
994
1120
|
let usage;
|
|
995
1121
|
for await (const chunk of stream) {
|
|
996
1122
|
logProviderPayload("openai", "stream chunk", chunk, request.debug);
|
|
997
|
-
|
|
998
|
-
break;
|
|
999
|
-
}
|
|
1123
|
+
throwIfAborted(request.signal);
|
|
1000
1124
|
const delta = chunk.choices[0]?.delta;
|
|
1001
1125
|
const choice = chunk.choices[0];
|
|
1002
1126
|
if (delta?.content) {
|
|
@@ -1019,38 +1143,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1019
1143
|
}
|
|
1020
1144
|
}
|
|
1021
1145
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
const tcExtraContent = toolCall.extra_content;
|
|
1038
|
-
currentToolCall = {
|
|
1039
|
-
id: toolCall.id,
|
|
1040
|
-
name: toolCall.function?.name || "",
|
|
1041
|
-
arguments: toolCall.function?.arguments || "",
|
|
1042
|
-
...tcExtraContent ? { extra_content: tcExtraContent } : {}
|
|
1043
|
-
};
|
|
1044
|
-
yield {
|
|
1045
|
-
type: "action:start",
|
|
1046
|
-
id: currentToolCall.id,
|
|
1047
|
-
name: currentToolCall.name,
|
|
1048
|
-
...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
|
|
1049
|
-
};
|
|
1050
|
-
} else if (currentToolCall && toolCall.function?.arguments) {
|
|
1051
|
-
currentToolCall.arguments += toolCall.function.arguments;
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1146
|
+
for (const part of delta?.tool_calls ?? []) {
|
|
1147
|
+
const index = part.index ?? 0;
|
|
1148
|
+
const call = toolCalls.get(index) ?? {
|
|
1149
|
+
id: "",
|
|
1150
|
+
name: "",
|
|
1151
|
+
arguments: ""
|
|
1152
|
+
};
|
|
1153
|
+
if (part.id) call.id = part.id;
|
|
1154
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1155
|
+
if (part.function?.arguments)
|
|
1156
|
+
call.arguments += part.function.arguments;
|
|
1157
|
+
if (part.extra_content) call.extra_content = part.extra_content;
|
|
1158
|
+
toolCalls.set(index, call);
|
|
1054
1159
|
}
|
|
1055
1160
|
if (chunk.usage) {
|
|
1056
1161
|
usage = {
|
|
@@ -1059,21 +1164,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1059
1164
|
total_tokens: chunk.usage.total_tokens
|
|
1060
1165
|
};
|
|
1061
1166
|
}
|
|
1062
|
-
if (choice?.finish_reason)
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1167
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1168
|
+
}
|
|
1169
|
+
throwIfAborted(request.signal);
|
|
1170
|
+
if (!finishReason)
|
|
1171
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1172
|
+
if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
|
|
1173
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1174
|
+
}
|
|
1175
|
+
for (const call of toolCalls.values()) {
|
|
1176
|
+
if (!call.id || !call.name)
|
|
1177
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1178
|
+
JSON.parse(call.arguments || "{}");
|
|
1179
|
+
}
|
|
1180
|
+
for (const call of toolCalls.values()) {
|
|
1181
|
+
yield {
|
|
1182
|
+
type: "action:start",
|
|
1183
|
+
id: call.id,
|
|
1184
|
+
name: call.name,
|
|
1185
|
+
...call.extra_content ? { extra_content: call.extra_content } : {}
|
|
1186
|
+
};
|
|
1187
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
1188
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
1077
1189
|
}
|
|
1078
1190
|
if (collectedCitations.length > 0) {
|
|
1079
1191
|
const uniqueCitations = deduplicateCitations(collectedCitations);
|
|
@@ -1082,26 +1194,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1082
1194
|
yield { type: "message:end" };
|
|
1083
1195
|
yield { type: "done", usage };
|
|
1084
1196
|
} catch (error) {
|
|
1085
|
-
yield {
|
|
1086
|
-
type: "error",
|
|
1087
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
1088
|
-
code: `${this.provider.toUpperCase()}_ERROR`
|
|
1089
|
-
};
|
|
1197
|
+
yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
|
|
1090
1198
|
}
|
|
1091
1199
|
}
|
|
1092
1200
|
async complete(request) {
|
|
1201
|
+
throwIfAborted(request.signal);
|
|
1093
1202
|
if (this.shouldUseResponsesApi(request)) {
|
|
1094
1203
|
return this.completeWithResponses(request);
|
|
1095
1204
|
}
|
|
1096
1205
|
const client = await this.getClient();
|
|
1097
1206
|
let messages;
|
|
1098
1207
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
1099
|
-
const sanitized = request.rawMessages
|
|
1100
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
1101
|
-
return { ...msg, content: null };
|
|
1102
|
-
}
|
|
1103
|
-
return msg;
|
|
1104
|
-
});
|
|
1208
|
+
const sanitized = this.formatRawMessages(request.rawMessages);
|
|
1105
1209
|
if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
|
|
1106
1210
|
messages = [
|
|
1107
1211
|
{ role: "system", content: request.systemPrompt },
|
|
@@ -1116,7 +1220,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1116
1220
|
request.systemPrompt
|
|
1117
1221
|
);
|
|
1118
1222
|
}
|
|
1119
|
-
const tools = request.
|
|
1223
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1224
|
+
type: "function",
|
|
1225
|
+
function: {
|
|
1226
|
+
name: tool.name,
|
|
1227
|
+
description: tool.description,
|
|
1228
|
+
parameters: normalizeObjectJsonSchema(
|
|
1229
|
+
tool.inputSchema
|
|
1230
|
+
)
|
|
1231
|
+
}
|
|
1232
|
+
})) : formatTools(request.actions ?? []);
|
|
1120
1233
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
1121
1234
|
const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
|
|
1122
1235
|
type: "function",
|
|
@@ -1131,16 +1244,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1131
1244
|
tools: tools.length > 0 ? tools : void 0,
|
|
1132
1245
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
1133
1246
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
1134
|
-
...
|
|
1247
|
+
...buildOpenAIParameters(
|
|
1135
1248
|
modelIdForCompletePayload,
|
|
1136
1249
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
1137
|
-
request.config?.temperature ?? this.config.temperature
|
|
1250
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1251
|
+
this.parameterOptions(request)
|
|
1138
1252
|
),
|
|
1139
1253
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
1140
1254
|
stream: false
|
|
1141
1255
|
};
|
|
1142
1256
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
1143
|
-
const response = await client.chat.completions.create(payload
|
|
1257
|
+
const response = await client.chat.completions.create(payload, {
|
|
1258
|
+
signal: request.signal
|
|
1259
|
+
});
|
|
1144
1260
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
1145
1261
|
const choice = response.choices?.[0];
|
|
1146
1262
|
const message = choice?.message;
|
|
@@ -1190,6 +1306,411 @@ function createOpenAIAdapter(config) {
|
|
|
1190
1306
|
return new OpenAIAdapter(config);
|
|
1191
1307
|
}
|
|
1192
1308
|
|
|
1309
|
+
// src/providers/openai/provider.ts
|
|
1310
|
+
var OPENAI_MODELS = {
|
|
1311
|
+
// GPT-4o series
|
|
1312
|
+
"gpt-4o": { vision: true, tools: true, jsonMode: true, maxTokens: 128e3 },
|
|
1313
|
+
"gpt-5.2": {
|
|
1314
|
+
vision: true,
|
|
1315
|
+
tools: true,
|
|
1316
|
+
jsonMode: true,
|
|
1317
|
+
maxTokens: 128e3
|
|
1318
|
+
},
|
|
1319
|
+
"gpt-4o-2024-11-20": {
|
|
1320
|
+
vision: true,
|
|
1321
|
+
tools: true,
|
|
1322
|
+
jsonMode: true,
|
|
1323
|
+
maxTokens: 128e3
|
|
1324
|
+
},
|
|
1325
|
+
"gpt-4o-2024-08-06": {
|
|
1326
|
+
vision: true,
|
|
1327
|
+
tools: true,
|
|
1328
|
+
jsonMode: true,
|
|
1329
|
+
maxTokens: 128e3
|
|
1330
|
+
},
|
|
1331
|
+
// GPT-4 Turbo
|
|
1332
|
+
"gpt-4-turbo": {
|
|
1333
|
+
vision: true,
|
|
1334
|
+
tools: true,
|
|
1335
|
+
jsonMode: true,
|
|
1336
|
+
maxTokens: 128e3
|
|
1337
|
+
},
|
|
1338
|
+
"gpt-4-turbo-preview": {
|
|
1339
|
+
vision: false,
|
|
1340
|
+
tools: true,
|
|
1341
|
+
jsonMode: true,
|
|
1342
|
+
maxTokens: 128e3
|
|
1343
|
+
},
|
|
1344
|
+
// GPT-4
|
|
1345
|
+
"gpt-4": { vision: false, tools: true, jsonMode: false, maxTokens: 8192 },
|
|
1346
|
+
"gpt-4-32k": {
|
|
1347
|
+
vision: false,
|
|
1348
|
+
tools: true,
|
|
1349
|
+
jsonMode: false,
|
|
1350
|
+
maxTokens: 32768
|
|
1351
|
+
},
|
|
1352
|
+
// GPT-3.5
|
|
1353
|
+
"gpt-3.5-turbo": {
|
|
1354
|
+
vision: false,
|
|
1355
|
+
tools: true,
|
|
1356
|
+
jsonMode: true,
|
|
1357
|
+
maxTokens: 16385
|
|
1358
|
+
},
|
|
1359
|
+
// O1 series (reasoning)
|
|
1360
|
+
o1: { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
1361
|
+
"o1-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
1362
|
+
"o1-preview": {
|
|
1363
|
+
vision: true,
|
|
1364
|
+
tools: false,
|
|
1365
|
+
jsonMode: false,
|
|
1366
|
+
maxTokens: 128e3
|
|
1367
|
+
},
|
|
1368
|
+
// O3 series
|
|
1369
|
+
"o3-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 }
|
|
1370
|
+
};
|
|
1371
|
+
function openai(modelId, options = {}) {
|
|
1372
|
+
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
1373
|
+
const baseURL = options.baseURL ?? "https://api.openai.com/v1";
|
|
1374
|
+
let client = null;
|
|
1375
|
+
async function getClient() {
|
|
1376
|
+
if (!client) {
|
|
1377
|
+
const { default: OpenAI } = await import('openai');
|
|
1378
|
+
client = new OpenAI({
|
|
1379
|
+
apiKey,
|
|
1380
|
+
baseURL,
|
|
1381
|
+
organization: options.organization,
|
|
1382
|
+
defaultHeaders: options.headers
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
return client;
|
|
1386
|
+
}
|
|
1387
|
+
function parameterOptions(params) {
|
|
1388
|
+
return {
|
|
1389
|
+
...options,
|
|
1390
|
+
reasoningEffort: params.reasoningEffort ?? options.reasoningEffort
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
function useResponses(params) {
|
|
1394
|
+
return useOpenAIResponses(
|
|
1395
|
+
modelId,
|
|
1396
|
+
baseURL,
|
|
1397
|
+
parameterOptions(params),
|
|
1398
|
+
resolveOpenAITransport(options, params.openai),
|
|
1399
|
+
!!params.tools?.length,
|
|
1400
|
+
params.messages.some(
|
|
1401
|
+
(message) => message.role === "assistant" && !!message.providerMetadata?.openai?.response
|
|
1402
|
+
)
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
const responsesAdapter = new OpenAIAdapter({
|
|
1406
|
+
...options,
|
|
1407
|
+
apiKey: apiKey ?? "",
|
|
1408
|
+
model: modelId,
|
|
1409
|
+
baseUrl: baseURL
|
|
1410
|
+
});
|
|
1411
|
+
function responsesRequest(params) {
|
|
1412
|
+
return {
|
|
1413
|
+
messages: [],
|
|
1414
|
+
rawMessages: formatMessagesForOpenAI2(params.messages, true),
|
|
1415
|
+
toolDefinitions: params.tools?.map((raw) => {
|
|
1416
|
+
const tool = raw;
|
|
1417
|
+
if (tool.type !== "function")
|
|
1418
|
+
throw new Error(
|
|
1419
|
+
"Only function tools are supported by this OpenAI generation path"
|
|
1420
|
+
);
|
|
1421
|
+
return {
|
|
1422
|
+
name: tool.function.name,
|
|
1423
|
+
description: tool.function.description ?? "",
|
|
1424
|
+
location: "server",
|
|
1425
|
+
inputSchema: tool.function.parameters
|
|
1426
|
+
};
|
|
1427
|
+
}),
|
|
1428
|
+
config: {
|
|
1429
|
+
openai: {
|
|
1430
|
+
...resolveOpenAITransport(options, params.openai),
|
|
1431
|
+
api: "responses"
|
|
1432
|
+
},
|
|
1433
|
+
temperature: params.temperature,
|
|
1434
|
+
maxTokens: params.maxTokens,
|
|
1435
|
+
responseFormat: params.responseFormat,
|
|
1436
|
+
reasoningEffort: parameterOptions(params).reasoningEffort
|
|
1437
|
+
},
|
|
1438
|
+
signal: params.signal
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
const modelConfig = OPENAI_MODELS[modelId] ?? OPENAI_MODELS["gpt-4o"];
|
|
1442
|
+
return {
|
|
1443
|
+
provider: "openai",
|
|
1444
|
+
modelId,
|
|
1445
|
+
capabilities: {
|
|
1446
|
+
supportsVision: modelConfig.vision,
|
|
1447
|
+
supportsTools: modelConfig.tools,
|
|
1448
|
+
supportsStreaming: true,
|
|
1449
|
+
supportsJsonMode: modelConfig.jsonMode,
|
|
1450
|
+
supportsThinking: false,
|
|
1451
|
+
supportsPDF: false,
|
|
1452
|
+
maxTokens: modelConfig.maxTokens,
|
|
1453
|
+
supportedImageTypes: modelConfig.vision ? ["image/png", "image/jpeg", "image/gif", "image/webp"] : []
|
|
1454
|
+
},
|
|
1455
|
+
async doGenerate(params) {
|
|
1456
|
+
throwIfAborted(params.signal);
|
|
1457
|
+
if (useResponses(params)) {
|
|
1458
|
+
const result = await responsesAdapter.complete(
|
|
1459
|
+
responsesRequest(params)
|
|
1460
|
+
);
|
|
1461
|
+
return {
|
|
1462
|
+
text: result.content,
|
|
1463
|
+
toolCalls: result.toolCalls,
|
|
1464
|
+
finishReason: result.toolCalls.length ? "tool-calls" : result.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
|
|
1465
|
+
usage: result.usage ?? {
|
|
1466
|
+
promptTokens: 0,
|
|
1467
|
+
completionTokens: 0,
|
|
1468
|
+
totalTokens: 0
|
|
1469
|
+
},
|
|
1470
|
+
rawResponse: result.rawResponse,
|
|
1471
|
+
providerMetadata: result.providerMetadata
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
const client2 = await getClient();
|
|
1475
|
+
const messages = formatMessagesForOpenAI2(params.messages);
|
|
1476
|
+
const response = await client2.chat.completions.create(
|
|
1477
|
+
{
|
|
1478
|
+
model: modelId,
|
|
1479
|
+
messages,
|
|
1480
|
+
tools: params.tools,
|
|
1481
|
+
...buildOpenAIParameters(
|
|
1482
|
+
modelId,
|
|
1483
|
+
params.maxTokens,
|
|
1484
|
+
params.temperature,
|
|
1485
|
+
parameterOptions(params)
|
|
1486
|
+
),
|
|
1487
|
+
response_format: toOpenAIResponseFormat(params.responseFormat)
|
|
1488
|
+
},
|
|
1489
|
+
{ signal: params.signal }
|
|
1490
|
+
);
|
|
1491
|
+
const choice = response.choices[0];
|
|
1492
|
+
const message = choice.message;
|
|
1493
|
+
const toolCalls = (message.tool_calls ?? []).map(
|
|
1494
|
+
(tc) => ({
|
|
1495
|
+
id: tc.id,
|
|
1496
|
+
name: tc.function.name,
|
|
1497
|
+
args: JSON.parse(tc.function.arguments || "{}")
|
|
1498
|
+
})
|
|
1499
|
+
);
|
|
1500
|
+
return {
|
|
1501
|
+
text: message.content ?? "",
|
|
1502
|
+
toolCalls,
|
|
1503
|
+
finishReason: mapFinishReason(choice.finish_reason),
|
|
1504
|
+
usage: {
|
|
1505
|
+
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
1506
|
+
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
1507
|
+
totalTokens: response.usage?.total_tokens ?? 0
|
|
1508
|
+
},
|
|
1509
|
+
rawResponse: response
|
|
1510
|
+
};
|
|
1511
|
+
},
|
|
1512
|
+
async *doStream(params) {
|
|
1513
|
+
throwIfAborted(params.signal);
|
|
1514
|
+
if (useResponses(params)) {
|
|
1515
|
+
const calls2 = /* @__PURE__ */ new Map();
|
|
1516
|
+
let hasTools = false;
|
|
1517
|
+
for await (const event of responsesAdapter.stream(
|
|
1518
|
+
responsesRequest(params)
|
|
1519
|
+
)) {
|
|
1520
|
+
switch (event.type) {
|
|
1521
|
+
case "message:delta":
|
|
1522
|
+
yield { type: "text-delta", text: event.content };
|
|
1523
|
+
break;
|
|
1524
|
+
case "action:start":
|
|
1525
|
+
calls2.set(event.id, { name: event.name, args: "" });
|
|
1526
|
+
break;
|
|
1527
|
+
case "action:args": {
|
|
1528
|
+
const call = calls2.get(event.id);
|
|
1529
|
+
if (call) call.args += event.args;
|
|
1530
|
+
break;
|
|
1531
|
+
}
|
|
1532
|
+
case "action:end": {
|
|
1533
|
+
const call = calls2.get(event.id);
|
|
1534
|
+
if (!call)
|
|
1535
|
+
throw new Error("Responses tool call has no matching start");
|
|
1536
|
+
hasTools = true;
|
|
1537
|
+
yield {
|
|
1538
|
+
type: "tool-call",
|
|
1539
|
+
toolCall: {
|
|
1540
|
+
id: event.id,
|
|
1541
|
+
name: call.name,
|
|
1542
|
+
args: JSON.parse(call.args || "{}")
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
calls2.delete(event.id);
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
case "error":
|
|
1549
|
+
throw errorFromEvent(event);
|
|
1550
|
+
case "done":
|
|
1551
|
+
yield {
|
|
1552
|
+
type: "finish",
|
|
1553
|
+
finishReason: hasTools ? "tool-calls" : event.providerMetadata?.openai?.response?.refusals.length ? "content-filter" : "stop",
|
|
1554
|
+
providerMetadata: event.providerMetadata,
|
|
1555
|
+
usage: {
|
|
1556
|
+
promptTokens: event.usage?.prompt_tokens ?? 0,
|
|
1557
|
+
completionTokens: event.usage?.completion_tokens ?? 0,
|
|
1558
|
+
totalTokens: event.usage?.total_tokens ?? 0
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
break;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
const client2 = await getClient();
|
|
1567
|
+
const messages = formatMessagesForOpenAI2(params.messages);
|
|
1568
|
+
const stream = await client2.chat.completions.create(
|
|
1569
|
+
{
|
|
1570
|
+
model: modelId,
|
|
1571
|
+
messages,
|
|
1572
|
+
tools: params.tools,
|
|
1573
|
+
...buildOpenAIParameters(
|
|
1574
|
+
modelId,
|
|
1575
|
+
params.maxTokens,
|
|
1576
|
+
params.temperature,
|
|
1577
|
+
parameterOptions(params)
|
|
1578
|
+
),
|
|
1579
|
+
response_format: toOpenAIResponseFormat(params.responseFormat),
|
|
1580
|
+
stream: true,
|
|
1581
|
+
stream_options: { include_usage: true }
|
|
1582
|
+
},
|
|
1583
|
+
{ signal: params.signal }
|
|
1584
|
+
);
|
|
1585
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1586
|
+
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1587
|
+
let finishReason;
|
|
1588
|
+
for await (const chunk of stream) {
|
|
1589
|
+
throwIfAborted(params.signal);
|
|
1590
|
+
if (chunk.usage) {
|
|
1591
|
+
usage = {
|
|
1592
|
+
promptTokens: chunk.usage.prompt_tokens ?? 0,
|
|
1593
|
+
completionTokens: chunk.usage.completion_tokens ?? 0,
|
|
1594
|
+
totalTokens: chunk.usage.total_tokens ?? (chunk.usage.prompt_tokens ?? 0) + (chunk.usage.completion_tokens ?? 0)
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
const choice = chunk.choices?.[0];
|
|
1598
|
+
if (choice?.delta?.content)
|
|
1599
|
+
yield { type: "text-delta", text: choice.delta.content };
|
|
1600
|
+
for (const part of choice?.delta?.tool_calls ?? []) {
|
|
1601
|
+
const index = part.index ?? 0;
|
|
1602
|
+
const call = toolCalls.get(index) ?? {
|
|
1603
|
+
id: "",
|
|
1604
|
+
name: "",
|
|
1605
|
+
arguments: ""
|
|
1606
|
+
};
|
|
1607
|
+
if (part.id) call.id = part.id;
|
|
1608
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1609
|
+
if (part.function?.arguments)
|
|
1610
|
+
call.arguments += part.function.arguments;
|
|
1611
|
+
toolCalls.set(index, call);
|
|
1612
|
+
}
|
|
1613
|
+
if (choice?.finish_reason)
|
|
1614
|
+
finishReason = mapFinishReason(choice.finish_reason);
|
|
1615
|
+
}
|
|
1616
|
+
throwIfAborted(params.signal);
|
|
1617
|
+
if (!finishReason)
|
|
1618
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1619
|
+
const calls = [...toolCalls.values()].map((call) => {
|
|
1620
|
+
if (!call.id || !call.name)
|
|
1621
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1622
|
+
return {
|
|
1623
|
+
id: call.id,
|
|
1624
|
+
name: call.name,
|
|
1625
|
+
args: JSON.parse(call.arguments || "{}")
|
|
1626
|
+
};
|
|
1627
|
+
});
|
|
1628
|
+
if (calls.length && finishReason !== "tool-calls")
|
|
1629
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1630
|
+
for (const toolCall of calls) yield { type: "tool-call", toolCall };
|
|
1631
|
+
yield { type: "finish", finishReason, usage };
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function mapFinishReason(reason) {
|
|
1636
|
+
switch (reason) {
|
|
1637
|
+
case "stop":
|
|
1638
|
+
return "stop";
|
|
1639
|
+
case "length":
|
|
1640
|
+
return "length";
|
|
1641
|
+
case "tool_calls":
|
|
1642
|
+
case "function_call":
|
|
1643
|
+
return "tool-calls";
|
|
1644
|
+
case "content_filter":
|
|
1645
|
+
return "content-filter";
|
|
1646
|
+
default:
|
|
1647
|
+
return "unknown";
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
function formatMessagesForOpenAI2(messages, responses = false) {
|
|
1651
|
+
return messages.map((msg) => {
|
|
1652
|
+
switch (msg.role) {
|
|
1653
|
+
case "system":
|
|
1654
|
+
return { role: "system", content: msg.content };
|
|
1655
|
+
case "user":
|
|
1656
|
+
if (typeof msg.content === "string") {
|
|
1657
|
+
return { role: "user", content: msg.content };
|
|
1658
|
+
}
|
|
1659
|
+
return {
|
|
1660
|
+
role: "user",
|
|
1661
|
+
content: msg.content.map((part) => {
|
|
1662
|
+
if (part.type === "text") {
|
|
1663
|
+
return { type: "text", text: part.text };
|
|
1664
|
+
}
|
|
1665
|
+
if (part.type === "image") {
|
|
1666
|
+
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
1667
|
+
const url = /^(data:|https?:\/\/)/.test(imageData) ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
1668
|
+
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
1669
|
+
}
|
|
1670
|
+
if (part.type === "file") {
|
|
1671
|
+
if (!responses)
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
"File inputs require the Responses API on this OpenAI path"
|
|
1674
|
+
);
|
|
1675
|
+
return /^https?:\/\//.test(part.data) ? { type: "input_file", file_url: part.data } : {
|
|
1676
|
+
type: "input_file",
|
|
1677
|
+
filename: part.filename ?? "document.pdf",
|
|
1678
|
+
file_data: part.data.startsWith("data:") ? part.data : `data:${part.mimeType};base64,${part.data}`
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
throw new Error("Unsupported OpenAI message content");
|
|
1682
|
+
})
|
|
1683
|
+
};
|
|
1684
|
+
case "assistant":
|
|
1685
|
+
const assistantMsg = {
|
|
1686
|
+
role: "assistant",
|
|
1687
|
+
content: msg.content,
|
|
1688
|
+
...responses && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
|
|
1689
|
+
};
|
|
1690
|
+
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
1691
|
+
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
1692
|
+
id: tc.id,
|
|
1693
|
+
type: "function",
|
|
1694
|
+
function: {
|
|
1695
|
+
name: tc.name,
|
|
1696
|
+
arguments: JSON.stringify(tc.args)
|
|
1697
|
+
}
|
|
1698
|
+
}));
|
|
1699
|
+
}
|
|
1700
|
+
return assistantMsg;
|
|
1701
|
+
case "tool":
|
|
1702
|
+
return {
|
|
1703
|
+
role: "tool",
|
|
1704
|
+
tool_call_id: msg.toolCallId,
|
|
1705
|
+
content: responses ? msg.content : textToolOutput(msg.modelOutput, msg.content),
|
|
1706
|
+
...responses && msg.modelOutput ? { modelOutput: msg.modelOutput } : {}
|
|
1707
|
+
};
|
|
1708
|
+
default:
|
|
1709
|
+
return msg;
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1193
1714
|
// src/providers/types.ts
|
|
1194
1715
|
function createCallableProvider(providerFn, properties) {
|
|
1195
1716
|
Object.defineProperty(providerFn, "name", {
|
|
@@ -1206,6 +1727,394 @@ function createCallableProvider(providerFn, properties) {
|
|
|
1206
1727
|
return providerFn;
|
|
1207
1728
|
}
|
|
1208
1729
|
|
|
1730
|
+
// src/core/audio.ts
|
|
1731
|
+
var audioErrorMarker = /* @__PURE__ */ Symbol.for("@yourgpt/llm-sdk/AudioAPIError");
|
|
1732
|
+
var _a;
|
|
1733
|
+
var AudioAPIError = class extends Error {
|
|
1734
|
+
constructor(message, code, status, requestId) {
|
|
1735
|
+
super(message);
|
|
1736
|
+
this.code = code;
|
|
1737
|
+
this.status = status;
|
|
1738
|
+
this.requestId = requestId;
|
|
1739
|
+
this[_a] = true;
|
|
1740
|
+
this.name = "AudioAPIError";
|
|
1741
|
+
}
|
|
1742
|
+
static [(_a = audioErrorMarker, Symbol.hasInstance)](value) {
|
|
1743
|
+
return !!value && typeof value === "object" && value[audioErrorMarker] === true;
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
|
|
1747
|
+
// src/providers/openai/audio.ts
|
|
1748
|
+
var formats = {
|
|
1749
|
+
mp3: "audio/mpeg",
|
|
1750
|
+
opus: "audio/ogg",
|
|
1751
|
+
aac: "audio/aac",
|
|
1752
|
+
flac: "audio/flac",
|
|
1753
|
+
wav: "audio/wav",
|
|
1754
|
+
pcm: "audio/pcm"
|
|
1755
|
+
};
|
|
1756
|
+
var extensions = {
|
|
1757
|
+
"audio/mpeg": "mp3",
|
|
1758
|
+
"audio/mp3": "mp3",
|
|
1759
|
+
"audio/mp4": "m4a",
|
|
1760
|
+
"video/mp4": "mp4",
|
|
1761
|
+
"audio/wav": "wav",
|
|
1762
|
+
"audio/x-wav": "wav",
|
|
1763
|
+
"audio/webm": "webm",
|
|
1764
|
+
"video/webm": "webm",
|
|
1765
|
+
"audio/ogg": "ogg",
|
|
1766
|
+
"audio/flac": "flac",
|
|
1767
|
+
"audio/x-flac": "flac"
|
|
1768
|
+
};
|
|
1769
|
+
var MAX_AUDIO = 25e6;
|
|
1770
|
+
function hasAudioHeader(data, format) {
|
|
1771
|
+
const head = Buffer.from(data.subarray(0, 12)).toString("latin1");
|
|
1772
|
+
if (format === "wav")
|
|
1773
|
+
return head.startsWith("RIFF") && head.slice(8, 12) === "WAVE";
|
|
1774
|
+
if (format === "mp3" || format === "mpeg" || format === "mpga")
|
|
1775
|
+
return head.startsWith("ID3") || data[0] === 255 && (data[1] & 224) === 224;
|
|
1776
|
+
if (format === "opus" || format === "ogg") return head.startsWith("OggS");
|
|
1777
|
+
if (format === "flac") return head.startsWith("fLaC");
|
|
1778
|
+
if (format === "aac") return data[0] === 255 && (data[1] & 246) === 240;
|
|
1779
|
+
if (format === "webm")
|
|
1780
|
+
return data[0] === 26 && data[1] === 69 && data[2] === 223 && data[3] === 163;
|
|
1781
|
+
if (format === "m4a" || format === "mp4") return head.slice(4, 8) === "ftyp";
|
|
1782
|
+
return format === "pcm" && data.length > 0 && data.length % 2 === 0;
|
|
1783
|
+
}
|
|
1784
|
+
function invalid(message) {
|
|
1785
|
+
throw new AudioAPIError(message, "invalid_input");
|
|
1786
|
+
}
|
|
1787
|
+
function unsupported(message) {
|
|
1788
|
+
throw new AudioAPIError(message, "unsupported_option");
|
|
1789
|
+
}
|
|
1790
|
+
function providerOptions(value, allowed) {
|
|
1791
|
+
for (const [provider, options] of Object.entries(value ?? {})) {
|
|
1792
|
+
if (provider !== "openai")
|
|
1793
|
+
unsupported(
|
|
1794
|
+
`Provider options for ${provider} cannot be used with OpenAI audio`
|
|
1795
|
+
);
|
|
1796
|
+
for (const key of Object.keys(options))
|
|
1797
|
+
if (!allowed.includes(key))
|
|
1798
|
+
unsupported(`Unsupported OpenAI audio option: ${key}`);
|
|
1799
|
+
}
|
|
1800
|
+
return value?.openai ?? {};
|
|
1801
|
+
}
|
|
1802
|
+
async function readBody(response, limit) {
|
|
1803
|
+
if (Number(response.headers.get("content-length")) > limit) {
|
|
1804
|
+
await response.body?.cancel();
|
|
1805
|
+
throw new AudioAPIError(
|
|
1806
|
+
"Audio API response exceeds the size limit",
|
|
1807
|
+
"invalid_response"
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
const reader = response.body?.getReader();
|
|
1811
|
+
if (!reader) return new Uint8Array();
|
|
1812
|
+
const chunks = [];
|
|
1813
|
+
let length = 0;
|
|
1814
|
+
try {
|
|
1815
|
+
while (true) {
|
|
1816
|
+
const { done, value } = await reader.read();
|
|
1817
|
+
if (done) break;
|
|
1818
|
+
length += value.length;
|
|
1819
|
+
if (length > limit) {
|
|
1820
|
+
await reader.cancel();
|
|
1821
|
+
throw new AudioAPIError(
|
|
1822
|
+
"Audio API response exceeds the size limit",
|
|
1823
|
+
"invalid_response"
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
chunks.push(value);
|
|
1827
|
+
}
|
|
1828
|
+
} finally {
|
|
1829
|
+
reader.releaseLock();
|
|
1830
|
+
}
|
|
1831
|
+
const data = new Uint8Array(length);
|
|
1832
|
+
let offset = 0;
|
|
1833
|
+
for (const chunk of chunks) {
|
|
1834
|
+
data.set(chunk, offset);
|
|
1835
|
+
offset += chunk.length;
|
|
1836
|
+
}
|
|
1837
|
+
return data;
|
|
1838
|
+
}
|
|
1839
|
+
function transport(config) {
|
|
1840
|
+
return async (path, body, signal) => {
|
|
1841
|
+
signal?.throwIfAborted();
|
|
1842
|
+
const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
|
|
1843
|
+
if (!apiKey) invalid("OpenAI audio requires an API key on the server");
|
|
1844
|
+
const headers = {
|
|
1845
|
+
Authorization: `Bearer ${apiKey}`
|
|
1846
|
+
};
|
|
1847
|
+
if (typeof body === "string") headers["Content-Type"] = "application/json";
|
|
1848
|
+
if (config.organization)
|
|
1849
|
+
headers["OpenAI-Organization"] = config.organization;
|
|
1850
|
+
if (config.project) headers["OpenAI-Project"] = config.project;
|
|
1851
|
+
let response;
|
|
1852
|
+
try {
|
|
1853
|
+
response = await (config.fetch ?? globalThis.fetch)(
|
|
1854
|
+
`${(config.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "")}${path}`,
|
|
1855
|
+
{ method: "POST", headers, body, signal, redirect: "error" }
|
|
1856
|
+
);
|
|
1857
|
+
} catch {
|
|
1858
|
+
signal?.throwIfAborted();
|
|
1859
|
+
throw new AudioAPIError("OpenAI audio request failed", "network_error");
|
|
1860
|
+
}
|
|
1861
|
+
if (!response.ok) {
|
|
1862
|
+
await response.body?.cancel();
|
|
1863
|
+
throw new AudioAPIError(
|
|
1864
|
+
`OpenAI audio request failed (HTTP ${response.status})`,
|
|
1865
|
+
"provider_error",
|
|
1866
|
+
response.status,
|
|
1867
|
+
response.headers.get("x-request-id") ?? void 0
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
return response;
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function openaiSpeech(modelId = "gpt-4o-mini-tts", config = {}) {
|
|
1874
|
+
const request = transport(config);
|
|
1875
|
+
return {
|
|
1876
|
+
provider: "openai",
|
|
1877
|
+
modelId,
|
|
1878
|
+
async doGenerate(options) {
|
|
1879
|
+
const {
|
|
1880
|
+
text,
|
|
1881
|
+
voice = "alloy",
|
|
1882
|
+
outputFormat = "mp3",
|
|
1883
|
+
speed,
|
|
1884
|
+
instructions,
|
|
1885
|
+
signal
|
|
1886
|
+
} = options;
|
|
1887
|
+
signal?.throwIfAborted();
|
|
1888
|
+
providerOptions(options.providerOptions, []);
|
|
1889
|
+
if (typeof text !== "string" || !text.trim() || text.length > 4096)
|
|
1890
|
+
invalid("Speech text must contain 1\u20134096 characters");
|
|
1891
|
+
if (!Object.prototype.hasOwnProperty.call(formats, outputFormat))
|
|
1892
|
+
unsupported("Unsupported OpenAI speech output format");
|
|
1893
|
+
if (typeof voice !== "string" || !voice.trim())
|
|
1894
|
+
invalid("Speech voice must not be empty");
|
|
1895
|
+
if (speed !== void 0 && (!Number.isFinite(speed) || speed < 0.25 || speed > 4))
|
|
1896
|
+
invalid("Speech speed must be between 0.25 and 4");
|
|
1897
|
+
if (instructions !== void 0) {
|
|
1898
|
+
if (typeof instructions !== "string" || instructions.length > 4096)
|
|
1899
|
+
invalid(
|
|
1900
|
+
"Speech instructions must be a string of at most 4096 characters"
|
|
1901
|
+
);
|
|
1902
|
+
if (modelId === "tts-1" || modelId === "tts-1-hd")
|
|
1903
|
+
unsupported("Instructions are not supported by tts-1 or tts-1-hd");
|
|
1904
|
+
}
|
|
1905
|
+
const response = await request(
|
|
1906
|
+
"/audio/speech",
|
|
1907
|
+
JSON.stringify({
|
|
1908
|
+
model: modelId,
|
|
1909
|
+
input: text,
|
|
1910
|
+
voice,
|
|
1911
|
+
response_format: outputFormat,
|
|
1912
|
+
speed,
|
|
1913
|
+
instructions
|
|
1914
|
+
}),
|
|
1915
|
+
signal
|
|
1916
|
+
);
|
|
1917
|
+
const mediaType = response.headers.get("content-type")?.split(";")[0].trim();
|
|
1918
|
+
if (mediaType && !mediaType.startsWith("audio/") && mediaType !== "application/octet-stream") {
|
|
1919
|
+
await response.body?.cancel();
|
|
1920
|
+
throw new AudioAPIError(
|
|
1921
|
+
"Speech endpoint returned a non-audio response",
|
|
1922
|
+
"invalid_response"
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
const data = await readBody(response, MAX_AUDIO);
|
|
1926
|
+
signal?.throwIfAborted();
|
|
1927
|
+
if (!data.length || !hasAudioHeader(data, outputFormat))
|
|
1928
|
+
throw new AudioAPIError(
|
|
1929
|
+
"Speech endpoint returned empty or invalid audio",
|
|
1930
|
+
"invalid_response"
|
|
1931
|
+
);
|
|
1932
|
+
return {
|
|
1933
|
+
audio: {
|
|
1934
|
+
uint8Array: data,
|
|
1935
|
+
get base64() {
|
|
1936
|
+
return Buffer.from(data).toString("base64");
|
|
1937
|
+
},
|
|
1938
|
+
mediaType: formats[outputFormat],
|
|
1939
|
+
format: outputFormat
|
|
1940
|
+
},
|
|
1941
|
+
warnings: [],
|
|
1942
|
+
response: {
|
|
1943
|
+
provider: "openai",
|
|
1944
|
+
modelId,
|
|
1945
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
1946
|
+
status: response.status
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
async function audioBytes(options) {
|
|
1953
|
+
const blob = typeof Blob !== "undefined" && options.audio instanceof Blob ? options.audio : void 0;
|
|
1954
|
+
if (blob && blob.size > MAX_AUDIO)
|
|
1955
|
+
invalid("Transcription file exceeds 25 MB");
|
|
1956
|
+
const declared = options.mediaType?.split(";")[0].trim();
|
|
1957
|
+
const blobType = blob?.type.split(";")[0].trim();
|
|
1958
|
+
if (declared && blobType && declared !== blobType)
|
|
1959
|
+
invalid("Transcription mediaType does not match the Blob type");
|
|
1960
|
+
const mediaType = declared || blobType || "";
|
|
1961
|
+
const extension = extensions[mediaType];
|
|
1962
|
+
if (!extension)
|
|
1963
|
+
unsupported(
|
|
1964
|
+
"Unsupported transcription media type; provide an encoded audio file"
|
|
1965
|
+
);
|
|
1966
|
+
let bytes;
|
|
1967
|
+
if (blob) bytes = new Uint8Array(await blob.arrayBuffer());
|
|
1968
|
+
else if (options.audio instanceof Uint8Array) bytes = options.audio;
|
|
1969
|
+
else if (options.audio instanceof ArrayBuffer)
|
|
1970
|
+
bytes = new Uint8Array(options.audio);
|
|
1971
|
+
else if (typeof options.audio === "string") {
|
|
1972
|
+
const base64 = options.audio;
|
|
1973
|
+
if (base64.length > MAX_AUDIO * 4 / 3 + 4)
|
|
1974
|
+
invalid("Transcription file exceeds 25 MB");
|
|
1975
|
+
if (!base64 || base64.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64))
|
|
1976
|
+
invalid(
|
|
1977
|
+
"Audio must be bytes, a Blob, or valid base64; URLs are not fetched"
|
|
1978
|
+
);
|
|
1979
|
+
bytes = Buffer.from(base64, "base64");
|
|
1980
|
+
if (Buffer.from(bytes).toString("base64") !== base64)
|
|
1981
|
+
invalid("Invalid audio base64");
|
|
1982
|
+
} else invalid("Audio must be bytes, a Blob, or base64");
|
|
1983
|
+
if (!bytes.length || bytes.length > MAX_AUDIO)
|
|
1984
|
+
invalid("Transcription file must contain 1 byte to 25 MB");
|
|
1985
|
+
if (!hasAudioHeader(bytes, extension))
|
|
1986
|
+
invalid("Audio header does not match the declared media type");
|
|
1987
|
+
return { bytes, mediaType, extension };
|
|
1988
|
+
}
|
|
1989
|
+
function openaiTranscription(modelId = "gpt-4o-mini-transcribe", config = {}) {
|
|
1990
|
+
const request = transport(config);
|
|
1991
|
+
return {
|
|
1992
|
+
provider: "openai",
|
|
1993
|
+
modelId,
|
|
1994
|
+
async doTranscribe(options) {
|
|
1995
|
+
options.signal?.throwIfAborted();
|
|
1996
|
+
if (modelId.includes("realtime"))
|
|
1997
|
+
unsupported(
|
|
1998
|
+
"Realtime models require a realtime session; use a file transcription model"
|
|
1999
|
+
);
|
|
2000
|
+
const opts = providerOptions(options.providerOptions, [
|
|
2001
|
+
"timestampGranularities",
|
|
2002
|
+
"includeLogprobs",
|
|
2003
|
+
"chunkingStrategy"
|
|
2004
|
+
]);
|
|
2005
|
+
const timestamps = opts.timestampGranularities;
|
|
2006
|
+
if (timestamps !== void 0 && (!Array.isArray(timestamps) || !timestamps.length || timestamps.some((x) => x !== "word" && x !== "segment")))
|
|
2007
|
+
invalid("timestampGranularities must contain word or segment");
|
|
2008
|
+
if (timestamps !== void 0 && modelId !== "whisper-1")
|
|
2009
|
+
unsupported("Timestamp granularities require whisper-1");
|
|
2010
|
+
if (opts.includeLogprobs !== void 0 && typeof opts.includeLogprobs !== "boolean")
|
|
2011
|
+
invalid("includeLogprobs must be boolean");
|
|
2012
|
+
if (opts.includeLogprobs && !/^gpt-4o-(mini-)?transcribe(?:-\d{4}-\d{2}-\d{2})?$/.test(modelId))
|
|
2013
|
+
unsupported(
|
|
2014
|
+
"Log probabilities require a supported gpt-4o transcription model"
|
|
2015
|
+
);
|
|
2016
|
+
const diarize = modelId === "gpt-4o-transcribe-diarize";
|
|
2017
|
+
if (opts.chunkingStrategy !== void 0 && opts.chunkingStrategy !== "auto")
|
|
2018
|
+
unsupported("Only automatic audio chunking is supported");
|
|
2019
|
+
if (diarize && options.prompt !== void 0)
|
|
2020
|
+
unsupported("Prompts are not supported by the diarization model");
|
|
2021
|
+
if (options.language !== void 0 && !/^[a-z]{2}$/.test(options.language))
|
|
2022
|
+
invalid("language must be a two-letter ISO-639-1 code");
|
|
2023
|
+
if (options.prompt !== void 0 && typeof options.prompt !== "string")
|
|
2024
|
+
invalid("Transcription prompt must be a string");
|
|
2025
|
+
const { bytes, mediaType, extension } = await audioBytes(options);
|
|
2026
|
+
options.signal?.throwIfAborted();
|
|
2027
|
+
const fileName = options.filename ?? `audio.${extension}`;
|
|
2028
|
+
if (!/^[^/\\\r\n\x00]+\.[a-z0-9]+$/i.test(fileName))
|
|
2029
|
+
invalid("Transcription filename must be a filename with an extension");
|
|
2030
|
+
const allowedExt = mediaType === "audio/mpeg" ? ["mp3", "mpeg", "mpga"] : mediaType === "audio/mp4" ? ["m4a", "mp4"] : [extension];
|
|
2031
|
+
if (!allowedExt.includes(fileName.split(".").at(-1).toLowerCase()))
|
|
2032
|
+
invalid("Transcription filename extension does not match mediaType");
|
|
2033
|
+
const form = new FormData();
|
|
2034
|
+
form.append(
|
|
2035
|
+
"file",
|
|
2036
|
+
new Blob([new Uint8Array(bytes)], { type: mediaType }),
|
|
2037
|
+
fileName
|
|
2038
|
+
);
|
|
2039
|
+
form.append("model", modelId);
|
|
2040
|
+
form.append(
|
|
2041
|
+
"response_format",
|
|
2042
|
+
diarize ? "diarized_json" : timestamps ? "verbose_json" : "json"
|
|
2043
|
+
);
|
|
2044
|
+
if (options.language) form.append("language", options.language);
|
|
2045
|
+
if (options.prompt) form.append("prompt", options.prompt);
|
|
2046
|
+
if (timestamps)
|
|
2047
|
+
for (const value2 of timestamps)
|
|
2048
|
+
form.append("timestamp_granularities[]", value2);
|
|
2049
|
+
if (opts.includeLogprobs) form.append("include[]", "logprobs");
|
|
2050
|
+
if (diarize || opts.chunkingStrategy)
|
|
2051
|
+
form.append("chunking_strategy", "auto");
|
|
2052
|
+
const response = await request(
|
|
2053
|
+
"/audio/transcriptions",
|
|
2054
|
+
form,
|
|
2055
|
+
options.signal
|
|
2056
|
+
);
|
|
2057
|
+
let value;
|
|
2058
|
+
try {
|
|
2059
|
+
value = JSON.parse(
|
|
2060
|
+
Buffer.from(await readBody(response, 5 * 1024 * 1024)).toString(
|
|
2061
|
+
"utf8"
|
|
2062
|
+
)
|
|
2063
|
+
);
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
options.signal?.throwIfAborted();
|
|
2066
|
+
if (error instanceof AudioAPIError) throw error;
|
|
2067
|
+
throw new AudioAPIError(
|
|
2068
|
+
"Transcription endpoint returned invalid JSON",
|
|
2069
|
+
"invalid_response"
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
options.signal?.throwIfAborted();
|
|
2073
|
+
if (!value || typeof value.text !== "string")
|
|
2074
|
+
throw new AudioAPIError(
|
|
2075
|
+
"Transcription response is missing text",
|
|
2076
|
+
"invalid_response"
|
|
2077
|
+
);
|
|
2078
|
+
function timed(items, word = false) {
|
|
2079
|
+
if (items === void 0) return void 0;
|
|
2080
|
+
if (!Array.isArray(items) || items.some(
|
|
2081
|
+
(x) => !x || typeof x[word ? "word" : "text"] !== "string" || !Number.isFinite(x.start) || !Number.isFinite(x.end) || x.start < 0 || x.end < x.start
|
|
2082
|
+
))
|
|
2083
|
+
throw new AudioAPIError(
|
|
2084
|
+
"Invalid transcription timestamps",
|
|
2085
|
+
"invalid_response"
|
|
2086
|
+
);
|
|
2087
|
+
return items.map(
|
|
2088
|
+
(x) => word ? { word: x.word, start: x.start, end: x.end } : {
|
|
2089
|
+
text: x.text,
|
|
2090
|
+
start: x.start,
|
|
2091
|
+
end: x.end,
|
|
2092
|
+
...typeof x.speaker === "string" ? { speaker: x.speaker } : {}
|
|
2093
|
+
}
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
return {
|
|
2097
|
+
text: value.text,
|
|
2098
|
+
language: typeof value.language === "string" ? value.language : void 0,
|
|
2099
|
+
durationSeconds: typeof value.duration === "number" ? value.duration : void 0,
|
|
2100
|
+
segments: timed(value.segments),
|
|
2101
|
+
words: timed(value.words, true),
|
|
2102
|
+
usage: value.usage,
|
|
2103
|
+
providerMetadata: {
|
|
2104
|
+
openai: { ...value.logprobs ? { logprobs: value.logprobs } : {} }
|
|
2105
|
+
},
|
|
2106
|
+
warnings: [],
|
|
2107
|
+
response: {
|
|
2108
|
+
provider: "openai",
|
|
2109
|
+
modelId,
|
|
2110
|
+
status: response.status,
|
|
2111
|
+
requestId: response.headers.get("x-request-id") ?? void 0
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
|
|
1209
2118
|
// src/providers/openai/index.ts
|
|
1210
2119
|
var OPENAI_MODELS2 = {
|
|
1211
2120
|
// GPT-4o series
|
|
@@ -1320,7 +2229,12 @@ function createOpenAI(config = {}) {
|
|
|
1320
2229
|
return createOpenAIAdapter({
|
|
1321
2230
|
apiKey,
|
|
1322
2231
|
model: modelId,
|
|
1323
|
-
baseUrl: config.baseUrl
|
|
2232
|
+
baseUrl: config.baseUrl,
|
|
2233
|
+
reasoningEffort: config.reasoningEffort,
|
|
2234
|
+
api: config.api,
|
|
2235
|
+
responses: config.responses,
|
|
2236
|
+
parameterPolicy: config.parameterPolicy,
|
|
2237
|
+
onWarning: config.onWarning
|
|
1324
2238
|
});
|
|
1325
2239
|
};
|
|
1326
2240
|
const getCapabilities = (modelId) => {
|
|
@@ -1352,5 +2266,7 @@ exports.createOpenAI = createOpenAI;
|
|
|
1352
2266
|
exports.createOpenAIModel = openai;
|
|
1353
2267
|
exports.createOpenAIProvider = createOpenAIProvider;
|
|
1354
2268
|
exports.openai = openai;
|
|
2269
|
+
exports.openaiSpeech = openaiSpeech;
|
|
2270
|
+
exports.openaiTranscription = openaiTranscription;
|
|
1355
2271
|
//# sourceMappingURL=index.js.map
|
|
1356
2272
|
//# sourceMappingURL=index.js.map
|