@yourgpt/llm-sdk 2.5.1-beta.6 → 2.6.0-beta.1

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