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