@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;
@@ -515,6 +561,28 @@ function formatMessagesForGoogle(messages) {
515
561
  });
516
562
  }
517
563
 
564
+ // src/core/provider-error.ts
565
+ function providerErrorEvent(error, code) {
566
+ const details = error ?? {};
567
+ const event = {
568
+ type: "error",
569
+ message: error instanceof Error ? error.message : String(error),
570
+ code,
571
+ ...typeof details.status === "number" ? { status: details.status } : {},
572
+ ...typeof details.code === "string" ? { providerCode: details.code } : {},
573
+ ...typeof details.param === "string" ? { param: details.param } : {},
574
+ ...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
575
+ };
576
+ Object.defineProperty(event, "cause", { value: error, enumerable: false });
577
+ return event;
578
+ }
579
+ function throwIfAborted(signal) {
580
+ if (!signal?.aborted) return;
581
+ const error = new Error("The operation was aborted");
582
+ error.name = "AbortError";
583
+ throw error;
584
+ }
585
+
518
586
  // src/core/utils.ts
519
587
  function generateId(prefix = "id") {
520
588
  return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
@@ -546,24 +614,84 @@ var OpenAIAdapter = class _OpenAIAdapter {
546
614
  const { default: OpenAI } = await import('openai');
547
615
  this.client = new OpenAI({
548
616
  apiKey: this.config.apiKey,
549
- baseURL: this.config.baseUrl
617
+ baseURL: this.config.baseUrl,
618
+ organization: this.config.organization,
619
+ defaultHeaders: this.config.headers
550
620
  });
551
621
  }
552
622
  return this.client;
553
623
  }
624
+ parameterOptions(request) {
625
+ return {
626
+ ...this.config,
627
+ reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
628
+ };
629
+ }
630
+ toolDefinitions(request) {
631
+ if (request.toolDefinitions?.length) return request.toolDefinitions;
632
+ return formatTools(request.actions ?? []).map((tool) => ({
633
+ name: tool.function.name,
634
+ description: tool.function.description ?? "",
635
+ location: "server",
636
+ inputSchema: tool.function.parameters
637
+ }));
638
+ }
554
639
  shouldUseResponsesApi(request) {
555
- return request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false && Array.isArray(request.toolDefinitions) && request.toolDefinitions.length > 0;
640
+ if (this.toolDefinitions(request).length === 0) return false;
641
+ if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
642
+ return true;
643
+ return requiresOpenAIResponses(
644
+ request.config?.model || this.model,
645
+ this.config.baseUrl,
646
+ this.parameterOptions(request)
647
+ );
648
+ }
649
+ /**
650
+ * Convert one message's content to Responses content parts.
651
+ *
652
+ * The Responses API does not accept Chat Completions content blocks: input
653
+ * parts are `input_text` / `input_image` (with a bare `image_url` string)
654
+ * rather than `text` / `image_url: { url }`. Passing the Chat Completions
655
+ * shape through unchanged makes the API reject the request, so anything
656
+ * array-shaped has to be translated rather than forwarded.
657
+ */
658
+ toResponsesContent(content, role) {
659
+ const textType = role === "assistant" ? "output_text" : "input_text";
660
+ if (typeof content === "string") return content;
661
+ if (!Array.isArray(content)) {
662
+ return content == null ? "" : JSON.stringify(content);
663
+ }
664
+ return content.map((part) => {
665
+ const p = part;
666
+ if (p?.type === "text") return { type: textType, text: p.text ?? "" };
667
+ if (p?.type === "image_url") {
668
+ const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
669
+ return {
670
+ type: "input_image",
671
+ image_url: url,
672
+ ...p.image_url?.detail ? { detail: p.image_url.detail } : {}
673
+ };
674
+ }
675
+ return p;
676
+ });
556
677
  }
