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