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