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