@bitkyc08/opencodex 2.64.0 → 2.65.0-preview.20260925
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/README.md +28 -25
- package/bin/ocx.mjs +16 -2
- package/gui/dist/assets/App-8NMiZxT0.css +1 -0
- package/gui/dist/assets/App-CsYvvpr3.js +50 -0
- package/gui/dist/assets/Tray-DUvc_Wul.js +1 -0
- package/gui/dist/assets/{index-DiBRuK-d.css → index--EWgGQvZ.css} +1 -1
- package/gui/dist/assets/{index-SggB6t3z.js → index-BB0iHG8-.js} +12 -12
- package/gui/dist/assets/tray-data-f0lTZ4sF.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -4
- package/src/adapters/anthropic.ts +30 -1
- package/src/adapters/claude-cli/adapter.ts +218 -0
- package/src/adapters/claude-cli/profiles.ts +38 -0
- package/src/adapters/codebuddy/profiles.ts +2 -0
- package/src/adapters/coding-agent/profile.ts +12 -4
- package/src/adapters/coding-agent/turn.ts +16 -6
- package/src/adapters/command-code-tool-text.ts +133 -23
- package/src/adapters/cursor/catalog.ts +4 -8
- package/src/adapters/cursor/discovery.ts +10 -5
- package/src/adapters/cursor/effort-map.ts +2 -6
- package/src/adapters/cursor/envelope-echo.ts +10 -5
- package/src/adapters/cursor/request-builder.ts +9 -4
- package/src/adapters/cursor/thread-continuity.ts +53 -0
- package/src/adapters/cursor.ts +29 -13
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/google-tool-schema.ts +26 -3
- package/src/adapters/identity.ts +214 -2
- package/src/adapters/openai-chat/passthrough.ts +1 -0
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +569 -0
- package/src/adapters/openai-chat.ts +43 -15
- package/src/adapters/openai-responses/canonical-forward.ts +17 -10
- package/src/adapters/openai-responses/passthrough.ts +19 -4
- package/src/adapters/openai-responses/request-strips.ts +25 -0
- package/src/adapters/openai-responses/web-search.ts +22 -4
- package/src/adapters/qoder/profiles.ts +2 -0
- package/src/adapters/registry.ts +8 -0
- package/src/adapters/run-turn-queue.ts +7 -0
- package/src/bridge/response-json.ts +6 -4
- package/src/bridge/sse.ts +7 -5
- package/src/claude/alias.ts +73 -26
- package/src/claude/context-windows.ts +15 -6
- package/src/claude/desktop-3p-library.ts +3 -2
- package/src/claude/desktop-3p.ts +1 -1
- package/src/claude/desktop-first-party.ts +63 -22
- package/src/claude/desktop-picker-profile.ts +302 -0
- package/src/claude/desktop-picker.ts +365 -0
- package/src/claude/desktop-risk.ts +20 -0
- package/src/claude/gateway-cache.ts +13 -4
- package/src/claude/inbound.ts +5 -2
- package/src/claude/intercept/connect-proxy.ts +106 -29
- package/src/claude/intercept/local-ca.ts +76 -18
- package/src/claude/intercept/model-bindings.ts +145 -0
- package/src/claude/intercept/picker-bootstrap.ts +141 -0
- package/src/claude/intercept/picker-ca.ts +126 -0
- package/src/claude/intercept/picker-listener.ts +213 -0
- package/src/claude/intercept/picker-models.ts +101 -0
- package/src/claude/intercept/picker-runtime.ts +330 -0
- package/src/claude/intercept/picker-trust.ts +125 -0
- package/src/claude/intercept/runtime.ts +149 -2
- package/src/claude/model-info.ts +18 -4
- package/src/cli/agent.ts +16 -2
- package/src/cli/capabilities.ts +71 -2
- package/src/cli/claude-desktop.ts +319 -33
- package/src/cli/claude.ts +15 -7
- package/src/cli/codex-shim-autorestore.ts +1 -0
- package/src/cli/dispatch.ts +29 -23
- package/src/cli/effort.ts +16 -6
- package/src/cli/ensure-desired-integrations.ts +29 -4
- package/src/cli/ready.ts +1 -1
- package/src/cli/registry.ts +8 -0
- package/src/cli/restart-scope.ts +9 -1
- package/src/cli/runtime-api.ts +17 -0
- package/src/cli/system-command.ts +11 -12
- package/src/client/connect.ts +12 -2
- package/src/client/state.ts +39 -1
- package/src/clients/config-export.ts +57 -9
- package/src/codex/auth-context.ts +51 -11
- package/src/codex/catalog/derive-entry.ts +5 -5
- package/src/codex/catalog/gather-capture.ts +25 -6
- package/src/codex/catalog/metadata.ts +3 -3
- package/src/codex/catalog/parsing.ts +39 -1
- package/src/codex/catalog/provider-models.ts +8 -6
- package/src/codex/catalog/retained-sync.ts +17 -7
- package/src/codex/catalog/sync.ts +2 -2
- package/src/codex/desktop-app-restart.ts +8 -0
- package/src/codex/desktop-switches.ts +9 -1
- package/src/codex/home.ts +43 -4
- package/src/codex/inject/config-toml.ts +137 -0
- package/src/codex/inject/plan.ts +23 -0
- package/src/codex/inject/remove.ts +21 -1
- package/src/codex/inject.ts +63 -8
- package/src/codex/journal.ts +36 -0
- package/src/codex/main-account-hard-lock.ts +14 -1
- package/src/codex/main-account-policy-wait.ts +109 -0
- package/src/codex/native-profile-startup.ts +24 -6
- package/src/codex/native-residue.ts +28 -10
- package/src/codex/quota-types.ts +8 -1
- package/src/codex/quota.ts +49 -7
- package/src/codex/runtime.ts +31 -10
- package/src/combos/failover.ts +7 -6
- package/src/combos/resolve.ts +91 -14
- package/src/combos/types.ts +18 -1
- package/src/config/load-degrade.ts +2 -0
- package/src/config/schema/config-schema.ts +21 -2
- package/src/config/schema/leaf-validators.ts +6 -3
- package/src/config/subagent-models.ts +3 -1
- package/src/generated/compatibility-version.json +275 -163
- package/src/images/artifacts.ts +14 -0
- package/src/integrations/owned-refresh.ts +1 -1
- package/src/integrations/ownership-policy.ts +32 -1
- package/src/integrations/state.ts +3 -1
- package/src/integrations/writer.ts +9 -0
- package/src/lib/config-ownership.ts +3 -0
- package/src/lib/errors.ts +87 -0
- package/src/lib/package-tree-integrity.ts +1 -1
- package/src/lib/request-execution-budget.ts +42 -14
- package/src/lib/request-resend-gate.ts +4 -3
- package/src/lib/retry-delay.ts +7 -1
- package/src/lib/tool-envelope-echo-filter.ts +236 -0
- package/src/lib/upstream-retry.ts +20 -12
- package/src/oauth/index.ts +1 -0
- package/src/oauth/kiro-credentials.ts +14 -1
- package/src/oauth/login-cli.ts +1 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/fast-opt-in.ts +31 -0
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/provider-id-rewrite.ts +2 -0
- package/src/providers/quota/vendor-probes-key.ts +8 -2
- package/src/providers/registry/entries-core.ts +45 -2
- package/src/providers/registry/entries-extended.ts +99 -12
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/types.ts +7 -1
- package/src/providers/resolved-model-policy.ts +11 -4
- package/src/providers/service-tier.ts +10 -3
- package/src/responses/code-mode-helper-compat.ts +3 -0
- package/src/responses/parser.ts +45 -3
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +8 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/claude-messages.ts +69 -14
- package/src/server/grok-upstream-envelope-echo.ts +120 -0
- package/src/server/index/claude-intercept-lifecycle.ts +24 -0
- package/src/server/index/serve-options.ts +2 -2
- package/src/server/index.ts +7 -0
- package/src/server/management/agent-settings-routes.ts +204 -112
- package/src/server/management/claude-desktop-picker-routes.ts +128 -0
- package/src/server/management/combo-routes.ts +59 -2
- package/src/server/management/config-routes.ts +81 -39
- package/src/server/management/context.ts +3 -0
- package/src/server/management/native-integration-routes.ts +169 -93
- package/src/server/management/provider-routes.ts +46 -4
- package/src/server/management/route-registry.ts +10 -0
- package/src/server/management/routing-profile-routes.ts +9 -0
- package/src/server/management/sidebar-routes.ts +52 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/proxy-liveness.ts +38 -3
- package/src/server/request-log-account-rotation.ts +53 -0
- package/src/server/request-log.ts +3 -16
- package/src/server/responses/adapter-dispatch.ts +17 -1
- package/src/server/responses/agent-task-recovery.ts +49 -26
- package/src/server/responses/codex-ws-exchange.ts +18 -7
- package/src/server/responses/codex-ws-wire.ts +27 -4
- package/src/server/responses/core-combo.ts +21 -3
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-opaque-recovery.ts +3 -2
- package/src/server/responses/encrypted-payload.ts +15 -14
- package/src/server/responses/fetch-helpers.ts +1 -1
- package/src/server/responses/input-admission.ts +10 -6
- package/src/server/responses/native-response-control.ts +2 -2
- package/src/server/responses/passthrough-delivery.ts +182 -12
- package/src/server/responses/passthrough-dispatch.ts +102 -44
- package/src/server/responses/policy-fallback.ts +3 -0
- package/src/server/responses/policy-refusal.ts +67 -0
- package/src/server/responses/request-send-budget.ts +4 -0
- package/src/server/responses/run-turn-execution.ts +42 -11
- package/src/server/responses/ws-upstream.ts +2 -1
- package/src/server/system-env-shell.ts +0 -1
- package/src/server/system-env.ts +25 -4
- package/src/service/cli.ts +34 -3
- package/src/tray/assets/opencodex-tray-offline-update.ico +0 -0
- package/src/tray/assets/opencodex-tray-online-update.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning-update.ico +0 -0
- package/src/tray/windows-tray.ps1 +188 -7
- package/src/tray/windows.ts +23 -2
- package/src/types/config.ts +52 -12
- package/src/types/provider.ts +22 -5
- package/src/types/tools.ts +11 -5
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +109 -0
- package/src/update/badge.ts +11 -17
- package/src/update/check-types.ts +9 -0
- package/src/update/desktop-badge.ts +102 -0
- package/src/update/index.ts +48 -8
- package/src/update/install-detection.d.mts +29 -2
- package/src/update/install-detection.mjs +213 -7
- package/src/update/job.ts +17 -12
- package/src/update/notify.ts +16 -11
- package/src/update/pnpm-owner-worker.ts +12 -0
- package/src/update/refresh-scheduler.ts +141 -0
- package/src/usage/cost.ts +31 -4
- package/src/usage/expected-prices.ts +57 -0
- package/assets/download-linux.svg +0 -10
- package/assets/download-macos.svg +0 -10
- package/assets/download-windows.svg +0 -10
- package/gui/dist/assets/App-CpuDF3ci.js +0 -50
- package/gui/dist/assets/App-I5AnaSLh.css +0 -1
- package/gui/dist/assets/Tray-B4uEIa1O.js +0 -1
- package/gui/dist/assets/usage-companion-chart-DLbKOJml.js +0 -1
|
@@ -45,6 +45,7 @@ import { messagesToChatFormat } from "./openai-chat/messages";
|
|
|
45
45
|
import { withOpenAIChatToolNames } from "./openai-chat/tool-name-registry";
|
|
46
46
|
import { openAIChatTransport, stripBracketedModelSuffix } from "./openai-chat/wire";
|
|
47
47
|
import { toolChoiceToChatFormat, toolsToChatFormatForProvider } from "./openai-chat/tool-schema";
|
|
48
|
+
import { reconcileSerializedToolCallEvents, reconcileStructuredToolCall, reconcileStructuredToolCalls, SerializedToolCallContentBuffer } from "./openai-chat/serialized-tool-call-content";
|
|
48
49
|
|
|
49
50
|
export { stripBracketedModelSuffix } from "./openai-chat/wire";
|
|
50
51
|
export { buildOpenAIChatPassthroughRequest } from "./openai-chat/passthrough";
|
|
@@ -143,7 +144,9 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
143
144
|
if (parsed.options.topP !== undefined && !modelInList(provider.noTopPModels, parsed.modelId)) {
|
|
144
145
|
body.top_p = parsed.options.topP;
|
|
145
146
|
}
|
|
146
|
-
if (parsed.options.stopSequences !== undefined
|
|
147
|
+
if (parsed.options.stopSequences !== undefined && !modelInList(provider.noStopModels, parsed.modelId)) {
|
|
148
|
+
body.stop = parsed.options.stopSequences;
|
|
149
|
+
}
|
|
147
150
|
const reasoningDisabled = modelInList(provider.noReasoningModels, parsed.modelId);
|
|
148
151
|
const reasoningEffort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
149
152
|
const explicitReasoning = applyExplicitChatReasoningWirePolicy({
|
|
@@ -299,6 +302,8 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
299
302
|
sawArgumentsString: boolean;
|
|
300
303
|
}
|
|
301
304
|
const pendingToolCalls: PendingToolCall[] = [];
|
|
305
|
+
const toolCallContent = new SerializedToolCallContentBuffer(budget);
|
|
306
|
+
const heldText = (): AdapterEvent[] => toolCallContent.drain([]);
|
|
302
307
|
let toolCallSeq = 0;
|
|
303
308
|
const closeToolCalls = (): PendingToolCall[] => {
|
|
304
309
|
const calls = [...pendingToolCalls];
|
|
@@ -310,7 +315,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
310
315
|
pendingToolCalls.length > 0 && pendingToolCalls.every(call => {
|
|
311
316
|
if (call.name.trim().length === 0 || !call.sawArgumentsString || call.args.length === 0) return false;
|
|
312
317
|
try {
|
|
313
|
-
const parsed = JSON.parse(call.args) as unknown;
|
|
318
|
+
const parsed = JSON.parse(reconcileStructuredToolCall(call.name, toolNames.restore(call.name), call.args, toolCallContent.current()).argumentsText) as unknown;
|
|
314
319
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
|
|
315
320
|
} catch {
|
|
316
321
|
return false;
|
|
@@ -320,7 +325,8 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
320
325
|
// stops the turn instead of emitting an unusable call. `closeToolCalls()` runs first,
|
|
321
326
|
// so budget reservations are released for every pending call even on the early return.
|
|
322
327
|
const flushToolCalls = function* (): Generator<AdapterEvent, "continue" | "terminate"> {
|
|
323
|
-
|
|
328
|
+
const calls = closeToolCalls();
|
|
329
|
+
for (const call of calls) {
|
|
324
330
|
// Ingest already proved `name` is a string; the typeof guard keeps this branch
|
|
325
331
|
// total so a future ingest change cannot turn a malformed name into a throw.
|
|
326
332
|
if (typeof call.name !== "string" || call.name.trim().length === 0) {
|
|
@@ -328,9 +334,14 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
328
334
|
hadId: call.id.length > 0,
|
|
329
335
|
argsBytes: call.argsBytes,
|
|
330
336
|
});
|
|
331
|
-
yield unnamedToolCallEvent(pendingUsage);
|
|
332
|
-
return "terminate";
|
|
337
|
+
return yield* terminateWithError(unnamedToolCallEvent(pendingUsage));
|
|
333
338
|
}
|
|
339
|
+
}
|
|
340
|
+
// Held markup is released only now, as one batch per response: the doubled-input repair needs every call.
|
|
341
|
+
const references = reconcileStructuredToolCalls(calls.map(call => ({ wireName: call.name, restoredName: toolNames.restore(call.name), argumentsText: call.args })), toolCallContent.current());
|
|
342
|
+
calls.forEach((call, index) => { call.args = references[index]!.argumentsText; });
|
|
343
|
+
yield* toolCallContent.drain(references);
|
|
344
|
+
for (const call of calls) {
|
|
334
345
|
if (!call.id) call.id = `call_${++toolCallSeq}`;
|
|
335
346
|
yield { type: "tool_call_start", id: call.id, name: toolNames.restore(call.name) };
|
|
336
347
|
if (call.args.length > 0) yield { type: "tool_call_delta", arguments: call.args };
|
|
@@ -342,6 +353,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
342
353
|
event: Extract<AdapterEvent, { type: "error" }>,
|
|
343
354
|
): Generator<AdapterEvent, "terminate"> {
|
|
344
355
|
closeToolCalls();
|
|
356
|
+
yield* heldText(); // Pending tools are not dispatched, so held text stays visible.
|
|
345
357
|
yield event;
|
|
346
358
|
return "terminate";
|
|
347
359
|
};
|
|
@@ -360,7 +372,13 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
360
372
|
// <think> blocks, which would otherwise render as the answer. Passthrough unless opted in.
|
|
361
373
|
const inlineThink = createInlineThinkContentSplitter(provider.inlineThinkTagModels, lastRequestedModelId, budget);
|
|
362
374
|
const emitContent = function* (events: AdapterEvent[]): Generator<AdapterEvent> {
|
|
363
|
-
for (const event of events) {
|
|
375
|
+
for (const event of events) {
|
|
376
|
+
// Any other event keeps its place behind held text instead of overtaking it.
|
|
377
|
+
if (event.type !== "text_delta") { yield* toolCallContent.hold(event); continue; }
|
|
378
|
+
sawUserFacingOutput = true;
|
|
379
|
+
const released = toolCallContent.ingestStreaming(event.text);
|
|
380
|
+
yield* released.length > 0 ? released : [{ type: "heartbeat" } as AdapterEvent];
|
|
381
|
+
}
|
|
364
382
|
};
|
|
365
383
|
|
|
366
384
|
const handleDataLine = function* (line: string): Generator<AdapterEvent, "continue" | "terminate"> {
|
|
@@ -381,8 +399,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
381
399
|
parsed = JSON.parse(payload);
|
|
382
400
|
} catch {
|
|
383
401
|
tierMetadata?.markResponseUnparseable();
|
|
384
|
-
yield { type: "error", message: "malformed upstream SSE data frame" };
|
|
385
|
-
return "terminate";
|
|
402
|
+
return yield* terminateWithError({ type: "error", message: "malformed upstream SSE data frame" });
|
|
386
403
|
}
|
|
387
404
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return "continue";
|
|
388
405
|
const chunk = parsed as Record<string, unknown>;
|
|
@@ -423,11 +440,11 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
423
440
|
if (detailSegments.length > 0) {
|
|
424
441
|
for (const segment of detailSegments) {
|
|
425
442
|
const reasoningDelta = reasoningDetailTracker.ingest(segment);
|
|
426
|
-
if (reasoningDelta !== null) yield { type: "reasoning_raw_delta", text: reasoningDelta };
|
|
443
|
+
if (reasoningDelta !== null) yield* toolCallContent.hold({ type: "reasoning_raw_delta", text: reasoningDelta });
|
|
427
444
|
}
|
|
428
445
|
} else {
|
|
429
446
|
const reasoningText = reasoningTextFrom(delta);
|
|
430
|
-
if (reasoningText !== undefined) yield { type: "reasoning_raw_delta", text: reasoningText };
|
|
447
|
+
if (reasoningText !== undefined) yield* toolCallContent.hold({ type: "reasoning_raw_delta", text: reasoningText });
|
|
431
448
|
}
|
|
432
449
|
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
433
450
|
yield* emitContent(inlineThink.feed(delta.content));
|
|
@@ -630,7 +647,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
630
647
|
hadUsage: pendingUsage !== undefined,
|
|
631
648
|
pendingToolCalls: pendingToolCalls.length,
|
|
632
649
|
});
|
|
633
|
-
yield { type: "error", message: "upstream stream ended mid tool call without a terminal signal — possible truncation" };
|
|
650
|
+
yield* terminateWithError({ type: "error", message: "upstream stream ended mid tool call without a terminal signal — possible truncation" });
|
|
634
651
|
return;
|
|
635
652
|
}
|
|
636
653
|
if (!sawFinish && !sawUserFacingOutput) {
|
|
@@ -638,13 +655,15 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
638
655
|
finishReason: finishReason ?? null,
|
|
639
656
|
hadUsage: pendingUsage !== undefined,
|
|
640
657
|
});
|
|
641
|
-
yield { type: "error", message: "upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation" };
|
|
658
|
+
yield* terminateWithError({ type: "error", message: "upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation" });
|
|
642
659
|
return;
|
|
643
660
|
}
|
|
644
661
|
if ((yield* flushToolCalls()) === "terminate") return;
|
|
645
662
|
const stopReason = stopReasonFor(finishReason);
|
|
646
663
|
yield { type: "done", usage: pendingUsage, ...(stopReason ? { stopReason } : {}) };
|
|
647
664
|
} catch (error) {
|
|
665
|
+
closeToolCalls();
|
|
666
|
+
yield* heldText();
|
|
648
667
|
if (isTranslatorBudgetExceededError(error)
|
|
649
668
|
|| (error instanceof Error && (error.cause as { code?: unknown } | undefined)?.code === "translation_buffer_limit")) {
|
|
650
669
|
yield {
|
|
@@ -662,6 +681,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
662
681
|
budget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
663
682
|
reasoningDetailTracker.release();
|
|
664
683
|
inlineThink.dispose();
|
|
684
|
+
toolCallContent.dispose();
|
|
665
685
|
closeToolCalls();
|
|
666
686
|
reader.releaseLock();
|
|
667
687
|
}
|
|
@@ -748,7 +768,12 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
748
768
|
if (segments.length > 0) reasoningText = segments.map(s => s.text).join("");
|
|
749
769
|
}
|
|
750
770
|
if (reasoningText !== undefined) events.push({ type: "reasoning_raw_delta", text: reasoningText });
|
|
771
|
+
const contentStart = events.length;
|
|
751
772
|
if (typeof msg.content === "string") events.push(...splitInlineThinkContent(provider.inlineThinkTagModels, lastRequestedModelId, budget, msg.content));
|
|
773
|
+
const contentEnd = events.length;
|
|
774
|
+
const answerText = events.slice(contentStart).map(event => (event.type === "text_delta" ? event.text : "")).join("");
|
|
775
|
+
// Each call holds the delta event it emitted, so the batch repair sets its arguments later.
|
|
776
|
+
const structuredCalls: { wireName: string; restoredName: string; argumentsText: string; delta: Extract<AdapterEvent, { type: "tool_call_delta" }> }[] = [];
|
|
752
777
|
const rawToolCalls = msg.tool_calls;
|
|
753
778
|
if (rawToolCalls !== undefined && rawToolCalls !== null) {
|
|
754
779
|
if (!Array.isArray(rawToolCalls)) {
|
|
@@ -771,11 +796,14 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
771
796
|
logInvalidToolCalls("response", rawToolCalls);
|
|
772
797
|
return [invalidToolCallsEvent(rawToolCalls, "response", usage)];
|
|
773
798
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
events.push({ type: "tool_call_end" });
|
|
799
|
+
const delta: Extract<AdapterEvent, { type: "tool_call_delta" }> = { type: "tool_call_delta", arguments: args };
|
|
800
|
+
structuredCalls.push({ wireName: name, restoredName: toolNames.restore(name), argumentsText: args, delta });
|
|
801
|
+
events.push({ type: "tool_call_start", id, name: toolNames.restore(name) }, delta, { type: "tool_call_end" });
|
|
777
802
|
}
|
|
778
803
|
}
|
|
804
|
+
const references = reconcileStructuredToolCalls(structuredCalls, answerText);
|
|
805
|
+
structuredCalls.forEach((call, index) => { call.delta.arguments = references[index]!.argumentsText; });
|
|
806
|
+
reconcileSerializedToolCallEvents(events, contentStart, contentEnd, references, budget);
|
|
779
807
|
const stopReason = stopReasonFor(choice.finish_reason);
|
|
780
808
|
events.push({
|
|
781
809
|
type: "done",
|
|
@@ -64,20 +64,27 @@ export function stripStatefulResponsesParams(body: unknown): unknown {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Remove top-level parameters
|
|
68
|
-
*
|
|
67
|
+
* Remove top-level parameters a forward destination rejects with
|
|
68
|
+
* `{"detail":"Unsupported parameter: …"}` (strict allowlist). Codex CLI never
|
|
69
69
|
* sends these — it controls output length via `reasoning.effort` — but third-party
|
|
70
70
|
* Responses API clients (GJC, SDK wrappers) include `max_output_tokens` per the
|
|
71
|
-
* public spec.
|
|
72
|
-
*
|
|
71
|
+
* public spec. No-op when the body carries none of the dropped fields, keeping the
|
|
72
|
+
* common Codex path allocation-free.
|
|
73
|
+
*
|
|
74
|
+
* The canonical ChatGPT backend rejects `metadata`, so the forwarding path removes it on every
|
|
75
|
+
* forward route for compatibility.
|
|
76
|
+
* `max_output_tokens` does not: it is the caller's cost cap on the turn, and a
|
|
77
|
+
* self-hosted or third-party gateway may honour it — dropping it there silently
|
|
78
|
+
* removes the cap. Only the canonical ChatGPT backend rejects it outright, so only
|
|
79
|
+
* `canonical` callers strip it.
|
|
73
80
|
*/
|
|
74
|
-
export function stripUnsupportedForwardParams(body: unknown): unknown {
|
|
81
|
+
export function stripUnsupportedForwardParams(body: unknown, canonical: boolean): unknown {
|
|
75
82
|
if (!isPlainObject(body)) return body;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
return
|
|
83
|
+
const drop = canonical ? (["metadata", "max_output_tokens"] as const) : (["metadata"] as const);
|
|
84
|
+
if (!drop.some(key => Object.hasOwn(body, key))) return body;
|
|
85
|
+
const next: Record<string, unknown> = { ...body };
|
|
86
|
+
for (const key of drop) delete next[key];
|
|
87
|
+
return next;
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/** Sampling controls the canonical ChatGPT backend rejects; other forward gateways accept them. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeRoutedAgentMessages } from "../routed-agent-messages";
|
|
2
|
+
import { nameRoutedIdentity, repairIdentityInResponsesBody, stripRoutedIdentity } from "../identity";
|
|
2
3
|
import { stripBracketedModelSuffix } from "../openai-chat";
|
|
3
4
|
import { normalizeOpenCodeGoAdditionalTools } from "../opencode-go-additional-tools";
|
|
4
5
|
import { isXaiResponsesDestination } from "../../providers/xai-transport";
|
|
@@ -33,7 +34,7 @@ import {
|
|
|
33
34
|
createAdapterTierMetadata,
|
|
34
35
|
} from "../../providers/fastwire";
|
|
35
36
|
import { dropResponsesReasoningInputItems, mapRoutedResponsesReasoningEffort, normalizeConfiguredReasoningSummaryDelivery, sanitizeReasoningInputContent, stripDisabledReasoningSummaries, stripDisabledVerbosity, stripUnsupportedReasoningSummaryDelivery } from "./reasoning";
|
|
36
|
-
import { scrubOcxCompactionItems, stripCanonicalOnlyToolFields, stripCanonicalOnlyTopLevelFields, stripInternalChatMessageMetadataPassthrough, stripInvalidItemIds, stripItemIdsWhenUnstored } from "./request-strips";
|
|
37
|
+
import { scrubOcxCompactionItems, stripCanonicalOnlyToolFields, stripCanonicalOnlyTopLevelFields, stripInternalChatMessageMetadataPassthrough, stripInvalidItemIds, stripItemIdsWhenUnstored, stripRejectedSamplingParams } from "./request-strips";
|
|
37
38
|
import { stripCanonicalForwardPromptCacheOptions, stripDeprecatedPromptCacheRetention } from "./prompt-cache";
|
|
38
39
|
import { isPlainObject } from "./internal";
|
|
39
40
|
import { normalizeToolSchemas, promoteClientLoadedTools, stripUnsupportedHostedTools } from "./tool-schema";
|
|
@@ -268,6 +269,17 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
268
269
|
if (!forward) outBody = normalizeRoutedAgentMessages(outBody, {
|
|
269
270
|
allowStringContent: isXaiResponsesDestination(provider),
|
|
270
271
|
});
|
|
272
|
+
// #5217: a sub-agent inherits the parent session's instruction block, so the identity
|
|
273
|
+
// sentence this proxy generated for the PARENT's model rides along to a worker running a
|
|
274
|
+
// different one. On a routed destination it is renamed to that destination — including the
|
|
275
|
+
// model-neutral catalog sentence, which this adapter never names itself; on a native/forward
|
|
276
|
+
// destination our sentence is dropped, because Codex's own identity wording (sent in the
|
|
277
|
+
// client's model_switch block) is the correct one there. Text the proxy did not generate —
|
|
278
|
+
// user turns, tool output, fenced code, provider-native blocks — is untouched.
|
|
279
|
+
outBody = repairIdentityInResponsesBody(
|
|
280
|
+
outBody,
|
|
281
|
+
forward ? stripRoutedIdentity : (text: string) => nameRoutedIdentity(text, parsed.modelId),
|
|
282
|
+
);
|
|
271
283
|
outBody = mapRoutedResponsesReasoningEffort(outBody, provider, parsed.modelId);
|
|
272
284
|
// stripPreviousResponseId() intentionally returns its input on a no-op. Detach before the
|
|
273
285
|
// tier write so a force-fast/default decision can never mutate parsed._rawBody.
|
|
@@ -312,9 +324,11 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
312
324
|
outBody = normalizeResponsesToolResultAdjacency(outBody);
|
|
313
325
|
}
|
|
314
326
|
if (forward) {
|
|
315
|
-
|
|
316
|
-
//
|
|
317
|
-
|
|
327
|
+
// `metadata` is stripped on every forward route for compatibility with the canonical backend.
|
|
328
|
+
// `max_output_tokens` is stripped only when this provider is the canonical backend.
|
|
329
|
+
outBody = stripUnsupportedForwardParams(outBody, isCanonicalOpenAiForwardProvider(provider));
|
|
330
|
+
// Only the canonical ChatGPT backend rejects the canonical-only fields below; a self-hosted
|
|
331
|
+
// or third-party forward gateway may accept them, so this guard must not be widened.
|
|
318
332
|
if (isCanonicalOpenAiForwardProvider(provider)) {
|
|
319
333
|
outBody = stripCanonicalForwardSamplingParams(outBody);
|
|
320
334
|
outBody = stripDeprecatedPromptCacheRetention(outBody, parsed.modelId);
|
|
@@ -323,6 +337,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
323
337
|
outBody = normalizeCanonicalForwardContinuationEnvelope(outBody);
|
|
324
338
|
}
|
|
325
339
|
} else {
|
|
340
|
+
outBody = stripRejectedSamplingParams(outBody, provider, parsed.modelId);
|
|
326
341
|
outBody = preferConfiguredHostedTools(
|
|
327
342
|
outBody,
|
|
328
343
|
provider,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { COMPACT_PROMPT, compactionItemToText, decodeCompactionSummary, isCompactionItemType } from "../../responses/compaction";
|
|
3
3
|
import { debugProviderDiagnostic } from "../../lib/debug";
|
|
4
|
+
import { modelInList } from "../../types";
|
|
4
5
|
import { isPlainObject } from "./internal";
|
|
5
6
|
import { activateDeferredTool } from "./tool-schema";
|
|
6
7
|
import { stripOpenAiOnlyWebSearchFields } from "./web-search";
|
|
@@ -165,6 +166,30 @@ export function stripCanonicalOnlyTopLevelFields(body: unknown): unknown {
|
|
|
165
166
|
return next;
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Sampling fields a model on the provider's `noStopModels` / `noPenaltyModels` list rejects on
|
|
171
|
+
* every wire. Claude inbound translates `stop_sequences` into a Responses `stop`
|
|
172
|
+
* (`src/claude/inbound.ts`), and a direct Responses caller can send penalties. Some listed models
|
|
173
|
+
* only have the Responses wire (xAI grok-4.20-multi-agent-0309 answers Chat Completions with 400),
|
|
174
|
+
* so the Chat adapter's omission cannot cover them. Returns the input unchanged when nothing is
|
|
175
|
+
* removed, so the caller-owned raw body is never mutated.
|
|
176
|
+
*/
|
|
177
|
+
export function stripRejectedSamplingParams(
|
|
178
|
+
body: unknown,
|
|
179
|
+
provider: { noStopModels?: string[]; noPenaltyModels?: string[] },
|
|
180
|
+
modelId: string,
|
|
181
|
+
): unknown {
|
|
182
|
+
if (!isPlainObject(body)) return body;
|
|
183
|
+
const dropStop = Object.hasOwn(body, "stop") && modelInList(provider.noStopModels, modelId);
|
|
184
|
+
const penalties = ["presence_penalty", "frequency_penalty"].filter(field => Object.hasOwn(body, field));
|
|
185
|
+
const dropPenalties = penalties.length > 0 && modelInList(provider.noPenaltyModels, modelId);
|
|
186
|
+
if (!dropStop && !dropPenalties) return body;
|
|
187
|
+
const next = { ...body };
|
|
188
|
+
if (dropStop) delete next.stop;
|
|
189
|
+
if (dropPenalties) for (const field of penalties) delete next[field];
|
|
190
|
+
return next;
|
|
191
|
+
}
|
|
192
|
+
|
|
168
193
|
/**
|
|
169
194
|
* When `store` is false, the upstream API does not persist response items. Any item ID
|
|
170
195
|
* forwarded in `input` is then interpreted as a reference to a stored item that does not
|
|
@@ -68,6 +68,10 @@ export function stripOpenAiOnlyWebSearchFields(body: unknown): unknown {
|
|
|
68
68
|
* same-shaped successor to 1.2 on the same Zen wire, and an equality check would
|
|
69
69
|
* have let a Codex-emitted `web_search` body reach the
|
|
70
70
|
* gateway and come back 400 for every request the moment 1.3 was selected.
|
|
71
|
+
*
|
|
72
|
+
* This list gates the two Zen destinations only. Zen serves nothing but the Contributor
|
|
73
|
+
* tiers there, so the id is a proxy for "this gateway"; the direct Meta host below serves
|
|
74
|
+
* a non-Contributor default and is gated by destination instead.
|
|
71
75
|
*/
|
|
72
76
|
const MUSE_SPARK_WEB_SEARCH_STRICT_MODELS = new Set([
|
|
73
77
|
"muse-spark-1.3-contributor",
|
|
@@ -76,10 +80,21 @@ const MUSE_SPARK_WEB_SEARCH_STRICT_MODELS = new Set([
|
|
|
76
80
|
"muse-spark-1.2-contributor-free",
|
|
77
81
|
]);
|
|
78
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Direct Meta Muse / Meta Model Responses. Its refusal is a gateway schema rule applied
|
|
85
|
+
* before inference, so it holds for every Muse model Meta serves — including the default
|
|
86
|
+
* `muse-spark-1.3`, which no Contributor-shaped list contains. Gating that host on model
|
|
87
|
+
* membership sent `search_content_types` through on every Codex `web_search` turn and 400ed
|
|
88
|
+
* the whole request. Same reading as the 64-char tool-name rewrite in
|
|
89
|
+
* `src/responses/muse-tool-name-alias.ts`, which is host-scoped and deliberately not
|
|
90
|
+
* model-gated for this reason.
|
|
91
|
+
*/
|
|
92
|
+
const MUSE_SPARK_STRICT_ANY_MODEL_DESTINATION = "https://api.meta.ai/v1/responses";
|
|
93
|
+
|
|
79
94
|
const MUSE_SPARK_WEB_SEARCH_STRICT_RESPONSE_URLS = new Set([
|
|
80
95
|
"https://opencode.ai/zen/v1/responses",
|
|
81
96
|
"https://opencode.ai/zen/go/v1/responses",
|
|
82
|
-
|
|
97
|
+
MUSE_SPARK_STRICT_ANY_MODEL_DESTINATION,
|
|
83
98
|
]);
|
|
84
99
|
|
|
85
100
|
const MUSE_SPARK_UNSUPPORTED_WEB_SEARCH_FIELDS = [
|
|
@@ -94,7 +109,8 @@ const MUSE_SPARK_UNSUPPORTED_WEB_SEARCH_FIELDS = [
|
|
|
94
109
|
* malformed, credentialed, or parameterized destinations keep their original body
|
|
95
110
|
* instead of assuming this gateway contract. Keep the rejected names together so a
|
|
96
111
|
* newly identified field is a one-line compatibility update rather than another
|
|
97
|
-
* bespoke rewrite.
|
|
112
|
+
* bespoke rewrite. The destination is the whole predicate on direct Meta; the Zen
|
|
113
|
+
* wires additionally require a known Contributor id.
|
|
98
114
|
*/
|
|
99
115
|
export function stripMuseSparkUnsupportedWebSearchFields(
|
|
100
116
|
body: unknown,
|
|
@@ -102,8 +118,6 @@ export function stripMuseSparkUnsupportedWebSearchFields(
|
|
|
102
118
|
responseUrl: string,
|
|
103
119
|
): unknown {
|
|
104
120
|
if (!isPlainObject(body)) return body;
|
|
105
|
-
if (typeof modelId !== "string") return body;
|
|
106
|
-
if (!MUSE_SPARK_WEB_SEARCH_STRICT_MODELS.has(modelId.trim().toLowerCase())) return body;
|
|
107
121
|
let destination: string;
|
|
108
122
|
try {
|
|
109
123
|
const url = new URL(responseUrl);
|
|
@@ -113,6 +127,10 @@ export function stripMuseSparkUnsupportedWebSearchFields(
|
|
|
113
127
|
return body;
|
|
114
128
|
}
|
|
115
129
|
if (!MUSE_SPARK_WEB_SEARCH_STRICT_RESPONSE_URLS.has(destination)) return body;
|
|
130
|
+
if (
|
|
131
|
+
destination !== MUSE_SPARK_STRICT_ANY_MODEL_DESTINATION
|
|
132
|
+
&& (typeof modelId !== "string" || !MUSE_SPARK_WEB_SEARCH_STRICT_MODELS.has(modelId.trim().toLowerCase()))
|
|
133
|
+
) return body;
|
|
116
134
|
|
|
117
135
|
const rewriteTools = (tools: unknown[]): { tools: unknown[]; changed: boolean } => {
|
|
118
136
|
let changed = false;
|
|
@@ -3,6 +3,8 @@ import { clearCodingAgentBinaryCache, resolveProfileByBaseUrl, type CodingAgentP
|
|
|
3
3
|
/** Official Qoder CLI profile. Region variants are separate profiles and credentials. */
|
|
4
4
|
export interface QoderProfile extends CodingAgentProviderProfile {
|
|
5
5
|
family: "qoder";
|
|
6
|
+
/** Required for this family: the CLI is driven with a personal access token. */
|
|
7
|
+
tokenEnv: string;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export const QODER_GLOBAL_PROFILE: QoderProfile = {
|
package/src/adapters/registry.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createAnthropicAdapter } from "./anthropic";
|
|
2
2
|
import { createAzureAdapter } from "./azure";
|
|
3
3
|
import type { ProviderAdapter } from "./base";
|
|
4
|
+
import { createClaudeCliAdapter } from "./claude-cli/adapter";
|
|
4
5
|
import { withClinePassDeepSeekV4ToolReplayCompatibility } from "./cline-pass-deepseek-v4-tool-replay";
|
|
5
6
|
import { createCodeBuddyAdapter } from "./codebuddy/adapter";
|
|
6
7
|
import { createQoderAdapter } from "./qoder/adapter";
|
|
@@ -138,6 +139,13 @@ export const ADAPTER_REGISTRY = {
|
|
|
138
139
|
contractParent: "codebuddy",
|
|
139
140
|
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createQoderAdapter(provider),
|
|
140
141
|
},
|
|
142
|
+
"claude-cli": {
|
|
143
|
+
// Claude Code speaks the same stream-json contract this repo already parses for CodeBuddy and
|
|
144
|
+
// Qoder, so the contract is inherited rather than restated. The family owns its args and env,
|
|
145
|
+
// and the CLI owns the credential: the adapter stores and injects none.
|
|
146
|
+
contractParent: "codebuddy",
|
|
147
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createClaudeCliAdapter(provider),
|
|
148
|
+
},
|
|
141
149
|
} as const satisfies Record<string, AdapterDefinition>;
|
|
142
150
|
|
|
143
151
|
export type AdapterId = keyof typeof ADAPTER_REGISTRY;
|
|
@@ -152,6 +152,7 @@ async function* replay(
|
|
|
152
152
|
|
|
153
153
|
export async function preflightAdapterEvents(
|
|
154
154
|
source: AsyncIterable<AdapterEvent>,
|
|
155
|
+
classifyFirstEvent?: (event: AdapterEvent) => Extract<AdapterEvent, { type: "error" }> | undefined,
|
|
155
156
|
): Promise<AdapterEventPreflight> {
|
|
156
157
|
const iterator = source[Symbol.asyncIterator]();
|
|
157
158
|
const buffered: AdapterEvent[] = [];
|
|
@@ -165,6 +166,12 @@ export async function preflightAdapterEvents(
|
|
|
165
166
|
if (buffered.length > PREFLIGHT_HEARTBEAT_RETAIN_LIMIT) buffered.shift();
|
|
166
167
|
continue;
|
|
167
168
|
}
|
|
169
|
+
const classifiedError = replayUnsafe ? undefined : classifyFirstEvent?.(next.value);
|
|
170
|
+
if (classifiedError) {
|
|
171
|
+
buffered.push(classifiedError);
|
|
172
|
+
await iterator.return?.();
|
|
173
|
+
return { stream: replay(buffered, iterator), error: classifiedError, empty: false, replayUnsafe };
|
|
174
|
+
}
|
|
168
175
|
buffered.push(next.value);
|
|
169
176
|
if (next.value.type === "error") {
|
|
170
177
|
await iterator.return?.();
|
|
@@ -45,6 +45,7 @@ import { adapterFailureFromEvent, emptyChunks, joinChunks, responsesUsage, toolC
|
|
|
45
45
|
import type { OutputItem, StringChunks } from "./internal";
|
|
46
46
|
import { bridgeToResponsesSSE } from "./sse";
|
|
47
47
|
|
|
48
|
+
/** Build a buffered Responses result within a caller-owned or temporary translator budget. */
|
|
48
49
|
export function buildResponseJSON(
|
|
49
50
|
events: AdapterEvent[],
|
|
50
51
|
modelId: string,
|
|
@@ -68,13 +69,14 @@ export function buildResponseJSON(
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/** Fold adapter events into a Responses result while enforcing the requested tool boundary. */
|
|
71
73
|
function buildResponseJSONWithBudget(
|
|
72
74
|
events: AdapterEvent[],
|
|
73
75
|
modelId: string,
|
|
74
76
|
options?: {
|
|
75
77
|
hideThinkingSummary?: boolean;
|
|
76
78
|
toolNsMap?: Map<string, { namespace: string; name: string; freeform?: true }>;
|
|
77
|
-
/** Request-visible tool names.
|
|
79
|
+
/** Request-visible tool names. Required for client calls when enforcement is explicitly enabled. */
|
|
78
80
|
declaredToolNames?: ReadonlySet<string>;
|
|
79
81
|
/** See `bridgeToResponsesSSE`: enforcement is separate from normalization (#4735). */
|
|
80
82
|
enforceDeclaredToolNames?: boolean;
|
|
@@ -443,9 +445,9 @@ function buildResponseJSONWithBudget(
|
|
|
443
445
|
flushToolCall();
|
|
444
446
|
const effectiveName = normalizeDeclaredToolName(e.name, options?.declaredToolNames);
|
|
445
447
|
if (
|
|
446
|
-
options?.declaredToolNames
|
|
447
|
-
&& options
|
|
448
|
-
&& !options
|
|
448
|
+
(options?.enforceDeclaredToolNames === true || options?.declaredToolNames != null)
|
|
449
|
+
&& options?.enforceDeclaredToolNames !== false
|
|
450
|
+
&& !options?.declaredToolNames?.has(effectiveName)
|
|
449
451
|
) {
|
|
450
452
|
errorEvent = {
|
|
451
453
|
type: "error",
|
package/src/bridge/sse.ts
CHANGED
|
@@ -61,6 +61,7 @@ function responseError(status: number, type: string, message: string): OcxErrorP
|
|
|
61
61
|
|
|
62
62
|
export type ResponsesTerminalStatus = "completed" | "failed" | "incomplete";
|
|
63
63
|
|
|
64
|
+
/** Stream adapter events as Responses frames, applying tool authorization before relaying calls. */
|
|
64
65
|
export function bridgeToResponsesSSE(
|
|
65
66
|
events: AsyncIterable<AdapterEvent>,
|
|
66
67
|
modelId: string,
|
|
@@ -92,13 +93,14 @@ export function bridgeToResponsesSSE(
|
|
|
92
93
|
* from this callback instead of re-parsing the bridged SSE.
|
|
93
94
|
*/
|
|
94
95
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
95
|
-
/** Request-visible tool names.
|
|
96
|
+
/** Request-visible tool names. Required for client calls when enforcement is explicitly enabled. */
|
|
96
97
|
declaredToolNames?: ReadonlySet<string>;
|
|
97
98
|
/**
|
|
98
99
|
* Whether `declaredToolNames` is an authorization boundary this proxy enforces, or only the
|
|
99
100
|
* catalog used to normalize provider-invented names back to declared ones.
|
|
100
101
|
*
|
|
101
|
-
* Defaults to enforcing
|
|
102
|
+
* Defaults to enforcing when a catalog is supplied. Explicit true also fails closed when the
|
|
103
|
+
* catalog is absent. The chat and Anthropic inbound wires set it false: those specs make
|
|
102
104
|
* the server relay a tool call and leave execution or refusal to the client's own runner, and
|
|
103
105
|
* harnesses on them legitimately defer part of their catalog (#4735).
|
|
104
106
|
*
|
|
@@ -1010,9 +1012,9 @@ export function bridgeToResponsesSSE(
|
|
|
1010
1012
|
const mapped = toolNsMap?.get(effectiveName);
|
|
1011
1013
|
const realName = mapped?.name ?? effectiveName;
|
|
1012
1014
|
if (
|
|
1013
|
-
options?.declaredToolNames
|
|
1014
|
-
&& options
|
|
1015
|
-
&& !options
|
|
1015
|
+
(options?.enforceDeclaredToolNames === true || options?.declaredToolNames != null)
|
|
1016
|
+
&& options?.enforceDeclaredToolNames !== false
|
|
1017
|
+
&& !options?.declaredToolNames?.has(effectiveName)
|
|
1016
1018
|
) {
|
|
1017
1019
|
const failure = responseError(
|
|
1018
1020
|
502,
|