deepcodex 0.1.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.
Files changed (43) hide show
  1. package/.codex-plugin/plugin.json +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +178 -0
  4. package/bin/opencodex.js +49 -0
  5. package/config/desktop.json +9 -0
  6. package/config/pilot.json +16 -0
  7. package/config/worker.json +78 -0
  8. package/node_modules/smol-toml/LICENSE +24 -0
  9. package/node_modules/smol-toml/README.md +418 -0
  10. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  11. package/node_modules/smol-toml/dist/date.js +127 -0
  12. package/node_modules/smol-toml/dist/error.d.ts +38 -0
  13. package/node_modules/smol-toml/dist/error.js +63 -0
  14. package/node_modules/smol-toml/dist/extract.js +69 -0
  15. package/node_modules/smol-toml/dist/index.cjs +734 -0
  16. package/node_modules/smol-toml/dist/index.d.ts +43 -0
  17. package/node_modules/smol-toml/dist/index.js +33 -0
  18. package/node_modules/smol-toml/dist/parse.d.ts +36 -0
  19. package/node_modules/smol-toml/dist/parse.js +149 -0
  20. package/node_modules/smol-toml/dist/primitive.js +238 -0
  21. package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
  22. package/node_modules/smol-toml/dist/stringify.js +181 -0
  23. package/node_modules/smol-toml/dist/struct.js +179 -0
  24. package/node_modules/smol-toml/dist/util.d.ts +38 -0
  25. package/node_modules/smol-toml/dist/util.js +89 -0
  26. package/node_modules/smol-toml/package.json +68 -0
  27. package/package.json +47 -0
  28. package/prompts/worker.md +20 -0
  29. package/scripts/credentials.js +102 -0
  30. package/scripts/desktop.js +199 -0
  31. package/scripts/pilot-router.js +241 -0
  32. package/scripts/pilot.js +242 -0
  33. package/scripts/toml.js +6 -0
  34. package/scripts/worker.js +637 -0
  35. package/skills/delegate-flash/SKILL.md +63 -0
  36. package/vendor/codex-router/LICENSE +21 -0
  37. package/vendor/codex-router/deepseek-responses.js +55 -0
  38. package/vendor/codex-router/json-number-rewrite.js +58 -0
  39. package/vendor/codex-router/namespace-relay.js +4294 -0
  40. package/vendor/codex-router/sse-prefix.js +115 -0
  41. package/vendor/codex-router/subagent-completion.js +261 -0
  42. package/vendor/codex-router/tool-arguments.js +111 -0
  43. package/vendor/codex-router/tool-schema-root.js +1008 -0
