@yourgpt/llm-sdk 2.6.0-beta.0 → 2.6.0-beta.2
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 +189 -0
- package/dist/adapters/index.d.mts +11 -13
- package/dist/adapters/index.d.ts +11 -13
- package/dist/adapters/index.js +610 -186
- package/dist/adapters/index.mjs +610 -186
- package/dist/audio-BBSwnjo8.d.mts +92 -0
- package/dist/audio-BBSwnjo8.d.ts +92 -0
- package/dist/{base-tNgbBaSo.d.mts → base-BICaQaRS.d.mts} +8 -3
- package/dist/{base-C58Dsr9p.d.ts → base-CpRPB8u8.d.ts} +8 -3
- 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 +18 -35
- package/dist/index.d.ts +18 -35
- package/dist/index.js +304 -88
- package/dist/index.mjs +302 -89
- package/dist/providers/anthropic/index.d.mts +3 -3
- package/dist/providers/anthropic/index.d.ts +3 -3
- package/dist/providers/anthropic/index.js +123 -7
- package/dist/providers/anthropic/index.mjs +123 -7
- package/dist/providers/azure/index.d.mts +3 -3
- package/dist/providers/azure/index.d.ts +3 -3
- package/dist/providers/azure/index.js +101 -4
- package/dist/providers/azure/index.mjs +101 -4
- package/dist/providers/fireworks/index.d.mts +3 -3
- package/dist/providers/fireworks/index.d.ts +3 -3
- package/dist/providers/fireworks/index.js +559 -178
- package/dist/providers/fireworks/index.mjs +559 -178
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +559 -178
- package/dist/providers/google/index.mjs +559 -178
- package/dist/providers/ollama/index.d.mts +4 -4
- package/dist/providers/ollama/index.d.ts +4 -4
- package/dist/providers/ollama/index.js +87 -2
- package/dist/providers/ollama/index.mjs +87 -2
- package/dist/providers/openai/index.d.mts +25 -23
- package/dist/providers/openai/index.d.ts +25 -23
- package/dist/providers/openai/index.js +1397 -481
- package/dist/providers/openai/index.mjs +1396 -482
- package/dist/providers/openrouter/index.d.mts +3 -3
- package/dist/providers/openrouter/index.d.ts +3 -3
- package/dist/providers/openrouter/index.js +559 -178
- package/dist/providers/openrouter/index.mjs +559 -178
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +559 -178
- package/dist/providers/togetherai/index.mjs +559 -178
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +559 -178
- package/dist/providers/xai/index.mjs +559 -178
- package/dist/{types-B6dhnguR.d.mts → types-C3RgRbEC.d.mts} +11 -4
- package/dist/{types-CCxPmkmK.d.ts → types-DCFqkJAy.d.ts} +11 -4
- package/dist/{types-BQ31QIsA.d.ts → types-DHQEMFHA.d.mts} +3 -11
- package/dist/{types-BkQCSiIt.d.mts → types-Dg1Bcg1k.d.mts} +127 -3
- package/dist/{types-BkQCSiIt.d.ts → types-Dg1Bcg1k.d.ts} +127 -3
- package/dist/{types-BSSiJW2o.d.mts → types-Dikj3En4.d.ts} +3 -11
- package/dist/yourgpt/index.d.mts +1 -1
- package/dist/yourgpt/index.d.ts +1 -1
- package/docs/multimodal-and-audio.md +170 -0
- package/package.json +117 -49
- package/LICENSE +0 -21
|
@@ -1,3 +1,157 @@
|
|
|
1
|
+
// src/core/tool-output.ts
|
|
2
|
+
var imageTypes = /* @__PURE__ */ new Set([
|
|
3
|
+
"image/png",
|
|
4
|
+
"image/jpeg",
|
|
5
|
+
"image/webp",
|
|
6
|
+
"image/gif"
|
|
7
|
+
]);
|
|
8
|
+
function normalizeToolOutput(value) {
|
|
9
|
+
const output = value;
|
|
10
|
+
if (output?.type !== "content" || !Array.isArray(output.content) || !output.content.length) {
|
|
11
|
+
throw new Error("Tool model output must contain a nonempty content array");
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
type: "content",
|
|
15
|
+
content: output.content.map((part) => {
|
|
16
|
+
if (part?.type === "text" && typeof part.text === "string")
|
|
17
|
+
return { type: "text", text: part.text };
|
|
18
|
+
if (part?.type !== "image")
|
|
19
|
+
throw new Error("Unsupported tool output content type");
|
|
20
|
+
const sources = ["data", "url", "fileId"].filter((key) => key in part);
|
|
21
|
+
if (sources.length !== 1)
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Tool image requires exactly one of data, url, or fileId"
|
|
24
|
+
);
|
|
25
|
+
if ("fileId" in part) {
|
|
26
|
+
if (part.provider !== "openai" || typeof part.fileId !== "string" || !part.fileId.trim())
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Tool image file references must name the openai provider and fileId"
|
|
29
|
+
);
|
|
30
|
+
return { type: "image", fileId: part.fileId, provider: "openai" };
|
|
31
|
+
}
|
|
32
|
+
if ("url" in part) {
|
|
33
|
+
let url;
|
|
34
|
+
try {
|
|
35
|
+
url = new URL(part.url);
|
|
36
|
+
} catch {
|
|
37
|
+
throw new Error("Invalid tool image URL");
|
|
38
|
+
}
|
|
39
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password)
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Tool image URL must use HTTP(S) without credentials"
|
|
42
|
+
);
|
|
43
|
+
return { type: "image", url: url.href };
|
|
44
|
+
}
|
|
45
|
+
if (!("data" in part) || !imageTypes.has(part.mediaType))
|
|
46
|
+
throw new Error("Unsupported tool image media type");
|
|
47
|
+
let data = part.data instanceof Uint8Array ? Buffer.from(part.data).toString("base64") : part.data;
|
|
48
|
+
if (typeof data !== "string")
|
|
49
|
+
throw new Error("Tool image data must be base64 or bytes");
|
|
50
|
+
if (data.startsWith("data:")) {
|
|
51
|
+
const match = /^data:([^;,]+);base64,(.+)$/.exec(data);
|
|
52
|
+
if (!match || match[1] !== part.mediaType)
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Tool image data URL media type does not match mediaType"
|
|
55
|
+
);
|
|
56
|
+
data = match[2];
|
|
57
|
+
}
|
|
58
|
+
if (!data.length || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(data))
|
|
59
|
+
throw new Error("Invalid tool image base64 data");
|
|
60
|
+
if (Buffer.byteLength(data, "base64") > 20 * 1024 * 1024)
|
|
61
|
+
throw new Error("Tool image exceeds 20 MiB");
|
|
62
|
+
if (Buffer.from(data, "base64").toString("base64") !== data)
|
|
63
|
+
throw new Error("Invalid tool image base64 data");
|
|
64
|
+
return { type: "image", data, mediaType: part.mediaType };
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function toolOutputForProvider(value, provider) {
|
|
69
|
+
const output = normalizeToolOutput(value);
|
|
70
|
+
return output.content.map((part) => {
|
|
71
|
+
if (part.type === "text")
|
|
72
|
+
return {
|
|
73
|
+
type: provider === "responses" ? "input_text" : "text",
|
|
74
|
+
text: part.text
|
|
75
|
+
};
|
|
76
|
+
if (provider === "unsupported")
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Image tool results require OpenAI Responses or Anthropic; this endpoint is not supported"
|
|
79
|
+
);
|
|
80
|
+
if ("fileId" in part) {
|
|
81
|
+
if (provider !== "responses")
|
|
82
|
+
throw new Error(
|
|
83
|
+
"OpenAI image file references cannot be sent to another provider"
|
|
84
|
+
);
|
|
85
|
+
return { type: "input_image", file_id: part.fileId, detail: "auto" };
|
|
86
|
+
}
|
|
87
|
+
if (provider === "responses")
|
|
88
|
+
return {
|
|
89
|
+
type: "input_image",
|
|
90
|
+
image_url: "url" in part ? part.url : `data:${part.mediaType};base64,${part.data}`,
|
|
91
|
+
detail: "auto"
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
type: "image",
|
|
95
|
+
source: "url" in part ? { type: "url", url: part.url } : { type: "base64", media_type: part.mediaType, data: part.data }
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function textToolOutput(value, fallback) {
|
|
100
|
+
if (value === void 0) return fallback;
|
|
101
|
+
return toolOutputForProvider(value, "unsupported").map((part) => part.text).join("\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/core/openai-parameters.ts
|
|
105
|
+
function capabilities(model) {
|
|
106
|
+
const gpt = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/i.exec(model);
|
|
107
|
+
const major = Number(gpt?.[1] ?? 0);
|
|
108
|
+
const minor = Number(gpt?.[2] ?? 0);
|
|
109
|
+
const chat = gpt?.[3]?.startsWith("chat") ?? false;
|
|
110
|
+
const reasoning = /^o\d+(?:-|$)/i.test(model) || major >= 5 && !chat;
|
|
111
|
+
const supportsNone = major === 5 && minor >= 1 && !chat || /^gpt-6-(?:sol|luna)(?:-\d{4}-\d{2}-\d{2})?$/i.test(model);
|
|
112
|
+
return { major, minor, reasoning, supportsNone };
|
|
113
|
+
}
|
|
114
|
+
function requiresOpenAIResponses(model, baseUrl, options = {}) {
|
|
115
|
+
if (options.parameterPolicy === "passthrough") return false;
|
|
116
|
+
const c = capabilities(model);
|
|
117
|
+
if (!c.reasoning || options.reasoningEffort === "none") return false;
|
|
118
|
+
if (!(c.major >= 6 || c.major === 5 && c.minor >= 4)) return false;
|
|
119
|
+
if (!baseUrl) return true;
|
|
120
|
+
try {
|
|
121
|
+
return new URL(baseUrl).hostname === "api.openai.com";
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function buildOpenAIParameters(model, maxTokens, temperature, options = {}, api = "chat") {
|
|
127
|
+
const c = capabilities(model ?? "");
|
|
128
|
+
const normalize = options.parameterPolicy !== "passthrough";
|
|
129
|
+
const effort = options.reasoningEffort;
|
|
130
|
+
if (normalize && c.reasoning && effort === "none" && !c.supportsNone) {
|
|
131
|
+
throw new Error(`${model} does not support reasoningEffort: none`);
|
|
132
|
+
}
|
|
133
|
+
if (normalize && c.major >= 6 && effort === "minimal") {
|
|
134
|
+
throw new Error(`${model} does not support reasoningEffort: minimal`);
|
|
135
|
+
}
|
|
136
|
+
const result = {
|
|
137
|
+
[api === "responses" ? "max_output_tokens" : normalize && c.reasoning ? "max_completion_tokens" : "max_tokens"]: maxTokens
|
|
138
|
+
};
|
|
139
|
+
if (effort !== void 0) {
|
|
140
|
+
if (api === "responses") result.reasoning = { effort };
|
|
141
|
+
else result.reasoning_effort = effort;
|
|
142
|
+
}
|
|
143
|
+
if (temperature !== void 0) {
|
|
144
|
+
if (!normalize || !c.reasoning || effort === "none" && c.supportsNone) {
|
|
145
|
+
result.temperature = temperature;
|
|
146
|
+
} else {
|
|
147
|
+
(options.onWarning ?? console.warn)(
|
|
148
|
+
`[llm-sdk] ${model}: omitted temperature because reasoning is enabled.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
1
155
|
// src/adapters/base.ts
|
|
2
156
|
function stringifyForDebug(value) {
|
|
3
157
|
return JSON.stringify(
|
|
@@ -104,16 +258,6 @@ function normalizeObjectJsonSchema(schema) {
|
|
|
104
258
|
}
|
|
105
259
|
return normalized;
|
|
106
260
|
}
|
|
107
|
-
function isOpenAIReasoningModel(modelId) {
|
|
108
|
-
if (!modelId) return false;
|
|
109
|
-
return /^(o1|o3|o4|gpt-5)/i.test(modelId);
|
|
110
|
-
}
|
|
111
|
-
function buildOpenAITokenParams(modelId, maxTokens, temperature) {
|
|
112
|
-
if (isOpenAIReasoningModel(modelId)) {
|
|
113
|
-
return { max_completion_tokens: maxTokens };
|
|
114
|
-
}
|
|
115
|
-
return { max_tokens: maxTokens, temperature };
|
|
116
|
-
}
|
|
117
261
|
function toOpenAIResponseFormat(rf) {
|
|
118
262
|
if (!rf) return void 0;
|
|
119
263
|
if (rf.type === "json_object") return { type: "json_object" };
|
|
@@ -198,7 +342,7 @@ function messageToOpenAIContent(message) {
|
|
|
198
342
|
}
|
|
199
343
|
return blocks;
|
|
200
344
|
}
|
|
201
|
-
function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
345
|
+
function formatMessagesForOpenAI(messages, systemPrompt, preserveProviderMetadata = false) {
|
|
202
346
|
const formatted = [];
|
|
203
347
|
if (systemPrompt) {
|
|
204
348
|
formatted.push({ role: "system", content: systemPrompt });
|
|
@@ -216,7 +360,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
216
360
|
const assistantMsg = {
|
|
217
361
|
role: "assistant",
|
|
218
362
|
// Gemini/xAI (OpenAI-compatible) reject content: "" on assistant messages with tool_calls
|
|
219
|
-
content: hasToolCalls ? msg.content || null : msg.content
|
|
363
|
+
content: hasToolCalls ? msg.content || null : msg.content,
|
|
364
|
+
...preserveProviderMetadata && msg.providerMetadata ? { providerMetadata: msg.providerMetadata } : {}
|
|
220
365
|
};
|
|
221
366
|
if (hasToolCalls) {
|
|
222
367
|
assistantMsg.tool_calls = msg.tool_calls;
|
|
@@ -225,7 +370,8 @@ function formatMessagesForOpenAI(messages, systemPrompt) {
|
|
|
225
370
|
} else if (msg.role === "tool" && msg.tool_call_id) {
|
|
226
371
|
formatted.push({
|
|
227
372
|
role: "tool",
|
|
228
|
-
content: msg.content ?? "",
|
|
373
|
+
content: preserveProviderMetadata ? msg.content ?? "" : textToolOutput(msg.modelOutput, msg.content ?? ""),
|
|
374
|
+
...preserveProviderMetadata && msg.modelOutput ? { modelOutput: msg.modelOutput } : {},
|
|
229
375
|
tool_call_id: msg.tool_call_id
|
|
230
376
|
});
|
|
231
377
|
}
|
|
@@ -512,7 +658,7 @@ function formatMessagesForGoogle(messages) {
|
|
|
512
658
|
return {
|
|
513
659
|
role: "tool",
|
|
514
660
|
tool_call_id: msg.toolCallId,
|
|
515
|
-
content: msg.content
|
|
661
|
+
content: textToolOutput(msg.modelOutput, msg.content)
|
|
516
662
|
};
|
|
517
663
|
default:
|
|
518
664
|
return msg;
|
|
@@ -520,6 +666,86 @@ function formatMessagesForGoogle(messages) {
|
|
|
520
666
|
});
|
|
521
667
|
}
|
|
522
668
|
|
|
669
|
+
// src/core/openai-responses.ts
|
|
670
|
+
function resolveOpenAITransport(defaults, overrides) {
|
|
671
|
+
return {
|
|
672
|
+
api: overrides?.api ?? defaults.api ?? "auto",
|
|
673
|
+
responses: defaults.responses !== void 0 || overrides?.responses !== void 0 ? { ...defaults.responses, ...overrides?.responses } : void 0
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function useOpenAIResponses(model, baseURL, parameters, transport, hasTools, hasReplay, nativeToolSearch = false) {
|
|
677
|
+
const api = transport.api ?? "auto";
|
|
678
|
+
if (!["auto", "chat", "responses"].includes(api)) {
|
|
679
|
+
throw new Error(`Unknown OpenAI API selection: ${api}`);
|
|
680
|
+
}
|
|
681
|
+
if (api === "chat") {
|
|
682
|
+
if (transport.responses !== void 0 || nativeToolSearch) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
"Responses options and native tool search require the Responses API"
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
let officialEndpoint = !baseURL;
|
|
690
|
+
try {
|
|
691
|
+
officialEndpoint = !baseURL || new URL(baseURL).hostname === "api.openai.com";
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
return api === "responses" || transport.responses !== void 0 || hasReplay && officialEndpoint || nativeToolSearch || hasTools && requiresOpenAIResponses(model, baseURL, parameters);
|
|
695
|
+
}
|
|
696
|
+
function responseMetadata(response) {
|
|
697
|
+
const output = Array.isArray(response?.output) ? response.output : [];
|
|
698
|
+
return {
|
|
699
|
+
openai: {
|
|
700
|
+
response: {
|
|
701
|
+
id: response?.id,
|
|
702
|
+
model: response?.model,
|
|
703
|
+
status: response?.status,
|
|
704
|
+
output,
|
|
705
|
+
usage: response?.usage,
|
|
706
|
+
store: response?.store,
|
|
707
|
+
refusals: output.flatMap(
|
|
708
|
+
(item) => item?.type === "message" && Array.isArray(item.content) ? item.content.filter(
|
|
709
|
+
(part) => part?.type === "refusal" && typeof part.refusal === "string"
|
|
710
|
+
).map((part) => part.refusal) : []
|
|
711
|
+
)
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function getResponseReplay(metadata) {
|
|
717
|
+
const response = metadata?.openai?.response;
|
|
718
|
+
if (response === void 0) return void 0;
|
|
719
|
+
if (!Array.isArray(response.output) || response.output.some((item) => !item || typeof item.type !== "string")) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"Invalid OpenAI Responses replay metadata: output must contain typed items"
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return response.output;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/core/provider-error.ts
|
|
728
|
+
function providerErrorEvent(error, code) {
|
|
729
|
+
const details = error ?? {};
|
|
730
|
+
const event = {
|
|
731
|
+
type: "error",
|
|
732
|
+
message: error instanceof Error ? error.message : String(error),
|
|
733
|
+
code,
|
|
734
|
+
...typeof details.status === "number" ? { status: details.status } : {},
|
|
735
|
+
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
736
|
+
...typeof details.param === "string" ? { param: details.param } : {},
|
|
737
|
+
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
738
|
+
};
|
|
739
|
+
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
740
|
+
return event;
|
|
741
|
+
}
|
|
742
|
+
function throwIfAborted(signal) {
|
|
743
|
+
if (!signal?.aborted) return;
|
|
744
|
+
const error = new Error("The operation was aborted");
|
|
745
|
+
error.name = "AbortError";
|
|
746
|
+
throw error;
|
|
747
|
+
}
|
|
748
|
+
|
|
523
749
|
// src/core/utils.ts
|
|
524
750
|
function generateId(prefix = "id") {
|
|
525
751
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -551,39 +777,41 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
551
777
|
const { default: OpenAI } = await import('openai');
|
|
552
778
|
this.client = new OpenAI({
|
|
553
779
|
apiKey: this.config.apiKey,
|
|
554
|
-
baseURL: this.config.baseUrl
|
|
780
|
+
baseURL: this.config.baseUrl,
|
|
781
|
+
organization: this.config.organization,
|
|
782
|
+
defaultHeaders: this.config.headers
|
|
555
783
|
});
|
|
556
784
|
}
|
|
557
785
|
return this.client;
|
|
558
786
|
}
|
|
787
|
+
parameterOptions(request) {
|
|
788
|
+
return {
|
|
789
|
+
...this.config,
|
|
790
|
+
reasoningEffort: request.config?.reasoningEffort ?? this.config.reasoningEffort
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
toolDefinitions(request) {
|
|
794
|
+
if (request.toolDefinitions?.length) return request.toolDefinitions;
|
|
795
|
+
return formatTools(request.actions ?? []).map((tool) => ({
|
|
796
|
+
name: tool.function.name,
|
|
797
|
+
description: tool.function.description ?? "",
|
|
798
|
+
location: "server",
|
|
799
|
+
inputSchema: tool.function.parameters
|
|
800
|
+
}));
|
|
801
|
+
}
|
|
559
802
|
shouldUseResponsesApi(request) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
request.
|
|
803
|
+
return useOpenAIResponses(
|
|
804
|
+
request.config?.model || this.model,
|
|
805
|
+
this.config.baseUrl,
|
|
806
|
+
this.parameterOptions(request),
|
|
807
|
+
resolveOpenAITransport(this.config, request.config?.openai),
|
|
808
|
+
this.toolDefinitions(request).length > 0,
|
|
809
|
+
!!(request.rawMessages ?? request.messages)?.some(
|
|
810
|
+
(message) => message.providerMetadata?.openai?.response
|
|
811
|
+
),
|
|
812
|
+
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true && request.providerToolOptions.openai.nativeToolSearch.useResponsesApi !== false
|
|
567
813
|
);
|
|
568
814
|
}
|
|
569
|
-
/**
|
|
570
|
-
* Whether this model rejects function tools on /v1/chat/completions.
|
|
571
|
-
*
|
|
572
|
-
* Applies to OpenAI proper only — the adapter is reused for OpenAI-compatible
|
|
573
|
-
* providers (Azure, xAI, Google, Fireworks) whose endpoints have no such
|
|
574
|
-
* restriction and may not implement /v1/responses at all.
|
|
575
|
-
*/
|
|
576
|
-
requiresResponsesApiForTools(modelId) {
|
|
577
|
-
if (!modelId) return false;
|
|
578
|
-
if (!/^gpt-5\.(?:[4-9]|\d{2,})/i.test(modelId)) return false;
|
|
579
|
-
const baseUrl = this.config.baseUrl;
|
|
580
|
-
if (!baseUrl) return true;
|
|
581
|
-
try {
|
|
582
|
-
return new URL(baseUrl).hostname === "api.openai.com";
|
|
583
|
-
} catch {
|
|
584
|
-
return false;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
815
|
/**
|
|
588
816
|
* Convert one message's content to Responses content parts.
|
|
589
817
|
*
|
|
@@ -614,11 +842,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
614
842
|
});
|
|
615
843
|
}
|
|
616
844
|
buildResponsesInput(request) {
|
|
617
|
-
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? request.rawMessages : formatMessagesForOpenAI(request.messages, void 0);
|
|
845
|
+
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
|
|
618
846
|
const input = [];
|
|
619
847
|
for (const message of sourceMessages) {
|
|
620
848
|
if (message.role === "system") {
|
|
621
|
-
if (
|
|
849
|
+
if (message.content && message.content !== request.systemPrompt) {
|
|
622
850
|
input.push({
|
|
623
851
|
type: "message",
|
|
624
852
|
role: "developer",
|
|
@@ -628,6 +856,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
628
856
|
continue;
|
|
629
857
|
}
|
|
630
858
|
if (message.role === "assistant") {
|
|
859
|
+
const replay = getResponseReplay(message.providerMetadata);
|
|
860
|
+
if (replay) {
|
|
861
|
+
input.push(...replay);
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
631
864
|
const content = this.toResponsesContent(message.content, "assistant");
|
|
632
865
|
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
633
866
|
input.push({
|
|
@@ -651,7 +884,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
651
884
|
input.push({
|
|
652
885
|
type: "function_call_output",
|
|
653
886
|
call_id: message.tool_call_id,
|
|
654
|
-
output: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
887
|
+
output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
655
888
|
});
|
|
656
889
|
continue;
|
|
657
890
|
}
|
|
@@ -664,6 +897,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
664
897
|
}
|
|
665
898
|
return input;
|
|
666
899
|
}
|
|
900
|
+
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
901
|
+
formatRawMessages(messages, preserveProviderMetadata = false) {
|
|
902
|
+
return messages.map((message) => {
|
|
903
|
+
const { attachments, providerMetadata, modelOutput, ...rest } = message;
|
|
904
|
+
let content = message.role === "tool" && !preserveProviderMetadata ? textToolOutput(modelOutput, String(message.content ?? "")) : message.content;
|
|
905
|
+
if (preserveProviderMetadata && modelOutput)
|
|
906
|
+
rest.modelOutput = modelOutput;
|
|
907
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0 && content === "") {
|
|
908
|
+
content = null;
|
|
909
|
+
}
|
|
910
|
+
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
911
|
+
const images = attachments.filter((attachment) => attachment && typeof attachment === "object").map(
|
|
912
|
+
(attachment) => attachmentToOpenAIImage(attachment)
|
|
913
|
+
).filter((image) => image !== null);
|
|
914
|
+
if (images.length > 0) {
|
|
915
|
+
const parts = Array.isArray(content) ? content : content ? [{ type: "text", text: content }] : [];
|
|
916
|
+
content = [...parts, ...images];
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
...rest,
|
|
921
|
+
content,
|
|
922
|
+
...preserveProviderMetadata && providerMetadata ? { providerMetadata } : {}
|
|
923
|
+
};
|
|
924
|
+
});
|
|
925
|
+
}
|
|
667
926
|
/**
|
|
668
927
|
* Build the web_search tool for the Responses API, or undefined when web
|
|
669
928
|
* search is off.
|
|
@@ -742,20 +1001,25 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
742
1001
|
).map((part) => part.text) : []
|
|
743
1002
|
).join("") : "";
|
|
744
1003
|
const content = textFromOutput || (typeof response?.output_text === "string" ? response.output_text : "");
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
name
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1004
|
+
const seenCallIds = /* @__PURE__ */ new Set();
|
|
1005
|
+
const toolCalls = Array.isArray(response?.output) ? response.output.filter((item) => item?.type === "function_call").map((item) => {
|
|
1006
|
+
if (typeof item.call_id !== "string" || !item.call_id || typeof item.name !== "string" || !item.name || typeof item.arguments !== "string" || item.status && item.status !== "completed") {
|
|
1007
|
+
throw new Error("Responses contains an incomplete function call");
|
|
1008
|
+
}
|
|
1009
|
+
if (seenCallIds.has(item.call_id))
|
|
1010
|
+
throw new Error("Responses contains duplicate function call IDs");
|
|
1011
|
+
seenCallIds.add(item.call_id);
|
|
1012
|
+
const args = JSON.parse(item.arguments);
|
|
1013
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
1014
|
+
throw new Error(
|
|
1015
|
+
"Responses function arguments must be a JSON object"
|
|
1016
|
+
);
|
|
1017
|
+
return { id: item.call_id, name: item.name, args };
|
|
1018
|
+
}) : [];
|
|
756
1019
|
return {
|
|
757
1020
|
content,
|
|
758
1021
|
toolCalls,
|
|
1022
|
+
providerMetadata: responseMetadata(response),
|
|
759
1023
|
usage: response?.usage ? {
|
|
760
1024
|
promptTokens: response.usage.input_tokens ?? 0,
|
|
761
1025
|
completionTokens: response.usage.output_tokens ?? 0,
|
|
@@ -766,6 +1030,17 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
766
1030
|
}
|
|
767
1031
|
buildResponsesPayload(request, stream) {
|
|
768
1032
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
1033
|
+
const options = resolveOpenAITransport(
|
|
1034
|
+
this.config,
|
|
1035
|
+
request.config?.openai
|
|
1036
|
+
).responses;
|
|
1037
|
+
const parameters = buildOpenAIParameters(
|
|
1038
|
+
request.config?.model || this.model,
|
|
1039
|
+
request.config?.maxTokens ?? this.config.maxTokens,
|
|
1040
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1041
|
+
this.parameterOptions(request),
|
|
1042
|
+
"responses"
|
|
1043
|
+
);
|
|
769
1044
|
const responsesTextFormat = toOpenAIResponsesTextFormat(
|
|
770
1045
|
request.config?.responseFormat
|
|
771
1046
|
);
|
|
@@ -775,18 +1050,32 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
775
1050
|
instructions: request.systemPrompt,
|
|
776
1051
|
input: this.buildResponsesInput(request),
|
|
777
1052
|
tools: this.buildResponsesTools(
|
|
778
|
-
|
|
1053
|
+
this.toolDefinitions(request),
|
|
779
1054
|
request.providerToolOptions?.openai?.nativeToolSearch?.enabled === true,
|
|
780
1055
|
this.buildResponsesWebSearchTool(request)
|
|
781
1056
|
),
|
|
782
|
-
tool_choice: openaiToolOptions?.toolChoice
|
|
1057
|
+
tool_choice: openaiToolOptions?.toolChoice,
|
|
783
1058
|
parallel_tool_calls: openaiToolOptions?.parallelToolCalls,
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1059
|
+
...parameters,
|
|
1060
|
+
...options?.reasoningSummary ? {
|
|
1061
|
+
reasoning: {
|
|
1062
|
+
...parameters.reasoning,
|
|
1063
|
+
summary: options.reasoningSummary
|
|
1064
|
+
}
|
|
1065
|
+
} : {},
|
|
1066
|
+
...options?.store !== void 0 ? { store: options.store } : {},
|
|
1067
|
+
...options?.previousResponseId !== void 0 ? { previous_response_id: options.previousResponseId } : {},
|
|
1068
|
+
...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
|
|
1069
|
+
...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
|
|
1070
|
+
...options?.promptCacheRetention !== void 0 ? { prompt_cache_retention: options.promptCacheRetention } : {},
|
|
1071
|
+
...options?.store === false || options?.include ? {
|
|
1072
|
+
include: [
|
|
1073
|
+
.../* @__PURE__ */ new Set([
|
|
1074
|
+
...options?.include ?? [],
|
|
1075
|
+
...options?.store === false ? ["reasoning.encrypted_content"] : []
|
|
1076
|
+
])
|
|
1077
|
+
]
|
|
1078
|
+
} : {},
|
|
790
1079
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
791
1080
|
stream
|
|
792
1081
|
};
|
|
@@ -794,10 +1083,26 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
794
1083
|
}
|
|
795
1084
|
async completeWithResponses(request) {
|
|
796
1085
|
const client = await this.getClient();
|
|
1086
|
+
throwIfAborted(request.signal);
|
|
797
1087
|
const payload = this.buildResponsesPayload(request, false);
|
|
798
1088
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
799
|
-
|
|
1089
|
+
if (!client.responses?.create)
|
|
1090
|
+
throw new Error(
|
|
1091
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
1092
|
+
);
|
|
1093
|
+
const response = await client.responses.create(payload, {
|
|
1094
|
+
signal: request.signal
|
|
1095
|
+
});
|
|
1096
|
+
throwIfAborted(request.signal);
|
|
800
1097
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
1098
|
+
if (response.status && response.status !== "completed") {
|
|
1099
|
+
throw Object.assign(
|
|
1100
|
+
new Error(
|
|
1101
|
+
response.error?.message ?? `Responses request ${response.status}`
|
|
1102
|
+
),
|
|
1103
|
+
response.error
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
801
1106
|
return this.parseResponsesResult(response);
|
|
802
1107
|
}
|
|
803
1108
|
/**
|
|
@@ -815,13 +1120,30 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
815
1120
|
*/
|
|
816
1121
|
async *streamWithResponses(request) {
|
|
817
1122
|
const client = await this.getClient();
|
|
1123
|
+
throwIfAborted(request.signal);
|
|
818
1124
|
const payload = this.buildResponsesPayload(request, true);
|
|
819
1125
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
820
|
-
|
|
1126
|
+
if (!client.responses?.create)
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
"This OpenAI client does not support Responses; install an openai version with Responses support."
|
|
1129
|
+
);
|
|
1130
|
+
const stream = await client.responses.create(payload, {
|
|
1131
|
+
signal: request.signal
|
|
1132
|
+
});
|
|
821
1133
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
822
1134
|
let usage;
|
|
823
1135
|
const citations = [];
|
|
1136
|
+
let completed = false;
|
|
1137
|
+
let terminalResponse;
|
|
1138
|
+
let responseIdentity = {};
|
|
1139
|
+
const outputItems = /* @__PURE__ */ new Map();
|
|
1140
|
+
let streamedText = "";
|
|
824
1141
|
function* flushToolCalls() {
|
|
1142
|
+
for (const call of toolCalls.values()) {
|
|
1143
|
+
if (!call.name)
|
|
1144
|
+
throw new Error("Responses tool call is missing its name");
|
|
1145
|
+
JSON.parse(call.arguments || "{}");
|
|
1146
|
+
}
|
|
825
1147
|
for (const call of toolCalls.values()) {
|
|
826
1148
|
yield { type: "action:start", id: call.id, name: call.name };
|
|
827
1149
|
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
@@ -830,19 +1152,38 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
830
1152
|
toolCalls.clear();
|
|
831
1153
|
}
|
|
832
1154
|
for await (const event of stream) {
|
|
833
|
-
|
|
1155
|
+
throwIfAborted(request.signal);
|
|
834
1156
|
logProviderPayload("openai", "stream chunk", event, request.debug);
|
|
1157
|
+
if (completed)
|
|
1158
|
+
throw new Error("Responses stream emitted an event after completion");
|
|
835
1159
|
const index = event?.output_index ?? 0;
|
|
836
1160
|
switch (event?.type) {
|
|
1161
|
+
case "response.created":
|
|
1162
|
+
responseIdentity = {
|
|
1163
|
+
id: event.response?.id,
|
|
1164
|
+
model: event.response?.model
|
|
1165
|
+
};
|
|
1166
|
+
break;
|
|
1167
|
+
case "response.output_item.done":
|
|
1168
|
+
if (event.item) outputItems.set(index, event.item);
|
|
1169
|
+
if (event.item?.type === "function_call") {
|
|
1170
|
+
toolCalls.set(index, {
|
|
1171
|
+
id: event.item.call_id,
|
|
1172
|
+
name: event.item.name,
|
|
1173
|
+
arguments: event.item.arguments
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
break;
|
|
837
1177
|
case "response.output_text.delta":
|
|
838
1178
|
if (event.delta) {
|
|
1179
|
+
streamedText += event.delta;
|
|
839
1180
|
yield { type: "message:delta", content: event.delta };
|
|
840
1181
|
}
|
|
841
1182
|
break;
|
|
842
1183
|
case "response.output_item.added":
|
|
843
1184
|
if (event.item?.type === "function_call") {
|
|
844
1185
|
toolCalls.set(index, {
|
|
845
|
-
id: event.item.call_id ??
|
|
1186
|
+
id: event.item.call_id ?? "",
|
|
846
1187
|
name: event.item.name ?? "",
|
|
847
1188
|
arguments: event.item.arguments ?? ""
|
|
848
1189
|
});
|
|
@@ -860,8 +1201,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
860
1201
|
}
|
|
861
1202
|
break;
|
|
862
1203
|
}
|
|
863
|
-
case "response.completed":
|
|
864
|
-
|
|
1204
|
+
case "response.completed": {
|
|
1205
|
+
if (event.response?.status && event.response.status !== "completed")
|
|
1206
|
+
throw new Error(`Responses stream ${event.response.status}`);
|
|
1207
|
+
completed = true;
|
|
1208
|
+
terminalResponse = { ...responseIdentity, ...event.response };
|
|
865
1209
|
const found = this.collectResponsesCitations(
|
|
866
1210
|
event.response?.output,
|
|
867
1211
|
citations.length
|
|
@@ -877,21 +1221,88 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
877
1221
|
}
|
|
878
1222
|
break;
|
|
879
1223
|
}
|
|
880
|
-
case "
|
|
881
|
-
case "response.failed":
|
|
1224
|
+
case "response.incomplete":
|
|
882
1225
|
throw new Error(
|
|
883
|
-
|
|
1226
|
+
`Responses stream incomplete: ${event.response?.incomplete_details?.reason ?? "unknown reason"}`
|
|
1227
|
+
);
|
|
1228
|
+
case "error":
|
|
1229
|
+
case "response.failed": {
|
|
1230
|
+
const details = event.response?.error ?? event;
|
|
1231
|
+
throw Object.assign(
|
|
1232
|
+
new Error(details.message ?? "Responses stream failed"),
|
|
1233
|
+
{
|
|
1234
|
+
code: details.code,
|
|
1235
|
+
param: details.param,
|
|
1236
|
+
status: details.status
|
|
1237
|
+
}
|
|
884
1238
|
);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
throwIfAborted(request.signal);
|
|
1243
|
+
if (!completed)
|
|
1244
|
+
throw new Error("Responses stream ended without a completed response");
|
|
1245
|
+
const output = terminalResponse.output?.length ? terminalResponse.output : [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
|
|
1246
|
+
if (output.some((item) => item.type === "function_call"))
|
|
1247
|
+
toolCalls.clear();
|
|
1248
|
+
for (const [index, item] of output.entries()) {
|
|
1249
|
+
if (item.type === "function_call") {
|
|
1250
|
+
toolCalls.set(index, {
|
|
1251
|
+
id: item.call_id,
|
|
1252
|
+
name: item.name,
|
|
1253
|
+
arguments: item.arguments
|
|
1254
|
+
});
|
|
885
1255
|
}
|
|
886
1256
|
}
|
|
1257
|
+
const uniqueCalls = /* @__PURE__ */ new Map();
|
|
1258
|
+
for (const call of toolCalls.values()) {
|
|
1259
|
+
if (!call.id || !call.name)
|
|
1260
|
+
throw new Error("Responses stream contains an incomplete tool call");
|
|
1261
|
+
uniqueCalls.set(call.id, call);
|
|
1262
|
+
}
|
|
1263
|
+
toolCalls.clear();
|
|
1264
|
+
[...uniqueCalls.values()].forEach(
|
|
1265
|
+
(call, index) => toolCalls.set(index, call)
|
|
1266
|
+
);
|
|
1267
|
+
if (!output.length && toolCalls.size) {
|
|
1268
|
+
for (const call of toolCalls.values())
|
|
1269
|
+
output.push({
|
|
1270
|
+
type: "function_call",
|
|
1271
|
+
call_id: call.id,
|
|
1272
|
+
name: call.name,
|
|
1273
|
+
arguments: call.arguments
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
if (!output.length && streamedText)
|
|
1277
|
+
output.push({
|
|
1278
|
+
type: "message",
|
|
1279
|
+
role: "assistant",
|
|
1280
|
+
content: [{ type: "output_text", text: streamedText, annotations: [] }]
|
|
1281
|
+
});
|
|
1282
|
+
terminalResponse.output = output;
|
|
1283
|
+
const finalText = this.parseResponsesResult(terminalResponse).content;
|
|
1284
|
+
if (finalText.startsWith(streamedText) && finalText.length > streamedText.length) {
|
|
1285
|
+
yield {
|
|
1286
|
+
type: "message:delta",
|
|
1287
|
+
content: finalText.slice(streamedText.length)
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
887
1290
|
yield* flushToolCalls();
|
|
888
1291
|
if (citations.length > 0) {
|
|
889
1292
|
yield { type: "citation", citations: deduplicateCitations(citations) };
|
|
890
1293
|
}
|
|
891
|
-
yield {
|
|
892
|
-
|
|
1294
|
+
yield {
|
|
1295
|
+
type: "message:end",
|
|
1296
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1297
|
+
};
|
|
1298
|
+
yield {
|
|
1299
|
+
type: "done",
|
|
1300
|
+
usage,
|
|
1301
|
+
providerMetadata: responseMetadata(terminalResponse)
|
|
1302
|
+
};
|
|
893
1303
|
}
|
|
894
1304
|
async *stream(request) {
|
|
1305
|
+
throwIfAborted(request.signal);
|
|
895
1306
|
if (this.shouldUseResponsesApi(request)) {
|
|
896
1307
|
const messageId2 = generateMessageId();
|
|
897
1308
|
yield { type: "message:start", id: messageId2 };
|
|
@@ -899,47 +1310,14 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
899
1310
|
yield* this.streamWithResponses(request);
|
|
900
1311
|
return;
|
|
901
1312
|
} catch (error) {
|
|
902
|
-
yield
|
|
903
|
-
type: "error",
|
|
904
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
905
|
-
code: "OPENAI_RESPONSES_ERROR"
|
|
906
|
-
};
|
|
1313
|
+
yield providerErrorEvent(error, "OPENAI_RESPONSES_ERROR");
|
|
907
1314
|
return;
|
|
908
1315
|
}
|
|
909
1316
|
}
|
|
910
1317
|
const client = await this.getClient();
|
|
911
1318
|
let messages;
|
|
912
1319
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
913
|
-
const processedMessages = request.rawMessages
|
|
914
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
915
|
-
return { ...msg, content: null };
|
|
916
|
-
}
|
|
917
|
-
const hasAttachments = msg.attachments && Array.isArray(msg.attachments) && msg.attachments.length > 0;
|
|
918
|
-
if (hasAttachments) {
|
|
919
|
-
const content = [];
|
|
920
|
-
if (msg.content) {
|
|
921
|
-
content.push({ type: "text", text: msg.content });
|
|
922
|
-
}
|
|
923
|
-
for (const attachment of msg.attachments) {
|
|
924
|
-
if (attachment.type === "image") {
|
|
925
|
-
let imageUrl;
|
|
926
|
-
if (attachment.url) {
|
|
927
|
-
imageUrl = attachment.url;
|
|
928
|
-
} else if (attachment.data) {
|
|
929
|
-
imageUrl = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType || "image/png"};base64,${attachment.data}`;
|
|
930
|
-
} else {
|
|
931
|
-
continue;
|
|
932
|
-
}
|
|
933
|
-
content.push({
|
|
934
|
-
type: "image_url",
|
|
935
|
-
image_url: { url: imageUrl, detail: "auto" }
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
return { ...msg, content, attachments: void 0 };
|
|
940
|
-
}
|
|
941
|
-
return msg;
|
|
942
|
-
});
|
|
1320
|
+
const processedMessages = this.formatRawMessages(request.rawMessages);
|
|
943
1321
|
if (request.systemPrompt) {
|
|
944
1322
|
const hasSystem = processedMessages.some((m) => m.role === "system");
|
|
945
1323
|
if (!hasSystem) {
|
|
@@ -959,7 +1337,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
959
1337
|
request.systemPrompt
|
|
960
1338
|
);
|
|
961
1339
|
}
|
|
962
|
-
const tools = request.
|
|
1340
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1341
|
+
type: "function",
|
|
1342
|
+
function: {
|
|
1343
|
+
name: tool.name,
|
|
1344
|
+
description: tool.description,
|
|
1345
|
+
parameters: normalizeObjectJsonSchema(
|
|
1346
|
+
tool.inputSchema
|
|
1347
|
+
)
|
|
1348
|
+
}
|
|
1349
|
+
})) : formatTools(request.actions ?? []);
|
|
963
1350
|
const webSearchConfig = request.webSearch ?? this.config.webSearch;
|
|
964
1351
|
if (webSearchConfig) {
|
|
965
1352
|
const webSearchTool = {
|
|
@@ -988,26 +1375,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
988
1375
|
tools: tools.length > 0 ? tools : void 0,
|
|
989
1376
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
990
1377
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
991
|
-
...
|
|
1378
|
+
...buildOpenAIParameters(
|
|
992
1379
|
modelIdForPayload,
|
|
993
1380
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
994
|
-
request.config?.temperature ?? this.config.temperature
|
|
1381
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1382
|
+
this.parameterOptions(request)
|
|
995
1383
|
),
|
|
996
1384
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
997
1385
|
stream: true,
|
|
998
1386
|
stream_options: { include_usage: true }
|
|
999
1387
|
};
|
|
1000
1388
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
1001
|
-
const stream = await client.chat.completions.create(payload
|
|
1002
|
-
|
|
1389
|
+
const stream = await client.chat.completions.create(payload, {
|
|
1390
|
+
signal: request.signal
|
|
1391
|
+
});
|
|
1392
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1393
|
+
let finishReason;
|
|
1003
1394
|
const collectedCitations = [];
|
|
1004
1395
|
let citationIndex = 0;
|
|
1005
1396
|
let usage;
|
|
1006
1397
|
for await (const chunk of stream) {
|
|
1007
1398
|
logProviderPayload("openai", "stream chunk", chunk, request.debug);
|
|
1008
|
-
|
|
1009
|
-
break;
|
|
1010
|
-
}
|
|
1399
|
+
throwIfAborted(request.signal);
|
|
1011
1400
|
const delta = chunk.choices[0]?.delta;
|
|
1012
1401
|
const choice = chunk.choices[0];
|
|
1013
1402
|
if (delta?.content) {
|
|
@@ -1030,38 +1419,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1030
1419
|
}
|
|
1031
1420
|
}
|
|
1032
1421
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
const tcExtraContent = toolCall.extra_content;
|
|
1049
|
-
currentToolCall = {
|
|
1050
|
-
id: toolCall.id,
|
|
1051
|
-
name: toolCall.function?.name || "",
|
|
1052
|
-
arguments: toolCall.function?.arguments || "",
|
|
1053
|
-
...tcExtraContent ? { extra_content: tcExtraContent } : {}
|
|
1054
|
-
};
|
|
1055
|
-
yield {
|
|
1056
|
-
type: "action:start",
|
|
1057
|
-
id: currentToolCall.id,
|
|
1058
|
-
name: currentToolCall.name,
|
|
1059
|
-
...currentToolCall.extra_content ? { extra_content: currentToolCall.extra_content } : {}
|
|
1060
|
-
};
|
|
1061
|
-
} else if (currentToolCall && toolCall.function?.arguments) {
|
|
1062
|
-
currentToolCall.arguments += toolCall.function.arguments;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1422
|
+
for (const part of delta?.tool_calls ?? []) {
|
|
1423
|
+
const index = part.index ?? 0;
|
|
1424
|
+
const call = toolCalls.get(index) ?? {
|
|
1425
|
+
id: "",
|
|
1426
|
+
name: "",
|
|
1427
|
+
arguments: ""
|
|
1428
|
+
};
|
|
1429
|
+
if (part.id) call.id = part.id;
|
|
1430
|
+
if (part.function?.name) call.name += part.function.name;
|
|
1431
|
+
if (part.function?.arguments)
|
|
1432
|
+
call.arguments += part.function.arguments;
|
|
1433
|
+
if (part.extra_content) call.extra_content = part.extra_content;
|
|
1434
|
+
toolCalls.set(index, call);
|
|
1065
1435
|
}
|
|
1066
1436
|
if (chunk.usage) {
|
|
1067
1437
|
usage = {
|
|
@@ -1070,21 +1440,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1070
1440
|
total_tokens: chunk.usage.total_tokens
|
|
1071
1441
|
};
|
|
1072
1442
|
}
|
|
1073
|
-
if (choice?.finish_reason)
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1443
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1444
|
+
}
|
|
1445
|
+
throwIfAborted(request.signal);
|
|
1446
|
+
if (!finishReason)
|
|
1447
|
+
throw new Error("OpenAI stream ended without a finish reason");
|
|
1448
|
+
if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
|
|
1449
|
+
throw new Error("OpenAI stream ended with incomplete tool calls");
|
|
1450
|
+
}
|
|
1451
|
+
for (const call of toolCalls.values()) {
|
|
1452
|
+
if (!call.id || !call.name)
|
|
1453
|
+
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1454
|
+
JSON.parse(call.arguments || "{}");
|
|
1455
|
+
}
|
|
1456
|
+
for (const call of toolCalls.values()) {
|
|
1457
|
+
yield {
|
|
1458
|
+
type: "action:start",
|
|
1459
|
+
id: call.id,
|
|
1460
|
+
name: call.name,
|
|
1461
|
+
...call.extra_content ? { extra_content: call.extra_content } : {}
|
|
1462
|
+
};
|
|
1463
|
+
yield { type: "action:args", id: call.id, args: call.arguments };
|
|
1464
|
+
yield { type: "action:end", id: call.id, name: call.name };
|
|
1088
1465
|
}
|
|
1089
1466
|
if (collectedCitations.length > 0) {
|
|
1090
1467
|
const uniqueCitations = deduplicateCitations(collectedCitations);
|
|
@@ -1093,26 +1470,18 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1093
1470
|
yield { type: "message:end" };
|
|
1094
1471
|
yield { type: "done", usage };
|
|
1095
1472
|
} catch (error) {
|
|
1096
|
-
yield {
|
|
1097
|
-
type: "error",
|
|
1098
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
1099
|
-
code: `${this.provider.toUpperCase()}_ERROR`
|
|
1100
|
-
};
|
|
1473
|
+
yield providerErrorEvent(error, `${this.provider.toUpperCase()}_ERROR`);
|
|
1101
1474
|
}
|
|
1102
1475
|
}
|
|
1103
1476
|
async complete(request) {
|
|
1477
|
+
throwIfAborted(request.signal);
|
|
1104
1478
|
if (this.shouldUseResponsesApi(request)) {
|
|
1105
1479
|
return this.completeWithResponses(request);
|
|
1106
1480
|
}
|
|
1107
1481
|
const client = await this.getClient();
|
|
1108
1482
|
let messages;
|
|
1109
1483
|
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
1110
|
-
const sanitized = request.rawMessages
|
|
1111
|
-
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 && msg.content === "") {
|
|
1112
|
-
return { ...msg, content: null };
|
|
1113
|
-
}
|
|
1114
|
-
return msg;
|
|
1115
|
-
});
|
|
1484
|
+
const sanitized = this.formatRawMessages(request.rawMessages);
|
|
1116
1485
|
if (request.systemPrompt && !sanitized.some((message2) => message2.role === "system")) {
|
|
1117
1486
|
messages = [
|
|
1118
1487
|
{ role: "system", content: request.systemPrompt },
|
|
@@ -1127,7 +1496,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1127
1496
|
request.systemPrompt
|
|
1128
1497
|
);
|
|
1129
1498
|
}
|
|
1130
|
-
const tools = request.
|
|
1499
|
+
const tools = request.toolDefinitions?.length ? request.toolDefinitions.filter((tool) => tool.available !== false).map((tool) => ({
|
|
1500
|
+
type: "function",
|
|
1501
|
+
function: {
|
|
1502
|
+
name: tool.name,
|
|
1503
|
+
description: tool.description,
|
|
1504
|
+
parameters: normalizeObjectJsonSchema(
|
|
1505
|
+
tool.inputSchema
|
|
1506
|
+
)
|
|
1507
|
+
}
|
|
1508
|
+
})) : formatTools(request.actions ?? []);
|
|
1131
1509
|
const openaiToolOptions = request.providerToolOptions?.openai;
|
|
1132
1510
|
const toolChoice = openaiToolOptions?.toolChoice && typeof openaiToolOptions.toolChoice === "object" ? {
|
|
1133
1511
|
type: "function",
|
|
@@ -1142,16 +1520,19 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1142
1520
|
tools: tools.length > 0 ? tools : void 0,
|
|
1143
1521
|
tool_choice: tools.length > 0 ? toolChoice : void 0,
|
|
1144
1522
|
parallel_tool_calls: tools.length > 0 ? openaiToolOptions?.parallelToolCalls : void 0,
|
|
1145
|
-
...
|
|
1523
|
+
...buildOpenAIParameters(
|
|
1146
1524
|
modelIdForCompletePayload,
|
|
1147
1525
|
request.config?.maxTokens ?? this.config.maxTokens,
|
|
1148
|
-
request.config?.temperature ?? this.config.temperature
|
|
1526
|
+
request.config?.temperature ?? this.config.temperature,
|
|
1527
|
+
this.parameterOptions(request)
|
|
1149
1528
|
),
|
|
1150
1529
|
response_format: toOpenAIResponseFormat(request.config?.responseFormat),
|
|
1151
1530
|
stream: false
|
|
1152
1531
|
};
|
|
1153
1532
|
logProviderPayload("openai", "request payload", payload, request.debug);
|
|
1154
|
-
const response = await client.chat.completions.create(payload
|
|
1533
|
+
const response = await client.chat.completions.create(payload, {
|
|
1534
|
+
signal: request.signal
|
|
1535
|
+
});
|
|
1155
1536
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
1156
1537
|
const choice = response.choices?.[0];
|
|
1157
1538
|
const message = choice?.message;
|