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