@hileeon/mcc 0.1.7 → 0.1.9
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 +226 -127
- package/dist/accounts/store.d.ts +1 -0
- package/dist/accounts/store.d.ts.map +1 -1
- package/dist/accounts/store.js.map +1 -1
- package/dist/commands/launch.d.ts +9 -0
- package/dist/commands/launch.d.ts.map +1 -0
- package/dist/commands/launch.js +158 -0
- package/dist/commands/launch.js.map +1 -0
- package/dist/commands/mcp.d.ts +9 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +112 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/profile.d.ts +8 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +125 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/core/model-router.d.ts.map +1 -1
- package/dist/core/model-router.js +5 -2
- package/dist/core/model-router.js.map +1 -1
- package/dist/{dashboard-server.d.ts → dashboard/server.d.ts} +1 -1
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/{dashboard-server.js → dashboard/server.js} +169 -51
- package/dist/dashboard/server.js.map +1 -0
- package/dist/mcc.d.ts +4 -2
- package/dist/mcc.d.ts.map +1 -1
- package/dist/mcc.js +121 -408
- package/dist/mcc.js.map +1 -1
- package/dist/mcp/mcp-config.d.ts +17 -1
- package/dist/mcp/mcp-config.d.ts.map +1 -1
- package/dist/mcp/mcp-config.js +50 -17
- package/dist/mcp/mcp-config.js.map +1 -1
- package/dist/proxy/proxy-daemon.d.ts.map +1 -1
- package/dist/proxy/proxy-daemon.js +17 -2
- package/dist/proxy/proxy-daemon.js.map +1 -1
- package/dist/proxy/proxy-entry.js +5 -3
- package/dist/proxy/proxy-entry.js.map +1 -1
- package/dist/proxy/proxy-server.d.ts.map +1 -1
- package/dist/proxy/proxy-server.js +32 -6
- package/dist/proxy/proxy-server.js.map +1 -1
- package/dist/shared/config.d.ts +15 -0
- package/dist/shared/config.d.ts.map +1 -0
- package/dist/shared/config.js +79 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/logger.d.ts +23 -18
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/logger.js +17 -178
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/provider-preset-catalog.d.ts +6 -2
- package/dist/shared/provider-preset-catalog.d.ts.map +1 -1
- package/dist/shared/provider-preset-catalog.js +47 -26
- package/dist/shared/provider-preset-catalog.js.map +1 -1
- package/dist/ui/assets/index-ClqmrjNk.js +40 -0
- package/dist/ui/assets/index-CwMwQ-Z4.css +1 -0
- package/dist/ui/index.html +21 -13
- package/dist/update.d.ts +31 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +196 -0
- package/dist/update.js.map +1 -0
- package/lib/mcp/mcc-image-analysis-server.cjs +454 -454
- package/lib/mcp/mcc-websearch-server.cjs +339 -339
- package/lib/mcp-hooks/image-analysis-runtime.cjs +510 -510
- package/lib/mcp-hooks/image-analyzer-transformer.cjs +526 -526
- package/lib/mcp-hooks/websearch-transformer.cjs +1597 -1421
- package/lib/proxy/config/config-loader-facade.js +24 -24
- package/lib/proxy/glmt/delta-accumulator.js +362 -362
- package/lib/proxy/glmt/glmt-transformer.js +203 -203
- package/lib/proxy/glmt/index.js +40 -40
- package/lib/proxy/glmt/locale-enforcer.js +68 -68
- package/lib/proxy/glmt/pipeline/content-transformer.js +161 -161
- package/lib/proxy/glmt/pipeline/index.js +19 -19
- package/lib/proxy/glmt/pipeline/request-transformer.js +115 -115
- package/lib/proxy/glmt/pipeline/response-builder.js +204 -204
- package/lib/proxy/glmt/pipeline/stream-parser.js +233 -233
- package/lib/proxy/glmt/pipeline/tool-call-handler.js +77 -77
- package/lib/proxy/glmt/pipeline/types.js +5 -5
- package/lib/proxy/glmt/reasoning-enforcer.js +150 -150
- package/lib/proxy/glmt/sse-parser.js +101 -101
- package/lib/proxy/services/logging.js +13 -13
- package/lib/proxy/transformers/request-transformer.js +471 -451
- package/lib/proxy/transformers/sse-stream-transformer.js +198 -198
- package/lib/shared/logger.cjs +156 -138
- package/package.json +58 -41
- package/dist/dashboard-server.d.ts.map +0 -1
- package/dist/dashboard-server.js.map +0 -1
- package/dist/ui/assets/index-B16lhKZ6.js +0 -40
- package/dist/ui/assets/index-jEfiB6-h.css +0 -1
|
@@ -1,452 +1,472 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ProxyRequestTransformer = void 0;
|
|
4
|
-
const TOOL_USE_ARGUMENTS_FALLBACK = '{}';
|
|
5
|
-
function assertObject(value, label) {
|
|
6
|
-
if (typeof value !== 'object' || value === null) {
|
|
7
|
-
throw new Error(`${label} must be an object`);
|
|
8
|
-
}
|
|
9
|
-
return value;
|
|
10
|
-
}
|
|
11
|
-
function asNumber(value) {
|
|
12
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
13
|
-
}
|
|
14
|
-
function asStringArray(value) {
|
|
15
|
-
if (!Array.isArray(value)) {
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
const result = value.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
19
|
-
return result.length > 0 ? result : undefined;
|
|
20
|
-
}
|
|
21
|
-
function asMetadata(value) {
|
|
22
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
23
|
-
? value
|
|
24
|
-
: undefined;
|
|
25
|
-
}
|
|
26
|
-
function safeJsonStringify(value, fallback) {
|
|
27
|
-
try {
|
|
28
|
-
const serialized = JSON.stringify(value);
|
|
29
|
-
return typeof serialized === 'string' ? serialized : fallback;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return fallback;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function flattenTextContent(content, label) {
|
|
36
|
-
if (typeof content === 'string') {
|
|
37
|
-
return content;
|
|
38
|
-
}
|
|
39
|
-
if (!Array.isArray(content)) {
|
|
40
|
-
throw new Error(`${label} must be a string or content block array`);
|
|
41
|
-
}
|
|
42
|
-
return content
|
|
43
|
-
.map((block, index) => {
|
|
44
|
-
const parsed = assertObject(block, `${label}[${index}]`);
|
|
45
|
-
if (parsed.type !== 'text') {
|
|
46
|
-
throw new Error(`${label}[${index}].type "${String(parsed.type)}" is not supported`);
|
|
47
|
-
}
|
|
48
|
-
return typeof parsed.text === 'string' ? parsed.text : '';
|
|
49
|
-
})
|
|
50
|
-
.join('\n');
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Convert tool_result content to OpenAI-compatible format.
|
|
54
|
-
* Handles strings, arrays with text/image blocks, and error prefixing.
|
|
55
|
-
* Ported from openclaude's convertToolResultContent.
|
|
56
|
-
*/
|
|
57
|
-
function convertToolResultContent(content, isError, label) {
|
|
58
|
-
if (content === undefined) {
|
|
59
|
-
return '';
|
|
60
|
-
}
|
|
61
|
-
if (typeof content === 'string') {
|
|
62
|
-
return isError ? `Error: ${content}` : content;
|
|
63
|
-
}
|
|
64
|
-
if (!Array.isArray(content)) {
|
|
65
|
-
const text = safeJsonStringify(content, '[unserializable content]');
|
|
66
|
-
return isError ? `Error: ${text}` : text;
|
|
67
|
-
}
|
|
68
|
-
const parts = [];
|
|
69
|
-
for (const [index, block] of content.entries()) {
|
|
70
|
-
const parsed = assertObject(block, `${label}[${index}]`);
|
|
71
|
-
if (parsed.type === 'text' && typeof parsed.text === 'string') {
|
|
72
|
-
parts.push(parsed.text);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (parsed.type === 'image') {
|
|
76
|
-
const source = typeof parsed.source === 'object' && parsed.source !== null
|
|
77
|
-
? parsed.source
|
|
78
|
-
: undefined;
|
|
79
|
-
const description = source?.type === 'url'
|
|
80
|
-
? 'url image payload'
|
|
81
|
-
: source?.type === 'base64' && typeof source.media_type === 'string'
|
|
82
|
-
? `${source.media_type} base64 payload`
|
|
83
|
-
: 'unsupported image payload';
|
|
84
|
-
parts.push(`[tool_result image omitted: ${description}]`);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (typeof parsed.text === 'string') {
|
|
88
|
-
parts.push(parsed.text);
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
throw new Error(`${label}[${index}].type "${String(parsed.type)}" is not supported`);
|
|
92
|
-
}
|
|
93
|
-
const text = parts.join('\n');
|
|
94
|
-
if (!text) {
|
|
95
|
-
return isError ? 'Error:' : '';
|
|
96
|
-
}
|
|
97
|
-
return isError ? `Error: ${text}` : text;
|
|
98
|
-
}
|
|
99
|
-
function createFallbackToolId(messageIndex, blockIndex) {
|
|
100
|
-
return `toolu_proxy_fallback_${messageIndex}_${blockIndex}`;
|
|
101
|
-
}
|
|
102
|
-
function toImagePart(block, label) {
|
|
103
|
-
const source = block.source;
|
|
104
|
-
if (!source) {
|
|
105
|
-
throw new Error(`${label}.source is missing`);
|
|
106
|
-
}
|
|
107
|
-
if (source.type === 'url' && source.url) {
|
|
108
|
-
return {
|
|
109
|
-
type: 'image_url',
|
|
110
|
-
image_url: { url: source.url },
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
if (source.type === 'base64' && source.media_type && source.data) {
|
|
114
|
-
return {
|
|
115
|
-
type: 'image_url',
|
|
116
|
-
image_url: {
|
|
117
|
-
url: `data:${source.media_type};base64,${source.data}`,
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
throw new Error(`${label}.source must be a base64 or url image payload`);
|
|
122
|
-
}
|
|
123
|
-
function isImageBlock(block) {
|
|
124
|
-
return block.type === 'image';
|
|
125
|
-
}
|
|
126
|
-
function isToolUseBlock(block) {
|
|
127
|
-
return block.type === 'tool_use';
|
|
128
|
-
}
|
|
129
|
-
function isToolResultBlock(block) {
|
|
130
|
-
return block.type === 'tool_result';
|
|
131
|
-
}
|
|
132
|
-
function flushUserContent(messages, parts) {
|
|
133
|
-
if (parts.length === 0) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const onlyText = parts.every((part) => part.type === 'text');
|
|
137
|
-
messages.push({
|
|
138
|
-
role: 'user',
|
|
139
|
-
content: onlyText ? parts.map((part) => part.text).join('\n') : [...parts],
|
|
140
|
-
});
|
|
141
|
-
parts.length = 0;
|
|
142
|
-
}
|
|
143
|
-
function transformTools(value) {
|
|
144
|
-
if (!Array.isArray(value)) {
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
const tools = value
|
|
148
|
-
.filter((entry) => typeof entry === 'object' && entry !== null)
|
|
149
|
-
.map((entry) => {
|
|
150
|
-
const rawSchema = typeof entry.input_schema === 'object' && entry.input_schema !== null
|
|
151
|
-
? entry.input_schema
|
|
152
|
-
: { type: 'object', properties: {} };
|
|
153
|
-
return {
|
|
154
|
-
type: 'function',
|
|
155
|
-
function: {
|
|
156
|
-
name: typeof entry.name === 'string' ? entry.name : 'tool',
|
|
157
|
-
...(typeof entry.description === 'string' ? { description: entry.description } : {}),
|
|
158
|
-
parameters: rawSchema,
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
});
|
|
162
|
-
return tools.length > 0 ? tools : undefined;
|
|
163
|
-
}
|
|
164
|
-
function transformToolChoice(value, hasTools) {
|
|
165
|
-
if (!value) {
|
|
166
|
-
return hasTools ? { tool_choice: 'auto' } : {};
|
|
167
|
-
}
|
|
168
|
-
if (!hasTools) {
|
|
169
|
-
throw new Error('tool_choice requires tools');
|
|
170
|
-
}
|
|
171
|
-
const parallelToolCalls = value.disable_parallel_tool_use === true ? { parallel_tool_calls: false } : {};
|
|
172
|
-
switch (value.type) {
|
|
173
|
-
case undefined:
|
|
174
|
-
case 'auto':
|
|
175
|
-
return { tool_choice: 'auto', ...parallelToolCalls };
|
|
176
|
-
case 'none':
|
|
177
|
-
return { tool_choice: 'none' };
|
|
178
|
-
case 'any':
|
|
179
|
-
return { tool_choice: 'required', ...parallelToolCalls };
|
|
180
|
-
case 'tool':
|
|
181
|
-
if (typeof value.name !== 'string' || value.name.trim().length === 0) {
|
|
182
|
-
throw new Error('tool_choice.name must be a non-empty string when type is "tool"');
|
|
183
|
-
}
|
|
184
|
-
return {
|
|
185
|
-
tool_choice: {
|
|
186
|
-
type: 'function',
|
|
187
|
-
function: { name: value.name.trim() },
|
|
188
|
-
},
|
|
189
|
-
...parallelToolCalls,
|
|
190
|
-
};
|
|
191
|
-
default:
|
|
192
|
-
throw new Error('tool_choice.type must be "auto", "any", "tool", or "none"');
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function mapThinkingToReasoning(thinking, outputConfig) {
|
|
196
|
-
if (!thinking || thinking.type === 'disabled') {
|
|
197
|
-
return {};
|
|
198
|
-
}
|
|
199
|
-
if (thinking.type === 'adaptive') {
|
|
200
|
-
const effort = toOpenAIEffort(resolveOutputConfigEffort(outputConfig) ?? 'high');
|
|
201
|
-
return {
|
|
202
|
-
reasoning_effort: effort,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
if (thinking.type !== 'enabled') {
|
|
206
|
-
throw new Error('thinking.type must be "enabled", "adaptive", or "disabled"');
|
|
207
|
-
}
|
|
208
|
-
const effort = typeof thinking.budget_tokens === 'number' && thinking.budget_tokens >= 8192
|
|
209
|
-
? 'high'
|
|
210
|
-
: 'medium';
|
|
211
|
-
return {
|
|
212
|
-
reasoning_effort: effort,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
const VALID_EFFORT_LEVELS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
216
|
-
function resolveOutputConfigEffort(outputConfig) {
|
|
217
|
-
if (!outputConfig || typeof outputConfig.effort !== 'string') {
|
|
218
|
-
return undefined;
|
|
219
|
-
}
|
|
220
|
-
const normalized = outputConfig.effort.trim().toLowerCase();
|
|
221
|
-
return VALID_EFFORT_LEVELS.has(normalized) ? normalized : undefined;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Map Anthropic effort levels to OpenAI-compatible reasoning_effort.
|
|
225
|
-
* Anthropic's `max` has no standard OpenAI equivalent — most providers
|
|
226
|
-
* only accept low/medium/high and reject unknown values with a 400.
|
|
227
|
-
* Ported from openclaude's standardEffortToOpenAI() which maps max -> xhigh
|
|
228
|
-
* for Codex; for generic OpenAI-compat providers we clamp to high.
|
|
229
|
-
*/
|
|
230
|
-
function toOpenAIEffort(effort) {
|
|
231
|
-
return effort === 'max' || effort === 'xhigh' ? 'high' : effort;
|
|
232
|
-
}
|
|
233
|
-
function transformMessages(messagesValue) {
|
|
234
|
-
if (!Array.isArray(messagesValue)) {
|
|
235
|
-
throw new Error('messages must be an array');
|
|
236
|
-
}
|
|
237
|
-
const translatedMessages = [];
|
|
238
|
-
let pendingToolUseIds = null;
|
|
239
|
-
let hasPendingToolUseIds = false;
|
|
240
|
-
messagesValue.forEach((message, messageIndex) => {
|
|
241
|
-
const parsedMessage = assertObject(message, `messages[${messageIndex}]`);
|
|
242
|
-
const role = parsedMessage.role;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProxyRequestTransformer = void 0;
|
|
4
|
+
const TOOL_USE_ARGUMENTS_FALLBACK = '{}';
|
|
5
|
+
function assertObject(value, label) {
|
|
6
|
+
if (typeof value !== 'object' || value === null) {
|
|
7
|
+
throw new Error(`${label} must be an object`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function asNumber(value) {
|
|
12
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
function asStringArray(value) {
|
|
15
|
+
if (!Array.isArray(value)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const result = value.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
19
|
+
return result.length > 0 ? result : undefined;
|
|
20
|
+
}
|
|
21
|
+
function asMetadata(value) {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
23
|
+
? value
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
function safeJsonStringify(value, fallback) {
|
|
27
|
+
try {
|
|
28
|
+
const serialized = JSON.stringify(value);
|
|
29
|
+
return typeof serialized === 'string' ? serialized : fallback;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return fallback;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function flattenTextContent(content, label) {
|
|
36
|
+
if (typeof content === 'string') {
|
|
37
|
+
return content;
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(content)) {
|
|
40
|
+
throw new Error(`${label} must be a string or content block array`);
|
|
41
|
+
}
|
|
42
|
+
return content
|
|
43
|
+
.map((block, index) => {
|
|
44
|
+
const parsed = assertObject(block, `${label}[${index}]`);
|
|
45
|
+
if (parsed.type !== 'text') {
|
|
46
|
+
throw new Error(`${label}[${index}].type "${String(parsed.type)}" is not supported`);
|
|
47
|
+
}
|
|
48
|
+
return typeof parsed.text === 'string' ? parsed.text : '';
|
|
49
|
+
})
|
|
50
|
+
.join('\n');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert tool_result content to OpenAI-compatible format.
|
|
54
|
+
* Handles strings, arrays with text/image blocks, and error prefixing.
|
|
55
|
+
* Ported from openclaude's convertToolResultContent.
|
|
56
|
+
*/
|
|
57
|
+
function convertToolResultContent(content, isError, label) {
|
|
58
|
+
if (content === undefined) {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
if (typeof content === 'string') {
|
|
62
|
+
return isError ? `Error: ${content}` : content;
|
|
63
|
+
}
|
|
64
|
+
if (!Array.isArray(content)) {
|
|
65
|
+
const text = safeJsonStringify(content, '[unserializable content]');
|
|
66
|
+
return isError ? `Error: ${text}` : text;
|
|
67
|
+
}
|
|
68
|
+
const parts = [];
|
|
69
|
+
for (const [index, block] of content.entries()) {
|
|
70
|
+
const parsed = assertObject(block, `${label}[${index}]`);
|
|
71
|
+
if (parsed.type === 'text' && typeof parsed.text === 'string') {
|
|
72
|
+
parts.push(parsed.text);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (parsed.type === 'image') {
|
|
76
|
+
const source = typeof parsed.source === 'object' && parsed.source !== null
|
|
77
|
+
? parsed.source
|
|
78
|
+
: undefined;
|
|
79
|
+
const description = source?.type === 'url'
|
|
80
|
+
? 'url image payload'
|
|
81
|
+
: source?.type === 'base64' && typeof source.media_type === 'string'
|
|
82
|
+
? `${source.media_type} base64 payload`
|
|
83
|
+
: 'unsupported image payload';
|
|
84
|
+
parts.push(`[tool_result image omitted: ${description}]`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (typeof parsed.text === 'string') {
|
|
88
|
+
parts.push(parsed.text);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`${label}[${index}].type "${String(parsed.type)}" is not supported`);
|
|
92
|
+
}
|
|
93
|
+
const text = parts.join('\n');
|
|
94
|
+
if (!text) {
|
|
95
|
+
return isError ? 'Error:' : '';
|
|
96
|
+
}
|
|
97
|
+
return isError ? `Error: ${text}` : text;
|
|
98
|
+
}
|
|
99
|
+
function createFallbackToolId(messageIndex, blockIndex) {
|
|
100
|
+
return `toolu_proxy_fallback_${messageIndex}_${blockIndex}`;
|
|
101
|
+
}
|
|
102
|
+
function toImagePart(block, label) {
|
|
103
|
+
const source = block.source;
|
|
104
|
+
if (!source) {
|
|
105
|
+
throw new Error(`${label}.source is missing`);
|
|
106
|
+
}
|
|
107
|
+
if (source.type === 'url' && source.url) {
|
|
108
|
+
return {
|
|
109
|
+
type: 'image_url',
|
|
110
|
+
image_url: { url: source.url },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (source.type === 'base64' && source.media_type && source.data) {
|
|
114
|
+
return {
|
|
115
|
+
type: 'image_url',
|
|
116
|
+
image_url: {
|
|
117
|
+
url: `data:${source.media_type};base64,${source.data}`,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`${label}.source must be a base64 or url image payload`);
|
|
122
|
+
}
|
|
123
|
+
function isImageBlock(block) {
|
|
124
|
+
return block.type === 'image';
|
|
125
|
+
}
|
|
126
|
+
function isToolUseBlock(block) {
|
|
127
|
+
return block.type === 'tool_use';
|
|
128
|
+
}
|
|
129
|
+
function isToolResultBlock(block) {
|
|
130
|
+
return block.type === 'tool_result';
|
|
131
|
+
}
|
|
132
|
+
function flushUserContent(messages, parts) {
|
|
133
|
+
if (parts.length === 0) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const onlyText = parts.every((part) => part.type === 'text');
|
|
137
|
+
messages.push({
|
|
138
|
+
role: 'user',
|
|
139
|
+
content: onlyText ? parts.map((part) => part.text).join('\n') : [...parts],
|
|
140
|
+
});
|
|
141
|
+
parts.length = 0;
|
|
142
|
+
}
|
|
143
|
+
function transformTools(value) {
|
|
144
|
+
if (!Array.isArray(value)) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const tools = value
|
|
148
|
+
.filter((entry) => typeof entry === 'object' && entry !== null)
|
|
149
|
+
.map((entry) => {
|
|
150
|
+
const rawSchema = typeof entry.input_schema === 'object' && entry.input_schema !== null
|
|
151
|
+
? entry.input_schema
|
|
152
|
+
: { type: 'object', properties: {} };
|
|
153
|
+
return {
|
|
154
|
+
type: 'function',
|
|
155
|
+
function: {
|
|
156
|
+
name: typeof entry.name === 'string' ? entry.name : 'tool',
|
|
157
|
+
...(typeof entry.description === 'string' ? { description: entry.description } : {}),
|
|
158
|
+
parameters: rawSchema,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
return tools.length > 0 ? tools : undefined;
|
|
163
|
+
}
|
|
164
|
+
function transformToolChoice(value, hasTools) {
|
|
165
|
+
if (!value) {
|
|
166
|
+
return hasTools ? { tool_choice: 'auto' } : {};
|
|
167
|
+
}
|
|
168
|
+
if (!hasTools) {
|
|
169
|
+
throw new Error('tool_choice requires tools');
|
|
170
|
+
}
|
|
171
|
+
const parallelToolCalls = value.disable_parallel_tool_use === true ? { parallel_tool_calls: false } : {};
|
|
172
|
+
switch (value.type) {
|
|
173
|
+
case undefined:
|
|
174
|
+
case 'auto':
|
|
175
|
+
return { tool_choice: 'auto', ...parallelToolCalls };
|
|
176
|
+
case 'none':
|
|
177
|
+
return { tool_choice: 'none' };
|
|
178
|
+
case 'any':
|
|
179
|
+
return { tool_choice: 'required', ...parallelToolCalls };
|
|
180
|
+
case 'tool':
|
|
181
|
+
if (typeof value.name !== 'string' || value.name.trim().length === 0) {
|
|
182
|
+
throw new Error('tool_choice.name must be a non-empty string when type is "tool"');
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
tool_choice: {
|
|
186
|
+
type: 'function',
|
|
187
|
+
function: { name: value.name.trim() },
|
|
188
|
+
},
|
|
189
|
+
...parallelToolCalls,
|
|
190
|
+
};
|
|
191
|
+
default:
|
|
192
|
+
throw new Error('tool_choice.type must be "auto", "any", "tool", or "none"');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function mapThinkingToReasoning(thinking, outputConfig) {
|
|
196
|
+
if (!thinking || thinking.type === 'disabled') {
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
if (thinking.type === 'adaptive') {
|
|
200
|
+
const effort = toOpenAIEffort(resolveOutputConfigEffort(outputConfig) ?? 'high');
|
|
201
|
+
return {
|
|
202
|
+
reasoning_effort: effort,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (thinking.type !== 'enabled') {
|
|
206
|
+
throw new Error('thinking.type must be "enabled", "adaptive", or "disabled"');
|
|
207
|
+
}
|
|
208
|
+
const effort = typeof thinking.budget_tokens === 'number' && thinking.budget_tokens >= 8192
|
|
209
|
+
? 'high'
|
|
210
|
+
: 'medium';
|
|
211
|
+
return {
|
|
212
|
+
reasoning_effort: effort,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const VALID_EFFORT_LEVELS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
216
|
+
function resolveOutputConfigEffort(outputConfig) {
|
|
217
|
+
if (!outputConfig || typeof outputConfig.effort !== 'string') {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
const normalized = outputConfig.effort.trim().toLowerCase();
|
|
221
|
+
return VALID_EFFORT_LEVELS.has(normalized) ? normalized : undefined;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Map Anthropic effort levels to OpenAI-compatible reasoning_effort.
|
|
225
|
+
* Anthropic's `max` has no standard OpenAI equivalent — most providers
|
|
226
|
+
* only accept low/medium/high and reject unknown values with a 400.
|
|
227
|
+
* Ported from openclaude's standardEffortToOpenAI() which maps max -> xhigh
|
|
228
|
+
* for Codex; for generic OpenAI-compat providers we clamp to high.
|
|
229
|
+
*/
|
|
230
|
+
function toOpenAIEffort(effort) {
|
|
231
|
+
return effort === 'max' || effort === 'xhigh' ? 'high' : effort;
|
|
232
|
+
}
|
|
233
|
+
function transformMessages(messagesValue) {
|
|
234
|
+
if (!Array.isArray(messagesValue)) {
|
|
235
|
+
throw new Error('messages must be an array');
|
|
236
|
+
}
|
|
237
|
+
const translatedMessages = [];
|
|
238
|
+
let pendingToolUseIds = null;
|
|
239
|
+
let hasPendingToolUseIds = false;
|
|
240
|
+
messagesValue.forEach((message, messageIndex) => {
|
|
241
|
+
const parsedMessage = assertObject(message, `messages[${messageIndex}]`);
|
|
242
|
+
const role = parsedMessage.role;
|
|
243
|
+
const content = parsedMessage.content;
|
|
244
|
+
if (role === 'system') {
|
|
245
|
+
// System messages: pass through as-is for OpenAI compatibility.
|
|
246
|
+
if (typeof content === 'string') {
|
|
247
|
+
translatedMessages.push({ role: 'system', content });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (Array.isArray(content)) {
|
|
251
|
+
const textParts = [];
|
|
252
|
+
for (const block of content) {
|
|
253
|
+
if (block.type === 'text') {
|
|
254
|
+
textParts.push(typeof block.text === 'string' ? block.text : '');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (textParts.length > 0) {
|
|
258
|
+
translatedMessages.push({ role: 'system', content: textParts.join('') });
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (role !== 'user' && role !== 'assistant') {
|
|
265
|
+
throw new Error(`messages[${messageIndex}].role must be "user" or "assistant"`);
|
|
266
|
+
}
|
|
267
|
+
if (pendingToolUseIds && pendingToolUseIds.size > 0 && role !== 'user') {
|
|
268
|
+
throw new Error(`messages[${messageIndex}].role must be "user" with tool_result blocks after assistant tool_use`);
|
|
269
|
+
}
|
|
270
|
+
if (typeof content === 'string') {
|
|
271
|
+
if (pendingToolUseIds && pendingToolUseIds.size > 0) {
|
|
272
|
+
throw new Error(`messages[${messageIndex}].content must start with tool_result blocks for pending tool_use ids`);
|
|
273
|
+
}
|
|
274
|
+
translatedMessages.push({ role, content });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(content)) {
|
|
278
|
+
throw new Error(`messages[${messageIndex}].content must be a string or array`);
|
|
279
|
+
}
|
|
280
|
+
if (role === 'user') {
|
|
281
|
+
const userParts = [];
|
|
282
|
+
const followUpParts = [];
|
|
283
|
+
const resolvedToolUseIds = new Set();
|
|
284
|
+
const handleUserPart = (part, blockIndex, kind) => {
|
|
285
|
+
if (!pendingToolUseIds || pendingToolUseIds.size === 0) {
|
|
286
|
+
userParts.push(part);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (resolvedToolUseIds.size === 0) {
|
|
290
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}] ${kind} is not allowed before tool_result blocks for pending tool_use ids`);
|
|
291
|
+
}
|
|
292
|
+
if (resolvedToolUseIds.size !== pendingToolUseIds.size) {
|
|
293
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}] ${kind} is not allowed between tool_result blocks for pending tool_use ids`);
|
|
294
|
+
}
|
|
295
|
+
followUpParts.push(part);
|
|
296
|
+
};
|
|
297
|
+
content.forEach((block, blockIndex) => {
|
|
298
|
+
const parsed = assertObject(block, `messages[${messageIndex}].content[${blockIndex}]`);
|
|
299
|
+
if (parsed.type === 'thinking' || parsed.type === 'redacted_thinking') {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (parsed.type === 'text') {
|
|
303
|
+
const text = typeof parsed.text === 'string' ? parsed.text : '';
|
|
304
|
+
handleUserPart({ type: 'text', text }, blockIndex, 'text');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (isImageBlock(parsed)) {
|
|
308
|
+
handleUserPart(toImagePart(parsed, `messages[${messageIndex}].content[${blockIndex}]`), blockIndex, 'image');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (isToolResultBlock(parsed)) {
|
|
312
|
+
if (!pendingToolUseIds || pendingToolUseIds.size === 0) {
|
|
313
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}] tool_result requires a preceding assistant tool_use`);
|
|
314
|
+
}
|
|
315
|
+
if (typeof parsed.tool_use_id !== 'string' || parsed.tool_use_id.trim().length === 0) {
|
|
316
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}].tool_use_id must be a non-empty string`);
|
|
317
|
+
}
|
|
318
|
+
if (!pendingToolUseIds.has(parsed.tool_use_id)) {
|
|
319
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}].tool_use_id "${parsed.tool_use_id}" does not match a pending tool_use`);
|
|
320
|
+
}
|
|
321
|
+
if (resolvedToolUseIds.has(parsed.tool_use_id)) {
|
|
322
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}].tool_use_id "${parsed.tool_use_id}" is duplicated`);
|
|
323
|
+
}
|
|
324
|
+
resolvedToolUseIds.add(parsed.tool_use_id);
|
|
325
|
+
translatedMessages.push({
|
|
326
|
+
role: 'tool',
|
|
327
|
+
tool_call_id: parsed.tool_use_id,
|
|
328
|
+
content: convertToolResultContent(parsed.content, parsed.is_error === true, `messages[${messageIndex}].content[${blockIndex}].content`),
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (isToolUseBlock(parsed)) {
|
|
333
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}] tool_use requires assistant role`);
|
|
334
|
+
}
|
|
335
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}].type "${String(parsed.type)}" is not supported`);
|
|
336
|
+
});
|
|
337
|
+
if (resolvedToolUseIds.size > 0) {
|
|
338
|
+
if (resolvedToolUseIds.size !== pendingToolUseIds?.size) {
|
|
339
|
+
throw new Error(`messages[${messageIndex}].content must provide tool_result blocks for all pending tool_use ids`);
|
|
340
|
+
}
|
|
341
|
+
pendingToolUseIds = null;
|
|
342
|
+
hasPendingToolUseIds = false;
|
|
343
|
+
}
|
|
344
|
+
if (pendingToolUseIds && pendingToolUseIds.size > 0) {
|
|
345
|
+
throw new Error(`messages[${messageIndex}].content must include tool_result blocks for pending tool_use ids`);
|
|
346
|
+
}
|
|
347
|
+
if (userParts.length > 0) {
|
|
348
|
+
flushUserContent(translatedMessages, userParts);
|
|
349
|
+
}
|
|
350
|
+
if (followUpParts.length > 0) {
|
|
351
|
+
flushUserContent(translatedMessages, followUpParts);
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// Assistant role
|
|
356
|
+
const assistantTextParts = [];
|
|
357
|
+
const toolCalls = [];
|
|
358
|
+
content.forEach((block, blockIndex) => {
|
|
359
|
+
const parsed = assertObject(block, `messages[${messageIndex}].content[${blockIndex}]`);
|
|
360
|
+
if (parsed.type === 'thinking' || parsed.type === 'redacted_thinking') {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (parsed.type === 'text') {
|
|
364
|
+
const text = typeof parsed.text === 'string' ? parsed.text : '';
|
|
365
|
+
assistantTextParts.push(text);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (isToolUseBlock(parsed)) {
|
|
369
|
+
toolCalls.push({
|
|
370
|
+
id: typeof parsed.id === 'string' && parsed.id.length > 0
|
|
371
|
+
? parsed.id
|
|
372
|
+
: createFallbackToolId(messageIndex, blockIndex),
|
|
373
|
+
type: 'function',
|
|
374
|
+
function: {
|
|
375
|
+
name: typeof parsed.name === 'string' ? parsed.name : 'tool',
|
|
376
|
+
arguments: safeJsonStringify(parsed.input ?? {}, TOOL_USE_ARGUMENTS_FALLBACK),
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (isImageBlock(parsed)) {
|
|
382
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}] image requires user role`);
|
|
383
|
+
}
|
|
384
|
+
if (isToolResultBlock(parsed)) {
|
|
385
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}] tool_result requires user role`);
|
|
386
|
+
}
|
|
387
|
+
throw new Error(`messages[${messageIndex}].content[${blockIndex}].type "${String(parsed.type)}" is not supported`);
|
|
388
|
+
});
|
|
389
|
+
if (assistantTextParts.length === 0 && toolCalls.length === 0) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
pendingToolUseIds =
|
|
393
|
+
toolCalls.length > 0 ? new Set(toolCalls.map((toolCall) => toolCall.id)) : null;
|
|
394
|
+
hasPendingToolUseIds = toolCalls.length > 0;
|
|
395
|
+
translatedMessages.push({
|
|
396
|
+
role: 'assistant',
|
|
397
|
+
content: assistantTextParts.join('\n'),
|
|
398
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
if (hasPendingToolUseIds) {
|
|
402
|
+
throw new Error('messages must provide tool_result blocks for the latest assistant tool_use');
|
|
403
|
+
}
|
|
404
|
+
return translatedMessages;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Coalesce consecutive messages of the same role.
|
|
408
|
+
* OpenAI/vLLM/Ollama/Mistral require strict user<->assistant alternation.
|
|
409
|
+
* Multiple consecutive tool messages are allowed (assistant -> tool* -> user).
|
|
410
|
+
* Ported from openclaude's coalescing pass.
|
|
411
|
+
*/
|
|
412
|
+
function coalesceMessages(messages) {
|
|
413
|
+
const coalesced = [];
|
|
414
|
+
for (const msg of messages) {
|
|
415
|
+
const prev = coalesced[coalesced.length - 1];
|
|
416
|
+
if (prev && prev.role === msg.role && msg.role !== 'tool' && msg.role !== 'system') {
|
|
417
|
+
const prevContent = prev.content;
|
|
418
|
+
const curContent = msg.content;
|
|
419
|
+
if (typeof prevContent === 'string' && typeof curContent === 'string') {
|
|
420
|
+
prev.content = prevContent + (prevContent && curContent ? '\n' : '') + curContent;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
const toArray = (c) => {
|
|
424
|
+
if (!c)
|
|
425
|
+
return [];
|
|
426
|
+
if (typeof c === 'string')
|
|
427
|
+
return c ? [{ type: 'text', text: c }] : [];
|
|
428
|
+
return c;
|
|
429
|
+
};
|
|
430
|
+
prev.content = [...toArray(prevContent), ...toArray(curContent)];
|
|
431
|
+
}
|
|
432
|
+
if (msg.tool_calls?.length) {
|
|
433
|
+
prev.tool_calls = [...(prev.tool_calls ?? []), ...msg.tool_calls];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
coalesced.push({ ...msg });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return coalesced;
|
|
441
|
+
}
|
|
442
|
+
class ProxyRequestTransformer {
|
|
443
|
+
transform(raw) {
|
|
444
|
+
const source = assertObject(raw || {}, 'request');
|
|
445
|
+
const tools = transformTools(source.tools);
|
|
446
|
+
const messages = transformMessages(source.messages);
|
|
447
|
+
const system = source.system;
|
|
448
|
+
const allMessages = system !== undefined
|
|
449
|
+
? [
|
|
450
|
+
{ role: 'system', content: flattenTextContent(system, 'system') },
|
|
451
|
+
...messages,
|
|
452
|
+
]
|
|
453
|
+
: messages;
|
|
454
|
+
return {
|
|
455
|
+
model: typeof source.model === 'string' && source.model.trim().length > 0
|
|
456
|
+
? source.model.trim()
|
|
457
|
+
: undefined,
|
|
458
|
+
stream: source.stream === true,
|
|
459
|
+
messages: coalesceMessages(allMessages),
|
|
460
|
+
max_tokens: asNumber(source.max_tokens),
|
|
461
|
+
temperature: asNumber(source.temperature),
|
|
462
|
+
top_p: asNumber(source.top_p),
|
|
463
|
+
stop: asStringArray(source.stop_sequences),
|
|
464
|
+
metadata: asMetadata(source.metadata),
|
|
465
|
+
tools,
|
|
466
|
+
...transformToolChoice(source.tool_choice, tools !== undefined),
|
|
467
|
+
...mapThinkingToReasoning(source.thinking, source.output_config),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
exports.ProxyRequestTransformer = ProxyRequestTransformer;
|
|
452
472
|
//# sourceMappingURL=request-transformer.js.map
|