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