@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,54 @@
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
+
1
52
  // src/adapters/base.ts
2
53
  function stringifyForDebug(value) {
3
54
  return JSON.stringify(
@@ -89,25 +140,20 @@ function normalizeObjectJsonSchema(schema) {
89
140
  (value) => typeof value === "string"
90
141
  ) : [];
91
142
  normalized.required = Array.from(/* @__PURE__ */ new Set([...required, ...propertyKeys]));
92
- if (normalized.additionalProperties === void 0) {
93
- normalized.additionalProperties = false;
94
- }
143
+ normalized.additionalProperties = false;
95
144
  } else if (type === "array" && normalized.items && typeof normalized.items === "object") {
96
145
  normalized.items = normalizeObjectJsonSchema(
97
146
  normalized.items
98
147
  );
99
148
  }
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 };
149
+ for (const keyword of ["anyOf", "oneOf", "allOf"]) {
150
+ const branches = normalized[keyword];
151
+ if (!Array.isArray(branches)) continue;
152
+ normalized[keyword] = branches.map(
153
+ (branch) => branch && typeof branch === "object" ? normalizeObjectJsonSchema(branch) : branch
154
+ );
109
155
  }
110
- return { max_tokens: maxTokens, temperature };
156
+ return normalized;
111
157
  }
112
158
  function toOpenAIResponseFormat(rf) {
113
159
  if (!rf) return void 0;
@@ -435,6 +481,28 @@ function formatMessages(messages) {
435
481
  });
436
482
  }
437
483
 
484
+ // src/core/provider-error.ts
485
+ function providerErrorEvent(error, code) {
486
+ const details = error ?? {};
487
+ const event = {
488
+ type: "error",
489
+ message: error instanceof Error ? error.message : String(error),
490
+ code,
491
+ ...typeof details.status === "number" ? { status: details.status } : {},
492
+ ...typeof details.code === "string" ? { providerCode: details.code } : {},
493
+ ...typeof details.param === "string" ? { param: details.param } : {},
494
+ ...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
495
+ };
496
+ Object.defineProperty(event, "cause", { value: error, enumerable: false });
497
+ return event;
498
+ }
499
+ function throwIfAborted(signal) {
500
+ if (!signal?.aborted) return;
501
+ const error = new Error("The operation was aborted");
502
+ error.name = "AbortError";
503
+ throw error;
504
+ }
505
+
438
506
  // src/core/utils.ts
439
507
  function generateId(prefix = "id") {
440
508
  return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
@@ -466,24 +534,84 @@ var OpenAIAdapter = class _OpenAIAdapter {
466
534
  const { default: OpenAI } = await import('openai');
467
535
  this.client = new OpenAI({
468
536
  apiKey: this.config.apiKey,
469
- baseURL: this.config.baseUrl
537
+ baseURL: this.config.baseUrl,
538
+ organization: this.config.organization,
539
+ defaultHeaders: this.config.headers
470
540
  });
471
541
  }
472
542
  return this.client;
473
543
  }
544
+ parameterOptions(request) {
545
+ return {
546
+ ...this.config,
547
+ reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
548
+ };
549
+ }
550
+ toolDefinitions(request) {
551
+ if (request.toolDefinitions?.length) return request.toolDefinitions;
552
+ return formatTools(request.actions ?? []).map((tool) => ({
553
+ name: tool.function.name,
554
+ description: tool.function.description ?? "",
555
+ location: "server",
556
+ inputSchema: tool.function.parameters
557
+ }));
558
+ }
474
559
  shouldUseResponsesApi(request) {
475
- return request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false && Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
560
+ if (this.toolDefinitions(request).length === 0) return false;
561
+ if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
562
+ return true;
563
+ return requiresOpenAIResponses(
564
+ request.config?.model || this.model,
565
+ this.config.baseUrl,
566
+ this.parameterOptions(request)
567
+ );
568
+ }
569
+ /**
570
+ * Convert one message's content to Responses content parts.
571
+ *
572
+ * The Responses API does not accept Chat Completions content blocks: input
573
+ * parts are `input_text` / `input_image` (with a bare `image_url` string)
574
+ * rather than `text` / `image_url: { url }`. Passing the Chat Completions
575
+ * shape through unchanged makes the API reject the request, so anything
576
+ * array-shaped has to be translated rather than forwarded.
577
+ */
578
+ toResponsesContent(content, role) {
579
+ const textType = role === "assistant" ? "output_text" : "input_text";
580
+ if (typeof content === "string") return content;
581
+ if (!Array.isArray(content)) {
582
+ return content == null ? "" : JSON.stringify(content);
583
+ }
584
+ return content.map((part) => {
585
+ const p = part;
586
+ if (p?.type === "text") return { type: textType, text: p.text ?? "" };
587
+ if (p?.type === "image_url") {
588
+ const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
589
+ return {
590
+ type: "input_image",
591
+ image_url: url,
592
+ ...p.image_url?.detail ? { detail: p.image_url.detail } : {}
593
+ };
594
+ }
595
+ return p;
596
+ });
476
597
  }