@@ -0,0 +1,4294 @@
1
+ // Vendored from duolahypercho/codex-router at 63ec1f3602c28f2a28ccb7e9edaf7b4f7d191c6c.
2
+ // Source: src/namespace-relay.js; MIT license in LICENSE.
3
+ import { isUtf8 } from "node:buffer";
4
+ import { createHash } from "node:crypto";
5
+ import { Transform } from "node:stream";
6
+ import { isDeepStrictEqual } from "node:util";
7
+
8
+ import { jsonNumberIsStableForRewrite } from "./json-number-rewrite.js";
9
+ import { HeaderlessSseDetector } from "./sse-prefix.js";
10
+ import { coerceFunctionCallArguments } from "./tool-arguments.js";
11
+ import {
12
+ inlineForeignRefs,
13
+ declareSchemaTypes,
14
+ nonRecursiveToolSchema,
15
+ providerToolSchema,
16
+ } from "./tool-schema-root.js";
17
+ import {
18
+ buildInterruptAgentCall,
19
+ filterAlreadyInterrupted,
20
+ interruptTargetFromCall,
21
+ } from "./subagent-completion.js";
22
+
23
+ // The Codex client ships most of its toolset as `type: "namespace"` entries:
24
+ // the collaboration runtime, the app toolset (threads, automations,
25
+ // navigation), and every MCP server (node_repl, peekaboo, github, ...).
26
+ //
27
+ // LiteLLM's Responses -> Chat Completions bridge drops namespace tools, which
28
+ // is how the app sends all of those to the model. A routed chat-completions
29
+ // provider would therefore see none of them: no collaboration tools, no
30
+ // threads, and no `mcp__node_repl__js` -- the runtime the in-app browser and
31
+ // computer-use skills drive. This module is the one relay for all of it:
32
+ //
33
+ // 1. flattenNamespaceTools -- namespace entries -> plain
34
+ // `<namespace>__<tool>` functions the
35
+ // provider accepts
36
+ // 2. flattenNamespacedHistory -- stored calls renamed to the flattened
37
+ // form so the model's transcript matches
38
+ // its tool list
39
+ // 3. NamespaceToolCallTransform -- function calls coming back restored to
40
+ // the app's native `{name, namespace}`
41
+ // shape so the client dispatches them
42
+ //
43
+ // The router only relays definitions and results; it never executes an app
44
+ // tool itself. Namespace names themselves may contain the delimiter
45
+ // (`mcp__codex_apps__github`), so restoration always resolves through the map
46
+ // built from the exact tools that were flattened -- never by splitting names.
47
+ // The same map may also index a dotted inventory alias (`namespace.tool`) when
48
+ // a Responses-native model echoes that wire form (#611); that is still an
49
+ // exact inventory hit, not a split.
50
+
51
+ export const NAMESPACE_DELIMITER = "__";
52
+ const DEFAULT_FUNCTION_NAMESPACE = "functions";
53
+ const MCP_NAMESPACE_PREFIX = "mcp__";
54
+
55
+ // Metadata derived from the request's exact tool schema. Keeping it beside the
56
+ // Map in a WeakMap preserves the Map's public shape for existing callers while
57
+ // letting the response path validate model-generated overrides.
58
+ const SPAWN_AGENT_MODELS = new WeakMap();
59
+ const TOOL_SEARCH_RELAYS = new WeakMap();
60
+ const CUSTOM_TOOL_RELAYS = new WeakMap();
61
+ const CUSTOM_TOOL_CODECS = new WeakMap();
62
+ const FUNCTION_RELAYS = new WeakMap();
63
+ // Flattened custom definitions retain the exact native identity beside the
64
+ // object. A literal plain name containing `__` must never acquire a namespace.
65
+ const CUSTOM_TOOL_IDENTITIES = new WeakMap();
66
+ const NAME_ALIASES = new WeakMap();
67
+ const PLAIN_TOOL_NAMES = new WeakMap();
68
+ // A provider-facing function reference can retain the same spelling as a
69
+ // bridged custom/tool-search relay while a later-discovered ordinary function
70
+ // with that native name receives an alias. Object identity is the only honest
71
+ // discriminator after both shapes have become `type: "function"`; keep it
72
+ // request-local and garbage-collectable rather than guessing from the name.
73
+ const SPECIAL_FUNCTION_REFERENCES = new WeakSet();
74
+
75
+ const TOOL_SEARCH_FUNCTION_NAME = "tool_search";
76
+ const CUSTOM_TOOL_INPUT_PROPERTY = "input";
77
+
78
+ export function toolSearchRelayAvailable(namespaces) {
79
+ return TOOL_SEARCH_RELAYS.has(namespaces);
80
+ }
81
+
82
+ function providerFunctionName(tool) {
83
+ return tool?.name ?? tool?.function?.name;
84
+ }
85
+
86
+ function withProviderFunctionName(tool, name) {
87
+ if (tool?.function?.name !== undefined) {
88
+ return { ...tool, function: { ...tool.function, name } };
89
+ }
90
+ return { ...tool, name };
91
+ }
92
+
93
+ function nativeToolKey(namespace, name) {
94
+ return JSON.stringify([namespace ?? null, name]);
95
+ }
96
+
97
+ function boundedNameCandidate(wireName, identity, maxNameLength, attempt) {
98
+ const digest = createHash("sha256")
99
+ .update(`${identity}\0${attempt}`)
100
+ .digest("hex")
101
+ .slice(0, 12);
102
+ const suffix = `_${digest}`;
103
+ if (!Number.isFinite(maxNameLength)) return `${wireName}${suffix}`;
104
+ return `${wireName.slice(0, maxNameLength - suffix.length)}${suffix}`;
105
+ }
106
+
107
+ function assignProviderName(relay, identity, wireName, native, { forceAlias = false } = {}) {
108
+ const existing = relay.nativeToProvider.get(identity);
109
+ if (existing) return existing;
110
+
111
+ let providerName = wireName;
112
+ if (
113
+ forceAlias ||
114
+ wireName.length > relay.maxNameLength ||
115
+ relay.providerOwners.has(wireName)
116
+ ) {
117
+ let attempt = 0;
118
+ do {
119
+ providerName = boundedNameCandidate(
120
+ wireName,
121
+ identity,
122
+ relay.maxNameLength,
123
+ attempt,
124
+ );
125
+ attempt += 1;
126
+ } while (relay.providerOwners.has(providerName));
127
+ }
128
+
129
+ relay.nativeToProvider.set(identity, providerName);
130
+ relay.providerOwners.set(providerName, identity);
131
+ if (native && providerName !== wireName) relay.providerToNative.set(providerName, native);
132
+ if (native) {
133
+ if (native.namespace === undefined) relay.plainProviderNames.add(providerName);
134
+ if (!relay.wireOwners.has(wireName)) relay.wireOwners.set(wireName, new Set());
135
+ relay.wireOwners.get(wireName).add(identity);
136
+ }
137
+ return providerName;
138
+ }
139
+
140
+ function initialFunctionIdentities(tools) {
141
+ const identities = new Map();
142
+ if (!Array.isArray(tools)) return identities;
143
+ for (const tool of tools) {
144
+ if (tool?.type === "namespace" && typeof tool.name === "string" && Array.isArray(tool.tools)) {
145
+ for (const child of tool.tools) {
146
+ if (child?.type !== "function" || typeof child.name !== "string" || !child.name) continue;
147
+ const wireName = `${tool.name}${NAMESPACE_DELIMITER}${child.name}`;
148
+ const native = { namespace: tool.name, name: child.name };
149
+ identities.set(nativeToolKey(native.namespace, native.name), { wireName, native });
150
+ }
151
+ continue;
152
+ }
153
+ if (tool?.type !== "function") continue;
154
+ const name = providerFunctionName(tool);
155
+ if (typeof name !== "string" || !name) continue;
156
+ const native = { name };
157
+ identities.set(nativeToolKey(undefined, name), { wireName: name, native });
158
+ }
159
+ return identities;
160
+ }
161
+
162
+ function initializeNameAliases(namespaces, tools, maxNameLength, aliasCollisions = false) {
163
+ const bounded = Number.isInteger(maxNameLength) && maxNameLength >= 16;
164
+ if (!bounded && !aliasCollisions) return undefined;
165
+ const relay = {
166
+ maxNameLength: bounded ? maxNameLength : Infinity,
167
+ nativeToProvider: new Map(),
168
+ providerToNative: new Map(),
169
+ providerOwners: new Map(),
170
+ plainProviderNames: new Set(),
171
+ wireOwners: new Map(),
172
+ };
173
+ NAME_ALIASES.set(namespaces, relay);
174
+
175
+ const identities = initialFunctionIdentities(tools);
176
+ const wireCounts = new Map();
177
+ for (const { wireName } of identities.values()) {
178
+ wireCounts.set(wireName, (wireCounts.get(wireName) || 0) + 1);
179
+ }
180
+
181
+ // Reserve every legal, unique name first. Long names and native collisions
182
+ // are then assigned in stable identity order, so reordering an otherwise
183
+ // identical tool list cannot change the aliases sent to the provider.
184
+ const pending = [];
185
+ for (const [identity, entry] of [...identities].sort(([left], [right]) =>
186
+ left.localeCompare(right),
187
+ )) {
188
+ if (entry.wireName.length <= relay.maxNameLength && wireCounts.get(entry.wireName) === 1) {
189
+ assignProviderName(relay, identity, entry.wireName, entry.native);
190
+ } else {
191
+ pending.push([identity, entry]);
192
+ }
193
+ }
194
+ for (const [identity, entry] of pending) {
195
+ assignProviderName(relay, identity, entry.wireName, entry.native, { forceAlias: true });
196
+ }
197
+ return relay;
198
+ }
199
+
200
+ function providerNameForNative(namespaces, namespace, name) {
201
+ const relay = NAME_ALIASES.get(namespaces);
202
+ if (!relay) return namespace === undefined ? name : `${namespace}${NAMESPACE_DELIMITER}${name}`;
203
+ const identity = nativeToolKey(namespace, name);
204
+ const existing = relay.nativeToProvider.get(identity);
205
+ if (existing) return existing;
206
+ const wireName = namespace === undefined ? name : `${namespace}${NAMESPACE_DELIMITER}${name}`;
207
+ return assignProviderName(relay, identity, wireName, {
208
+ ...(namespace === undefined ? {} : { namespace }),
209
+ name,
210
+ });
211
+ }
212
+
213
+ function reserveSpecialProviderName(namespaces, identity, wireName) {
214
+ const relay = NAME_ALIASES.get(namespaces);
215
+ if (!relay) return wireName;
216
+ return assignProviderName(relay, identity, wireName);
217
+ }
218
+
219
+ function providerNameForWire(namespaces, wireName) {
220
+ const relay = NAME_ALIASES.get(namespaces);
221
+ if (!relay) return undefined;
222
+ const owners = relay.wireOwners.get(wireName);
223
+ if (!owners || owners.size !== 1) return undefined;
224
+ const [identity] = owners;
225
+ return relay.nativeToProvider.get(identity);
226
+ }
227
+
228
+ function providerVisibleToolNames(tools) {
229
+ const names = new Set();
230
+ if (!Array.isArray(tools)) return names;
231
+ for (const tool of tools) {
232
+ if (tool?.type === "namespace" && Array.isArray(tool.tools)) {
233
+ if (typeof tool.name === "string" && tool.name) names.add(tool.name);
234
+ for (const fn of tool.tools) {
235
+ if (fn?.name) names.add(`${tool.name}${NAMESPACE_DELIMITER}${fn.name}`);
236
+ }
237
+ continue;
238
+ }
239
+ const name = providerFunctionName(tool);
240
+ if (typeof name === "string" && name) names.add(name);
241
+ }
242
+ return names;
243
+ }
244
+
245
+ function availableCustomToolName(nativeName, visibleNames) {
246
+ if (!visibleNames.has(nativeName)) return nativeName;
247
+ const stem = `codex_custom_${nativeName}`;
248
+ if (!visibleNames.has(stem)) return stem;
249
+ let suffix = 1;
250
+ while (visibleNames.has(`${stem}_${suffix}`)) suffix += 1;
251
+ return `${stem}_${suffix}`;
252
+ }
253
+
254
+ // A custom tool's `format` is the model's only specification of the freeform
255
+ // payload it must emit: Codex ships apply_patch's V4A dialect as a lark
256
+ // grammar and describes the format nowhere else. A function tool has no
257
+ // grammar slot, so dropping `format.definition` on the way through would hand
258
+ // the model a bare "raw input" string and leave any model that has not
259
+ // memorised V4A emitting patches Codex cannot parse. Carry the definition in
260
+ // the bridged description instead -- that is the one field every
261
+ // function-tool provider does put in front of the model.
262
+ export function bridgedCustomToolDescription(tool) {
263
+ const sections = [];
264
+ if (typeof tool?.description === "string" && tool.description.trim()) {
265
+ sections.push(tool.description.trim());
266
+ }
267
+ const format = tool?.format;
268
+ const definition = typeof format?.definition === "string" ? format.definition.trim() : "";
269
+ if (definition) {
270
+ const syntax = typeof format?.syntax === "string" && format.syntax.trim()
271
+ ? format.syntax.trim()
272
+ : "grammar";
273
+ sections.push(
274
+ `The \`${CUSTOM_TOOL_INPUT_PROPERTY}\` string is freeform text, not JSON, and must parse ` +
275
+ `against this ${syntax} grammar:\n\n${definition}`,
276
+ );
277
+ }
278
+ return sections.length ? sections.join("\n\n") : undefined;
279
+ }
280
+
281
+ // OpenCode accepts ordinary JSON-schema function tools but rejects OpenAI's
282
+ // freeform `type: "custom"` definition. Codex exposes apply_patch only in that
283
+ // native form. Present the same raw-patch contract as one required string
284
+ // property, translate matching history and a forced native custom choice, and
285
+ // retain a request-local reverse map so the response path can restore the exact
286
+ // custom-tool shape Codex executes. An unrelated function or native namespace
287
+ // with the same name receives a collision-safe alias and is otherwise untouched.
288
+ export function bridgeCustomTools(
289
+ tools,
290
+ input,
291
+ namespaces,
292
+ toolChoice,
293
+ names = ["apply_patch"],
294
+ { maxNameLength, bridgeAll = false, codecs } = {},
295
+ ) {
296
+ if (!(namespaces instanceof Map)) {
297
+ return { tools, input, toolChoice, bridged: false };
298
+ }
299
+ if (Number.isInteger(maxNameLength) && !NAME_ALIASES.has(namespaces)) {
300
+ initializeNameAliases(namespaces, tools, maxNameLength);
301
+ }
302
+ const requested = new Set(names);
303
+ const shouldBridge = (name) => bridgeAll || requested.has(name);
304
+ // Stored custom calls may use the client's already-flat spelling. Resolve
305
+ // only exact live definitions, before registering history or forced choices,
306
+ // so they share the declaration's identity and bounded provider alias.
307
+ const customWireIdentities = new Map();
308
+ const otherWireNames = new Set();
309
+ for (const tool of Array.isArray(tools) ? tools : []) {
310
+ const native = CUSTOM_TOOL_IDENTITIES.get(tool);
311
+ if (native) {
312
+ const wireName = `${native.namespace}${NAMESPACE_DELIMITER}${native.name}`;
313
+ const previous = customWireIdentities.get(wireName);
314
+ customWireIdentities.set(wireName,
315
+ !customWireIdentities.has(wireName) ||
316
+ (previous?.namespace === native.namespace && previous?.name === native.name)
317
+ ? native : undefined);
318
+ } else {
319
+ const name = providerFunctionName(tool);
320
+ const original = NAME_ALIASES.get(namespaces)?.providerToNative.get(name);
321
+ otherWireNames.add(original?.namespace === undefined ? original?.name ?? name
322
+ : `${original.namespace}${NAMESPACE_DELIMITER}${original.name}`);
323
+ }
324
+ }
325
+ const identityOf = (reference) => CUSTOM_TOOL_IDENTITIES.get(reference) ||
326
+ (reference.namespace === undefined && !otherWireNames.has(reference.name)
327
+ ? customWireIdentities.get(reference.name) : undefined) || {
328
+ name: reference.name,
329
+ ...(typeof reference.namespace === "string" && reference.namespace
330
+ ? { namespace: reference.namespace } : {}),
331
+ };
332
+ const keyOf = (reference) => {
333
+ const native = identityOf(reference);
334
+ return nativeToolKey(native.namespace, native.name);
335
+ };
336
+ const nativeTools = new Map();
337
+ const remember = (reference) => {
338
+ const native = identityOf(reference);
339
+ if (typeof native.name !== "string" || !native.name) return;
340
+ const wireName = CUSTOM_TOOL_IDENTITIES.has(reference) ? reference.name
341
+ : native.namespace === undefined ? native.name
342
+ : providerNameForNative(namespaces, native.namespace, native.name);
343
+ if (shouldBridge(reference.name) || shouldBridge(wireName)) {
344
+ nativeTools.set(keyOf(reference), { native, wireName });
345
+ }
346
+ };
347
+ if (Array.isArray(tools)) {
348
+ for (const tool of tools) if (tool?.type === "custom") remember(tool);
349
+ }
350
+ if (Array.isArray(input)) {
351
+ for (const item of input) if (item?.type === "custom_tool_call") remember(item);
352
+ }
353
+ if (toolChoice?.type === "custom") remember(toolChoice);
354
+ if (toolChoice?.type === "allowed_tools" && Array.isArray(toolChoice.tools)) {
355
+ for (const choice of toolChoice.tools) {
356
+ if (choice?.type === "custom") remember(choice);
357
+ }
358
+ }
359
+ if (!nativeTools.size) return { tools, input, toolChoice, bridged: false };
360
+
361
+ // Console Go validates optional item ids on function-shaped history against
362
+ // the `fc` prefix. The rewrite used to keep `ctc_` / `ctco_` ids on the new
363
+ // type, which 400s every follow-up after apply_patch (#780). call_id still
364
+ // pairs the call with its result. A native-minted `fc…` id is kept.
365
+ const withoutIncompatibleFunctionItemId = (item) => {
366
+ if (typeof item?.id !== "string" || item.id.startsWith("fc")) return item;
367
+ const { id: _id, ...rest } = item;
368
+ return rest;
369
+ };
370
+
371
+ const ordinaryTools = Array.isArray(tools)
372
+ ? tools.filter((tool) => !(tool?.type === "custom" && nativeTools.has(keyOf(tool))))
373
+ : tools;
374
+ const visibleNames = providerVisibleToolNames(ordinaryTools);
375
+ const nativeToProvider = new Map();
376
+ const providerToNative = new Map();
377
+ const providerCodecs = new Map();
378
+ for (const [identity, { native, wireName }] of nativeTools) {
379
+ const availableName = availableCustomToolName(wireName, visibleNames);
380
+ const providerName = Number.isInteger(maxNameLength)
381
+ ? reserveSpecialProviderName(
382
+ namespaces,
383
+ native.namespace === undefined ? `custom:${native.name}` : `custom:${identity}`,
384
+ availableName,
385
+ )
386
+ : availableName;
387
+ visibleNames.add(providerName);
388
+ nativeToProvider.set(identity, providerName);
389
+ providerToNative.set(providerName, native.namespace === undefined ? native.name : native);
390
+ // History/forced choice alone never grants a codec-backed tool. It must
391
+ // be a plain native custom tool declared in this exact client request.
392
+ if (
393
+ native.namespace === undefined &&
394
+ codecs instanceof Map && codecs.has(native.name) &&
395
+ tools?.some((tool) => tool?.type === "custom" && keyOf(tool) === identity)
396
+ ) providerCodecs.set(providerName, codecs.get(native.name));
397
+ }
398
+ CUSTOM_TOOL_RELAYS.set(namespaces, providerToNative);
399
+ CUSTOM_TOOL_CODECS.set(namespaces, providerCodecs);
400
+
401
+ let changedTools = false;
402
+ const routedTools = Array.isArray(tools)
403
+ ? tools.map((tool) => {
404
+ const providerName =
405
+ tool?.type === "custom" ? nativeToProvider.get(keyOf(tool)) : undefined;
406
+ if (!providerName) return tool;
407
+ changedTools = true;
408
+ const codec = providerCodecs.get(providerName);
409
+ const description = codec ? codec.description(tool.description) : bridgedCustomToolDescription(tool);
410
+ return {
411
+ type: "function",
412
+ name: providerName,
413
+ ...(description ? { description } : {}),
414
+ parameters: codec?.parameters ?? {
415
+ type: "object",
416
+ properties: {
417
+ [CUSTOM_TOOL_INPUT_PROPERTY]: {
418
+ type: "string",
419
+ description: "The complete raw freeform input for this tool, preserved verbatim.",
420
+ },
421
+ },
422
+ required: [CUSTOM_TOOL_INPUT_PROPERTY],
423
+ additionalProperties: false,
424
+ },
425
+ };
426
+ })
427
+ : tools;
428
+
429
+ let routedToolChoice = toolChoice;
430
+ const providerChoiceName =
431
+ toolChoice?.type === "custom" ? nativeToProvider.get(keyOf(toolChoice)) : undefined;
432
+ if (providerChoiceName) {
433
+ const { namespace: _namespace, ...rest } = toolChoice;
434
+ routedToolChoice = { ...rest, type: "function", name: providerChoiceName };
435
+ SPECIAL_FUNCTION_REFERENCES.add(routedToolChoice);
436
+ } else if (toolChoice?.type === "allowed_tools" && Array.isArray(toolChoice.tools)) {
437
+ let changed = false;
438
+ const choices = toolChoice.tools.map((choice) => {
439
+ const providerName =
440
+ choice?.type === "custom" ? nativeToProvider.get(keyOf(choice)) : undefined;
441
+ if (!providerName) return choice;
442
+ changed = true;
443
+ const { namespace: _namespace, ...rest } = choice;
444
+ const routedChoice = { ...rest, type: "function", name: providerName };
445
+ SPECIAL_FUNCTION_REFERENCES.add(routedChoice);
446
+ return routedChoice;
447
+ });
448
+ if (changed) routedToolChoice = { ...toolChoice, tools: choices };
449
+ }
450
+ const changedToolChoice = routedToolChoice !== toolChoice;
451
+
452
+ if (!Array.isArray(input)) {
453
+ return {
454
+ tools: routedTools,
455
+ input,
456
+ toolChoice: routedToolChoice,
457
+ bridged: changedTools || changedToolChoice,
458
+ };
459
+ }
460
+ const bridgedCallIds = new Set();
461
+ let changedInput = false;
462
+ const routedInput = input.map((item) => {
463
+ const providerName =
464
+ item?.type === "custom_tool_call" ? nativeToProvider.get(keyOf(item)) : undefined;
465
+ if (providerName && typeof item.input === "string") {
466
+ const { type: _type, input: customInput, name: _name, namespace: _namespace, ...rest } = item;
467
+ if (typeof item.call_id === "string" && item.call_id) {
468
+ bridgedCallIds.add(item.call_id);
469
+ }
470
+ changedInput = true;
471
+ // A negotiated client adapter may carry its original provider argument
472
+ // string inside native input. Restore it verbatim, including failed JSON.
473
+ // History conversion does not register an executable response codec.
474
+ // Codecs are registered for plain native names only; a namespaced tool
475
+ // with the same bare name keeps the ordinary history envelope.
476
+ const historicalArguments = item.namespace === undefined
477
+ ? codecs?.get(item.name)?.encodeHistoryInput?.(customInput)
478
+ : undefined;
479
+ const routedCall = withoutIncompatibleFunctionItemId({
480
+ ...rest,
481
+ type: "function_call",
482
+ name: providerName,
483
+ arguments: historicalArguments ?? JSON.stringify({ [CUSTOM_TOOL_INPUT_PROPERTY]: customInput }),
484
+ });
485
+ SPECIAL_FUNCTION_REFERENCES.add(routedCall);
486
+ return routedCall;
487
+ }
488
+ if (
489
+ item?.type === "custom_tool_call_output" &&
490
+ typeof item.call_id === "string" &&
491
+ bridgedCallIds.has(item.call_id)
492
+ ) {
493
+ changedInput = true;
494
+ return withoutIncompatibleFunctionItemId({ ...item, type: "function_call_output" });
495
+ }
496
+ return item;
497
+ });
498
+ return {
499
+ tools: routedTools,
500
+ input: changedInput ? routedInput : input,
501
+ toolChoice: routedToolChoice,
502
+ bridged: changedTools || changedInput || changedToolChoice,
503
+ };
504
+ }
505
+
506
+ // Extra provider-visible names that restore to an already-bridged native custom
507
+ // tool. Used by the Grok edit facade to offer search_replace/write while Codex
508
+ // still executes apply_patch. Aliases never create a native tool that the
509
+ // client did not declare; they only add spellings onto an existing relay.
510
+ export function registerCustomToolRelays(namespaces, aliases) {
511
+ if (!(namespaces instanceof Map) || !Array.isArray(aliases) || aliases.length === 0) {
512
+ return false;
513
+ }
514
+ const relays = CUSTOM_TOOL_RELAYS.get(namespaces);
515
+ const codecs = CUSTOM_TOOL_CODECS.get(namespaces);
516
+ if (!(relays instanceof Map) || !(codecs instanceof Map)) return false;
517
+ let changed = false;
518
+ for (const alias of aliases) {
519
+ const providerName = typeof alias?.providerName === "string" ? alias.providerName.trim() : "";
520
+ const nativeName = typeof alias?.nativeName === "string" ? alias.nativeName.trim() : "";
521
+ if (!providerName || !nativeName) continue;
522
+ if (![...relays.values()].includes(nativeName)) continue;
523
+ relays.set(providerName, nativeName);
524
+ if (alias.codec) codecs.set(providerName, alias.codec);
525
+ changed = true;
526
+ }
527
+ return changed;
528
+ }
529
+
530
+ // Provider-visible function names that restore to an ordinary client function
531
+ // (not a custom tool). Used by the Grok read facade to offer read_file/grep
532
+ // while Codex still executes exec_command. Relays never invent a native
533
+ // function the client did not declare.
534
+ export function registerFunctionRelays(namespaces, relays) {
535
+ if (!(namespaces instanceof Map) || !Array.isArray(relays) || relays.length === 0) {
536
+ return false;
537
+ }
538
+ const existing = FUNCTION_RELAYS.get(namespaces) ?? new Map();
539
+ let changed = false;
540
+ for (const relay of relays) {
541
+ const providerName = typeof relay?.providerName === "string" ? relay.providerName.trim() : "";
542
+ const nativeName = typeof relay?.nativeName === "string" ? relay.nativeName.trim() : "";
543
+ if (!providerName || !nativeName || typeof relay.rewriteArguments !== "function") continue;
544
+ existing.set(providerName, {
545
+ nativeName,
546
+ ...(typeof relay.nativeNamespace === "string" && relay.nativeNamespace
547
+ ? { nativeNamespace: relay.nativeNamespace }
548
+ : {}),
549
+ rewriteArguments: relay.rewriteArguments,
550
+ maxArgumentBytes: Number.isInteger(relay.maxArgumentBytes) && relay.maxArgumentBytes > 0
551
+ ? relay.maxArgumentBytes
552
+ : 256 * 1024,
553
+ });
554
+ changed = true;
555
+ }
556
+ if (!changed) return false;
557
+ FUNCTION_RELAYS.set(namespaces, existing);
558
+ return true;
559
+ }
560
+
561
+ function availableToolSearchName(tools) {
562
+ const names = providerVisibleToolNames(tools);
563
+ if (!names.has(TOOL_SEARCH_FUNCTION_NAME)) return TOOL_SEARCH_FUNCTION_NAME;
564
+ let suffix = 1;
565
+ while (names.has(`codex_tool_search_${suffix}`)) suffix += 1;
566
+ return `codex_tool_search_${suffix}`;
567
+ }
568
+
569
+ function providerToolSearchDescription(description, providerName) {
570
+ if (typeof description !== "string") return undefined;
571
+ if (providerName === TOOL_SEARCH_FUNCTION_NAME) return description;
572
+ const rewritten = description.replaceAll(
573
+ `\`${TOOL_SEARCH_FUNCTION_NAME}\``,
574
+ `\`${providerName}\``,
575
+ );
576
+ return `${rewritten}\n\nFor this routed request, call \`${providerName}\` for deferred tool discovery; \`${TOOL_SEARCH_FUNCTION_NAME}\` is a separate ordinary function.`;
577
+ }
578
+
579
+ function schemaStringValues(schema, values = new Set()) {
580
+ if (!schema || typeof schema !== "object") return values;
581
+ if (typeof schema.const === "string") values.add(schema.const);
582
+ if (Array.isArray(schema.enum)) {
583
+ for (const value of schema.enum) if (typeof value === "string") values.add(value);
584
+ }
585
+ for (const keyword of ["anyOf", "oneOf", "allOf"]) {
586
+ if (!Array.isArray(schema[keyword])) continue;
587
+ for (const branch of schema[keyword]) schemaStringValues(branch, values);
588
+ }
589
+ return values;
590
+ }
591
+
592
+ // A fresh local thread inherits the routed session model when the caller did
593
+ // not choose one. An in-session subagent is always pinned to the routed parent:
594
+ // its model argument is generated by the parent model, not an independent user
595
+ // choice, and preserving a cross-provider value can silently cross a billing
596
+ // boundary. Follow-up messages intentionally keep the target thread's settings,
597
+ // and cloud tasks require model omission, so neither is rewritten.
598
+ export const SPAWN_MODEL_TOOLS = new Set(["create_thread", "spawn_agent"]);
599
+ const SPAWN_MODEL_NAMESPACES = new Map([
600
+ ["codex_app", new Set(["create_thread"])],
601
+ ["collaboration", new Set(["spawn_agent"])],
602
+ ]);
603
+
604
+ function isSpawnModelCall(item) {
605
+ if (!item || typeof item.name !== "string") return false;
606
+ // Flattened forms the router sends to chat-completions bridges, such as
607
+ // `codex_app__create_thread` and `collaboration__spawn_agent`.
608
+ for (const [namespace, names] of SPAWN_MODEL_NAMESPACES) {
609
+ const prefix = `${namespace}${NAMESPACE_DELIMITER}`;
610
+ if (item.name.startsWith(prefix)) return names.has(item.name.slice(prefix.length));
611
+ }
612
+ // Native namespace form openai-responses providers keep.
613
+ return SPAWN_MODEL_NAMESPACES.get(item.namespace)?.has(item.name) === true;
614
+ }
615
+
616
+ function isSubagentSpawnCall(item) {
617
+ if (!item || typeof item.name !== "string") return false;
618
+ if (item.namespace === "collaboration" && item.name === "spawn_agent") return true;
619
+ return item.namespace === undefined && item.name === `collaboration${NAMESPACE_DELIMITER}spawn_agent`;
620
+ }
621
+
622
+ // Inject the session model into local create_thread calls that omitted it, and
623
+ // into spawn_agent calls that carry no model of their own.
624
+ //
625
+ // An explicit subagent model wins. Codex ships the override as a plain string
626
+ // on the tool schema rather than a schema enum, and validates the value against
627
+ // its own advertised list, so anything that reaches here is the operator's own
628
+ // delegation choice and not a value the parent model invented. Rewriting it
629
+ // back to the routed parent discarded that choice and made a cross-provider
630
+ // subagent impossible from any routed session, which is the one thing
631
+ // `expose_spawn_agent_model_overrides = true` exists to allow.
632
+ //
633
+ // `sanitizeSpawnAgentModel` still drops a value outside the advertised set when
634
+ // a client version does ship one, so that guard is unaffected: the item it
635
+ // clears arrives here without a model and inherits the parent as before.
636
+ //
637
+ // `model` is the routed session's model (route.slug). Returns a rewritten item
638
+ // only when the call carries no model of its own; otherwise returns the item
639
+ // untouched.
640
+ export function injectSessionModelForSpawnCalls(item, model) {
641
+ if (!isSpawnModelCall(item)) return item;
642
+ if (typeof model !== "string" || !model) return item;
643
+ if (typeof item.arguments !== "string") return item;
644
+ if (!jsonArgumentsAreUnambiguous(item.arguments, { allowEmpty: true })) return item;
645
+ let args;
646
+ try {
647
+ args = JSON.parse(item.arguments);
648
+ } catch {
649
+ return item;
650
+ }
651
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return item;
652
+ if (args.model !== undefined && !isSubagentSpawnCall(item)) return item;
653
+ if (args.target?.type === "chatgptWorkCloud") return item;
654
+ if (typeof args.model === "string" && args.model) return item;
655
+ return { ...item, arguments: JSON.stringify({ ...args, model }) };
656
+ }
657
+
658
+ const MAX_JSON_CAPTURE_BYTES = 64 * 1024 * 1024;
659
+ const CAPTURE_PART_BYTES = 64 * 1024;
660
+ const INITIAL_SSE_CAPTURE_PART_BYTES = 1024;
661
+ const MAX_TRACKED_OUTPUT_ITEMS = 4096;
662
+ const MAX_TRACKED_STATE_BYTES = 8 * 1024 * 1024;
663
+ const TRACKED_STATE_FIXED_BYTES = 512;
664
+ // Before any semantic output, stop staging an undecided SSE frame before
665
+ // downstream response guards lose sight of their own byte ceilings. Once a
666
+ // namespace rewrite, suppression, or injection has committed the wire shape,
667
+ // a later terminal event may carry the complete response and therefore shares
668
+ // the non-streaming JSON capture bound. Crossing either phase's bound releases
669
+ // raw bytes before a commit and terminates the stream after one.
670
+ const MAX_SSE_FRAME_BYTES = 10 * 1024 * 1024;
671
+ const MAX_COMMITTED_SSE_FRAME_BYTES = MAX_JSON_CAPTURE_BYTES;
672
+ const LINE_FEED = 0x0a;
673
+ const CARRIAGE_RETURN = 0x0d;
674
+ const UTF8_BOM = Buffer.from([0xef, 0xbb, 0xbf]);
675
+ const SSE_EVENT_FIELD = Buffer.from("event", "ascii");
676
+ const SSE_DATA_FIELD = Buffer.from("data", "ascii");
677
+ const JSON_NUMBER_AT_OFFSET = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
678
+
679
+ function sseFieldValue(line, prefixLength) {
680
+ const value = line.slice(prefixLength);
681
+ // The SSE grammar removes one optional U+0020 after the colon. Tabs,
682
+ // repeated spaces, and trailing spaces are event data, not formatting.
683
+ return value.startsWith(" ") ? value.slice(1) : value;
684
+ }
685
+
686
+ function sseLineFieldValue(line, name) {
687
+ return line === name ? "" : sseFieldValue(line, name.length + 1);
688
+ }
689
+
690
+ function stringFingerprint(value) {
691
+ return {
692
+ length: value.length,
693
+ digest: createHash("sha256").update(value, "utf16le").digest(),
694
+ };
695
+ }
696
+
697
+ function canonicalJsonFingerprint(value) {
698
+ const hash = createHash("sha256");
699
+ let length = 0;
700
+ const update = (part, encoding = "utf8") => {
701
+ hash.update(part, encoding);
702
+ length += Buffer.byteLength(part, encoding);
703
+ };
704
+ const updateString = (marker, text) => {
705
+ update(`${marker}${text.length}:`, "ascii");
706
+ update(text, "utf16le");
707
+ };
708
+ const stack = [{ kind: "value", value }];
709
+ while (stack.length) {
710
+ const frame = stack.pop();
711
+ if (frame.kind === "array") {
712
+ if (frame.index >= frame.value.length) {
713
+ update("]", "ascii");
714
+ continue;
715
+ }
716
+ stack.push({ ...frame, index: frame.index + 1 });
717
+ stack.push({ kind: "value", value: frame.value[frame.index] });
718
+ continue;
719
+ }
720
+ if (frame.kind === "object") {
721
+ if (frame.index >= frame.keys.length) {
722
+ update("}", "ascii");
723
+ continue;
724
+ }
725
+ const key = frame.keys[frame.index];
726
+ stack.push({ ...frame, index: frame.index + 1 });
727
+ stack.push({ kind: "value", value: frame.value[key] });
728
+ stack.push({ kind: "key", value: key });
729
+ continue;
730
+ }
731
+ if (frame.kind === "key") {
732
+ updateString("k", frame.value);
733
+ continue;
734
+ }
735
+
736
+ const current = frame.value;
737
+ if (current === null) {
738
+ update("n", "ascii");
739
+ } else if (typeof current === "string") {
740
+ updateString("s", current);
741
+ } else if (typeof current === "number") {
742
+ const number = Number.isNaN(current)
743
+ ? "NaN"
744
+ : Object.is(current, -0)
745
+ ? "-0"
746
+ : String(current);
747
+ updateString("d", number);
748
+ } else if (typeof current === "boolean") {
749
+ update(current ? "t" : "f", "ascii");
750
+ } else if (Array.isArray(current)) {
751
+ update(`a${current.length}:[`, "ascii");
752
+ stack.push({ kind: "array", value: current, index: 0 });
753
+ } else if (typeof current === "object") {
754
+ const keys = Object.keys(current).sort();
755
+ update(`o${keys.length}:{`, "ascii");
756
+ stack.push({ kind: "object", value: current, keys, index: 0 });
757
+ } else {
758
+ throw new TypeError("Tool-search arguments contain a non-JSON value.");
759
+ }
760
+ }
761
+ return { length, digest: hash.digest() };
762
+ }
763
+
764
+ function fingerprintMatches(fingerprint, length, digest) {
765
+ return (
766
+ fingerprint.length === length &&
767
+ Buffer.isBuffer(digest) &&
768
+ fingerprint.digest.equals(digest)
769
+ );
770
+ }
771
+
772
+ function trackedStateBytes(state) {
773
+ let bytes = TRACKED_STATE_FIXED_BYTES;
774
+ for (const field of [
775
+ "itemId",
776
+ "callId",
777
+ "sourceType",
778
+ "sourceName",
779
+ "sourceNamespace",
780
+ "outputType",
781
+ "outputName",
782
+ "outputNamespace",
783
+ ]) {
784
+ if (typeof state[field] === "string") bytes += Buffer.byteLength(state[field], "utf8");
785
+ }
786
+ return bytes;
787
+ }
788
+
789
+ // JSON.parse deliberately accepts duplicate object members and keeps the last
790
+ // one. That is useful for ordinary application input, but unsafe at a rewrite
791
+ // boundary: the bytes can name one tool first and another tool last, while a
792
+ // downstream parser is free to make the opposite choice. Parsing also rounds
793
+ // unsafe integers and accepts overflowing exponents that stringify as null.
794
+ // Audit the complete JSON grammar before parsing, compare decoded key values
795
+ // so spellings such as `"name"` and `"\u006eame"` collide, and reject numeric
796
+ // values whose parse/stringify semantics are known to be lossy.
797
+ function jsonIsUnambiguousForRewrite(text, { allowLossyNumbers = false } = {}) {
798
+ if (typeof text !== "string") return false;
799
+ let offset = 0;
800
+
801
+ const skipWhitespace = () => {
802
+ while (
803
+ offset < text.length &&
804
+ (text[offset] === " " ||
805
+ text[offset] === "\t" ||
806
+ text[offset] === "\r" ||
807
+ text[offset] === "\n")
808
+ ) {
809
+ offset += 1;
810
+ }
811
+ };
812
+
813
+ const stringToken = () => {
814
+ if (text[offset] !== '"') return undefined;
815
+ const start = offset;
816
+ offset += 1;
817
+ while (offset < text.length) {
818
+ const code = text.charCodeAt(offset);
819
+ const character = text[offset];
820
+ if (character === '"') {
821
+ offset += 1;
822
+ return text.slice(start, offset);
823
+ }
824
+ if (code < 0x20) return undefined;
825
+ if (character !== "\\") {
826
+ offset += 1;
827
+ continue;
828
+ }
829
+ offset += 1;
830
+ const escape = text[offset];
831
+ if (escape === "u") {
832
+ const digits = text.slice(offset + 1, offset + 5);
833
+ if (digits.length !== 4 || !/^[0-9a-fA-F]{4}$/.test(digits)) return undefined;
834
+ offset += 5;
835
+ continue;
836
+ }
837
+ if (!['"', "\\", "/", "b", "f", "n", "r", "t"].includes(escape)) {
838
+ return undefined;
839
+ }
840
+ offset += 1;
841
+ }
842
+ return undefined;
843
+ };
844
+
845
+ const literal = (value) => {
846
+ if (!text.startsWith(value, offset)) return false;
847
+ offset += value.length;
848
+ return true;
849
+ };
850
+
851
+ const number = () => {
852
+ JSON_NUMBER_AT_OFFSET.lastIndex = offset;
853
+ const match = JSON_NUMBER_AT_OFFSET.exec(text);
854
+ if (!match) return false;
855
+ // Turn metadata is inspected only for string/bool namespace identities and
856
+ // is never reserialized here. Its unrelated numeric fields may therefore
857
+ // be lossy without changing the identity decision. Duplicate object keys
858
+ // remain forbidden in every mode: JSON.parse's last-wins behavior would
859
+ // otherwise let ambiguous metadata hide an ordinary-function collision.
860
+ if (!allowLossyNumbers && !jsonNumberIsStableForRewrite(match[0])) return false;
861
+ offset = JSON_NUMBER_AT_OFFSET.lastIndex;
862
+ return true;
863
+ };
864
+
865
+ const value = () => {
866
+ skipWhitespace();
867
+ const character = text[offset];
868
+ if (character === "{") return object();
869
+ if (character === "[") return array();
870
+ if (character === '"') return stringToken() !== undefined;
871
+ if (character === "t") return literal("true");
872
+ if (character === "f") return literal("false");
873
+ if (character === "n") return literal("null");
874
+ return number();
875
+ };
876
+
877
+ const object = () => {
878
+ offset += 1;
879
+ skipWhitespace();
880
+ if (text[offset] === "}") {
881
+ offset += 1;
882
+ return true;
883
+ }
884
+ const keys = new Set();
885
+ while (offset < text.length) {
886
+ skipWhitespace();
887
+ const token = stringToken();
888
+ if (token === undefined) return false;
889
+ let key;
890
+ try {
891
+ key = JSON.parse(token);
892
+ } catch {
893
+ return false;
894
+ }
895
+ if (keys.has(key)) return false;
896
+ keys.add(key);
897
+ skipWhitespace();
898
+ if (text[offset] !== ":") return false;
899
+ offset += 1;
900
+ if (!value()) return false;
901
+ skipWhitespace();
902
+ if (text[offset] === "}") {
903
+ offset += 1;
904
+ return true;
905
+ }
906
+ if (text[offset] !== ",") return false;
907
+ offset += 1;
908
+ }
909
+ return false;
910
+ };
911
+
912
+ const array = () => {
913
+ offset += 1;
914
+ skipWhitespace();
915
+ if (text[offset] === "]") {
916
+ offset += 1;
917
+ return true;
918
+ }
919
+ while (offset < text.length) {
920
+ if (!value()) return false;
921
+ skipWhitespace();
922
+ if (text[offset] === "]") {
923
+ offset += 1;
924
+ return true;
925
+ }
926
+ if (text[offset] !== ",") return false;
927
+ offset += 1;
928
+ }
929
+ return false;
930
+ };
931
+
932
+ try {
933
+ if (!value()) return false;
934
+ skipWhitespace();
935
+ return offset === text.length;
936
+ } catch {
937
+ // Excessive nesting and any other scanner failure are ambiguity, not
938
+ // permission to fall back to JSON.parse's lossy interpretation.
939
+ return false;
940
+ }
941
+ }
942
+
943
+ export function jsonArgumentsAreUnambiguous(value, { allowEmpty = false } = {}) {
944
+ if (typeof value !== "string") return true;
945
+ if (allowEmpty && value.trim() === "") return true;
946
+ return jsonIsUnambiguousForRewrite(value);
947
+ }
948
+
949
+ // Repair one tool's parameter root, or return it untouched. Providers reject a
950
+ // union or nullable-object root by name -- xAI, DeepSeek V4, and the
951
+ // opencode-go Responses surface all do -- and none of them care whether the
952
+ // tool arrived inside a namespace. `providerToolSchema` returns anything it
953
+ // does not recognize by identity, so an ordinary root costs one call and no
954
+ // copy.
955
+ export function repairToolSchemaRoot(
956
+ tool,
957
+ { nonRecursive = false, inlineForeignRefs: inlineRefs = false, declareTypes = false } = {},
958
+ ) {
959
+ // Moonshot alone rejects a `$ref` that does not point into `#/$defs/` or one
960
+ // that carries sibling keywords, so only the route that asks for it pays the
961
+ // inlining -- every other provider keeps the exact wire payload it has today.
962
+ // The normalizer returns a clean schema by identity, so an ordinary toolset
963
+ // is not copied.
964
+ const relaySchema = (schema) => {
965
+ const repaired = providerToolSchema(schema);
966
+ const inlined = inlineRefs ? inlineForeignRefs(repaired) : repaired;
967
+ // After inlining, so a type is declared on the branches a `$ref` brought in
968
+ // rather than only on the reference that pointed at them.
969
+ return declareTypes ? declareSchemaTypes(inlined) : inlined;
970
+ };
971
+
972
+ // Preserve the established shared-provider behavior byte-for-byte. Native
973
+ // namespace traversal and inputSchema rewriting belong only to the OpenCode
974
+ // compatibility pass below; other providers keep the original root repair.
975
+ if (!nonRecursive) {
976
+ const parameters = tool?.function?.parameters ?? tool?.parameters;
977
+ if (parameters === undefined) return tool;
978
+ const repaired = relaySchema(parameters);
979
+ if (repaired === parameters) return tool;
980
+ return tool.function
981
+ ? { ...tool, function: { ...tool.function, parameters: repaired } }
982
+ : { ...tool, parameters: repaired };
983
+ }
984
+
985
+ if (tool?.type === "namespace" && Array.isArray(tool.tools)) {
986
+ let changed = false;
987
+ const children = tool.tools.map((child) => {
988
+ const repaired = repairToolSchemaRoot(child, {
989
+ nonRecursive,
990
+ inlineForeignRefs: inlineRefs,
991
+ });
992
+ if (repaired !== child) changed = true;
993
+ return repaired;
994
+ });
995
+ return changed ? { ...tool, tools: children } : tool;
996
+ }
997
+
998
+ let repairedTool = tool;
999
+ let changed = false;
1000
+ const repair = (schema) => nonRecursiveToolSchema(relaySchema(schema));
1001
+
1002
+ if (tool?.function?.parameters !== undefined) {
1003
+ const parameters = repair(tool.function.parameters);
1004
+ if (parameters !== tool.function.parameters) {
1005
+ repairedTool = {
1006
+ ...repairedTool,
1007
+ function: { ...repairedTool.function, parameters },
1008
+ };
1009
+ changed = true;
1010
+ }
1011
+ }
1012
+ // Flattened namespace children deliberately carry both inputSchema (the
1013
+ // client-native declaration) and parameters (the Chat Completions alias).
1014
+ // Repair both: choosing inputSchema first would leave the provider-facing
1015
+ // parameters recursive on Ox even though the Responses branch was fixed.
1016
+ for (const field of ["parameters", "inputSchema"]) {
1017
+ if (tool?.[field] === undefined) continue;
1018
+ const schema = repair(tool[field]);
1019
+ if (schema === tool[field]) continue;
1020
+ repairedTool = { ...repairedTool, [field]: schema };
1021
+ changed = true;
1022
+ }
1023
+ return changed ? repairedTool : tool;
1024
+ }
1025
+
1026
+ // Array form for callers that relay tools without flattening them --
1027
+ // Responses-native providers keep the namespace shape but still need a root
1028
+ // their upstream accepts. Returns the original array when nothing needed
1029
+ // repair, so the common request is not copied.
1030
+ export function repairToolSchemaRoots(tools, options) {
1031
+ if (!Array.isArray(tools)) return tools;
1032
+ let changed = false;
1033
+ const repaired = tools.map((tool) => {
1034
+ const next = repairToolSchemaRoot(tool, options);
1035
+ if (next !== tool) changed = true;
1036
+ return next;
1037
+ });
1038
+ return changed ? repaired : tools;
1039
+ }
1040
+
1041
+ // OpenCode currently accepts search_content_types only on the legacy
1042
+ // web_search_preview shape. Codex sends the field on web_search, so remove only
1043
+ // that unsupported extension and preserve every other search-tool option.
1044
+ export function stripSearchContentTypes(tools) {
1045
+ if (!Array.isArray(tools)) return tools;
1046
+ let changed = false;
1047
+ const stripped = tools.map((tool) => {
1048
+ if (tool?.type !== "web_search" || !("search_content_types" in tool)) return tool;
1049
+ changed = true;
1050
+ const { search_content_types: _unsupported, ...rest } = tool;
1051
+ return rest;
1052
+ });
1053
+ return changed ? stripped : tools;
1054
+ }
1055
+
1056
+ // agent_message is a Codex collaboration input item, not part of the public
1057
+ // Responses schema OpenCode implements. The readable handoff has already been
1058
+ // recovered before this boundary, so keep its content and present it as the
1059
+ // equivalent user message strict compatible endpoints accept.
1060
+ export function agentMessagesAsUserMessages(input) {
1061
+ if (!Array.isArray(input)) return input;
1062
+ let changed = false;
1063
+ const converted = input.map((item) => {
1064
+ if (item?.type !== "agent_message" || !Array.isArray(item.content)) return item;
1065
+ changed = true;
1066
+ return { type: "message", role: "user", content: item.content };
1067
+ });
1068
+ return changed ? converted : input;
1069
+ }
1070
+
1071
+ // Codex can inherit an image with detail:"original" from the parent thread.
1072
+ // OpenCode rejects that OpenAI-only hint but accepts the same image as auto.
1073
+ // Preserve the image bytes and surrounding transcript; change only the hint.
1074
+ export function downgradeOriginalImageDetail(input) {
1075
+ if (!Array.isArray(input)) return input;
1076
+ let changed = false;
1077
+ const converted = input.map((item) => {
1078
+ if (!Array.isArray(item?.content)) return item;
1079
+ let contentChanged = false;
1080
+ const content = item.content.map((part) => {
1081
+ if (part?.type !== "input_image" || part.detail !== "original") return part;
1082
+ changed = true;
1083
+ contentChanged = true;
1084
+ return { ...part, detail: "auto" };
1085
+ });
1086
+ return contentChanged ? { ...item, content } : item;
1087
+ });
1088
+ return changed ? converted : input;
1089
+ }
1090
+
1091
+ const REASONING_ENCRYPTED_INCLUDE = "reasoning.encrypted_content";
1092
+
1093
+ function reasoningItemHasVisibleText(item) {
1094
+ if (typeof item?.summary === "string" && item.summary) return true;
1095
+ if (
1096
+ Array.isArray(item?.summary) &&
1097
+ item.summary.some((part) => typeof part?.text === "string" && part.text)
1098
+ ) {
1099
+ return true;
1100
+ }
1101
+ if (typeof item?.content === "string" && item.content) return true;
1102
+ if (
1103
+ Array.isArray(item?.content) &&
1104
+ item.content.some((part) => typeof part?.text === "string" && part.text)
1105
+ ) {
1106
+ return true;
1107
+ }
1108
+ return false;
1109
+ }
1110
+
1111
+ // OpenCode Zen's anonymous Muse Contributor Free Responses route is a Console
1112
+ // proxy. Meta issues reasoning `encrypted_content` to Console's caller, not to
1113
+ // this router. Replaying it 400s with "reasoning `encrypted_content` was not
1114
+ // issued to this caller". Drop the continuation token; keep any summary text.
1115
+ // Paid Zen/Go keep a stable key and stay outside this exact-route gate.
1116
+ export function stripUnissuedEncryptedReasoning(input) {
1117
+ if (!Array.isArray(input)) return input;
1118
+ let changed = false;
1119
+ const next = [];
1120
+ for (const item of input) {
1121
+ if (item?.type !== "reasoning" || item.encrypted_content === undefined) {
1122
+ next.push(item);
1123
+ continue;
1124
+ }
1125
+ changed = true;
1126
+ const { encrypted_content: _encryptedContent, ...rest } = item;
1127
+ if (reasoningItemHasVisibleText(rest)) next.push(rest);
1128
+ }
1129
+ return changed ? next : input;
1130
+ }
1131
+
1132
+ export function stripUnissuedEncryptedReasoningInclude(include) {
1133
+ if (!Array.isArray(include)) return include;
1134
+ const next = include.filter((entry) => entry !== REASONING_ENCRYPTED_INCLUDE);
1135
+ if (next.length === include.length) return include;
1136
+ return next.length > 0 ? next : undefined;
1137
+ }
1138
+
1139
+ function flattenNamespaceChild(namespace, fn, providerName) {
1140
+ const clientSchema = fn.parameters ?? fn.inputSchema;
1141
+ const parameters =
1142
+ clientSchema === undefined ? undefined : providerToolSchema(clientSchema);
1143
+ const flattened = {
1144
+ ...fn,
1145
+ name: providerName ?? `${namespace}${NAMESPACE_DELIMITER}${fn.name}`,
1146
+ ...(parameters === undefined ? {} : { parameters }),
1147
+ };
1148
+ if (fn.type === "custom") {
1149
+ CUSTOM_TOOL_IDENTITIES.set(flattened, { namespace, name: fn.name });
1150
+ }
1151
+ return flattened;
1152
+ }
1153
+
1154
+ // Flatten every namespace entry into plain functions named
1155
+ // `<namespace>__<tool>`. Returns the set of namespaces that were flattened
1156
+ // (name -> tool names) so callers can rename history and restore calls.
1157
+ export function flattenNamespaceTools(
1158
+ tools,
1159
+ { bridgeToolSearch = true, maxNameLength, aliasCollisions = false } = {},
1160
+ ) {
1161
+ if (!Array.isArray(tools)) return { tools, flattened: false, namespaces: new Map() };
1162
+ const flattened = [];
1163
+ const namespaces = new Map();
1164
+ const plainToolNames = new Set();
1165
+ PLAIN_TOOL_NAMES.set(namespaces, plainToolNames);
1166
+ initializeNameAliases(namespaces, tools, maxNameLength, aliasCollisions);
1167
+ const spawnAgentModels = new Set();
1168
+ const toolSearchName = bridgeToolSearch
1169
+ ? reserveSpecialProviderName(
1170
+ namespaces,
1171
+ "tool-search",
1172
+ availableToolSearchName(tools),
1173
+ )
1174
+ : undefined;
1175
+ let toolSearchRelay;
1176
+ let changed = false;
1177
+ for (const tool of tools) {
1178
+ // Codex registers deferred tools client-side and exposes this native
1179
+ // control so the model can search them on demand. Chat-completions
1180
+ // providers reject the native type, so present the same request-local
1181
+ // capability as an ordinary function and restore its calls on the
1182
+ // response path. Only the native client-executed shape enables the relay;
1183
+ // an unrelated function named `tool_search` never does. When such a plain
1184
+ // function already exists, use a deterministic alias so neither call can
1185
+ // hijack the other.
1186
+ if (tool?.type === "tool_search") {
1187
+ changed = true;
1188
+ if (bridgeToolSearch && tool.execution === "client" && !toolSearchRelay) {
1189
+ const parameters =
1190
+ tool.parameters === undefined ? undefined : providerToolSchema(tool.parameters);
1191
+ const description = providerToolSearchDescription(tool.description, toolSearchName);
1192
+ flattened.push({
1193
+ type: "function",
1194
+ name: toolSearchName,
1195
+ ...(description === undefined ? {} : { description }),
1196
+ ...(parameters === undefined ? {} : { parameters }),
1197
+ });
1198
+ toolSearchRelay = { providerName: toolSearchName };
1199
+ }
1200
+ continue;
1201
+ }
1202
+ if (tool?.type === "namespace" && Array.isArray(tool.tools)) {
1203
+ const names = new Set();
1204
+ for (const fn of tool.tools) {
1205
+ if (!fn?.name) continue;
1206
+ // Codex names function schemas `inputSchema`, while LiteLLM's
1207
+ // Responses -> Chat Completions adapter reads only `parameters`.
1208
+ // Without this alias every flattened namespace child reaches the
1209
+ // provider as an empty object schema, so MCP calls cannot receive the
1210
+ // arguments their server requires. Keep inputSchema too: it is the
1211
+ // client's native representation and responses-native routes retain
1212
+ // it untouched.
1213
+ //
1214
+ // Strict upstreams (Moonshot/Kimi, the xAI CLI proxy) reject the whole
1215
+ // request -- not the one tool -- over a union-rooted parameter schema or
1216
+ // an enum literal that contradicts its declared type. Codex's own
1217
+ // `codex_app__automation_update` ships a `oneOf` root, so a session that
1218
+ // never touches automations still dies on its first message. Normalize
1219
+ // only the provider-facing copy; `inputSchema` stays exactly as the
1220
+ // client sent it.
1221
+ flattened.push(
1222
+ flattenNamespaceChild(
1223
+ tool.name,
1224
+ fn,
1225
+ providerNameForNative(namespaces, tool.name, fn.name),
1226
+ ),
1227
+ );
1228
+ names.add(fn.name);
1229
+ if (tool.name === "collaboration" && fn.name === "spawn_agent") {
1230
+ const schema = fn.parameters ?? fn.inputSchema;
1231
+ schemaStringValues(schema?.properties?.model, spawnAgentModels);
1232
+ }
1233
+ }
1234
+ if (names.size > 0) {
1235
+ namespaces.set(tool.name, names);
1236
+ changed = true;
1237
+ }
1238
+ continue;
1239
+ }
1240
+ // A plain function tool needs the same repair as a namespaced one. The
1241
+ // rejections are the provider's, not the namespace's: DeepSeek V4 Flash and
1242
+ // Pro both 400 a `type: ["object","null"]` root with "schema must be a JSON
1243
+ // Schema of 'type: \"object\"'", and xAI rejects a union root the same way,
1244
+ // whether the tool arrived inside a namespace or on its own. Repairing only
1245
+ // the flattened children left every client-declared tool to fail on the
1246
+ // provider that objects. `providerToolSchema` returns anything it does not
1247
+ // recognize unchanged, so a tool with an ordinary root is not copied.
1248
+ let repaired = repairToolSchemaRoot(tool);
1249
+ const name = tool?.type === "function" ? providerFunctionName(repaired) : undefined;
1250
+ if (typeof name === "string" && name) {
1251
+ const providerName = providerNameForNative(namespaces, undefined, name);
1252
+ if (providerName !== name) repaired = withProviderFunctionName(repaired, providerName);
1253
+ plainToolNames.add(providerName);
1254
+ }
1255
+ if (repaired !== tool) changed = true;
1256
+ flattened.push(repaired);
1257
+ }
1258
+ if (spawnAgentModels.size > 0) SPAWN_AGENT_MODELS.set(namespaces, spawnAgentModels);
1259
+ if (toolSearchRelay) TOOL_SEARCH_RELAYS.set(namespaces, toolSearchRelay);
1260
+ return { tools: flattened, flattened: changed, namespaces };
1261
+ }
1262
+
1263
+ // Codex may flatten native function/custom names before sending a custom-provider
1264
+ // request. Recover only declarations backed by its canonical turn metadata, then
1265
+ // let the normal namespace pipeline handle aliases, schemas, custom tools and
1266
+ // response restoration. Metadata alone must never add an executable tool.
1267
+ export function restorePreflattenedToolNamespaces(tools, clientMetadata) {
1268
+ if (!Array.isArray(tools)) return tools;
1269
+ const encoded = clientMetadata?.["x-codex-turn-metadata"];
1270
+ if (typeof encoded !== "string" ||
1271
+ !jsonIsUnambiguousForRewrite(encoded, { allowLossyNumbers: true })) return tools;
1272
+ let metadata;
1273
+ try {
1274
+ metadata = JSON.parse(encoded);
1275
+ } catch {
1276
+ return tools;
1277
+ }
1278
+ const inventory = metadata?.tool_namespaces_info;
1279
+ if (!plainObject(inventory)) return tools;
1280
+
1281
+ // Default-namespace entries are already plain tools. Never reinterpret a
1282
+ // literal name containing __ merely because another namespace claims it.
1283
+ const ordinaryNames = new Set();
1284
+ if (Object.hasOwn(inventory, DEFAULT_FUNCTION_NAMESPACE)) {
1285
+ const ordinary = inventory[DEFAULT_FUNCTION_NAMESPACE];
1286
+ if (ordinary?.name !== DEFAULT_FUNCTION_NAMESPACE ||
1287
+ !plainObject(ordinary.functions)) return tools;
1288
+ for (const [name, info] of Object.entries(ordinary.functions)) {
1289
+ if (!name || !plainObject(info) || info.name !== name) return tools;
1290
+ ordinaryNames.add(name);
1291
+ }
1292
+ }
1293
+
1294
+ const candidates = new Map();
1295
+ const remember = (wireName, native) => {
1296
+ if (!candidates.has(wireName)) {
1297
+ candidates.set(wireName, native);
1298
+ return;
1299
+ }
1300
+ const previous = candidates.get(wireName);
1301
+ if (!previous || previous.namespace !== native.namespace ||
1302
+ previous.name !== native.name) candidates.set(wireName, undefined);
1303
+ };
1304
+ for (const [namespace, namespaceInfo] of Object.entries(inventory)) {
1305
+ if (!namespace || namespace === DEFAULT_FUNCTION_NAMESPACE ||
1306
+ namespaceInfo?.name !== namespace || !plainObject(namespaceInfo.functions)) continue;
1307
+ for (const [name, info] of Object.entries(namespaceInfo.functions)) {
1308
+ const mcp = info?.source?.kind === "mcp" &&
1309
+ typeof info.source.server_name === "string" && info.source.server_name &&
1310
+ namespace === `${MCP_NAMESPACE_PREFIX}${info.source.server_name}`;
1311
+ if (!name || info?.name !== name || info.direct !== true ||
1312
+ (!mcp && info.source?.kind !== "harness")) continue;
1313
+ remember(`${namespace}${NAMESPACE_DELIMITER}${name}`, { namespace, name });
1314
+ }
1315
+ }
1316
+
1317
+ const existingNamespaces = new Map();
1318
+ const existingIdentities = new Set();
1319
+ const repeatedNamespaces = new Set();
1320
+ const declarations = new Map();
1321
+ for (const tool of tools) {
1322
+ if (tool?.type === "namespace" && Array.isArray(tool.tools)) {
1323
+ if (existingNamespaces.has(tool.name)) repeatedNamespaces.add(tool.name);
1324
+ existingNamespaces.set(tool.name, tool);
1325
+ for (const child of tool.tools) {
1326
+ if (typeof child?.name !== "string" || !child.name) continue;
1327
+ remember(`${tool.name}${NAMESPACE_DELIMITER}${child.name}`,
1328
+ { namespace: tool.name, name: child.name });
1329
+ existingIdentities.add(nativeToolKey(tool.name, child.name));
1330
+ }
1331
+ } else if (tool?.type === "function" || tool?.type === "custom") {
1332
+ const name = providerFunctionName(tool);
1333
+ // Metadata has no function/custom discriminator. Repeated wire names
1334
+ // cannot be safely assigned to one declaration, even across those types.
1335
+ declarations.set(name, declarations.has(name) ? undefined : tool);
1336
+ }
1337
+ }
1338
+
1339
+ const recovered = new Map();
1340
+ const groups = new Map();
1341
+ for (const [wireName, native] of candidates) {
1342
+ const tool = declarations.get(wireName);
1343
+ if (!native || !tool || ordinaryNames.has(wireName) ||
1344
+ repeatedNamespaces.has(native.namespace) ||
1345
+ existingIdentities.has(nativeToolKey(native.namespace, native.name))) continue;
1346
+ recovered.set(tool, native);
1347
+ if (!groups.has(native.namespace)) {
1348
+ const existing = existingNamespaces.get(native.namespace);
1349
+ groups.set(native.namespace, existing
1350
+ ? { ...existing, tools: [] }
1351
+ : { type: "namespace", name: native.namespace, tools: [] });
1352
+ }
1353
+ }
1354
+ if (!recovered.size) return tools;
1355
+
1356
+ // One declaration per namespace lets app expansion prefer the complete client
1357
+ // schema without injecting the same deferred snapshot into multiple fragments.
1358
+ const restored = [];
1359
+ const emitted = new Set();
1360
+ for (const tool of tools) {
1361
+ const native = recovered.get(tool);
1362
+ const group = native ? groups.get(native.namespace)
1363
+ : tool?.type === "namespace" ? groups.get(tool.name) : undefined;
1364
+ if (!group) {
1365
+ restored.push(tool);
1366
+ continue;
1367
+ }
1368
+ if (!emitted.has(group)) {
1369
+ emitted.add(group);
1370
+ restored.push(group);
1371
+ }
1372
+ if (native) {
1373
+ const { function: definition, ...rest } = tool;
1374
+ group.tools.push({ ...rest, ...definition, name: native.name });
1375
+ } else {
1376
+ group.tools.push(...tool.tools);
1377
+ }
1378
+ }
1379
+ return restored;
1380
+ }
1381
+
1382
+ function plainObject(value) {
1383
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
1384
+ }
1385
+
1386
+ function validToolSearchHistoryArguments(value) {
1387
+ const argumentsObject = plainObject(value);
1388
+ if (!argumentsObject || typeof argumentsObject.query !== "string") return false;
1389
+ if (!argumentsObject.query.trim()) return false;
1390
+ const { limit } = argumentsObject;
1391
+ return limit === undefined || (Number.isInteger(limit) && limit > 0);
1392
+ }
1393
+
1394
+ function discoveredProviderTools(toolSpecs, namespaces) {
1395
+ if (!Array.isArray(toolSpecs)) return [];
1396
+ const discovered = [];
1397
+ for (const tool of toolSpecs) {
1398
+ if (tool?.type === "namespace" && typeof tool.name === "string" && Array.isArray(tool.tools)) {
1399
+ for (const fn of tool.tools) {
1400
+ if (fn?.type !== "function" || !fn.name) continue;
1401
+ discovered.push({
1402
+ tool: flattenNamespaceChild(
1403
+ tool.name,
1404
+ fn,
1405
+ providerNameForNative(namespaces, tool.name, fn.name),
1406
+ ),
1407
+ native: { namespace: tool.name, name: fn.name },
1408
+ nativeName: fn.name,
1409
+ identity: nativeToolKey(tool.name, fn.name),
1410
+ });
1411
+ }
1412
+ continue;
1413
+ }
1414
+ if (tool?.type !== "function" || !providerFunctionName(tool)) continue;
1415
+ const nativeName = providerFunctionName(tool);
1416
+ const providerName = providerNameForNative(namespaces, undefined, nativeName);
1417
+ let providerTool = repairToolSchemaRoot(tool);
1418
+ if (providerName !== nativeName) {
1419
+ providerTool = withProviderFunctionName(providerTool, providerName);
1420
+ }
1421
+ discovered.push({
1422
+ tool: providerTool,
1423
+ nativeName,
1424
+ identity: nativeToolKey(undefined, nativeName),
1425
+ });
1426
+ }
1427
+ return discovered;
1428
+ }
1429
+
1430
+ function addDiscoveredNamespace(namespaces, native) {
1431
+ if (!native) return;
1432
+ let names = namespaces.get(native.namespace);
1433
+ if (!names) {
1434
+ names = new Set();
1435
+ namespaces.set(native.namespace, names);
1436
+ }
1437
+ names.add(native.name);
1438
+ }
1439
+
1440
+ export class ToolSearchHistoryCapacityError extends Error {
1441
+ constructor({ available, required }) {
1442
+ super(
1443
+ `Stored tool_search history references ${required} discovered tools, but only ` +
1444
+ `${available} provider tool slots remain.`,
1445
+ );
1446
+ this.name = "ToolSearchHistoryCapacityError";
1447
+ this.available = available;
1448
+ this.required = required;
1449
+ }
1450
+ }
1451
+
1452
+ // A native tool_search output changes what the model may call on the next
1453
+ // turn. The Responses API understands that special history item directly;
1454
+ // LiteLLM's chat-completions bridge does not. Translate matched call/output
1455
+ // pairs into ordinary function history and add the returned definitions to
1456
+ // this request's provider-facing tool list. A model switch may leave no live
1457
+ // search relay; in that explicitly enabled mode, preserve the definitions but
1458
+ // drop the now-unusable native control pair. Live top-level schemas win on a
1459
+ // name collision. Native items that do not form one unique, ordered,
1460
+ // well-formed pair are dropped: a chat-completions provider cannot consume
1461
+ // them, and forwarding one would make the transcript promise unavailable
1462
+ // tools.
1463
+ export function flattenToolSearchHistory(
1464
+ input,
1465
+ tools,
1466
+ namespaces,
1467
+ { maxTools = Infinity, recoverWithoutRelay = false, toolChoice } = {},
1468
+ ) {
1469
+ const relay = TOOL_SEARCH_RELAYS.get(namespaces);
1470
+ if (!Array.isArray(input)) {
1471
+ return { input, tools, flattened: false };
1472
+ }
1473
+ if (!Array.isArray(tools)) {
1474
+ const routedInput = input.filter(
1475
+ (item) => item?.type !== "tool_search_call" && item?.type !== "tool_search_output",
1476
+ );
1477
+ return {
1478
+ input: routedInput.length === input.length ? input : routedInput,
1479
+ tools,
1480
+ flattened: routedInput.length !== input.length,
1481
+ };
1482
+ }
1483
+
1484
+ const callsById = new Map();
1485
+ const invalidIds = new Set();
1486
+ let nativeItems = 0;
1487
+ // Pair in one forward walk. Outputs may follow several parallel calls, but
1488
+ // they may never reach backwards past an orphan, duplicate, or malformed
1489
+ // item with the same id. Records are materialized only after the walk, so a
1490
+ // duplicate discovered late invalidates the entire id before any tool can be
1491
+ // added to the provider request.
1492
+ for (let index = 0; index < input.length; index += 1) {
1493
+ const item = input[index];
1494
+ const isCall = item?.type === "tool_search_call";
1495
+ const isOutput = item?.type === "tool_search_output";
1496
+ if (!isCall && !isOutput) continue;
1497
+ nativeItems += 1;
1498
+
1499
+ const id = typeof item.call_id === "string" && item.call_id ? item.call_id : undefined;
1500
+ if (!id) continue;
1501
+ if (invalidIds.has(id)) continue;
1502
+
1503
+ if (isCall) {
1504
+ const valid =
1505
+ item.execution === "client" && validToolSearchHistoryArguments(item.arguments);
1506
+ if (!valid || callsById.has(id)) {
1507
+ callsById.delete(id);
1508
+ invalidIds.add(id);
1509
+ continue;
1510
+ }
1511
+ callsById.set(id, { call: item, callIndex: index });
1512
+ continue;
1513
+ }
1514
+
1515
+ const call = callsById.get(id);
1516
+ const valid =
1517
+ item.execution === "client" &&
1518
+ item.status === "completed" &&
1519
+ Array.isArray(item.tools);
1520
+ if (!valid || !call || call.output) {
1521
+ callsById.delete(id);
1522
+ invalidIds.add(id);
1523
+ continue;
1524
+ }
1525
+ call.output = item;
1526
+ call.outputIndex = index;
1527
+ }
1528
+
1529
+ if (nativeItems === 0) return { input, tools, flattened: false };
1530
+
1531
+ const callsByIndex = new Map();
1532
+ const outputsByIndex = new Map();
1533
+ if (relay || recoverWithoutRelay) {
1534
+ for (const [id, record] of callsById) {
1535
+ if (!record.output || invalidIds.has(id)) continue;
1536
+ callsByIndex.set(record.callIndex, record);
1537
+ outputsByIndex.set(record.outputIndex, record);
1538
+ }
1539
+ }
1540
+
1541
+ const toolCapacity = Number.isInteger(maxTools) && maxTools >= 0 ? maxTools : Infinity;
1542
+ const remainingToolCapacity = Math.max(0, toolCapacity - tools.length);
1543
+ const visibleNames = providerVisibleToolNames(tools);
1544
+ const initialNameAliases = new Map(
1545
+ NAME_ALIASES.get(namespaces)?.nativeToProvider || [],
1546
+ );
1547
+ const definitionOwnersByName = new Map();
1548
+ const discoveries = [];
1549
+ const discoveriesByOutputIndex = new Map();
1550
+ for (let index = 0; index < input.length; index += 1) {
1551
+ const item = input[index];
1552
+ if (!outputsByIndex.has(index)) continue;
1553
+ const records = [];
1554
+ for (const candidate of discoveredProviderTools(item.tools, namespaces)) {
1555
+ const name = providerFunctionName(candidate.tool);
1556
+ if (!name) continue;
1557
+ const priorOwner = definitionOwnersByName.get(name);
1558
+ const shadowedByClient = visibleNames.has(name) && !priorOwner;
1559
+ const record = {
1560
+ ...candidate,
1561
+ name,
1562
+ outputIndex: index,
1563
+ shadowed: shadowedByClient || priorOwner !== undefined,
1564
+ definitionOwner: shadowedByClient ? undefined : priorOwner,
1565
+ };
1566
+ if (!visibleNames.has(name)) {
1567
+ visibleNames.add(name);
1568
+ definitionOwnersByName.set(name, record);
1569
+ record.definitionOwner = record;
1570
+ }
1571
+ discoveries.push(record);
1572
+ records.push(record);
1573
+ }
1574
+ discoveriesByOutputIndex.set(index, records);
1575
+ }
1576
+
1577
+ // A bounded provider surface may omit only unused discoveries. Resolve each
1578
+ // stored call against the definitions that existed at that point in the
1579
+ // transcript, using the same precedence as flattenNamespacedHistory: an
1580
+ // explicit namespace is exact, an exact plain native identity wins over a
1581
+ // stale flattened namespace spelling, then provider aliases/raw namespace
1582
+ // spellings, then a unique bare namespace name. Later discoveries must not
1583
+ // retroactively make an earlier bare call ambiguous. A forced tool choice is
1584
+ // evaluated after all stored discoveries and reserves its schema too.
1585
+ const CURRENT_DEFINITION = Symbol("current-tool-definition");
1586
+ const identityOwners = new Map();
1587
+ const plainNativeOwners = new Map();
1588
+ const providerOwners = new Map();
1589
+ const wireNamespaceOwners = new Map();
1590
+ const bareNamespaceOwners = new Map();
1591
+ const addOwner = (owners, name, owner) => {
1592
+ if (typeof name !== "string" || !name) return;
1593
+ if (!owners.has(name)) owners.set(name, new Set());
1594
+ owners.get(name).add(owner);
1595
+ };
1596
+ const uniqueOwner = (owners) => owners?.size === 1 ? [...owners][0] : undefined;
1597
+ const rememberIdentity = ({ identity, native, nativeName, name, owner }) => {
1598
+ if (!identityOwners.has(identity)) identityOwners.set(identity, owner);
1599
+ addOwner(providerOwners, name, identity);
1600
+ if (native) {
1601
+ addOwner(
1602
+ wireNamespaceOwners,
1603
+ `${native.namespace}${NAMESPACE_DELIMITER}${native.name}`,
1604
+ identity,
1605
+ );
1606
+ addOwner(bareNamespaceOwners, native.name, identity);
1607
+ } else {
1608
+ addOwner(plainNativeOwners, nativeName, identity);
1609
+ }
1610
+ };
1611
+
1612
+ for (const [namespace, names] of namespaces) {
1613
+ for (const name of names) {
1614
+ rememberIdentity({
1615
+ identity: nativeToolKey(namespace, name),
1616
+ native: { namespace, name },
1617
+ name: providerNameForNative(namespaces, namespace, name),
1618
+ owner: CURRENT_DEFINITION,
1619
+ });
1620
+ }
1621
+ }
1622
+ if (initialNameAliases.size) {
1623
+ for (const [identity, providerName] of initialNameAliases) {
1624
+ let decoded;
1625
+ try {
1626
+ decoded = JSON.parse(identity);
1627
+ } catch {
1628
+ continue;
1629
+ }
1630
+ if (!Array.isArray(decoded) || decoded.length !== 2 || decoded[0] !== null) continue;
1631
+ rememberIdentity({
1632
+ identity,
1633
+ nativeName: decoded[1],
1634
+ name: providerName,
1635
+ owner: CURRENT_DEFINITION,
1636
+ });
1637
+ }
1638
+ } else {
1639
+ for (const name of PLAIN_TOOL_NAMES.get(namespaces) || []) {
1640
+ rememberIdentity({
1641
+ identity: nativeToolKey(undefined, name),
1642
+ nativeName: name,
1643
+ name,
1644
+ owner: CURRENT_DEFINITION,
1645
+ });
1646
+ }
1647
+ }
1648
+ // Custom/tool-search relays are provider-visible but are never ordinary
1649
+ // discovered definitions. Reserve their spellings as current identities so
1650
+ // a matching model-visible name cannot be attributed to a discovery.
1651
+ for (const name of CUSTOM_TOOL_RELAYS.get(namespaces)?.keys() || []) {
1652
+ const identity = `special:custom:${name}`;
1653
+ identityOwners.set(identity, CURRENT_DEFINITION);
1654
+ addOwner(providerOwners, name, identity);
1655
+ }
1656
+ for (const name of FUNCTION_RELAYS.get(namespaces)?.keys() || []) {
1657
+ const identity = `special:function:${name}`;
1658
+ identityOwners.set(identity, CURRENT_DEFINITION);
1659
+ addOwner(providerOwners, name, identity);
1660
+ }
1661
+ const toolSearch = TOOL_SEARCH_RELAYS.get(namespaces);
1662
+ if (toolSearch) {
1663
+ const identity = "special:tool-search";
1664
+ identityOwners.set(identity, CURRENT_DEFINITION);
1665
+ addOwner(providerOwners, toolSearch.providerName, identity);
1666
+ }
1667
+
1668
+ const referencedDefinitions = new Set();
1669
+ const referencedIdentities = new Set();
1670
+ const markReference = (reference) => {
1671
+ if (!reference || typeof reference !== "object" || Array.isArray(reference)) return;
1672
+ const nestedName = reference.function?.name;
1673
+ const name = typeof nestedName === "string" ? nestedName : reference.name;
1674
+ if (typeof name !== "string" || !name) return;
1675
+ const namespace =
1676
+ typeof reference.namespace === "string" && reference.namespace
1677
+ ? reference.namespace
1678
+ : undefined;
1679
+ let identity;
1680
+ if (namespace) {
1681
+ const exact = nativeToolKey(namespace, name);
1682
+ if (identityOwners.has(exact)) identity = exact;
1683
+ } else if (!SPECIAL_FUNCTION_REFERENCES.has(reference)) {
1684
+ identity = uniqueOwner(plainNativeOwners.get(name));
1685
+ identity ??= uniqueOwner(providerOwners.get(name));
1686
+ identity ??= uniqueOwner(wireNamespaceOwners.get(name));
1687
+ identity ??= uniqueOwner(bareNamespaceOwners.get(name));
1688
+ }
1689
+ if (!identity) return;
1690
+ referencedIdentities.add(identity);
1691
+ const owner = identityOwners.get(identity);
1692
+ if (owner && owner !== CURRENT_DEFINITION) referencedDefinitions.add(owner);
1693
+ };
1694
+ const discoveriesByIndex = new Map();
1695
+ for (const discovery of discoveries) {
1696
+ if (!discoveriesByIndex.has(discovery.outputIndex)) {
1697
+ discoveriesByIndex.set(discovery.outputIndex, []);
1698
+ }
1699
+ discoveriesByIndex.get(discovery.outputIndex).push(discovery);
1700
+ }
1701
+ for (let index = 0; index < input.length; index += 1) {
1702
+ for (const discovery of discoveriesByIndex.get(index) || []) {
1703
+ const owner = discovery.definitionOwner || CURRENT_DEFINITION;
1704
+ rememberIdentity({ ...discovery, owner });
1705
+ }
1706
+ const item = input[index];
1707
+ if (item?.type === "function_call") markReference(item);
1708
+ }
1709
+ if (toolChoice?.type === "allowed_tools" && Array.isArray(toolChoice.tools)) {
1710
+ for (const choice of toolChoice.tools) {
1711
+ if (choice?.type === "function") markReference(choice);
1712
+ }
1713
+ } else if (toolChoice?.type === "function") {
1714
+ markReference(toolChoice);
1715
+ }
1716
+ const requiredDefinitions = [...referencedDefinitions];
1717
+ if (requiredDefinitions.length > remainingToolCapacity) {
1718
+ throw new ToolSearchHistoryCapacityError({
1719
+ available: remainingToolCapacity,
1720
+ required: requiredDefinitions.length,
1721
+ });
1722
+ }
1723
+ const acceptedDiscoveries = new Set(requiredDefinitions);
1724
+ for (const discovery of discoveries) {
1725
+ if (acceptedDiscoveries.size >= remainingToolCapacity) break;
1726
+ if (discovery.shadowed) continue;
1727
+ acceptedDiscoveries.add(discovery);
1728
+ }
1729
+
1730
+ let routedTools = tools;
1731
+ const routedInput = [];
1732
+ for (let index = 0; index < input.length; index += 1) {
1733
+ const item = input[index];
1734
+ if (item?.type === "tool_search_call") {
1735
+ if (!callsByIndex.has(index)) continue;
1736
+ if (!relay) continue;
1737
+ const {
1738
+ type: _type,
1739
+ execution: _execution,
1740
+ status: _status,
1741
+ arguments: searchArguments,
1742
+ ...rest
1743
+ } = item;
1744
+ const routedCall = {
1745
+ ...rest,
1746
+ type: "function_call",
1747
+ name: relay.providerName,
1748
+ arguments: JSON.stringify(searchArguments),
1749
+ };
1750
+ SPECIAL_FUNCTION_REFERENCES.add(routedCall);
1751
+ routedInput.push(routedCall);
1752
+ continue;
1753
+ }
1754
+
1755
+ if (item?.type !== "tool_search_output") {
1756
+ routedInput.push(item);
1757
+ continue;
1758
+ }
1759
+ if (!outputsByIndex.has(index)) continue;
1760
+
1761
+ const accepted = [];
1762
+ for (const discovery of discoveriesByOutputIndex.get(index) || []) {
1763
+ if (acceptedDiscoveries.has(discovery)) {
1764
+ accepted.push(discovery.tool);
1765
+ addDiscoveredNamespace(namespaces, discovery.native);
1766
+ if (!discovery.native) PLAIN_TOOL_NAMES.get(namespaces)?.add(discovery.name);
1767
+ } else if (
1768
+ recoverWithoutRelay &&
1769
+ discovery.shadowed &&
1770
+ referencedIdentities.has(discovery.identity)
1771
+ ) {
1772
+ // A current client definition owns the provider-visible name and its
1773
+ // schema must win. The stored native identity is still needed to
1774
+ // flatten the later historical call and restore any repeated call.
1775
+ addDiscoveredNamespace(namespaces, discovery.native);
1776
+ }
1777
+ }
1778
+ // Keep the live-name set across outputs. The first valid discovery wins;
1779
+ // later outputs omit a duplicate from both their result and the request's
1780
+ // tool list instead of advertising a schema that cannot take precedence.
1781
+ if (accepted.length) {
1782
+ if (routedTools === tools) routedTools = [...tools];
1783
+ routedTools.push(...accepted);
1784
+ }
1785
+
1786
+ // The current provider cannot execute a fresh native tool_search call.
1787
+ // Preserve the discovered definitions above, but remove the now-unusable
1788
+ // call/output control pair from the chat-completions transcript.
1789
+ if (!relay) continue;
1790
+
1791
+ const {
1792
+ type: _type,
1793
+ execution: _execution,
1794
+ status: _status,
1795
+ tools: _tools,
1796
+ ...rest
1797
+ } = item;
1798
+ routedInput.push({
1799
+ ...rest,
1800
+ type: "function_call_output",
1801
+ output: JSON.stringify({ tools: accepted }),
1802
+ });
1803
+ }
1804
+
1805
+ return {
1806
+ input: routedInput,
1807
+ tools: routedTools,
1808
+ flattened: true,
1809
+ };
1810
+ }
1811
+
1812
+ // Flattening only the tool list leaves the model reading two names for one
1813
+ // tool: `collaboration__spawn_agent` in its tools, but a bare `spawn_agent`
1814
+ // in its own call history, because LiteLLM's bridge drops the `namespace`
1815
+ // field when it converts stored function calls to Chat Completions tool calls.
1816
+ // The model imitates the history, emits the bare name, nothing rewrites it,
1817
+ // and Codex answers `unsupported call` -- permanently, since every failure
1818
+ // adds another bare example. Rename the history to match the flattened tools.
1819
+ export function flattenNamespacedHistory(input, namespaces) {
1820
+ const nameRelay = NAME_ALIASES.get(namespaces);
1821
+ if (!Array.isArray(input) || (namespaces.size === 0 && !nameRelay)) return input;
1822
+ const flattenedNames = new Set();
1823
+ const bareOwners = new Map();
1824
+ for (const [namespace, names] of namespaces) {
1825
+ for (const name of names) {
1826
+ flattenedNames.add(`${namespace}${NAMESPACE_DELIMITER}${name}`);
1827
+ if (!bareOwners.has(name)) bareOwners.set(name, new Set());
1828
+ bareOwners.get(name).add(namespace);
1829
+ }
1830
+ }
1831
+ const providerNames = new Set([
1832
+ ...(PLAIN_TOOL_NAMES.get(namespaces) || []),
1833
+ ...(nameRelay?.providerToNative.keys() || []),
1834
+ ...(nameRelay?.plainProviderNames || []),
1835
+ ...(CUSTOM_TOOL_RELAYS.get(namespaces)?.keys() || []),
1836
+ ...(FUNCTION_RELAYS.get(namespaces)?.keys() || []),
1837
+ ]);
1838
+ const toolSearch = TOOL_SEARCH_RELAYS.get(namespaces);
1839
+ if (toolSearch) providerNames.add(toolSearch.providerName);
1840
+ return input.map((item) => {
1841
+ if (item?.type !== "function_call") return item;
1842
+ const { name } = item;
1843
+ if (typeof name !== "string") return item;
1844
+ // The client stores namespaced calls as { name, namespace }.
1845
+ const namespace = item.namespace;
1846
+ if (typeof namespace === "string" && namespaces.get(namespace)?.has(name)) {
1847
+ const { namespace: _namespace, ...rest } = item;
1848
+ return { ...rest, name: providerNameForNative(namespaces, namespace, name) };
1849
+ }
1850
+ // A custom/tool-search call already bridged in this request owns its exact
1851
+ // provider spelling. A later-discovered ordinary function can have the same
1852
+ // native name but a different provider alias; object identity keeps the two
1853
+ // histories distinct after both have become ordinary function calls.
1854
+ if (SPECIAL_FUNCTION_REFERENCES.has(item)) return item;
1855
+ // A plain native function may have the exact spelling a namespace child
1856
+ // would normally flatten to (for example plain `a__b` beside namespace
1857
+ // `a` / child `b`). Both definitions receive collision aliases. Resolve
1858
+ // the exact plain identity before treating that spelling as a raw
1859
+ // namespace wire name, or stored plain history would cite neither alias.
1860
+ const plainProviderName =
1861
+ namespace === undefined
1862
+ ? nameRelay?.nativeToProvider.get(nativeToolKey(undefined, name))
1863
+ : undefined;
1864
+ if (plainProviderName && plainProviderName !== name) {
1865
+ return { ...item, name: plainProviderName };
1866
+ }
1867
+ // Provider-visible plain and special-relay names take precedence only when
1868
+ // history carries no valid native namespace or exact aliased plain identity.
1869
+ // Otherwise a plain `read` tool could prevent `{ namespace: "mcp", name:
1870
+ // "read" }` from being rewritten to the namespaced definition actually sent
1871
+ // upstream.
1872
+ if (providerNames.has(name)) return item;
1873
+ if (flattenedNames.has(name)) {
1874
+ const providerName = providerNameForWire(namespaces, name);
1875
+ return providerName && providerName !== name ? { ...item, name: providerName } : item;
1876
+ }
1877
+ // Calls stored without a namespace field whose bare name belongs to
1878
+ // exactly one flattened namespace.
1879
+ if (namespace === undefined) {
1880
+ const owners = bareOwners.get(name);
1881
+ if (owners && owners.size === 1) {
1882
+ const [owner] = [...owners];
1883
+ const { namespace: _namespace, ...rest } = item;
1884
+ return { ...rest, name: providerNameForNative(namespaces, owner, name) };
1885
+ }
1886
+ }
1887
+ return item;
1888
+ });
1889
+ }
1890
+
1891
+ function compactionToolIdentityInventory(input, tools) {
1892
+ const plainNames = new Set();
1893
+ const namespaceNames = new Map();
1894
+ const rememberNamespace = (namespace, name) => {
1895
+ if (typeof namespace !== "string" || !namespace || typeof name !== "string" || !name) {
1896
+ return;
1897
+ }
1898
+ if (!namespaceNames.has(namespace)) namespaceNames.set(namespace, new Set());
1899
+ namespaceNames.get(namespace).add(name);
1900
+ };
1901
+
1902
+ for (const tool of Array.isArray(tools) ? tools : []) {
1903
+ if (tool?.type === "namespace" && typeof tool.name === "string") {
1904
+ for (const child of Array.isArray(tool.tools) ? tool.tools : []) {
1905
+ if (child?.type === "function") rememberNamespace(tool.name, child.name);
1906
+ }
1907
+ continue;
1908
+ }
1909
+ if (tool?.type !== "function") continue;
1910
+ const name = providerFunctionName(tool);
1911
+ if (typeof name === "string" && name) plainNames.add(name);
1912
+ }
1913
+
1914
+ const calls = (Array.isArray(input) ? input : []).filter(
1915
+ (item) => item?.type === "function_call" && typeof item.name === "string" && item.name,
1916
+ );
1917
+ for (const item of calls) rememberNamespace(item.namespace, item.name);
1918
+
1919
+ const rawNamespaceOwners = new Map();
1920
+ const bareNamespaceOwners = new Map();
1921
+ for (const [namespace, names] of namespaceNames) {
1922
+ for (const name of names) {
1923
+ const wireName = `${namespace}${NAMESPACE_DELIMITER}${name}`;
1924
+ if (!rawNamespaceOwners.has(wireName)) rawNamespaceOwners.set(wireName, new Set());
1925
+ rawNamespaceOwners.get(wireName).add(namespace);
1926
+ if (!bareNamespaceOwners.has(name)) bareNamespaceOwners.set(name, new Set());
1927
+ bareNamespaceOwners.get(name).add(namespace);
1928
+ }
1929
+ }
1930
+ for (const item of calls) {
1931
+ if (item.namespace !== undefined) continue;
1932
+ const rawOwners = rawNamespaceOwners.get(item.name);
1933
+ const bareOwners = bareNamespaceOwners.get(item.name);
1934
+ if (
1935
+ !plainNames.has(item.name) &&
1936
+ ((rawOwners && rawOwners.size === 1) || (bareOwners && bareOwners.size === 1))
1937
+ ) {
1938
+ continue;
1939
+ }
1940
+ plainNames.add(item.name);
1941
+ }
1942
+
1943
+ return [
1944
+ ...[...plainNames]
1945
+ .sort((left, right) => left.localeCompare(right))
1946
+ .map((name) => ({ type: "function", name })),
1947
+ ...[...namespaceNames]
1948
+ .sort(([left], [right]) => left.localeCompare(right))
1949
+ .map(([name, names]) => ({
1950
+ type: "namespace",
1951
+ name,
1952
+ tools: [...names]
1953
+ .sort((left, right) => left.localeCompare(right))
1954
+ .map((childName) => ({ type: "function", name: childName })),
1955
+ })),
1956
+ ];
1957
+ }
1958
+
1959
+ // Routed compaction sends no live tools, but it still replays the complete
1960
+ // transcript to the summarizer. Console Go rejects Codex-native tool item
1961
+ // discriminators on that replay just as it does on an ordinary turn. Build a
1962
+ // names-only request-local inventory from the payload and explicit history,
1963
+ // bridge custom calls, flatten namespace history with the same bounded naming
1964
+ // contract, and remove deferred-search metadata (schemas, not tool results)
1965
+ // that cannot be consumed without a live tool_search control.
1966
+ export function strictOpenCodeCompactionInput(input, tools, { maxNameLength = 64 } = {}) {
1967
+ if (!Array.isArray(input)) return input;
1968
+ const inventory = compactionToolIdentityInventory(input, tools);
1969
+ const flattened = flattenNamespaceTools(inventory, {
1970
+ bridgeToolSearch: false,
1971
+ maxNameLength,
1972
+ });
1973
+ const customNames = [
1974
+ ...new Set(
1975
+ input
1976
+ .filter(
1977
+ (item) =>
1978
+ item?.type === "custom_tool_call" &&
1979
+ typeof item.name === "string" &&
1980
+ item.name,
1981
+ )
1982
+ .map((item) => item.name),
1983
+ ),
1984
+ ];
1985
+ const custom = bridgeCustomTools(
1986
+ [],
1987
+ input,
1988
+ flattened.namespaces,
1989
+ undefined,
1990
+ customNames,
1991
+ { maxNameLength },
1992
+ );
1993
+ const withoutSearch = custom.input.filter(
1994
+ (item) =>
1995
+ item?.type !== "tool_search_call" &&
1996
+ item?.type !== "tool_search_output" &&
1997
+ item?.type !== "custom_tool_call" &&
1998
+ item?.type !== "custom_tool_call_output",
1999
+ );
2000
+ return flattenNamespacedHistory(withoutSearch, flattened.namespaces);
2001
+ }
2002
+
2003
+ function flattenToolChoiceReference(reference, namespaces) {
2004
+ if (!reference || typeof reference !== "object" || Array.isArray(reference)) return reference;
2005
+ const toolSearch = TOOL_SEARCH_RELAYS.get(namespaces);
2006
+ if (reference.type === "tool_search" && toolSearch) {
2007
+ const { execution: _execution, ...rest } = reference;
2008
+ const routedReference = {
2009
+ ...rest,
2010
+ type: "function",
2011
+ name: toolSearch.providerName,
2012
+ };
2013
+ SPECIAL_FUNCTION_REFERENCES.add(routedReference);
2014
+ return routedReference;
2015
+ }
2016
+ if (reference.type !== "function") return reference;
2017
+
2018
+ const nestedName = reference.function?.name;
2019
+ const name = typeof nestedName === "string" ? nestedName : reference.name;
2020
+ if (typeof name !== "string" || !name) return reference;
2021
+ const namespace =
2022
+ typeof reference.namespace === "string" && reference.namespace
2023
+ ? reference.namespace
2024
+ : undefined;
2025
+ let providerName;
2026
+ if (namespace && namespaces.get(namespace)?.has(name)) {
2027
+ providerName = providerNameForNative(namespaces, namespace, name);
2028
+ } else if (!namespace) {
2029
+ if (SPECIAL_FUNCTION_REFERENCES.has(reference)) return reference;
2030
+ const exactPlainProviderName = NAME_ALIASES.get(namespaces)?.nativeToProvider.get(
2031
+ nativeToolKey(undefined, name),
2032
+ );
2033
+ if (exactPlainProviderName && exactPlainProviderName !== name) {
2034
+ providerName = exactPlainProviderName;
2035
+ }
2036
+ const alreadyProviderVisible =
2037
+ PLAIN_TOOL_NAMES.get(namespaces)?.has(name) ||
2038
+ NAME_ALIASES.get(namespaces)?.plainProviderNames.has(name) ||
2039
+ CUSTOM_TOOL_RELAYS.get(namespaces)?.has(name) ||
2040
+ TOOL_SEARCH_RELAYS.get(namespaces)?.providerName === name;
2041
+ if (!providerName && alreadyProviderVisible) return reference;
2042
+ providerName ||= exactPlainProviderName || providerNameForWire(namespaces, name);
2043
+ if (!providerName) {
2044
+ const owners = [...namespaces].filter(([, names]) => names.has(name));
2045
+ if (owners.length === 1) {
2046
+ providerName = providerNameForNative(namespaces, owners[0][0], name);
2047
+ }
2048
+ }
2049
+ }
2050
+ if (!providerName || (providerName === name && namespace === undefined)) return reference;
2051
+
2052
+ const { namespace: _namespace, ...rest } = reference;
2053
+ if (typeof nestedName === "string") {
2054
+ return { ...rest, function: { ...reference.function, name: providerName } };
2055
+ }
2056
+ return { ...rest, name: providerName };
2057
+ }
2058
+
2059
+ // A bounded provider name is one contract across the whole request. Rewrite
2060
+ // forced references only after tool-search history has expanded the live tool
2061
+ // set, so a discovered definition and an allowed-tools choice cannot disagree.
2062
+ export function flattenToolChoice(toolChoice, namespaces) {
2063
+ if (!toolChoice || typeof toolChoice !== "object" || Array.isArray(toolChoice)) {
2064
+ return toolChoice;
2065
+ }
2066
+ if (toolChoice.type !== "allowed_tools" || !Array.isArray(toolChoice.tools)) {
2067
+ return flattenToolChoiceReference(toolChoice, namespaces);
2068
+ }
2069
+ let changed = false;
2070
+ const tools = toolChoice.tools.map((tool) => {
2071
+ const rewritten = flattenToolChoiceReference(tool, namespaces);
2072
+ if (rewritten !== tool) changed = true;
2073
+ return rewritten;
2074
+ });
2075
+ return changed ? { ...toolChoice, tools } : toolChoice;
2076
+ }
2077
+
2078
+ // Reverse lookups for restoring calls: flattened name -> native
2079
+ // { namespace, name }, and bare tool name -> namespaces that own it.
2080
+ export function buildNamespaceLookups(namespaces) {
2081
+ const flatToNative = new Map();
2082
+ const bareToNamespaces = new Map();
2083
+ const nameAliases = NAME_ALIASES.get(namespaces);
2084
+ const customTools = CUSTOM_TOOL_RELAYS.get(namespaces);
2085
+ const bridgedCustomIdentities = new Set(
2086
+ [...(customTools?.values() || [])]
2087
+ .filter((native) => typeof native === "object")
2088
+ .map((native) => nativeToolKey(native.namespace, native.name)),
2089
+ );
2090
+ // Dotted wire spellings (`namespace.tool`) some Responses-native models emit
2091
+ // instead of `__` (#611). Collect candidates first; only an unambiguous
2092
+ // inventory pair is registered — never invent identity by splitting a name
2093
+ // (#568).
2094
+ const dottedCandidates = new Map();
2095
+ const rememberDotted = (dottedName, native) => {
2096
+ if (!dottedCandidates.has(dottedName)) {
2097
+ dottedCandidates.set(dottedName, native);
2098
+ return;
2099
+ }
2100
+ const previous = dottedCandidates.get(dottedName);
2101
+ if (
2102
+ !previous ||
2103
+ previous.namespace !== native.namespace ||
2104
+ previous.name !== native.name
2105
+ ) {
2106
+ dottedCandidates.set(dottedName, undefined);
2107
+ }
2108
+ };
2109
+ for (const [namespace, names] of namespaces) {
2110
+ for (const name of names) {
2111
+ // The custom relay owns this identity now, including any collision alias.
2112
+ // Its former flattened spelling may belong to an ordinary function.
2113
+ if (bridgedCustomIdentities.has(nativeToolKey(namespace, name))) continue;
2114
+ const providerName =
2115
+ nameAliases?.nativeToProvider.get(nativeToolKey(namespace, name)) ||
2116
+ `${namespace}${NAMESPACE_DELIMITER}${name}`;
2117
+ const native = { namespace, name };
2118
+ flatToNative.set(providerName, native);
2119
+ const dottedName = `${namespace}.${name}`;
2120
+ if (dottedName !== providerName) rememberDotted(dottedName, native);
2121
+ if (!bareToNamespaces.has(name)) bareToNamespaces.set(name, new Set());
2122
+ bareToNamespaces.get(name).add(namespace);
2123
+ }
2124
+ }
2125
+ if (nameAliases) {
2126
+ for (const [providerName, native] of nameAliases.providerToNative) {
2127
+ if (bridgedCustomIdentities.has(nativeToolKey(native.namespace, native.name))) continue;
2128
+ flatToNative.set(providerName, native);
2129
+ }
2130
+ }
2131
+ const plainToolNames = new Set([
2132
+ ...(PLAIN_TOOL_NAMES.get(namespaces) || []),
2133
+ ...(nameAliases?.plainProviderNames || []),
2134
+ ]);
2135
+ for (const [dottedName, native] of dottedCandidates) {
2136
+ if (!native || plainToolNames.has(dottedName)) continue;
2137
+ const existing = flatToNative.get(dottedName);
2138
+ if (
2139
+ existing &&
2140
+ (existing.namespace !== native.namespace || existing.name !== native.name)
2141
+ ) {
2142
+ continue;
2143
+ }
2144
+ flatToNative.set(dottedName, native);
2145
+ }
2146
+ return {
2147
+ flatToNative,
2148
+ bareToNamespaces,
2149
+ plainToolNames,
2150
+ identityAliases: Boolean(nameAliases),
2151
+ spawnAgentModels: SPAWN_AGENT_MODELS.get(namespaces),
2152
+ toolSearch: TOOL_SEARCH_RELAYS.get(namespaces),
2153
+ customTools,
2154
+ customCodecs: CUSTOM_TOOL_CODECS.get(namespaces),
2155
+ functionRelays: FUNCTION_RELAYS.get(namespaces),
2156
+ };
2157
+ }
2158
+
2159
+ function sanitizeSpawnAgentModel(item, lookups) {
2160
+ if (item?.namespace !== "collaboration" || item.name !== "spawn_agent") return item;
2161
+ const allowed = lookups.spawnAgentModels;
2162
+ if (!(allowed instanceof Set) || allowed.size === 0 || typeof item.arguments !== "string") {
2163
+ return item;
2164
+ }
2165
+ if (!jsonArgumentsAreUnambiguous(item.arguments)) return item;
2166
+ let args;
2167
+ try {
2168
+ args = JSON.parse(item.arguments);
2169
+ } catch {
2170
+ return item;
2171
+ }
2172
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return item;
2173
+ if (typeof args.model !== "string" || allowed.has(args.model)) return item;
2174
+ const { model: _invalidModel, ...safeArgs } = args;
2175
+ return { ...item, arguments: JSON.stringify(safeArgs) };
2176
+ }
2177
+
2178
+ // Restore one SSE event's function call to the client's native namespace
2179
+ // shape. A flattened `<namespace>__<tool>` name resolves exactly. A bare tool
2180
+ // name (some models emit the unqualified form) is restored only when it is
2181
+ // unambiguous across every flattened namespace; a collision stays untouched
2182
+ // rather than guessing which runtime owns it.
2183
+ function functionRelayIdentityMatches(item, relay) {
2184
+ if (!relay || item?.type !== "function_call" || item.name !== relay.nativeName) return false;
2185
+ if (typeof relay.nativeNamespace === "string" && relay.nativeNamespace) {
2186
+ return item.namespace === relay.nativeNamespace;
2187
+ }
2188
+ return item.namespace === undefined;
2189
+ }
2190
+
2191
+ function restoreFunctionRelayCall(item, relay, argumentsText) {
2192
+ const {
2193
+ name: _name,
2194
+ namespace: _namespace,
2195
+ arguments: _arguments,
2196
+ encrypted_function_args: _encryptedFunctionArgs,
2197
+ ...rest
2198
+ } = item;
2199
+ return {
2200
+ ...rest,
2201
+ name: relay.nativeName,
2202
+ ...(relay.nativeNamespace ? { namespace: relay.nativeNamespace } : {}),
2203
+ arguments: argumentsText,
2204
+ };
2205
+ }
2206
+
2207
+ function rewriteFunctionCallArguments(item) {
2208
+ if (!item || typeof item !== "object") return item;
2209
+ if (!jsonArgumentsAreUnambiguous(item.arguments, { allowEmpty: true })) return item;
2210
+ const argumentsText = coerceFunctionCallArguments(item.arguments);
2211
+ if (argumentsText === item.arguments) return item;
2212
+ return { ...item, arguments: argumentsText };
2213
+ }
2214
+
2215
+ function toolSearchArguments(value, allowPlaceholder) {
2216
+ if (plainObject(value)) return value;
2217
+ if (typeof value !== "string") return undefined;
2218
+ if (allowPlaceholder && value.trim() === "") return {};
2219
+ if (!jsonArgumentsAreUnambiguous(value)) return undefined;
2220
+ try {
2221
+ return plainObject(JSON.parse(value));
2222
+ } catch {
2223
+ return undefined;
2224
+ }
2225
+ }
2226
+
2227
+ function rewriteToolSearchFunctionCallItem(item, lookups, allowPlaceholder) {
2228
+ const relay = lookups.toolSearch;
2229
+ if (
2230
+ !relay ||
2231
+ item?.type !== "function_call" ||
2232
+ item.name !== relay.providerName ||
2233
+ item.namespace !== undefined ||
2234
+ typeof item.call_id !== "string" ||
2235
+ !item.call_id
2236
+ ) {
2237
+ return undefined;
2238
+ }
2239
+ const argumentsObject = toolSearchArguments(item.arguments, allowPlaceholder);
2240
+ if (!argumentsObject) return undefined;
2241
+ const {
2242
+ type: _type,
2243
+ name: _name,
2244
+ namespace: _namespace,
2245
+ arguments: _arguments,
2246
+ encrypted_function_args: _encryptedFunctionArgs,
2247
+ ...rest
2248
+ } = item;
2249
+ return {
2250
+ ...rest,
2251
+ type: "tool_search_call",
2252
+ execution: "client",
2253
+ arguments: argumentsObject,
2254
+ };
2255
+ }
2256
+
2257
+ function customToolInput(
2258
+ value,
2259
+ allowPlaceholder = false,
2260
+ property = CUSTOM_TOOL_INPUT_PROPERTY,
2261
+ codec,
2262
+ ) {
2263
+ if (allowPlaceholder && (value === undefined || value === "")) return "";
2264
+ const argumentsText = codec?.preserveRawArguments === true ? value : coerceFunctionCallArguments(value);
2265
+ if (typeof argumentsText !== "string") return undefined;
2266
+ if (codec) {
2267
+ try {
2268
+ return codec.decodeArguments(argumentsText);
2269
+ } catch {
2270
+ return undefined;
2271
+ }
2272
+ }
2273
+ if (!jsonArgumentsAreUnambiguous(argumentsText)) return undefined;
2274
+ try {
2275
+ const parsed = JSON.parse(argumentsText);
2276
+ return typeof parsed?.[property] === "string" ? parsed[property] : undefined;
2277
+ } catch {
2278
+ return undefined;
2279
+ }
2280
+ }
2281
+
2282
+ // LiteLLM decides a native custom call's input itself: it unwraps a string
2283
+ // `content` from a JSON object and otherwise keeps the provider's arguments
2284
+ // verbatim (`unwrap_custom_tool_arguments` in its custom_tools module). Its
2285
+ // completed item carries that decision, so the relay must derive the same
2286
+ // input rather than a stricter one. A present non-string `content` has no
2287
+ // faithful equivalent of Python's str() and stays unsupported.
2288
+ const LITELLM_MAX_CUSTOM_ARGUMENTS_LENGTH = 1_000_000;
2289
+
2290
+ function litellmCustomToolInput(argumentsText) {
2291
+ if (typeof argumentsText !== "string") return undefined;
2292
+ if (argumentsText === "") return "";
2293
+ if (argumentsText.length > LITELLM_MAX_CUSTOM_ARGUMENTS_LENGTH) return argumentsText;
2294
+ let parsed;
2295
+ try {
2296
+ parsed = JSON.parse(argumentsText);
2297
+ } catch {
2298
+ return argumentsText;
2299
+ }
2300
+ if (!plainObject(parsed) || !Object.hasOwn(parsed, LITELLM_CUSTOM_TOOL_INPUT_PROPERTY)) {
2301
+ return argumentsText;
2302
+ }
2303
+ const content = parsed[LITELLM_CUSTOM_TOOL_INPUT_PROPERTY];
2304
+ return typeof content === "string" ? content : undefined;
2305
+ }
2306
+
2307
+ function rewriteCustomToolFunctionCallItem(item, lookups, allowPlaceholder) {
2308
+ if (
2309
+ item?.type !== "function_call" ||
2310
+ item.namespace !== undefined ||
2311
+ !(lookups.customTools instanceof Map)
2312
+ ) {
2313
+ return undefined;
2314
+ }
2315
+ const native = customToolIdentity(lookups.customTools.get(item.name));
2316
+ if (!native) return undefined;
2317
+ const input = customToolInput(item.arguments, allowPlaceholder, CUSTOM_TOOL_INPUT_PROPERTY, lookups.customCodecs?.get(item.name));
2318
+ if (input === undefined) return undefined;
2319
+ const {
2320
+ type: _type,
2321
+ name: _name,
2322
+ arguments: _arguments,
2323
+ encrypted_function_args: _encryptedFunctionArgs,
2324
+ ...rest
2325
+ } = item;
2326
+ return {
2327
+ ...rest,
2328
+ type: "custom_tool_call",
2329
+ ...native,
2330
+ ...(allowPlaceholder && input === "" ? {} : { input }),
2331
+ };
2332
+ }
2333
+
2334
+ function customToolIdentity(value) {
2335
+ return typeof value === "string" ? { name: value } : value;
2336
+ }
2337
+
2338
+ function customCallIdentityMatches(source, item, lookups) {
2339
+ if (typeof item.name !== "string" || !item.name) return false;
2340
+ if (source?.type === "function_call") {
2341
+ if (source.namespace !== undefined) return false;
2342
+ const native = customToolIdentity(lookups.customTools?.get(source.name));
2343
+ return Boolean(native && native.name === item.name && native.namespace === item.namespace);
2344
+ }
2345
+ // Native custom calls are not rewritten; keep their existing exact shape.
2346
+ return source?.type === "custom_tool_call" && source.name === item.name &&
2347
+ source.namespace === item.namespace &&
2348
+ (item.namespace === undefined || (typeof item.namespace === "string" && Boolean(item.namespace)));
2349
+ }
2350
+
2351
+ function rewriteNamespaceFunctionCallItem(
2352
+ item,
2353
+ lookups,
2354
+ sessionModel,
2355
+ { allowIncompleteToolSearch = false } = {},
2356
+ ) {
2357
+ if (!item || item.type !== "function_call") return undefined;
2358
+ if (!rawCodecItem(item, lookups) && !jsonArgumentsAreUnambiguous(item.arguments, { allowEmpty: true })) return undefined;
2359
+ const exactPlainProviderIdentity =
2360
+ lookups.identityAliases &&
2361
+ item.namespace === undefined &&
2362
+ lookups.plainToolNames?.has(item.name);
2363
+ const functionRelay = lookups.functionRelays instanceof Map
2364
+ ? lookups.functionRelays.get(item.name)
2365
+ : undefined;
2366
+ if (functionRelay && item.namespace === undefined) {
2367
+ if (allowIncompleteToolSearch && (item.arguments === undefined || item.arguments === "")) {
2368
+ return restoreFunctionRelayCall(item, functionRelay, item.arguments ?? "");
2369
+ }
2370
+ if (
2371
+ typeof item.arguments !== "string" ||
2372
+ Buffer.byteLength(item.arguments, "utf8") > functionRelay.maxArgumentBytes
2373
+ ) {
2374
+ return undefined;
2375
+ }
2376
+ const rewrittenArguments = functionRelay.rewriteArguments(item.arguments);
2377
+ if (typeof rewrittenArguments !== "string") return undefined;
2378
+ return restoreFunctionRelayCall(item, functionRelay, rewrittenArguments);
2379
+ }
2380
+ const customTool = rewriteCustomToolFunctionCallItem(
2381
+ item,
2382
+ lookups,
2383
+ allowIncompleteToolSearch,
2384
+ );
2385
+ if (customTool) return customTool;
2386
+ const toolSearch = rewriteToolSearchFunctionCallItem(
2387
+ item,
2388
+ lookups,
2389
+ allowIncompleteToolSearch,
2390
+ );
2391
+ if (toolSearch) return toolSearch;
2392
+ let rewritten = item;
2393
+ const resolved = lookups.flatToNative.get(item.name);
2394
+ if (resolved) {
2395
+ const { namespace: _providerNamespace, ...rest } = item;
2396
+ rewritten = resolved.namespace === undefined
2397
+ ? { ...rest, name: resolved.name }
2398
+ : { ...rest, name: resolved.name, namespace: resolved.namespace };
2399
+ } else {
2400
+ const owners = lookups.bareToNamespaces.get(item.name);
2401
+ if (
2402
+ item.namespace === undefined &&
2403
+ !lookups.plainToolNames?.has(item.name) &&
2404
+ owners &&
2405
+ owners.size === 1
2406
+ ) {
2407
+ const [namespace] = [...owners];
2408
+ rewritten = {
2409
+ ...item,
2410
+ namespace,
2411
+ };
2412
+ }
2413
+ }
2414
+ rewritten = sanitizeSpawnAgentModel(rewritten, lookups);
2415
+ // A client may declare an ordinary function whose literal name is
2416
+ // `codex_app__create_thread`. Its request-local alias resolves back to that
2417
+ // exact plain identity, not the app namespace. Do not infer app semantics
2418
+ // from the restored spelling after the lookup has already proved otherwise.
2419
+ if (!exactPlainProviderIdentity) {
2420
+ rewritten = injectSessionModelForSpawnCalls(rewritten, sessionModel);
2421
+ }
2422
+ rewritten = rewriteFunctionCallArguments(rewritten);
2423
+ return rewritten === item ? undefined : rewritten;
2424
+ }
2425
+
2426
+ export function rewriteNamespaceFunctionCall(event, lookups, sessionModel) {
2427
+ const item = rewriteNamespaceFunctionCallItem(event?.item, lookups, sessionModel, {
2428
+ allowIncompleteToolSearch: event?.type === "response.output_item.added",
2429
+ });
2430
+ return item ? { ...event, item } : undefined;
2431
+ }
2432
+
2433
+ function rewriteOutputItems(output, lookups, sessionModel) {
2434
+ if (!Array.isArray(output)) return undefined;
2435
+ let changed = false;
2436
+ const rewritten = output.map((item) => {
2437
+ const next = rewriteNamespaceFunctionCallItem(item, lookups, sessionModel);
2438
+ if (!next) return item;
2439
+ changed = true;
2440
+ return next;
2441
+ });
2442
+ return changed ? rewritten : undefined;
2443
+ }
2444
+
2445
+ // Only the exact declared client-hook codec defers argument syntax to the
2446
+ // native hook. Identity, outer JSON, lifecycle and byte bounds stay enforced.
2447
+ function rawCodecItem(item, lookups) {
2448
+ return item?.type === "function_call" && item.namespace === undefined &&
2449
+ lookups?.customCodecs?.get(item.name)?.preserveRawArguments === true;
2450
+ }
2451
+
2452
+ function embeddedFunctionArgumentsAreUnambiguous(payload, lookups, rawArgumentsDone = false) {
2453
+ const safeItem = (item) =>
2454
+ item?.type !== "function_call" || rawCodecItem(item, lookups) ||
2455
+ jsonArgumentsAreUnambiguous(item.arguments, { allowEmpty: true });
2456
+ if (!safeItem(payload?.item)) return false;
2457
+ if (
2458
+ payload?.type === "response.function_call_arguments.done" &&
2459
+ !rawArgumentsDone &&
2460
+ !jsonArgumentsAreUnambiguous(payload.arguments, { allowEmpty: true })
2461
+ ) {
2462
+ return false;
2463
+ }
2464
+ for (const output of [payload?.output, payload?.response?.output]) {
2465
+ if (!Array.isArray(output)) continue;
2466
+ if (!output.every(safeItem)) return false;
2467
+ }
2468
+ return true;
2469
+ }
2470
+
2471
+ // Non-streaming Responses return completed function calls in an `output`
2472
+ // array instead of SSE `item` events. Restore both shapes through the same
2473
+ // exact request-local lookup so stream mode cannot change dispatch semantics.
2474
+ // Returns a copy only when at least one call was restored.
2475
+ export function rewriteNamespaceResponsePayload(payload, lookups, sessionModel) {
2476
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return undefined;
2477
+ let rewritten = rewriteNamespaceFunctionCall(payload, lookups, sessionModel) || payload;
2478
+ let changed = rewritten !== payload;
2479
+
2480
+ if (payload.type === "response.function_call_arguments.done") {
2481
+ const argumentsText = jsonArgumentsAreUnambiguous(rewritten.arguments, {
2482
+ allowEmpty: true,
2483
+ })
2484
+ ? coerceFunctionCallArguments(rewritten.arguments)
2485
+ : rewritten.arguments;
2486
+ if (argumentsText !== rewritten.arguments) {
2487
+ rewritten = { ...rewritten, arguments: argumentsText };
2488
+ changed = true;
2489
+ }
2490
+ }
2491
+
2492
+ const output = rewriteOutputItems(rewritten.output, lookups, sessionModel);
2493
+ if (output) {
2494
+ rewritten = { ...rewritten, output };
2495
+ changed = true;
2496
+ }
2497
+
2498
+ const responseOutput = rewriteOutputItems(rewritten.response?.output, lookups, sessionModel);
2499
+ if (responseOutput) {
2500
+ rewritten = {
2501
+ ...rewritten,
2502
+ response: { ...rewritten.response, output: responseOutput },
2503
+ };
2504
+ changed = true;
2505
+ }
2506
+ return changed ? rewritten : undefined;
2507
+ }
2508
+
2509
+ // Inject missing collaboration.interrupt_agent calls for children that already
2510
+ // finished (FINAL_ANSWER in the request input) when the model forgot to close
2511
+ // them. Codex 0.147 keeps those children Working until interrupt_agent runs or
2512
+ // the user opens the child. Sequence numbers continue after the last model
2513
+ // event so Codex accepts the spliced calls as part of the same response.
2514
+ function nextSequence(event, lastSequence) {
2515
+ const value = Number(event?.sequence_number);
2516
+ return Number.isFinite(value) ? value : lastSequence;
2517
+ }
2518
+
2519
+ function trackInterruptFromItem(item, interrupted) {
2520
+ if (!item) return;
2521
+ const target = interruptTargetFromCall(item);
2522
+ if (target) interrupted.add(target);
2523
+ }
2524
+
2525
+ function appendInterruptCallsToOutput(output, pending, interrupted) {
2526
+ const remaining = filterAlreadyInterrupted(pending, interrupted);
2527
+ if (!remaining.length) return { output, injected: 0, remaining: [] };
2528
+ const base = Array.isArray(output) ? [...output] : [];
2529
+ for (const item of base) trackInterruptFromItem(item, interrupted);
2530
+ const still = filterAlreadyInterrupted(remaining, interrupted);
2531
+ for (const target of still) {
2532
+ const call = buildInterruptAgentCall(target);
2533
+ base.push(call);
2534
+ interrupted.add(target);
2535
+ }
2536
+ return { output: base, injected: still.length, remaining: still };
2537
+ }
2538
+
2539
+ const CUSTOM_TOOL_OPENING_LIMIT = 1024;
2540
+ const LITELLM_CUSTOM_TOOL_INPUT_PROPERTY = "content";
2541
+ const CUSTOM_TOOL_OPENING_PATTERNS = Object.freeze({
2542
+ [CUSTOM_TOOL_INPUT_PROPERTY]: /^\s*\{\s*"input"\s*:\s*"/,
2543
+ [LITELLM_CUSTOM_TOOL_INPUT_PROPERTY]: /^\s*\{\s*"content"\s*:\s*"/,
2544
+ });
2545
+ const JSON_ESCAPES = Object.freeze({
2546
+ '"': '"',
2547
+ "\\": "\\",
2548
+ "/": "/",
2549
+ b: "\b",
2550
+ f: "\f",
2551
+ n: "\n",
2552
+ r: "\r",
2553
+ t: "\t",
2554
+ });
2555
+
2556
+ // Decode one new function-argument fragment into the native custom-tool input.
2557
+ // The wrapper prefix is bounded and every encoded patch character is visited
2558
+ // once. Only an incomplete escape (at most six characters) is retained between
2559
+ // calls, avoiding the quadratic full-patch rescans that large streamed patches
2560
+ // would otherwise trigger.
2561
+ function customToolInputDelta(
2562
+ state,
2563
+ fragment,
2564
+ property = CUSTOM_TOOL_INPUT_PROPERTY,
2565
+ ) {
2566
+ if (typeof fragment !== "string" || state.invalid || state.closed) return undefined;
2567
+ let encoded = fragment;
2568
+ if (!state.opened) {
2569
+ state.opening += encoded;
2570
+ const openingPattern = CUSTOM_TOOL_OPENING_PATTERNS[property];
2571
+ if (!openingPattern) {
2572
+ state.opening = "";
2573
+ state.invalid = true;
2574
+ return undefined;
2575
+ }
2576
+ const opening = state.opening.match(openingPattern);
2577
+ if (!opening) {
2578
+ if (state.opening.length > CUSTOM_TOOL_OPENING_LIMIT) {
2579
+ state.opening = "";
2580
+ state.invalid = true;
2581
+ }
2582
+ return undefined;
2583
+ }
2584
+ state.opened = true;
2585
+ encoded = state.opening.slice(opening[0].length);
2586
+ state.opening = "";
2587
+ }
2588
+
2589
+ if (state.escape) {
2590
+ encoded = state.escape + encoded;
2591
+ state.escape = "";
2592
+ }
2593
+ const decoded = [];
2594
+ for (let index = 0; index < encoded.length; index += 1) {
2595
+ const character = encoded[index];
2596
+ if (character === '"') {
2597
+ state.closed = true;
2598
+ break;
2599
+ }
2600
+ if (character !== "\\") {
2601
+ if (character.charCodeAt(0) < 0x20) {
2602
+ state.invalid = true;
2603
+ break;
2604
+ }
2605
+ decoded.push(character);
2606
+ continue;
2607
+ }
2608
+
2609
+ const escape = encoded[index + 1];
2610
+ if (escape === undefined) {
2611
+ state.escape = "\\";
2612
+ break;
2613
+ }
2614
+ if (escape === "u") {
2615
+ const digits = encoded.slice(index + 2, index + 6);
2616
+ if (!/^[0-9a-fA-F]*$/.test(digits)) {
2617
+ state.invalid = true;
2618
+ break;
2619
+ }
2620
+ if (digits.length < 4) {
2621
+ state.escape = encoded.slice(index);
2622
+ break;
2623
+ }
2624
+ decoded.push(String.fromCharCode(Number.parseInt(digits, 16)));
2625
+ index += 5;
2626
+ continue;
2627
+ }
2628
+ if (!(escape in JSON_ESCAPES)) {
2629
+ state.invalid = true;
2630
+ break;
2631
+ }
2632
+ decoded.push(JSON_ESCAPES[escape]);
2633
+ index += 1;
2634
+ }
2635
+ return decoded.length ? decoded.join("") : undefined;
2636
+ }
2637
+
2638
+ class NamespaceRelayCommittedStreamError extends Error {
2639
+ constructor(reason) {
2640
+ super(
2641
+ `The provider response became unsafe to relay after namespace output was committed (${reason}).`,
2642
+ );
2643
+ this.name = "NamespaceRelayCommittedStreamError";
2644
+ this.code = "ERR_NAMESPACE_RELAY_COMMITTED_STREAM";
2645
+ this.status = 502;
2646
+ }
2647
+ }
2648
+
2649
+ // Rewrites LiteLLM's flattened `<namespace>__<tool>` function calls back to
2650
+ // the namespace + name shape Codex dispatches through its app runtime.
2651
+ export class NamespaceToolCallTransform extends Transform {
2652
+ #eventStream;
2653
+ #pendingParts = [];
2654
+ #pendingBytes = 0;
2655
+ #pendingTailBytes = 0;
2656
+ #released = false;
2657
+ #headerlessDetector;
2658
+ #sseParts = [];
2659
+ #sseBytes = 0;
2660
+ #sseTailBytes = 0;
2661
+ #sseNextPartBytes = INITIAL_SSE_CAPTURE_PART_BYTES;
2662
+ #sseLineBytes = 0;
2663
+ #ssePendingCr = false;
2664
+ #ssePendingLineWasBlank = false;
2665
+ #sseAtStreamStart = true;
2666
+ #sseLineEnding = "\n";
2667
+ #sseLineEndingObserved = false;
2668
+ #maxJsonCaptureBytes;
2669
+ #maxSseFrameBytes;
2670
+ #maxCommittedSseFrameBytes;
2671
+ #maxTrackedOutputItems;
2672
+ #maxTrackedStateBytes;
2673
+ #rewriteDisabled = false;
2674
+ #semanticMutationCommitted = false;
2675
+ #requiresCodec = false;
2676
+ #lookups;
2677
+ #sessionModel;
2678
+ #pendingInterrupts;
2679
+ #injectOnly = false;
2680
+ #interruptedTargets = new Set();
2681
+ #lastSequence = 0;
2682
+ #injectQueue = [];
2683
+ #injectionsDone = false;
2684
+ #lastInjectedCalls = [];
2685
+ // Every observed output-item identity reserves both ids. Special relays keep
2686
+ // their source and native shapes here until terminal validation so a stream
2687
+ // cannot change owners or fall back to raw function-call events after its
2688
+ // opening was rewritten.
2689
+ #callsByItemId = new Map();
2690
+ #callsByCallId = new Map();
2691
+ #trackedCallCount = 0;
2692
+ #trackedStateBytes = 0;
2693
+
2694
+ constructor(namespaces, contentType = "", sessionModel, options = {}) {
2695
+ super();
2696
+ this.#lookups = buildNamespaceLookups(namespaces);
2697
+ this.#sessionModel = sessionModel;
2698
+ this.#pendingInterrupts = Array.isArray(options.pendingInterrupts)
2699
+ ? [...options.pendingInterrupts]
2700
+ : [];
2701
+ // Native turns attach this transform only to close finished children. A
2702
+ // native stream is otherwise relayed byte-identical, so inject-only mode
2703
+ // must not run the namespace rewrites (they exist for routed providers)
2704
+ // or re-serialize model-authored events it did not change.
2705
+ this.#injectOnly = Boolean(options.injectOnly);
2706
+ this.#requiresCodec = !this.#injectOnly && this.#lookups.customCodecs?.size > 0;
2707
+ this.#maxJsonCaptureBytes =
2708
+ Number.isInteger(options.maxJsonCaptureBytes) && options.maxJsonCaptureBytes > 0
2709
+ ? options.maxJsonCaptureBytes
2710
+ : MAX_JSON_CAPTURE_BYTES;
2711
+ const configuredSseFrameBytes =
2712
+ Number.isInteger(options.maxSseFrameBytes) && options.maxSseFrameBytes > 0
2713
+ ? options.maxSseFrameBytes
2714
+ : undefined;
2715
+ this.#maxSseFrameBytes = configuredSseFrameBytes ?? MAX_SSE_FRAME_BYTES;
2716
+ this.#maxCommittedSseFrameBytes =
2717
+ Number.isInteger(options.maxCommittedSseFrameBytes) &&
2718
+ options.maxCommittedSseFrameBytes > 0
2719
+ ? options.maxCommittedSseFrameBytes
2720
+ : configuredSseFrameBytes ??
2721
+ Math.min(this.#maxJsonCaptureBytes, MAX_COMMITTED_SSE_FRAME_BYTES);
2722
+ this.#maxTrackedOutputItems =
2723
+ Number.isInteger(options.maxTrackedOutputItems) && options.maxTrackedOutputItems > 0
2724
+ ? options.maxTrackedOutputItems
2725
+ : MAX_TRACKED_OUTPUT_ITEMS;
2726
+ this.#maxTrackedStateBytes =
2727
+ Number.isInteger(options.maxTrackedStateBytes) && options.maxTrackedStateBytes > 0
2728
+ ? options.maxTrackedStateBytes
2729
+ : MAX_TRACKED_STATE_BYTES;
2730
+ const declared = String(contentType).toLowerCase();
2731
+ this.#eventStream = declared.includes("text/event-stream");
2732
+ this.#headerlessDetector =
2733
+ !this.#eventStream && !declared.includes("json")
2734
+ ? new HeaderlessSseDetector()
2735
+ : undefined;
2736
+ }
2737
+
2738
+ _transform(chunk, _encoding, callback) {
2739
+ let error;
2740
+ try {
2741
+ if (this.#headerlessDetector) {
2742
+ const detected = this.#headerlessDetector.write(chunk);
2743
+ if (detected.decision !== "pending") {
2744
+ this.#headerlessDetector = undefined;
2745
+ this.#eventStream = detected.decision === "event-stream";
2746
+ for (const buffered of detected.chunks) this.#transformChunk(buffered);
2747
+ }
2748
+ } else {
2749
+ this.#transformChunk(chunk);
2750
+ }
2751
+ } catch (caught) {
2752
+ error = caught;
2753
+ }
2754
+ callback(error);
2755
+ }
2756
+
2757
+ #transformChunk(chunk) {
2758
+ const bytes = Buffer.from(chunk);
2759
+ if (!this.#eventStream) {
2760
+ if (this.#released) {
2761
+ this.push(bytes);
2762
+ return;
2763
+ }
2764
+ this.#captureJsonBytes(bytes);
2765
+ return;
2766
+ }
2767
+ if (this.#rewriteDisabled) {
2768
+ this.push(bytes);
2769
+ return;
2770
+ }
2771
+ this.#consumeSseChunk(bytes);
2772
+ }
2773
+
2774
+ _flush(callback) {
2775
+ let error;
2776
+ try {
2777
+ this.#flushTransform();
2778
+ } catch (caught) {
2779
+ error = caught;
2780
+ }
2781
+ callback(error);
2782
+ }
2783
+
2784
+ #flushTransform() {
2785
+ if (this.#headerlessDetector) {
2786
+ const detected = this.#headerlessDetector.end();
2787
+ this.#headerlessDetector = undefined;
2788
+ this.#eventStream = detected.decision === "event-stream";
2789
+ for (const buffered of detected.chunks) this.#transformChunk(buffered);
2790
+ }
2791
+ if (!this.#eventStream) {
2792
+ const body = this.#takeJsonCapture();
2793
+ if (this.#released || !body.length) {
2794
+ if (body.length) this.push(body);
2795
+ return;
2796
+ }
2797
+ if (!isUtf8(body)) {
2798
+ this.#rejectCodecPassthrough("invalid UTF-8 JSON response");
2799
+ this.push(body);
2800
+ return;
2801
+ }
2802
+ const text = body.toString("utf8");
2803
+ if (!jsonIsUnambiguousForRewrite(text)) {
2804
+ this.#rejectCodecPassthrough("ambiguous or invalid JSON response");
2805
+ this.push(body);
2806
+ return;
2807
+ }
2808
+ let original;
2809
+ try {
2810
+ original = JSON.parse(text);
2811
+ } catch {
2812
+ this.#rejectCodecPassthrough("invalid JSON response");
2813
+ this.push(body);
2814
+ return;
2815
+ }
2816
+ if (!embeddedFunctionArgumentsAreUnambiguous(original, this.#lookups)) {
2817
+ this.#rejectCodecPassthrough("ambiguous function arguments");
2818
+ this.push(body);
2819
+ return;
2820
+ }
2821
+ let payload = original;
2822
+ if (!this.#injectOnly) {
2823
+ const rewritten = rewriteNamespaceResponsePayload(
2824
+ payload,
2825
+ this.#lookups,
2826
+ this.#sessionModel,
2827
+ );
2828
+ if (rewritten) payload = rewritten;
2829
+ }
2830
+ if (this.#requiresCodec) {
2831
+ const reason = this.#validateOutputItems(original, payload, { allowAtomic: true });
2832
+ if (reason) this.#rejectCodecPassthrough(reason);
2833
+ if (original?.item) {
2834
+ const itemReason = this.#registerAtomicOutputItem(original.item, payload.item);
2835
+ if (itemReason) this.#rejectCodecPassthrough(itemReason);
2836
+ }
2837
+ }
2838
+ payload = this.#injectJsonInterrupts(payload);
2839
+ // Parsing is only permission to inspect. A response the transform did
2840
+ // not semantically change retains its exact original representation.
2841
+ if (payload !== original) this.#commitSemanticMutation();
2842
+ this.push(payload === original ? body : Buffer.from(JSON.stringify(payload), "utf8"));
2843
+ return;
2844
+ }
2845
+ if (this.#ssePendingCr && !this.#rewriteDisabled) {
2846
+ const blankLine = this.#ssePendingLineWasBlank;
2847
+ this.#ssePendingCr = false;
2848
+ this.#ssePendingLineWasBlank = false;
2849
+ this.#completeSseLine(blankLine);
2850
+ }
2851
+ let tailSeparator;
2852
+ if (!this.#rewriteDisabled && this.#sseBytes) {
2853
+ const tail = this.#takeSseFrame();
2854
+ this.#emitSseFrame(tail);
2855
+ tailSeparator = this.#separatorAfterSseTail(tail);
2856
+ }
2857
+ if (!this.#rewriteDisabled && this.#hasOpenSpecialCalls()) {
2858
+ if (this.#semanticMutationCommitted || this.#requiresCodec) {
2859
+ throw new NamespaceRelayCommittedStreamError("unterminated special tool call");
2860
+ }
2861
+ this.#disableSseRewriting();
2862
+ }
2863
+ // Streams that omit response.completed / [DONE] still need the closes,
2864
+ // unless an ambiguous frame made observing prior calls unsafe.
2865
+ if (!this.#rewriteDisabled) {
2866
+ const blocks = this.#drainInterruptBlocks();
2867
+ if (blocks.length && tailSeparator?.length) this.push(tailSeparator);
2868
+ for (const piece of blocks) this.push(piece);
2869
+ }
2870
+ }
2871
+
2872
+ #captureJsonBytes(bytes) {
2873
+ // Fixed-size parts keep both copies and metadata bounded even when an
2874
+ // upstream fragments a body into one-byte chunks. Each byte is copied once
2875
+ // while capturing and at most once more for the final JSON parse.
2876
+ let offset = 0;
2877
+ while (offset < bytes.length && !this.#released) {
2878
+ let tail = this.#pendingParts.at(-1);
2879
+ if (!tail || this.#pendingTailBytes === tail.length) {
2880
+ const remainingUntilRelease =
2881
+ this.#maxJsonCaptureBytes + 1 - this.#pendingBytes;
2882
+ tail = Buffer.allocUnsafe(
2883
+ Math.min(CAPTURE_PART_BYTES, remainingUntilRelease),
2884
+ );
2885
+ this.#pendingParts.push(tail);
2886
+ this.#pendingTailBytes = 0;
2887
+ }
2888
+ const copied = Math.min(tail.length - this.#pendingTailBytes, bytes.length - offset);
2889
+ bytes.copy(
2890
+ tail,
2891
+ this.#pendingTailBytes,
2892
+ offset,
2893
+ offset + copied,
2894
+ );
2895
+ this.#pendingTailBytes += copied;
2896
+ this.#pendingBytes += copied;
2897
+ offset += copied;
2898
+ if (this.#pendingBytes > this.#maxJsonCaptureBytes) {
2899
+ this.#rejectCodecPassthrough("JSON response byte limit");
2900
+ for (let index = 0; index < this.#pendingParts.length; index += 1) {
2901
+ const part = this.#pendingParts[index];
2902
+ this.push(
2903
+ index === this.#pendingParts.length - 1
2904
+ ? part.subarray(0, this.#pendingTailBytes)
2905
+ : part,
2906
+ );
2907
+ }
2908
+ this.#pendingParts = [];
2909
+ this.#pendingBytes = 0;
2910
+ this.#pendingTailBytes = 0;
2911
+ this.#released = true;
2912
+ }
2913
+ }
2914
+ if (offset < bytes.length) this.push(bytes.subarray(offset));
2915
+ }
2916
+
2917
+ #takeJsonCapture() {
2918
+ if (!this.#pendingParts.length) return Buffer.alloc(0);
2919
+ const lastIndex = this.#pendingParts.length - 1;
2920
+ const parts = this.#pendingParts.map((part, index) =>
2921
+ index === lastIndex ? part.subarray(0, this.#pendingTailBytes) : part,
2922
+ );
2923
+ const body =
2924
+ parts.length === 1 ? parts[0] : Buffer.concat(parts, this.#pendingBytes);
2925
+ this.#pendingParts = [];
2926
+ this.#pendingBytes = 0;
2927
+ this.#pendingTailBytes = 0;
2928
+ return body;
2929
+ }
2930
+
2931
+ #separatorAfterSseTail(frame) {
2932
+ if (!frame.length) return Buffer.alloc(0);
2933
+ const last = frame[frame.length - 1];
2934
+ const missingLineEndings = last === CARRIAGE_RETURN || last === LINE_FEED ? 1 : 2;
2935
+ return Buffer.from(this.#sseLineEnding.repeat(missingLineEndings), "utf8");
2936
+ }
2937
+
2938
+ #consumeSseChunk(chunk) {
2939
+ let offset = 0;
2940
+ if (this.#ssePendingCr) {
2941
+ const followedByLf = chunk[0] === LINE_FEED;
2942
+ if (followedByLf) {
2943
+ if (!this.#appendSsePiece(chunk.subarray(0, 1))) {
2944
+ if (chunk.length > 1) this.push(chunk.subarray(1));
2945
+ return;
2946
+ }
2947
+ offset = 1;
2948
+ }
2949
+ const blankLine = this.#ssePendingLineWasBlank;
2950
+ this.#ssePendingCr = false;
2951
+ this.#ssePendingLineWasBlank = false;
2952
+ this.#completeSseLine(blankLine);
2953
+ if (this.#rewriteDisabled) {
2954
+ if (offset < chunk.length) this.push(chunk.subarray(offset));
2955
+ return;
2956
+ }
2957
+ }
2958
+
2959
+ while (offset < chunk.length) {
2960
+ let lineEnd = offset;
2961
+ while (
2962
+ lineEnd < chunk.length &&
2963
+ chunk[lineEnd] !== CARRIAGE_RETURN &&
2964
+ chunk[lineEnd] !== LINE_FEED
2965
+ ) {
2966
+ lineEnd += 1;
2967
+ }
2968
+
2969
+ if (lineEnd === chunk.length) {
2970
+ const content = chunk.subarray(offset);
2971
+ this.#sseLineBytes += content.length;
2972
+ this.#appendSsePiece(content);
2973
+ return;
2974
+ }
2975
+ if (lineEnd > offset) {
2976
+ const content = chunk.subarray(offset, lineEnd);
2977
+ this.#sseLineBytes += content.length;
2978
+ if (!this.#appendSsePiece(content)) {
2979
+ this.push(chunk.subarray(lineEnd));
2980
+ return;
2981
+ }
2982
+ }
2983
+
2984
+ const blankLine = this.#sseLineBytes === 0;
2985
+ if (chunk[lineEnd] === CARRIAGE_RETURN) {
2986
+ if (!this.#appendSsePiece(chunk.subarray(lineEnd, lineEnd + 1))) {
2987
+ if (lineEnd + 1 < chunk.length) this.push(chunk.subarray(lineEnd + 1));
2988
+ return;
2989
+ }
2990
+ if (lineEnd + 1 === chunk.length) {
2991
+ this.#ssePendingCr = true;
2992
+ this.#ssePendingLineWasBlank = blankLine;
2993
+ this.#sseLineBytes = 0;
2994
+ return;
2995
+ }
2996
+ if (chunk[lineEnd + 1] === LINE_FEED) {
2997
+ if (!this.#appendSsePiece(chunk.subarray(lineEnd + 1, lineEnd + 2))) {
2998
+ if (lineEnd + 2 < chunk.length) this.push(chunk.subarray(lineEnd + 2));
2999
+ return;
3000
+ }
3001
+ offset = lineEnd + 2;
3002
+ } else {
3003
+ offset = lineEnd + 1;
3004
+ }
3005
+ } else {
3006
+ if (!this.#appendSsePiece(chunk.subarray(lineEnd, lineEnd + 1))) {
3007
+ if (lineEnd + 1 < chunk.length) this.push(chunk.subarray(lineEnd + 1));
3008
+ return;
3009
+ }
3010
+ offset = lineEnd + 1;
3011
+ }
3012
+ this.#completeSseLine(blankLine);
3013
+ if (this.#rewriteDisabled) {
3014
+ if (offset < chunk.length) this.push(chunk.subarray(offset));
3015
+ return;
3016
+ }
3017
+ }
3018
+ }
3019
+
3020
+ #appendSsePiece(piece) {
3021
+ if (!piece.length) return true;
3022
+ // Copy into fixed-size parts exactly as the non-streaming capture does.
3023
+ // Retaining one view per upstream chunk would let a one-byte-fragmented
3024
+ // frame allocate millions of Buffer objects before reaching its byte cap.
3025
+ let offset = 0;
3026
+ const frameByteLimit = this.#semanticMutationCommitted
3027
+ ? this.#maxCommittedSseFrameBytes
3028
+ : this.#maxSseFrameBytes;
3029
+ while (offset < piece.length && this.#sseBytes <= frameByteLimit) {
3030
+ let tail = this.#sseParts.at(-1);
3031
+ if (!tail || this.#sseTailBytes === tail.length) {
3032
+ const remainingUntilRelease =
3033
+ frameByteLimit + 1 - this.#sseBytes;
3034
+ const remainingPieceBytes = piece.length - offset;
3035
+ const partBytes = Math.min(
3036
+ CAPTURE_PART_BYTES,
3037
+ Math.max(this.#sseNextPartBytes, Math.min(CAPTURE_PART_BYTES, remainingPieceBytes)),
3038
+ remainingUntilRelease,
3039
+ );
3040
+ tail = Buffer.allocUnsafe(partBytes);
3041
+ this.#sseParts.push(tail);
3042
+ this.#sseTailBytes = 0;
3043
+ this.#sseNextPartBytes = Math.min(CAPTURE_PART_BYTES, partBytes * 2);
3044
+ }
3045
+ const copied = Math.min(tail.length - this.#sseTailBytes, piece.length - offset);
3046
+ piece.copy(tail, this.#sseTailBytes, offset, offset + copied);
3047
+ this.#sseTailBytes += copied;
3048
+ this.#sseBytes += copied;
3049
+ offset += copied;
3050
+ }
3051
+ if (this.#sseBytes <= frameByteLimit) return true;
3052
+ const buffered = this.#takeSseFrame();
3053
+ if (this.#semanticMutationCommitted || this.#requiresCodec) {
3054
+ throw new NamespaceRelayCommittedStreamError("SSE frame byte limit");
3055
+ }
3056
+ this.#disableSseRewriting();
3057
+ this.push(buffered);
3058
+ if (offset < piece.length) this.push(piece.subarray(offset));
3059
+ return false;
3060
+ }
3061
+
3062
+ #completeSseLine(blankLine) {
3063
+ this.#sseLineBytes = 0;
3064
+ if (blankLine) this.#emitSseFrame(this.#takeSseFrame());
3065
+ }
3066
+
3067
+ #takeSseFrame() {
3068
+ if (!this.#sseParts.length) return Buffer.alloc(0);
3069
+ const lastIndex = this.#sseParts.length - 1;
3070
+ const parts = this.#sseParts.map((part, index) =>
3071
+ index === lastIndex ? part.subarray(0, this.#sseTailBytes) : part,
3072
+ );
3073
+ const frame = parts.length === 1 ? parts[0] : Buffer.concat(parts, this.#sseBytes);
3074
+ this.#sseParts = [];
3075
+ this.#sseBytes = 0;
3076
+ this.#sseTailBytes = 0;
3077
+ this.#sseNextPartBytes = INITIAL_SSE_CAPTURE_PART_BYTES;
3078
+ this.#sseLineBytes = 0;
3079
+ this.#ssePendingCr = false;
3080
+ this.#ssePendingLineWasBlank = false;
3081
+ return frame;
3082
+ }
3083
+
3084
+ #sseFields(frame, atStreamStart) {
3085
+ let eventLine;
3086
+ let dataLine;
3087
+ let lineEndingLine;
3088
+ let repeated = false;
3089
+ let start = 0;
3090
+ let firstLine = true;
3091
+ while (start < frame.length) {
3092
+ let contentEnd = start;
3093
+ while (
3094
+ contentEnd < frame.length &&
3095
+ frame[contentEnd] !== CARRIAGE_RETURN &&
3096
+ frame[contentEnd] !== LINE_FEED
3097
+ ) {
3098
+ contentEnd += 1;
3099
+ }
3100
+ const end = contentEnd === frame.length
3101
+ ? frame.length
3102
+ : frame[contentEnd] === CARRIAGE_RETURN && frame[contentEnd + 1] === LINE_FEED
3103
+ ? contentEnd + 2
3104
+ : contentEnd + 1;
3105
+ let parseStart = start;
3106
+ if (
3107
+ firstLine &&
3108
+ atStreamStart &&
3109
+ frame.subarray(start, start + UTF8_BOM.length).equals(UTF8_BOM)
3110
+ ) {
3111
+ parseStart += UTF8_BOM.length;
3112
+ }
3113
+ firstLine = false;
3114
+ const line = { start, parseStart, contentEnd, end };
3115
+ if (!lineEndingLine && end > contentEnd) lineEndingLine = line;
3116
+ const lineLength = contentEnd - parseStart;
3117
+ const fieldLine = (name) =>
3118
+ lineLength >= name.length &&
3119
+ frame.subarray(parseStart, parseStart + name.length).equals(name) &&
3120
+ (lineLength === name.length || frame[parseStart + name.length] === 0x3a);
3121
+ if (fieldLine(SSE_EVENT_FIELD)) {
3122
+ if (eventLine) repeated = true;
3123
+ else eventLine = line;
3124
+ }
3125
+ if (fieldLine(SSE_DATA_FIELD)) {
3126
+ if (dataLine) repeated = true;
3127
+ else dataLine = line;
3128
+ }
3129
+ if (repeated || contentEnd === frame.length) break;
3130
+ start = end;
3131
+ }
3132
+ return { eventLine, dataLine, lineEndingLine, repeated };
3133
+ }
3134
+
3135
+ #rememberSseLineEnding(frame, line) {
3136
+ if (this.#sseLineEndingObserved || !line || line.end === line.contentEnd) return;
3137
+ const terminator = frame.subarray(line.contentEnd, line.end);
3138
+ if (terminator.equals(Buffer.from("\r\n"))) this.#sseLineEnding = "\r\n";
3139
+ else if (terminator.equals(Buffer.from("\n"))) this.#sseLineEnding = "\n";
3140
+ else if (terminator.equals(Buffer.from("\r"))) this.#sseLineEnding = "\r";
3141
+ else return;
3142
+ this.#sseLineEndingObserved = true;
3143
+ }
3144
+
3145
+ #emitSseFrame(frame) {
3146
+ for (const piece of this.#rewriteSseFrame(frame)) this.push(piece);
3147
+ }
3148
+
3149
+ #commitSemanticMutation() {
3150
+ this.#semanticMutationCommitted = true;
3151
+ }
3152
+
3153
+ #unsafeSseFrame(frame, reason) {
3154
+ if (this.#semanticMutationCommitted || this.#requiresCodec) {
3155
+ throw new NamespaceRelayCommittedStreamError(reason);
3156
+ }
3157
+ this.#disableSseRewriting();
3158
+ return [frame];
3159
+ }
3160
+
3161
+ #rejectCodecPassthrough(reason) {
3162
+ if (this.#requiresCodec) throw new NamespaceRelayCommittedStreamError(reason);
3163
+ }
3164
+
3165
+ #nativeCodecBypass(item) {
3166
+ if (!this.#requiresCodec || item?.type !== "custom_tool_call") return false;
3167
+ let codecBackedName = false;
3168
+ for (const [providerName, native] of this.#lookups.customTools) {
3169
+ if (typeof native === "object") {
3170
+ // A declared namespaced custom tool owns its identity and has no codec.
3171
+ if (native.namespace === item.namespace && native.name === item.name) return false;
3172
+ } else if (native === item.name && this.#lookups.customCodecs.has(providerName)) {
3173
+ codecBackedName = true;
3174
+ }
3175
+ }
3176
+ // A raw call carrying a codec-backed bare name, with or without a namespace
3177
+ // nobody declared, would reach the client without the codec's checks.
3178
+ return codecBackedName;
3179
+ }
3180
+
3181
+ #disableSseRewriting() {
3182
+ this.#rewriteDisabled = true;
3183
+ this.#injectionsDone = true;
3184
+ this.#lastInjectedCalls = [];
3185
+ this.#callsByItemId.clear();
3186
+ this.#callsByCallId.clear();
3187
+ this.#trackedCallCount = 0;
3188
+ this.#trackedStateBytes = 0;
3189
+ }
3190
+
3191
+ #specialCallKind(item) {
3192
+ if (item?.type === "custom_tool_call") return "custom";
3193
+ if (item?.type === "tool_search_call") return "tool_search";
3194
+ return undefined;
3195
+ }
3196
+
3197
+ #sourceSpecialCallKind(item) {
3198
+ const nativeKind = this.#specialCallKind(item);
3199
+ if (nativeKind) return nativeKind;
3200
+ if (item?.type !== "function_call") return undefined;
3201
+ if (this.#lookups.customTools instanceof Map && this.#lookups.customTools.has(item.name)) {
3202
+ return "custom";
3203
+ }
3204
+ if (this.#lookups.functionRelays instanceof Map && this.#lookups.functionRelays.has(item.name)) {
3205
+ return "function_codec";
3206
+ }
3207
+ if (item.name === this.#lookups.toolSearch?.providerName) return "tool_search";
3208
+ return undefined;
3209
+ }
3210
+
3211
+ #hasOpenSpecialCalls() {
3212
+ for (const state of this.#callsByItemId.values()) {
3213
+ if (state.kind && !state.closed) return true;
3214
+ }
3215
+ return false;
3216
+ }
3217
+
3218
+ #openingIdentityConflict(item) {
3219
+ const byItemId =
3220
+ typeof item?.id === "string" ? this.#callsByItemId.get(item.id) : undefined;
3221
+ const byCallId =
3222
+ typeof item?.call_id === "string"
3223
+ ? this.#callsByCallId.get(item.call_id)
3224
+ : undefined;
3225
+ if (byItemId || byCallId) return "duplicate output item identity";
3226
+ return undefined;
3227
+ }
3228
+
3229
+ #storeCallState(state) {
3230
+ if (this.#trackedCallCount >= this.#maxTrackedOutputItems) {
3231
+ return "output item identity limit";
3232
+ }
3233
+ const retainedBytes = trackedStateBytes(state);
3234
+ if (retainedBytes > this.#maxTrackedStateBytes - this.#trackedStateBytes) {
3235
+ return "output item state byte limit";
3236
+ }
3237
+ if (state.itemId) this.#callsByItemId.set(state.itemId, state);
3238
+ if (state.callId) this.#callsByCallId.set(state.callId, state);
3239
+ this.#trackedCallCount += 1;
3240
+ this.#trackedStateBytes += retainedBytes;
3241
+ return undefined;
3242
+ }
3243
+
3244
+ #registerCall(sourceItem, item) {
3245
+ if (this.#nativeCodecBypass(sourceItem)) return "structured tool bypassed its declared codec";
3246
+ const sourceKind = this.#sourceSpecialCallKind(sourceItem);
3247
+ const kind = sourceKind === "function_codec" ? "function_codec" : this.#specialCallKind(item);
3248
+ if ((sourceKind || kind) && sourceKind !== kind) {
3249
+ return "special tool call opening was not restored consistently";
3250
+ }
3251
+ const functionRelay = sourceKind === "function_codec"
3252
+ ? this.#lookups.functionRelays.get(sourceItem.name)
3253
+ : undefined;
3254
+ if (
3255
+ (kind === "custom" &&
3256
+ !customCallIdentityMatches(sourceItem, item, this.#lookups)) ||
3257
+ (kind === "function_codec" &&
3258
+ !functionRelayIdentityMatches(item, functionRelay)) ||
3259
+ (kind === "tool_search" &&
3260
+ (item.name !== undefined ||
3261
+ item.namespace !== undefined ||
3262
+ item.execution !== "client" ||
3263
+ !plainObject(item.arguments)))
3264
+ ) {
3265
+ return "special tool call opening is incomplete";
3266
+ }
3267
+ const itemId = typeof item?.id === "string" && item.id ? item.id : undefined;
3268
+ const callId =
3269
+ typeof item?.call_id === "string" && item.call_id ? item.call_id : undefined;
3270
+ if (
3271
+ kind &&
3272
+ (!itemId ||
3273
+ !callId ||
3274
+ sourceItem?.id !== itemId ||
3275
+ sourceItem?.call_id !== callId)
3276
+ ) {
3277
+ return "special tool call opening lacks stable identity";
3278
+ }
3279
+ if (!itemId && !callId) return undefined;
3280
+ const conflict = this.#openingIdentityConflict(item);
3281
+ if (conflict) return conflict;
3282
+ const state = {
3283
+ kind,
3284
+ itemId,
3285
+ callId,
3286
+ sourceType: sourceItem?.type,
3287
+ sourceName: sourceItem?.name,
3288
+ sourceNamespace: sourceItem?.namespace,
3289
+ outputType: item.type,
3290
+ outputName: item.name,
3291
+ outputNamespace: item.namespace,
3292
+ argumentsDone: false,
3293
+ finalInputLength: undefined,
3294
+ finalInputDigest: undefined,
3295
+ finalArgumentsLength: undefined,
3296
+ finalArgumentsDigest: undefined,
3297
+ sawArgumentDelta: false,
3298
+ deltaCharacters: 0,
3299
+ deltaHash: kind === "custom" ? createHash("sha256") : undefined,
3300
+ closed: false,
3301
+ summarySeen: false,
3302
+ deltaState:
3303
+ kind === "custom"
3304
+ ? {
3305
+ opening: "",
3306
+ opened: false,
3307
+ escape: "",
3308
+ closed: false,
3309
+ invalid: false,
3310
+ }
3311
+ : undefined,
3312
+ functionRelay,
3313
+ codec: sourceItem?.type === "function_call"
3314
+ ? this.#lookups.customCodecs?.get(sourceItem.name) ||
3315
+ (functionRelay
3316
+ ? { maxArgumentBytes: functionRelay.maxArgumentBytes }
3317
+ : undefined)
3318
+ : undefined,
3319
+ codecSourceHash: undefined,
3320
+ codecSourceCharacters: 0,
3321
+ codecSourceSeen: false,
3322
+ codecFinalLength: undefined,
3323
+ codecFinalDigest: undefined,
3324
+ codecOpening: undefined,
3325
+ };
3326
+ if (state.codec) {
3327
+ state.codecSourceHash = createHash("sha256");
3328
+ if (typeof sourceItem.arguments === "string" && sourceItem.arguments) {
3329
+ state.codecOpening = stringFingerprint(sourceItem.arguments);
3330
+ }
3331
+ }
3332
+ return this.#storeCallState(state);
3333
+ }
3334
+
3335
+ // Some Responses bridges omit output_item.added and emit only one complete
3336
+ // done item or a terminal output summary. Treat that self-contained item as
3337
+ // a closed lifecycle, but reserve its identities exactly like a streamed
3338
+ // opening so later events cannot change owners or replay it.
3339
+ #registerAtomicSpecialCall(sourceItem, item, { summarySeen = false } = {}) {
3340
+ if (this.#nativeCodecBypass(sourceItem)) return "structured tool bypassed its declared codec";
3341
+ const sourceKind = this.#sourceSpecialCallKind(sourceItem);
3342
+ const kind = sourceKind === "function_codec" ? "function_codec" : this.#specialCallKind(item);
3343
+ if (!sourceKind || sourceKind !== kind) {
3344
+ return "atomic special tool call was incomplete or restored inconsistently";
3345
+ }
3346
+
3347
+ const callId = typeof item?.call_id === "string" && item.call_id
3348
+ ? item.call_id
3349
+ : undefined;
3350
+ if (!callId || sourceItem?.call_id !== callId) {
3351
+ return "atomic special tool call lacks stable identity";
3352
+ }
3353
+ const sourceHasItemId = Object.hasOwn(sourceItem, "id");
3354
+ const outputHasItemId = Object.hasOwn(item, "id");
3355
+ if (
3356
+ sourceHasItemId !== outputHasItemId ||
3357
+ (sourceHasItemId &&
3358
+ (typeof item.id !== "string" || !item.id || sourceItem.id !== item.id))
3359
+ ) {
3360
+ return "atomic special tool call lacks stable identity";
3361
+ }
3362
+ const itemId = outputHasItemId ? item.id : undefined;
3363
+
3364
+ if (kind === "custom") {
3365
+ if (
3366
+ !customCallIdentityMatches(sourceItem, item, this.#lookups) ||
3367
+ typeof item.input !== "string"
3368
+ ) {
3369
+ return "incomplete atomic custom tool call";
3370
+ }
3371
+ if (sourceItem.type === "function_call") {
3372
+ if (
3373
+ customToolInput(sourceItem.arguments, false, CUSTOM_TOOL_INPUT_PROPERTY, this.#lookups.customCodecs?.get(sourceItem.name)) !== item.input
3374
+ ) {
3375
+ return "atomic custom tool call was not restored consistently";
3376
+ }
3377
+ } else if (
3378
+ sourceItem.input !== item.input
3379
+ ) {
3380
+ return "atomic custom tool call changed native content";
3381
+ }
3382
+ } else if (kind === "function_codec") {
3383
+ const relay = this.#lookups.functionRelays?.get(sourceItem.name);
3384
+ if (
3385
+ !functionRelayIdentityMatches(item, relay) ||
3386
+ typeof item.arguments !== "string"
3387
+ ) {
3388
+ return "incomplete atomic function relay call";
3389
+ }
3390
+ if (relay.rewriteArguments(sourceItem.arguments) !== item.arguments) {
3391
+ return "atomic function relay call was not restored consistently";
3392
+ }
3393
+ } else {
3394
+ if (
3395
+ item.name !== undefined ||
3396
+ item.namespace !== undefined ||
3397
+ item.execution !== "client" ||
3398
+ !plainObject(item.arguments)
3399
+ ) {
3400
+ return "incomplete atomic tool search call";
3401
+ }
3402
+ if (sourceItem.type === "function_call") {
3403
+ const sourceArguments = toolSearchArguments(sourceItem.arguments, false);
3404
+ if (
3405
+ sourceItem.name !== this.#lookups.toolSearch?.providerName ||
3406
+ !sourceArguments ||
3407
+ !isDeepStrictEqual(sourceArguments, item.arguments)
3408
+ ) {
3409
+ return "atomic tool search call was not restored consistently";
3410
+ }
3411
+ } else if (
3412
+ sourceItem.execution !== item.execution ||
3413
+ !isDeepStrictEqual(sourceItem.arguments, item.arguments)
3414
+ ) {
3415
+ return "atomic tool search call changed native content";
3416
+ }
3417
+ }
3418
+
3419
+ const conflict = this.#openingIdentityConflict(item);
3420
+ if (conflict) return conflict;
3421
+ const finalInputFingerprint = kind === "custom"
3422
+ ? stringFingerprint(item.input)
3423
+ : undefined;
3424
+ const finalArgumentsFingerprint = kind === "tool_search"
3425
+ ? canonicalJsonFingerprint(item.arguments)
3426
+ : kind === "function_codec"
3427
+ ? stringFingerprint(item.arguments)
3428
+ : undefined;
3429
+ const codec = sourceItem.type === "function_call"
3430
+ ? this.#lookups.customCodecs?.get(sourceItem.name) ||
3431
+ (kind === "function_codec"
3432
+ ? { maxArgumentBytes: this.#lookups.functionRelays?.get(sourceItem.name)?.maxArgumentBytes }
3433
+ : undefined)
3434
+ : undefined;
3435
+ const codecFingerprint = codec ? stringFingerprint(sourceItem.arguments) : undefined;
3436
+ const state = {
3437
+ kind,
3438
+ itemId,
3439
+ callId,
3440
+ sourceType: sourceItem.type,
3441
+ sourceName: sourceItem.name,
3442
+ sourceNamespace: sourceItem.namespace,
3443
+ outputType: item.type,
3444
+ outputName: item.name,
3445
+ outputNamespace: item.namespace,
3446
+ argumentsDone: true,
3447
+ finalInputLength: finalInputFingerprint?.length,
3448
+ finalInputDigest: finalInputFingerprint?.digest,
3449
+ finalArgumentsLength: finalArgumentsFingerprint?.length,
3450
+ finalArgumentsDigest: finalArgumentsFingerprint?.digest,
3451
+ sawArgumentDelta: false,
3452
+ deltaCharacters: 0,
3453
+ deltaHash: undefined,
3454
+ closed: true,
3455
+ summarySeen,
3456
+ deltaState: undefined,
3457
+ functionRelay: kind === "function_codec" ? this.#lookups.functionRelays?.get(sourceItem.name) : undefined,
3458
+ codec,
3459
+ codecFinalLength: codecFingerprint?.length,
3460
+ codecFinalDigest: codecFingerprint?.digest,
3461
+ };
3462
+ return this.#storeCallState(state);
3463
+ }
3464
+
3465
+ #registerOrdinaryOutputItem(
3466
+ sourceItem,
3467
+ item,
3468
+ { closed = true, summarySeen = false } = {},
3469
+ ) {
3470
+ // Ordinary items reserve the same id domains as special relays. This also
3471
+ // covers nonterminal response snapshots, which can precede an explicit
3472
+ // output_item.done event and therefore must not be marked closed yet.
3473
+ const itemId = typeof item?.id === "string" && item.id ? item.id : undefined;
3474
+ const callId = typeof item?.call_id === "string" && item.call_id
3475
+ ? item.call_id
3476
+ : undefined;
3477
+ const sourceItemId = typeof sourceItem?.id === "string" && sourceItem.id
3478
+ ? sourceItem.id
3479
+ : undefined;
3480
+ const sourceCallId = typeof sourceItem?.call_id === "string" && sourceItem.call_id
3481
+ ? sourceItem.call_id
3482
+ : undefined;
3483
+ const sourceHasItemId = Boolean(
3484
+ sourceItem && typeof sourceItem === "object" && Object.hasOwn(sourceItem, "id"),
3485
+ );
3486
+ const outputHasItemId = Boolean(
3487
+ item && typeof item === "object" && Object.hasOwn(item, "id"),
3488
+ );
3489
+ const sourceHasCallId = Boolean(
3490
+ sourceItem && typeof sourceItem === "object" && Object.hasOwn(sourceItem, "call_id"),
3491
+ );
3492
+ const outputHasCallId = Boolean(
3493
+ item && typeof item === "object" && Object.hasOwn(item, "call_id"),
3494
+ );
3495
+ if (
3496
+ sourceItemId !== itemId ||
3497
+ sourceCallId !== callId ||
3498
+ sourceHasItemId !== outputHasItemId ||
3499
+ sourceHasCallId !== outputHasCallId ||
3500
+ (sourceHasItemId && !itemId) ||
3501
+ (sourceHasCallId && !callId)
3502
+ ) {
3503
+ return "output item lacks stable identity";
3504
+ }
3505
+ if (!itemId && !callId) return undefined;
3506
+ const conflict = this.#openingIdentityConflict(item);
3507
+ if (conflict) return conflict;
3508
+ const state = {
3509
+ kind: undefined,
3510
+ itemId,
3511
+ callId,
3512
+ sourceType: sourceItem?.type,
3513
+ sourceName: sourceItem?.name,
3514
+ sourceNamespace: sourceItem?.namespace,
3515
+ outputType: item?.type,
3516
+ outputName: item?.name,
3517
+ outputNamespace: item?.namespace,
3518
+ argumentsDone: true,
3519
+ finalInputLength: undefined,
3520
+ finalInputDigest: undefined,
3521
+ finalArgumentsLength: undefined,
3522
+ finalArgumentsDigest: undefined,
3523
+ sawArgumentDelta: false,
3524
+ deltaCharacters: 0,
3525
+ deltaHash: undefined,
3526
+ closed,
3527
+ summarySeen,
3528
+ deltaState: undefined,
3529
+ };
3530
+ return this.#storeCallState(state);
3531
+ }
3532
+
3533
+ #registerAtomicOutputItem(sourceItem, item, { summarySeen = false } = {}) {
3534
+ const sourceKind = this.#sourceSpecialCallKind(sourceItem);
3535
+ const outputKind = this.#specialCallKind(item);
3536
+ if (sourceKind || outputKind) {
3537
+ return this.#registerAtomicSpecialCall(sourceItem, item, { summarySeen });
3538
+ }
3539
+ return this.#registerOrdinaryOutputItem(sourceItem, item, {
3540
+ closed: true,
3541
+ summarySeen,
3542
+ });
3543
+ }
3544
+
3545
+ #outputItemMatchesState(sourceItem, item, state) {
3546
+ return (
3547
+ sourceItem?.id === state.itemId &&
3548
+ sourceItem?.call_id === state.callId &&
3549
+ sourceItem?.type === state.sourceType &&
3550
+ sourceItem?.name === state.sourceName &&
3551
+ sourceItem?.namespace === state.sourceNamespace &&
3552
+ item?.id === state.itemId &&
3553
+ item?.call_id === state.callId &&
3554
+ item?.type === state.outputType &&
3555
+ item?.name === state.outputName &&
3556
+ item?.namespace === state.outputNamespace
3557
+ );
3558
+ }
3559
+
3560
+ #specialCallForArgumentsEvent(event) {
3561
+ const byItemId =
3562
+ typeof event?.item_id === "string"
3563
+ ? this.#callsByItemId.get(event.item_id)
3564
+ : undefined;
3565
+ const byCallId =
3566
+ typeof event?.call_id === "string"
3567
+ ? this.#callsByCallId.get(event.call_id)
3568
+ : undefined;
3569
+ if (byItemId && byCallId && byItemId !== byCallId) {
3570
+ return { reason: "conflicting special tool call identity" };
3571
+ }
3572
+ const state = byItemId || byCallId;
3573
+ if (!state) return this.#requiresCodec ? { reason: "arguments without a known output item" } : {};
3574
+ if (!state.kind) return {};
3575
+ if (event.item_id !== state.itemId) {
3576
+ return { reason: "mismatched special tool call item id" };
3577
+ }
3578
+ if (event.call_id !== undefined && event.call_id !== state.callId) {
3579
+ return { reason: "mismatched special tool call call id" };
3580
+ }
3581
+ if (state.closed) return { reason: "special tool call event after close" };
3582
+ return { state };
3583
+ }
3584
+
3585
+ #customDeltaMismatch(state, inputFingerprint) {
3586
+ if (state.codec) return undefined; // Source JSON is checked separately; no patch delta was emitted.
3587
+ if (!state.sawArgumentDelta) return undefined;
3588
+ // LiteLLM keeps arguments that are not a leading `content` wrapper verbatim,
3589
+ // so the incremental decoder cannot follow them. When it emitted no input
3590
+ // text, the client saw nothing the completed input could contradict.
3591
+ if (state.sourceType === "custom_tool_call" && state.deltaCharacters === 0) return undefined;
3592
+ if (
3593
+ state.deltaState.invalid ||
3594
+ !state.deltaState.opened ||
3595
+ !state.deltaState.closed ||
3596
+ state.deltaState.escape
3597
+ ) {
3598
+ return "incomplete custom tool argument delta sequence";
3599
+ }
3600
+ if (state.deltaCharacters !== inputFingerprint.length) {
3601
+ return "custom tool argument deltas disagree with completed input";
3602
+ }
3603
+ const streamed = state.deltaHash.copy().digest();
3604
+ return streamed.equals(inputFingerprint.digest)
3605
+ ? undefined
3606
+ : "custom tool argument deltas disagree with completed input";
3607
+ }
3608
+
3609
+ #validateCodecSource(state, argumentsText) {
3610
+ if (!state.codec) return undefined;
3611
+ if (typeof argumentsText !== "string" || Buffer.byteLength(argumentsText, "utf8") > state.codec.maxArgumentBytes) {
3612
+ return "invalid or oversized structured arguments";
3613
+ }
3614
+ const fingerprint = stringFingerprint(argumentsText);
3615
+ if (state.codecOpening && !fingerprintMatches(fingerprint, state.codecOpening.length, state.codecOpening.digest)) {
3616
+ return "structured arguments changed after opening";
3617
+ }
3618
+ if (state.codecFinalDigest) {
3619
+ return fingerprintMatches(fingerprint, state.codecFinalLength, state.codecFinalDigest)
3620
+ ? undefined : "structured arguments changed after completion";
3621
+ }
3622
+ if (state.codecSourceSeen && (
3623
+ state.codecSourceCharacters !== fingerprint.length ||
3624
+ !state.codecSourceHash.copy().digest().equals(fingerprint.digest)
3625
+ )) return "structured argument deltas disagree with completed arguments";
3626
+ state.codecFinalLength = fingerprint.length;
3627
+ state.codecFinalDigest = fingerprint.digest;
3628
+ state.codecSourceHash = undefined;
3629
+ state.codecOpening = undefined;
3630
+ return undefined;
3631
+ }
3632
+
3633
+ #closeOutputItem(sourceItem, item) {
3634
+ const byItemId =
3635
+ typeof sourceItem?.id === "string"
3636
+ ? this.#callsByItemId.get(sourceItem.id)
3637
+ : undefined;
3638
+ const byCallId =
3639
+ typeof sourceItem?.call_id === "string"
3640
+ ? this.#callsByCallId.get(sourceItem.call_id)
3641
+ : undefined;
3642
+ if (byItemId && byCallId && byItemId !== byCallId) {
3643
+ return "conflicting special tool call close identity";
3644
+ }
3645
+ const state = byItemId || byCallId;
3646
+ const sourceKind = this.#sourceSpecialCallKind(sourceItem);
3647
+ const outputKind = this.#specialCallKind(item);
3648
+ if (!state) {
3649
+ return this.#registerAtomicOutputItem(sourceItem, item);
3650
+ }
3651
+ if (state.closed) return "duplicate output item close";
3652
+ if (!this.#outputItemMatchesState(sourceItem, item, state)) {
3653
+ return state.kind || sourceKind || outputKind
3654
+ ? "mismatched special tool call close identity"
3655
+ : "mismatched output item close identity";
3656
+ }
3657
+ if (!state.kind) {
3658
+ state.closed = true;
3659
+ return undefined;
3660
+ }
3661
+ if (state.kind === "function_codec") {
3662
+ const codecReason = this.#validateCodecSource(state, sourceItem.arguments);
3663
+ if (codecReason) return codecReason;
3664
+ if (typeof item.arguments !== "string") {
3665
+ return "function relay arguments changed before close";
3666
+ }
3667
+ const argumentsFingerprint = stringFingerprint(item.arguments);
3668
+ if (
3669
+ state.argumentsDone &&
3670
+ !fingerprintMatches(
3671
+ argumentsFingerprint,
3672
+ state.finalArgumentsLength,
3673
+ state.finalArgumentsDigest,
3674
+ )
3675
+ ) {
3676
+ return "function relay arguments changed before close";
3677
+ }
3678
+ if (!state.argumentsDone) {
3679
+ const expected = state.functionRelay?.rewriteArguments(sourceItem.arguments);
3680
+ if (expected !== item.arguments) {
3681
+ return "function relay arguments changed before close";
3682
+ }
3683
+ state.finalArgumentsLength = argumentsFingerprint.length;
3684
+ state.finalArgumentsDigest = argumentsFingerprint.digest;
3685
+ }
3686
+ state.argumentsDone = true;
3687
+ state.deltaHash = undefined;
3688
+ state.deltaState = undefined;
3689
+ }
3690
+ if (state.kind === "custom") {
3691
+ const codecReason = this.#validateCodecSource(state, sourceItem.arguments);
3692
+ if (codecReason) return codecReason;
3693
+ if (typeof item.input !== "string") {
3694
+ return "custom tool call input changed before close";
3695
+ }
3696
+ const inputFingerprint = stringFingerprint(item.input);
3697
+ if (
3698
+ state.argumentsDone &&
3699
+ !fingerprintMatches(
3700
+ inputFingerprint,
3701
+ state.finalInputLength,
3702
+ state.finalInputDigest,
3703
+ )
3704
+ ) {
3705
+ return "custom tool call input changed before close";
3706
+ }
3707
+ if (!state.argumentsDone) {
3708
+ const reason = this.#customDeltaMismatch(state, inputFingerprint);
3709
+ if (reason) return reason;
3710
+ state.finalInputLength = inputFingerprint.length;
3711
+ state.finalInputDigest = inputFingerprint.digest;
3712
+ }
3713
+ state.argumentsDone = true;
3714
+ state.deltaHash = undefined;
3715
+ state.deltaState = undefined;
3716
+ }
3717
+ if (state.kind === "tool_search") {
3718
+ const argumentsFingerprint = canonicalJsonFingerprint(item.arguments);
3719
+ if (
3720
+ state.argumentsDone &&
3721
+ !fingerprintMatches(
3722
+ argumentsFingerprint,
3723
+ state.finalArgumentsLength,
3724
+ state.finalArgumentsDigest,
3725
+ )
3726
+ ) {
3727
+ return "tool search arguments changed before close";
3728
+ }
3729
+ state.finalArgumentsLength = argumentsFingerprint.length;
3730
+ state.finalArgumentsDigest = argumentsFingerprint.digest;
3731
+ state.argumentsDone = true;
3732
+ }
3733
+ state.closed = true;
3734
+ return undefined;
3735
+ }
3736
+
3737
+ #validateOutputSummaryItem(sourceItem, item, { allowAtomic = false } = {}) {
3738
+ const byItemId =
3739
+ typeof sourceItem?.id === "string"
3740
+ ? this.#callsByItemId.get(sourceItem.id)
3741
+ : undefined;
3742
+ const byCallId =
3743
+ typeof sourceItem?.call_id === "string"
3744
+ ? this.#callsByCallId.get(sourceItem.call_id)
3745
+ : undefined;
3746
+ if (byItemId && byCallId && byItemId !== byCallId) {
3747
+ return "conflicting special tool call summary identity";
3748
+ }
3749
+ const state = byItemId || byCallId;
3750
+ const sourceKind = this.#sourceSpecialCallKind(sourceItem);
3751
+ const outputKind = this.#specialCallKind(item);
3752
+ if (!state) {
3753
+ if (!allowAtomic) {
3754
+ if (sourceKind || outputKind) {
3755
+ return "special tool call summary without a matching opening";
3756
+ }
3757
+ return this.#registerOrdinaryOutputItem(sourceItem, item, {
3758
+ closed: false,
3759
+ summarySeen: false,
3760
+ });
3761
+ }
3762
+ return this.#registerAtomicOutputItem(sourceItem, item, { summarySeen: true });
3763
+ }
3764
+ if (!state.closed && state.kind) return "special tool call summary before close";
3765
+ if (state.summarySeen) return "duplicate output item summary";
3766
+ if (!this.#outputItemMatchesState(sourceItem, item, state)) {
3767
+ return state.kind || sourceKind || outputKind
3768
+ ? "mismatched special tool call summary identity"
3769
+ : "mismatched output item summary identity";
3770
+ }
3771
+ if (!state.kind) {
3772
+ // Progress snapshots may repeat the same ordinary item while its content
3773
+ // grows. They reserve and validate ownership, but only a terminal output
3774
+ // summary closes and consumes the one allowed summary transition.
3775
+ if (allowAtomic) {
3776
+ state.closed = true;
3777
+ state.summarySeen = true;
3778
+ }
3779
+ return undefined;
3780
+ }
3781
+ if (state.kind === "custom") {
3782
+ const codecReason = this.#validateCodecSource(state, sourceItem.arguments);
3783
+ if (codecReason) return codecReason;
3784
+ if (typeof item.input !== "string") {
3785
+ return "custom tool call summary input changed after close";
3786
+ }
3787
+ const inputFingerprint = stringFingerprint(item.input);
3788
+ if (
3789
+ !fingerprintMatches(
3790
+ inputFingerprint,
3791
+ state.finalInputLength,
3792
+ state.finalInputDigest,
3793
+ )
3794
+ ) {
3795
+ return "custom tool call summary input changed after close";
3796
+ }
3797
+ }
3798
+ if (state.kind === "tool_search") {
3799
+ const argumentsFingerprint = canonicalJsonFingerprint(item.arguments);
3800
+ if (
3801
+ !fingerprintMatches(
3802
+ argumentsFingerprint,
3803
+ state.finalArgumentsLength,
3804
+ state.finalArgumentsDigest,
3805
+ )
3806
+ ) {
3807
+ return "tool search arguments changed after close";
3808
+ }
3809
+ }
3810
+ if (state.kind === "function_codec") {
3811
+ const codecReason = this.#validateCodecSource(state, sourceItem.arguments);
3812
+ if (codecReason) return codecReason;
3813
+ if (typeof item.arguments !== "string") {
3814
+ return "function relay arguments changed after close";
3815
+ }
3816
+ const argumentsFingerprint = stringFingerprint(item.arguments);
3817
+ if (
3818
+ !fingerprintMatches(
3819
+ argumentsFingerprint,
3820
+ state.finalArgumentsLength,
3821
+ state.finalArgumentsDigest,
3822
+ )
3823
+ ) {
3824
+ return "function relay arguments changed after close";
3825
+ }
3826
+ }
3827
+ if (allowAtomic) state.summarySeen = true;
3828
+ return undefined;
3829
+ }
3830
+
3831
+ #validateOutputItems(sourceEvent, event, { allowAtomic = false } = {}) {
3832
+ for (const [sourceOutput, output] of [
3833
+ [sourceEvent?.output, event?.output],
3834
+ [sourceEvent?.response?.output, event?.response?.output],
3835
+ ]) {
3836
+ if (!Array.isArray(sourceOutput) || !Array.isArray(output)) continue;
3837
+ for (let index = 0; index < sourceOutput.length; index += 1) {
3838
+ const reason = this.#validateOutputSummaryItem(sourceOutput[index], output[index], {
3839
+ allowAtomic,
3840
+ });
3841
+ if (reason) return reason;
3842
+ }
3843
+ }
3844
+ return undefined;
3845
+ }
3846
+
3847
+ #rewrittenSseFrame(frame, replacements) {
3848
+ const pieces = [];
3849
+ let cursor = 0;
3850
+ for (const [line, replacement] of [...replacements].sort(
3851
+ ([left], [right]) => left.start - right.start,
3852
+ )) {
3853
+ if (!line) continue;
3854
+ if (line.start > cursor) pieces.push(frame.subarray(cursor, line.start));
3855
+ if (line.parseStart > line.start) {
3856
+ pieces.push(frame.subarray(line.start, line.parseStart));
3857
+ }
3858
+ pieces.push(Buffer.from(replacement, "utf8"));
3859
+ cursor = line.contentEnd;
3860
+ }
3861
+ if (cursor < frame.length) pieces.push(frame.subarray(cursor));
3862
+ return Buffer.concat(pieces);
3863
+ }
3864
+
3865
+ #rewriteSseFrame(frame) {
3866
+ const atStreamStart = this.#sseAtStreamStart;
3867
+ this.#sseAtStreamStart = false;
3868
+ // No decoder is allowed to see an undecided frame. Its replacement
3869
+ // character would make fail-open lossy before we knew whether to rewrite.
3870
+ if (!isUtf8(frame)) {
3871
+ return this.#unsafeSseFrame(frame, "invalid UTF-8");
3872
+ }
3873
+ const { eventLine, dataLine, lineEndingLine, repeated } = this.#sseFields(
3874
+ frame,
3875
+ atStreamStart,
3876
+ );
3877
+ this.#rememberSseLineEnding(frame, lineEndingLine);
3878
+ // SSE formally concatenates multiple data fields and gives repeated event
3879
+ // fields ordering semantics. Rewriting just one field would create bytes
3880
+ // whose EventSource meaning differs from the JSON we inspected. The
3881
+ // Responses wire uses exactly one of each, so anything else is preserved
3882
+ // and disables stateful rewriting for the remainder of this response.
3883
+ if (repeated) {
3884
+ return this.#unsafeSseFrame(frame, "repeated SSE event or data field");
3885
+ }
3886
+ const eventText = eventLine
3887
+ ? frame.subarray(eventLine.parseStart, eventLine.contentEnd).toString("utf8")
3888
+ : undefined;
3889
+ const eventName = eventText === undefined
3890
+ ? undefined
3891
+ : sseLineFieldValue(eventText, "event");
3892
+ const dataTextLine = dataLine
3893
+ ? frame.subarray(dataLine.parseStart, dataLine.contentEnd).toString("utf8")
3894
+ : undefined;
3895
+ const dataText = dataTextLine === undefined
3896
+ ? ""
3897
+ : sseLineFieldValue(dataTextLine, "data");
3898
+ if (!dataLine) return [frame];
3899
+ if (!dataText || dataText === "[DONE]") {
3900
+ if (eventName && eventName !== "message") {
3901
+ return this.#unsafeSseFrame(
3902
+ frame,
3903
+ "non-generic SSE event without a matching JSON type",
3904
+ );
3905
+ }
3906
+ // Inject before the stream terminator so Codex still executes the calls.
3907
+ if (dataText === "[DONE]") {
3908
+ if (this.#hasOpenSpecialCalls()) {
3909
+ return this.#unsafeSseFrame(frame, "stream ended before special tool call close");
3910
+ }
3911
+ return [...this.#drainInterruptBlocks(), frame];
3912
+ }
3913
+ return [frame];
3914
+ }
3915
+ if (!jsonIsUnambiguousForRewrite(dataText)) {
3916
+ return this.#unsafeSseFrame(frame, "ambiguous or malformed JSON event");
3917
+ }
3918
+ try {
3919
+ let event = JSON.parse(dataText);
3920
+ const payloadType = event?.type;
3921
+ const genericEventName = !eventName || eventName === "message";
3922
+ if (!genericEventName && payloadType !== eventName) {
3923
+ // The event field and JSON body are two claims about the same Responses
3924
+ // event. Do not choose whichever terminal/rewrite behavior is more
3925
+ // convenient when both claims are present and disagree.
3926
+ return this.#unsafeSseFrame(frame, "conflicting SSE event and JSON type");
3927
+ }
3928
+ const rawDoneMatch = event?.type === "response.function_call_arguments.done"
3929
+ ? this.#specialCallForArgumentsEvent(event) : undefined;
3930
+ // LiteLLM's native custom lifecycle keeps provider arguments verbatim when
3931
+ // they are not its JSON wrapper. They are not function arguments to
3932
+ // rewrite; the custom-tool check below validates them instead.
3933
+ const rawArgumentsDone = !rawDoneMatch?.reason && (
3934
+ rawDoneMatch?.state?.codec?.preserveRawArguments === true ||
3935
+ (rawDoneMatch?.state?.kind === "custom" &&
3936
+ rawDoneMatch.state.sourceType === "custom_tool_call" &&
3937
+ !rawDoneMatch.state.codec)
3938
+ );
3939
+ if (!embeddedFunctionArgumentsAreUnambiguous(event, this.#lookups, rawArgumentsDone)) {
3940
+ return this.#unsafeSseFrame(frame, "ambiguous function arguments");
3941
+ }
3942
+ const sourceEvent = event;
3943
+ const originalEventType = event?.type;
3944
+ let changed = false;
3945
+ if (sourceEvent?.type === "response.output_item.added") {
3946
+ const conflict = this.#openingIdentityConflict(sourceEvent.item);
3947
+ if (conflict) return this.#unsafeSseFrame(frame, conflict);
3948
+ }
3949
+ if (
3950
+ !this.#injectOnly &&
3951
+ (sourceEvent?.type === "response.custom_tool_call_input.delta" ||
3952
+ sourceEvent?.type === "response.custom_tool_call_input.done")
3953
+ ) {
3954
+ const matched = this.#specialCallForArgumentsEvent(sourceEvent);
3955
+ if (matched.reason) return this.#unsafeSseFrame(frame, matched.reason);
3956
+ if (matched.state?.sourceType === "function_call") {
3957
+ return this.#unsafeSseFrame(
3958
+ frame,
3959
+ "native custom input event inside a bridged function lifecycle",
3960
+ );
3961
+ }
3962
+ }
3963
+ if (
3964
+ !this.#injectOnly &&
3965
+ event?.type === "response.function_call_arguments.delta"
3966
+ ) {
3967
+ const matched = this.#specialCallForArgumentsEvent(event);
3968
+ if (matched.reason) return this.#unsafeSseFrame(frame, matched.reason);
3969
+ if (matched.state) {
3970
+ if (matched.state.argumentsDone) {
3971
+ return this.#unsafeSseFrame(frame, "special tool call delta after arguments done");
3972
+ }
3973
+ if (matched.state.kind === "tool_search") {
3974
+ this.#commitSemanticMutation();
3975
+ return [];
3976
+ }
3977
+ if (matched.state.codec || matched.state.kind === "function_codec") {
3978
+ // Hash the original JSON incrementally instead of retaining it or
3979
+ // interpreting partial operations as executable patch text.
3980
+ if (typeof event.delta !== "string") return this.#unsafeSseFrame(frame, "invalid structured argument delta");
3981
+ matched.state.codecSourceSeen = true;
3982
+ matched.state.codecSourceCharacters += event.delta.length;
3983
+ const maxBytes = matched.state.codec?.maxArgumentBytes ?? matched.state.functionRelay?.maxArgumentBytes;
3984
+ if (maxBytes && matched.state.codecSourceCharacters > maxBytes) {
3985
+ return this.#unsafeSseFrame(frame, "structured argument delta limit");
3986
+ }
3987
+ matched.state.codecSourceHash.update(Buffer.from(event.delta, "utf16le"));
3988
+ this.#commitSemanticMutation();
3989
+ return [];
3990
+ }
3991
+ matched.state.sawArgumentDelta = true;
3992
+ const argumentProperty =
3993
+ matched.state.sourceType === "custom_tool_call"
3994
+ ? LITELLM_CUSTOM_TOOL_INPUT_PROPERTY
3995
+ : CUSTOM_TOOL_INPUT_PROPERTY;
3996
+ const delta = customToolInputDelta(
3997
+ matched.state.deltaState,
3998
+ event.delta,
3999
+ argumentProperty,
4000
+ );
4001
+ if (delta === undefined) {
4002
+ this.#commitSemanticMutation();
4003
+ return [];
4004
+ }
4005
+ matched.state.deltaHash.update(Buffer.from(delta, "utf16le"));
4006
+ matched.state.deltaCharacters += delta.length;
4007
+ event = {
4008
+ ...event,
4009
+ type: "response.custom_tool_call_input.delta",
4010
+ delta,
4011
+ };
4012
+ changed = true;
4013
+ }
4014
+ }
4015
+ if (
4016
+ !this.#injectOnly &&
4017
+ event?.type === "response.function_call_arguments.done"
4018
+ ) {
4019
+ const matched = this.#specialCallForArgumentsEvent(event);
4020
+ if (matched.reason) return this.#unsafeSseFrame(frame, matched.reason);
4021
+ if (matched.state) {
4022
+ if (matched.state.argumentsDone) {
4023
+ return this.#unsafeSseFrame(frame, "duplicate special tool call arguments done");
4024
+ }
4025
+ if (matched.state.kind === "tool_search") {
4026
+ const argumentsObject = toolSearchArguments(event.arguments, false);
4027
+ if (!argumentsObject) {
4028
+ return this.#unsafeSseFrame(frame, "invalid tool search arguments done");
4029
+ }
4030
+ const argumentsFingerprint = canonicalJsonFingerprint(argumentsObject);
4031
+ matched.state.argumentsDone = true;
4032
+ matched.state.finalArgumentsLength = argumentsFingerprint.length;
4033
+ matched.state.finalArgumentsDigest = argumentsFingerprint.digest;
4034
+ this.#commitSemanticMutation();
4035
+ return [];
4036
+ }
4037
+ if (matched.state.kind === "function_codec") {
4038
+ const rewrittenArguments = matched.state.functionRelay?.rewriteArguments(event.arguments);
4039
+ if (typeof rewrittenArguments !== "string") {
4040
+ return this.#unsafeSseFrame(frame, "invalid function relay arguments done");
4041
+ }
4042
+ const codecReason = this.#validateCodecSource(matched.state, event.arguments);
4043
+ if (codecReason) return this.#unsafeSseFrame(frame, codecReason);
4044
+ const argumentsFingerprint = stringFingerprint(rewrittenArguments);
4045
+ event = { ...event, arguments: rewrittenArguments };
4046
+ matched.state.argumentsDone = true;
4047
+ matched.state.finalArgumentsLength = argumentsFingerprint.length;
4048
+ matched.state.finalArgumentsDigest = argumentsFingerprint.digest;
4049
+ matched.state.deltaHash = undefined;
4050
+ matched.state.deltaState = undefined;
4051
+ changed = true;
4052
+ } else {
4053
+ const argumentProperty =
4054
+ matched.state.sourceType === "custom_tool_call"
4055
+ ? LITELLM_CUSTOM_TOOL_INPUT_PROPERTY
4056
+ : CUSTOM_TOOL_INPUT_PROPERTY;
4057
+ const input = matched.state.sourceType === "custom_tool_call" && !matched.state.codec
4058
+ ? litellmCustomToolInput(event.arguments)
4059
+ : customToolInput(event.arguments, false, argumentProperty, matched.state.codec);
4060
+ if (input === undefined) {
4061
+ return this.#unsafeSseFrame(frame, "invalid custom tool arguments done");
4062
+ }
4063
+ const codecReason = this.#validateCodecSource(matched.state, event.arguments);
4064
+ if (codecReason) return this.#unsafeSseFrame(frame, codecReason);
4065
+ const inputFingerprint = stringFingerprint(input);
4066
+ const deltaReason = this.#customDeltaMismatch(
4067
+ matched.state,
4068
+ inputFingerprint,
4069
+ );
4070
+ if (deltaReason) return this.#unsafeSseFrame(frame, deltaReason);
4071
+ const { arguments: _arguments, ...rest } = event;
4072
+ event = {
4073
+ ...rest,
4074
+ type: "response.custom_tool_call_input.done",
4075
+ input,
4076
+ };
4077
+ matched.state.argumentsDone = true;
4078
+ matched.state.finalInputLength = inputFingerprint.length;
4079
+ matched.state.finalInputDigest = inputFingerprint.digest;
4080
+ matched.state.deltaHash = undefined;
4081
+ matched.state.deltaState = undefined;
4082
+ changed = true;
4083
+ }
4084
+ }
4085
+ }
4086
+ if (!this.#injectOnly) {
4087
+ const next = rewriteNamespaceResponsePayload(event, this.#lookups, this.#sessionModel);
4088
+ if (next) {
4089
+ event = next;
4090
+ changed = true;
4091
+ }
4092
+ }
4093
+ if (sourceEvent?.type === "response.output_item.added") {
4094
+ const reason = this.#registerCall(sourceEvent.item, event.item);
4095
+ if (reason) return this.#unsafeSseFrame(frame, reason);
4096
+ }
4097
+ if (sourceEvent?.type === "response.output_item.done") {
4098
+ const reason = this.#closeOutputItem(sourceEvent.item, event.item);
4099
+ if (reason) return this.#unsafeSseFrame(frame, reason);
4100
+ }
4101
+ const terminalEvent =
4102
+ event?.type === "response.completed" ||
4103
+ event?.type === "response.done" ||
4104
+ eventName === "response.completed" ||
4105
+ eventName === "response.done";
4106
+ const summaryReason = this.#validateOutputItems(sourceEvent, event, {
4107
+ allowAtomic: terminalEvent,
4108
+ });
4109
+ if (summaryReason) return this.#unsafeSseFrame(frame, summaryReason);
4110
+ if (terminalEvent) {
4111
+ if (this.#hasOpenSpecialCalls()) {
4112
+ return this.#unsafeSseFrame(frame, "terminal event before special tool call close");
4113
+ }
4114
+ }
4115
+ this.#observeEvent(event);
4116
+ const replacements = [];
4117
+ if (
4118
+ eventLine &&
4119
+ typeof event?.type === "string" &&
4120
+ event.type !== originalEventType
4121
+ ) {
4122
+ replacements.push([eventLine, `event: ${event.type}`]);
4123
+ }
4124
+ // Inject finished-child interrupts before the response closes so Codex
4125
+ // still executes them as ordinary tool calls in this turn.
4126
+ if (event?.type === "response.completed" || eventName === "response.completed") {
4127
+ const interruptBlocks = this.#drainInterruptBlocks();
4128
+ const withOutput = this.#mergeInjectedIntoCompleted(event);
4129
+ if (withOutput !== event) {
4130
+ event = withOutput;
4131
+ changed = true;
4132
+ }
4133
+ if (!changed) return [...interruptBlocks, frame];
4134
+ this.#commitSemanticMutation();
4135
+ replacements.push([dataLine, `data: ${JSON.stringify(event)}`]);
4136
+ return [
4137
+ ...interruptBlocks,
4138
+ this.#rewrittenSseFrame(frame, replacements),
4139
+ ];
4140
+ }
4141
+ if (event?.type === "response.done" || eventName === "response.done") {
4142
+ const interruptBlocks = this.#drainInterruptBlocks();
4143
+ if (!changed) return [...interruptBlocks, frame];
4144
+ this.#commitSemanticMutation();
4145
+ replacements.push([dataLine, `data: ${JSON.stringify(event)}`]);
4146
+ return [
4147
+ ...interruptBlocks,
4148
+ this.#rewrittenSseFrame(frame, replacements),
4149
+ ];
4150
+ }
4151
+ if (!changed) return [frame];
4152
+ this.#commitSemanticMutation();
4153
+ replacements.push([dataLine, `data: ${JSON.stringify(event)}`]);
4154
+ return [this.#rewrittenSseFrame(frame, replacements)];
4155
+ } catch (error) {
4156
+ if (error instanceof NamespaceRelayCommittedStreamError) throw error;
4157
+ return this.#unsafeSseFrame(frame, "event rewrite failure");
4158
+ }
4159
+ }
4160
+
4161
+ #observeEvent(event) {
4162
+ if (!event || typeof event !== "object") return;
4163
+ this.#lastSequence = nextSequence(event, this.#lastSequence);
4164
+ trackInterruptFromItem(event.item, this.#interruptedTargets);
4165
+ if (Array.isArray(event.output)) {
4166
+ for (const item of event.output) trackInterruptFromItem(item, this.#interruptedTargets);
4167
+ }
4168
+ if (Array.isArray(event.response?.output)) {
4169
+ for (const item of event.response.output) {
4170
+ trackInterruptFromItem(item, this.#interruptedTargets);
4171
+ }
4172
+ }
4173
+ }
4174
+
4175
+ #remainingInterrupts() {
4176
+ return filterAlreadyInterrupted(this.#pendingInterrupts, this.#interruptedTargets);
4177
+ }
4178
+
4179
+ #drainInterruptBlocks() {
4180
+ if (this.#injectionsDone) return [];
4181
+ const remaining = this.#remainingInterrupts();
4182
+ if (!remaining.length) {
4183
+ this.#injectionsDone = true;
4184
+ this.#lastInjectedCalls = [];
4185
+ return [];
4186
+ }
4187
+ this.#commitSemanticMutation();
4188
+ const blocks = [];
4189
+ const injectedCalls = [];
4190
+ for (const target of remaining) {
4191
+ // Each transform is request-scoped, so a local counter would restart on
4192
+ // every turn and reuse call IDs that remain in Codex conversation history.
4193
+ // Use the same fresh-ID helper as the non-stream injection path instead.
4194
+ const call = buildInterruptAgentCall(target);
4195
+ this.#interruptedTargets.add(target);
4196
+ injectedCalls.push(call);
4197
+ const addedSeq = this.#lastSequence + 1;
4198
+ const doneSeq = this.#lastSequence + 2;
4199
+ this.#lastSequence = doneSeq;
4200
+ const added = {
4201
+ type: "response.output_item.added",
4202
+ sequence_number: addedSeq,
4203
+ item: {
4204
+ type: "function_call",
4205
+ name: call.name,
4206
+ namespace: call.namespace,
4207
+ call_id: call.call_id,
4208
+ arguments: "",
4209
+ },
4210
+ };
4211
+ const done = {
4212
+ type: "response.output_item.done",
4213
+ sequence_number: doneSeq,
4214
+ item: {
4215
+ type: "function_call",
4216
+ name: call.name,
4217
+ namespace: call.namespace,
4218
+ call_id: call.call_id,
4219
+ arguments: call.arguments,
4220
+ },
4221
+ };
4222
+ blocks.push(
4223
+ Buffer.from(
4224
+ `event: response.output_item.added${this.#sseLineEnding}` +
4225
+ `data: ${JSON.stringify(added)}${this.#sseLineEnding}${this.#sseLineEnding}`,
4226
+ "utf8",
4227
+ ),
4228
+ );
4229
+ blocks.push(
4230
+ Buffer.from(
4231
+ `event: response.output_item.done${this.#sseLineEnding}` +
4232
+ `data: ${JSON.stringify(done)}${this.#sseLineEnding}${this.#sseLineEnding}`,
4233
+ "utf8",
4234
+ ),
4235
+ );
4236
+ }
4237
+ this.#lastInjectedCalls = injectedCalls;
4238
+ this.#injectionsDone = true;
4239
+ return blocks;
4240
+ }
4241
+
4242
+ #mergeInjectedIntoCompleted(event) {
4243
+ // drainInterruptBlocks already marked targets interrupted and emitted the
4244
+ // SSE tool calls. Mirror those calls into response.completed.output so
4245
+ // non-incremental consumers still see them.
4246
+ if (!event || typeof event !== "object") return event;
4247
+ const injected = this.#lastInjectedCalls;
4248
+ if (!Array.isArray(injected) || !injected.length) return event;
4249
+ if (Array.isArray(event.response?.output)) {
4250
+ return {
4251
+ ...event,
4252
+ response: {
4253
+ ...event.response,
4254
+ output: [...event.response.output, ...injected],
4255
+ },
4256
+ };
4257
+ }
4258
+ if (Array.isArray(event.output)) {
4259
+ return { ...event, output: [...event.output, ...injected] };
4260
+ }
4261
+ return event;
4262
+ }
4263
+
4264
+ #injectJsonInterrupts(payload) {
4265
+ if (!payload || typeof payload !== "object") return payload;
4266
+ // Non-streaming Responses put completed function calls in `output`.
4267
+ if (Array.isArray(payload.output)) {
4268
+ for (const item of payload.output) trackInterruptFromItem(item, this.#interruptedTargets);
4269
+ const result = appendInterruptCallsToOutput(
4270
+ payload.output,
4271
+ this.#pendingInterrupts,
4272
+ this.#interruptedTargets,
4273
+ );
4274
+ if (result.injected) payload = { ...payload, output: result.output };
4275
+ }
4276
+ if (payload.response && Array.isArray(payload.response.output)) {
4277
+ for (const item of payload.response.output) {
4278
+ trackInterruptFromItem(item, this.#interruptedTargets);
4279
+ }
4280
+ const result = appendInterruptCallsToOutput(
4281
+ payload.response.output,
4282
+ this.#pendingInterrupts,
4283
+ this.#interruptedTargets,
4284
+ );
4285
+ if (result.injected) {
4286
+ payload = {
4287
+ ...payload,
4288
+ response: { ...payload.response, output: result.output },
4289
+ };
4290
+ }
4291
+ }
4292
+ return payload;
4293
+ }
4294
+ }