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