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