557
678
  buildResponsesInput(request) {
558
- const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
679
+ const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
559
680
  const input = [];
560
681
  for (const message of sourceMessages) {
561
682
  if (message.role === "system") {
683
+ if (!request.systemPrompt && message.content) {
684
+ input.push({
685
+ type: "message",
686
+ role: "developer",
687
+ content: this.toResponsesContent(message.content, "developer")
688
+ });
689
+ }
562
690
  continue;
563
691
  }
564
692
  if (message.role === "assistant") {
565
- const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : message.content ? JSON.stringify(message.content) : "";
566
- if (content) {
693
+ const content = this.toResponsesContent(message.content, "assistant");
694
+ if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
567
695
  input.push({
568
696
  type: "message",
569
697
  role: "assistant",
@@ -589,15 +717,87 @@ var OpenAIAdapter = class _OpenAIAdapter {
589
717
  });
590
718
  continue;
591
719
  }
720
+ const role = message.role === "developer" ? "developer" : "user";
592
721
  input.push({
593
722
  type: "message",
594
- role: message.role === "developer" ? "developer" : "user",
595
- content: typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content : JSON.stringify(message.content ?? "")
723
+ role,
724
+ content: this.toResponsesContent(message.content, role)
596
725
  });
597
726
  }
598
727
  return input;
599
728
  }
600
- buildResponsesTools(tools) {
729
+ /** Preserve Copilot screenshot attachments on every OpenAI transport. */
730
+ formatRawMessages(messages) {
731
+ return messages.map((message) => {
732
+ const { attachments, ...rest } = message;
733
+ let content = message.content;
734
+ if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
735
+ content = null;
736
+ }
737
+ if (Array.isArray(attachments) && attachments.length > 0) {
738
+ const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
739
+ (attachment) => attachmentToOpenAIImage(attachment)
740
+ ).filter((image) => image !== null);
741
+ if (images.length > 0) {
742
+ const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
743
+ content = [...parts, ...images];
744
+ }
745
+ }
746
+ return { ...rest, content };
747
+ });
748
+ }
749
+ /**
750
+ * Build the web_search tool for the Responses API, or undefined when web
751
+ * search is off.
752
+ *
753
+ * The Chat Completions path uses `web_search_preview`; the Responses API
754
+ * names the same built-in `web_search`. Without this, enabling web search
755
+ * alongside a gpt-5.4+ function tool silently did nothing, because routing
756
+ * to /v1/responses skipped the Chat Completions tool assembly entirely.
757
+ */
758
+ buildResponsesWebSearchTool(request) {
759
+ const webSearchConfig = request.webSearch ?? this.config.webSearch;
760
+ if (!webSearchConfig) return void 0;
761
+ const tool = { type: "web_search" };
762
+ const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
763
+ if (wsConfig.userLocation) {
764
+ tool.search_context_size = "medium";
765
+ }
766
+ return tool;
767
+ }
768
+ /**
769
+ * Extract url_citation annotations from Responses output items.
770
+ *
771
+ * Annotations hang off the output_text content parts rather than arriving as
772
+ * their own stream events, so both the streaming and non-streaming paths read
773
+ * them from the same place.
774
+ */
775
+ collectResponsesCitations(output, startIndex = 0) {
776
+ if (!Array.isArray(output)) return [];
777
+ const citations = [];
778
+ let index = startIndex;
779
+ for (const item of output) {
780
+ if (item?.type !== "message" || !Array.isArray(item.content)) continue;
781
+ for (const part of item.content) {
782
+ if (!Array.isArray(part?.annotations)) continue;
783
+ for (const annotation of part.annotations) {
784
+ const url = annotation?.url;
785
+ if (annotation?.type !== "url_citation" || !url) continue;
786
+ index++;
787
+ const domain = extractDomain(url);
788
+ citations.push({
789
+ index,
790
+ url,
791
+ title: annotation.title || domain,
792
+ domain,
793
+ favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
794
+ });
795
+ }
796
+ }
797
+ }
798
+ return citations;
799
+ }
800
+ buildResponsesTools(tools, includeToolSearch, webSearchTool) {
601
801
  const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
602
802
  type: "function",
603
803
  name: tool.name,
@@ -612,20 +812,22 @@ var OpenAIAdapter = class _OpenAIAdapter {
612
812
  strict: true,
613
813
  defer_loading: tool.deferLoading === true
614
814
  }));
