@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.
Files changed (57) hide show
  1. package/README.md +77 -0
  2. package/dist/adapters/index.d.mts +54 -5
  3. package/dist/adapters/index.d.ts +54 -5
  4. package/dist/adapters/index.js +466 -169
  5. package/dist/adapters/index.mjs +466 -169
  6. package/dist/{base-C58Dsr9p.d.ts → base-BdTpUAWI.d.ts} +2 -1
  7. package/dist/{base-tNgbBaSo.d.mts → base-CkeT7u-O.d.mts} +2 -1
  8. package/dist/fallback/index.d.mts +4 -4
  9. package/dist/fallback/index.d.ts +4 -4
  10. package/dist/fallback/index.js +75 -39
  11. package/dist/fallback/index.mjs +75 -39
  12. package/dist/index.d.mts +8 -7
  13. package/dist/index.d.ts +8 -7
  14. package/dist/index.js +89 -30
  15. package/dist/index.mjs +89 -30
  16. package/dist/providers/anthropic/index.d.mts +3 -3
  17. package/dist/providers/anthropic/index.d.ts +3 -3
  18. package/dist/providers/azure/index.d.mts +3 -3
  19. package/dist/providers/azure/index.d.ts +3 -3
  20. package/dist/providers/azure/index.js +8 -3
  21. package/dist/providers/azure/index.mjs +8 -3
  22. package/dist/providers/fireworks/index.d.mts +3 -3
  23. package/dist/providers/fireworks/index.d.ts +3 -3
  24. package/dist/providers/fireworks/index.js +466 -169
  25. package/dist/providers/fireworks/index.mjs +466 -169
  26. package/dist/providers/google/index.d.mts +3 -3
  27. package/dist/providers/google/index.d.ts +3 -3
  28. package/dist/providers/google/index.js +466 -169
  29. package/dist/providers/google/index.mjs +466 -169
  30. package/dist/providers/ollama/index.d.mts +4 -4
  31. package/dist/providers/ollama/index.d.ts +4 -4
  32. package/dist/providers/openai/index.d.mts +6 -23
  33. package/dist/providers/openai/index.d.ts +6 -23
  34. package/dist/providers/openai/index.js +912 -500
  35. package/dist/providers/openai/index.mjs +912 -500
  36. package/dist/providers/openrouter/index.d.mts +3 -3
  37. package/dist/providers/openrouter/index.d.ts +3 -3
  38. package/dist/providers/openrouter/index.js +466 -169
  39. package/dist/providers/openrouter/index.mjs +466 -169
  40. package/dist/providers/togetherai/index.d.mts +3 -3
  41. package/dist/providers/togetherai/index.d.ts +3 -3
  42. package/dist/providers/togetherai/index.js +466 -169
  43. package/dist/providers/togetherai/index.mjs +466 -169
  44. package/dist/providers/xai/index.d.mts +3 -3
  45. package/dist/providers/xai/index.d.ts +3 -3
  46. package/dist/providers/xai/index.js +466 -169
  47. package/dist/providers/xai/index.mjs +466 -169
  48. package/dist/{types-BkQCSiIt.d.mts → types-BW3csYo5.d.mts} +22 -1
  49. package/dist/{types-BkQCSiIt.d.ts → types-BW3csYo5.d.ts} +22 -1
  50. package/dist/{types-B6dhnguR.d.mts → types-BWRYIvl_.d.mts} +11 -4
  51. package/dist/{types-CCxPmkmK.d.ts → types-CpXcT-tY.d.ts} +11 -4
  52. package/dist/{types-BQ31QIsA.d.ts → types-DA54bSmX.d.ts} +3 -11
  53. package/dist/{types-BSSiJW2o.d.mts → types-NOswlhm9.d.mts} +3 -11
  54. package/dist/yourgpt/index.d.mts +1 -1
  55. package/dist/yourgpt/index.d.ts +1 -1
  56. package/package.json +115 -48
  57. 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