477
598
  buildResponsesInput(request) {
478
- const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
599
+ const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
479
600
  const input = [];
480
601
  for (const message of sourceMessages) {
481
602
  if (message.role === "system") {
603
+ if (!request.systemPrompt && message.content) {
604
+ input.push({
605
+ type: "message",
606
+ role: "developer",
607
+ content: this.toResponsesContent(message.content, "developer")
608
+ });
609
+ }
482
610
  continue;
483
611
  }
484
612
  if (message.role === "assistant") {
485
- const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : message.content ? JSON.stringify(message.content) : "";
486
- if (content) {
613
+ const content = this.toResponsesContent(message.content, "assistant");
614
+ if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
487
615
  input.push({
488
616
  type: "message",
489
617
  role: "assistant",
@@ -509,15 +637,87 @@ var OpenAIAdapter = class _OpenAIAdapter {
509
637
  });
510
638
  continue;
511
639
  }
640
+ const role = message.role === "developer" ? "developer" : "user";
512
641
  input.push({
513
642
  type: "message",
514
- role: message.role === "developer" ? "developer" : "user",
515
- content: typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : JSON.stringify(message.content ?? "")
643
+ role,
644
+ content: this.toResponsesContent(message.content, role)
516
645
  });
517
646
  }
518
647
  return input;
519
648
  }
520
- buildResponsesTools(tools) {
649
+ /** Preserve Copilot screenshot attachments on every OpenAI transport. */
650
+ formatRawMessages(messages) {
651
+ return messages.map((message) => {
652
+ const { attachments, ...rest } = message;
653
+ let content = message.content;
654
+ if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
655
+ content = null;
656
+ }
657
+ if (Array.isArray(attachments) && attachments.length > 0) {
658
+ const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
659
+ (attachment) => attachmentToOpenAIImage(attachment)
660
+ ).filter((image) => image !== null);
661
+ if (images.length > 0) {
662
+ const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
663
+ content = [...parts, ...images];
664
+ }
665
+ }
666
+ return { ...rest, content };
667
+ });
668
+ }
669
+ /**
670
+ * Build the web_search tool for the Responses API, or undefined when web
671
+ * search is off.
672
+ *
673
+ * The Chat Completions path uses `web_search_preview`; the Responses API
674
+ * names the same built-in `web_search`. Without this, enabling web search
675
+ * alongside a gpt-5.4+ function tool silently did nothing, because routing
676
+ * to /v1/responses skipped the Chat Completions tool assembly entirely.
677
+ */
678
+ buildResponsesWebSearchTool(request) {
679
+ const webSearchConfig = request.webSearch ?? this.config.webSearch;
680
+ if (!webSearchConfig) return void 0;
681
+ const tool = { type: "web_search" };
682
+ const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
683
+ if (wsConfig.userLocation) {
684
+ tool.search_context_size = "medium";
685
+ }
686
+ return tool;
687
+ }
688
+ /**
689
+ * Extract url_citation annotations from Responses output items.
690
+ *
691
+ * Annotations hang off the output_text content parts rather than arriving as
692
+ * their own stream events, so both the streaming and non-streaming paths read
693
+ * them from the same place.
694
+ */
695
+ collectResponsesCitations(output, startIndex = 0) {
696
+ if (!Array.isArray(output)) return [];
697
+ const citations = [];
698
+ let index = startIndex;
699
+ for (const item of output) {
700
+ if (item?.type !== "message" || !Array.isArray(item.content)) continue;
701
+ for (const part of item.content) {
702
+ if (!Array.isArray(part?.annotations)) continue;
703
+ for (const annotation of part.annotations) {
704
+ const url = annotation?.url;
705
+ if (annotation?.type !== "url_citation" || !url) continue;
706
+ index++;
707
+ const domain = extractDomain(url);
708
+ citations.push({
709
+ index,
710
+ url,
711
+ title: annotation.title || domain,
712
+ domain,
713
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
714
+ });
715
+ }
716
+ }
717
+ }
718
+ return citations;
719
+ }
720
+ buildResponsesTools(tools, includeToolSearch, webSearchTool) {
521
721
  const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
522
722
  type: "function",
523
723
  name: tool.name,
@@ -532,20 +732,22 @@ var OpenAIAdapter = class _OpenAIAdapter {
532
732
  strict: true,
533
733
  defer_loading: tool.deferLoading === true
534
734
  }));
