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