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

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