615
- return [{ type: "tool_search" }, ...nativeTools];
815
+ const builtins = [];
816
+ if (includeToolSearch) builtins.push({ type: "tool_search" });
817
+ if (webSearchTool) builtins.push(webSearchTool);
818
+ return [...builtins, ...nativeTools];
616
819
  }
617
820
  parseResponsesResult(response) {
618
- const content = typeof response?.output_text === "string" ? response.output_text : "";
821
+ const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
822
+ (item) => Array.isArray(item.content) ? item.content.filter(
823
+ (part) => part?.type === "output_text" && typeof part.text === "string"
824
+ ).map((part) => part.text) : []
825
+ ).join("") : "";
826
+ const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
619
827
  const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
620
828
  id: item.call_id ?? item.id ?? generateToolCallId(),
621
829
  name: item.name,
622
- args: (() => {
623
- try {
624
- return JSON.parse(item.arguments ?? "{}");
625
- } catch {
626
- return {};
627
- }
628
- })()
830
+ args: JSON.parse(item.arguments ?? "{}")
629
831
  })) : [];
630
832
  return {
631
833
  content,
@@ -638,102 +840,194 @@ var OpenAIAdapter = class _OpenAIAdapter {
638
840
  rawResponse: response
639
841
  };
640
842
  }
