@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.
- package/gui/dist/assets/index-CWXut3rG.js +115 -0
- package/gui/dist/assets/index-EdoPnm9_.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/devin.svg +49 -0
- package/gui/dist/provider-icons/omo.svg +42 -0
- package/package.json +3 -1
- package/src/AGENTS.md +1 -1
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +0 -1
- package/src/adapters/command-code.ts +0 -1
- package/src/adapters/cursor/checkpoint-store.ts +37 -0
- package/src/adapters/cursor/live-transport.ts +74 -2
- package/src/adapters/cursor.ts +6 -0
- package/src/adapters/devin/cloud-direct/auth.ts +264 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +306 -0
- package/src/adapters/devin/cloud-direct/chat.ts +1274 -0
- package/src/adapters/devin/cloud-direct/index.ts +65 -0
- package/src/adapters/devin/cloud-direct/metadata.ts +134 -0
- package/src/adapters/devin/cloud-direct/wire.ts +206 -0
- package/src/adapters/devin/live-models.ts +133 -0
- package/src/adapters/devin-cli/acp.ts +204 -0
- package/src/adapters/devin-cli/adapter.ts +345 -0
- package/src/adapters/devin-cli/binary.ts +69 -0
- package/src/adapters/devin-cli/models.ts +57 -0
- package/src/adapters/devin.ts +326 -0
- package/src/adapters/google.ts +1 -1
- package/src/adapters/openai-chat.ts +31 -10
- package/src/adapters/openai-responses.ts +16 -1
- package/src/adapters/registry.ts +25 -1
- package/src/bridge.ts +8 -2
- package/src/claude/inbound-cache-stabilize.ts +130 -0
- package/src/claude/inbound.ts +45 -5
- package/src/cli/account-auth.ts +60 -1
- package/src/cli/account-extended.ts +23 -30
- package/src/cli/account.ts +2 -1
- package/src/cli/capabilities.ts +50 -8
- package/src/cli/config-command.ts +2 -2
- package/src/cli/dispatch.ts +14 -2
- package/src/cli/export-command.ts +11 -25
- package/src/cli/help.ts +2 -2
- package/src/cli/opencode.ts +5 -0
- package/src/cli/registry.ts +15 -4
- package/src/clients/config-export/cline.ts +71 -0
- package/src/clients/config-export/contracts.ts +10 -1
- package/src/clients/config-export/model-metadata.ts +33 -0
- package/src/clients/config-export/zcode.ts +23 -12
- package/src/clients/config-export.ts +156 -4
- package/src/codex/account-store.ts +7 -2
- package/src/codex/auth-context.ts +1 -1
- package/src/codex/catalog/effort.ts +4 -3
- package/src/codex/catalog/metadata.ts +8 -4
- package/src/codex/catalog/native-models.ts +24 -4
- package/src/codex/catalog/parsing.ts +19 -1
- package/src/codex/catalog/provider-fetch.ts +57 -0
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/context-compat.ts +97 -0
- package/src/codex/context-owner.ts +201 -0
- package/src/codex/history-provider.ts +161 -14
- package/src/codex/inject-coordination.ts +3 -2
- package/src/codex/inject.ts +128 -9
- package/src/codex/pool-rotation.ts +8 -292
- package/src/codex/quota.ts +41 -12
- package/src/codex/retired-model-migration.ts +41 -0
- package/src/codex/routing.ts +327 -22
- package/src/codex/warmup.ts +2 -2
- package/src/combos/failover.ts +5 -0
- package/src/combos/resolve.ts +11 -15
- package/src/config.ts +46 -14
- package/src/generated/compatibility-version.json +293 -113
- package/src/grok/grpc-web.ts +120 -0
- package/src/grok/reset-coupon-ledger.ts +139 -0
- package/src/grok/reset-coupons.ts +278 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/cline-document.ts +73 -0
- package/src/integrations/cline-io.ts +149 -0
- package/src/integrations/cline-transaction.ts +42 -0
- package/src/integrations/config-io.ts +21 -2
- package/src/integrations/journal.ts +43 -1
- package/src/integrations/omp-yaml-source.ts +123 -4
- package/src/integrations/ownership-policy.ts +7 -0
- package/src/integrations/ownership.ts +36 -0
- package/src/integrations/registry.ts +27 -0
- package/src/integrations/state.ts +5 -2
- package/src/integrations/store.ts +6 -0
- package/src/integrations/writer.ts +53 -9
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/abort.ts +36 -0
- package/src/lib/local-destinations.ts +1 -1
- package/src/lib/upstream-retry.ts +36 -1
- package/src/oauth/account-quota-rank.ts +11 -0
- package/src/oauth/callback-server.ts +10 -5
- package/src/oauth/devin/api-base.ts +63 -0
- package/src/oauth/devin/login.ts +1 -0
- package/src/oauth/devin/register-user.ts +186 -0
- package/src/oauth/devin/types.ts +71 -0
- package/src/oauth/devin-cli.ts +149 -0
- package/src/oauth/devin.ts +170 -0
- package/src/oauth/generic-account-failover.ts +169 -1
- package/src/oauth/index.ts +20 -1
- package/src/oauth/login-cli.ts +19 -5
- package/src/oauth/pool-kernel.ts +321 -0
- package/src/oauth/pool-settings-capability.ts +127 -9
- package/src/oauth/store.ts +7 -3
- package/src/oauth/token-guardian.ts +1 -1
- package/src/providers/codebuddy-models.ts +0 -3
- package/src/providers/command-code-efforts.ts +23 -4
- package/src/providers/default-aliases.ts +4 -0
- package/src/providers/derive.ts +8 -0
- package/src/providers/devin-cli-authmode-migration.ts +57 -0
- package/src/providers/key-failover.ts +175 -0
- package/src/providers/model-rename-startup.ts +21 -1
- package/src/providers/qoder-models.ts +0 -1
- package/src/providers/quota-key-accounts.ts +50 -0
- package/src/providers/quota-routing-cache.ts +65 -6
- package/src/providers/quota-types.ts +7 -0
- package/src/providers/quota.ts +113 -30
- package/src/providers/registry.ts +237 -77
- package/src/providers/stale-context-window-migration.ts +92 -0
- package/src/providers/zai-responses-migration.ts +45 -0
- package/src/quota/reset-observer.ts +2 -1
- package/src/quota/reset-seen-store.ts +9 -1
- package/src/remote-control/crypto.ts +442 -0
- package/src/remote-control/host.ts +175 -0
- package/src/remote-control/index.ts +100 -0
- package/src/remote-control/protocol.ts +200 -0
- package/src/remote-control/relay.ts +162 -0
- package/src/remote-control/workspace-agent-protocol.ts +246 -0
- package/src/remote-control/workspace-rpc-framing.ts +128 -0
- package/src/remote-control/workspace-tools.ts +237 -0
- package/src/remote-control/workspace-utf8.ts +24 -0
- package/src/responses/custom-tool-compat.ts +23 -0
- package/src/router.ts +6 -1
- package/src/routing/compatibility/behavior.ts +3 -0
- package/src/server/adapter-resolve.ts +6 -2
- package/src/server/auth-cors.ts +71 -6
- package/src/server/chat-completions.ts +5 -3
- package/src/server/chat-native.ts +9 -0
- package/src/server/claude-messages.ts +25 -30
- package/src/server/context-history.ts +207 -0
- package/src/server/images.ts +28 -1
- package/src/server/index.ts +42 -22
- package/src/server/live.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +7 -1
- package/src/server/management/config-routes.ts +3 -3
- package/src/server/management/grok-coupon-routes.ts +287 -0
- package/src/server/management/integration-routes.ts +6 -1
- package/src/server/management/oauth-account-routes.ts +124 -7
- package/src/server/management/provider-routes.ts +77 -2
- package/src/server/management/route-registry.ts +6 -1
- package/src/server/management-api.ts +7 -0
- package/src/server/request-log.ts +4 -0
- package/src/server/responses/codex-ws-exchange.ts +90 -14
- package/src/server/responses/codex-ws-wire.ts +51 -1
- package/src/server/responses/compact.ts +8 -1
- package/src/server/responses/core.ts +188 -22
- package/src/server/responses/ws-upstream.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +261 -12
- package/src/server/zai-responses-startup.ts +21 -0
- package/src/types/config.ts +33 -3
- package/src/types/provider.ts +54 -4
- package/src/types/request.ts +1 -1
- package/src/types/tools.ts +36 -6
- package/src/update/job.ts +33 -11
- package/src/vision/plan.ts +40 -37
- package/src/web-search/index.ts +1 -0
- package/gui/dist/assets/index-BoBRSehJ.css +0 -1
- 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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
411
|
-
|
|
412
|
-
|
|
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
|
+
}
|
package/src/types/config.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
1133
|
+
/** Model used for optional Codex pool warmup. Default gpt-5.6-luna. */
|
|
1104
1134
|
codexWarmupModel?: string;
|
|
1105
1135
|
}
|
|
1106
1136
|
|
package/src/types/provider.ts
CHANGED
|
@@ -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
|
|
285
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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;
|
package/src/types/request.ts
CHANGED
|
@@ -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.
|
|
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). */
|
package/src/types/tools.ts
CHANGED
|
@@ -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.
|
|
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
|
|
88
|
+
if (!declared) return name;
|
|
74
89
|
if (declared.has(name)) return name;
|
|
75
|
-
|
|
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
|
|
110
|
+
return candidate;
|
|
81
111
|
}
|
|
82
|
-
return (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(
|
|
112
|
+
return (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(candidate)
|
|
83
113
|
? CODE_MODE_EXEC_TOOL_NAME
|
|
84
|
-
:
|
|
114
|
+
: candidate;
|
|
85
115
|
}
|
|
86
116
|
|
|
87
117
|
/**
|