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

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