doer-agent 0.8.6 → 0.8.8

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.
@@ -0,0 +1,1489 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ const DEFAULT_CHAT_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
4
+ const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
5
+ const CHAT_BRIDGE_ENV_KEY = "DOER_CODEX_CHAT_BRIDGE_API_KEY";
6
+ const ANTHROPIC_VERSION = "2023-06-01";
7
+ const PROVIDER_ADAPTERS = {
8
+ anthropic: {
9
+ kind: "anthropic_messages",
10
+ defaultBaseUrl: DEFAULT_ANTHROPIC_BASE_URL,
11
+ upstreamAuthHeaders: (apiKey) => ({
12
+ "x-api-key": apiKey,
13
+ "anthropic-version": ANTHROPIC_VERSION,
14
+ }),
15
+ },
16
+ default: {
17
+ kind: "chat_completions",
18
+ defaultBaseUrl: DEFAULT_CHAT_BASE_URL,
19
+ upstreamAuthHeaders: (apiKey) => ({
20
+ authorization: `Bearer ${apiKey}`,
21
+ }),
22
+ },
23
+ };
24
+ function normalizeBaseUrl(value) {
25
+ return value.replace(/\/+$/, "");
26
+ }
27
+ function resolveProviderAdapter(providerId) {
28
+ return PROVIDER_ADAPTERS[providerId?.trim().toLowerCase() || ""] ?? PROVIDER_ADAPTERS.default;
29
+ }
30
+ function resolveTargetBaseUrl(args) {
31
+ const providerId = args.providerId?.trim().toLowerCase();
32
+ const baseUrl = args.targetBaseUrl?.trim();
33
+ if (providerId === "zai" && (!baseUrl || !baseUrl.includes("/coding/paas/"))) {
34
+ return DEFAULT_CHAT_BASE_URL;
35
+ }
36
+ const adapter = resolveProviderAdapter(providerId);
37
+ return normalizeBaseUrl(baseUrl || adapter.defaultBaseUrl);
38
+ }
39
+ function stringValue(value) {
40
+ return typeof value === "string" && value.trim() ? value.trim() : null;
41
+ }
42
+ function textValue(value) {
43
+ return typeof value === "string" && value.length > 0 ? value : null;
44
+ }
45
+ function finiteNumber(value) {
46
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
47
+ }
48
+ function recordValue(value) {
49
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
50
+ }
51
+ function readRequestJson(req) {
52
+ return new Promise((resolve, reject) => {
53
+ const chunks = [];
54
+ req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
55
+ req.on("end", () => {
56
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
57
+ if (!raw) {
58
+ resolve({});
59
+ return;
60
+ }
61
+ try {
62
+ const parsed = JSON.parse(raw);
63
+ resolve(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
64
+ }
65
+ catch (error) {
66
+ reject(error);
67
+ }
68
+ });
69
+ req.on("error", reject);
70
+ });
71
+ }
72
+ function contentToText(value) {
73
+ if (typeof value === "string") {
74
+ return value;
75
+ }
76
+ if (!Array.isArray(value)) {
77
+ return "";
78
+ }
79
+ return value
80
+ .map((part) => {
81
+ if (typeof part === "string") {
82
+ return part;
83
+ }
84
+ if (!part || typeof part !== "object" || Array.isArray(part)) {
85
+ return "";
86
+ }
87
+ const record = part;
88
+ return textValue(record.text) ?? textValue(record.output_text) ?? "";
89
+ })
90
+ .filter(Boolean)
91
+ .join("\n");
92
+ }
93
+ function parseJsonObject(value) {
94
+ try {
95
+ const parsed = JSON.parse(value);
96
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
97
+ }
98
+ catch {
99
+ return {};
100
+ }
101
+ }
102
+ function responsePartToChatPart(part) {
103
+ if (typeof part === "string") {
104
+ return part ? { type: "text", text: part } : null;
105
+ }
106
+ const record = recordValue(part);
107
+ if (!record) {
108
+ return null;
109
+ }
110
+ const type = stringValue(record.type);
111
+ if (type === "input_text" || type === "output_text" || type === "text") {
112
+ const text = textValue(record.text) ?? textValue(record.output_text);
113
+ return text ? { type: "text", text } : null;
114
+ }
115
+ if (type === "input_image" || type === "image_url") {
116
+ const rawImageUrl = recordValue(record.image_url);
117
+ const imageUrl = textValue(record.image_url) ?? textValue(rawImageUrl?.url) ?? textValue(record.url);
118
+ if (!imageUrl) {
119
+ return null;
120
+ }
121
+ const detail = stringValue(record.detail) ?? stringValue(rawImageUrl?.detail);
122
+ return {
123
+ type: "image_url",
124
+ image_url: {
125
+ url: imageUrl,
126
+ ...(detail ? { detail } : {}),
127
+ },
128
+ };
129
+ }
130
+ if (type === "input_file" || type === "file") {
131
+ const filename = stringValue(record.filename) ?? stringValue(record.name) ?? "attached file";
132
+ const fileId = stringValue(record.file_id);
133
+ const fileData = textValue(record.file_data);
134
+ const suffix = fileId ? ` file_id=${fileId}` : "";
135
+ return {
136
+ type: "text",
137
+ text: fileData ? `[attached file: ${filename}]\n${fileData}` : `[attached file: ${filename}${suffix}]`,
138
+ };
139
+ }
140
+ return null;
141
+ }
142
+ function contentToChatContent(value) {
143
+ if (typeof value === "string") {
144
+ return value;
145
+ }
146
+ if (!Array.isArray(value)) {
147
+ return "";
148
+ }
149
+ const parts = value.map(responsePartToChatPart).filter((part) => Boolean(part));
150
+ if (parts.length === 0) {
151
+ return "";
152
+ }
153
+ if (parts.every((part) => part.type === "text")) {
154
+ return parts.map((part) => textValue(part.text) ?? "").filter(Boolean).join("\n");
155
+ }
156
+ return parts;
157
+ }
158
+ function inputItemToMessages(item) {
159
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
160
+ return [];
161
+ }
162
+ const record = item;
163
+ const type = stringValue(record.type);
164
+ const role = stringValue(record.role);
165
+ if (role === "user" || role === "assistant" || role === "system" || role === "developer") {
166
+ return [{
167
+ role: role === "developer" ? "system" : role,
168
+ content: contentToChatContent(record.content),
169
+ }];
170
+ }
171
+ if (type === "message") {
172
+ const messageRole = stringValue(record.role) ?? "user";
173
+ return [{
174
+ role: messageRole === "developer" ? "system" : messageRole,
175
+ content: contentToChatContent(record.content),
176
+ }];
177
+ }
178
+ if (type === "function_call") {
179
+ const name = stringValue(record.name);
180
+ const callId = stringValue(record.call_id) ?? stringValue(record.id) ?? `call_${randomUUID().replace(/-/g, "")}`;
181
+ if (!name) {
182
+ return [];
183
+ }
184
+ const namespace = stringValue(record.namespace);
185
+ return [{
186
+ role: "assistant",
187
+ content: null,
188
+ tool_calls: [{
189
+ id: callId,
190
+ type: "function",
191
+ function: {
192
+ name: chatNameFromResponsesFunctionCall(namespace, name),
193
+ arguments: typeof record.arguments === "string" ? record.arguments : "",
194
+ },
195
+ }],
196
+ }];
197
+ }
198
+ if (type === "function_call_output") {
199
+ const callId = stringValue(record.call_id);
200
+ if (!callId) {
201
+ return [];
202
+ }
203
+ return [{
204
+ role: "tool",
205
+ tool_call_id: callId,
206
+ content: typeof record.output === "string" ? record.output : JSON.stringify(record.output ?? ""),
207
+ }];
208
+ }
209
+ return [];
210
+ }
211
+ function inputToMessages(input) {
212
+ if (typeof input === "string") {
213
+ return [{ role: "user", content: input }];
214
+ }
215
+ if (!Array.isArray(input)) {
216
+ return [];
217
+ }
218
+ return input.flatMap(inputItemToMessages);
219
+ }
220
+ function schemaValue(value) {
221
+ return value && typeof value === "object" && !Array.isArray(value)
222
+ ? value
223
+ : { type: "object", properties: {} };
224
+ }
225
+ function chatFunctionTool(args) {
226
+ if (!/^[a-zA-Z0-9_-]+$/.test(args.name)) {
227
+ return null;
228
+ }
229
+ return {
230
+ type: "function",
231
+ function: {
232
+ name: args.name,
233
+ description: args.description,
234
+ parameters: schemaValue(args.parameters),
235
+ },
236
+ };
237
+ }
238
+ function namespaceToolChatName(namespace, name) {
239
+ if (!namespace) {
240
+ return name;
241
+ }
242
+ const prefix = namespace.startsWith("mcp__") ? namespace : `mcp__${namespace}`;
243
+ return `${prefix.endsWith("__") ? prefix : `${prefix}__`}${name}`;
244
+ }
245
+ function chatNameFromResponsesFunctionCall(namespace, name) {
246
+ if (!namespace) {
247
+ return name;
248
+ }
249
+ return `${namespace.endsWith("__") ? namespace : `${namespace}__`}${name}`;
250
+ }
251
+ function responsesFunctionCallNameFromChatName(name) {
252
+ if (!name.startsWith("mcp__")) {
253
+ return { name };
254
+ }
255
+ const separatorIndex = name.lastIndexOf("__");
256
+ if (separatorIndex <= "mcp__".length || separatorIndex >= name.length - 2) {
257
+ return { name };
258
+ }
259
+ return {
260
+ namespace: name.slice(0, separatorIndex),
261
+ name: name.slice(separatorIndex + 2),
262
+ };
263
+ }
264
+ function responsesToolToChatTools(tool, onLog) {
265
+ if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
266
+ return [];
267
+ }
268
+ const record = tool;
269
+ const type = stringValue(record.type);
270
+ if (type === "namespace") {
271
+ const namespace = stringValue(record.name);
272
+ const namespaceDescription = stringValue(record.description);
273
+ const namespaceTools = Array.isArray(record.tools)
274
+ ? record.tools
275
+ : Array.isArray(record.functions)
276
+ ? record.functions
277
+ : [];
278
+ const tools = namespaceTools
279
+ .map((namespaceTool) => {
280
+ const toolRecord = recordValue(namespaceTool);
281
+ const name = stringValue(toolRecord?.name);
282
+ if (!name) {
283
+ return null;
284
+ }
285
+ const description = [
286
+ namespace ? `Namespace: ${namespaceToolChatName(namespace, "").replace(/__$/, "__")}. Original tool: ${name}.` : "",
287
+ stringValue(toolRecord?.description) ?? namespaceDescription ?? "",
288
+ ].filter(Boolean).join(" ");
289
+ return chatFunctionTool({
290
+ name: namespaceToolChatName(namespace, name),
291
+ description,
292
+ parameters: toolRecord?.parameters ?? toolRecord?.input_schema ?? toolRecord?.schema,
293
+ });
294
+ })
295
+ .filter((item) => Boolean(item));
296
+ if (tools.length === 0) {
297
+ onLog?.(`Codex chat bridge dropped unsupported Responses tool type=namespace`);
298
+ }
299
+ return tools;
300
+ }
301
+ if (type !== "function") {
302
+ if (type) {
303
+ onLog?.(`Codex chat bridge dropped unsupported Responses tool type=${type}`);
304
+ }
305
+ return [];
306
+ }
307
+ const name = stringValue(record.name);
308
+ if (!name) {
309
+ return [];
310
+ }
311
+ const chatTool = chatFunctionTool({
312
+ name,
313
+ description: stringValue(record.description) ?? "",
314
+ parameters: record.parameters,
315
+ });
316
+ return chatTool ? [chatTool] : [];
317
+ }
318
+ function chatToolChoiceFromResponses(value) {
319
+ const text = stringValue(value);
320
+ if (text === "auto" || text === "none" || text === "required") {
321
+ return text;
322
+ }
323
+ const record = recordValue(value);
324
+ if (!record) {
325
+ return undefined;
326
+ }
327
+ const type = stringValue(record.type);
328
+ const name = stringValue(record.name);
329
+ if (type === "function" && name) {
330
+ return { type: "function", function: { name } };
331
+ }
332
+ return undefined;
333
+ }
334
+ function chatResponseFormatFromResponsesText(value) {
335
+ const text = recordValue(value);
336
+ const format = recordValue(text?.format);
337
+ const type = stringValue(format?.type);
338
+ if (type === "json_object") {
339
+ return { type: "json_object" };
340
+ }
341
+ if (type !== "json_schema") {
342
+ return undefined;
343
+ }
344
+ const schema = recordValue(format?.schema) ?? recordValue(format?.json_schema);
345
+ if (!schema) {
346
+ return undefined;
347
+ }
348
+ const name = stringValue(format?.name) ?? "response";
349
+ return {
350
+ type: "json_schema",
351
+ json_schema: {
352
+ name,
353
+ schema,
354
+ ...(typeof format?.strict === "boolean" ? { strict: format.strict } : {}),
355
+ },
356
+ };
357
+ }
358
+ function responseMetadataFromBody(body) {
359
+ const tools = Array.isArray(body.tools) ? body.tools : [];
360
+ return {
361
+ instructions: stringValue(body.instructions),
362
+ maxOutputTokens: finiteNumber(body.max_output_tokens),
363
+ metadata: recordValue(body.metadata) ?? {},
364
+ parallelToolCalls: typeof body.parallel_tool_calls === "boolean" ? body.parallel_tool_calls : null,
365
+ previousResponseId: stringValue(body.previous_response_id),
366
+ temperature: finiteNumber(body.temperature),
367
+ text: recordValue(body.text) ?? { format: { type: "text" } },
368
+ toolChoice: body.tool_choice ?? "auto",
369
+ tools,
370
+ topP: finiteNumber(body.top_p),
371
+ truncation: stringValue(body.truncation),
372
+ user: stringValue(body.user),
373
+ };
374
+ }
375
+ function buildChatRequest(body, stream, onLog) {
376
+ const messages = inputToMessages(body.input);
377
+ const instructions = stringValue(body.instructions);
378
+ if (instructions) {
379
+ messages.unshift({ role: "system", content: instructions });
380
+ }
381
+ const tools = Array.isArray(body.tools) ? body.tools.flatMap((tool) => responsesToolToChatTools(tool, onLog)) : [];
382
+ const toolChoice = chatToolChoiceFromResponses(body.tool_choice);
383
+ const responseFormat = chatResponseFormatFromResponsesText(body.text);
384
+ return {
385
+ model: stringValue(body.model) ?? "glm-5.2",
386
+ messages,
387
+ stream,
388
+ ...(stream ? { stream_options: { include_usage: true } } : {}),
389
+ ...(typeof body.temperature === "number" ? { temperature: body.temperature } : {}),
390
+ ...(typeof body.top_p === "number" ? { top_p: body.top_p } : {}),
391
+ ...(typeof body.max_output_tokens === "number" ? { max_tokens: body.max_output_tokens } : {}),
392
+ ...(typeof body.presence_penalty === "number" ? { presence_penalty: body.presence_penalty } : {}),
393
+ ...(typeof body.frequency_penalty === "number" ? { frequency_penalty: body.frequency_penalty } : {}),
394
+ ...(typeof body.seed === "number" ? { seed: body.seed } : {}),
395
+ ...(typeof body.stop === "string" || Array.isArray(body.stop) ? { stop: body.stop } : {}),
396
+ ...(typeof body.parallel_tool_calls === "boolean" ? { parallel_tool_calls: body.parallel_tool_calls } : {}),
397
+ ...(typeof body.user === "string" ? { user: body.user } : {}),
398
+ ...(responseFormat ? { response_format: responseFormat } : {}),
399
+ ...(toolChoice ? { tool_choice: toolChoice } : {}),
400
+ ...(tools.length > 0 ? { tools } : {}),
401
+ };
402
+ }
403
+ function anthropicContentPartFromChatPart(part) {
404
+ const type = stringValue(part.type);
405
+ if (type === "text") {
406
+ const text = textValue(part.text);
407
+ return text ? { type: "text", text } : null;
408
+ }
409
+ if (type === "image_url") {
410
+ const rawImageUrl = recordValue(part.image_url);
411
+ const imageUrl = textValue(part.image_url) ?? textValue(rawImageUrl?.url);
412
+ const dataUrlMatch = imageUrl?.match(/^data:([^;,]+);base64,(.+)$/);
413
+ if (!dataUrlMatch) {
414
+ return null;
415
+ }
416
+ return {
417
+ type: "image",
418
+ source: {
419
+ type: "base64",
420
+ media_type: dataUrlMatch[1],
421
+ data: dataUrlMatch[2],
422
+ },
423
+ };
424
+ }
425
+ return null;
426
+ }
427
+ function anthropicContentFromChatContent(content) {
428
+ if (typeof content === "string") {
429
+ return content;
430
+ }
431
+ if (!Array.isArray(content)) {
432
+ return "";
433
+ }
434
+ const parts = content
435
+ .map((part) => recordValue(part))
436
+ .filter((part) => Boolean(part))
437
+ .map(anthropicContentPartFromChatPart)
438
+ .filter((part) => Boolean(part));
439
+ return parts.length > 0 ? parts : "";
440
+ }
441
+ function anthropicMessagesFromChatMessages(messages) {
442
+ const systemParts = [];
443
+ const anthropicMessages = [];
444
+ for (const message of messages) {
445
+ const role = stringValue(message.role);
446
+ if (role === "system" || role === "developer") {
447
+ const text = contentToText(message.content);
448
+ if (text) {
449
+ systemParts.push(text);
450
+ }
451
+ continue;
452
+ }
453
+ if (role === "tool") {
454
+ const toolUseId = stringValue(message.tool_call_id);
455
+ if (!toolUseId) {
456
+ continue;
457
+ }
458
+ anthropicMessages.push({
459
+ role: "user",
460
+ content: [{
461
+ type: "tool_result",
462
+ tool_use_id: toolUseId,
463
+ content: contentToText(message.content),
464
+ }],
465
+ });
466
+ continue;
467
+ }
468
+ if (role === "assistant") {
469
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
470
+ if (toolCalls.length > 0) {
471
+ const content = toolCalls
472
+ .map((toolCall) => {
473
+ const toolCallRecord = recordValue(toolCall);
474
+ const fn = recordValue(toolCallRecord?.function);
475
+ const name = stringValue(fn?.name);
476
+ if (!toolCallRecord || !fn || !name) {
477
+ return null;
478
+ }
479
+ return {
480
+ type: "tool_use",
481
+ id: stringValue(toolCallRecord.id) ?? `call_${randomUUID().replace(/-/g, "")}`,
482
+ name,
483
+ input: parseJsonObject(typeof fn.arguments === "string" ? fn.arguments : ""),
484
+ };
485
+ })
486
+ .filter((item) => item !== null);
487
+ if (content.length > 0) {
488
+ anthropicMessages.push({ role: "assistant", content });
489
+ }
490
+ continue;
491
+ }
492
+ anthropicMessages.push({ role: "assistant", content: anthropicContentFromChatContent(message.content) });
493
+ continue;
494
+ }
495
+ anthropicMessages.push({ role: "user", content: anthropicContentFromChatContent(message.content) });
496
+ }
497
+ return {
498
+ system: systemParts.length > 0 ? systemParts.join("\n\n") : null,
499
+ messages: anthropicMessages,
500
+ };
501
+ }
502
+ function anthropicToolsFromChatTools(tools) {
503
+ if (!Array.isArray(tools)) {
504
+ return [];
505
+ }
506
+ const converted = tools
507
+ .map((tool) => {
508
+ const record = recordValue(tool);
509
+ const fn = recordValue(record?.function);
510
+ const name = stringValue(fn?.name);
511
+ if (!fn || !name) {
512
+ return null;
513
+ }
514
+ return {
515
+ name,
516
+ description: stringValue(fn?.description) ?? "",
517
+ input_schema: schemaValue(fn?.parameters),
518
+ };
519
+ });
520
+ return converted.filter((tool) => tool !== null);
521
+ }
522
+ function anthropicToolChoiceFromChatToolChoice(value) {
523
+ const text = stringValue(value);
524
+ if (text === "auto") {
525
+ return { type: "auto" };
526
+ }
527
+ if (text === "required") {
528
+ return { type: "any" };
529
+ }
530
+ if (text === "none") {
531
+ return undefined;
532
+ }
533
+ const record = recordValue(value);
534
+ const fn = recordValue(record?.function);
535
+ const name = stringValue(fn?.name);
536
+ return name ? { type: "tool", name } : undefined;
537
+ }
538
+ function buildAnthropicRequest(body, stream, onLog) {
539
+ const chatRequest = buildChatRequest(body, stream, onLog);
540
+ const chatMessages = Array.isArray(chatRequest.messages) ? chatRequest.messages : [];
541
+ const converted = anthropicMessagesFromChatMessages(chatMessages);
542
+ const tools = anthropicToolsFromChatTools(chatRequest.tools);
543
+ const toolChoice = anthropicToolChoiceFromChatToolChoice(chatRequest.tool_choice);
544
+ return {
545
+ model: stringValue(chatRequest.model) ?? "claude-sonnet-4-6",
546
+ messages: converted.messages,
547
+ stream,
548
+ max_tokens: finiteNumber(chatRequest.max_tokens) ?? 4096,
549
+ ...(converted.system ? { system: converted.system } : {}),
550
+ ...(typeof chatRequest.temperature === "number" ? { temperature: chatRequest.temperature } : {}),
551
+ ...(typeof chatRequest.top_p === "number" ? { top_p: chatRequest.top_p } : {}),
552
+ ...(typeof chatRequest.stop === "string" || Array.isArray(chatRequest.stop) ? { stop_sequences: chatRequest.stop } : {}),
553
+ ...(tools.length > 0 ? { tools } : {}),
554
+ ...(toolChoice ? { tool_choice: toolChoice } : {}),
555
+ };
556
+ }
557
+ function upstreamErrorMessage(value) {
558
+ const body = recordValue(value);
559
+ const error = recordValue(body?.error);
560
+ return stringValue(error?.message)
561
+ ?? stringValue(body?.message)
562
+ ?? (typeof value === "string" && value ? value : "Chat Completions upstream failed");
563
+ }
564
+ function streamDeltaText(delta) {
565
+ const content = delta.content;
566
+ if (typeof content === "string") {
567
+ return content;
568
+ }
569
+ if (Array.isArray(content)) {
570
+ return contentToText(content);
571
+ }
572
+ return "";
573
+ }
574
+ function writeSse(res, event, data) {
575
+ res.write(`event: ${event}\n`);
576
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
577
+ }
578
+ function chatUsageToResponseUsage(value) {
579
+ const usage = recordValue(value);
580
+ if (!usage) {
581
+ return null;
582
+ }
583
+ const promptTokens = finiteNumber(usage.prompt_tokens) ?? 0;
584
+ const completionTokens = finiteNumber(usage.completion_tokens) ?? 0;
585
+ const totalTokens = finiteNumber(usage.total_tokens) ?? promptTokens + completionTokens;
586
+ const promptDetails = recordValue(usage.prompt_tokens_details);
587
+ const completionDetails = recordValue(usage.completion_tokens_details);
588
+ const cachedTokens = finiteNumber(promptDetails?.cached_tokens);
589
+ const reasoningTokens = finiteNumber(completionDetails?.reasoning_tokens);
590
+ return {
591
+ input_tokens: promptTokens,
592
+ ...(cachedTokens !== null ? { input_tokens_details: { cached_tokens: cachedTokens } } : {}),
593
+ output_tokens: completionTokens,
594
+ ...(reasoningTokens !== null ? { output_tokens_details: { reasoning_tokens: reasoningTokens } } : {}),
595
+ total_tokens: totalTokens,
596
+ };
597
+ }
598
+ function responseBase(responseId, model, output = [], metadata = responseMetadataFromBody({}), usage = null, status = "completed", error = null) {
599
+ return {
600
+ id: responseId,
601
+ object: "response",
602
+ created_at: Math.floor(Date.now() / 1000),
603
+ status,
604
+ error,
605
+ incomplete_details: null,
606
+ instructions: metadata.instructions,
607
+ max_output_tokens: metadata.maxOutputTokens,
608
+ model,
609
+ output,
610
+ parallel_tool_calls: metadata.parallelToolCalls ?? true,
611
+ previous_response_id: metadata.previousResponseId,
612
+ reasoning: null,
613
+ store: false,
614
+ temperature: metadata.temperature,
615
+ text: metadata.text,
616
+ tool_choice: metadata.toolChoice,
617
+ tools: metadata.tools,
618
+ top_p: metadata.topP,
619
+ truncation: metadata.truncation ?? "disabled",
620
+ usage,
621
+ user: metadata.user,
622
+ metadata: metadata.metadata,
623
+ };
624
+ }
625
+ function chatMessageToResponseOutput(message) {
626
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
627
+ if (toolCalls.length > 0) {
628
+ return toolCalls
629
+ .map((toolCall) => {
630
+ if (!toolCall || typeof toolCall !== "object" || Array.isArray(toolCall)) {
631
+ return null;
632
+ }
633
+ const record = toolCall;
634
+ const fn = record.function && typeof record.function === "object" && !Array.isArray(record.function)
635
+ ? record.function
636
+ : {};
637
+ const name = stringValue(fn.name);
638
+ if (!name) {
639
+ return null;
640
+ }
641
+ const responseName = responsesFunctionCallNameFromChatName(name);
642
+ return {
643
+ type: "function_call",
644
+ id: stringValue(record.id) ?? `fc_${randomUUID().replace(/-/g, "")}`,
645
+ call_id: stringValue(record.id) ?? `call_${randomUUID().replace(/-/g, "")}`,
646
+ ...responseName,
647
+ arguments: typeof fn.arguments === "string" ? fn.arguments : "",
648
+ status: "completed",
649
+ };
650
+ })
651
+ .filter(Boolean);
652
+ }
653
+ return [{
654
+ id: `msg_${randomUUID().replace(/-/g, "")}`,
655
+ type: "message",
656
+ status: "completed",
657
+ role: "assistant",
658
+ content: [{ type: "output_text", text: contentToText(message.content), annotations: [] }],
659
+ }];
660
+ }
661
+ function anthropicUsageToResponseUsage(value) {
662
+ const usage = recordValue(value);
663
+ if (!usage) {
664
+ return null;
665
+ }
666
+ const inputTokens = finiteNumber(usage.input_tokens) ?? 0;
667
+ const outputTokens = finiteNumber(usage.output_tokens) ?? 0;
668
+ return {
669
+ input_tokens: inputTokens,
670
+ output_tokens: outputTokens,
671
+ total_tokens: inputTokens + outputTokens,
672
+ };
673
+ }
674
+ function anthropicContentToResponseOutput(content) {
675
+ const blocks = Array.isArray(content) ? content : [];
676
+ const output = [];
677
+ const text = blocks
678
+ .map((block) => {
679
+ const record = recordValue(block);
680
+ return stringValue(record?.type) === "text" ? textValue(record?.text) ?? "" : "";
681
+ })
682
+ .filter(Boolean)
683
+ .join("");
684
+ if (text) {
685
+ output.push({
686
+ id: `msg_${randomUUID().replace(/-/g, "")}`,
687
+ type: "message",
688
+ status: "completed",
689
+ role: "assistant",
690
+ content: [{ type: "output_text", text, annotations: [] }],
691
+ });
692
+ }
693
+ for (const block of blocks) {
694
+ const record = recordValue(block);
695
+ if (stringValue(record?.type) !== "tool_use") {
696
+ continue;
697
+ }
698
+ const name = stringValue(record?.name);
699
+ if (!name) {
700
+ continue;
701
+ }
702
+ const responseName = responsesFunctionCallNameFromChatName(name);
703
+ output.push({
704
+ type: "function_call",
705
+ id: stringValue(record?.id) ?? `fc_${randomUUID().replace(/-/g, "")}`,
706
+ call_id: stringValue(record?.id) ?? `call_${randomUUID().replace(/-/g, "")}`,
707
+ ...responseName,
708
+ arguments: JSON.stringify(record?.input ?? {}),
709
+ status: "completed",
710
+ });
711
+ }
712
+ return output;
713
+ }
714
+ async function forwardAnthropicNonStreaming(args) {
715
+ const anthropicRequest = buildAnthropicRequest(args.body, false, args.onLog);
716
+ const responseMetadata = responseMetadataFromBody(args.body);
717
+ const upstream = await fetch(`${normalizeBaseUrl(args.targetBaseUrl)}/messages`, {
718
+ method: "POST",
719
+ headers: {
720
+ ...args.upstreamHeaders,
721
+ "content-type": "application/json",
722
+ },
723
+ signal: args.signal,
724
+ body: JSON.stringify(anthropicRequest),
725
+ });
726
+ const upstreamBody = await upstream.json().catch(async () => ({ error: { message: await upstream.text() } }));
727
+ if (!upstream.ok) {
728
+ args.res.writeHead(upstream.status, { "content-type": "application/json" });
729
+ args.res.end(JSON.stringify({
730
+ error: {
731
+ message: upstreamErrorMessage(upstreamBody),
732
+ type: "server_error",
733
+ status: upstream.status,
734
+ upstream: recordValue(upstreamBody.error) ?? upstreamBody,
735
+ },
736
+ }));
737
+ return;
738
+ }
739
+ const responseId = `resp_${randomUUID().replace(/-/g, "")}`;
740
+ const usage = anthropicUsageToResponseUsage(upstreamBody.usage);
741
+ args.res.writeHead(200, { "content-type": "application/json" });
742
+ args.res.end(JSON.stringify(responseBase(responseId, stringValue(anthropicRequest.model) ?? "claude-sonnet-4-6", anthropicContentToResponseOutput(upstreamBody.content), responseMetadata, usage)));
743
+ }
744
+ async function forwardNonStreaming(args) {
745
+ const chatRequest = buildChatRequest(args.body, false, args.onLog);
746
+ const responseMetadata = responseMetadataFromBody(args.body);
747
+ const upstream = await fetch(`${normalizeBaseUrl(args.targetBaseUrl)}/chat/completions`, {
748
+ method: "POST",
749
+ headers: {
750
+ authorization: args.upstreamAuthorization,
751
+ "content-type": "application/json",
752
+ },
753
+ signal: args.signal,
754
+ body: JSON.stringify(chatRequest),
755
+ });
756
+ const upstreamBody = await upstream.json().catch(async () => ({ error: { message: await upstream.text() } }));
757
+ if (!upstream.ok) {
758
+ args.res.writeHead(upstream.status, { "content-type": "application/json" });
759
+ args.res.end(JSON.stringify({
760
+ error: {
761
+ message: upstreamErrorMessage(upstreamBody),
762
+ type: "server_error",
763
+ status: upstream.status,
764
+ upstream: recordValue(upstreamBody.error) ?? upstreamBody,
765
+ },
766
+ }));
767
+ return;
768
+ }
769
+ const choices = Array.isArray(upstreamBody.choices) ? upstreamBody.choices : [];
770
+ const firstChoice = choices[0] && typeof choices[0] === "object" ? choices[0] : {};
771
+ const message = firstChoice.message && typeof firstChoice.message === "object" ? firstChoice.message : {};
772
+ if (!message.content && typeof message.reasoning_content === "string") {
773
+ message.content = message.reasoning_content;
774
+ }
775
+ const responseId = `resp_${randomUUID().replace(/-/g, "")}`;
776
+ const usage = chatUsageToResponseUsage(upstreamBody.usage);
777
+ args.res.writeHead(200, { "content-type": "application/json" });
778
+ args.res.end(JSON.stringify(responseBase(responseId, stringValue(chatRequest.model) ?? "glm-5.2", chatMessageToResponseOutput(message), responseMetadata, usage)));
779
+ }
780
+ function parseStreamingToolCalls(delta) {
781
+ const toolCalls = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
782
+ return toolCalls
783
+ .map((toolCall) => {
784
+ if (!toolCall || typeof toolCall !== "object" || Array.isArray(toolCall)) {
785
+ return null;
786
+ }
787
+ const record = toolCall;
788
+ const fn = record.function && typeof record.function === "object" && !Array.isArray(record.function)
789
+ ? record.function
790
+ : {};
791
+ const id = stringValue(record.id) ?? `call_${randomUUID().replace(/-/g, "")}`;
792
+ return {
793
+ id,
794
+ itemId: `fc_${id.replace(/[^a-zA-Z0-9_-]/g, "")}`,
795
+ name: stringValue(fn.name) ?? "",
796
+ arguments: typeof fn.arguments === "string" ? fn.arguments : "",
797
+ outputIndex: typeof record.index === "number" ? record.index : 0,
798
+ started: false,
799
+ };
800
+ })
801
+ .filter((item) => Boolean(item));
802
+ }
803
+ function writeFailedSse(args) {
804
+ writeSse(args.res, "response.failed", {
805
+ type: "response.failed",
806
+ response: responseBase(args.responseId, args.model, [], args.responseMetadata, null, "failed", {
807
+ message: args.errorMessage,
808
+ type: "server_error",
809
+ }),
810
+ });
811
+ }
812
+ async function forwardStreaming(args) {
813
+ const chatRequest = buildChatRequest(args.body, true, args.onLog);
814
+ const model = stringValue(chatRequest.model) ?? "glm-5.2";
815
+ const responseMetadata = responseMetadataFromBody(args.body);
816
+ const responseId = `resp_${randomUUID().replace(/-/g, "")}`;
817
+ const messageId = `msg_${randomUUID().replace(/-/g, "")}`;
818
+ const outputItems = [];
819
+ let text = "";
820
+ let reasoningText = "";
821
+ let textStarted = false;
822
+ let streamUsage = null;
823
+ const tools = new Map();
824
+ args.res.writeHead(200, {
825
+ "content-type": "text/event-stream; charset=utf-8",
826
+ "cache-control": "no-cache",
827
+ connection: "keep-alive",
828
+ });
829
+ writeSse(args.res, "response.created", { type: "response.created", response: responseBase(responseId, model, [], responseMetadata) });
830
+ writeSse(args.res, "response.in_progress", { type: "response.in_progress", response: responseBase(responseId, model, [], responseMetadata, null, "in_progress") });
831
+ const upstream = await fetch(`${normalizeBaseUrl(args.targetBaseUrl)}/chat/completions`, {
832
+ method: "POST",
833
+ headers: {
834
+ authorization: args.upstreamAuthorization,
835
+ "content-type": "application/json",
836
+ },
837
+ signal: args.signal,
838
+ body: JSON.stringify(chatRequest),
839
+ });
840
+ if (!upstream.ok || !upstream.body) {
841
+ const errorText = await upstream.text();
842
+ writeFailedSse({
843
+ errorMessage: `Chat Completions upstream failed: ${upstream.status} ${errorText}`,
844
+ model,
845
+ res: args.res,
846
+ responseId,
847
+ responseMetadata,
848
+ });
849
+ args.res.end();
850
+ return;
851
+ }
852
+ const reader = upstream.body.getReader();
853
+ const decoder = new TextDecoder();
854
+ let buffer = "";
855
+ let upstreamDone = false;
856
+ let streamFailed = false;
857
+ const processDataLine = (data) => {
858
+ if (!data) {
859
+ return false;
860
+ }
861
+ if (data === "[DONE]") {
862
+ upstreamDone = true;
863
+ return true;
864
+ }
865
+ let parsed;
866
+ try {
867
+ parsed = JSON.parse(data);
868
+ }
869
+ catch (error) {
870
+ writeFailedSse({
871
+ errorMessage: `Failed to parse Chat Completions stream chunk: ${error instanceof Error ? error.message : String(error)}`,
872
+ model,
873
+ res: args.res,
874
+ responseId,
875
+ responseMetadata,
876
+ });
877
+ args.res.end();
878
+ upstreamDone = true;
879
+ streamFailed = true;
880
+ return true;
881
+ }
882
+ const usage = chatUsageToResponseUsage(parsed.usage);
883
+ if (usage) {
884
+ streamUsage = usage;
885
+ }
886
+ const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
887
+ const firstChoice = choices[0] && typeof choices[0] === "object" ? choices[0] : {};
888
+ const delta = firstChoice.delta && typeof firstChoice.delta === "object" ? firstChoice.delta : {};
889
+ const message = firstChoice.message && typeof firstChoice.message === "object" ? firstChoice.message : {};
890
+ const effectiveDelta = Object.keys(delta).length > 0 ? delta : message;
891
+ const reasoningChunk = textValue(effectiveDelta.reasoning_content) ?? "";
892
+ if (reasoningChunk) {
893
+ reasoningText += reasoningChunk;
894
+ }
895
+ const chunk = streamDeltaText(effectiveDelta);
896
+ if (chunk) {
897
+ if (!textStarted) {
898
+ textStarted = true;
899
+ writeSse(args.res, "response.output_item.added", {
900
+ type: "response.output_item.added",
901
+ output_index: 0,
902
+ item: { id: messageId, type: "message", status: "in_progress", role: "assistant", content: [] },
903
+ });
904
+ writeSse(args.res, "response.content_part.added", {
905
+ type: "response.content_part.added",
906
+ item_id: messageId,
907
+ output_index: 0,
908
+ content_index: 0,
909
+ part: { type: "output_text", text: "", annotations: [] },
910
+ });
911
+ }
912
+ text += chunk;
913
+ writeSse(args.res, "response.output_text.delta", {
914
+ type: "response.output_text.delta",
915
+ item_id: messageId,
916
+ output_index: 0,
917
+ content_index: 0,
918
+ delta: chunk,
919
+ });
920
+ }
921
+ for (const partial of parseStreamingToolCalls(effectiveDelta)) {
922
+ const existing = tools.get(partial.id) ?? { ...partial, arguments: "" };
923
+ existing.name = partial.name || existing.name;
924
+ existing.arguments += partial.arguments;
925
+ if (!existing.started && existing.name) {
926
+ existing.started = true;
927
+ const outputIndex = tools.size + (textStarted ? 1 : 0);
928
+ existing.outputIndex = outputIndex;
929
+ const responseName = responsesFunctionCallNameFromChatName(existing.name);
930
+ writeSse(args.res, "response.output_item.added", {
931
+ type: "response.output_item.added",
932
+ output_index: outputIndex,
933
+ item: {
934
+ id: existing.itemId,
935
+ type: "function_call",
936
+ status: "in_progress",
937
+ call_id: existing.id,
938
+ ...responseName,
939
+ arguments: "",
940
+ },
941
+ });
942
+ }
943
+ if (partial.arguments) {
944
+ writeSse(args.res, "response.function_call_arguments.delta", {
945
+ type: "response.function_call_arguments.delta",
946
+ item_id: existing.itemId,
947
+ output_index: existing.outputIndex,
948
+ delta: partial.arguments,
949
+ });
950
+ }
951
+ tools.set(partial.id, existing);
952
+ }
953
+ return false;
954
+ };
955
+ try {
956
+ while (!upstreamDone) {
957
+ const { done, value } = await reader.read();
958
+ if (done)
959
+ break;
960
+ buffer += decoder.decode(value, { stream: true });
961
+ const lines = buffer.split(/\r?\n/);
962
+ buffer = lines.pop() ?? "";
963
+ for (const line of lines) {
964
+ const trimmed = line.trim();
965
+ if (!trimmed.startsWith("data:")) {
966
+ continue;
967
+ }
968
+ const data = trimmed.slice(5).trim();
969
+ if (processDataLine(data)) {
970
+ await reader.cancel().catch(() => undefined);
971
+ break;
972
+ }
973
+ }
974
+ }
975
+ const tail = buffer.trim();
976
+ if (!upstreamDone && tail.startsWith("data:")) {
977
+ processDataLine(tail.slice(5).trim());
978
+ }
979
+ }
980
+ catch (error) {
981
+ if (args.signal.aborted) {
982
+ return;
983
+ }
984
+ writeFailedSse({
985
+ errorMessage: `Chat Completions stream failed: ${error instanceof Error ? error.message : String(error)}`,
986
+ model,
987
+ res: args.res,
988
+ responseId,
989
+ responseMetadata,
990
+ });
991
+ args.res.end();
992
+ return;
993
+ }
994
+ if (streamFailed) {
995
+ return;
996
+ }
997
+ if (!textStarted && reasoningText) {
998
+ textStarted = true;
999
+ text = reasoningText;
1000
+ writeSse(args.res, "response.output_item.added", {
1001
+ type: "response.output_item.added",
1002
+ output_index: 0,
1003
+ item: { id: messageId, type: "message", status: "in_progress", role: "assistant", content: [] },
1004
+ });
1005
+ writeSse(args.res, "response.content_part.added", {
1006
+ type: "response.content_part.added",
1007
+ item_id: messageId,
1008
+ output_index: 0,
1009
+ content_index: 0,
1010
+ part: { type: "output_text", text: "", annotations: [] },
1011
+ });
1012
+ writeSse(args.res, "response.output_text.delta", {
1013
+ type: "response.output_text.delta",
1014
+ item_id: messageId,
1015
+ output_index: 0,
1016
+ content_index: 0,
1017
+ delta: text,
1018
+ });
1019
+ }
1020
+ if (textStarted) {
1021
+ writeSse(args.res, "response.output_text.done", {
1022
+ type: "response.output_text.done",
1023
+ item_id: messageId,
1024
+ output_index: 0,
1025
+ content_index: 0,
1026
+ text,
1027
+ });
1028
+ writeSse(args.res, "response.content_part.done", {
1029
+ type: "response.content_part.done",
1030
+ item_id: messageId,
1031
+ output_index: 0,
1032
+ content_index: 0,
1033
+ part: { type: "output_text", text, annotations: [] },
1034
+ });
1035
+ const item = {
1036
+ id: messageId,
1037
+ type: "message",
1038
+ status: "completed",
1039
+ role: "assistant",
1040
+ content: [{ type: "output_text", text, annotations: [] }],
1041
+ };
1042
+ outputItems.push(item);
1043
+ writeSse(args.res, "response.output_item.done", { type: "response.output_item.done", output_index: 0, item });
1044
+ }
1045
+ for (const tool of tools.values()) {
1046
+ const responseName = responsesFunctionCallNameFromChatName(tool.name);
1047
+ const item = {
1048
+ id: tool.itemId,
1049
+ type: "function_call",
1050
+ status: "completed",
1051
+ call_id: tool.id,
1052
+ ...responseName,
1053
+ arguments: tool.arguments,
1054
+ };
1055
+ outputItems.push(item);
1056
+ writeSse(args.res, "response.function_call_arguments.done", {
1057
+ type: "response.function_call_arguments.done",
1058
+ item_id: tool.itemId,
1059
+ output_index: tool.outputIndex,
1060
+ arguments: tool.arguments,
1061
+ });
1062
+ writeSse(args.res, "response.output_item.done", {
1063
+ type: "response.output_item.done",
1064
+ output_index: tool.outputIndex,
1065
+ item,
1066
+ });
1067
+ }
1068
+ writeSse(args.res, "response.completed", { type: "response.completed", response: responseBase(responseId, model, outputItems, responseMetadata, streamUsage) });
1069
+ args.res.end();
1070
+ }
1071
+ async function forwardAnthropicStreaming(args) {
1072
+ const anthropicRequest = buildAnthropicRequest(args.body, true, args.onLog);
1073
+ const model = stringValue(anthropicRequest.model) ?? "claude-sonnet-4-6";
1074
+ const responseMetadata = responseMetadataFromBody(args.body);
1075
+ const responseId = `resp_${randomUUID().replace(/-/g, "")}`;
1076
+ const outputItems = [];
1077
+ const blocks = new Map();
1078
+ let streamUsage = null;
1079
+ args.res.writeHead(200, {
1080
+ "content-type": "text/event-stream; charset=utf-8",
1081
+ "cache-control": "no-cache",
1082
+ connection: "keep-alive",
1083
+ });
1084
+ writeSse(args.res, "response.created", { type: "response.created", response: responseBase(responseId, model, [], responseMetadata) });
1085
+ writeSse(args.res, "response.in_progress", { type: "response.in_progress", response: responseBase(responseId, model, [], responseMetadata, null, "in_progress") });
1086
+ const upstream = await fetch(`${normalizeBaseUrl(args.targetBaseUrl)}/messages`, {
1087
+ method: "POST",
1088
+ headers: {
1089
+ ...args.upstreamHeaders,
1090
+ "content-type": "application/json",
1091
+ },
1092
+ signal: args.signal,
1093
+ body: JSON.stringify(anthropicRequest),
1094
+ });
1095
+ if (!upstream.ok || !upstream.body) {
1096
+ const errorText = await upstream.text();
1097
+ writeFailedSse({
1098
+ errorMessage: `Anthropic Messages upstream failed: ${upstream.status} ${errorText}`,
1099
+ model,
1100
+ res: args.res,
1101
+ responseId,
1102
+ responseMetadata,
1103
+ });
1104
+ args.res.end();
1105
+ return;
1106
+ }
1107
+ const reader = upstream.body.getReader();
1108
+ const decoder = new TextDecoder();
1109
+ let buffer = "";
1110
+ let upstreamDone = false;
1111
+ let streamFailed = false;
1112
+ let currentEvent = "";
1113
+ const processAnthropicEvent = (event, data) => {
1114
+ if (!data) {
1115
+ return false;
1116
+ }
1117
+ let parsed;
1118
+ try {
1119
+ parsed = JSON.parse(data);
1120
+ }
1121
+ catch (error) {
1122
+ writeFailedSse({
1123
+ errorMessage: `Failed to parse Anthropic stream chunk: ${error instanceof Error ? error.message : String(error)}`,
1124
+ model,
1125
+ res: args.res,
1126
+ responseId,
1127
+ responseMetadata,
1128
+ });
1129
+ args.res.end();
1130
+ streamFailed = true;
1131
+ upstreamDone = true;
1132
+ return true;
1133
+ }
1134
+ const type = stringValue(parsed.type) ?? event;
1135
+ if (type === "error") {
1136
+ writeFailedSse({
1137
+ errorMessage: upstreamErrorMessage(parsed),
1138
+ model,
1139
+ res: args.res,
1140
+ responseId,
1141
+ responseMetadata,
1142
+ });
1143
+ args.res.end();
1144
+ streamFailed = true;
1145
+ upstreamDone = true;
1146
+ return true;
1147
+ }
1148
+ if (type === "message_start") {
1149
+ const usage = anthropicUsageToResponseUsage(recordValue(parsed.message)?.usage);
1150
+ if (usage) {
1151
+ streamUsage = usage;
1152
+ }
1153
+ return false;
1154
+ }
1155
+ if (type === "message_delta") {
1156
+ const usage = anthropicUsageToResponseUsage(parsed.usage);
1157
+ if (usage) {
1158
+ streamUsage = {
1159
+ input_tokens: streamUsage?.input_tokens ?? usage.input_tokens,
1160
+ output_tokens: usage.output_tokens,
1161
+ total_tokens: (streamUsage?.input_tokens ?? usage.input_tokens) + usage.output_tokens,
1162
+ };
1163
+ }
1164
+ return false;
1165
+ }
1166
+ if (type === "content_block_start") {
1167
+ const index = finiteNumber(parsed.index) ?? blocks.size;
1168
+ const block = recordValue(parsed.content_block);
1169
+ const blockType = stringValue(block?.type);
1170
+ if (blockType === "text") {
1171
+ const itemId = `msg_${randomUUID().replace(/-/g, "")}`;
1172
+ blocks.set(index, {
1173
+ id: itemId,
1174
+ outputIndex: index,
1175
+ type: "text",
1176
+ text: "",
1177
+ toolId: "",
1178
+ toolName: "",
1179
+ toolInputJson: "",
1180
+ started: true,
1181
+ });
1182
+ writeSse(args.res, "response.output_item.added", {
1183
+ type: "response.output_item.added",
1184
+ output_index: index,
1185
+ item: { id: itemId, type: "message", status: "in_progress", role: "assistant", content: [] },
1186
+ });
1187
+ writeSse(args.res, "response.content_part.added", {
1188
+ type: "response.content_part.added",
1189
+ item_id: itemId,
1190
+ output_index: index,
1191
+ content_index: 0,
1192
+ part: { type: "output_text", text: "", annotations: [] },
1193
+ });
1194
+ }
1195
+ else if (blockType === "tool_use") {
1196
+ const toolId = stringValue(block?.id) ?? `call_${randomUUID().replace(/-/g, "")}`;
1197
+ const toolName = stringValue(block?.name) ?? "";
1198
+ const responseName = responsesFunctionCallNameFromChatName(toolName);
1199
+ const itemId = `fc_${toolId.replace(/[^a-zA-Z0-9_-]/g, "")}`;
1200
+ blocks.set(index, {
1201
+ id: itemId,
1202
+ outputIndex: index,
1203
+ type: "tool_use",
1204
+ text: "",
1205
+ toolId,
1206
+ toolName,
1207
+ toolInputJson: "",
1208
+ started: true,
1209
+ });
1210
+ writeSse(args.res, "response.output_item.added", {
1211
+ type: "response.output_item.added",
1212
+ output_index: index,
1213
+ item: {
1214
+ id: itemId,
1215
+ type: "function_call",
1216
+ status: "in_progress",
1217
+ call_id: toolId,
1218
+ ...responseName,
1219
+ arguments: "",
1220
+ },
1221
+ });
1222
+ }
1223
+ return false;
1224
+ }
1225
+ if (type === "content_block_delta") {
1226
+ const index = finiteNumber(parsed.index) ?? 0;
1227
+ const block = blocks.get(index);
1228
+ const delta = recordValue(parsed.delta);
1229
+ if (!block || !delta) {
1230
+ return false;
1231
+ }
1232
+ const deltaType = stringValue(delta.type);
1233
+ if (block.type === "text" && deltaType === "text_delta") {
1234
+ const chunk = textValue(delta.text) ?? "";
1235
+ if (!chunk) {
1236
+ return false;
1237
+ }
1238
+ block.text += chunk;
1239
+ writeSse(args.res, "response.output_text.delta", {
1240
+ type: "response.output_text.delta",
1241
+ item_id: block.id,
1242
+ output_index: block.outputIndex,
1243
+ content_index: 0,
1244
+ delta: chunk,
1245
+ });
1246
+ }
1247
+ else if (block.type === "tool_use" && deltaType === "input_json_delta") {
1248
+ const chunk = textValue(delta.partial_json) ?? "";
1249
+ if (!chunk) {
1250
+ return false;
1251
+ }
1252
+ block.toolInputJson += chunk;
1253
+ writeSse(args.res, "response.function_call_arguments.delta", {
1254
+ type: "response.function_call_arguments.delta",
1255
+ item_id: block.id,
1256
+ output_index: block.outputIndex,
1257
+ delta: chunk,
1258
+ });
1259
+ }
1260
+ blocks.set(index, block);
1261
+ return false;
1262
+ }
1263
+ if (type === "content_block_stop") {
1264
+ const index = finiteNumber(parsed.index) ?? 0;
1265
+ const block = blocks.get(index);
1266
+ if (!block) {
1267
+ return false;
1268
+ }
1269
+ if (block.type === "text") {
1270
+ writeSse(args.res, "response.output_text.done", {
1271
+ type: "response.output_text.done",
1272
+ item_id: block.id,
1273
+ output_index: block.outputIndex,
1274
+ content_index: 0,
1275
+ text: block.text,
1276
+ });
1277
+ writeSse(args.res, "response.content_part.done", {
1278
+ type: "response.content_part.done",
1279
+ item_id: block.id,
1280
+ output_index: block.outputIndex,
1281
+ content_index: 0,
1282
+ part: { type: "output_text", text: block.text, annotations: [] },
1283
+ });
1284
+ const item = {
1285
+ id: block.id,
1286
+ type: "message",
1287
+ status: "completed",
1288
+ role: "assistant",
1289
+ content: [{ type: "output_text", text: block.text, annotations: [] }],
1290
+ };
1291
+ outputItems.push(item);
1292
+ writeSse(args.res, "response.output_item.done", { type: "response.output_item.done", output_index: block.outputIndex, item });
1293
+ }
1294
+ else {
1295
+ const responseName = responsesFunctionCallNameFromChatName(block.toolName);
1296
+ const item = {
1297
+ id: block.id,
1298
+ type: "function_call",
1299
+ status: "completed",
1300
+ call_id: block.toolId,
1301
+ ...responseName,
1302
+ arguments: block.toolInputJson || "{}",
1303
+ };
1304
+ outputItems.push(item);
1305
+ writeSse(args.res, "response.function_call_arguments.done", {
1306
+ type: "response.function_call_arguments.done",
1307
+ item_id: block.id,
1308
+ output_index: block.outputIndex,
1309
+ arguments: block.toolInputJson || "{}",
1310
+ });
1311
+ writeSse(args.res, "response.output_item.done", { type: "response.output_item.done", output_index: block.outputIndex, item });
1312
+ }
1313
+ return false;
1314
+ }
1315
+ if (type === "message_stop") {
1316
+ upstreamDone = true;
1317
+ return true;
1318
+ }
1319
+ return false;
1320
+ };
1321
+ try {
1322
+ while (!upstreamDone) {
1323
+ const { done, value } = await reader.read();
1324
+ if (done)
1325
+ break;
1326
+ buffer += decoder.decode(value, { stream: true });
1327
+ const lines = buffer.split(/\r?\n/);
1328
+ buffer = lines.pop() ?? "";
1329
+ for (const line of lines) {
1330
+ const trimmed = line.trim();
1331
+ if (!trimmed) {
1332
+ continue;
1333
+ }
1334
+ if (trimmed.startsWith("event:")) {
1335
+ currentEvent = trimmed.slice(6).trim();
1336
+ continue;
1337
+ }
1338
+ if (!trimmed.startsWith("data:")) {
1339
+ continue;
1340
+ }
1341
+ const data = trimmed.slice(5).trim();
1342
+ if (processAnthropicEvent(currentEvent, data)) {
1343
+ await reader.cancel().catch(() => undefined);
1344
+ break;
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ catch (error) {
1350
+ if (args.signal.aborted) {
1351
+ return;
1352
+ }
1353
+ writeFailedSse({
1354
+ errorMessage: `Anthropic Messages stream failed: ${error instanceof Error ? error.message : String(error)}`,
1355
+ model,
1356
+ res: args.res,
1357
+ responseId,
1358
+ responseMetadata,
1359
+ });
1360
+ args.res.end();
1361
+ return;
1362
+ }
1363
+ if (streamFailed) {
1364
+ return;
1365
+ }
1366
+ writeSse(args.res, "response.completed", { type: "response.completed", response: responseBase(responseId, model, outputItems, responseMetadata, streamUsage) });
1367
+ args.res.end();
1368
+ }
1369
+ function requestAbortSignal(req, res) {
1370
+ const abortController = new AbortController();
1371
+ const abort = () => {
1372
+ if (!res.writableEnded && !abortController.signal.aborted) {
1373
+ abortController.abort();
1374
+ }
1375
+ };
1376
+ req.once("aborted", abort);
1377
+ res.once("close", abort);
1378
+ return abortController.signal;
1379
+ }
1380
+ async function startResponsesBridge(args) {
1381
+ const targetBaseUrl = normalizeBaseUrl(args.targetBaseUrl);
1382
+ const expectedAuthorization = `Bearer ${args.localApiKey}`;
1383
+ const upstreamHeaders = args.adapter.upstreamAuthHeaders(args.upstreamApiKey);
1384
+ const server = createServer(async (req, res) => {
1385
+ try {
1386
+ if (req.method !== "POST" || !req.url?.replace(/\?.*$/, "").endsWith("/responses")) {
1387
+ res.writeHead(404, { "content-type": "application/json" });
1388
+ res.end(JSON.stringify({ error: { message: "Not Found" } }));
1389
+ return;
1390
+ }
1391
+ const authorization = stringValue(req.headers.authorization) ?? "";
1392
+ if (authorization !== expectedAuthorization) {
1393
+ res.writeHead(401, { "content-type": "application/json" });
1394
+ res.end(JSON.stringify({ error: { message: "Invalid Authorization header" } }));
1395
+ return;
1396
+ }
1397
+ const body = await readRequestJson(req);
1398
+ const signal = requestAbortSignal(req, res);
1399
+ if (args.adapter.kind === "anthropic_messages") {
1400
+ if (body.stream === true) {
1401
+ await forwardAnthropicStreaming({ body, res, signal, targetBaseUrl, upstreamHeaders, onLog: args.onLog });
1402
+ }
1403
+ else {
1404
+ await forwardAnthropicNonStreaming({ body, res, signal, targetBaseUrl, upstreamHeaders, onLog: args.onLog });
1405
+ }
1406
+ }
1407
+ else if (body.stream === true) {
1408
+ await forwardStreaming({
1409
+ body,
1410
+ res,
1411
+ signal,
1412
+ targetBaseUrl,
1413
+ upstreamAuthorization: upstreamHeaders.authorization ?? "",
1414
+ onLog: args.onLog,
1415
+ });
1416
+ }
1417
+ else {
1418
+ await forwardNonStreaming({
1419
+ body,
1420
+ res,
1421
+ signal,
1422
+ targetBaseUrl,
1423
+ upstreamAuthorization: upstreamHeaders.authorization ?? "",
1424
+ onLog: args.onLog,
1425
+ });
1426
+ }
1427
+ }
1428
+ catch (error) {
1429
+ if (error instanceof Error && error.name === "AbortError") {
1430
+ return;
1431
+ }
1432
+ args.onLog?.(`Codex chat bridge failed: ${error instanceof Error ? error.message : String(error)}`);
1433
+ if (!res.headersSent) {
1434
+ res.writeHead(500, { "content-type": "application/json" });
1435
+ }
1436
+ res.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error) } }));
1437
+ }
1438
+ });
1439
+ await new Promise((resolve, reject) => {
1440
+ server.once("error", reject);
1441
+ server.listen(0, "127.0.0.1", () => {
1442
+ server.off("error", reject);
1443
+ resolve();
1444
+ });
1445
+ });
1446
+ const address = server.address();
1447
+ if (!address || typeof address === "string") {
1448
+ throw new Error("Failed to start Codex chat bridge");
1449
+ }
1450
+ return {
1451
+ baseUrl: `http://127.0.0.1:${address.port}`,
1452
+ stop: () => stopServer(server),
1453
+ };
1454
+ }
1455
+ export async function startCodexChatBridge(args) {
1456
+ const providerName = args.providerName?.trim() || "Chat Completions provider";
1457
+ const providerApiKey = args.providerApiKey.trim();
1458
+ if (!providerApiKey) {
1459
+ throw new Error(`${providerName} API key is required to start the Codex chat bridge`);
1460
+ }
1461
+ const targetBaseUrl = resolveTargetBaseUrl({ providerId: args.providerId, targetBaseUrl: args.targetBaseUrl });
1462
+ const adapter = resolveProviderAdapter(args.providerId);
1463
+ const apiKey = `sk-doer-chat-bridge-${randomUUID().replace(/-/g, "")}`;
1464
+ const bridge = await startResponsesBridge({
1465
+ adapter,
1466
+ localApiKey: apiKey,
1467
+ targetBaseUrl,
1468
+ upstreamApiKey: providerApiKey,
1469
+ onLog: args.onLog,
1470
+ });
1471
+ args.onLog?.(`Codex provider gateway listening baseUrl=${bridge.baseUrl} target=${targetBaseUrl} provider=${providerName} adapter=${adapter.kind}`);
1472
+ return {
1473
+ baseUrl: bridge.baseUrl,
1474
+ envKey: CHAT_BRIDGE_ENV_KEY,
1475
+ apiKey,
1476
+ stop: bridge.stop,
1477
+ };
1478
+ }
1479
+ function stopServer(server) {
1480
+ return new Promise((resolve, reject) => {
1481
+ server.close((error) => {
1482
+ if (error) {
1483
+ reject(error);
1484
+ return;
1485
+ }
1486
+ resolve();
1487
+ });
1488
+ });
1489
+ }