llm-relay 0.40.0 → 0.41.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.
@@ -0,0 +1,480 @@
1
+ /**
2
+ * OpenAI Responses REQUEST → Anthropic Messages REQUEST.
3
+ *
4
+ * Why this is relay-owned. Until 2026-08-23 `fetchOpenAiFront()` handed a `/v1/responses` body to
5
+ * llm-bridge's `translateBetweenProviders("openai-responses", "anthropic", …)`. Its
6
+ * `openaiResponsesToUniversal` models exactly one tool-shaped input item — `function_call_output`
7
+ * — and nothing else, so on a multi-turn tool conversation:
8
+ *
9
+ * - a `function_call` item (the assistant's own tool call) has no `role`, defaulted to `"user"`,
10
+ * and its `content` is `undefined` ⇒ the turn became `[{type:"text", text: undefined}]`. The
11
+ * tool call vanished, and the `tool_result` that followed had no `tool_use` to answer — an
12
+ * Anthropic target 400s on it, and an `openai`-kind target received a `role:"tool"` message
13
+ * with no `tool_calls` before it. Every Responses tool conversation was broken past the first
14
+ * call;
15
+ * - an assistant `message` whose parts are `output_text` hit `parseResponsesContent`'s
16
+ * fall-through and reached the backend as `JSON.stringify(part)` — the assistant's own prior
17
+ * answer delivered as a JSON string. Same class of leak as the request-side IR envelope
18
+ * v0.39.0 removed on the Chat direction (docs/tool-call-dialect-leak.md §"Second mechanism");
19
+ * - a `reasoning` item (Codex sends one before most turns) became a bogus user turn;
20
+ * - `instructions` — Codex's system prompt — was read by nobody and dropped entirely.
21
+ *
22
+ * The rule this module exists to keep is the same one `openai-request.ts` keeps in the other
23
+ * direction: **the body the relay sends is the caller's conversation, never the relay's
24
+ * internals.** Deterministic wire-shape translation on the safe side of the repair boundary — no
25
+ * model, no inference, no invented content — and anything with no representation is REFUSED
26
+ * (`RequestMappingError` → a clean local 400, the `documents.ts` precedent) rather than
27
+ * stringified, because a mangled prompt reads exactly like a working one.
28
+ *
29
+ * Per-field decisions, all deliberate:
30
+ *
31
+ * - `instructions` → the head of `system`; `system`/`developer` role items append to it in
32
+ * order, joined with "\n" (llm-bridge's separator for the item half).
33
+ * - `input` as a string → one user text turn. `input[]` items map to turns, merging CONSECUTIVE
34
+ * same-role items into one turn: an assistant `message` followed by its `function_call`s is
35
+ * one assistant turn, and consecutive `function_call_output`s are one user turn — which is
36
+ * what Anthropic requires (every `tool_result` in the user turn immediately after the
37
+ * `tool_use` turn). Within a user turn `tool_result` blocks lead, mirroring the tool-message
38
+ * ordering rule in `openai-request.ts` — so a user `message` the caller wrote immediately
39
+ * BEFORE a `function_call_output` lands after that turn's tool results. That is the one
40
+ * reordering this mapper performs, it is within a single turn, and the alternatives are both
41
+ * shapes Anthropic rejects (results not leading, or the turn split in two). Nothing is
42
+ * reordered ACROSS turns.
43
+ * - ids round-trip unchanged: `function_call.call_id` → `tool_use.id`, and
44
+ * `function_call_output.call_id` → `tool_result.tool_use_id`. Those ids are the ones this
45
+ * relay minted on the way out (`anthropicMessageToOpenAi` sets `call_id` = the Anthropic
46
+ * `tool_use` id), so the linkage survives the whole round trip and `openai-request.ts` can
47
+ * re-emit them as `tool_calls[].id` / `tool_call_id` for an `openai`-kind target.
48
+ * - `reasoning` items are DROPPED: a reasoning summary has no Anthropic representation without
49
+ * the signature the minting provider issued, and forwarding one vendor's private reasoning to
50
+ * another vendor is not translation. Same decision as `openai-request.ts` dropping `thinking`.
51
+ * Dropping is neutral for turn merging, so an assistant turn split by a `reasoning` item still
52
+ * merges into one.
53
+ * - `reasoning.effort` is DROPPED. llm-bridge turned it into `thinking.budget_tokens: 10240` —
54
+ * a token budget nobody stated, i.e. an invented figure, which provenance forbids.
55
+ * - `text.format` of `json_schema`/`json_object` is REFUSED. It is a contract about the shape of
56
+ * the answer; Anthropic Messages has no equivalent, so dropping it returns prose to a caller
57
+ * that will parse it as JSON — a silent corruption, and the one thing worse than a 400.
58
+ * - `previous_response_id` is REFUSED: the relay holds no response state, so honouring it
59
+ * silently would drop the whole conversation prefix it names.
60
+ * - `max_output_tokens` → `max_tokens`; absent falls back to 1024 — llm-bridge's carried default,
61
+ * kept ONLY because the field is mandatory downstream. It is a default, not a measurement, and
62
+ * nothing labels it otherwise.
63
+ * - `store`, `prompt_cache_key`, `include`, `metadata`, `user`, `truncation`, `text.verbosity`,
64
+ * `service_tier`, `background` and non-function tool declarations (`web_search_preview`, …)
65
+ * are dropped: no representation, and no effect on the conversation. That is also exactly
66
+ * what llm-bridge did with them (into an unread `provider_params`), so the behaviour is
67
+ * preserved rather than newly decided.
68
+ * - Any other input item type (`item_reference`, `local_shell_call`, `custom_tool_call`,
69
+ * `web_search_call`, `computer_call`, `image_generation_call`, …) is REFUSED, naming the type.
70
+ * A newer Codex build inventing an item shape must fail loudly on the first request, not
71
+ * quietly reshape the conversation.
72
+ *
73
+ * `model` is left exactly as the caller sent it: `fetchOpenAiFront` overwrites it with the
74
+ * resolved deployment id whenever the target declares one, and there is no fallback string here —
75
+ * llm-bridge's `String(body.model || "unknown")` would put a model id nobody named on the wire.
76
+ */
77
+ import { RequestMappingError } from "./openai-request.js";
78
+ function isRecord(v) {
79
+ return typeof v === "object" && v !== null && !Array.isArray(v);
80
+ }
81
+ /** Name an unexpected type in an error without echoing an arbitrary payload back at the caller. */
82
+ function describeType(t) {
83
+ return typeof t === "string" && t.length > 0 ? `"${t.slice(0, 40)}"` : "(missing type)";
84
+ }
85
+ /** A base64 data URL, split into its media type and payload. `.*` with `s` so newlines survive. */
86
+ const DATA_URL = /^data:([^;,]+);base64,(.*)$/s;
87
+ /**
88
+ * One Responses `input_image` part → an Anthropic `image` block.
89
+ *
90
+ * A `data:` URL becomes a base64 source (Anthropic wants the media type and payload apart); an
91
+ * http(s) URL becomes a url source. Anything else — notably a bare `file_id`, which names a file
92
+ * on OpenAI's servers this relay cannot read — is refused rather than forwarded as a URL the
93
+ * backend will fail to fetch.
94
+ */
95
+ function imageBlock(part) {
96
+ const raw = part.image_url;
97
+ const url = typeof raw === "string" ? raw : isRecord(raw) && typeof raw.url === "string" ? raw.url : "";
98
+ if (url.length === 0)
99
+ throw new RequestMappingError("input_image without an image_url");
100
+ const data = DATA_URL.exec(url);
101
+ if (data)
102
+ return { type: "image", source: { type: "base64", media_type: data[1], data: data[2] } };
103
+ if (/^https?:\/\//i.test(url))
104
+ return { type: "image", source: { type: "url", url } };
105
+ throw new RequestMappingError("input_image url must be a base64 data URL or an http(s) URL");
106
+ }
107
+ /**
108
+ * One Responses `input_file` part → an Anthropic `document` block.
109
+ *
110
+ * Only a base64 **PDF** data URL is representable: Anthropic's `document` block takes a
111
+ * `media_type` and the only one it accepts is `application/pdf`. A bare base64 payload with a
112
+ * filename, a `file_id`, or a `file_url` is refused — guessing the media type from an extension
113
+ * is the kind of inference this module exists to avoid, and a `file_id` names storage the relay
114
+ * cannot read.
115
+ */
116
+ function documentBlock(part) {
117
+ const fileData = typeof part.file_data === "string" ? part.file_data : "";
118
+ const data = DATA_URL.exec(fileData);
119
+ if (data && data[1] === "application/pdf") {
120
+ return { type: "document", source: { type: "base64", media_type: "application/pdf", data: data[2] } };
121
+ }
122
+ throw new RequestMappingError("input_file has no Anthropic representation; only a base64 application/pdf data URL in file_data is carried");
123
+ }
124
+ /**
125
+ * The parts of one message item (or of a `function_call_output` list) → Anthropic content blocks.
126
+ *
127
+ * `documents` is false inside a `tool_result` and on an assistant turn: Anthropic's tool_result
128
+ * content carries text and images only, and an assistant turn is what the model already said, so
129
+ * a document in either has nowhere to go and is refused rather than flattened. `context` names
130
+ * which of them the caller is being told about — a refusal is zero-egress and its message is the
131
+ * only diagnostic they get, so it must not point at a place their request does not contain.
132
+ *
133
+ * An EMPTY text part yields no block. Anthropic rejects an empty text block outright
134
+ * ("text content blocks must contain non-whitespace text"), so emitting one would turn a caller's
135
+ * harmless empty string into a 400 on every candidate.
136
+ */
137
+ function contentBlocks(content, opts) {
138
+ if (content === undefined || content === null)
139
+ return [];
140
+ if (typeof content === "string")
141
+ return content.length > 0 ? [{ type: "text", text: content }] : [];
142
+ if (!Array.isArray(content)) {
143
+ throw new RequestMappingError(`${opts.context} content must be a string or a list of parts`);
144
+ }
145
+ const blocks = [];
146
+ for (const raw of content) {
147
+ if (!isRecord(raw))
148
+ throw new RequestMappingError(`${opts.context} content part is not an object`);
149
+ switch (raw.type) {
150
+ case "input_text":
151
+ case "output_text":
152
+ case "text": {
153
+ const text = typeof raw.text === "string" ? raw.text : "";
154
+ if (text.length > 0)
155
+ blocks.push({ type: "text", text });
156
+ break;
157
+ }
158
+ // What the assistant said when it declined. It is content, not a status field, and
159
+ // dropping it would leave a hole in the conversation the model is asked to continue.
160
+ case "refusal": {
161
+ const text = typeof raw.refusal === "string" ? raw.refusal : "";
162
+ if (text.length > 0)
163
+ blocks.push({ type: "text", text });
164
+ break;
165
+ }
166
+ case "input_image":
167
+ blocks.push(imageBlock(raw));
168
+ break;
169
+ case "input_file":
170
+ if (!opts.documents) {
171
+ throw new RequestMappingError(`${opts.context} carries text and images only, not an input_file`);
172
+ }
173
+ blocks.push(documentBlock(raw));
174
+ break;
175
+ default:
176
+ throw new RequestMappingError(`${opts.context} carries an unsupported Responses content part ${describeType(raw.type)}`);
177
+ }
178
+ }
179
+ return blocks;
180
+ }
181
+ /**
182
+ * `system` / `developer` item content → the text it contributes to `system`.
183
+ *
184
+ * Anthropic's `system` is text, so a non-text part has no representation here and is REFUSED like
185
+ * every other one — the same rule `contentBlocks` keeps. Silently skipping it would deliver a
186
+ * system prompt with a hole in it, which reads exactly like a working one: the failure this
187
+ * module exists to remove, and strictly worse than the stringification it replaced.
188
+ */
189
+ function systemTextOf(content) {
190
+ if (content === undefined || content === null)
191
+ return "";
192
+ if (typeof content === "string")
193
+ return content;
194
+ if (!Array.isArray(content)) {
195
+ throw new RequestMappingError("a system/developer item's content must be a string or a list of parts");
196
+ }
197
+ const parts = [];
198
+ for (const raw of content) {
199
+ if (!isRecord(raw))
200
+ throw new RequestMappingError("a system/developer content part is not an object");
201
+ if (raw.type !== "input_text" && raw.type !== "output_text" && raw.type !== "text") {
202
+ throw new RequestMappingError(`a system/developer item carries an unsupported ${describeType(raw.type)} part; Anthropic system is text only`);
203
+ }
204
+ if (typeof raw.text === "string" && raw.text.length > 0)
205
+ parts.push(raw.text);
206
+ }
207
+ // "\n", not llm-bridge's " ": these are independent instructions, and a space runs the last
208
+ // word of one into the first word of the next.
209
+ return parts.join("\n");
210
+ }
211
+ /**
212
+ * `function_call.arguments` → the `tool_use.input` object.
213
+ *
214
+ * Anthropic requires an OBJECT. An absent or empty string is the no-argument call every client
215
+ * spells differently and becomes `{}`; anything else that does not parse to an object is REFUSED,
216
+ * because wrapping a string in a synthetic key (`{"input": "…"}`) would invent a schema the tool
217
+ * never declared and the model would read the invention back as its own prior call.
218
+ */
219
+ function toolArguments(raw, name) {
220
+ if (raw === undefined || raw === null)
221
+ return {};
222
+ if (isRecord(raw))
223
+ return raw;
224
+ if (typeof raw !== "string") {
225
+ throw new RequestMappingError(`function_call ${describeType(name)} arguments are neither a string nor an object`);
226
+ }
227
+ if (raw.trim().length === 0)
228
+ return {};
229
+ let parsed;
230
+ try {
231
+ parsed = JSON.parse(raw);
232
+ }
233
+ catch {
234
+ throw new RequestMappingError(`function_call ${describeType(name)} arguments are not valid JSON`);
235
+ }
236
+ if (!isRecord(parsed)) {
237
+ throw new RequestMappingError(`function_call ${describeType(name)} arguments are not a JSON object`);
238
+ }
239
+ return parsed;
240
+ }
241
+ /** A `function_call` item → the assistant `tool_use` block it is. */
242
+ function toolUseBlock(item) {
243
+ // The `call_id` IS the linkage to the `function_call_output` that answers it — and, on a turn
244
+ // this relay served, the Anthropic `tool_use` id it minted. Synthesizing one would silently
245
+ // detach the result from the call, so an item without one is refused.
246
+ const id = typeof item.call_id === "string" ? item.call_id : "";
247
+ if (id.length === 0)
248
+ throw new RequestMappingError("function_call without a call_id");
249
+ const name = typeof item.name === "string" ? item.name : "";
250
+ if (name.length === 0)
251
+ throw new RequestMappingError("function_call without a name");
252
+ return { type: "tool_use", id, name, input: toolArguments(item.arguments, name) };
253
+ }
254
+ /** A `function_call_output` item → the user-side `tool_result` block it is. */
255
+ function toolResultBlock(item) {
256
+ const id = typeof item.call_id === "string" ? item.call_id : "";
257
+ if (id.length === 0)
258
+ throw new RequestMappingError("function_call_output without a call_id");
259
+ const block = { type: "tool_result", tool_use_id: id };
260
+ const output = item.output;
261
+ // A tool_result's `content` is a plain string FIELD, not a text block, so "" is representable
262
+ // where an empty `{type:"text"}` block is not — but an empty LIST is the shape Anthropic
263
+ // rejects, the same rule `flush()` keeps for a turn. A result the caller stated nothing for, or
264
+ // whose parts carried no block, is therefore the empty string: the tool answered with nothing.
265
+ if (output === undefined || output === null)
266
+ block.content = "";
267
+ else if (typeof output === "string")
268
+ block.content = output;
269
+ else if (Array.isArray(output)) {
270
+ const blocks = contentBlocks(output, { documents: false, context: "a tool result" });
271
+ block.content = blocks.length > 0 ? blocks : "";
272
+ }
273
+ else
274
+ throw new RequestMappingError("function_call_output output must be a string or a list of parts");
275
+ return block;
276
+ }
277
+ /**
278
+ * `tools[]` → Anthropic tool declarations.
279
+ *
280
+ * Only `type: "function"` has a representation. Built-in declarations (`web_search_preview`,
281
+ * `file_search`, `computer_use_preview`, `code_interpreter`, …) are DROPPED — that is what
282
+ * llm-bridge already did with them (into a `provider_params` block nothing reads), they are
283
+ * hosted server-side by OpenAI rather than executed by the caller, and refusing the request
284
+ * outright would break every Codex session that declares one it never uses. Preserved as
285
+ * existing behaviour, flagged here so it can be revisited with evidence.
286
+ *
287
+ * `strict` is dropped: Anthropic has no per-tool strict-schema flag, and the schema itself is
288
+ * carried verbatim.
289
+ *
290
+ * A `tools` field that is not a list is REFUSED rather than ignored: quietly emitting no tools has
291
+ * the same consequence as dropping a nameless declaration below — the model cannot call a tool the
292
+ * harness granted, and the failure surfaces several turns later as an unexplained refusal.
293
+ */
294
+ function mapTools(tools) {
295
+ if (tools === undefined || tools === null)
296
+ return null;
297
+ if (!Array.isArray(tools))
298
+ throw new RequestMappingError("tools must be a list of tool declarations");
299
+ const out = [];
300
+ for (const raw of tools) {
301
+ if (!isRecord(raw))
302
+ throw new RequestMappingError("tool declaration is not an object");
303
+ if (raw.type !== undefined && raw.type !== "function")
304
+ continue;
305
+ const name = typeof raw.name === "string" ? raw.name : "";
306
+ if (name.length === 0) {
307
+ // Dropping it would leave the model unable to call a tool the harness granted, and the
308
+ // failure would surface as an unexplained refusal several turns later.
309
+ throw new RequestMappingError("function tool declaration without a name");
310
+ }
311
+ const fn = { name };
312
+ if (typeof raw.description === "string" && raw.description.length > 0)
313
+ fn.description = raw.description;
314
+ // Anthropic requires an object schema. A declared schema is carried verbatim; only the
315
+ // mandatory envelope key is supplied when the caller omitted it, and a missing/`null`
316
+ // `parameters` becomes the empty object schema — "this tool takes no declared parameters",
317
+ // which is what the declaration means.
318
+ fn.input_schema = isRecord(raw.parameters)
319
+ ? (raw.parameters.type === undefined ? { type: "object", ...raw.parameters } : raw.parameters)
320
+ : { type: "object", properties: {} };
321
+ out.push(fn);
322
+ }
323
+ return out.length > 0 ? out : null;
324
+ }
325
+ /**
326
+ * `tool_choice` → the Anthropic spelling. OpenAI's `required` ("call SOME tool") is Anthropic's
327
+ * `any`; a named function becomes the `tool` form. An unrecognised shape (`allowed_tools`, a
328
+ * hosted-tool choice) is dropped rather than guessed at — the default is `auto`, which is where
329
+ * llm-bridge left it anyway.
330
+ */
331
+ function mapToolChoice(choice) {
332
+ if (choice === "auto")
333
+ return { type: "auto" };
334
+ if (choice === "required")
335
+ return { type: "any" };
336
+ if (choice === "none")
337
+ return { type: "none" };
338
+ if (!isRecord(choice))
339
+ return undefined;
340
+ if (choice.type === "function" && typeof choice.name === "string" && choice.name.length > 0) {
341
+ return { type: "tool", name: choice.name };
342
+ }
343
+ if (choice.type === "auto")
344
+ return { type: "auto" };
345
+ if (choice.type === "none")
346
+ return { type: "none" };
347
+ return undefined;
348
+ }
349
+ /**
350
+ * Translate one OpenAI Responses request body into an Anthropic Messages request body.
351
+ *
352
+ * Turn order is preserved exactly; only CONSECUTIVE same-role items merge. Unknown top-level
353
+ * fields are not forwarded — this is a translation between two contracts, not a passthrough.
354
+ *
355
+ * @throws {RequestMappingError} for an item, part or declaration that cannot be represented.
356
+ */
357
+ export function openaiResponsesRequestToAnthropic(reqJson) {
358
+ const body = isRecord(reqJson) ? reqJson : {};
359
+ if (typeof body.previous_response_id === "string" && body.previous_response_id.length > 0) {
360
+ throw new RequestMappingError("previous_response_id is not supported: this relay stores no responses, so the conversation prefix it names would be silently dropped");
361
+ }
362
+ const format = isRecord(body.text) && isRecord(body.text.format) ? body.text.format : null;
363
+ if (format && (format.type === "json_schema" || format.type === "json_object")) {
364
+ throw new RequestMappingError(`text.format ${describeType(format.type)} has no Anthropic Messages equivalent; dropping it would return prose to a caller that parses JSON`);
365
+ }
366
+ const systemParts = [];
367
+ if (typeof body.instructions === "string" && body.instructions.length > 0)
368
+ systemParts.push(body.instructions);
369
+ const messages = [];
370
+ let role = null;
371
+ let leading = [];
372
+ let trailing = [];
373
+ const flush = () => {
374
+ if (role !== null) {
375
+ const content = [...leading, ...trailing];
376
+ // A turn that produced no block says nothing; emitting it would be an empty content array,
377
+ // which Anthropic rejects. Nothing the caller wrote is lost — there was nothing to carry.
378
+ if (content.length > 0)
379
+ messages.push({ role, content });
380
+ }
381
+ role = null;
382
+ leading = [];
383
+ trailing = [];
384
+ };
385
+ /** `first: true` puts the blocks at the head of the turn — where Anthropic wants tool_results. */
386
+ const push = (next, blocks, first = false) => {
387
+ if (blocks.length === 0)
388
+ return;
389
+ if (role !== next) {
390
+ flush();
391
+ role = next;
392
+ }
393
+ if (first)
394
+ leading.push(...blocks);
395
+ else
396
+ trailing.push(...blocks);
397
+ };
398
+ const input = body.input;
399
+ if (typeof input === "string") {
400
+ push("user", contentBlocks(input, { documents: true, context: "a user message" }));
401
+ }
402
+ else if (Array.isArray(input)) {
403
+ for (const raw of input) {
404
+ if (!isRecord(raw))
405
+ throw new RequestMappingError("input item is not an object");
406
+ const type = typeof raw.type === "string" ? raw.type : undefined;
407
+ if (type === "function_call") {
408
+ push("assistant", [toolUseBlock(raw)]);
409
+ continue;
410
+ }
411
+ if (type === "function_call_output") {
412
+ push("user", [toolResultBlock(raw)], true);
413
+ continue;
414
+ }
415
+ // Dropped, and deliberately WITHOUT flushing: a Codex assistant turn is often
416
+ // message → reasoning → function_call, and flushing here would split it in two.
417
+ if (type === "reasoning")
418
+ continue;
419
+ if (type !== undefined && type !== "message") {
420
+ throw new RequestMappingError(`unsupported Responses input item ${describeType(raw.type)}`);
421
+ }
422
+ const itemRole = typeof raw.role === "string" ? raw.role : undefined;
423
+ if (itemRole === undefined) {
424
+ // Two different malformations, and the message must name the one the caller actually
425
+ // sent: a `message` item that forgot its role, or an item with no type at all.
426
+ throw new RequestMappingError(type === "message" ? "message item without a role" : "input item has neither a type nor a role");
427
+ }
428
+ if (itemRole === "system" || itemRole === "developer") {
429
+ const text = systemTextOf(raw.content);
430
+ if (text.length > 0)
431
+ systemParts.push(text);
432
+ continue;
433
+ }
434
+ if (itemRole !== "user" && itemRole !== "assistant") {
435
+ throw new RequestMappingError(`unsupported Responses input role ${describeType(raw.role)}`);
436
+ }
437
+ push(itemRole, contentBlocks(raw.content, {
438
+ documents: itemRole === "user",
439
+ context: itemRole === "user" ? "a user message" : "an assistant message",
440
+ }));
441
+ }
442
+ }
443
+ else if (input !== undefined && input !== null) {
444
+ throw new RequestMappingError("input must be a string or a list of items");
445
+ }
446
+ flush();
447
+ const out = {
448
+ // Mandatory downstream, so llm-bridge's 1024 is carried when the caller stated nothing.
449
+ // A carried default, not a measurement of anything.
450
+ max_tokens: typeof body.max_output_tokens === "number" ? body.max_output_tokens : 1024,
451
+ messages,
452
+ };
453
+ // The caller's own id, untouched: `fetchOpenAiFront` replaces it with the resolved deployment.
454
+ if (typeof body.model === "string" && body.model.length > 0)
455
+ out.model = body.model;
456
+ if (systemParts.length > 0)
457
+ out.system = systemParts.join("\n");
458
+ const tools = mapTools(body.tools);
459
+ if (tools) {
460
+ out.tools = tools;
461
+ // `tool_choice` without `tools` is rejected by Anthropic and means nothing anyway.
462
+ const toolChoice = mapToolChoice(body.tool_choice);
463
+ if (toolChoice) {
464
+ // Anthropic spells the parallel-call switch on tool_choice, and only where it can apply —
465
+ // `{type:"none"}` calls no tool, so the flag there would be a field the API rejects.
466
+ if (body.parallel_tool_calls === false && toolChoice.type !== "none") {
467
+ toolChoice.disable_parallel_tool_use = true;
468
+ }
469
+ out.tool_choice = toolChoice;
470
+ }
471
+ }
472
+ if (typeof body.temperature === "number")
473
+ out.temperature = body.temperature;
474
+ if (typeof body.top_p === "number")
475
+ out.top_p = body.top_p;
476
+ if (typeof body.stream === "boolean")
477
+ out.stream = body.stream;
478
+ return out;
479
+ }
480
+ //# sourceMappingURL=responses-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses-request.js","sourceRoot":"","sources":["../src/responses-request.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -12,7 +12,7 @@ import { reconstructFromSse } from "./sse.js";
12
12
  import { emitSse, emitSseTail, syntheticMessageId } from "./emitSse.js";
13
13
  import { repair, destructiveMatcher } from "./repair.js";
14
14
  import { CredentialWalkReshaper, HttpReshaper, } from "./reshaper.js";
15
- import { fetchBackend, fetchOpenAiFront, normalizeOpenAiErrorBody, parseRetryAfterMs, postHeaderBodyFailure, upstreamReportedModel, SERVED_BY_HEADER, POOL_ATTEMPTS_HEADER, UNKNOWN_REFUSAL_HEADER, DEGRADED_HEADER, PAID_HEADER, QUOTA_DEMOTED_HEADER, CREDENTIAL_HEADER, CREDENTIAL_ATTEMPTS_HEADER, HARD_CAP_HEADER, errorOrigin } from "./backend.js";
15
+ import { fetchBackend, fetchOpenAiFront, normalizeOpenAiErrorBody, parseRetryAfterMs, postHeaderBodyFailure, upstreamReportedModel, toolUseIdRewrites, SERVED_BY_HEADER, POOL_ATTEMPTS_HEADER, UNKNOWN_REFUSAL_HEADER, DEGRADED_HEADER, PAID_HEADER, QUOTA_DEMOTED_HEADER, CREDENTIAL_HEADER, CREDENTIAL_ATTEMPTS_HEADER, HARD_CAP_HEADER, errorOrigin } from "./backend.js";
16
16
  import { probeStreamForCommit } from "./stream-commit.js";
17
17
  import { ModelCatalog } from "./catalog.js";
18
18
  import { handleAdminRoutes } from "./routes/admin.js";
@@ -3037,7 +3037,14 @@ async function openAiFrontPath(res, credentialWalk, credentialTrace, ctx, h) {
3037
3037
  return;
3038
3038
  }
3039
3039
  const audit = recoveryAudit.value;
3040
- const log = baseLog(ctx.started, ctx.path, ctx.hadTools, streamed, upstream.status, audit?.validated ?? "skipped", target, attemptTrace.snapshot(), upstreamReportedModel(reportedModelSource), resolvedAttempt.credentialId);
3040
+ const log = {
3041
+ ...baseLog(ctx.started, ctx.path, ctx.hadTools, streamed, upstream.status, audit?.validated ?? "skipped", target, attemptTrace.snapshot(), upstreamReportedModel(reportedModelSource), resolvedAttempt.credentialId),
3042
+ // This front's TRANSLATED lane (anything but openai-kind + Chat) runs through
3043
+ // `fetchBackend`, so a Codex `/v1/responses` turn on an openai-kind target gets the same
3044
+ // id mint the Messages front does — and it must be reported on the SERVED turn, not only
3045
+ // on the mid-stream error path that already carried it.
3046
+ ...toolUseIdRewriteField(reportedModelSource),
3047
+ };
3041
3048
  h.logger.write(audit ? {
3042
3049
  ...log,
3043
3050
  toolUseCount: audit.toolUseCount,
@@ -3077,6 +3084,18 @@ async function openAiFrontPath(res, credentialWalk, credentialTrace, ctx, h) {
3077
3084
  respondAllCapped(res, h, { started: ctx.started, path: ctx.path, hadTools: ctx.hadTools, streamed: ctx.wantsStream }, "openai", pool429, attemptTrace.snapshot());
3078
3085
  }
3079
3086
  }
3087
+ /**
3088
+ * The metadata-only `tool_use` id-minting counter for this response, or nothing when the host's
3089
+ * own ids were already unique.
3090
+ *
3091
+ * Read from the response the backend adapter produced, and only at LOG time: on a stream the pass
3092
+ * runs while the body drains, so the figure is final once the request is over and would be a guess
3093
+ * any earlier. A count, never an id.
3094
+ */
3095
+ function toolUseIdRewriteField(source) {
3096
+ const rewrites = toolUseIdRewrites(source);
3097
+ return rewrites === undefined ? {} : { toolUseIdRewrites: rewrites };
3098
+ }
3080
3099
  /** Winning-candidate response metadata, shared by transparent and repair streaming paths. */
3081
3100
  function responseHeadersForTarget(backendRes, ctx) {
3082
3101
  const responseHeaders = filterResponseHeaders(backendRes.headers);
@@ -3202,6 +3221,7 @@ async function transparentPath(res, backendRes, timer, ctx, h) {
3202
3221
  h.logger.write({
3203
3222
  ...baseLog(ctx.started, ctx.path, ctx.hadTools, ctx.streamed, backendRes.status, validated, ctx.target, ctx.attempt.trace.snapshot(), upstreamReportedModel(ctx.reportedModelSource), ctx.attempt.resolvedAttempt.credentialId),
3204
3223
  toolUseCount, uncheckableCount, errorKinds,
3224
+ ...toolUseIdRewriteField(ctx.reportedModelSource),
3205
3225
  });
3206
3226
  }
3207
3227
  /** repair: route to the streaming or buffered variant. */
@@ -3395,6 +3415,7 @@ async function repairStreamingPath(res, backendRes, timer, ctx, h) {
3395
3415
  h.logger.write({
3396
3416
  ...baseLog(ctx.started, ctx.path, ctx.hadTools, true, backendRes.status, validated, ctx.target, ctx.attempt.trace.snapshot(), upstreamReportedModel(ctx.reportedModelSource), ctx.attempt.resolvedAttempt.credentialId),
3397
3417
  toolUseCount, uncheckableCount, errorKinds, repair: repairOutcome,
3418
+ ...toolUseIdRewriteField(ctx.reportedModelSource),
3398
3419
  });
3399
3420
  }
3400
3421
  /** repair, buffered (non-streamed JSON): buffer, validate; if invalid, reshape and re-emit. */
@@ -3520,6 +3541,7 @@ async function repairBufferedPath(res, backendRes, timer, ctx, h) {
3520
3541
  h.logger.write({
3521
3542
  ...baseLog(ctx.started, ctx.path, ctx.hadTools, ctx.streamed, backendRes.status, validated, ctx.target, ctx.attempt.trace.snapshot(), upstreamReportedModel(ctx.reportedModelSource), ctx.attempt.terminal === "succeeded" ? ctx.attempt.resolvedAttempt.credentialId : null),
3522
3543
  toolUseCount, uncheckableCount, errorKinds, repair: repairOutcome,
3544
+ ...toolUseIdRewriteField(ctx.reportedModelSource),
3523
3545
  });
3524
3546
  return null;
3525
3547
  }
@@ -3809,6 +3831,7 @@ function handleMidStreamError(res, e, started, path, hadTools, streamed, backend
3809
3831
  h.logger.write({
3810
3832
  ...baseLog(started, path, hadTools, streamed, backendStatus, "skipped", target, attempt.trace.snapshot(), upstreamReportedModel(reportedModelSource)),
3811
3833
  errorKinds: [MID_STREAM_ERROR_KIND],
3834
+ ...toolUseIdRewriteField(reportedModelSource),
3812
3835
  });
3813
3836
  }
3814
3837
  /** The client-facing description of a mid-transfer upstream failure. */