- if (normalized.additionalProperties === void 0) {
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
- return normalized;
101
- }
102
- function isOpenAIReasoningModel(modelId) {
103
- if (!modelId) return false;
104
- return /^(o1|o3|o4|gpt-5)/i.test(modelId);
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 { max_tokens: maxTokens, temperature };
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/providers/openai/provider.ts
232
- var OPENAI_MODELS = {
233
- // GPT-4o series
234
- "gpt-4o": { vision: true, tools: true, jsonMode: true, maxTokens: 128e3 },
235
- "gpt-5.2": {
236
- vision: true,
237
- tools: true,
238
- jsonMode: true,
239
- maxTokens: 128e3
240
- },
241
- "gpt-4o-2024-11-20": {
242
- vision: true,
243
- tools: true,
244
- jsonMode: true,
245
- maxTokens: 128e3
246
- },
247
- "gpt-4o-2024-08-06": {
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: options.organization,
304
- defaultHeaders: options.headers
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
- const modelConfig = OPENAI_MODELS[modelId] ?? OPENAI_MODELS["gpt-4o"];
310
- return {
311
- provider: "openai",
312
- modelId,
313
- capabilities: {
314
- supportsVision: modelConfig.vision,
315
- supportsTools: modelConfig.tools,
316
- supportsStreaming: true,
317
- supportsJsonMode: modelConfig.jsonMode,
318
- supportsThinking: false,
319
- supportsPDF: false,
320
- maxTokens: modelConfig.maxTokens,
321
- supportedImageTypes: modelConfig.vision ? ["image/png", "image/jpeg", "image/gif", "image/webp"] : []
322
- },
323
- async doGenerate(params) {
324
- const client2 = await getClient();
325
- const messages = formatMessagesForOpenAI2(params.messages);
326
- const response = await client2.chat.completions.create({
327
- model: modelId,
328
- messages,
329
- tools: params.tools,
330
- ...buildOpenAITokenParams(
331
- modelId,
332
- params.maxTokens,
333
- params.temperature
334
- ),
335
- response_format: toOpenAIResponseFormat(params.responseFormat)
336
- });
337
- const choice = response.choices[0];
338
- const message = choice.message;
339
- const toolCalls = (message.tool_calls ?? []).map(
340
- (tc) => ({
341
- id: tc.id,
342
- name: tc.function.name,
343
- args: JSON.parse(tc.function.arguments || "{}")
344
- })
345
- );
346
- return {
347
- text: message.content ?? "",
348
- toolCalls,
349
- finishReason: mapFinishReason(choice.finish_reason),
350
- usage: {
351
- promptTokens: response.usage?.prompt_tokens ?? 0,
352
- completionTokens: response.usage?.completion_tokens ?? 0,
353
- totalTokens: response.usage?.total_tokens ?? 0
354
- },
355
- rawResponse: response
356
- };
357
- },
358
- async *doStream(params) {
359
- const client2 = await getClient();
360
- const messages = formatMessagesForOpenAI2(params.messages);
361
- const stream = await client2.chat.completions.create({
362
- model: modelId,
363
- messages,
364
- tools: params.tools,
365
- ...buildOpenAITokenParams(
366
- modelId,
367
- params.maxTokens,
368
- params.temperature
369
- ),
370
- response_format: toOpenAIResponseFormat(params.responseFormat),
371
- stream: true
372
- });
373
- let currentToolCall = null;
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
- if (delta?.tool_calls) {
387
- for (const tc of delta.tool_calls) {
388
- if (tc.id) {
389
- if (currentToolCall) {
390
- yield {
391
- type: "tool-call",
392
- toolCall: {
393
- id: currentToolCall.id,
394
- name: currentToolCall.name,
395
- args: JSON.parse(currentToolCall.arguments || "{}")
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
- if (choice?.finish_reason) {
410
- if (currentToolCall) {
411
- yield {
412
- type: "tool-call",
413
- toolCall: {
414
- id: currentToolCall.id,
415
- name: currentToolCall.name,
416
- args: JSON.parse(currentToolCall.arguments || "{}")
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
- function formatMessagesForOpenAI2(messages) {
455
- return messages.map((msg) => {
456
- switch (msg.role) {
457
- case "system":
458
- return { role: "system", content: msg.content };
459
- case "user":
460
- if (typeof msg.content === "string") {
461
- return { role: "user", content: msg.content };
462
- }
463
- return {
464
- role: "user",
465
- content: msg.content.map((part) => {
466
- if (part.type === "text") {
467
- return { type: "text", text: part.text };
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
- return assistantMsg;
493
- case "tool":
494
- return {
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
- async getClient() {
532
- if (!this.client) {
533
- const { default: OpenAI } = await import('openai');
534
- this.client = new OpenAI({
535
- apiKey: this.config.apiKey,
536
- baseURL: this.config.baseUrl
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 this.client;
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
- buildResponsesInput(request) {
545
- const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
546
- const input = [];
547
- for (const message of sourceMessages) {
548
- if (message.role === "system") {
549
- continue;
550
- }
551
- if (message.role === "assistant") {
552
- const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : message.content ? JSON.stringify(message.content) : "";
553
- if (content) {
554
- input.push({
555
- type: "message",
556
- role: "assistant",
557
- content
558
- });
559
- }
560
- const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
561
- for (const toolCall of toolCalls) {
562
- input.push({
563
- type: "function_call",
564
- call_id: toolCall.id,
565
- name: toolCall.function?.name,
566
- arguments: toolCall.function?.arguments ?? "{}"
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 input;
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
- return [{ type: "tool_search" }, ...nativeTools];
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 content = typeof response?.output_text === "string" ? response.output_text : "";
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
- async completeWithResponses(request) {
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: request.config?.model || this.model,
572
+ model: modelIdForPayload,
636
573
  instructions: request.systemPrompt,
637
574
  input: this.buildResponsesInput(request),
638
- tools: this.buildResponsesTools(request.toolDefinitions ?? []),
639
- tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
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
- temperature: request.config?.temperature ?? this.config.temperature,
642
- max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
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: false
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
- const response = await client.responses.create(payload);
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
- const result = await this.completeWithResponses(request);
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.map((msg) => {
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.actions?.length ? formatTools(request.actions) : [];
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
- ...buildOpenAITokenParams(
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
- let currentToolCall = null;
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
- if (request.signal?.aborted) {
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
- if (delta?.tool_calls) {
815
- for (const toolCall of delta.tool_calls) {
816
- if (toolCall.id) {
817
- if (currentToolCall) {
818
- yield {
819
- type: "action:args",
820
- id: currentToolCall.id,
821
- args: currentToolCall.arguments
822
- };
823
- yield {
824
- type: "action:end",
825
- id: currentToolCall.id,
826
- name: currentToolCall.name
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
- if (currentToolCall) {
856
- yield {
857
- type: "action:args",
858
- id: currentToolCall.id,
859
- args: currentToolCall.arguments
860
- };
861
- yield {
862
- type: "action:end",
863
- id: currentToolCall.id,
864
- name: currentToolCall.name
865
- };
866
- currentToolCall = null;
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.map((msg) => {
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.actions?.length ? formatTools(request.actions) : [];
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
- ...buildOpenAITokenParams(
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) => {