535
- return [{ type: "tool_search" }, ...nativeTools];
735
+ const builtins = [];
736
+ if (includeToolSearch) builtins.push({ type: "tool_search" });
737
+ if (webSearchTool) builtins.push(webSearchTool);
738
+ return [...builtins, ...nativeTools];
536
739
  }
537
740
  parseResponsesResult(response) {
538
- const content = typeof response?.output_text === "string" ? response.output_text : "";
741
+ const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
742
+ (item) => Array.isArray(item.content) ? item.content.filter(
743
+ (part) => part?.type === "output_text" && typeof part.text === "string"
744
+ ).map((part) => part.text) : []
745
+ ).join("") : "";
746
+ const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
539
747
  const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
540
748
  id: item.call_id ?? item.id ?? generateToolCallId(),
541
749
  name: item.name,
542
- args: (() => {
543
- try {
544
- return JSON.parse(item.arguments ?? "{}");
545
- } catch {
546
- return {};
547
- }
548
- })()
750
+ args: JSON.parse(item.arguments ?? "{}")
549
751
  })) : [];
550
752
  return {
551
753
  content,
@@ -558,102 +760,194 @@ var OpenAIAdapter = class _OpenAIAdapter {
558
760
  rawResponse: response
559
761
  };
560
762
  }
