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