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