561
- async completeWithResponses(request) {
562
- const client = await this.getClient();
763
+ buildResponsesPayload(request, stream) {
563
764
  const openaiToolOptions = request.providerToolOptions?.openai;
564
765
  const responsesTextFormat = toOpenAIResponsesTextFormat(
565
766
  request.config?.responseFormat
566
767
  );
768
+ const modelIdForPayload = request.config?.model || this.model;
567
769
  const payload = {
568
- model: request.config?.model || this.model,
770
+ model: modelIdForPayload,
569
771
  instructions: request.systemPrompt,
570
772
  input: this.buildResponsesInput(request),
571
- tools: this.buildResponsesTools(request.toolDefinitions ?? []),
572
- tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
773
+ tools: this.buildResponsesTools(
774
+ this.toolDefinitions(request),
775
+ request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
776
+ this.buildResponsesWebSearchTool(request)
777
+ ),
778
+ tool_choice: openaiToolOptions?.toolChoice,
573
779
  parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
574
- temperature: request.config?.temperature ?? this.config.temperature,
575
- max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
780
+ ...buildOpenAIParameters(
781
+ modelIdForPayload,
782
+ request.config?.maxTokens ?? this.config.maxTokens,
783
+ request.config?.temperature ?? this.config.temperature,
784
+ this.parameterOptions(request),
785
+ "responses"
786
+ ),
576
787
  ...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
577
- stream: false
788
+ stream
578
789
  };
790
+ return payload;
791
+ }
792
+ async completeWithResponses(request) {
793
+ const client = await this.getClient();
794
+ throwIfAborted(request.signal);
795
+ const payload = this.buildResponsesPayload(request, false);
579
796
  logProviderPayload("openai", "request payload", payload, request.debug);
580
- const response = await client.responses.create(payload);
797
+ if (!client.responses?.create)
798
+ throw new Error(
799
+ "This OpenAI client does not support Responses; install an openai version with Responses support."
800
+ );
801
+ const response = await client.responses.create(payload, {
802
+ signal: request.signal
803
+ });
804
+ throwIfAborted(request.signal);
581
805
  logProviderPayload("openai", "response payload", response, request.debug);
806
+ if (response.status && response.status !== "completed") {
807
+ throw Object.assign(
808
+ new Error(
809
+ response.error?.message ?? `Responses request ${response.status}`
810
+ ),
811
+ response.error
812
+ );
813
+ }
582
814
  return this.parseResponsesResult(response);
583
815
  }
816
+ /**
817
+ * Stream from /v1/responses as real SSE.
818
+ *
819
+ * The Responses API emits semantic events rather than Chat Completions
820
+ * deltas, so the interesting ones are:
821
+ * response.output_text.delta — incremental assistant text
822
+ * response.output_item.added — a function_call item begins (name/id)
823
+ * response.function_call_arguments.delta / .done — tool arguments
824
+ * response.completed — terminal, carries usage
825
+ *
826
+ * Tool calls are buffered and flushed as contiguous start→args→end triples
827
+ * because the runtime pairs action:args with the action:end that follows it.
828
+ */
829
+ async *streamWithResponses(request) {
830
+ const client = await this.getClient();
831
+ throwIfAborted(request.signal);
832
+ const payload = this.buildResponsesPayload(request, true);
833
+ logProviderPayload("openai", "request payload", payload, request.debug);
834
+ if (!client.responses?.create)
835
+ throw new Error(
836
+ "This OpenAI client does not support Responses; install an openai version with Responses support."
837
+ );
838
+ const stream = await client.responses.create(payload, {
839
+ signal: request.signal
840
+ });
841
+ const toolCalls = /* @__PURE__ */ new Map();
842
+ let usage;
843
+ const citations = [];
844
+ let completed = false;
845
+ function* flushToolCalls() {
846
+ for (const call of toolCalls.values()) {
847
+ if (!call.name)
848
+ throw new Error("Responses tool call is missing its name");
849
+ JSON.parse(call.arguments || "{}");
850
+ }
851
+ for (const call of toolCalls.values()) {
852
+ yield { type: "action:start", id: call.id, name: call.name };
853
+ yield { type: "action:args", id: call.id, args: call.arguments };
854
+ yield { type: "action:end", id: call.id, name: call.name };
855
+ }
856
+ toolCalls.clear();
857
+ }
858
+ for await (const event of stream) {
859
+ throwIfAborted(request.signal);
860
+ logProviderPayload("openai", "stream chunk", event, request.debug);
861
+ const index = event?.output_index ?? 0;
862
+ switch (event?.type) {
863
+ case "response.output_text.delta":
864
+ if (event.delta) {
865
+ yield { type: "message:delta", content: event.delta };
866
+ }
867
+ break;
868
+ case "response.output_item.added":
869
+ if (event.item?.type === "function_call") {
870
+ toolCalls.set(index, {
871
+ id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
872
+ name: event.item.name ?? "",
873
+ arguments: event.item.arguments ?? ""
874
+ });
875
+ }
876
+ break;
877
+ case "response.function_call_arguments.delta": {
878
+ const existing = toolCalls.get(index);
879
+ if (existing && event.delta) existing.arguments += event.delta;
880
+ break;
881
+ }
882
+ case "response.function_call_arguments.done": {
883
+ const existing = toolCalls.get(index);
884
+ if (existing && typeof event.arguments === "string") {
885
+ existing.arguments = event.arguments;
886
+ }
887
+ break;
888
+ }
889
+ case "response.completed": {
890
+ completed = true;
891
+ const found = this.collectResponsesCitations(
892
+ event.response?.output,
893
+ citations.length
894
+ );
895
+ if (found.length > 0) citations.push(...found);
896
+ const u = event.response?.usage;
897
+ if (u) {
898
+ usage = {
899
+ prompt_tokens: u.input_tokens ?? 0,
900
+ completion_tokens: u.output_tokens ?? 0,
901
+ total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
902
+ };
903
+ }
904
+ break;
905
+ }
906
+ case "response.incomplete":
907
+ throw new Error(
908
+ `Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
909
+ );
910
+ case "error":
911
+ case "response.failed": {
912
+ const details = event.response?.error ?? event;
913
+ throw Object.assign(
914
+ new Error(details.message ?? "Responses stream failed"),
915
+ {
916
+ code: details.code,
917
+ param: details.param,
918
+ status: details.status
919
+ }
920
+ );
921
+ }
922
+ }
923
+ }
924
+ throwIfAborted(request.signal);
925
+ if (!completed)
926
+ throw new Error("Responses stream ended without a completed response");
927
+ yield* flushToolCalls();
928
+ if (citations.length > 0) {
929
+ yield { type: "citation", citations: deduplicateCitations(citations) };
930
+ }
931
+ yield { type: "message:end" };
932
+ yield { type: "done", usage };
933
+ }
584
934
  async *stream(request) {
935
+ throwIfAborted(request.signal);
585
936
  if (this.shouldUseResponsesApi(request)) {
586
937
  const messageId2 = generateMessageId();
587
938
  yield { type: "message:start", id: messageId2 };
588
939
  try {
589
- const result = await this.completeWithResponses(request);
590
- if (result.content) {
591
- yield { type: "message:delta", content: result.content };
592
- }
593
- for (const toolCall of result.toolCalls) {
594
- yield {
595
- type: "action:start",
596
- id: toolCall.id,
597
- name: toolCall.name
598
- };
599
- yield {
600
- type: "action:args",
601
- id: toolCall.id,
602
- args: JSON.stringify(toolCall.args)
603
- };
604
- }
605
- yield { type: "message:end" };
606
- yield {
607
- type: "done",
608
- usage: result.usage ? {
609
- prompt_tokens: result.usage.promptTokens,
610
- completion_tokens: result.usage.completionTokens,
611
- total_tokens: result.usage.totalTokens
612
- } : void 0
613
- };
940
+ yield* this.streamWithResponses(request);
614
941
  return;
615
942
  } catch (error) {
616
- yield {
617
- type: "error",
618
- message: error instanceof Error ? error.message : "Unknown error",
619
- code: "OPENAI_RESPONSES_ERROR"
620
- };
943
+ yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
621
944
  return;
622
945
  }
623
946
  }
624
947
  const client = await this.getClient();
625
948
  let messages;
626
949
  if (request.rawMessages && request.rawMessages.length > 0) {
627
- const processedMessages = request.rawMessages.map((msg) => {
628
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
629
- return { ...msg, content: null };
630
- }
631
- const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
632
- if (hasAttachments) {
633
- const content = [];
634
- if (msg.content) {
635
- content.push({ type: "text", text: msg.content });
636
- }
637
- for (const attachment of msg.attachments) {
638
- if (attachment.type === "image") {
639
- let imageUrl;
640
- if (attachment.url) {
641
- imageUrl = attachment.url;
642
- } else if (attachment.data) {
643
- imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
644
- } else {
645
- continue;
646
- }
647
- content.push({
648
- type: "image_url",
649
- image_url: { url: imageUrl, detail: "auto" }
650
- });
651
- }
652
- }
653
- return { ...msg, content, attachments: void 0 };
654
- }
655
- return msg;
656
- });
950
+ const processedMessages = this.formatRawMessages(request.rawMessages);
657
951
  if (request.systemPrompt) {
658
952
  const hasSystem = processedMessages.some((m) => m.role === "system");
659
953
  if (!hasSystem) {
@@ -673,7 +967,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
673
967
  request.systemPrompt
674
968
  );
675
969
  }
676
- const tools = request.actions?.length ? formatTools(request.actions) : [];
970
+ const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
971
+ type: "function",
972
+ function: {
973
+ name: tool.name,
974
+ description: tool.description,
975
+ parameters: normalizeObjectJsonSchema(
976
+ tool.inputSchema
977
+ )
978
+ }
979
+ })) : formatTools(request.actions ?? []);
677
980
  const webSearchConfig = request.webSearch ?? this.config.webSearch;
678
981
  if (webSearchConfig) {
679
982
  const webSearchTool = {
@@ -702,26 +1005,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
702
1005
  tools: tools.length > 0 ? tools : void 0,
703
1006
  tool_choice: tools.length > 0 ? toolChoice : void 0,
704
1007
  parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
705
- ...buildOpenAITokenParams(
1008
+ ...buildOpenAIParameters(
706
1009
  modelIdForPayload,
707
1010
  request.config?.maxTokens ?? this.config.maxTokens,
708
- request.config?.temperature ?? this.config.temperature
1011
+ request.config?.temperature ?? this.config.temperature,
1012
+ this.parameterOptions(request)
709
1013
  ),
710
1014
  response_format: toOpenAIResponseFormat(request.config?.responseFormat),
711
1015
  stream: true,
712
1016
  stream_options: { include_usage: true }
713
1017
  };
714
1018
  logProviderPayload("openai", "request payload", payload, request.debug);
715
- const stream = await client.chat.completions.create(payload);
716
- let currentToolCall = null;
1019
+ const stream = await client.chat.completions.create(payload, {
1020
+ signal: request.signal
1021
+ });
1022
+ const toolCalls = /* @__PURE__ */ new Map();
1023
+ let finishReason;
717
1024
  const collectedCitations = [];
718
1025
  let citationIndex = 0;
719
1026
  let usage;
720
1027
  for await (const chunk of stream) {
721
1028
  logProviderPayload("openai", "stream chunk", chunk, request.debug);
722
- if (request.signal?.aborted) {
723
- break;
724
- }
1029
+ throwIfAborted(request.signal);
725
1030
  const delta = chunk.choices[0]?.delta;
726
1031
  const choice = chunk.choices[0];
727
1032
  if (delta?.content) {
@@ -744,38 +1049,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
744
1049
  }
745
1050
  }
746
1051
  }
747
- if (delta?.tool_calls) {
748
- for (const toolCall of delta.tool_calls) {
749
- if (toolCall.id) {
750
- if (currentToolCall) {
751
- yield {
752
- type: "action:args",
753
- id: currentToolCall.id,
754
- args: currentToolCall.arguments
755
- };
756
- yield {
757
- type: "action:end",
758
- id: currentToolCall.id,
759
- name: currentToolCall.name
760
- };
761
- }
762
- const tcExtraContent = toolCall.extra_content;
763
- currentToolCall = {
764
- id: toolCall.id,
765
- name: toolCall.function?.name || "",
766
- arguments: toolCall.function?.arguments || "",
767
- ...tcExtraContent ? { extra_content: tcExtraContent } : {}
768
- };
769
- yield {
770
- type: "action:start",
771
- id: currentToolCall.id,
772
- name: currentToolCall.name,
773
- ...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
774
- };
775
- } else if (currentToolCall && toolCall.function?.arguments) {
776
- currentToolCall.arguments += toolCall.function.arguments;
777
- }
778
- }
1052
+ for (const part of delta?.tool_calls ?? []) {
1053
+ const index = part.index ?? 0;
1054
+ const call = toolCalls.get(index) ?? {
1055
+ id: "",
1056
+ name: "",
1057
+ arguments: ""
1058
+ };
1059
+ if (part.id) call.id = part.id;
1060
+ if (part.function?.name) call.name += part.function.name;
1061
+ if (part.function?.arguments)
1062
+ call.arguments += part.function.arguments;
1063
+ if (part.extra_content) call.extra_content = part.extra_content;
1064
+ toolCalls.set(index, call);
779
1065
  }
780
1066
  if (chunk.usage) {
781
1067
  usage = {
@@ -784,21 +1070,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
784
1070
  total_tokens: chunk.usage.total_tokens
785
1071
  };
786
1072
  }
787
- if (choice?.finish_reason) {
788
- if (currentToolCall) {
789
- yield {
790
- type: "action:args",
791
- id: currentToolCall.id,
792
- args: currentToolCall.arguments
793
- };
794
- yield {
795
- type: "action:end",
796
- id: currentToolCall.id,
797
- name: currentToolCall.name
798
- };
799
- currentToolCall = null;
800
- }
801
- }
1073
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
1074
+ }
1075
+ throwIfAborted(request.signal);
1076
+ if (!finishReason)
1077
+ throw new Error("OpenAI stream ended without a finish reason");
1078
+ if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
1079
+ throw new Error("OpenAI stream ended with incomplete tool calls");
1080
+ }
1081
+ for (const call of toolCalls.values()) {
1082
+ if (!call.id || !call.name)
1083
+ throw new Error("OpenAI stream contains an incomplete tool call");
1084
+ JSON.parse(call.arguments || "{}");
1085
+ }
1086
+ for (const call of toolCalls.values()) {
1087
+ yield {
1088
+ type: "action:start",
1089
+ id: call.id,
1090
+ name: call.name,
1091
+ ...call.extra_content ? { extra_content: call.extra_content } : {}
1092
+ };
1093
+ yield { type: "action:args", id: call.id, args: call.arguments };
1094
+ yield { type: "action:end", id: call.id, name: call.name };
802
1095
  }
803
1096
  if (collectedCitations.length > 0) {
804
1097
  const uniqueCitations = deduplicateCitations(collectedCitations);
@@ -807,26 +1100,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
807
1100
  yield { type: "message:end" };
808
1101
  yield { type: "done", usage };
809
1102
  } catch (error) {
810
- yield {
811
- type: "error",
812
- message: error instanceof Error ? error.message : "Unknown error",
813
- code: `${this.provider.toUpperCase()}_ERROR`
814
- };
1103
+ yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
815
1104
  }
816
1105
  }
817
1106
  async complete(request) {
1107
+ throwIfAborted(request.signal);
818
1108
  if (this.shouldUseResponsesApi(request)) {
819
1109
  return this.completeWithResponses(request);
820
1110
  }
821
1111
  const client = await this.getClient();
822
1112
  let messages;
823
1113
  if (request.rawMessages && request.rawMessages.length > 0) {
824
- const sanitized = request.rawMessages.map((msg) => {
825
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
826
- return { ...msg, content: null };
827
- }
828
- return msg;
829
- });
1114
+ const sanitized = this.formatRawMessages(request.rawMessages);
830
1115
  if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
831
1116
  messages = [
832
1117
  { role: "system", content: request.systemPrompt },
@@ -841,7 +1126,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
841
1126
  request.systemPrompt
842
1127
  );
843
1128
  }
844
- const tools = request.actions?.length ? formatTools(request.actions) : [];
1129
+ const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
1130
+ type: "function",
1131
+ function: {
1132
+ name: tool.name,
1133
+ description: tool.description,
1134
+ parameters: normalizeObjectJsonSchema(
1135
+ tool.inputSchema
1136
+ )
1137
+ }
1138
+ })) : formatTools(request.actions ?? []);
845
1139
  const openaiToolOptions = request.providerToolOptions?.openai;
846
1140
  const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
847
1141
  type: "function",
@@ -856,16 +1150,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
856
1150
  tools: tools.length > 0 ? tools : void 0,
857
1151
  tool_choice: tools.length > 0 ? toolChoice : void 0,
858
1152
  parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
859
- ...buildOpenAITokenParams(
1153
+ ...buildOpenAIParameters(
860
1154
  modelIdForCompletePayload,
861
1155
  request.config?.maxTokens ?? this.config.maxTokens,
862
- request.config?.temperature ?? this.config.temperature
1156
+ request.config?.temperature ?? this.config.temperature,
1157
+ this.parameterOptions(request)
863
1158
  ),
864
1159
  response_format: toOpenAIResponseFormat(request.config?.responseFormat),
865
1160
  stream: false
866
1161
  };
867
1162
  logProviderPayload("openai", "request payload", payload, request.debug);
868
- const response = await client.chat.completions.create(payload);
1163
+ const response = await client.chat.completions.create(payload, {
1164
+ signal: request.signal
1165
+ });
869
1166
  logProviderPayload("openai", "response payload", response, request.debug);
870
1167
  const choice = response.choices?.[0];
871
1168
  const message = choice?.message;