@yourgpt/llm-sdk 2.5.1-beta.6 → 2.6.0-beta.1
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 +77 -0
- package/dist/adapters/index.d.mts +54 -5
- package/dist/adapters/index.d.ts +54 -5
- package/dist/adapters/index.js +466 -169
- package/dist/adapters/index.mjs +466 -169
- package/dist/{base-C58Dsr9p.d.ts → base-BdTpUAWI.d.ts} +2 -1
- package/dist/{base-tNgbBaSo.d.mts → base-CkeT7u-O.d.mts} +2 -1
- 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 +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +89 -30
- package/dist/index.mjs +89 -30
- package/dist/providers/anthropic/index.d.mts +3 -3
- package/dist/providers/anthropic/index.d.ts +3 -3
- package/dist/providers/azure/index.d.mts +3 -3
- package/dist/providers/azure/index.d.ts +3 -3
- package/dist/providers/azure/index.js +8 -3
- package/dist/providers/azure/index.mjs +8 -3
- package/dist/providers/fireworks/index.d.mts +3 -3
- package/dist/providers/fireworks/index.d.ts +3 -3
- package/dist/providers/fireworks/index.js +466 -169
- package/dist/providers/fireworks/index.mjs +466 -169
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +466 -169
- package/dist/providers/google/index.mjs +466 -169
- package/dist/providers/ollama/index.d.mts +4 -4
- package/dist/providers/ollama/index.d.ts +4 -4
- package/dist/providers/openai/index.d.mts +6 -23
- package/dist/providers/openai/index.d.ts +6 -23
- package/dist/providers/openai/index.js +912 -500
- package/dist/providers/openai/index.mjs +912 -500
- package/dist/providers/openrouter/index.d.mts +3 -3
- package/dist/providers/openrouter/index.d.ts +3 -3
- package/dist/providers/openrouter/index.js +466 -169
- package/dist/providers/openrouter/index.mjs +466 -169
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +466 -169
- package/dist/providers/togetherai/index.mjs +466 -169
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +466 -169
- package/dist/providers/xai/index.mjs +466 -169
- package/dist/{types-BkQCSiIt.d.mts → types-BW3csYo5.d.mts} +22 -1
- package/dist/{types-BkQCSiIt.d.ts → types-BW3csYo5.d.ts} +22 -1
- package/dist/{types-B6dhnguR.d.mts → types-BWRYIvl_.d.mts} +11 -4
- package/dist/{types-CCxPmkmK.d.ts → types-CpXcT-tY.d.ts} +11 -4
- package/dist/{types-BQ31QIsA.d.ts → types-DA54bSmX.d.ts} +3 -11
- package/dist/{types-BSSiJW2o.d.mts → types-NOswlhm9.d.mts} +3 -11
- package/dist/yourgpt/index.d.mts +1 -1
- package/dist/yourgpt/index.d.ts +1 -1
- package/package.json +115 -48
- package/LICENSE +0 -21
|
@@ -1,5 +1,98 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/core/openai-parameters.ts
|
|
4
|
+
function capabilities(model) {
|
|
5
|
+
const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
|
|
6
|
+
const major = Number(gpt?.[1] ?? 0);
|
|
7
|
+
const minor = Number(gpt?.[2] ?? 0);
|
|
8
|
+
const chat = gpt?.[3]?.startsWith("chat") ?? false;
|
|
9
|
+
const reasoning = /^o\d+(?:-|$)/i.test(model) || major >= 5 && !chat;
|
|
10
|
+
const supportsNone = major === 5 && minor >= 1 && !chat || /^gpt-6-(?:sol|luna)(?:-\d{4}-\d{2}-\d{2})?$/i.test(model);
|
|
11
|
+
return { major, minor, reasoning, supportsNone };
|
|
12
|
+
}
|
|
13
|
+
function requiresOpenAIResponses(model, baseUrl, options = {}) {
|
|
14
|
+
if (options.parameterPolicy === "passthrough") return false;
|
|
15
|
+
const c = capabilities(model);
|
|
16
|
+
if (!c.reasoning || options.reasoningEffort === "none") return false;
|
|
17
|
+
if (!(c.major >= 6 || c.major === 5 && c.minor >= 4)) return false;
|
|
18
|
+
if (!baseUrl) return true;
|
|
19
|
+
try {
|
|
20
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function buildOpenAIParameters(model, maxTokens, temperature, options = {}, api = "chat") {
|
|
26
|
+
const c = capabilities(model ?? "");
|
|
27
|
+
const normalize = options.parameterPolicy !== "passthrough";
|
|
28
|
+
const effort = options.reasoningEffort;
|
|
29
|
+
if (normalize && c.reasoning && effort === "none" && !c.supportsNone) {
|
|
30
|
+
throw new Error(`${model} does not support reasoningEffort: none`);
|
|
31
|
+
}
|
|
32
|
+
if (normalize && c.major >= 6 && effort === "minimal") {
|
|
33
|
+
throw new Error(`${model} does not support reasoningEffort: minimal`);
|
|
34
|
+
}
|
|
35
|
+
const result = {
|
|
36
|
+
[api === "responses" ? "max_output_tokens" : normalize && c.reasoning ? "max_completion_tokens" : "max_tokens"]: maxTokens
|
|
37
|
+
};
|
|
38
|
+
if (effort !== void 0) {
|
|
39
|
+
if (api === "responses") result.reasoning = { effort };
|
|
40
|
+
else result.reasoning_effort = effort;
|
|
41
|
+
}
|
|
42
|
+
if (temperature !== void 0) {
|
|
43
|
+
if (!normalize || !c.reasoning || effort === "none" && c.supportsNone) {
|
|
44
|
+
result.temperature = temperature;
|
|
45
|
+
} else {
|
|
46
|
+
(options.onWarning ?? console.warn)(
|
|
47
|
+
`[llm-sdk] ${model}: omitted temperature because reasoning is enabled.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/core/provider-error.ts
|
|
55
|
+
function providerErrorEvent(error, code) {
|
|
56
|
+
const details = error ?? {};
|
|
57
|
+
const event = {
|
|
58
|
+
type: "error",
|
|
59
|
+
message: error instanceof Error ? error.message : String(error),
|
|
60
|
+
code,
|
|
61
|
+
...typeof details.status === "number" ? { status: details.status } : {},
|
|
62
|
+
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
63
|
+
...typeof details.param === "string" ? { param: details.param } : {},
|
|
64
|
+
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
65
|
+
};
|
|
66
|
+
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
67
|
+
return event;
|
|
68
|
+
}
|
|
69
|
+
function errorFromEvent(event) {
|
|
70
|
+
if (event.cause instanceof Error) return event.cause;
|
|
71
|
+
return Object.assign(new Error(event.message), {
|
|
72
|
+
status: event.status,
|
|
73
|
+
code: event.providerCode ?? event.code,
|
|
74
|
+
param: event.param,
|
|
75
|
+
request_id: event.requestId
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function throwIfAborted(signal) {
|
|
79
|
+
if (!signal?.aborted) return;
|
|
80
|
+
const error = new Error("The operation was aborted");
|
|
81
|
+
error.name = "AbortError";
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/core/utils.ts
|
|
86
|
+
function generateId(prefix = "id") {
|
|
87
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
88
|
+
}
|
|
89
|
+
function generateMessageId() {
|
|
90
|
+
return generateId("msg");
|
|
91
|
+
}
|
|
92
|
+
function generateToolCallId() {
|
|
93
|
+
return generateId("call");
|
|
94
|
+
}
|
|
95
|
+
|
|
3
96
|
// src/adapters/base.ts
|
|
4
97
|
function stringifyForDebug(value) {
|
|
5
98
|
return JSON.stringify(
|
|
@@ -91,25 +184,20 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
91
184
|
(value) => typeof value === "string"
|
|
92
185
|
) : [];
|
|
93
186
|
normalized.required = Array.from(/* @__PURE__ */ new Set([...required, ...propertyKeys]));
|
|
94
|
-
|
|
95
|
-
normalized.additionalProperties = false;
|
|
96
|
-
}
|
|
187
|
+
normalized.additionalProperties = false;
|
|
97
188
|
} else if (type === "array" && normalized.items && typeof normalized.items === "object") {
|
|
98
189
|
normalized.items = normalizeObjectJsonSchema(
|
|
99
190
|
normalized.items
|
|
100
191
|
);
|
|
101
192
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
function buildOpenAITokenParams(modelId, maxTokens, temperature) {
|
|
109
|
-
if (isOpenAIReasoningModel(modelId)) {
|
|
110
|
-
return { max_completion_tokens: maxTokens };
|
|
193
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
194
|
+
const branches = normalized[keyword];
|
|
195
|
+
if (!Array.isArray(branches)) continue;
|
|
196
|
+
normalized[keyword] = branches.map(
|
|
197
|
+
(branch) => branch && typeof branch === "object" ? normalizeObjectJsonSchema(branch) : branch
|
|
198
|
+
);
|
|
111
199
|
}
|
|
112
|
-
return
|
|
200
|
+
return normalized;
|
|
113
201
|
}
|
|
114
202
|
function toOpenAIResponseFormat(rf) {
|
|
115
203
|
if (!rf) return void 0;
|
|
@@ -230,363 +318,210 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
230
318
|
return formatted;
|
|
231
319
|
}
|
|
232
320
|
|
|
233
|
-
// src/
|
|
234
|
-
var
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
vision: true,
|
|
251
|
-
tools: true,
|
|
252
|
-
jsonMode: true,
|
|
253
|
-
maxTokens: 128e3
|
|
254
|
-
},
|
|
255
|
-
// GPT-4 Turbo
|
|
256
|
-
"gpt-4-turbo": {
|
|
257
|
-
vision: true,
|
|
258
|
-
tools: true,
|
|
259
|
-
jsonMode: true,
|
|
260
|
-
maxTokens: 128e3
|
|
261
|
-
},
|
|
262
|
-
"gpt-4-turbo-preview": {
|
|
263
|
-
vision: false,
|
|
264
|
-
tools: true,
|
|
265
|
-
jsonMode: true,
|
|
266
|
-
maxTokens: 128e3
|
|
267
|
-
},
|
|
268
|
-
// GPT-4
|
|
269
|
-
"gpt-4": { vision: false, tools: true, jsonMode: false, maxTokens: 8192 },
|
|
270
|
-
"gpt-4-32k": {
|
|
271
|
-
vision: false,
|
|
272
|
-
tools: true,
|
|
273
|
-
jsonMode: false,
|
|
274
|
-
maxTokens: 32768
|
|
275
|
-
},
|
|
276
|
-
// GPT-3.5
|
|
277
|
-
"gpt-3.5-turbo": {
|
|
278
|
-
vision: false,
|
|
279
|
-
tools: true,
|
|
280
|
-
jsonMode: true,
|
|
281
|
-
maxTokens: 16385
|
|
282
|
-
},
|
|
283
|
-
// O1 series (reasoning)
|
|
284
|
-
o1: { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
285
|
-
"o1-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
286
|
-
"o1-preview": {
|
|
287
|
-
vision: true,
|
|
288
|
-
tools: false,
|
|
289
|
-
jsonMode: false,
|
|
290
|
-
maxTokens: 128e3
|
|
291
|
-
},
|
|
292
|
-
// O3 series
|
|
293
|
-
"o3-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 }
|
|
294
|
-
};
|
|
295
|
-
function openai(modelId, options = {}) {
|
|
296
|
-
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
297
|
-
const baseURL = options.baseURL ?? "https://api.openai.com/v1";
|
|
298
|
-
let client = null;
|
|
299
|
-
async function getClient() {
|
|
300
|
-
if (!client) {
|
|
321
|
+
// src/adapters/openai.ts
|
|
322
|
+
var OpenAIAdapter = class _OpenAIAdapter {
|
|
323
|
+
constructor(config) {
|
|
324
|
+
this.config = config;
|
|
325
|
+
this.model = config.model || "gpt-4o";
|
|
326
|
+
this.provider = _OpenAIAdapter.resolveProviderName(config.baseUrl);
|
|
327
|
+
}
|
|
328
|
+
static resolveProviderName(baseUrl) {
|
|
329
|
+
if (!baseUrl) return "openai";
|
|
330
|
+
if (baseUrl.includes("generativelanguage.googleapis.com")) return "google";
|
|
331
|
+
if (baseUrl.includes("x.ai")) return "xai";
|
|
332
|
+
if (baseUrl.includes("azure")) return "azure";
|
|
333
|
+
if (baseUrl.includes("fireworks.ai")) return "fireworks";
|
|
334
|
+
return "openai";
|
|
335
|
+
}
|
|
336
|
+
async getClient() {
|
|
337
|
+
if (!this.client) {
|
|
301
338
|
const { default: OpenAI } = await import('openai');
|
|
302
|
-
client = new OpenAI({
|
|
303
|
-
apiKey,
|
|
304
|
-
baseURL,
|
|
305
|
-
organization:
|
|
306
|
-
defaultHeaders:
|
|
339
|
+
this.client = new OpenAI({
|
|
340
|
+
apiKey: this.config.apiKey,
|
|
341
|
+
baseURL: this.config.baseUrl,
|
|
342
|
+
organization: this.config.organization,
|
|
343
|
+
defaultHeaders: this.config.headers
|
|
307
344
|
});
|
|
308
345
|
}
|
|
309
|
-
return client;
|
|
346
|
+
return this.client;
|
|
310
347
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
let totalPromptTokens = 0;
|
|
377
|
-
let totalCompletionTokens = 0;
|
|
378
|
-
for await (const chunk of stream) {
|
|
379
|
-
if (params.signal?.aborted) {
|
|
380
|
-
yield { type: "error", error: new Error("Aborted") };
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
const choice = chunk.choices[0];
|
|
384
|
-
const delta = choice?.delta;
|
|
385
|
-
if (delta?.content) {
|
|
386
|
-
yield { type: "text-delta", text: delta.content };
|
|
348
|
+
parameterOptions(request) {
|
|
349
|
+
return {
|
|
350
|
+
...this.config,
|
|
351
|
+
reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
toolDefinitions(request) {
|
|
355
|
+
if (request.toolDefinitions?.length) return request.toolDefinitions;
|
|
356
|
+
return formatTools(request.actions ?? []).map((tool) => ({
|
|
357
|
+
name: tool.function.name,
|
|
358
|
+
description: tool.function.description ?? "",
|
|
359
|
+
location: "server",
|
|
360
|
+
inputSchema: tool.function.parameters
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
shouldUseResponsesApi(request) {
|
|
364
|
+
if (this.toolDefinitions(request).length === 0) return false;
|
|
365
|
+
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
|
|
366
|
+
return true;
|
|
367
|
+
return requiresOpenAIResponses(
|
|
368
|
+
request.config?.model || this.model,
|
|
369
|
+
this.config.baseUrl,
|
|
370
|
+
this.parameterOptions(request)
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Convert one message's content to Responses content parts.
|
|
375
|
+
*
|
|
376
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
377
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
378
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
379
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
380
|
+
* array-shaped has to be translated rather than forwarded.
|
|
381
|
+
*/
|
|
382
|
+
toResponsesContent(content, role) {
|
|
383
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
384
|
+
if (typeof content === "string") return content;
|
|
385
|
+
if (!Array.isArray(content)) {
|
|
386
|
+
return content == null ? "" : JSON.stringify(content);
|
|
387
|
+
}
|
|
388
|
+
return content.map((part) => {
|
|
389
|
+
const p = part;
|
|
390
|
+
if (p?.type === "text") return { type: textType, text: p.text ?? "" };
|
|
391
|
+
if (p?.type === "image_url") {
|
|
392
|
+
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
393
|
+
return {
|
|
394
|
+
type: "input_image",
|
|
395
|
+
image_url: url,
|
|
396
|
+
...p.image_url?.detail ? { detail: p.image_url.detail } : {}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return p;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
buildResponsesInput(request) {
|
|
403
|
+
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
|
|
404
|
+
const input = [];
|
|
405
|
+
for (const message of sourceMessages) {
|
|
406
|
+
if (message.role === "system") {
|
|
407
|
+
if (!request.systemPrompt && message.content) {
|
|
408
|
+
input.push({
|
|
409
|
+
type: "message",
|
|
410
|
+
role: "developer",
|
|
411
|
+
content: this.toResponsesContent(message.content, "developer")
|
|
412
|
+
});
|
|
387
413
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
currentToolCall = {
|
|
402
|
-
id: tc.id,
|
|
403
|
-
name: tc.function?.name ?? "",
|
|
404
|
-
arguments: tc.function?.arguments ?? ""
|
|
405
|
-
};
|
|
406
|
-
} else if (currentToolCall && tc.function?.arguments) {
|
|
407
|
-
currentToolCall.arguments += tc.function.arguments;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (message.role === "assistant") {
|
|
417
|
+
const content = this.toResponsesContent(message.content, "assistant");
|
|
418
|
+
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
419
|
+
input.push({
|
|
420
|
+
type: "message",
|
|
421
|
+
role: "assistant",
|
|
422
|
+
content
|
|
423
|
+
});
|
|
410
424
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
currentToolCall = null;
|
|
422
|
-
}
|
|
423
|
-
if (chunk.usage) {
|
|
424
|
-
totalPromptTokens = chunk.usage.prompt_tokens;
|
|
425
|
-
totalCompletionTokens = chunk.usage.completion_tokens;
|
|
426
|
-
}
|
|
427
|
-
yield {
|
|
428
|
-
type: "finish",
|
|
429
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
430
|
-
usage: {
|
|
431
|
-
promptTokens: totalPromptTokens,
|
|
432
|
-
completionTokens: totalCompletionTokens,
|
|
433
|
-
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
434
|
-
}
|
|
435
|
-
};
|
|
425
|
+
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
426
|
+
for (const toolCall of toolCalls) {
|
|
427
|
+
input.push({
|
|
428
|
+
type: "function_call",
|
|
429
|
+
call_id: toolCall.id,
|
|
430
|
+
name: toolCall.function?.name,
|
|
431
|
+
arguments: toolCall.function?.arguments ?? "{}"
|
|
432
|
+
});
|
|
436
433
|
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (message.role === "tool") {
|
|
437
|
+
input.push({
|
|
438
|
+
type: "function_call_output",
|
|
439
|
+
call_id: message.tool_call_id,
|
|
440
|
+
output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
441
|
+
});
|
|
442
|
+
continue;
|
|
437
443
|
}
|
|
444
|
+
const role = message.role === "developer" ? "developer" : "user";
|
|
445
|
+
input.push({
|
|
446
|
+
type: "message",
|
|
447
|
+
role,
|
|
448
|
+
content: this.toResponsesContent(message.content, role)
|
|
449
|
+
});
|
|
438
450
|
}
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
function mapFinishReason(reason) {
|
|
442
|
-
switch (reason) {
|
|
443
|
-
case "stop":
|
|
444
|
-
return "stop";
|
|
445
|
-
case "length":
|
|
446
|
-
return "length";
|
|
447
|
-
case "tool_calls":
|
|
448
|
-
case "function_call":
|
|
449
|
-
return "tool-calls";
|
|
450
|
-
case "content_filter":
|
|
451
|
-
return "content-filter";
|
|
452
|
-
default:
|
|
453
|
-
return "unknown";
|
|
451
|
+
return input;
|
|
454
452
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
if (part.type === "image") {
|
|
472
|
-
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
473
|
-
const url = imageData.startsWith("data:") ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
474
|
-
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
475
|
-
}
|
|
476
|
-
return { type: "text", text: "" };
|
|
477
|
-
})
|
|
478
|
-
};
|
|
479
|
-
case "assistant":
|
|
480
|
-
const assistantMsg = {
|
|
481
|
-
role: "assistant",
|
|
482
|
-
content: msg.content
|
|
483
|
-
};
|
|
484
|
-
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
485
|
-
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
486
|
-
id: tc.id,
|
|
487
|
-
type: "function",
|
|
488
|
-
function: {
|
|
489
|
-
name: tc.name,
|
|
490
|
-
arguments: JSON.stringify(tc.args)
|
|
491
|
-
}
|
|
492
|
-
}));
|
|
453
|
+
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
454
|
+
formatRawMessages(messages) {
|
|
455
|
+
return messages.map((message) => {
|
|
456
|
+
const { attachments, ...rest } = message;
|
|
457
|
+
let content = message.content;
|
|
458
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
|
|
459
|
+
content = null;
|
|
460
|
+
}
|
|
461
|
+
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
462
|
+
const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
|
|
463
|
+
(attachment) => attachmentToOpenAIImage(attachment)
|
|
464
|
+
).filter((image) => image !== null);
|
|
465
|
+
if (images.length > 0) {
|
|
466
|
+
const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
|
|
467
|
+
content = [...parts, ...images];
|
|
493
468
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
role: "tool",
|
|
498
|
-
tool_call_id: msg.toolCallId,
|
|
499
|
-
content: msg.content
|
|
500
|
-
};
|
|
501
|
-
default:
|
|
502
|
-
return msg;
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// src/core/utils.ts
|
|
508
|
-
function generateId(prefix = "id") {
|
|
509
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
510
|
-
}
|
|
511
|
-
function generateMessageId() {
|
|
512
|
-
return generateId("msg");
|
|
513
|
-
}
|
|
514
|
-
function generateToolCallId() {
|
|
515
|
-
return generateId("call");
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// src/adapters/openai.ts
|
|
519
|
-
var OpenAIAdapter = class _OpenAIAdapter {
|
|
520
|
-
constructor(config) {
|
|
521
|
-
this.config = config;
|
|
522
|
-
this.model = config.model || "gpt-4o";
|
|
523
|
-
this.provider = _OpenAIAdapter.resolveProviderName(config.baseUrl);
|
|
524
|
-
}
|
|
525
|
-
static resolveProviderName(baseUrl) {
|
|
526
|
-
if (!baseUrl) return "openai";
|
|
527
|
-
if (baseUrl.includes("generativelanguage.googleapis.com")) return "google";
|
|
528
|
-
if (baseUrl.includes("x.ai")) return "xai";
|
|
529
|
-
if (baseUrl.includes("azure")) return "azure";
|
|
530
|
-
if (baseUrl.includes("fireworks.ai")) return "fireworks";
|
|
531
|
-
return "openai";
|
|
469
|
+
}
|
|
470
|
+
return { ...rest, content };
|
|
471
|
+
});
|
|
532
472
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
473
|
+
/**
|
|
474
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
475
|
+
* search is off.
|
|
476
|
+
*
|
|
477
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
478
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
479
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
480
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
481
|
+
*/
|
|
482
|
+
buildResponsesWebSearchTool(request) {
|
|
483
|
+
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
484
|
+
if (!webSearchConfig) return void 0;
|
|
485
|
+
const tool = { type: "web_search" };
|
|
486
|
+
const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
|
|
487
|
+
if (wsConfig.userLocation) {
|
|
488
|
+
tool.search_context_size = "medium";
|
|
540
489
|
}
|
|
541
|
-
return
|
|
542
|
-
}
|
|
543
|
-
shouldUseResponsesApi(request) {
|
|
544
|
-
return request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false && Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
|
|
490
|
+
return tool;
|
|
545
491
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Extract url_citation annotations from Responses output items.
|
|
494
|
+
*
|
|
495
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
496
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
497
|
+
* them from the same place.
|
|
498
|
+
*/
|
|
499
|
+
collectResponsesCitations(output, startIndex = 0) {
|
|
500
|
+
if (!Array.isArray(output)) return [];
|
|
501
|
+
const citations = [];
|
|
502
|
+
let index = startIndex;
|
|
503
|
+
for (const item of output) {
|
|
504
|
+
if (item?.type !== "message" || !Array.isArray(item.content)) continue;
|
|
505
|
+
for (const part of item.content) {
|
|
506
|
+
if (!Array.isArray(part?.annotations)) continue;
|
|
507
|
+
for (const annotation of part.annotations) {
|
|
508
|
+
const url = annotation?.url;
|
|
509
|
+
if (annotation?.type !== "url_citation" || !url) continue;
|
|
510
|
+
index++;
|
|
511
|
+
const domain = extractDomain(url);
|
|
512
|
+
citations.push({
|
|
513
|
+
index,
|
|
514
|
+
url,
|
|
515
|
+
title: annotation.title || domain,
|
|
516
|
+
domain,
|
|
517
|
+
favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
|
|
569
518
|
});
|
|
570
519
|
}
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
if (message.role === "tool") {
|
|
574
|
-
input.push({
|
|
575
|
-
type: "function_call_output",
|
|
576
|
-
call_id: message.tool_call_id,
|
|
577
|
-
output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
578
|
-
});
|
|
579
|
-
continue;
|
|
580
520
|
}
|
|
581
|
-
input.push({
|
|
582
|
-
type: "message",
|
|
583
|
-
role: message.role === "developer" ? "developer" : "user",
|
|
584
|
-
content: typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : JSON.stringify(message.content ?? "")
|
|
585
|
-
});
|
|
586
521
|
}
|
|
587
|
-
return
|
|
522
|
+
return citations;
|
|
588
523
|
}
|
|
589
|
-
buildResponsesTools(tools) {
|
|
524
|
+
buildResponsesTools(tools, includeToolSearch, webSearchTool) {
|
|
590
525
|
const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
|
|
591
526
|
type: "function",
|
|
592
527
|
name: tool.name,
|
|
@@ -601,20 +536,22 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
601
536
|
strict: true,
|
|
602
537
|
defer_loading: tool.deferLoading === true
|
|
603
538
|
}));
|
|
604
|
-
|
|
539
|
+
const builtins = [];
|
|
540
|
+
if (includeToolSearch) builtins.push({ type: "tool_search" });
|
|
541
|
+
if (webSearchTool) builtins.push(webSearchTool);
|
|
542
|
+
return [...builtins, ...nativeTools];
|
|
605
543
|
}
|
|
606
544
|
parseResponsesResult(response) {
|
|
607
|
-
const
|
|
545
|
+
const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
|
|
546
|
+
(item) => Array.isArray(item.content) ? item.content.filter(
|
|
547
|
+
(part) => part?.type === "output_text" && typeof part.text === "string"
|
|
548
|
+
).map((part) => part.text) : []
|
|
549
|
+
).join("") : "";
|
|
550
|
+
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
608
551
|
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
|
|
609
552
|
id: item.call_id ?? item.id ?? generateToolCallId(),
|
|
610
553
|
name: item.name,
|
|
611
|
-
args: (
|
|
612
|
-
try {
|
|
613
|
-
return JSON.parse(item.arguments ?? "{}");
|
|
614
|
-
} catch {
|
|
615
|
-
return {};
|
|
616
|
-
}
|
|
617
|
-
})()
|
|
554
|
+
args: JSON.parse(item.arguments ?? "{}")
|
|
618
555
|
})) : [];
|
|
619
556
|
return {
|
|
620
557
|
content,
|
|
@@ -627,102 +564,194 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
627
564
|
rawResponse: response
|
|
628
565
|
};
|
|
629
566
|
}
|
|
630
|
-
|
|
631
|
-
const client = await this.getClient();
|
|
567
|
+
buildResponsesPayload(request, stream) {
|
|
632
568
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
633
569
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
634
570
|
request.config?.responseFormat
|
|
635
571
|
);
|
|
572
|
+
const modelIdForPayload = request.config?.model || this.model;
|
|
636
573
|
const payload = {
|
|
637
|
-
model:
|
|
574
|
+
model: modelIdForPayload,
|
|
638
575
|
instructions: request.systemPrompt,
|
|
639
576
|
input: this.buildResponsesInput(request),
|
|
640
|
-
tools: this.buildResponsesTools(
|
|
641
|
-
|
|
577
|
+
tools: this.buildResponsesTools(
|
|
578
|
+
this.toolDefinitions(request),
|
|
579
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
580
|
+
this.buildResponsesWebSearchTool(request)
|
|
581
|
+
),
|
|
582
|
+
tool_choice: openaiToolOptions?.toolChoice,
|
|
642
583
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
643
|
-
|
|
644
|
-
|
|
584
|
+
...buildOpenAIParameters(
|
|
585
|
+
modelIdForPayload,
|
|
586
|
+
request.config?.maxTokens ?? this.config.maxTokens,
|
|
587
|
+
request.config?.temperature ?? this.config.temperature,
|
|
588
|
+
this.parameterOptions(request),
|
|
589
|
+
"responses"
|
|
590
|
+
),
|
|
645
591
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
646
|
-
stream
|
|
592
|
+
stream
|
|
647
593
|
};
|
|
594
|
+
return payload;
|
|
595
|
+
}
|
|
596
|
+
async completeWithResponses(request) {
|
|
597
|
+
const client = await this.getClient();
|
|
598
|
+
throwIfAborted(request.signal);
|
|
599
|
+
const payload = this.buildResponsesPayload(request, false);
|
|
648
600
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
649
|
-
|
|
601
|
+
if (!client.responses?.create)
|
|
602
|
+
throw new Error(
|
|
603
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
604
|
+
);
|
|
605
|
+
const response = await client.responses.create(payload, {
|
|
606
|
+
signal: request.signal
|
|
607
|
+
});
|
|
608
|
+
throwIfAborted(request.signal);
|
|
650
609
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
610
|
+
if (response.status && response.status !== "completed") {
|
|
611
|
+
throw Object.assign(
|
|
612
|
+
new Error(
|
|
613
|
+
response.error?.message ?? `Responses request ${response.status}`
|
|
614
|
+
),
|
|
615
|
+
response.error
|
|
616
|
+
);
|
|
617
|
+
}
|
|
651
618
|
return this.parseResponsesResult(response);
|
|
652
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* Stream from /v1/responses as real SSE.
|
|
622
|
+
*
|
|
623
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
624
|
+
* deltas, so the interesting ones are:
|
|
625
|
+
* response.output_text.delta — incremental assistant text
|
|
626
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
627
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
628
|
+
* response.completed — terminal, carries usage
|
|
629
|
+
*
|
|
630
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
631
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
632
|
+
*/
|
|
633
|
+
async *streamWithResponses(request) {
|
|
634
|
+
const client = await this.getClient();
|
|
635
|
+
throwIfAborted(request.signal);
|
|
636
|
+
const payload = this.buildResponsesPayload(request, true);
|
|
637
|
+
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
638
|
+
if (!client.responses?.create)
|
|
639
|
+
throw new Error(
|
|
640
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
641
|
+
);
|
|
642
|
+
const stream = await client.responses.create(payload, {
|
|
643
|
+
signal: request.signal
|
|
644
|
+
});
|
|
645
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
646
|
+
let usage;
|
|
647
|
+
const citations = [];
|
|
648
|
+
let completed = false;
|
|
649
|
+
function* flushToolCalls() {
|
|
650
|
+
for (const call of toolCalls.values()) {
|
|
651
|
+
if (!call.name)
|
|
652
|
+
throw new Error("Responses tool call is missing its name");
|
|
653
|
+
JSON.parse(call.arguments || "{}");
|
|
654
|
+
}
|
|
655
|
+
for (const call of toolCalls.values()) {
|
|
656
|
+
yield { type: "action:start", id: call.id, name: call.name };
|
|
657
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
658
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
659
|
+
}
|
|
660
|
+
toolCalls.clear();
|
|
661
|
+
}
|
|
662
|
+
for await (const event of stream) {
|
|
663
|
+
throwIfAborted(request.signal);
|
|
664
|
+
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
665
|
+
const index = event?.output_index ?? 0;
|
|
666
|
+
switch (event?.type) {
|
|
667
|
+
case "response.output_text.delta":
|
|
668
|
+
if (event.delta) {
|
|
669
|
+
yield { type: "message:delta", content: event.delta };
|
|
670
|
+
}
|
|
671
|
+
break;
|
|
672
|
+
case "response.output_item.added":
|
|
673
|
+
if (event.item?.type === "function_call") {
|
|
674
|
+
toolCalls.set(index, {
|
|
675
|
+
id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
|
|
676
|
+
name: event.item.name ?? "",
|
|
677
|
+
arguments: event.item.arguments ?? ""
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
break;
|
|
681
|
+
case "response.function_call_arguments.delta": {
|
|
682
|
+
const existing = toolCalls.get(index);
|
|
683
|
+
if (existing && event.delta) existing.arguments += event.delta;
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case "response.function_call_arguments.done": {
|
|
687
|
+
const existing = toolCalls.get(index);
|
|
688
|
+
if (existing && typeof event.arguments === "string") {
|
|
689
|
+
existing.arguments = event.arguments;
|
|
690
|
+
}
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
case "response.completed": {
|
|
694
|
+
completed = true;
|
|
695
|
+
const found = this.collectResponsesCitations(
|
|
696
|
+
event.response?.output,
|
|
697
|
+
citations.length
|
|
698
|
+
);
|
|
699
|
+
if (found.length > 0) citations.push(...found);
|
|
700
|
+
const u = event.response?.usage;
|
|
701
|
+
if (u) {
|
|
702
|
+
usage = {
|
|
703
|
+
prompt_tokens: u.input_tokens ?? 0,
|
|
704
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
705
|
+
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
case "response.incomplete":
|
|
711
|
+
throw new Error(
|
|
712
|
+
`Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
|
|
713
|
+
);
|
|
714
|
+
case "error":
|
|
715
|
+
case "response.failed": {
|
|
716
|
+
const details = event.response?.error ?? event;
|
|
717
|
+
throw Object.assign(
|
|
718
|
+
new Error(details.message ?? "Responses stream failed"),
|
|
719
|
+
{
|
|
720
|
+
code: details.code,
|
|
721
|
+
param: details.param,
|
|
722
|
+
status: details.status
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
throwIfAborted(request.signal);
|
|
729
|
+
if (!completed)
|
|
730
|
+
throw new Error("Responses stream ended without a completed response");
|
|
731
|
+
yield* flushToolCalls();
|
|
732
|
+
if (citations.length > 0) {
|
|
733
|
+
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
734
|
+
}
|
|
735
|
+
yield { type: "message:end" };
|
|
736
|
+
yield { type: "done", usage };
|
|
737
|
+
}
|
|
653
738
|
async *stream(request) {
|
|
739
|
+
throwIfAborted(request.signal);
|
|
654
740
|
if (this.shouldUseResponsesApi(request)) {
|
|
655
741
|
const messageId2 = generateMessageId();
|
|
656
742
|
yield { type: "message:start", id: messageId2 };
|
|
657
743
|
try {
|
|
658
|
-
|
|
659
|
-
if (result.content) {
|
|
660
|
-
yield { type: "message:delta", content: result.content };
|
|
661
|
-
}
|
|
662
|
-
for (const toolCall of result.toolCalls) {
|
|
663
|
-
yield {
|
|
664
|
-
type: "action:start",
|
|
665
|
-
id: toolCall.id,
|
|
666
|
-
name: toolCall.name
|
|
667
|
-
};
|
|
668
|
-
yield {
|
|
669
|
-
type: "action:args",
|
|
670
|
-
id: toolCall.id,
|
|
671
|
-
args: JSON.stringify(toolCall.args)
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
yield { type: "message:end" };
|
|
675
|
-
yield {
|
|
676
|
-
type: "done",
|
|
677
|
-
usage: result.usage ? {
|
|
678
|
-
prompt_tokens: result.usage.promptTokens,
|
|
679
|
-
completion_tokens: result.usage.completionTokens,
|
|
680
|
-
total_tokens: result.usage.totalTokens
|
|
681
|
-
} : void 0
|
|
682
|
-
};
|
|
744
|
+
yield* this.streamWithResponses(request);
|
|
683
745
|
return;
|
|
684
746
|
} catch (error) {
|
|
685
|
-
yield
|
|
686
|
-
type: "error",
|
|
687
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
688
|
-
code: "OPENAI_RESPONSES_ERROR"
|
|
689
|
-
};
|
|
747
|
+
yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
|
|
690
748
|
return;
|
|
691
749
|
}
|
|
692
750
|
}
|
|
693
751
|
const client = await this.getClient();
|
|
694
752
|
let messages;
|
|
695
753
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
696
|
-
const processedMessages = request.rawMessages
|
|
697
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
698
|
-
return { ...msg, content: null };
|
|
699
|
-
}
|
|
700
|
-
const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
|
|
701
|
-
if (hasAttachments) {
|
|
702
|
-
const content = [];
|
|
703
|
-
if (msg.content) {
|
|
704
|
-
content.push({ type: "text", text: msg.content });
|
|
705
|
-
}
|
|
706
|
-
for (const attachment of msg.attachments) {
|
|
707
|
-
if (attachment.type === "image") {
|
|
708
|
-
let imageUrl;
|
|
709
|
-
if (attachment.url) {
|
|
710
|
-
imageUrl = attachment.url;
|
|
711
|
-
} else if (attachment.data) {
|
|
712
|
-
imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
|
|
713
|
-
} else {
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
content.push({
|
|
717
|
-
type: "image_url",
|
|
718
|
-
image_url: { url: imageUrl, detail: "auto" }
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
return { ...msg, content, attachments: void 0 };
|
|
723
|
-
}
|
|
724
|
-
return msg;
|
|
725
|
-
});
|
|
754
|
+
const processedMessages = this.formatRawMessages(request.rawMessages);
|
|
726
755
|
if (request.systemPrompt) {
|
|
727
756
|
const hasSystem = processedMessages.some((m) => m.role === "system");
|
|
728
757
|
if (!hasSystem) {
|
|
@@ -742,7 +771,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
742
771
|
request.systemPrompt
|
|
743
772
|
);
|
|
744
773
|
}
|
|
745
|
-
const tools = request.
|
|
774
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
775
|
+
type: "function",
|
|
776
|
+
function: {
|
|
777
|
+
name: tool.name,
|
|
778
|
+
description: tool.description,
|
|
779
|
+
parameters: normalizeObjectJsonSchema(
|
|
780
|
+
tool.inputSchema
|
|
781
|
+
)
|
|
782
|
+
}
|
|
783
|
+
})) : formatTools(request.actions ?? []);
|
|
746
784
|
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
747
785
|
if (webSearchConfig) {
|
|
748
786
|
const webSearchTool = {
|
|
@@ -771,26 +809,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
771
809
|
tools: tools.length > 0 ? tools : void 0,
|
|
772
810
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
773
811
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
774
|
-
...
|
|
812
|
+
...buildOpenAIParameters(
|
|
775
813
|
modelIdForPayload,
|
|
776
814
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
777
|
-
request.config?.temperature ?? this.config.temperature
|
|
815
|
+
request.config?.temperature ?? this.config.temperature,
|
|
816
|
+
this.parameterOptions(request)
|
|
778
817
|
),
|
|
779
818
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
780
819
|
stream: true,
|
|
781
820
|
stream_options: { include_usage: true }
|
|
782
821
|
};
|
|
783
822
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
784
|
-
const stream = await client.chat.completions.create(payload
|
|
785
|
-
|
|
823
|
+
const stream = await client.chat.completions.create(payload, {
|
|
824
|
+
signal: request.signal
|
|
825
|
+
});
|
|
826
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
827
|
+
let finishReason;
|
|
786
828
|
const collectedCitations = [];
|
|
787
829
|
let citationIndex = 0;
|
|
788
830
|
let usage;
|
|
789
831
|
for await (const chunk of stream) {
|
|
790
832
|
logProviderPayload("openai", "stream chunk", chunk, request.debug);
|
|
791
|
-
|
|
792
|
-
break;
|
|
793
|
-
}
|
|
833
|
+
throwIfAborted(request.signal);
|
|
794
834
|
const delta = chunk.choices[0]?.delta;
|
|
795
835
|
const choice = chunk.choices[0];
|
|
796
836
|
if (delta?.content) {
|
|
@@ -813,38 +853,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
813
853
|
}
|
|
814
854
|
}
|
|
815
855
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
const tcExtraContent = toolCall.extra_content;
|
|
832
|
-
currentToolCall = {
|
|
833
|
-
id: toolCall.id,
|
|
834
|
-
name: toolCall.function?.name || "",
|
|
835
|
-
arguments: toolCall.function?.arguments || "",
|
|
836
|
-
...tcExtraContent ? { extra_content: tcExtraContent } : {}
|
|
837
|
-
};
|
|
838
|
-
yield {
|
|
839
|
-
type: "action:start",
|
|
840
|
-
id: currentToolCall.id,
|
|
841
|
-
name: currentToolCall.name,
|
|
842
|
-
...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
|
|
843
|
-
};
|
|
844
|
-
} else if (currentToolCall && toolCall.function?.arguments) {
|
|
845
|
-
currentToolCall.arguments += toolCall.function.arguments;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
856
|
+
for (const part of delta?.tool_calls ?? []) {
|
|
857
|
+
const index = part.index ?? 0;
|
|
858
|
+
const call = toolCalls.get(index) ?? {
|
|
859
|
+
id: "",
|
|
860
|
+
name: "",
|
|
861
|
+
arguments: ""
|
|
862
|
+
};
|
|
863
|
+
if (part.id) call.id = part.id;
|
|
864
|
+
if (part.function?.name) call.name += part.function.name;
|
|
865
|
+
if (part.function?.arguments)
|
|
866
|
+
call.arguments += part.function.arguments;
|
|
867
|
+
if (part.extra_content) call.extra_content = part.extra_content;
|
|
868
|
+
toolCalls.set(index, call);
|
|
848
869
|
}
|
|
849
870
|
if (chunk.usage) {
|
|
850
871
|
usage = {
|
|
@@ -853,21 +874,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
853
874
|
total_tokens: chunk.usage.total_tokens
|
|
854
875
|
};
|
|
855
876
|
}
|
|
856
|
-
if (choice?.finish_reason)
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
877
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
878
|
+
}
|
|
879
|
+
throwIfAborted(request.signal);
|
|
880
|
+
if (!finishReason)
|
|
881
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
882
|
+
if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
|
|
883
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
884
|
+
}
|
|
885
|
+
for (const call of toolCalls.values()) {
|
|
886
|
+
if (!call.id || !call.name)
|
|
887
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
888
|
+
JSON.parse(call.arguments || "{}");
|
|
889
|
+
}
|
|
890
|
+
for (const call of toolCalls.values()) {
|
|
891
|
+
yield {
|
|
892
|
+
type: "action:start",
|
|
893
|
+
id: call.id,
|
|
894
|
+
name: call.name,
|
|
895
|
+
...call.extra_content ? { extra_content: call.extra_content } : {}
|
|
896
|
+
};
|
|
897
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
898
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
871
899
|
}
|
|
872
900
|
if (collectedCitations.length > 0) {
|
|
873
901
|
const uniqueCitations = deduplicateCitations(collectedCitations);
|
|
@@ -876,26 +904,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
876
904
|
yield { type: "message:end" };
|
|
877
905
|
yield { type: "done", usage };
|
|
878
906
|
} catch (error) {
|
|
879
|
-
yield {
|
|
880
|
-
type: "error",
|
|
881
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
882
|
-
code: `${this.provider.toUpperCase()}_ERROR`
|
|
883
|
-
};
|
|
907
|
+
yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
|
|
884
908
|
}
|
|
885
909
|
}
|
|
886
910
|
async complete(request) {
|
|
911
|
+
throwIfAborted(request.signal);
|
|
887
912
|
if (this.shouldUseResponsesApi(request)) {
|
|
888
913
|
return this.completeWithResponses(request);
|
|
889
914
|
}
|
|
890
915
|
const client = await this.getClient();
|
|
891
916
|
let messages;
|
|
892
917
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
893
|
-
const sanitized = request.rawMessages
|
|
894
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
895
|
-
return { ...msg, content: null };
|
|
896
|
-
}
|
|
897
|
-
return msg;
|
|
898
|
-
});
|
|
918
|
+
const sanitized = this.formatRawMessages(request.rawMessages);
|
|
899
919
|
if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
|
|
900
920
|
messages = [
|
|
901
921
|
{ role: "system", content: request.systemPrompt },
|
|
@@ -910,7 +930,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
910
930
|
request.systemPrompt
|
|
911
931
|
);
|
|
912
932
|
}
|
|
913
|
-
const tools = request.
|
|
933
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
934
|
+
type: "function",
|
|
935
|
+
function: {
|
|
936
|
+
name: tool.name,
|
|
937
|
+
description: tool.description,
|
|
938
|
+
parameters: normalizeObjectJsonSchema(
|
|
939
|
+
tool.inputSchema
|
|
940
|
+
)
|
|
941
|
+
}
|
|
942
|
+
})) : formatTools(request.actions ?? []);
|
|
914
943
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
915
944
|
const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
|
|
916
945
|
type: "function",
|
|
@@ -925,16 +954,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
925
954
|
tools: tools.length > 0 ? tools : void 0,
|
|
926
955
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
927
956
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
928
|
-
...
|
|
957
|
+
...buildOpenAIParameters(
|
|
929
958
|
modelIdForCompletePayload,
|
|
930
959
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
931
|
-
request.config?.temperature ?? this.config.temperature
|
|
960
|
+
request.config?.temperature ?? this.config.temperature,
|
|
961
|
+
this.parameterOptions(request)
|
|
932
962
|
),
|
|
933
963
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
934
964
|
stream: false
|
|
935
965
|
};
|
|
936
966
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
937
|
-
const response = await client.chat.completions.create(payload
|
|
967
|
+
const response = await client.chat.completions.create(payload, {
|
|
968
|
+
signal: request.signal
|
|
969
|
+
});
|
|
938
970
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
939
971
|
const choice = response.choices?.[0];
|
|
940
972
|
const message = choice?.message;
|
|
@@ -984,6 +1016,383 @@ function createOpenAIAdapter(config) {
|
|
|
984
1016
|
return new OpenAIAdapter(config);
|
|
985
1017
|
}
|
|
986
1018
|
|
|
1019
|
+
// src/providers/openai/provider.ts
|
|
1020
|
+
var OPENAI_MODELS = {
|
|
1021
|
+
// GPT-4o series
|
|
1022
|
+
"gpt-4o": { vision: true, tools: true, jsonMode: true, maxTokens: 128e3 },
|
|
1023
|
+
"gpt-5.2": {
|
|
1024
|
+
vision: true,
|
|
1025
|
+
tools: true,
|
|
1026
|
+
jsonMode: true,
|
|
1027
|
+
maxTokens: 128e3
|
|
1028
|
+
},
|
|
1029
|
+
"gpt-4o-2024-11-20": {
|
|
1030
|
+
vision: true,
|
|
1031
|
+
tools: true,
|
|
1032
|
+
jsonMode: true,
|
|
1033
|
+
maxTokens: 128e3
|
|
1034
|
+
},
|
|
1035
|
+
"gpt-4o-2024-08-06": {
|
|
1036
|
+
vision: true,
|
|
1037
|
+
tools: true,
|
|
1038
|
+
jsonMode: true,
|
|
1039
|
+
maxTokens: 128e3
|
|
1040
|
+
},
|
|
1041
|
+
// GPT-4 Turbo
|
|
1042
|
+
"gpt-4-turbo": {
|
|
1043
|
+
vision: true,
|
|
1044
|
+
tools: true,
|
|
1045
|
+
jsonMode: true,
|
|
1046
|
+
maxTokens: 128e3
|
|
1047
|
+
},
|
|
1048
|
+
"gpt-4-turbo-preview": {
|
|
1049
|
+
vision: false,
|
|
1050
|
+
tools: true,
|
|
1051
|
+
jsonMode: true,
|
|
1052
|
+
maxTokens: 128e3
|
|
1053
|
+
},
|
|
1054
|
+
// GPT-4
|
|
1055
|
+
"gpt-4": { vision: false, tools: true, jsonMode: false, maxTokens: 8192 },
|
|
1056
|
+
"gpt-4-32k": {
|
|
1057
|
+
vision: false,
|
|
1058
|
+
tools: true,
|
|
1059
|
+
jsonMode: false,
|
|
1060
|
+
maxTokens: 32768
|
|
1061
|
+
},
|
|
1062
|
+
// GPT-3.5
|
|
1063
|
+
"gpt-3.5-turbo": {
|
|
1064
|
+
vision: false,
|
|
1065
|
+
tools: true,
|
|
1066
|
+
jsonMode: true,
|
|
1067
|
+
maxTokens: 16385
|
|
1068
|
+
},
|
|
1069
|
+
// O1 series (reasoning)
|
|
1070
|
+
o1: { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
1071
|
+
"o1-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 },
|
|
1072
|
+
"o1-preview": {
|
|
1073
|
+
vision: true,
|
|
1074
|
+
tools: false,
|
|
1075
|
+
jsonMode: false,
|
|
1076
|
+
maxTokens: 128e3
|
|
1077
|
+
},
|
|
1078
|
+
// O3 series
|
|
1079
|
+
"o3-mini": { vision: true, tools: false, jsonMode: false, maxTokens: 128e3 }
|
|
1080
|
+
};
|
|
1081
|
+
function openai(modelId, options = {}) {
|
|
1082
|
+
const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
|
|
1083
|
+
const baseURL = options.baseURL ?? "https://api.openai.com/v1";
|
|
1084
|
+
let client = null;
|
|
1085
|
+
async function getClient() {
|
|
1086
|
+
if (!client) {
|
|
1087
|
+
const { default: OpenAI } = await import('openai');
|
|
1088
|
+
client = new OpenAI({
|
|
1089
|
+
apiKey,
|
|
1090
|
+
baseURL,
|
|
1091
|
+
organization: options.organization,
|
|
1092
|
+
defaultHeaders: options.headers
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return client;
|
|
1096
|
+
}
|
|
1097
|
+
function parameterOptions(params) {
|
|
1098
|
+
return {
|
|
1099
|
+
...options,
|
|
1100
|
+
reasoningEffort: params.reasoningEffort ?? options.reasoningEffort
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function useResponses(params) {
|
|
1104
|
+
return !!params.tools?.length && requiresOpenAIResponses(modelId, baseURL, parameterOptions(params));
|
|
1105
|
+
}
|
|
1106
|
+
const responsesAdapter = new OpenAIAdapter({
|
|
1107
|
+
...options,
|
|
1108
|
+
apiKey: apiKey ?? "",
|
|
1109
|
+
model: modelId,
|
|
1110
|
+
baseUrl: baseURL
|
|
1111
|
+
});
|
|
1112
|
+
function responsesRequest(params) {
|
|
1113
|
+
return {
|
|
1114
|
+
messages: [],
|
|
1115
|
+
rawMessages: formatMessagesForOpenAI2(params.messages),
|
|
1116
|
+
toolDefinitions: params.tools?.map((raw) => {
|
|
1117
|
+
const tool = raw;
|
|
1118
|
+
if (tool.type !== "function")
|
|
1119
|
+
throw new Error(
|
|
1120
|
+
"Only function tools are supported by this OpenAI generation path"
|
|
1121
|
+
);
|
|
1122
|
+
return {
|
|
1123
|
+
name: tool.function.name,
|
|
1124
|
+
description: tool.function.description ?? "",
|
|
1125
|
+
location: "server",
|
|
1126
|
+
inputSchema: tool.function.parameters
|
|
1127
|
+
};
|
|
1128
|
+
}),
|
|
1129
|
+
config: {
|
|
1130
|
+
temperature: params.temperature,
|
|
1131
|
+
maxTokens: params.maxTokens,
|
|
1132
|
+
responseFormat: params.responseFormat,
|
|
1133
|
+
reasoningEffort: parameterOptions(params).reasoningEffort
|
|
1134
|
+
},
|
|
1135
|
+
signal: params.signal
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
const modelConfig = OPENAI_MODELS[modelId] ?? OPENAI_MODELS["gpt-4o"];
|
|
1139
|
+
return {
|
|
1140
|
+
provider: "openai",
|
|
1141
|
+
modelId,
|
|
1142
|
+
capabilities: {
|
|
1143
|
+
supportsVision: modelConfig.vision,
|
|
1144
|
+
supportsTools: modelConfig.tools,
|
|
1145
|
+
supportsStreaming: true,
|
|
1146
|
+
supportsJsonMode: modelConfig.jsonMode,
|
|
1147
|
+
supportsThinking: false,
|
|
1148
|
+
supportsPDF: false,
|
|
1149
|
+
maxTokens: modelConfig.maxTokens,
|
|
1150
|
+
supportedImageTypes: modelConfig.vision ? ["image/png", "image/jpeg", "image/gif", "image/webp"] : []
|
|
1151
|
+
},
|
|
1152
|
+
async doGenerate(params) {
|
|
1153
|
+
throwIfAborted(params.signal);
|
|
1154
|
+
if (useResponses(params)) {
|
|
1155
|
+
const result = await responsesAdapter.complete(
|
|
1156
|
+
responsesRequest(params)
|
|
1157
|
+
);
|
|
1158
|
+
return {
|
|
1159
|
+
text: result.content,
|
|
1160
|
+
toolCalls: result.toolCalls,
|
|
1161
|
+
finishReason: result.toolCalls.length ? "tool-calls" : "stop",
|
|
1162
|
+
usage: result.usage ?? {
|
|
1163
|
+
promptTokens: 0,
|
|
1164
|
+
completionTokens: 0,
|
|
1165
|
+
totalTokens: 0
|
|
1166
|
+
},
|
|
1167
|
+
rawResponse: result.rawResponse
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
const client2 = await getClient();
|
|
1171
|
+
const messages = formatMessagesForOpenAI2(params.messages);
|
|
1172
|
+
const response = await client2.chat.completions.create(
|
|
1173
|
+
{
|
|
1174
|
+
model: modelId,
|
|
1175
|
+
messages,
|
|
1176
|
+
tools: params.tools,
|
|
1177
|
+
...buildOpenAIParameters(
|
|
1178
|
+
modelId,
|
|
1179
|
+
params.maxTokens,
|
|
1180
|
+
params.temperature,
|
|
1181
|
+
parameterOptions(params)
|
|
1182
|
+
),
|
|
1183
|
+
response_format: toOpenAIResponseFormat(params.responseFormat)
|
|
1184
|
+
},
|
|
1185
|
+
{ signal: params.signal }
|
|
1186
|
+
);
|
|
1187
|
+
const choice = response.choices[0];
|
|
1188
|
+
const message = choice.message;
|
|
1189
|
+
const toolCalls = (message.tool_calls ?? []).map(
|
|
1190
|
+
(tc) => ({
|
|
1191
|
+
id: tc.id,
|
|
1192
|
+
name: tc.function.name,
|
|
1193
|
+
args: JSON.parse(tc.function.arguments || "{}")
|
|
1194
|
+
})
|
|
1195
|
+
);
|
|
1196
|
+
return {
|
|
1197
|
+
text: message.content ?? "",
|
|
1198
|
+
toolCalls,
|
|
1199
|
+
finishReason: mapFinishReason(choice.finish_reason),
|
|
1200
|
+
usage: {
|
|
1201
|
+
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
1202
|
+
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
1203
|
+
totalTokens: response.usage?.total_tokens ?? 0
|
|
1204
|
+
},
|
|
1205
|
+
rawResponse: response
|
|
1206
|
+
};
|
|
1207
|
+
},
|
|
1208
|
+
async *doStream(params) {
|
|
1209
|
+
throwIfAborted(params.signal);
|
|
1210
|
+
if (useResponses(params)) {
|
|
1211
|
+
const calls2 = /* @__PURE__ */ new Map();
|
|
1212
|
+
let hasTools = false;
|
|
1213
|
+
for await (const event of responsesAdapter.stream(
|
|
1214
|
+
responsesRequest(params)
|
|
1215
|
+
)) {
|
|
1216
|
+
switch (event.type) {
|
|
1217
|
+
case "message:delta":
|
|
1218
|
+
yield { type: "text-delta", text: event.content };
|
|
1219
|
+
break;
|
|
1220
|
+
case "action:start":
|
|
1221
|
+
calls2.set(event.id, { name: event.name, args: "" });
|
|
1222
|
+
break;
|
|
1223
|
+
case "action:args": {
|
|
1224
|
+
const call = calls2.get(event.id);
|
|
1225
|
+
if (call) call.args += event.args;
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1228
|
+
case "action:end": {
|
|
1229
|
+
const call = calls2.get(event.id);
|
|
1230
|
+
if (!call)
|
|
1231
|
+
throw new Error("Responses tool call has no matching start");
|
|
1232
|
+
hasTools = true;
|
|
1233
|
+
yield {
|
|
1234
|
+
type: "tool-call",
|
|
1235
|
+
toolCall: {
|
|
1236
|
+
id: event.id,
|
|
1237
|
+
name: call.name,
|
|
1238
|
+
args: JSON.parse(call.args || "{}")
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
calls2.delete(event.id);
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
case "error":
|
|
1245
|
+
throw errorFromEvent(event);
|
|
1246
|
+
case "done":
|
|
1247
|
+
yield {
|
|
1248
|
+
type: "finish",
|
|
1249
|
+
finishReason: hasTools ? "tool-calls" : "stop",
|
|
1250
|
+
usage: {
|
|
1251
|
+
promptTokens: event.usage?.prompt_tokens ?? 0,
|
|
1252
|
+
completionTokens: event.usage?.completion_tokens ?? 0,
|
|
1253
|
+
totalTokens: event.usage?.total_tokens ?? 0
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
break;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const client2 = await getClient();
|
|
1262
|
+
const messages = formatMessagesForOpenAI2(params.messages);
|
|
1263
|
+
const stream = await client2.chat.completions.create(
|
|
1264
|
+
{
|
|
1265
|
+
model: modelId,
|
|
1266
|
+
messages,
|
|
1267
|
+
tools: params.tools,
|
|
1268
|
+
...buildOpenAIParameters(
|
|
1269
|
+
modelId,
|
|
1270
|
+
params.maxTokens,
|
|
1271
|
+
params.temperature,
|
|
1272
|
+
parameterOptions(params)
|
|
1273
|
+
),
|
|
1274
|
+
response_format: toOpenAIResponseFormat(params.responseFormat),
|
|
1275
|
+
stream: true,
|
|
1276
|
+
stream_options: { include_usage: true }
|
|
1277
|
+
},
|
|
1278
|
+
{ signal: params.signal }
|
|
1279
|
+
);
|
|
1280
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1281
|
+
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1282
|
+
let finishReason;
|
|
1283
|
+
for await (const chunk of stream) {
|
|
1284
|
+
throwIfAborted(params.signal);
|
|
1285
|
+
if (chunk.usage) {
|
|
1286
|
+
usage = {
|
|
1287
|
+
promptTokens: chunk.usage.prompt_tokens ?? 0,
|
|
1288
|
+
completionTokens: chunk.usage.completion_tokens ?? 0,
|
|
1289
|
+
totalTokens: chunk.usage.total_tokens ?? (chunk.usage.prompt_tokens ?? 0) + (chunk.usage.completion_tokens ?? 0)
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
const choice = chunk.choices?.[0];
|
|
1293
|
+
if (choice?.delta?.content)
|
|
1294
|
+
yield { type: "text-delta", text: choice.delta.content };
|
|
1295
|
+
for (const part of choice?.delta?.tool_calls ?? []) {
|
|
1296
|
+
const index = part.index ?? 0;
|
|
1297
|
+
const call = toolCalls.get(index) ?? {
|
|
1298
|
+
id: "",
|
|
1299
|
+
name: "",
|
|
1300
|
+
arguments: ""
|
|
1301
|
+
};
|
|
1302
|
+
if (part.id) call.id = part.id;
|
|
1303
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1304
|
+
if (part.function?.arguments)
|
|
1305
|
+
call.arguments += part.function.arguments;
|
|
1306
|
+
toolCalls.set(index, call);
|
|
1307
|
+
}
|
|
1308
|
+
if (choice?.finish_reason)
|
|
1309
|
+
finishReason = mapFinishReason(choice.finish_reason);
|
|
1310
|
+
}
|
|
1311
|
+
throwIfAborted(params.signal);
|
|
1312
|
+
if (!finishReason)
|
|
1313
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1314
|
+
const calls = [...toolCalls.values()].map((call) => {
|
|
1315
|
+
if (!call.id || !call.name)
|
|
1316
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1317
|
+
return {
|
|
1318
|
+
id: call.id,
|
|
1319
|
+
name: call.name,
|
|
1320
|
+
args: JSON.parse(call.arguments || "{}")
|
|
1321
|
+
};
|
|
1322
|
+
});
|
|
1323
|
+
if (calls.length && finishReason !== "tool-calls")
|
|
1324
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1325
|
+
for (const toolCall of calls) yield { type: "tool-call", toolCall };
|
|
1326
|
+
yield { type: "finish", finishReason, usage };
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function mapFinishReason(reason) {
|
|
1331
|
+
switch (reason) {
|
|
1332
|
+
case "stop":
|
|
1333
|
+
return "stop";
|
|
1334
|
+
case "length":
|
|
1335
|
+
return "length";
|
|
1336
|
+
case "tool_calls":
|
|
1337
|
+
case "function_call":
|
|
1338
|
+
return "tool-calls";
|
|
1339
|
+
case "content_filter":
|
|
1340
|
+
return "content-filter";
|
|
1341
|
+
default:
|
|
1342
|
+
return "unknown";
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function formatMessagesForOpenAI2(messages) {
|
|
1346
|
+
return messages.map((msg) => {
|
|
1347
|
+
switch (msg.role) {
|
|
1348
|
+
case "system":
|
|
1349
|
+
return { role: "system", content: msg.content };
|
|
1350
|
+
case "user":
|
|
1351
|
+
if (typeof msg.content === "string") {
|
|
1352
|
+
return { role: "user", content: msg.content };
|
|
1353
|
+
}
|
|
1354
|
+
return {
|
|
1355
|
+
role: "user",
|
|
1356
|
+
content: msg.content.map((part) => {
|
|
1357
|
+
if (part.type === "text") {
|
|
1358
|
+
return { type: "text", text: part.text };
|
|
1359
|
+
}
|
|
1360
|
+
if (part.type === "image") {
|
|
1361
|
+
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
1362
|
+
const url = imageData.startsWith("data:") ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
1363
|
+
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
1364
|
+
}
|
|
1365
|
+
return { type: "text", text: "" };
|
|
1366
|
+
})
|
|
1367
|
+
};
|
|
1368
|
+
case "assistant":
|
|
1369
|
+
const assistantMsg = {
|
|
1370
|
+
role: "assistant",
|
|
1371
|
+
content: msg.content
|
|
1372
|
+
};
|
|
1373
|
+
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
1374
|
+
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
1375
|
+
id: tc.id,
|
|
1376
|
+
type: "function",
|
|
1377
|
+
function: {
|
|
1378
|
+
name: tc.name,
|
|
1379
|
+
arguments: JSON.stringify(tc.args)
|
|
1380
|
+
}
|
|
1381
|
+
}));
|
|
1382
|
+
}
|
|
1383
|
+
return assistantMsg;
|
|
1384
|
+
case "tool":
|
|
1385
|
+
return {
|
|
1386
|
+
role: "tool",
|
|
1387
|
+
tool_call_id: msg.toolCallId,
|
|
1388
|
+
content: msg.content
|
|
1389
|
+
};
|
|
1390
|
+
default:
|
|
1391
|
+
return msg;
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
|
|
987
1396
|
// src/providers/types.ts
|
|
988
1397
|
function createCallableProvider(providerFn, properties) {
|
|
989
1398
|
Object.defineProperty(providerFn, "name", {
|
|
@@ -1114,7 +1523,10 @@ function createOpenAI(config = {}) {
|
|
|
1114
1523
|
return createOpenAIAdapter({
|
|
1115
1524
|
apiKey,
|
|
1116
1525
|
model: modelId,
|
|
1117
|
-
baseUrl: config.baseUrl
|
|
1526
|
+
baseUrl: config.baseUrl,
|
|
1527
|
+
reasoningEffort: config.reasoningEffort,
|
|
1528
|
+
parameterPolicy: config.parameterPolicy,
|
|
1529
|
+
onWarning: config.onWarning
|
|
1118
1530
|
});
|
|
1119
1531
|
};
|
|
1120
1532
|
const getCapabilities = (modelId) => {
|