@bitkyc08/opencodex 2.52.0-preview.20260911 → 2.52.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 (166) hide show
  1. package/gui/dist/assets/index-CWXut3rG.js +115 -0
  2. package/gui/dist/assets/index-EdoPnm9_.css +1 -0
  3. package/gui/dist/index.html +2 -2
  4. package/gui/dist/provider-icons/devin.svg +49 -0
  5. package/gui/dist/provider-icons/omo.svg +42 -0
  6. package/package.json +3 -1
  7. package/src/AGENTS.md +1 -1
  8. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +0 -1
  9. package/src/adapters/command-code.ts +0 -1
  10. package/src/adapters/cursor/checkpoint-store.ts +37 -0
  11. package/src/adapters/cursor/live-transport.ts +74 -2
  12. package/src/adapters/cursor.ts +6 -0
  13. package/src/adapters/devin/cloud-direct/auth.ts +264 -0
  14. package/src/adapters/devin/cloud-direct/catalog.ts +306 -0
  15. package/src/adapters/devin/cloud-direct/chat.ts +1274 -0
  16. package/src/adapters/devin/cloud-direct/index.ts +65 -0
  17. package/src/adapters/devin/cloud-direct/metadata.ts +134 -0
  18. package/src/adapters/devin/cloud-direct/wire.ts +206 -0
  19. package/src/adapters/devin/live-models.ts +133 -0
  20. package/src/adapters/devin-cli/acp.ts +204 -0
  21. package/src/adapters/devin-cli/adapter.ts +345 -0
  22. package/src/adapters/devin-cli/binary.ts +69 -0
  23. package/src/adapters/devin-cli/models.ts +57 -0
  24. package/src/adapters/devin.ts +326 -0
  25. package/src/adapters/google.ts +1 -1
  26. package/src/adapters/openai-chat.ts +31 -10
  27. package/src/adapters/openai-responses.ts +16 -1
  28. package/src/adapters/registry.ts +25 -1
  29. package/src/bridge.ts +8 -2
  30. package/src/claude/inbound-cache-stabilize.ts +130 -0
  31. package/src/claude/inbound.ts +45 -5
  32. package/src/cli/account-auth.ts +60 -1
  33. package/src/cli/account-extended.ts +23 -30
  34. package/src/cli/account.ts +2 -1
  35. package/src/cli/capabilities.ts +50 -8
  36. package/src/cli/config-command.ts +2 -2
  37. package/src/cli/dispatch.ts +14 -2
  38. package/src/cli/export-command.ts +11 -25
  39. package/src/cli/help.ts +2 -2
  40. package/src/cli/opencode.ts +5 -0
  41. package/src/cli/registry.ts +15 -4
  42. package/src/clients/config-export/cline.ts +71 -0
  43. package/src/clients/config-export/contracts.ts +10 -1
  44. package/src/clients/config-export/model-metadata.ts +33 -0
  45. package/src/clients/config-export/zcode.ts +23 -12
  46. package/src/clients/config-export.ts +156 -4
  47. package/src/codex/account-store.ts +7 -2
  48. package/src/codex/auth-context.ts +1 -1
  49. package/src/codex/catalog/effort.ts +4 -3
  50. package/src/codex/catalog/metadata.ts +8 -4
  51. package/src/codex/catalog/native-models.ts +24 -4
  52. package/src/codex/catalog/parsing.ts +19 -1
  53. package/src/codex/catalog/provider-fetch.ts +57 -0
  54. package/src/codex/catalog/sync.ts +1 -1
  55. package/src/codex/context-compat.ts +97 -0
  56. package/src/codex/context-owner.ts +201 -0
  57. package/src/codex/history-provider.ts +161 -14
  58. package/src/codex/inject-coordination.ts +3 -2
  59. package/src/codex/inject.ts +128 -9
  60. package/src/codex/pool-rotation.ts +8 -292
  61. package/src/codex/quota.ts +41 -12
  62. package/src/codex/retired-model-migration.ts +41 -0
  63. package/src/codex/routing.ts +327 -22
  64. package/src/codex/warmup.ts +2 -2
  65. package/src/combos/failover.ts +5 -0
  66. package/src/combos/resolve.ts +11 -15
  67. package/src/config.ts +46 -14
  68. package/src/generated/compatibility-version.json +293 -113
  69. package/src/grok/grpc-web.ts +120 -0
  70. package/src/grok/reset-coupon-ledger.ts +139 -0
  71. package/src/grok/reset-coupons.ts +278 -0
  72. package/src/integrations/catalog-refresh.ts +1 -1
  73. package/src/integrations/cline-document.ts +73 -0
  74. package/src/integrations/cline-io.ts +149 -0
  75. package/src/integrations/cline-transaction.ts +42 -0
  76. package/src/integrations/config-io.ts +21 -2
  77. package/src/integrations/journal.ts +43 -1
  78. package/src/integrations/omp-yaml-source.ts +123 -4
  79. package/src/integrations/ownership-policy.ts +7 -0
  80. package/src/integrations/ownership.ts +36 -0
  81. package/src/integrations/registry.ts +27 -0
  82. package/src/integrations/state.ts +5 -2
  83. package/src/integrations/store.ts +6 -0
  84. package/src/integrations/writer.ts +53 -9
  85. package/src/lab/subject/behavior-fingerprint.ts +1 -1
  86. package/src/lib/abort.ts +36 -0
  87. package/src/lib/local-destinations.ts +1 -1
  88. package/src/lib/upstream-retry.ts +36 -1
  89. package/src/oauth/account-quota-rank.ts +11 -0
  90. package/src/oauth/callback-server.ts +10 -5
  91. package/src/oauth/devin/api-base.ts +63 -0
  92. package/src/oauth/devin/login.ts +1 -0
  93. package/src/oauth/devin/register-user.ts +186 -0
  94. package/src/oauth/devin/types.ts +71 -0
  95. package/src/oauth/devin-cli.ts +149 -0
  96. package/src/oauth/devin.ts +170 -0
  97. package/src/oauth/generic-account-failover.ts +169 -1
  98. package/src/oauth/index.ts +20 -1
  99. package/src/oauth/login-cli.ts +19 -5
  100. package/src/oauth/pool-kernel.ts +321 -0
  101. package/src/oauth/pool-settings-capability.ts +127 -9
  102. package/src/oauth/store.ts +7 -3
  103. package/src/oauth/token-guardian.ts +1 -1
  104. package/src/providers/codebuddy-models.ts +0 -3
  105. package/src/providers/command-code-efforts.ts +23 -4
  106. package/src/providers/default-aliases.ts +4 -0
  107. package/src/providers/derive.ts +8 -0
  108. package/src/providers/devin-cli-authmode-migration.ts +57 -0
  109. package/src/providers/key-failover.ts +175 -0
  110. package/src/providers/model-rename-startup.ts +21 -1
  111. package/src/providers/qoder-models.ts +0 -1
  112. package/src/providers/quota-key-accounts.ts +50 -0
  113. package/src/providers/quota-routing-cache.ts +65 -6
  114. package/src/providers/quota-types.ts +7 -0
  115. package/src/providers/quota.ts +113 -30
  116. package/src/providers/registry.ts +237 -77
  117. package/src/providers/stale-context-window-migration.ts +92 -0
  118. package/src/providers/zai-responses-migration.ts +45 -0
  119. package/src/quota/reset-observer.ts +2 -1
  120. package/src/quota/reset-seen-store.ts +9 -1
  121. package/src/remote-control/crypto.ts +442 -0
  122. package/src/remote-control/host.ts +175 -0
  123. package/src/remote-control/index.ts +100 -0
  124. package/src/remote-control/protocol.ts +200 -0
  125. package/src/remote-control/relay.ts +162 -0
  126. package/src/remote-control/workspace-agent-protocol.ts +246 -0
  127. package/src/remote-control/workspace-rpc-framing.ts +128 -0
  128. package/src/remote-control/workspace-tools.ts +237 -0
  129. package/src/remote-control/workspace-utf8.ts +24 -0
  130. package/src/responses/custom-tool-compat.ts +23 -0
  131. package/src/router.ts +6 -1
  132. package/src/routing/compatibility/behavior.ts +3 -0
  133. package/src/server/adapter-resolve.ts +6 -2
  134. package/src/server/auth-cors.ts +71 -6
  135. package/src/server/chat-completions.ts +5 -3
  136. package/src/server/chat-native.ts +9 -0
  137. package/src/server/claude-messages.ts +25 -30
  138. package/src/server/context-history.ts +207 -0
  139. package/src/server/images.ts +28 -1
  140. package/src/server/index.ts +42 -22
  141. package/src/server/live.ts +2 -1
  142. package/src/server/management/agent-settings-routes.ts +7 -1
  143. package/src/server/management/config-routes.ts +3 -3
  144. package/src/server/management/grok-coupon-routes.ts +287 -0
  145. package/src/server/management/integration-routes.ts +6 -1
  146. package/src/server/management/oauth-account-routes.ts +124 -7
  147. package/src/server/management/provider-routes.ts +77 -2
  148. package/src/server/management/route-registry.ts +6 -1
  149. package/src/server/management-api.ts +7 -0
  150. package/src/server/request-log.ts +4 -0
  151. package/src/server/responses/codex-ws-exchange.ts +90 -14
  152. package/src/server/responses/codex-ws-wire.ts +51 -1
  153. package/src/server/responses/compact.ts +8 -1
  154. package/src/server/responses/core.ts +188 -22
  155. package/src/server/responses/ws-upstream.ts +1 -1
  156. package/src/server/responses-undeclared-tool-guard.ts +261 -12
  157. package/src/server/zai-responses-startup.ts +21 -0
  158. package/src/types/config.ts +33 -3
  159. package/src/types/provider.ts +54 -4
  160. package/src/types/request.ts +1 -1
  161. package/src/types/tools.ts +36 -6
  162. package/src/update/job.ts +33 -11
  163. package/src/vision/plan.ts +40 -37
  164. package/src/web-search/index.ts +1 -0
  165. package/gui/dist/assets/index-BoBRSehJ.css +0 -1
  166. package/gui/dist/assets/index-Dx0xv2EA.js +0 -115
