@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.
- package/README.md +77 -0
- package/dist/adapters/index.d.mts +54 -5
- package/dist/adapters/index.d.ts +54 -5
- package/dist/adapters/index.js +466 -169
- package/dist/adapters/index.mjs +466 -169
- package/dist/{base-C58Dsr9p.d.ts → base-BdTpUAWI.d.ts} +2 -1
- package/dist/{base-tNgbBaSo.d.mts → base-CkeT7u-O.d.mts} +2 -1
- package/dist/fallback/index.d.mts +4 -4
- package/dist/fallback/index.d.ts +4 -4
- package/dist/fallback/index.js +75 -39
- package/dist/fallback/index.mjs +75 -39
- package/dist/index.d.mts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +89 -30
- package/dist/index.mjs +89 -30
- package/dist/providers/anthropic/index.d.mts +3 -3
- package/dist/providers/anthropic/index.d.ts +3 -3
- package/dist/providers/azure/index.d.mts +3 -3
- package/dist/providers/azure/index.d.ts +3 -3
- package/dist/providers/azure/index.js +8 -3
- package/dist/providers/azure/index.mjs +8 -3
- package/dist/providers/fireworks/index.d.mts +3 -3
- package/dist/providers/fireworks/index.d.ts +3 -3
- package/dist/providers/fireworks/index.js +466 -169
- package/dist/providers/fireworks/index.mjs +466 -169
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +466 -169
- package/dist/providers/google/index.mjs +466 -169
- package/dist/providers/ollama/index.d.mts +4 -4
- package/dist/providers/ollama/index.d.ts +4 -4
- package/dist/providers/openai/index.d.mts +6 -23
- package/dist/providers/openai/index.d.ts +6 -23
- package/dist/providers/openai/index.js +912 -500
- package/dist/providers/openai/index.mjs +912 -500
- package/dist/providers/openrouter/index.d.mts +3 -3
- package/dist/providers/openrouter/index.d.ts +3 -3
- package/dist/providers/openrouter/index.js +466 -169
- package/dist/providers/openrouter/index.mjs +466 -169
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +466 -169
- package/dist/providers/togetherai/index.mjs +466 -169
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +466 -169
- package/dist/providers/xai/index.mjs +466 -169
- package/dist/{types-BkQCSiIt.d.mts → types-BW3csYo5.d.mts} +22 -1
- package/dist/{types-BkQCSiIt.d.ts → types-BW3csYo5.d.ts} +22 -1
- package/dist/{types-B6dhnguR.d.mts → types-BWRYIvl_.d.mts} +11 -4
- package/dist/{types-CCxPmkmK.d.ts → types-CpXcT-tY.d.ts} +11 -4
- package/dist/{types-BQ31QIsA.d.ts → types-DA54bSmX.d.ts} +3 -11
- package/dist/{types-BSSiJW2o.d.mts → types-NOswlhm9.d.mts} +3 -11
- package/dist/yourgpt/index.d.mts +1 -1
- package/dist/yourgpt/index.d.ts +1 -1
- package/package.json +115 -48
- 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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
158
|
+
return normalized;
|
|
113
159
|
}
|
|
114
160
|
function toOpenAIResponseFormat(rf) {
|
|
115
161
|
if (!rf) return void 0;
|
|
@@ -517,6 +563,28 @@ function formatMessagesForGoogle(messages) {
|
|
|
517
563
|
});
|
|
518
564
|
}
|
|
519
565
|
|
|
566
|
+
// src/core/provider-error.ts
|
|
567
|
+
function providerErrorEvent(error, code) {
|
|
568
|
+
const details = error ?? {};
|
|
569
|
+
const event = {
|
|
570
|
+
type: "error",
|
|
571
|
+
message: error instanceof Error ? error.message : String(error),
|
|
572
|
+
code,
|
|
573
|
+
...typeof details.status === "number" ? { status: details.status } : {},
|
|
574
|
+
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
575
|
+
...typeof details.param === "string" ? { param: details.param } : {},
|
|
576
|
+
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
577
|
+
};
|
|
578
|
+
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
579
|
+
return event;
|
|
580
|
+
}
|
|
581
|
+
function throwIfAborted(signal) {
|
|
582
|
+
if (!signal?.aborted) return;
|
|
583
|
+
const error = new Error("The operation was aborted");
|
|
584
|
+
error.name = "AbortError";
|
|
585
|
+
throw error;
|
|
586
|
+
}
|
|
587
|
+
|
|
520
588
|
// src/core/utils.ts
|
|
521
589
|
function generateId(prefix = "id") {
|
|
522
590
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -548,24 +616,84 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
548
616
|
const { default: OpenAI } = await import('openai');
|
|
549
617
|
this.client = new OpenAI({
|
|
550
618
|
apiKey: this.config.apiKey,
|
|
551
|
-
baseURL: this.config.baseUrl
|
|
619
|
+
baseURL: this.config.baseUrl,
|
|
620
|
+
organization: this.config.organization,
|
|
621
|
+
defaultHeaders: this.config.headers
|
|
552
622
|
});
|
|
553
623
|
}
|
|
554
624
|
return this.client;
|
|
555
625
|
}
|
|
626
|
+
parameterOptions(request) {
|
|
627
|
+
return {
|
|
628
|
+
...this.config,
|
|
629
|
+
reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
toolDefinitions(request) {
|
|
633
|
+
if (request.toolDefinitions?.length) return request.toolDefinitions;
|
|
634
|
+
return formatTools(request.actions ?? []).map((tool) => ({
|
|
635
|
+
name: tool.function.name,
|
|
636
|
+
description: tool.function.description ?? "",
|
|
637
|
+
location: "server",
|
|
638
|
+
inputSchema: tool.function.parameters
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
556
641
|
shouldUseResponsesApi(request) {
|
|
557
|
-
|
|
642
|
+
if (this.toolDefinitions(request).length === 0) return false;
|
|
643
|
+
if (request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false)
|
|
644
|
+
return true;
|
|
645
|
+
return requiresOpenAIResponses(
|
|
646
|
+
request.config?.model || this.model,
|
|
647
|
+
this.config.baseUrl,
|
|
648
|
+
this.parameterOptions(request)
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Convert one message's content to Responses content parts.
|
|
653
|
+
*
|
|
654
|
+
* The Responses API does not accept Chat Completions content blocks: input
|
|
655
|
+
* parts are `input_text` / `input_image` (with a bare `image_url` string)
|
|
656
|
+
* rather than `text` / `image_url: { url }`. Passing the Chat Completions
|
|
657
|
+
* shape through unchanged makes the API reject the request, so anything
|
|
658
|
+
* array-shaped has to be translated rather than forwarded.
|
|
659
|
+
*/
|
|
660
|
+
toResponsesContent(content, role) {
|
|
661
|
+
const textType = role === "assistant" ? "output_text" : "input_text";
|
|
662
|
+
if (typeof content === "string") return content;
|
|
663
|
+
if (!Array.isArray(content)) {
|
|
664
|
+
return content == null ? "" : JSON.stringify(content);
|
|
665
|
+
}
|
|
666
|
+
return content.map((part) => {
|
|
667
|
+
const p = part;
|
|
668
|
+
if (p?.type === "text") return { type: textType, text: p.text ?? "" };
|
|
669
|
+
if (p?.type === "image_url") {
|
|
670
|
+
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
671
|
+
return {
|
|
672
|
+
type: "input_image",
|
|
673
|
+
image_url: url,
|
|
674
|
+
...p.image_url?.detail ? { detail: p.image_url.detail } : {}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return p;
|
|
678
|
+
});
|
|
558
679
|
}
|
|
559
680
|
buildResponsesInput(request) {
|
|
560
|
-
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
681
|
+
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages) : formatMessagesForOpenAI(request.messages, void 0);
|
|
561
682
|
const input = [];
|
|
562
683
|
for (const message of sourceMessages) {
|
|
563
684
|
if (message.role === "system") {
|
|
685
|
+
if (!request.systemPrompt && message.content) {
|
|
686
|
+
input.push({
|
|
687
|
+
type: "message",
|
|
688
|
+
role: "developer",
|
|
689
|
+
content: this.toResponsesContent(message.content, "developer")
|
|
690
|
+
});
|
|
691
|
+
}
|
|
564
692
|
continue;
|
|
565
693
|
}
|
|
566
694
|
if (message.role === "assistant") {
|
|
567
|
-
const content =
|
|
568
|
-
if (content) {
|
|
695
|
+
const content = this.toResponsesContent(message.content, "assistant");
|
|
696
|
+
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
569
697
|
input.push({
|
|
570
698
|
type: "message",
|
|
571
699
|
role: "assistant",
|
|
@@ -591,15 +719,87 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
591
719
|
});
|
|
592
720
|
continue;
|
|
593
721
|
}
|
|
722
|
+
const role = message.role === "developer" ? "developer" : "user";
|
|
594
723
|
input.push({
|
|
595
724
|
type: "message",
|
|
596
|
-
role
|
|
597
|
-
content:
|
|
725
|
+
role,
|
|
726
|
+
content: this.toResponsesContent(message.content, role)
|
|
598
727
|
});
|
|
599
728
|
}
|
|
600
729
|
return input;
|
|
601
730
|
}
|
|
602
|
-
|
|
731
|
+
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
732
|
+
formatRawMessages(messages) {
|
|
733
|
+
return messages.map((message) => {
|
|
734
|
+
const { attachments, ...rest } = message;
|
|
735
|
+
let content = message.content;
|
|
736
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
|
|
737
|
+
content = null;
|
|
738
|
+
}
|
|
739
|
+
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
740
|
+
const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
|
|
741
|
+
(attachment) => attachmentToOpenAIImage(attachment)
|
|
742
|
+
).filter((image) => image !== null);
|
|
743
|
+
if (images.length > 0) {
|
|
744
|
+
const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
|
|
745
|
+
content = [...parts, ...images];
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return { ...rest, content };
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Build the web_search tool for the Responses API, or undefined when web
|
|
753
|
+
* search is off.
|
|
754
|
+
*
|
|
755
|
+
* The Chat Completions path uses `web_search_preview`; the Responses API
|
|
756
|
+
* names the same built-in `web_search`. Without this, enabling web search
|
|
757
|
+
* alongside a gpt-5.4+ function tool silently did nothing, because routing
|
|
758
|
+
* to /v1/responses skipped the Chat Completions tool assembly entirely.
|
|
759
|
+
*/
|
|
760
|
+
buildResponsesWebSearchTool(request) {
|
|
761
|
+
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
762
|
+
if (!webSearchConfig) return void 0;
|
|
763
|
+
const tool = { type: "web_search" };
|
|
764
|
+
const wsConfig = typeof webSearchConfig === "object" ? webSearchConfig : {};
|
|
765
|
+
if (wsConfig.userLocation) {
|
|
766
|
+
tool.search_context_size = "medium";
|
|
767
|
+
}
|
|
768
|
+
return tool;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Extract url_citation annotations from Responses output items.
|
|
772
|
+
*
|
|
773
|
+
* Annotations hang off the output_text content parts rather than arriving as
|
|
774
|
+
* their own stream events, so both the streaming and non-streaming paths read
|
|
775
|
+
* them from the same place.
|
|
776
|
+
*/
|
|
777
|
+
collectResponsesCitations(output, startIndex = 0) {
|
|
778
|
+
if (!Array.isArray(output)) return [];
|
|
779
|
+
const citations = [];
|
|
780
|
+
let index = startIndex;
|
|
781
|
+
for (const item of output) {
|
|
782
|
+
if (item?.type !== "message" || !Array.isArray(item.content)) continue;
|
|
783
|
+
for (const part of item.content) {
|
|
784
|
+
if (!Array.isArray(part?.annotations)) continue;
|
|
785
|
+
for (const annotation of part.annotations) {
|
|
786
|
+
const url = annotation?.url;
|
|
787
|
+
if (annotation?.type !== "url_citation" || !url) continue;
|
|
788
|
+
index++;
|
|
789
|
+
const domain = extractDomain(url);
|
|
790
|
+
citations.push({
|
|
791
|
+
index,
|
|
792
|
+
url,
|
|
793
|
+
title: annotation.title || domain,
|
|
794
|
+
domain,
|
|
795
|
+
favicon: domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=32` : void 0
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return citations;
|
|
801
|
+
}
|
|
802
|
+
buildResponsesTools(tools, includeToolSearch, webSearchTool) {
|
|
603
803
|
const nativeTools = tools.filter((tool) => tool.available !== false).map((tool) => ({
|
|
604
804
|
type: "function",
|
|
605
805
|
name: tool.name,
|
|
@@ -614,20 +814,22 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
614
814
|
strict: true,
|
|
615
815
|
defer_loading: tool.deferLoading === true
|
|
616
816
|
}));
|
|
617
|
-
|
|
817
|
+
const builtins = [];
|
|
818
|
+
if (includeToolSearch) builtins.push({ type: "tool_search" });
|
|
819
|
+
if (webSearchTool) builtins.push(webSearchTool);
|
|
820
|
+
return [...builtins, ...nativeTools];
|
|
618
821
|
}
|
|
619
822
|
parseResponsesResult(response) {
|
|
620
|
-
const
|
|
823
|
+
const textFromOutput = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "message").flatMap(
|
|
824
|
+
(item) => Array.isArray(item.content) ? item.content.filter(
|
|
825
|
+
(part) => part?.type === "output_text" && typeof part.text === "string"
|
|
826
|
+
).map((part) => part.text) : []
|
|
827
|
+
).join("") : "";
|
|
828
|
+
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
621
829
|
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => ({
|
|
622
830
|
id: item.call_id ?? item.id ?? generateToolCallId(),
|
|
623
831
|
name: item.name,
|
|
624
|
-
args: (
|
|
625
|
-
try {
|
|
626
|
-
return JSON.parse(item.arguments ?? "{}");
|
|
627
|
-
} catch {
|
|
628
|
-
return {};
|
|
629
|
-
}
|
|
630
|
-
})()
|
|
832
|
+
args: JSON.parse(item.arguments ?? "{}")
|
|
631
833
|
})) : [];
|
|
632
834
|
return {
|
|
633
835
|
content,
|
|
@@ -640,102 +842,194 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
640
842
|
rawResponse: response
|
|
641
843
|
};
|
|
642
844
|
}
|
|
643
|
-
|
|
644
|
-
const client = await this.getClient();
|
|
845
|
+
buildResponsesPayload(request, stream) {
|
|
645
846
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
646
847
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
647
848
|
request.config?.responseFormat
|
|
648
849
|
);
|
|
850
|
+
const modelIdForPayload = request.config?.model || this.model;
|
|
649
851
|
const payload = {
|
|
650
|
-
model:
|
|
852
|
+
model: modelIdForPayload,
|
|
651
853
|
instructions: request.systemPrompt,
|
|
652
854
|
input: this.buildResponsesInput(request),
|
|
653
|
-
tools: this.buildResponsesTools(
|
|
654
|
-
|
|
855
|
+
tools: this.buildResponsesTools(
|
|
856
|
+
this.toolDefinitions(request),
|
|
857
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
858
|
+
this.buildResponsesWebSearchTool(request)
|
|
859
|
+
),
|
|
860
|
+
tool_choice: openaiToolOptions?.toolChoice,
|
|
655
861
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
656
|
-
|
|
657
|
-
|
|
862
|
+
...buildOpenAIParameters(
|
|
863
|
+
modelIdForPayload,
|
|
864
|
+
request.config?.maxTokens ?? this.config.maxTokens,
|
|
865
|
+
request.config?.temperature ?? this.config.temperature,
|
|
866
|
+
this.parameterOptions(request),
|
|
867
|
+
"responses"
|
|
868
|
+
),
|
|
658
869
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
659
|
-
stream
|
|
870
|
+
stream
|
|
660
871
|
};
|
|
872
|
+
return payload;
|
|
873
|
+
}
|
|
874
|
+
async completeWithResponses(request) {
|
|
875
|
+
const client = await this.getClient();
|
|
876
|
+
throwIfAborted(request.signal);
|
|
877
|
+
const payload = this.buildResponsesPayload(request, false);
|
|
661
878
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
662
|
-
|
|
879
|
+
if (!client.responses?.create)
|
|
880
|
+
throw new Error(
|
|
881
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
882
|
+
);
|
|
883
|
+
const response = await client.responses.create(payload, {
|
|
884
|
+
signal: request.signal
|
|
885
|
+
});
|
|
886
|
+
throwIfAborted(request.signal);
|
|
663
887
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
888
|
+
if (response.status && response.status !== "completed") {
|
|
889
|
+
throw Object.assign(
|
|
890
|
+
new Error(
|
|
891
|
+
response.error?.message ?? `Responses request ${response.status}`
|
|
892
|
+
),
|
|
893
|
+
response.error
|
|
894
|
+
);
|
|
895
|
+
}
|
|
664
896
|
return this.parseResponsesResult(response);
|
|
665
897
|
}
|
|
898
|
+
/**
|
|
899
|
+
* Stream from /v1/responses as real SSE.
|
|
900
|
+
*
|
|
901
|
+
* The Responses API emits semantic events rather than Chat Completions
|
|
902
|
+
* deltas, so the interesting ones are:
|
|
903
|
+
* response.output_text.delta — incremental assistant text
|
|
904
|
+
* response.output_item.added — a function_call item begins (name/id)
|
|
905
|
+
* response.function_call_arguments.delta / .done — tool arguments
|
|
906
|
+
* response.completed — terminal, carries usage
|
|
907
|
+
*
|
|
908
|
+
* Tool calls are buffered and flushed as contiguous start→args→end triples
|
|
909
|
+
* because the runtime pairs action:args with the action:end that follows it.
|
|
910
|
+
*/
|
|
911
|
+
async *streamWithResponses(request) {
|
|
912
|
+
const client = await this.getClient();
|
|
913
|
+
throwIfAborted(request.signal);
|
|
914
|
+
const payload = this.buildResponsesPayload(request, true);
|
|
915
|
+
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
916
|
+
if (!client.responses?.create)
|
|
917
|
+
throw new Error(
|
|
918
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
919
|
+
);
|
|
920
|
+
const stream = await client.responses.create(payload, {
|
|
921
|
+
signal: request.signal
|
|
922
|
+
});
|
|
923
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
924
|
+
let usage;
|
|
925
|
+
const citations = [];
|
|
926
|
+
let completed = false;
|
|
927
|
+
function* flushToolCalls() {
|
|
928
|
+
for (const call of toolCalls.values()) {
|
|
929
|
+
if (!call.name)
|
|
930
|
+
throw new Error("Responses tool call is missing its name");
|
|
931
|
+
JSON.parse(call.arguments || "{}");
|
|
932
|
+
}
|
|
933
|
+
for (const call of toolCalls.values()) {
|
|
934
|
+
yield { type: "action:start", id: call.id, name: call.name };
|
|
935
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
936
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
937
|
+
}
|
|
938
|
+
toolCalls.clear();
|
|
939
|
+
}
|
|
940
|
+
for await (const event of stream) {
|
|
941
|
+
throwIfAborted(request.signal);
|
|
942
|
+
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
943
|
+
const index = event?.output_index ?? 0;
|
|
944
|
+
switch (event?.type) {
|
|
945
|
+
case "response.output_text.delta":
|
|
946
|
+
if (event.delta) {
|
|
947
|
+
yield { type: "message:delta", content: event.delta };
|
|
948
|
+
}
|
|
949
|
+
break;
|
|
950
|
+
case "response.output_item.added":
|
|
951
|
+
if (event.item?.type === "function_call") {
|
|
952
|
+
toolCalls.set(index, {
|
|
953
|
+
id: event.item.call_id ?? event.item.id ?? generateToolCallId(),
|
|
954
|
+
name: event.item.name ?? "",
|
|
955
|
+
arguments: event.item.arguments ?? ""
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
break;
|
|
959
|
+
case "response.function_call_arguments.delta": {
|
|
960
|
+
const existing = toolCalls.get(index);
|
|
961
|
+
if (existing && event.delta) existing.arguments += event.delta;
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
case "response.function_call_arguments.done": {
|
|
965
|
+
const existing = toolCalls.get(index);
|
|
966
|
+
if (existing && typeof event.arguments === "string") {
|
|
967
|
+
existing.arguments = event.arguments;
|
|
968
|
+
}
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
case "response.completed": {
|
|
972
|
+
completed = true;
|
|
973
|
+
const found = this.collectResponsesCitations(
|
|
974
|
+
event.response?.output,
|
|
975
|
+
citations.length
|
|
976
|
+
);
|
|
977
|
+
if (found.length > 0) citations.push(...found);
|
|
978
|
+
const u = event.response?.usage;
|
|
979
|
+
if (u) {
|
|
980
|
+
usage = {
|
|
981
|
+
prompt_tokens: u.input_tokens ?? 0,
|
|
982
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
983
|
+
total_tokens: u.total_tokens ?? (u.input_tokens ?? 0) + (u.output_tokens ?? 0)
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
case "response.incomplete":
|
|
989
|
+
throw new Error(
|
|
990
|
+
`Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
|
|
991
|
+
);
|
|
992
|
+
case "error":
|
|
993
|
+
case "response.failed": {
|
|
994
|
+
const details = event.response?.error ?? event;
|
|
995
|
+
throw Object.assign(
|
|
996
|
+
new Error(details.message ?? "Responses stream failed"),
|
|
997
|
+
{
|
|
998
|
+
code: details.code,
|
|
999
|
+
param: details.param,
|
|
1000
|
+
status: details.status
|
|
1001
|
+
}
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
throwIfAborted(request.signal);
|
|
1007
|
+
if (!completed)
|
|
1008
|
+
throw new Error("Responses stream ended without a completed response");
|
|
1009
|
+
yield* flushToolCalls();
|
|
1010
|
+
if (citations.length > 0) {
|
|
1011
|
+
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
1012
|
+
}
|
|
1013
|
+
yield { type: "message:end" };
|
|
1014
|
+
yield { type: "done", usage };
|
|
1015
|
+
}
|
|
666
1016
|
async *stream(request) {
|
|
1017
|
+
throwIfAborted(request.signal);
|
|
667
1018
|
if (this.shouldUseResponsesApi(request)) {
|
|
668
1019
|
const messageId2 = generateMessageId();
|
|
669
1020
|
yield { type: "message:start", id: messageId2 };
|
|
670
1021
|
try {
|
|
671
|
-
|
|
672
|
-
if (result.content) {
|
|
673
|
-
yield { type: "message:delta", content: result.content };
|
|
674
|
-
}
|
|
675
|
-
for (const toolCall of result.toolCalls) {
|
|
676
|
-
yield {
|
|
677
|
-
type: "action:start",
|
|
678
|
-
id: toolCall.id,
|
|
679
|
-
name: toolCall.name
|
|
680
|
-
};
|
|
681
|
-
yield {
|
|
682
|
-
type: "action:args",
|
|
683
|
-
id: toolCall.id,
|
|
684
|
-
args: JSON.stringify(toolCall.args)
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
yield { type: "message:end" };
|
|
688
|
-
yield {
|
|
689
|
-
type: "done",
|
|
690
|
-
usage: result.usage ? {
|
|
691
|
-
prompt_tokens: result.usage.promptTokens,
|
|
692
|
-
completion_tokens: result.usage.completionTokens,
|
|
693
|
-
total_tokens: result.usage.totalTokens
|
|
694
|
-
} : void 0
|
|
695
|
-
};
|
|
1022
|
+
yield* this.streamWithResponses(request);
|
|
696
1023
|
return;
|
|
697
1024
|
} catch (error) {
|
|
698
|
-
yield
|
|
699
|
-
type: "error",
|
|
700
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
701
|
-
code: "OPENAI_RESPONSES_ERROR"
|
|
702
|
-
};
|
|
1025
|
+
yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
|
|
703
1026
|
return;
|
|
704
1027
|
}
|
|
705
1028
|
}
|
|
706
1029
|
const client = await this.getClient();
|
|
707
1030
|
let messages;
|
|
708
1031
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
709
|
-
const processedMessages = request.rawMessages
|
|
710
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
711
|
-
return { ...msg, content: null };
|
|
712
|
-
}
|
|
713
|
-
const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
|
|
714
|
-
if (hasAttachments) {
|
|
715
|
-
const content = [];
|
|
716
|
-
if (msg.content) {
|
|
717
|
-
content.push({ type: "text", text: msg.content });
|
|
718
|
-
}
|
|
719
|
-
for (const attachment of msg.attachments) {
|
|
720
|
-
if (attachment.type === "image") {
|
|
721
|
-
let imageUrl;
|
|
722
|
-
if (attachment.url) {
|
|
723
|
-
imageUrl = attachment.url;
|
|
724
|
-
} else if (attachment.data) {
|
|
725
|
-
imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
|
|
726
|
-
} else {
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
content.push({
|
|
730
|
-
type: "image_url",
|
|
731
|
-
image_url: { url: imageUrl, detail: "auto" }
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
return { ...msg, content, attachments: void 0 };
|
|
736
|
-
}
|
|
737
|
-
return msg;
|
|
738
|
-
});
|
|
1032
|
+
const processedMessages = this.formatRawMessages(request.rawMessages);
|
|
739
1033
|
if (request.systemPrompt) {
|
|
740
1034
|
const hasSystem = processedMessages.some((m) => m.role === "system");
|
|
741
1035
|
if (!hasSystem) {
|
|
@@ -755,7 +1049,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
755
1049
|
request.systemPrompt
|
|
756
1050
|
);
|
|
757
1051
|
}
|
|
758
|
-
const tools = request.
|
|
1052
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1053
|
+
type: "function",
|
|
1054
|
+
function: {
|
|
1055
|
+
name: tool.name,
|
|
1056
|
+
description: tool.description,
|
|
1057
|
+
parameters: normalizeObjectJsonSchema(
|
|
1058
|
+
tool.inputSchema
|
|
1059
|
+
)
|
|
1060
|
+
}
|
|
1061
|
+
})) : formatTools(request.actions ?? []);
|
|
759
1062
|
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
760
1063
|
if (webSearchConfig) {
|
|
761
1064
|
const webSearchTool = {
|
|
@@ -784,26 +1087,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
784
1087
|
tools: tools.length > 0 ? tools : void 0,
|
|
785
1088
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
786
1089
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
787
|
-
...
|
|
1090
|
+
...buildOpenAIParameters(
|
|
788
1091
|
modelIdForPayload,
|
|
789
1092
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
790
|
-
request.config?.temperature ?? this.config.temperature
|
|
1093
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1094
|
+
this.parameterOptions(request)
|
|
791
1095
|
),
|
|
792
1096
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
793
1097
|
stream: true,
|
|
794
1098
|
stream_options: { include_usage: true }
|
|
795
1099
|
};
|
|
796
1100
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
797
|
-
const stream = await client.chat.completions.create(payload
|
|
798
|
-
|
|
1101
|
+
const stream = await client.chat.completions.create(payload, {
|
|
1102
|
+
signal: request.signal
|
|
1103
|
+
});
|
|
1104
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1105
|
+
let finishReason;
|
|
799
1106
|
const collectedCitations = [];
|
|
800
1107
|
let citationIndex = 0;
|
|
801
1108
|
let usage;
|
|
802
1109
|
for await (const chunk of stream) {
|
|
803
1110
|
logProviderPayload("openai", "stream chunk", chunk, request.debug);
|
|
804
|
-
|
|
805
|
-
break;
|
|
806
|
-
}
|
|
1111
|
+
throwIfAborted(request.signal);
|
|
807
1112
|
const delta = chunk.choices[0]?.delta;
|
|
808
1113
|
const choice = chunk.choices[0];
|
|
809
1114
|
if (delta?.content) {
|
|
@@ -826,38 +1131,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
826
1131
|
}
|
|
827
1132
|
}
|
|
828
1133
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
const tcExtraContent = toolCall.extra_content;
|
|
845
|
-
currentToolCall = {
|
|
846
|
-
id: toolCall.id,
|
|
847
|
-
name: toolCall.function?.name || "",
|
|
848
|
-
arguments: toolCall.function?.arguments || "",
|
|
849
|
-
...tcExtraContent ? { extra_content: tcExtraContent } : {}
|
|
850
|
-
};
|
|
851
|
-
yield {
|
|
852
|
-
type: "action:start",
|
|
853
|
-
id: currentToolCall.id,
|
|
854
|
-
name: currentToolCall.name,
|
|
855
|
-
...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
|
|
856
|
-
};
|
|
857
|
-
} else if (currentToolCall && toolCall.function?.arguments) {
|
|
858
|
-
currentToolCall.arguments += toolCall.function.arguments;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
1134
|
+
for (const part of delta?.tool_calls ?? []) {
|
|
1135
|
+
const index = part.index ?? 0;
|
|
1136
|
+
const call = toolCalls.get(index) ?? {
|
|
1137
|
+
id: "",
|
|
1138
|
+
name: "",
|
|
1139
|
+
arguments: ""
|
|
1140
|
+
};
|
|
1141
|
+
if (part.id) call.id = part.id;
|
|
1142
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1143
|
+
if (part.function?.arguments)
|
|
1144
|
+
call.arguments += part.function.arguments;
|
|
1145
|
+
if (part.extra_content) call.extra_content = part.extra_content;
|
|
1146
|
+
toolCalls.set(index, call);
|
|
861
1147
|
}
|
|
862
1148
|
if (chunk.usage) {
|
|
863
1149
|
usage = {
|
|
@@ -866,21 +1152,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
866
1152
|
total_tokens: chunk.usage.total_tokens
|
|
867
1153
|
};
|
|
868
1154
|
}
|
|
869
|
-
if (choice?.finish_reason)
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1155
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1156
|
+
}
|
|
1157
|
+
throwIfAborted(request.signal);
|
|
1158
|
+
if (!finishReason)
|
|
1159
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1160
|
+
if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
|
|
1161
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1162
|
+
}
|
|
1163
|
+
for (const call of toolCalls.values()) {
|
|
1164
|
+
if (!call.id || !call.name)
|
|
1165
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1166
|
+
JSON.parse(call.arguments || "{}");
|
|
1167
|
+
}
|
|
1168
|
+
for (const call of toolCalls.values()) {
|
|
1169
|
+
yield {
|
|
1170
|
+
type: "action:start",
|
|
1171
|
+
id: call.id,
|
|
1172
|
+
name: call.name,
|
|
1173
|
+
...call.extra_content ? { extra_content: call.extra_content } : {}
|
|
1174
|
+
};
|
|
1175
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
1176
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
884
1177
|
}
|
|
885
1178
|
if (collectedCitations.length > 0) {
|
|
886
1179
|
const uniqueCitations = deduplicateCitations(collectedCitations);
|
|
@@ -889,26 +1182,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
889
1182
|
yield { type: "message:end" };
|
|
890
1183
|
yield { type: "done", usage };
|
|
891
1184
|
} catch (error) {
|
|
892
|
-
yield {
|
|
893
|
-
type: "error",
|
|
894
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
895
|
-
code: `${this.provider.toUpperCase()}_ERROR`
|
|
896
|
-
};
|
|
1185
|
+
yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
|
|
897
1186
|
}
|
|
898
1187
|
}
|
|
899
1188
|
async complete(request) {
|
|
1189
|
+
throwIfAborted(request.signal);
|
|
900
1190
|
if (this.shouldUseResponsesApi(request)) {
|
|
901
1191
|
return this.completeWithResponses(request);
|
|
902
1192
|
}
|
|
903
1193
|
const client = await this.getClient();
|
|
904
1194
|
let messages;
|
|
905
1195
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
906
|
-
const sanitized = request.rawMessages
|
|
907
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
908
|
-
return { ...msg, content: null };
|
|
909
|
-
}
|
|
910
|
-
return msg;
|
|
911
|
-
});
|
|
1196
|
+
const sanitized = this.formatRawMessages(request.rawMessages);
|
|
912
1197
|
if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
|
|
913
1198
|
messages = [
|
|
914
1199
|
{ role: "system", content: request.systemPrompt },
|
|
@@ -923,7 +1208,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
923
1208
|
request.systemPrompt
|
|
924
1209
|
);
|
|
925
1210
|
}
|
|
926
|
-
const tools = request.
|
|
1211
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1212
|
+
type: "function",
|
|
1213
|
+
function: {
|
|
1214
|
+
name: tool.name,
|
|
1215
|
+
description: tool.description,
|
|
1216
|
+
parameters: normalizeObjectJsonSchema(
|
|
1217
|
+
tool.inputSchema
|
|
1218
|
+
)
|
|
1219
|
+
}
|
|
1220
|
+
})) : formatTools(request.actions ?? []);
|
|
927
1221
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
928
1222
|
const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
|
|
929
1223
|
type: "function",
|
|
@@ -938,16 +1232,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
938
1232
|
tools: tools.length > 0 ? tools : void 0,
|
|
939
1233
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
940
1234
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
941
|
-
...
|
|
1235
|
+
...buildOpenAIParameters(
|
|
942
1236
|
modelIdForCompletePayload,
|
|
943
1237
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
944
|
-
request.config?.temperature ?? this.config.temperature
|
|
1238
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1239
|
+
this.parameterOptions(request)
|
|
945
1240
|
),
|
|
946
1241
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
947
1242
|
stream: false
|
|
948
1243
|
};
|
|
949
1244
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
950
|
-
const response = await client.chat.completions.create(payload
|
|
1245
|
+
const response = await client.chat.completions.create(payload, {
|
|
1246
|
+
signal: request.signal
|
|
1247
|
+
});
|
|
951
1248
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
952
1249
|
const choice = response.choices?.[0];
|
|
953
1250
|
const message = choice?.message;
|