641
- async completeWithResponses(request) {
642
- const client = await this.getClient();
843
+ buildResponsesPayload(request, stream) {
643
844
  const openaiToolOptions = request.providerToolOptions?.openai;
644
845
  const responsesTextFormat = toOpenAIResponsesTextFormat(
645
846
  request.config?.responseFormat
646
847
  );
848
+ const modelIdForPayload = request.config?.model || this.model;
647
849
  const payload = {
648
- model: request.config?.model || this.model,
850
+ model: modelIdForPayload,
649
851
  instructions: request.systemPrompt,
650
852
  input: this.buildResponsesInput(request),
651
- tools: this.buildResponsesTools(request.toolDefinitions ?? []),
652
- tool_choice: openaiToolOptions?.toolChoice === "required" ? "required" : openaiToolOptions?.toolChoice === "auto" ? "auto" : void 0,
853
+ tools: this.buildResponsesTools(
854
+ this.toolDefinitions(request),
855
+ request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
856
+ this.buildResponsesWebSearchTool(request)
857
+ ),
858
+ tool_choice: openaiToolOptions?.toolChoice,
653
859
  parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
654
- temperature: request.config?.temperature ?? this.config.temperature,
655
- max_output_tokens: request.config?.maxTokens ?? this.config.maxTokens,
860
+ ...buildOpenAIParameters(
861
+ modelIdForPayload,
862
+ request.config?.maxTokens ?? this.config.maxTokens,
863
+ request.config?.temperature ?? this.config.temperature,
864
+ this.parameterOptions(request),
865
+ "responses"
866
+ ),
656
867
  ...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
657
- stream: false
868
+ stream
658
869
  };
870
+ return payload;
871
+ }
872
+ async completeWithResponses(request) {
873
+ const client = await this.getClient();
874
+ throwIfAborted(request.signal);
875
+ const payload = this.buildResponsesPayload(request, false);
659
876
  logProviderPayload("openai", "request payload", payload, request.debug);
660
- const response = await client.responses.create(payload);
877
+ if (!client.responses?.create)
878
+ throw new Error(
879
+ "This OpenAI client does not support Responses; install an openai version with Responses support."
880
+ );
881
+ const response = await client.responses.create(payload, {
882
+ signal: request.signal
883
+ });
884
+ throwIfAborted(request.signal);
661
885
  logProviderPayload("openai", "response payload", response, request.debug);
886
+ if (response.status && response.status !== "completed") {
887
+ throw Object.assign(
888
+ new Error(
889
+ response.error?.message ?? `Responses request ${response.status}`
890
+ ),
891
+ response.error
892
+ );
893
+ }
662
894
  return this.parseResponsesResult(response);
663
895
  }
896
+ /**
897
+ * Stream from /v1/responses as real SSE.
898
+ *
899
+ * The Responses API emits semantic events rather than Chat Completions
900
+ * deltas, so the interesting ones are:
901
+ * response.output_text.delta — incremental assistant text
902
+ * response.output_item.added — a function_call item begins (name/id)
903
+ * response.function_call_arguments.delta / .done — tool arguments
904
+ * response.completed — terminal, carries usage
905
+ *
906
+ * Tool calls are buffered and flushed as contiguous start→args→end triples
907
+ * because the runtime pairs action:args with the action:end that follows it.
908
+ */
909
+ async *streamWithResponses(request) {
910
+ const client = await this.getClient();
911
+ throwIfAborted(request.signal);
912
+ const payload = this.buildResponsesPayload(request, true);
913
+ logProviderPayload("openai", "request payload", payload, request.debug);
914
+ if (!client.responses?.create)
915
+ throw new Error(
916
+ "This OpenAI client does not support Responses; install an openai version with Responses support."
917
+ );
918
+ const stream = await client.responses.create(payload, {
919
+ signal: request.signal
920
+ });
921
+ const toolCalls = /* @__PURE__ */ new Map();
922
+ let usage;
923
+ const citations = [];
924
+ let completed = false;
925
+ function* flushToolCalls() {
926
+ for (const call of toolCalls.values()) {
927
+ if (!call.name)
928
+ throw new Error("Responses tool call is missing its name");
929
+ JSON.parse(call.arguments || "{}");
930
+ }
931
+ for (const call of toolCalls.values()) {
932
+ yield { type: "action:start", id: call.id, name: call.name };
933
+ yield { type: "action:args", id: call.id, args: call.arguments };
934
+ yield { type: "action:end", id: call.id, name: call.name };
935
+ }
936
+ toolCalls.clear();
937
+ }
938
+ for await (const event of stream) {
939
+ throwIfAborted(request.signal);
940
+ logProviderPayload("openai", "stream chunk", event, request.debug);
941
+ const index = event?.output_index ?? 0;
942
+ switch (event?.type) {
943
+ case "response.output_text.delta":
944
+ if (event.delta) {
945
+ yield { type: "message:delta", content: event.delta };
946
+ }
947
+ break;
948
+ case "response.output_item.added":
949
+ if (event.item?.type === "function_call") {
950
+ toolCalls.set(index, {
951
+ id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
952
+ name: event.item.name ?? "",
953
+ arguments: event.item.arguments ?? ""
954
+ });
955
+ }
956
+ break;
957
+ case "response.function_call_arguments.delta": {
958
+ const existing = toolCalls.get(index);
959
+ if (existing && event.delta) existing.arguments += event.delta;
960
+ break;
961
+ }
962
+ case "response.function_call_arguments.done": {
963
+ const existing = toolCalls.get(index);
964
+ if (existing && typeof event.arguments === "string") {
965
+ existing.arguments = event.arguments;
966
+ }
967
+ break;
968
+ }
969
+ case "response.completed": {
970
+ completed = true;
971
+ const found = this.collectResponsesCitations(
972
+ event.response?.output,
973
+ citations.length
974
+ );
975
+ if (found.length > 0) citations.push(...found);
976
+ const u = event.response?.usage;
977
+ if (u) {
978
+ usage = {
979
+ prompt_tokens: u.input_tokens ?? 0,
980
+ completion_tokens: u.output_tokens ?? 0,
981
+ total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
982
+ };
983
+ }
984
+ break;
985
+ }
986
+ case "response.incomplete":
987
+ throw new Error(
988
+ `Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
989
+ );
990
+ case "error":
991
+ case "response.failed": {
992
+ const details = event.response?.error ?? event;
993
+ throw Object.assign(
994
+ new Error(details.message ?? "Responses stream failed"),
995
+ {
996
+ code: details.code,
997
+ param: details.param,
998
+ status: details.status
999
+ }
1000
+ );
1001
+ }
1002
+ }
1003
+ }
1004
+ throwIfAborted(request.signal);
1005
+ if (!completed)
1006
+ throw new Error("Responses stream ended without a completed response");
1007
+ yield* flushToolCalls();
1008
+ if (citations.length > 0) {
1009
+ yield { type: "citation", citations: deduplicateCitations(citations) };
1010
+ }
1011
+ yield { type: "message:end" };
1012
+ yield { type: "done", usage };
1013
+ }
664
1014
  async *stream(request) {
1015
+ throwIfAborted(request.signal);
665
1016
  if (this.shouldUseResponsesApi(request)) {
666
1017
  const messageId2 = generateMessageId();
667
1018
  yield { type: "message:start", id: messageId2 };
668
1019
  try {
669
- const result = await this.completeWithResponses(request);
670
- if (result.content) {
671
- yield { type: "message:delta", content: result.content };
672
- }
673
- for (const toolCall of result.toolCalls) {
674
- yield {
675
- type: "action:start",
676
- id: toolCall.id,
677
- name: toolCall.name
678
- };
679
- yield {
680
- type: "action:args",
681
- id: toolCall.id,
682
- args: JSON.stringify(toolCall.args)
683
- };
684
- }
685
- yield { type: "message:end" };
686
- yield {
687
- type: "done",
688
- usage: result.usage ? {
689
- prompt_tokens: result.usage.promptTokens,
690
- completion_tokens: result.usage.completionTokens,
691
- total_tokens: result.usage.totalTokens
692
- } : void 0
693
- };
1020
+ yield* this.streamWithResponses(request);
694
1021
  return;
695
1022
  } catch (error) {
696
- yield {
697
- type: "error",
698
- message: error instanceof Error ? error.message : "Unknown error",
699
- code: "OPENAI_RESPONSES_ERROR"
700
- };
1023
+ yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
701
1024
  return;
702
1025
  }
703
1026
  }
704
1027
  const client = await this.getClient();
705
1028
  let messages;
706
1029
  if (request.rawMessages && request.rawMessages.length > 0) {
707
- const processedMessages = request.rawMessages.map((msg) => {
708
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
709
- return { ...msg, content: null };
710
- }
711
- const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
712
- if (hasAttachments) {
713
- const content = [];
714
- if (msg.content) {
715
- content.push({ type: "text", text: msg.content });
716
- }
717
- for (const attachment of msg.attachments) {
718
- if (attachment.type === "image") {
719
- let imageUrl;
720
- if (attachment.url) {
721
- imageUrl = attachment.url;
722
- } else if (attachment.data) {
723
- imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
724
- } else {
725
- continue;
726
- }
727
- content.push({
728
- type: "image_url",
729
- image_url: { url: imageUrl, detail: "auto" }
730
- });
731
- }
732
- }
733
- return { ...msg, content, attachments: void 0 };
734
- }
735
- return msg;
736
- });
1030
+ const processedMessages = this.formatRawMessages(request.rawMessages);
737
1031
  if (request.systemPrompt) {
738
1032
  const hasSystem = processedMessages.some((m) => m.role === "system");
739
1033
  if (!hasSystem) {
@@ -753,7 +1047,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
753
1047
  request.systemPrompt
754
1048
  );
755
1049
  }
756
- const tools = request.actions?.length ? formatTools(request.actions) : [];
1050
+ const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
1051
+ type: "function",
1052
+ function: {
1053
+ name: tool.name,
1054
+ description: tool.description,
1055
+ parameters: normalizeObjectJsonSchema(
1056
+ tool.inputSchema
1057
+ )
1058
+ }
1059
+ })) : formatTools(request.actions ?? []);
757
1060
  const webSearchConfig = request.webSearch ?? this.config.webSearch;
758
1061
  if (webSearchConfig) {
759
1062
  const webSearchTool = {
@@ -782,26 +1085,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
782
1085
  tools: tools.length > 0 ? tools : void 0,
783
1086
  tool_choice: tools.length > 0 ? toolChoice : void 0,
784
1087
  parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
785
- ...buildOpenAITokenParams(
1088
+ ...buildOpenAIParameters(
786
1089
  modelIdForPayload,
787
1090
  request.config?.maxTokens ?? this.config.maxTokens,
788
- request.config?.temperature ?? this.config.temperature
1091
+ request.config?.temperature ?? this.config.temperature,
1092
+ this.parameterOptions(request)
789
1093
  ),
790
1094
  response_format: toOpenAIResponseFormat(request.config?.responseFormat),
791
1095
  stream: true,
792
1096
  stream_options: { include_usage: true }
793
1097
  };
794
1098
  logProviderPayload("openai", "request payload", payload, request.debug);
795
- const stream = await client.chat.completions.create(payload);
796
- let currentToolCall = null;
1099
+ const stream = await client.chat.completions.create(payload, {
1100
+ signal: request.signal
1101
+ });
1102
+ const toolCalls = /* @__PURE__ */ new Map();
1103
+ let finishReason;
797
1104
  const collectedCitations = [];
798
1105
  let citationIndex = 0;
799
1106
  let usage;
800
1107
  for await (const chunk of stream) {
801
1108
  logProviderPayload("openai", "stream chunk", chunk, request.debug);
802
- if (request.signal?.aborted) {
803
- break;
804
- }
1109
+ throwIfAborted(request.signal);
805
1110
  const delta = chunk.choices[0]?.delta;
806
1111
  const choice = chunk.choices[0];
807
1112
  if (delta?.content) {
@@ -824,38 +1129,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
824
1129
  }
825
1130
  }
826
1131
  }
827
- if (delta?.tool_calls) {
828
- for (const toolCall of delta.tool_calls) {
829
- if (toolCall.id) {
830
- if (currentToolCall) {
831
- yield {
832
- type: "action:args",
833
- id: currentToolCall.id,
834
- args: currentToolCall.arguments
835
- };
836
- yield {
837
- type: "action:end",
838
- id: currentToolCall.id,
839
- name: currentToolCall.name
840
- };
841
- }
842
- const tcExtraContent = toolCall.extra_content;
843
- currentToolCall = {
844
- id: toolCall.id,
845
- name: toolCall.function?.name || "",
846
- arguments: toolCall.function?.arguments || "",
847
- ...tcExtraContent ? { extra_content: tcExtraContent } : {}
848
- };
849
- yield {
850
- type: "action:start",
851
- id: currentToolCall.id,
852
- name: currentToolCall.name,
853
- ...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
854
- };
855
- } else if (currentToolCall && toolCall.function?.arguments) {
856
- currentToolCall.arguments += toolCall.function.arguments;
857
- }
858
- }
1132
+ for (const part of delta?.tool_calls ?? []) {
1133
+ const index = part.index ?? 0;
1134
+ const call = toolCalls.get(index) ?? {
1135
+ id: "",
1136
+ name: "",
1137
+ arguments: ""
1138
+ };
1139
+ if (part.id) call.id = part.id;
1140
+ if (part.function?.name) call.name += part.function.name;
1141
+ if (part.function?.arguments)
1142
+ call.arguments += part.function.arguments;
1143
+ if (part.extra_content) call.extra_content = part.extra_content;
1144
+ toolCalls.set(index, call);
859
1145
  }
860
1146
  if (chunk.usage) {
861
1147
  usage = {
@@ -864,21 +1150,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
864
1150
  total_tokens: chunk.usage.total_tokens
865
1151
  };
866
1152
  }
867
- if (choice?.finish_reason) {
868
- if (currentToolCall) {
869
- yield {
870
- type: "action:args",
871
- id: currentToolCall.id,
872
- args: currentToolCall.arguments
873
- };
874
- yield {
875
- type: "action:end",
876
- id: currentToolCall.id,
877
- name: currentToolCall.name
878
- };
879
- currentToolCall = null;
880
- }
881
- }
1153
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
1154
+ }
1155
+ throwIfAborted(request.signal);
1156
+ if (!finishReason)
1157
+ throw new Error("OpenAI stream ended without a finish reason");
1158
+ if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
1159
+ throw new Error("OpenAI stream ended with incomplete tool calls");
1160
+ }
1161
+ for (const call of toolCalls.values()) {
1162
+ if (!call.id || !call.name)
1163
+ throw new Error("OpenAI stream contains an incomplete tool call");
1164
+ JSON.parse(call.arguments || "{}");
1165
+ }
1166
+ for (const call of toolCalls.values()) {
1167
+ yield {
1168
+ type: "action:start",
1169
+ id: call.id,
1170
+ name: call.name,
1171
+ ...call.extra_content ? { extra_content: call.extra_content } : {}
1172
+ };
1173
+ yield { type: "action:args", id: call.id, args: call.arguments };
1174
+ yield { type: "action:end", id: call.id, name: call.name };
882
1175
  }
883
1176
  if (collectedCitations.length > 0) {
884
1177
  const uniqueCitations = deduplicateCitations(collectedCitations);
@@ -887,26 +1180,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
887
1180
  yield { type: "message:end" };
888
1181
  yield { type: "done", usage };
889
1182
  } catch (error) {
890
- yield {
891
- type: "error",
892
- message: error instanceof Error ? error.message : "Unknown error",
893
- code: `${this.provider.toUpperCase()}_ERROR`
894
- };
1183
+ yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
895
1184
  }
896
1185
  }
897
1186
  async complete(request) {
1187
+ throwIfAborted(request.signal);
898
1188
  if (this.shouldUseResponsesApi(request)) {
899
1189
  return this.completeWithResponses(request);
900
1190
  }
901
1191
  const client = await this.getClient();
902
1192
  let messages;
903
1193
  if (request.rawMessages && request.rawMessages.length > 0) {
904
- const sanitized = request.rawMessages.map((msg) => {
905
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
906
- return { ...msg, content: null };
907
- }
908
- return msg;
909
- });
1194
+ const sanitized = this.formatRawMessages(request.rawMessages);
910
1195
  if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
911
1196
  messages = [
912
1197
  { role: "system", content: request.systemPrompt },
@@ -921,7 +1206,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
921
1206
  request.systemPrompt
922
1207
  );
923
1208
  }
924
- const tools = request.actions?.length ? formatTools(request.actions) : [];
1209
+ const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
1210
+ type: "function",
1211
+ function: {
1212
+ name: tool.name,
1213
+ description: tool.description,
1214
+ parameters: normalizeObjectJsonSchema(
1215
+ tool.inputSchema
1216
+ )
1217
+ }
1218
+ })) : formatTools(request.actions ?? []);
925
1219
  const openaiToolOptions = request.providerToolOptions?.openai;
926
1220
  const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
927
1221
  type: "function",
@@ -936,16 +1230,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
936
1230
  tools: tools.length > 0 ? tools : void 0,
937
1231
  tool_choice: tools.length > 0 ? toolChoice : void 0,
938
1232
  parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
939
- ...buildOpenAITokenParams(
1233
+ ...buildOpenAIParameters(
940
1234
  modelIdForCompletePayload,
941
1235
  request.config?.maxTokens ?? this.config.maxTokens,
942
- request.config?.temperature ?? this.config.temperature
1236
+ request.config?.temperature ?? this.config.temperature,
1237
+ this.parameterOptions(request)
943
1238
  ),
944
1239
  response_format: toOpenAIResponseFormat(request.config?.responseFormat),
945
1240
  stream: false
946
1241
  };
947
1242
  logProviderPayload("openai", "request payload", payload, request.debug);
948
- const response = await client.chat.completions.create(payload);
1243
+ const response = await client.chat.completions.create(payload, {
1244
+ signal: request.signal
1245
+ });
949
1246
  logProviderPayload("openai", "response payload", response, request.debug);
950
1247
  const choice = response.choices?.[0];
951
1248
  const message = choice?.message;