@nvae/llmswitch 0.7.0 → 0.9.0
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 +220 -0
- package/dist/adapters/opencode.js +22 -2
- package/dist/bridge/anthropic-to-chat-response.js +332 -0
- package/dist/bridge/chat-to-anthropic-request.js +270 -0
- package/dist/bridge/chat-to-responses-request.js +216 -0
- package/dist/bridge/responses-to-chat-response.js +393 -0
- package/dist/cli.js +2 -0
- package/dist/commands/gateway-cmd.js +1040 -0
- package/dist/commands/prompts.js +63 -18
- package/dist/commands/tool.js +1 -0
- package/dist/gateway/health.js +45 -0
- package/dist/gateway/keys.js +433 -0
- package/dist/gateway/manager.js +278 -0
- package/dist/gateway/pipeline.js +328 -0
- package/dist/gateway/rate-limit.js +285 -0
- package/dist/gateway/router.js +163 -0
- package/dist/gateway/runtime.js +45 -0
- package/dist/gateway/server.js +1053 -0
- package/dist/gateway/state.js +135 -0
- package/dist/gateway/store.js +392 -0
- package/dist/gateway/tokens.js +423 -0
- package/dist/gateway/types.js +30 -0
- package/dist/gateway/usage.js +152 -0
- package/dist/store/profiles.js +10 -0
- package/dist/utils/model-metadata.js +179 -0
- package/dist/utils/paths.js +24 -0
- package/package.json +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate OpenAI Chat Completions requests → Anthropic Messages API.
|
|
3
|
+
*
|
|
4
|
+
* Reverse direction of `anthropic-translate-request.ts`. Used by the gateway
|
|
5
|
+
* when an OpenAI-format client is routed to an Anthropic upstream.
|
|
6
|
+
*/
|
|
7
|
+
/** Anthropic requires max_tokens; use this when the client omits it. */
|
|
8
|
+
export const DEFAULT_ANTHROPIC_MAX_TOKENS = 4096;
|
|
9
|
+
function asRecord(value) {
|
|
10
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function parseDataUrl(url) {
|
|
16
|
+
const match = url.match(/^data:([^;,]+);base64,([\s\S]*)$/);
|
|
17
|
+
if (!match)
|
|
18
|
+
return null;
|
|
19
|
+
return { mediaType: match[1] ?? "image/png", data: match[2] ?? "" };
|
|
20
|
+
}
|
|
21
|
+
function imageBlock(url) {
|
|
22
|
+
const inline = parseDataUrl(url);
|
|
23
|
+
if (inline) {
|
|
24
|
+
return {
|
|
25
|
+
type: "image",
|
|
26
|
+
source: {
|
|
27
|
+
type: "base64",
|
|
28
|
+
media_type: inline.mediaType,
|
|
29
|
+
data: inline.data,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (/^https?:\/\//i.test(url)) {
|
|
34
|
+
return { type: "image", source: { type: "url", url } };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** Chat content (string or multimodal parts) → Anthropic content blocks. */
|
|
39
|
+
export function chatContentToAnthropicBlocks(content) {
|
|
40
|
+
if (typeof content === "string") {
|
|
41
|
+
return content ? [{ type: "text", text: content }] : [];
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(content))
|
|
44
|
+
return [];
|
|
45
|
+
const blocks = [];
|
|
46
|
+
for (const raw of content) {
|
|
47
|
+
const part = asRecord(raw);
|
|
48
|
+
if (!part)
|
|
49
|
+
continue;
|
|
50
|
+
const type = String(part.type || "");
|
|
51
|
+
if ((type === "text" || type === "input_text" || type === "output_text") &&
|
|
52
|
+
typeof part.text === "string") {
|
|
53
|
+
blocks.push({ type: "text", text: part.text });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (type === "image_url") {
|
|
57
|
+
const nested = asRecord(part.image_url);
|
|
58
|
+
const url = typeof nested?.url === "string"
|
|
59
|
+
? nested.url
|
|
60
|
+
: typeof part.image_url === "string"
|
|
61
|
+
? part.image_url
|
|
62
|
+
: "";
|
|
63
|
+
const block = url ? imageBlock(url) : null;
|
|
64
|
+
if (block)
|
|
65
|
+
blocks.push(block);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Already an Anthropic-shaped block (image/document/thinking): keep as-is.
|
|
69
|
+
if (part.source || type === "thinking" || type === "redacted_thinking") {
|
|
70
|
+
blocks.push(part);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (typeof part.text === "string") {
|
|
74
|
+
blocks.push({ type: "text", text: part.text });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return blocks;
|
|
78
|
+
}
|
|
79
|
+
function stringifyToolArguments(raw) {
|
|
80
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
81
|
+
return raw;
|
|
82
|
+
}
|
|
83
|
+
if (typeof raw !== "string" || !raw.trim())
|
|
84
|
+
return {};
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(raw);
|
|
87
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Partial or non-JSON arguments: forward as a single field.
|
|
93
|
+
}
|
|
94
|
+
return { input: raw };
|
|
95
|
+
}
|
|
96
|
+
function contentToText(content) {
|
|
97
|
+
if (typeof content === "string")
|
|
98
|
+
return content;
|
|
99
|
+
if (!Array.isArray(content))
|
|
100
|
+
return "";
|
|
101
|
+
return content
|
|
102
|
+
.map((raw) => {
|
|
103
|
+
const part = asRecord(raw);
|
|
104
|
+
return part && typeof part.text === "string" ? part.text : "";
|
|
105
|
+
})
|
|
106
|
+
.join("");
|
|
107
|
+
}
|
|
108
|
+
function pushMessage(messages, role, blocks) {
|
|
109
|
+
if (!blocks.length)
|
|
110
|
+
return;
|
|
111
|
+
const last = messages[messages.length - 1];
|
|
112
|
+
// Anthropic expects alternating roles; merge consecutive same-role turns.
|
|
113
|
+
if (last && last.role === role) {
|
|
114
|
+
last.content.push(...blocks);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
messages.push({ role, content: blocks });
|
|
118
|
+
}
|
|
119
|
+
export function chatMessagesToAnthropicMessages(chatMessages) {
|
|
120
|
+
const systemParts = [];
|
|
121
|
+
const messages = [];
|
|
122
|
+
for (const message of chatMessages) {
|
|
123
|
+
const role = String(message.role || "user");
|
|
124
|
+
if (role === "system" || role === "developer") {
|
|
125
|
+
const text = contentToText(message.content);
|
|
126
|
+
if (text)
|
|
127
|
+
systemParts.push(text);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (role === "tool" || role === "function") {
|
|
131
|
+
const toolUseId = message.tool_call_id || message.name || "";
|
|
132
|
+
if (!toolUseId)
|
|
133
|
+
continue;
|
|
134
|
+
pushMessage(messages, "user", [
|
|
135
|
+
{
|
|
136
|
+
type: "tool_result",
|
|
137
|
+
tool_use_id: toolUseId,
|
|
138
|
+
content: contentToText(message.content),
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (role === "assistant") {
|
|
144
|
+
const blocks = chatContentToAnthropicBlocks(message.content);
|
|
145
|
+
for (const call of message.tool_calls ?? []) {
|
|
146
|
+
blocks.push({
|
|
147
|
+
type: "tool_use",
|
|
148
|
+
id: call.id,
|
|
149
|
+
name: call.function?.name || "tool",
|
|
150
|
+
input: stringifyToolArguments(call.function?.arguments),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
pushMessage(messages, "assistant", blocks);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
pushMessage(messages, "user", chatContentToAnthropicBlocks(message.content));
|
|
157
|
+
}
|
|
158
|
+
return { system: systemParts.join("\n\n"), messages };
|
|
159
|
+
}
|
|
160
|
+
export function chatToolsToAnthropicTools(tools) {
|
|
161
|
+
if (!Array.isArray(tools) || !tools.length)
|
|
162
|
+
return undefined;
|
|
163
|
+
const out = [];
|
|
164
|
+
for (const tool of tools) {
|
|
165
|
+
const fn = tool?.function;
|
|
166
|
+
const name = String(fn?.name || "").trim();
|
|
167
|
+
if (!name)
|
|
168
|
+
continue;
|
|
169
|
+
const entry = {
|
|
170
|
+
name,
|
|
171
|
+
input_schema: fn?.parameters ?? { type: "object", properties: {} },
|
|
172
|
+
};
|
|
173
|
+
if (typeof fn?.description === "string")
|
|
174
|
+
entry.description = fn.description;
|
|
175
|
+
out.push(entry);
|
|
176
|
+
}
|
|
177
|
+
return out.length ? out : undefined;
|
|
178
|
+
}
|
|
179
|
+
export function chatToolChoiceToAnthropic(toolChoice) {
|
|
180
|
+
if (toolChoice == null)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (toolChoice === "auto")
|
|
183
|
+
return { type: "auto" };
|
|
184
|
+
if (toolChoice === "required")
|
|
185
|
+
return { type: "any" };
|
|
186
|
+
if (toolChoice === "none")
|
|
187
|
+
return { type: "none" };
|
|
188
|
+
const obj = asRecord(toolChoice);
|
|
189
|
+
if (!obj)
|
|
190
|
+
return undefined;
|
|
191
|
+
if (obj.type === "function") {
|
|
192
|
+
const nested = asRecord(obj.function);
|
|
193
|
+
const name = String(nested?.name || obj.name || "");
|
|
194
|
+
return name ? { type: "tool", name } : { type: "auto" };
|
|
195
|
+
}
|
|
196
|
+
// Already Anthropic-shaped.
|
|
197
|
+
if (obj.type === "auto" || obj.type === "any" || obj.type === "tool") {
|
|
198
|
+
return obj;
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
function normalizeStop(stop) {
|
|
203
|
+
if (typeof stop === "string")
|
|
204
|
+
return stop ? [stop] : undefined;
|
|
205
|
+
if (Array.isArray(stop)) {
|
|
206
|
+
const list = stop.filter((v) => typeof v === "string");
|
|
207
|
+
return list.length ? list : undefined;
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Chat Completions request → Anthropic Messages request body.
|
|
213
|
+
* `max_tokens` is mandatory upstream, so a default is applied when missing.
|
|
214
|
+
*/
|
|
215
|
+
export function chatToAnthropicRequest(body) {
|
|
216
|
+
const chatMessages = Array.isArray(body.messages)
|
|
217
|
+
? body.messages
|
|
218
|
+
: [];
|
|
219
|
+
const { system, messages } = chatMessagesToAnthropicMessages(chatMessages);
|
|
220
|
+
const stream = Boolean(body.stream);
|
|
221
|
+
const maxTokens = typeof body.max_tokens === "number"
|
|
222
|
+
? body.max_tokens
|
|
223
|
+
: typeof body.max_completion_tokens === "number"
|
|
224
|
+
? body.max_completion_tokens
|
|
225
|
+
: DEFAULT_ANTHROPIC_MAX_TOKENS;
|
|
226
|
+
const out = {
|
|
227
|
+
model: String(body.model || ""),
|
|
228
|
+
max_tokens: maxTokens,
|
|
229
|
+
messages,
|
|
230
|
+
stream,
|
|
231
|
+
};
|
|
232
|
+
if (system)
|
|
233
|
+
out.system = system;
|
|
234
|
+
const tools = chatToolsToAnthropicTools(body.tools);
|
|
235
|
+
if (tools)
|
|
236
|
+
out.tools = tools;
|
|
237
|
+
if (body.tool_choice !== undefined) {
|
|
238
|
+
const choice = chatToolChoiceToAnthropic(body.tool_choice);
|
|
239
|
+
if (choice !== undefined)
|
|
240
|
+
out.tool_choice = choice;
|
|
241
|
+
}
|
|
242
|
+
if (typeof body.temperature === "number")
|
|
243
|
+
out.temperature = body.temperature;
|
|
244
|
+
if (typeof body.top_p === "number")
|
|
245
|
+
out.top_p = body.top_p;
|
|
246
|
+
const stopSequences = normalizeStop(body.stop);
|
|
247
|
+
if (stopSequences)
|
|
248
|
+
out.stop_sequences = stopSequences;
|
|
249
|
+
if (typeof body.metadata === "object" && body.metadata) {
|
|
250
|
+
out.metadata = body.metadata;
|
|
251
|
+
}
|
|
252
|
+
// Map OpenAI reasoning_effort onto Anthropic extended thinking budget.
|
|
253
|
+
const effort = body.reasoning_effort;
|
|
254
|
+
if (typeof effort === "string") {
|
|
255
|
+
const budget = effort === "low"
|
|
256
|
+
? 1024
|
|
257
|
+
: effort === "medium"
|
|
258
|
+
? 4096
|
|
259
|
+
: effort === "high"
|
|
260
|
+
? 8192
|
|
261
|
+
: 0;
|
|
262
|
+
if (budget > 0) {
|
|
263
|
+
out.thinking = { type: "enabled", budget_tokens: budget };
|
|
264
|
+
// Thinking requires headroom above the budget.
|
|
265
|
+
if (maxTokens <= budget)
|
|
266
|
+
out.max_tokens = budget + 1024;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate OpenAI Chat Completions requests → OpenAI Responses API.
|
|
3
|
+
*
|
|
4
|
+
* Reverse direction of `translate-request.ts`. Used by the gateway when a
|
|
5
|
+
* Chat-format client is routed to a Responses upstream.
|
|
6
|
+
*/
|
|
7
|
+
function asRecord(value) {
|
|
8
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function contentToText(content) {
|
|
14
|
+
if (typeof content === "string")
|
|
15
|
+
return content;
|
|
16
|
+
if (!Array.isArray(content))
|
|
17
|
+
return "";
|
|
18
|
+
return content
|
|
19
|
+
.map((raw) => {
|
|
20
|
+
const part = asRecord(raw);
|
|
21
|
+
return part && typeof part.text === "string" ? part.text : "";
|
|
22
|
+
})
|
|
23
|
+
.join("");
|
|
24
|
+
}
|
|
25
|
+
/** Chat content parts → Responses input content parts. */
|
|
26
|
+
function chatContentToInputParts(content, textType) {
|
|
27
|
+
if (typeof content === "string") {
|
|
28
|
+
return content ? [{ type: textType, text: content }] : [];
|
|
29
|
+
}
|
|
30
|
+
if (!Array.isArray(content))
|
|
31
|
+
return [];
|
|
32
|
+
const parts = [];
|
|
33
|
+
for (const raw of content) {
|
|
34
|
+
const part = asRecord(raw);
|
|
35
|
+
if (!part)
|
|
36
|
+
continue;
|
|
37
|
+
const type = String(part.type || "");
|
|
38
|
+
if ((type === "text" || type === "input_text" || type === "output_text") &&
|
|
39
|
+
typeof part.text === "string") {
|
|
40
|
+
parts.push({ type: textType, text: part.text });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (type === "image_url") {
|
|
44
|
+
const nested = asRecord(part.image_url);
|
|
45
|
+
const url = typeof nested?.url === "string"
|
|
46
|
+
? nested.url
|
|
47
|
+
: typeof part.image_url === "string"
|
|
48
|
+
? part.image_url
|
|
49
|
+
: "";
|
|
50
|
+
if (url) {
|
|
51
|
+
const item = { type: "input_image", image_url: url };
|
|
52
|
+
if (typeof nested?.detail === "string")
|
|
53
|
+
item.detail = nested.detail;
|
|
54
|
+
parts.push(item);
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (type === "input_audio") {
|
|
59
|
+
const nested = asRecord(part.input_audio);
|
|
60
|
+
if (nested)
|
|
61
|
+
parts.push({ type: "input_audio", input_audio: nested });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (type === "file") {
|
|
65
|
+
const nested = asRecord(part.file);
|
|
66
|
+
if (nested)
|
|
67
|
+
parts.push({ type: "input_file", ...nested });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (typeof part.text === "string") {
|
|
71
|
+
parts.push({ type: textType, text: part.text });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return parts;
|
|
75
|
+
}
|
|
76
|
+
export function chatMessagesToResponsesInput(chatMessages) {
|
|
77
|
+
const instructionParts = [];
|
|
78
|
+
const input = [];
|
|
79
|
+
for (const message of chatMessages) {
|
|
80
|
+
const role = String(message.role || "user");
|
|
81
|
+
if (role === "system" || role === "developer") {
|
|
82
|
+
const text = contentToText(message.content);
|
|
83
|
+
if (text)
|
|
84
|
+
instructionParts.push(text);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (role === "tool" || role === "function") {
|
|
88
|
+
const callId = message.tool_call_id || message.name || "";
|
|
89
|
+
if (!callId)
|
|
90
|
+
continue;
|
|
91
|
+
input.push({
|
|
92
|
+
type: "function_call_output",
|
|
93
|
+
call_id: callId,
|
|
94
|
+
output: contentToText(message.content),
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (role === "assistant") {
|
|
99
|
+
const parts = chatContentToInputParts(message.content, "output_text");
|
|
100
|
+
if (parts.length) {
|
|
101
|
+
input.push({ type: "message", role: "assistant", content: parts });
|
|
102
|
+
}
|
|
103
|
+
for (const call of message.tool_calls ?? []) {
|
|
104
|
+
input.push({
|
|
105
|
+
type: "function_call",
|
|
106
|
+
call_id: call.id,
|
|
107
|
+
name: call.function?.name || "tool",
|
|
108
|
+
arguments: typeof call.function?.arguments === "string"
|
|
109
|
+
? call.function.arguments
|
|
110
|
+
: JSON.stringify(call.function?.arguments ?? {}),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const parts = chatContentToInputParts(message.content, "input_text");
|
|
116
|
+
if (parts.length) {
|
|
117
|
+
input.push({ type: "message", role: "user", content: parts });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { instructions: instructionParts.join("\n\n"), input };
|
|
121
|
+
}
|
|
122
|
+
export function chatToolsToResponsesTools(tools) {
|
|
123
|
+
if (!Array.isArray(tools) || !tools.length)
|
|
124
|
+
return undefined;
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const tool of tools) {
|
|
127
|
+
const fn = tool?.function;
|
|
128
|
+
const name = String(fn?.name || "").trim();
|
|
129
|
+
if (!name)
|
|
130
|
+
continue;
|
|
131
|
+
const entry = {
|
|
132
|
+
type: "function",
|
|
133
|
+
name,
|
|
134
|
+
parameters: fn?.parameters ?? { type: "object", properties: {} },
|
|
135
|
+
strict: false,
|
|
136
|
+
};
|
|
137
|
+
if (typeof fn?.description === "string")
|
|
138
|
+
entry.description = fn.description;
|
|
139
|
+
out.push(entry);
|
|
140
|
+
}
|
|
141
|
+
return out.length ? out : undefined;
|
|
142
|
+
}
|
|
143
|
+
export function chatToolChoiceToResponses(toolChoice) {
|
|
144
|
+
if (toolChoice == null)
|
|
145
|
+
return undefined;
|
|
146
|
+
if (toolChoice === "auto" ||
|
|
147
|
+
toolChoice === "none" ||
|
|
148
|
+
toolChoice === "required") {
|
|
149
|
+
return toolChoice;
|
|
150
|
+
}
|
|
151
|
+
const obj = asRecord(toolChoice);
|
|
152
|
+
if (!obj)
|
|
153
|
+
return undefined;
|
|
154
|
+
if (obj.type === "function") {
|
|
155
|
+
const nested = asRecord(obj.function);
|
|
156
|
+
const name = String(nested?.name || obj.name || "");
|
|
157
|
+
return name ? { type: "function", name } : "auto";
|
|
158
|
+
}
|
|
159
|
+
return toolChoice;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Chat Completions request → Responses request body.
|
|
163
|
+
* `store` defaults to false: the gateway is stateless and must not create
|
|
164
|
+
* server-side conversation state on the upstream.
|
|
165
|
+
*/
|
|
166
|
+
export function chatToResponsesRequest(body) {
|
|
167
|
+
const chatMessages = Array.isArray(body.messages)
|
|
168
|
+
? body.messages
|
|
169
|
+
: [];
|
|
170
|
+
const { instructions, input } = chatMessagesToResponsesInput(chatMessages);
|
|
171
|
+
const stream = Boolean(body.stream);
|
|
172
|
+
const out = {
|
|
173
|
+
model: String(body.model || ""),
|
|
174
|
+
input,
|
|
175
|
+
stream,
|
|
176
|
+
store: false,
|
|
177
|
+
};
|
|
178
|
+
if (instructions)
|
|
179
|
+
out.instructions = instructions;
|
|
180
|
+
const tools = chatToolsToResponsesTools(body.tools);
|
|
181
|
+
if (tools)
|
|
182
|
+
out.tools = tools;
|
|
183
|
+
if (body.tool_choice !== undefined) {
|
|
184
|
+
const choice = chatToolChoiceToResponses(body.tool_choice);
|
|
185
|
+
if (choice !== undefined)
|
|
186
|
+
out.tool_choice = choice;
|
|
187
|
+
}
|
|
188
|
+
if (typeof body.parallel_tool_calls === "boolean") {
|
|
189
|
+
out.parallel_tool_calls = body.parallel_tool_calls;
|
|
190
|
+
}
|
|
191
|
+
if (typeof body.temperature === "number")
|
|
192
|
+
out.temperature = body.temperature;
|
|
193
|
+
if (typeof body.top_p === "number")
|
|
194
|
+
out.top_p = body.top_p;
|
|
195
|
+
const maxOut = typeof body.max_completion_tokens === "number"
|
|
196
|
+
? body.max_completion_tokens
|
|
197
|
+
: typeof body.max_tokens === "number"
|
|
198
|
+
? body.max_tokens
|
|
199
|
+
: undefined;
|
|
200
|
+
if (typeof maxOut === "number")
|
|
201
|
+
out.max_output_tokens = maxOut;
|
|
202
|
+
if (typeof body.reasoning_effort === "string") {
|
|
203
|
+
out.reasoning = { effort: body.reasoning_effort };
|
|
204
|
+
}
|
|
205
|
+
const responseFormat = asRecord(body.response_format);
|
|
206
|
+
if (responseFormat) {
|
|
207
|
+
out.text = { format: responseFormat };
|
|
208
|
+
}
|
|
209
|
+
if (typeof body.metadata === "object" && body.metadata) {
|
|
210
|
+
out.metadata = body.metadata;
|
|
211
|
+
}
|
|
212
|
+
if (typeof body.user === "string" && body.user) {
|
|
213
|
+
out.user = body.user;
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|