@@ -5,7 +5,7 @@ import {
5
5
  namespacedToolName,
6
6
  normalizeDeclaredToolName,
7
7
  } from "../types";
8
- import { sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
8
+ import { replaceSseDataPayload, sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
9
9
 
10
10
  /** Item types the client executes through a request-declared wire name. */
11
11
  const CLIENT_EXECUTED_CALL_TYPES = new Set(["function_call", "custom_tool_call"]);
@@ -171,6 +171,49 @@ export function collectDeclaredWireToolNames(body: unknown): Set<string> {
171
171
  return names;
172
172
  }
173
173
 
174
+ /**
175
+ * Collects explicitly declared bare wire tool names from a Responses request body.
176
+ *
177
+ * Bare wire tools are top-level declarations (or grouped under the builtin `functions`
178
+ * namespace) that are not namespaced and do not carry a flattened namespace delimiter (`__`)
179
+ * or dotted namespace alias (`.`).
180
+ *
181
+ * @param body - The outbound or inbound request body.
182
+ * @returns A set of declared bare tool names.
183
+ */
184
+ export function collectDeclaredBareWireToolNames(body: unknown): Set<string> {
185
+ const names = new Set<string>();
186
+ if (!isPlainObject(body)) return names;
187
+ const specGroups: unknown[] = [body.tools];
188
+ if (Array.isArray(body.input)) {
189
+ for (const item of body.input) {
190
+ if (
191
+ isPlainObject(item)
192
+ && (item.type === "additional_tools" || item.type === "tool_search_output")
193
+ ) specGroups.push(item.tools);
194
+ }
195
+ }
196
+ for (const specs of specGroups) {
197
+ if (!Array.isArray(specs)) continue;
198
+ for (const spec of specs) {
199
+ if (!isPlainObject(spec)) continue;
200
+ if (spec.type === "namespace" && Array.isArray(spec.tools)) {
201
+ if (spec.name === BUILTIN_FUNCTIONS_NAMESPACE) {
202
+ for (const inner of spec.tools) {
203
+ if (!isPlainObject(inner)) continue;
204
+ const name = wireToolInnerName(inner);
205
+ if (name && !name.includes("__") && !name.includes(".")) names.add(name);
206
+ }
207
+ }
208
+ continue;
209
+ }
210
+ const name = wireToolInnerName(spec);
211
+ if (name && !name.includes("__") && !name.includes(".")) names.add(name);
212
+ }
213
+ }
214
+ return names;
215
+ }
216
+
174
217
  function addNamelessClientCallTypes(callTypes: Set<string>, specs: unknown): void {
175
218
  if (!Array.isArray(specs)) return;
176
219
  for (const spec of specs) {
@@ -286,11 +329,22 @@ export function hasExplicitWireToolCatalog(body: unknown): boolean {
286
329
  );
287
330
  }
288
331
 
332
+ /**
333
+ * Evaluates whether an individual output item represents an undeclared tool call.
334
+ *
335
+ * @param item - The item to check.
336
+ * @param declared - All wire tool names declared in the request catalog.
337
+ * @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
338
+ * @param providerExecutedCallTypes - Call types executed by the provider.
339
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
340
+ * @returns The undeclared tool call name if unauthorized, or undefined if permitted.
341
+ */
289
342
  function undeclaredNameInItem(
290
343
  item: unknown,
291
344
  declared: ReadonlySet<string>,
292
345
  declaredNamelessClientCallTypes: ReadonlySet<string>,
293
346
  providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
347
+ declaredBare?: ReadonlySet<string>,
294
348
  ): string | undefined {
295
349
  if (!isPlainObject(item)) return undefined;
296
350
  if (typeof item.type !== "string") return undefined;
@@ -319,51 +373,232 @@ function undeclaredNameInItem(
319
373
  dottedAliasIsUnambiguous(item.namespace, name)
320
374
  && declared.has(dottedToolName(item.namespace, name))
321
375
  ) return undefined;
376
+ const bareDeclared = declaredBare ?? declared;
377
+ const bare = name.startsWith("default.") ? name.slice("default.".length) : name;
378
+ if (
379
+ item.namespace === "default"
380
+ && bare.length > 0
381
+ && bareDeclared.has(bare)
382
+ && !declared.has(namespacedToolName(item.namespace, bare))
383
+ && !declared.has(dottedToolName(item.namespace, bare))
384
+ ) return undefined;
322
385
  return name;
323
386
  }
324
- const effectiveName = normalizeDeclaredToolName(name, declared);
387
+ const effectiveName = normalizeDeclaredToolName(name, declared, declaredBare);
325
388
  if (declared.has(effectiveName)) return undefined;
326
389
  return name;
327
390
  }
328
391
 
329
- /** First undeclared client tool named by a Responses SSE payload, or undefined. */
392
+ /**
393
+ * First undeclared client tool named by a Responses SSE payload, or undefined.
394
+ *
395
+ * @param payload - The parsed SSE event payload.
396
+ * @param declared - All wire tool names declared in the request catalog.
397
+ * @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
398
+ * @param providerExecutedCallTypes - Call types executed by the provider.
399
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
400
+ * @returns The name of the first undeclared tool call, or undefined.
401
+ */
330
402
  export function undeclaredToolCallName(
331
403
  payload: unknown,
332
404
  declared: ReadonlySet<string>,
333
405
  declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
334
406
  providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
407
+ declaredBare?: ReadonlySet<string>,
335
408
  ): string | undefined {
336
409
  if (!isPlainObject(payload)) return undefined;
337
410
  if (payload.type === "response.output_item.added" || payload.type === "response.output_item.done") {
338
- return undeclaredNameInItem(payload.item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
411
+ return undeclaredNameInItem(payload.item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
412
+ }
413
+ if (payload.type === "response.function_call_arguments.done" && typeof payload.name === "string") {
414
+ const fakeItem = { type: "function_call", name: payload.name, namespace: payload.namespace };
415
+ return undeclaredNameInItem(fakeItem, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
339
416
  }
340
417
  // Sparse gateways skip incremental items and only ever ship the terminal snapshot.
341
418
  if (payload.type === "response.completed" || payload.type === "response.incomplete") {
342
- return undeclaredToolCallNameInResponse(payload.response, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
419
+ return undeclaredToolCallNameInResponse(payload.response, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
343
420
  }
344
421
  return undefined;
345
422
  }
346
423
 
347
- /** First undeclared client tool in a Responses object's `output` array, or undefined. */
424
+ /**
425
+ * First undeclared client tool in a Responses object's `output` array, or undefined.
426
+ *
427
+ * @param response - The Responses result object containing `output`.
428
+ * @param declared - All wire tool names declared in the request catalog.
429
+ * @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
430
+ * @param providerExecutedCallTypes - Call types executed by the provider.
431
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
432
+ * @returns The name of the first undeclared tool call, or undefined.
433
+ */
348
434
  export function undeclaredToolCallNameInResponse(
349
435
  response: unknown,
350
436
  declared: ReadonlySet<string>,
351
437
  declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
352
438
  providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
439
+ declaredBare?: ReadonlySet<string>,
353
440
  ): string | undefined {
354
441
  if (!isPlainObject(response) || !Array.isArray(response.output)) return undefined;
355
442
  for (const item of response.output) {
356
- const name = undeclaredNameInItem(item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
443
+ const name = undeclaredNameInItem(item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
357
444
  if (name !== undefined) return name;
358
445
  }
359
446
  return undefined;
360
447
  }
361
448
 
449
+ /**
450
+ * Formats an error message indicating that a routed provider emitted an undeclared tool call.
451
+ *
452
+ * @param name - The undeclared tool name emitted by the provider.
453
+ * @returns A formatted error message string.
454
+ */
362
455
  export function undeclaredToolCallMessage(name: string): string {
363
456
  const reported = name.slice(0, MAX_REPORTED_NAME_CHARS);
364
457
  return `routed provider emitted undeclared client tool "${reported}"; only request-declared tools may be called`;
365
458
  }
366
459
 
460
+ /**
461
+ * Normalizes a single output item's default-namespaced tool call back to declared bare tool.
462
+ *
463
+ * Strips invented `default.` prefixes or `namespace: "default"` from tool calls when the bare
464
+ * tool name was declared and neither dotted nor flattened namespaced forms were declared (#4176).
465
+ *
466
+ * @param item - The output item to normalize.
467
+ * @param declared - All wire tool names declared in the request catalog.
468
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
469
+ * @returns An object with the normalized value and a boolean indicating if changes occurred.
470
+ */
471
+ export function normalizeDefaultNamespaceInItem(
472
+ item: unknown,
473
+ declared: ReadonlySet<string>,
474
+ declaredBare?: ReadonlySet<string>,
475
+ ): { value: unknown; changed: boolean } {
476
+ if (!isPlainObject(item)) return { value: item, changed: false };
477
+ if (!CLIENT_EXECUTED_CALL_TYPES.has(item.type as string)) {
478
+ return { value: item, changed: false };
479
+ }
480
+ const name = item.name;
481
+ if (typeof name !== "string" || name.length === 0) {
482
+ return { value: item, changed: false };
483
+ }
484
+ const bareDeclared = declaredBare ?? declared;
485
+ if (item.namespace === "default") {
486
+ const bare = name.startsWith("default.") ? name.slice("default.".length) : name;
487
+ if (
488
+ bare.length > 0
489
+ && bareDeclared.has(bare)
490
+ && !declared.has(namespacedToolName("default", bare))
491
+ && !declared.has(dottedToolName("default", bare))
492
+ ) {
493
+ const next: Record<string, unknown> = { ...(item as Record<string, unknown>), name: bare };
494
+ delete next.namespace;
495
+ return { value: next, changed: true };
496
+ }
497
+ return { value: item, changed: false };
498
+ }
499
+ if (item.namespace === undefined || item.namespace === BUILTIN_FUNCTIONS_NAMESPACE) {
500
+ if (name.startsWith("default.")) {
501
+ const bare = name.slice("default.".length);
502
+ if (
503
+ bare.length > 0
504
+ && bareDeclared.has(bare)
505
+ && !declared.has("default." + bare)
506
+ && !declared.has("default__" + bare)
507
+ ) {
508
+ return { value: { ...item, name: bare }, changed: true };
509
+ }
510
+ }
511
+ }
512
+ return { value: item, changed: false };
513
+ }
514
+
515
+ /**
516
+ * Normalizes default-namespaced tool calls in a Responses object's `output` array.
517
+ *
518
+ * @param response - The Responses result object containing `output`.
519
+ * @param declared - All wire tool names declared in the request catalog.
520
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
521
+ * @returns An object with the normalized response and a boolean indicating if changes occurred.
522
+ */
523
+ export function normalizeDefaultNamespaceInResponse(
524
+ response: unknown,
525
+ declared: ReadonlySet<string>,
526
+ declaredBare?: ReadonlySet<string>,
527
+ ): { value: unknown; changed: boolean } {
528
+ if (!isPlainObject(response) || !Array.isArray(response.output)) {
529
+ return { value: response, changed: false };
530
+ }
531
+ let changed = false;
532
+ const newOutput = response.output.map(item => {
533
+ const res = normalizeDefaultNamespaceInItem(item, declared, declaredBare);
534
+ if (res.changed) changed = true;
535
+ return res.value;
536
+ });
537
+ if (!changed) return { value: response, changed: false };
538
+ return { value: { ...response, output: newOutput }, changed: true };
539
+ }
540
+
541
+ /**
542
+ * Normalizes default-namespaced tool calls in a Responses SSE payload object.
543
+ *
544
+ * @param payload - The parsed SSE event payload.
545
+ * @param declared - All wire tool names declared in the request catalog.
546
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
547
+ * @returns An object with the normalized payload and a boolean indicating if changes occurred.
548
+ */
549
+ export function normalizeDefaultNamespaceInPayload(
550
+ payload: unknown,
551
+ declared: ReadonlySet<string>,
552
+ declaredBare?: ReadonlySet<string>,
553
+ ): { value: unknown; changed: boolean } {
554
+ if (!isPlainObject(payload)) return { value: payload, changed: false };
555
+ if (payload.type === "response.output_item.added" || payload.type === "response.output_item.done") {
556
+ const res = normalizeDefaultNamespaceInItem(payload.item, declared, declaredBare);
557
+ if (!res.changed) return { value: payload, changed: false };
558
+ return { value: { ...payload, item: res.value }, changed: true };
559
+ }
560
+ if (payload.type === "response.function_call_arguments.done" && typeof payload.name === "string") {
561
+ const fakeItem = { type: "function_call", name: payload.name, namespace: payload.namespace };
562
+ const res = normalizeDefaultNamespaceInItem(fakeItem, declared, declaredBare);
563
+ if (res.changed) {
564
+ const normalizedItem = res.value as Record<string, unknown>;
565
+ const next: Record<string, unknown> = { ...payload, name: normalizedItem.name };
566
+ if ("namespace" in next && !("namespace" in normalizedItem)) {
567
+ delete next.namespace;
568
+ }
569
+ return { value: next, changed: true };
570
+ }
571
+ }
572
+ if (payload.type === "response.completed" || payload.type === "response.incomplete") {
573
+ const res = normalizeDefaultNamespaceInResponse(payload.response, declared, declaredBare);
574
+ if (!res.changed) return { value: payload, changed: false };
575
+ return { value: { ...payload, response: res.value }, changed: true };
576
+ }
577
+ return { value: payload, changed: false };
578
+ }
579
+
580
+ /**
581
+ * Normalizes default-namespaced tool calls in a raw Responses JSON string.
582
+ *
583
+ * @param jsonText - Raw JSON string representing a Responses object.
584
+ * @param declared - All wire tool names declared in the request catalog.
585
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
586
+ * @returns The normalized JSON string, or original text if unchanged or invalid JSON.
587
+ */
588
+ export function normalizeDefaultNamespaceInJson(
589
+ jsonText: string,
590
+ declared: ReadonlySet<string>,
591
+ declaredBare?: ReadonlySet<string>,
592
+ ): string {
593
+ try {
594
+ const parsed = JSON.parse(jsonText);
595
+ const normalized = normalizeDefaultNamespaceInResponse(parsed, declared, declaredBare);
596
+ return normalized.changed ? JSON.stringify(normalized.value) : jsonText;
597
+ } catch {
598
+ return jsonText;
599
+ }
600
+ }
601
+
367
602
  function failedBlocks(name: string, newline: string): readonly string[] {
368
603
  const failure = {
369
604
  type: "upstream_error",
@@ -378,7 +613,8 @@ function failedBlocks(name: string, newline: string): readonly string[] {
378
613
  }
379
614
 
380
615
  /**
381
- * Fail closed when a routed provider calls a tool the request never declared (#1700).
616
+ * Fail closed when a routed provider calls a tool the request never declared (#1700),
617
+ * and normalize provider-invented default namespaces back to declared bare tools (#4176).
382
618
  *
383
619
  * The bridged paths already refuse such a call (`declaredToolNames` in src/bridge.ts), but the
384
620
  * native Responses passthrough relayed it verbatim: Codex received a `function_call` for a tool
@@ -389,11 +625,18 @@ function failedBlocks(name: string, newline: string): readonly string[] {
389
625
  *
390
626
  * Everything after the trip is dropped so a later `response.completed` cannot contradict the
391
627
  * terminal already sent. Non-JSON and non-item blocks pass through untouched.
628
+ *
629
+ * @param declared - All wire tool names declared in the request catalog.
630
+ * @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
631
+ * @param providerExecutedCallTypes - Call types executed by the provider.
632
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
633
+ * @returns An SSE block rewrite function.
392
634
  */
393
635
  export function createUndeclaredToolCallGuardBlockRewrite(
394
636
  declared: ReadonlySet<string>,
395
637
  declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
396
638
  providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
639
+ declaredBare?: ReadonlySet<string>,
397
640
  ): SseBlockRewrite {
398
641
  let tripped = false;
399
642
  return (block: string) => {
@@ -406,9 +649,15 @@ export function createUndeclaredToolCallGuardBlockRewrite(
406
649
  } catch {
407
650
  return [block];
408
651
  }
409
- const name = undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes);
410
- if (name === undefined) return [block];
411
- tripped = true;
412
- return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
652
+ const name = undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
653
+ if (name !== undefined) {
654
+ tripped = true;
655
+ return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
656
+ }
657
+ const normalized = normalizeDefaultNamespaceInPayload(parsed, declared, declaredBare);
658
+ if (normalized.changed) {
659
+ return [replaceSseDataPayload(block, JSON.stringify(normalized.value))];
660
+ }
661
+ return [block];
413
662
  };
414
663
  }
@@ -0,0 +1,21 @@
1
+ import { mutatePersistedConfig } from "../config";
2
+ import { migrateZaiResponsesDefault } from "../providers/zai-responses-migration";
3
+ import type { OcxConfig } from "../types";
4
+
5
+ /** Rebase the one-time Z.AI wire upgrade before initializing any live config consumers. */
6
+ export function migrateStartupZaiResponses(config: OcxConfig): OcxConfig {
7
+ const projection = { ...config };
8
+ if (!migrateZaiResponsesDefault(projection)) return config;
9
+ try {
10
+ const outcome = mutatePersistedConfig(fresh => ({
11
+ changed: migrateZaiResponsesDefault(fresh),
12
+ value: fresh,
13
+ }));
14
+ if (outcome.status !== "unavailable") return outcome.value;
15
+ console.warn(`[zai-responses-migration] Persistence unavailable (${outcome.reason}); using Responses in memory only.`);
16
+ } catch {
17
+ // Filesystem errors can carry private paths. Startup must still remain available.
18
+ console.warn("[zai-responses-migration] Persistence failed; using Responses in memory only.");
19
+ }
20
+ return projection;
21
+ }
@@ -6,6 +6,12 @@ import type { CodexAccount } from "./accounts";
6
6
  * /v1/messages surface, the `ocx claude` launcher, and the GUI Claude page.
7
7
  */
8
8
  export interface OcxClaudeCodeConfig {
9
+ /**
10
+ * Opt-in relocation of supported trailing Claude harness notices from system instructions
11
+ * to a user input message on translated routes. Changes the Desktop cache-key prefix.
12
+ * Default: false; only literal true enables it. Native passthrough is unchanged.
13
+ */
14
+ stabilizePromptCache?: boolean;
9
15
  /** Opt-in translated Messages admission; unset keeps legacy behavior. Native passthrough is exempt. */
10
16
  compatibility?: "shadow" | "enforce";
11
17
  /** Kill switch for the /v1/messages inbound (GUI "Claude ON" toggle). Default: enabled. */
@@ -601,7 +607,9 @@ export interface OcxConfig {
601
607
  /**
602
608
  * Shadow call intercept: redirect Codex's hard-coded helper calls (title generation,
603
609
  * commit messages, skill orchestration) to a user-chosen model. Default intercepted
604
- * source models: gpt-5.4-mini (older clients) and gpt-5.6-luna (Codex 0.145.0+).
610
+ * source model: gpt-5.6-luna (Codex 0.145.0+). Clients through 0.144.x emitted
611
+ * gpt-5.4-mini instead; that model is retired upstream, but it stays available as an
612
+ * opt-in `sourceModels` prefix so an old client's helper calls can still be intercepted.
605
613
  * Opt-in; disabled by default. Matching requests preserve their configured reasoning effort.
606
614
  * All requests for configured shadow source models are intercepted regardless of request kind,
607
615
  * except when the replacement intersects the same provider+model source set.
@@ -611,7 +619,7 @@ export interface OcxConfig {
611
619
  enabled?: boolean;
612
620
  /** Replacement model id (e.g. "gpt-5.5"). */
613
621
  model?: string;
614
- /** Optional override of intercepted source-model prefixes (default: gpt-5.4-mini, gpt-5.6-luna). */
622
+ /** Optional override of intercepted source-model prefixes (default: gpt-5.6-luna). */
615
623
  sourceModels?: string[];
616
624
  };
617
625
  /**
@@ -831,6 +839,28 @@ export interface OcxConfig {
831
839
  * spends a second credit. A malformed value reads as off.
832
840
  */
833
841
  resetCreditAutoRedeem?: { enabled?: boolean; leadTimeMinutes?: number };
842
+ /**
843
+ * Shared account-pool kernel, opt-in and off by default.
844
+ *
845
+ * `kernel: true` is what makes a generic OAuth provider's stored `strategy` and
846
+ * `autoSwitchThreshold` actually select an account instead of merely being persisted.
847
+ * Off restores the pre-kernel path exactly, which is why the DTO keeps reporting
848
+ * `inert: true` until this is on. A malformed value reads as off.
849
+ */
850
+ pool?: {
851
+ kernel?: boolean;
852
+ /**
853
+ * Opt-in cache-affinity ordering, off by default.
854
+ *
855
+ * With it on, a bound Codex thread keeps its account until that account genuinely cannot
856
+ * serve, instead of moving the moment usage crosses `autoSwitchThreshold`. Moving a live
857
+ * conversation throws away the prompt cache warmed on that account, and a threshold
858
+ * crossing is a hint rather than evidence the account is spent. Separate from `kernel`
859
+ * on purpose: that one governs the generic OAuth strategy consumer, and one switch
860
+ * carrying two unrelated meanings cannot be turned on alone.
861
+ */
862
+ cacheAffinity?: boolean;
863
+ };
834
864
  /** Active pool account id for next session. undefined = main (passthrough as-is). */
835
865
  activeCodexAccountId?: string;
836
866
  /** Auto-switch threshold (0-100). Default 80. 0 = disabled. */
@@ -1100,7 +1130,7 @@ export interface OcxTokenGuardianConfig {
1100
1130
  codexWarmupEnabled?: boolean;
1101
1131
  /** Max age before a Codex pool account is revalidated via `/codex/responses`. Default 691200 (8d). */
1102
1132
  codexWarmupMaxAgeSeconds?: number;
1103
- /** Model used for optional Codex pool warmup. Default gpt-5.4-mini. */
1133
+ /** Model used for optional Codex pool warmup. Default gpt-5.6-luna. */
1104
1134
  codexWarmupModel?: string;
1105
1135
  }
1106
1136
 
@@ -272,6 +272,17 @@ export interface OcxProviderConfig {
272
272
  * the legacy `/v1/responses` construction.
273
273
  */
274
274
  responsesPath?: string;
275
+ /**
276
+ * Optional relative send path for the `openai-chat` wire, mirroring `responsesPath`.
277
+ * Same shape rules: must start with `/`, no URL scheme, query string, or fragment.
278
+ * When omitted the adapter keeps `openaiChatCompletionsUrl(baseUrl)`.
279
+ *
280
+ * This exists because a per-model wire override swaps `adapter` and leaves `baseUrl`
281
+ * alone, so an upstream that serves Chat Completions and Responses under different
282
+ * path prefixes cannot be reached by the adapter swap by itself. Z.AI is that case:
283
+ * `/api/v1/responses` and `/api/coding/paas/v4/chat/completions` on one host and one key.
284
+ */
285
+ chatCompletionsPath?: string;
275
286
  /**
276
287
  * Command Code protocol version sent as `x-command-code-version` on /alpha/generate requests.
277
288
  * The internal endpoint's schema drifts with the CLI version; operators can pin a known-good
@@ -281,8 +292,8 @@ export interface OcxProviderConfig {
281
292
  /**
282
293
  * Responses upstream that stores nothing server-side (DeepSeek documents "the API
283
294
  * is stateless"). Stateful request parameters are dropped, `store` is pinned false,
284
- * and orphaned tool results left by a replay miss are repaired rather than
285
- * forwarded to an upstream that cannot resolve their pair.
295
+ * and missing local continuation history returns previous_response_not_found so
296
+ * clients can resend full input. Explicit input still receives orphan-item repair.
286
297
  */
287
298
  statelessResponses?: boolean;
288
299
  /**
@@ -387,6 +398,16 @@ export interface OcxProviderConfig {
387
398
  * `apiKey` seeds a one-entry pool on first management touch.
388
399
  */
389
400
  apiKeyPool?: Array<{ id: string; key: string; label?: string; addedAt?: number }>;
401
+ /**
402
+ * Optional proactive ordering for `apiKeyPool` when the committed key is already
403
+ * cooling. Deliberately NOT named like the OAuth `accountPoolStrategy`: an API key
404
+ * is a different identity from an OAuth account set, and key rotation is a
405
+ * rate-limit scheduling problem rather than a prompt-cache one.
406
+ *
407
+ * Absent means today's behaviour: no pre-dispatch pick at all, only the reactive
408
+ * 429/401 walk in `key-failover`.
409
+ */
410
+ apiKeyPoolStrategy?: "round-robin" | "fill-first" | "quota";
390
411
  /** Changes on manual selection (including re-selection) and committed automatic allocation. */
391
412
  apiKeySelectionRevision?: string;
392
413
  /** Runtime only. Never expose in management responses or persist a routed provider. */
@@ -511,11 +532,21 @@ export interface OcxProviderConfig {
511
532
  enabled?: boolean;
512
533
  /**
513
534
  * Generic OAuth pool selection strategy (#695). Persisted through the pool-settings
514
- * contract; the selector does not consume it yet, so omitted keeps today's behavior.
535
+ * contract. Consumed by the selector only while `pool.kernel` is on; with the flag off
536
+ * it is still merely persisted, so omitted and set behave the same.
515
537
  */
516
538
  strategy?: "quota" | "round-robin" | "fill-first";
517
- /** 0-100 usage percent at which a proactive switch may be considered (#695); inert today. */
539
+ /**
540
+ * 0-100 usage percent at which fill-first advances off the active account (#695).
541
+ * Read only under `pool.kernel` with `strategy: "fill-first"`; 80 when unset, matching
542
+ * the Codex and Anthropic pools.
543
+ */
518
544
  autoSwitchThreshold?: number;
545
+ /**
546
+ * Successful dispatches retained on one round-robin selection. Default 1; range 1..100.
547
+ * Read only under `pool.kernel` with `strategy: "round-robin"`.
548
+ */
549
+ stickyLimit?: number;
519
550
  };
520
551
  /** Allow an explicitly key/oauth provider to run without a credential (for keyless local proxies). */
521
552
  keyOptional?: boolean;
@@ -589,6 +620,12 @@ export interface OcxProviderConfig {
589
620
  xaiResponsesXSearch?: boolean;
590
621
  /** One-time Grok subscription wire upgrade; later explicit Chat choices remain authoritative. */
591
622
  xaiResponsesDefaultVersion?: number;
623
+ /**
624
+ * One-time Z.AI coding-plan wire upgrade. The router already canonicalizes the `zai` row onto the
625
+ * Responses destination at request time; the marker records that the saved row was rewritten to
626
+ * match, so a later explicit Chat choice is not re-migrated on the next boot.
627
+ */
628
+ zaiResponsesDefaultVersion?: number;
592
629
  /**
593
630
  * Whether the Responses upstream accepts native custom tools and custom_tool_call items.
594
631
  * Set false only for a provider whose native contract rejects them; absence preserves
@@ -641,6 +678,19 @@ export interface OcxProviderConfig {
641
678
  * per-model compatibility escape hatch for mixed-capability gateways.
642
679
  */
643
680
  noStructuredOutputModels?: string[];
681
+ /**
682
+ * Model ids whose Chat Completions endpoint rejects `response_format` of type
683
+ * `json_schema` specifically. Such a request is downgraded to
684
+ * `{ type: "json_object" }` instead of being dropped, so a client that asked for
685
+ * JSON still gets JSON rather than prose — at the cost of the schema itself, which
686
+ * the upstream would have rejected anyway.
687
+ *
688
+ * Deliberately narrower than `noStructuredOutputModels`: that field claims the
689
+ * endpoint rejects the whole `response_format` field, which is a strictly stronger
690
+ * claim than any reported upstream error supports for these gateways. When a model
691
+ * appears in both lists the stronger opt-out wins and the field is omitted entirely.
692
+ */
693
+ noJsonSchemaModels?: string[];
644
694
  /**
645
695
  * Model ids that accept a reasoning-effort field on an ordinary turn but reject it
646
696
  * once function tools are present. The model keeps its advertised effort ladder;
@@ -100,7 +100,7 @@ export interface OcxParsedRequest {
100
100
  /**
101
101
  * The hosted `{type:"web_search", ...}` tool config, stashed when Codex enables web search. Routed
102
102
  * (non-OpenAI) providers can't run it server-side, so the proxy re-exposes it as a function tool and
103
- * executes searches via the gpt-5.4-mini sidecar (see src/web-search). Absent when not requested.
103
+ * executes searches via the gpt-5.6-luna sidecar (see src/web-search). Absent when not requested.
104
104
  */
105
105
  _webSearch?: Record<string, unknown>;
106
106
  /** Hosted image_generation tool config stashed for the image bridge sidecar (see src/images). */
@@ -13,7 +13,7 @@ export interface OcxTool {
13
13
  loadedFromToolSearch?: boolean;
14
14
  /** Cursor-only synthetic exact-match edit tool; never inferred from the wire name. */
15
15
  cursorStructuredEdit?: true;
16
- /** Synthetic web_search tool: the model's call is executed by the gpt-5.4-mini sidecar, not relayed to Codex. */
16
+ /** Synthetic web_search tool: the model's call is executed by the gpt-5.6-luna sidecar, not relayed to Codex. */
17
17
  webSearch?: boolean;
18
18
  /** Synthetic image_gen tool: the model's call is executed by the xAI image bridge sidecar, not relayed to Codex. */
19
19
  imageGeneration?: boolean;
@@ -66,22 +66,52 @@ const CODE_MODE_HELPER_TOOL_NAMES = [
66
66
  */
67
67
  export const CODE_MODE_EXEC_TOOL_NAME = "exec";
68
68
 
69
+ /**
70
+ * Normalizes provider-emitted tool names against declared tool catalogs.
71
+ *
72
+ * Rewrites invented `default.<name>` prefixes back to a declared bare tool when that bare tool
73
+ * is declared and neither `default.<name>` nor `default__<name>` was explicitly declared (#4176).
74
+ * Also normalizes legacy helper names (`exec_command`, `shell_command`, `apply_patch`) to
75
+ * `exec` when code-mode `exec` is declared in the request catalog.
76
+ *
77
+ * @param name - The tool name emitted on the wire by the provider.
78
+ * @param declared - All wire tool names declared in the request catalog, including aliases.
79
+ * @param declaredBare - Explicitly declared bare tool names without namespace provenance.
80
+ * When omitted, falls back to `declared`.
81
+ * @returns The normalized tool name to expose downstream.
82
+ */
69
83
  export function normalizeDeclaredToolName(
70
84
  name: string,
71
85
  declared: ReadonlySet<string> | undefined,
86
+ declaredBare?: ReadonlySet<string>,
72
87
  ): string {
73
- if (!declared || !declared.has(CODE_MODE_EXEC_TOOL_NAME)) return name;
88
+ if (!declared) return name;
74
89
  if (declared.has(name)) return name;
75
- if (name === "apply_patch") return CODE_MODE_EXEC_TOOL_NAME;
90
+ let candidate = name;
91
+ if (name.startsWith("default.")) {
92
+ const bare = name.slice("default.".length);
93
+ const bareDeclared = declaredBare ?? declared;
94
+ if (
95
+ bare.length > 0
96
+ && bareDeclared.has(bare)
97
+ && !declared.has("default." + bare)
98
+ && !declared.has("default__" + bare)
99
+ ) {
100
+ candidate = bare;
101
+ }
102
+ }
103
+ if (!declared.has(CODE_MODE_EXEC_TOOL_NAME)) return candidate;
104
+ if (declared.has(candidate)) return candidate;
105
+ if (candidate === "apply_patch") return CODE_MODE_EXEC_TOOL_NAME;
76
106
  // When the catalog explicitly declares any legacy shell bridge name, the environment
77
107
  // genuinely exposes that tool — turn normalization off so a call is never mis-routed
78
108
  // to `exec`.
79
109
  if ((LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy))) {
80
- return name;
110
+ return candidate;
81
111
  }
82
- return (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(name)
112
+ return (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(candidate)
83
113
  ? CODE_MODE_EXEC_TOOL_NAME
84
- : name;
114
+ : candidate;
85
115
  }
86
116
 
87
117
  /**