@bitkyc08/opencodex 2.63.0 → 2.64.0-preview.20260923
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/AGENTS_INSTALL.md +4 -3
- package/README.md +46 -33
- package/assets/download-linux.svg +10 -0
- package/assets/download-macos.svg +10 -0
- package/assets/download-windows.svg +10 -0
- package/bin/ocx.mjs +12 -4
- package/gui/dist/assets/App-D5eN1TID.js +50 -0
- package/gui/dist/assets/App-I5AnaSLh.css +1 -0
- package/gui/dist/assets/{Tray-CncKDBTp.js → Tray-Bh_ErQDh.js} +1 -1
- package/gui/dist/assets/index-BEq4OCOz.js +86 -0
- package/gui/dist/assets/index-DiBRuK-d.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DBG_37kQ.js +1 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/src/protocol.rs +5 -0
- package/package.json +4 -1
- package/src/adapters/anthropic.ts +66 -7
- package/src/adapters/codebuddy/adapter.ts +112 -16
- package/src/adapters/codebuddy/mcp-server.ts +180 -0
- package/src/adapters/codebuddy/scaffold-guard.ts +25 -8
- package/src/adapters/codebuddy/tool-bridge.ts +597 -0
- package/src/adapters/coding-agent/protocol.ts +123 -10
- package/src/adapters/coding-agent/turn.ts +324 -2
- package/src/adapters/command-code-restored-schema.ts +112 -0
- package/src/adapters/command-code-tool-text.ts +589 -0
- package/src/adapters/command-code.ts +80 -6
- package/src/adapters/cursor/catalog.ts +12 -0
- package/src/adapters/cursor/current-request.ts +46 -0
- package/src/adapters/cursor/discovery.ts +1 -1
- package/src/adapters/cursor/effort-map.ts +4 -0
- package/src/adapters/cursor/native-exec.ts +15 -0
- package/src/adapters/cursor/protobuf-events.ts +40 -12
- package/src/adapters/cursor/protobuf-request.ts +83 -18
- package/src/adapters/cursor/request-builder.ts +5 -4
- package/src/adapters/cursor/tool-guidance.ts +1 -1
- package/src/adapters/devin/live-models.ts +7 -0
- package/src/adapters/inline-think-tags.ts +251 -0
- package/src/adapters/kiro/adapter.ts +1 -0
- package/src/adapters/kiro/stream.ts +5 -3
- package/src/adapters/kiro/usage.ts +4 -3
- package/src/adapters/mimo-free.ts +37 -18
- package/src/adapters/openai-chat/messages.ts +5 -5
- package/src/adapters/openai-chat/tool-schema.ts +124 -2
- package/src/adapters/openai-chat.ts +13 -3
- package/src/adapters/openai-responses/passthrough.ts +15 -2
- package/src/adapters/openai-responses/reasoning.ts +14 -1
- package/src/adapters/openai-responses/request-strips.ts +29 -3
- package/src/adapters/openai-responses/tool-output-recovery.ts +9 -3
- package/src/adapters/qoder/adapter.ts +15 -12
- package/src/adapters/qoder/scaffold-guard.ts +45 -13
- package/src/bridge/internal.ts +20 -0
- package/src/bridge/sse.ts +14 -5
- package/src/claude/agents-inject.ts +2 -1
- package/src/claude/desktop-applied-marker.ts +44 -0
- package/src/claude/desktop-profile.ts +22 -12
- package/src/claude/inbound.ts +8 -3
- package/src/cli/access.ts +22 -5
- package/src/cli/account-api.ts +6 -0
- package/src/cli/account-auth.ts +5 -2
- package/src/cli/account.ts +5 -6
- package/src/cli/aside-profiles.ts +61 -1
- package/src/cli/claude-desktop.ts +3 -2
- package/src/cli/claude.ts +3 -1
- package/src/cli/codex-cli-update.ts +2 -2
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +12 -4
- package/src/cli/index.ts +98 -10
- package/src/cli/registry.ts +1 -1
- package/src/cli/resolve.ts +113 -1
- package/src/cli/root.ts +5 -0
- package/src/cli/stop-approval.ts +186 -0
- package/src/cli/stop-report.ts +1 -1
- package/src/cli/system-restart-client.ts +19 -4
- package/src/client/hub-client.ts +25 -18
- package/src/clients/config-export.ts +12 -4
- package/src/codex/account-auto-switch.ts +59 -0
- package/src/codex/account-lifecycle.ts +7 -0
- package/src/codex/account-priority.ts +10 -0
- package/src/codex/account-usability.ts +9 -0
- package/src/codex/auth-api/account-list.ts +5 -0
- package/src/codex/auth-api/login-flow.ts +15 -5
- package/src/codex/auth-api/login-state.ts +5 -16
- package/src/codex/auth-api/pool-mode-gate.ts +30 -7
- package/src/codex/auth-api/routes.ts +39 -3
- package/src/codex/auth-context.ts +145 -17
- package/src/codex/catalog/build-entries.ts +2 -1
- package/src/codex/catalog/effort.ts +8 -2
- package/src/codex/catalog/metadata.ts +33 -7
- package/src/codex/catalog/native-models.ts +102 -5
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +19 -6
- package/src/codex/cli-installation-targets.ts +44 -40
- package/src/codex/codex-write-lock.ts +32 -2
- package/src/codex/inject/multi-agent-v2.ts +90 -0
- package/src/codex/inject/plan.ts +365 -0
- package/src/codex/inject.ts +237 -362
- package/src/codex/log-guard/maintenance.ts +30 -13
- package/src/codex/project-config-warnings.ts +47 -7
- package/src/codex/prompt-layers/encoding.ts +41 -0
- package/src/codex/prompt-layers/toml-read.ts +1 -1
- package/src/codex/prompt-layers.ts +17 -13
- package/src/codex/prompt-text-probe.ts +20 -10
- package/src/codex/quota-observation-freshness.ts +34 -0
- package/src/codex/quota-rejection.ts +8 -4
- package/src/codex/quota.ts +4 -2
- package/src/codex/routing/selection.ts +32 -9
- package/src/codex/routing.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +4 -3
- package/src/codex/sync.ts +11 -0
- package/src/codex/windows-installation-files.ts +33 -11
- package/src/codex/write-coordination.ts +4 -0
- package/src/combos/request.ts +6 -2
- package/src/companion/settings.ts +6 -1
- package/src/config/atomic-write.ts +12 -1
- package/src/config/derived-registries.ts +29 -0
- package/src/config/diagnostics.ts +17 -0
- package/src/config/live-reconcile.ts +11 -2
- package/src/config/load-degrade.ts +9 -2
- package/src/config/persist-unlocked.ts +4 -3
- package/src/config/persisted-mutation.ts +94 -0
- package/src/config/provider-validation.ts +1 -1
- package/src/config/proxy-env.ts +62 -12
- package/src/config/rebase-provenance.ts +80 -1
- package/src/config/schema/config-schema.ts +5 -0
- package/src/config/schema/leaf-validators.ts +50 -1
- package/src/config/subagent-models.ts +41 -15
- package/src/config.ts +18 -95
- package/src/generated/compatibility-version.json +330 -218
- package/src/generated/model-metadata.ts +8 -5
- package/src/github/star-state.ts +46 -6
- package/src/grok/inject.ts +74 -40
- package/src/grok/status.ts +11 -4
- package/src/integrations/cursor-effort-table.ts +50 -13
- package/src/integrations/raycast-detect.ts +19 -4
- package/src/integrations/serialize.ts +11 -1
- package/src/lib/bounded-body.ts +30 -0
- package/src/lib/crash-guard.ts +52 -4
- package/src/lib/local-aside-sync-contract.ts +41 -0
- package/src/lib/package-tree-integrity.ts +181 -9
- package/src/lib/proxy-env.ts +18 -1
- package/src/lib/request-failure-attribution.ts +1 -0
- package/src/lib/request-failure-model.ts +3 -0
- package/src/lib/service-secrets.ts +99 -2
- package/src/lib/socks5-fetch.ts +43 -14
- package/src/lib/token-estimate.ts +17 -2
- package/src/oauth/command-code.ts +3 -2
- package/src/oauth/generic-account-failover.ts +29 -1
- package/src/oauth/index.ts +24 -16
- package/src/oauth/login-flow-state.ts +5 -5
- package/src/oauth/meta-muse-device.ts +49 -7
- package/src/oauth/store.ts +74 -9
- package/src/providers/alibaba-region-backup.ts +16 -1
- package/src/providers/anthropic-fast.ts +89 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +288 -0
- package/src/providers/anthropic-reset-grants.ts +329 -0
- package/src/providers/claude-cli-identity.ts +12 -0
- package/src/providers/command-code-efforts.ts +184 -102
- package/src/providers/derive.ts +11 -3
- package/src/providers/fastwire.ts +16 -3
- package/src/providers/label.ts +8 -3
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota/vendor-probes-oauth.ts +2 -1
- package/src/providers/reasoning-metadata.ts +38 -15
- package/src/providers/registry/entries-core.ts +95 -7
- package/src/providers/registry/entries-extended.ts +21 -10
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +31 -3
- package/src/providers/registry/types.ts +3 -1
- package/src/providers/resolved-model-policy-merge.ts +38 -8
- package/src/providers/resolved-model-policy.ts +4 -2
- package/src/providers/service-tier.ts +3 -1
- package/src/reasoning-effort.ts +6 -5
- package/src/responses/code-mode-helper-compat.ts +14 -4
- package/src/responses/code-mode-shell-input.ts +54 -0
- package/src/responses/custom-tool-compat.ts +173 -5
- package/src/responses/parser.ts +38 -2
- package/src/responses/reasoning-replay-cache.ts +26 -0
- package/src/router.ts +11 -1
- package/src/routing/history/indexer.ts +7 -2
- package/src/routing/history/schema.ts +3 -1
- package/src/server/adapter-resolve.ts +2 -2
- package/src/server/auth-cors.ts +3 -0
- package/src/server/chat-completions.ts +14 -1
- package/src/server/chat-native.ts +17 -0
- package/src/server/claude-messages.ts +4 -3
- package/src/server/direct-local-http.ts +45 -19
- package/src/server/gui-session.ts +6 -15
- package/src/server/index/package-tree-guard.ts +53 -0
- package/src/server/index/serve-options.ts +17 -3
- package/src/server/index/startup-warnings.ts +17 -0
- package/src/server/index.ts +15 -17
- package/src/server/lifecycle.ts +8 -0
- package/src/server/live.ts +54 -4
- package/src/server/management/agent-settings-routes.ts +44 -8
- package/src/server/management/anthropic-reset-grant-routes.ts +252 -0
- package/src/server/management/codex-prompt-routes.ts +5 -1
- package/src/server/management/config-routes.ts +14 -4
- package/src/server/management/oauth-account-routes.ts +17 -1
- package/src/server/management/provider-overwrite-carry.ts +164 -0
- package/src/server/management/provider-routes.ts +36 -6
- package/src/server/management/remote-workspace-routes.ts +2 -2
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/management/shadow-call-validation.ts +39 -0
- package/src/server/management/system-restart.ts +72 -6
- package/src/server/management-api.ts +16 -2
- package/src/server/management-auth.ts +52 -8
- package/src/server/proxy-liveness.ts +110 -4
- package/src/server/request-log.ts +26 -9
- package/src/server/request-metrics.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +6 -3
- package/src/server/responses/adapter-dispatch.ts +65 -1
- package/src/server/responses/compact.ts +31 -1
- package/src/server/responses/compaction-routing.ts +28 -3
- package/src/server/responses/core-codex-account.ts +85 -26
- package/src/server/responses/core-combo-failure.ts +16 -15
- package/src/server/responses/core-normalize.ts +5 -1
- package/src/server/responses/core-opaque-recovery.ts +50 -2
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core-replay.ts +7 -0
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/encrypted-payload.ts +23 -9
- package/src/server/responses/passthrough-delivery.ts +55 -35
- package/src/server/responses/passthrough-dispatch.ts +12 -13
- package/src/server/responses/policy-fallback.ts +12 -1
- package/src/server/responses/request-prepare.ts +46 -7
- package/src/server/responses/request-spend.ts +31 -15
- package/src/server/responses/run-turn-execution.ts +6 -5
- package/src/server/responses/shadow-target-availability.ts +61 -0
- package/src/server/responses-custom-tool-repair.ts +2 -0
- package/src/service/claim.ts +185 -0
- package/src/service/cli.ts +10 -1
- package/src/service/guarded-manager-target.ts +151 -0
- package/src/service/guards.ts +47 -48
- package/src/service/managing-cli.ts +170 -0
- package/src/service/orchestration.ts +3 -1
- package/src/service/state.ts +4 -0
- package/src/service/systemd.ts +16 -1
- package/src/service.ts +1 -1
- package/src/types/config.ts +8 -0
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +10 -0
- package/src/types/tools.ts +9 -1
- package/src/types/wire.ts +68 -4
- package/src/types.ts +1 -0
- package/src/update/job.ts +10 -16
- package/src/update/npm-invocation.mjs +17 -16
- package/src/update/transactional-install.d.mts +22 -1
- package/src/update/transactional-install.mjs +176 -19
- package/src/update/update-failure-guidance.d.mts +8 -0
- package/src/update/update-failure-guidance.mjs +47 -0
- package/src/usage/expected-prices.ts +54 -12
- package/src/usage/log.ts +117 -3
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/timeline.ts +34 -6
- package/src/usage/user-cost-overlay-reconciler.ts +3 -3
- package/src/vision/reasoning.ts +2 -4
- package/src/web-search/xai-executor.ts +14 -4
- package/gui/dist/assets/App-BqrsSrIR.js +0 -50
- package/gui/dist/assets/index-C6SJrh0N.js +0 -86
- package/gui/dist/assets/index-DdDunwDb.css +0 -1
- package/gui/dist/assets/usage-companion-chart-CzAAAB1o.js +0 -1
- package/src/adapters/kiro-thinking.ts +0 -112
|
@@ -37,7 +37,7 @@ export type StaticProviderPolicyField =
|
|
|
37
37
|
| "supportsResponsesCustomTools" | "preserveResponsesReasoningContent" | "dropResponsesReasoningItems"
|
|
38
38
|
| "modelSupportsReasoningSummaries"
|
|
39
39
|
| "supportsVerbosity" | "modelSupportsVerbosity" | "responsesItemIdRepair" | "autoToolChoiceOnlyModels"
|
|
40
|
-
| "preserveReasoningContentModels" | "requiresReasoningPlaceholderModels" | "reasoningSplitModels"
|
|
40
|
+
| "preserveReasoningContentModels" | "requiresReasoningPlaceholderModels" | "reasoningSplitModels" | "inlineThinkTagModels"
|
|
41
41
|
| "reasoningDetailsModels" | "thinkingToggleModels" | "thinkingBudgetModels" | "showThinkingSummary"
|
|
42
42
|
| "escapeBuiltinToolNames" | "googleMode" | "project" | "location" | "modelCapabilities"
|
|
43
43
|
| "modelAutoCompactTokenLimits" | "modelSuppressSyntheticMax" | "modelReasoningSummaryDelivery"
|
|
@@ -230,6 +230,8 @@ export function resolveModelPolicy(input: ResolveModelPolicyInput): ResolvedMode
|
|
|
230
230
|
"preserveReasoningContentModels", "requiresReasoningPlaceholderModels",
|
|
231
231
|
"reasoningSplitModels", "reasoningDetailsModels", "thinkingToggleModels", "thinkingBudgetModels",
|
|
232
232
|
] as const) putUnion(key, entry?.[key]);
|
|
233
|
+
// This parser is opt-in: an explicit list, including [], overrides registry defaults.
|
|
234
|
+
putScalar("inlineThinkTagModels", entry?.inlineThinkTagModels);
|
|
233
235
|
for (const directModel of entry?.directReasoningEffortModels ?? []) {
|
|
234
236
|
const staleBudget = [directModel, ...(entry?.thinkingBudgetModels ?? [])];
|
|
235
237
|
const routedStaleBudget = [...(entry?.thinkingBudgetModels ?? []), directModel];
|
|
@@ -281,7 +283,7 @@ export function resolveModelPolicy(input: ResolveModelPolicyInput): ResolvedMode
|
|
|
281
283
|
return exact !== "unknown" ? exact : operatorDefault !== undefined ? "operator" : registryDefault !== undefined ? "registry" : "unknown";
|
|
282
284
|
};
|
|
283
285
|
const configuredAdapter = provider.modelAdapters?.[input.modelId];
|
|
284
|
-
const pin = pinnedWireAdapter(input.providerName, input.modelId);
|
|
286
|
+
const pin = pinnedWireAdapter(input.providerName, input.modelId, provider);
|
|
285
287
|
const normalizedModelId = input.modelId.trim().toLowerCase();
|
|
286
288
|
const registryWire = wireDefault(entry?.modelWireDefaults?.[normalizedModelId], provider, entry,
|
|
287
289
|
input.inboundWire ?? "responses", resolvedAuthMode);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "../types";
|
|
2
|
-
import { captureWireAdapterHardPins } from "../types";
|
|
2
|
+
import { captureWireAdapterHardPinPrefixes, captureWireAdapterHardPins } from "../types";
|
|
3
3
|
import { isCanonicalOpenAiForwardProvider } from "./openai-tiers";
|
|
4
4
|
import {
|
|
5
5
|
getProviderRegistryEntry,
|
|
@@ -113,6 +113,7 @@ function buildFastPolicyAuthority(
|
|
|
113
113
|
}),
|
|
114
114
|
modelAdapters: Object.freeze({ ...(provider.modelAdapters ?? {}) }),
|
|
115
115
|
hardPins: captureWireAdapterHardPins(providerName),
|
|
116
|
+
hardPinPrefixes: captureWireAdapterHardPinPrefixes(providerName, provider),
|
|
116
117
|
registryWireDefaults: cloneRegistryWireDefaults(registry?.modelWireDefaults),
|
|
117
118
|
});
|
|
118
119
|
return authority;
|
|
@@ -158,6 +159,7 @@ function authorityForProvider(
|
|
|
158
159
|
...authority,
|
|
159
160
|
modelAdapters: Object.freeze({}),
|
|
160
161
|
hardPins: Object.freeze({}),
|
|
162
|
+
hardPinPrefixes: Object.freeze({}),
|
|
161
163
|
registryWireDefaults: Object.freeze({}),
|
|
162
164
|
});
|
|
163
165
|
}
|
package/src/reasoning-effort.ts
CHANGED
|
@@ -168,11 +168,12 @@ export function configuredReasoningEfforts(provider: OcxProviderConfig, modelId:
|
|
|
168
168
|
}
|
|
169
169
|
// models.dev publishes the per-model ladder that routed providers never expose on /models.
|
|
170
170
|
// (OpenCode Zen Go answers ids only). Only consulted when nothing was configured for this
|
|
171
|
-
// model, so every hand-written contract stays authoritative.
|
|
172
|
-
//
|
|
173
|
-
// The refresh
|
|
174
|
-
//
|
|
175
|
-
// missing or corrupt, since that is the case this
|
|
171
|
+
// model, so every hand-written contract stays authoritative. Catalog sync bootstraps the
|
|
172
|
+
// snapshot with a bounded wait; an existing stale snapshot refreshes in the background.
|
|
173
|
+
// The refresh helper refuses to fetch a missing snapshot, so it only ever refreshes a STALE
|
|
174
|
+
// snapshot, including when a classified model supplied the fallback ladder. Review asked for
|
|
175
|
+
// the opposite — refresh when the snapshot is missing or corrupt, since that is the case this
|
|
176
|
+
// lookup cannot serve. That is declined here:
|
|
176
177
|
// a missing snapshot is the default state of every fresh install and every test process, so
|
|
177
178
|
// requesting the fetch here puts a models.dev request on the request path of the first routed
|
|
178
179
|
// turn to a gated destination. Refreshing a snapshot that does not exist is catalog-sync work,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
unwrapFreeformToolInput,
|
|
5
5
|
} from "./apply-patch-envelope";
|
|
6
6
|
import { declaresCodeModeExec } from "../types/tools";
|
|
7
|
+
import { parseCodeModeShellInput } from "./code-mode-shell-input";
|
|
7
8
|
|
|
8
9
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
9
10
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -31,6 +32,10 @@ export function compileCodeModeHelperInput(
|
|
|
31
32
|
const helperName = toolName.startsWith("default.")
|
|
32
33
|
? toolName.slice("default.".length)
|
|
33
34
|
: toolName;
|
|
35
|
+
if (helperName === "exec_command" && wireToolName === "exec") {
|
|
36
|
+
const args = parseCodeModeShellInput(argumentsText);
|
|
37
|
+
if (args) return `const result = await tools.exec_command(${JSON.stringify(args)});\ntext(result);`;
|
|
38
|
+
}
|
|
34
39
|
if (helperName === "apply_patch") {
|
|
35
40
|
// `resolveCodeModeHelperName` decides this IS an apply-patch call by reading
|
|
36
41
|
// `unwrapFreeformToolInput(argumentsText, wireToolName)`, which strips an outer Markdown
|
|
@@ -43,8 +48,12 @@ export function compileCodeModeHelperInput(
|
|
|
43
48
|
// is an apply_patch wrapper and is not an `exec` fallback field, and the recognizer already
|
|
44
49
|
// declines it under `exec`; reading it here would compile a body that recognition rejected,
|
|
45
50
|
// which is exactly the drift a second, looser unwrap introduces.
|
|
51
|
+
const bodyToolName = wireToolName ?? helperName;
|
|
52
|
+
const normalizedBodyToolName = bodyToolName.startsWith("default.")
|
|
53
|
+
? bodyToolName.slice("default.".length)
|
|
54
|
+
: bodyToolName;
|
|
46
55
|
const patch = normalizeApplyPatchDelimiters(
|
|
47
|
-
unwrapFreeformToolInput(argumentsText,
|
|
56
|
+
unwrapFreeformToolInput(argumentsText, normalizedBodyToolName),
|
|
48
57
|
);
|
|
49
58
|
return `const result = await tools.apply_patch(${JSON.stringify(patch)});\ntext(result);`;
|
|
50
59
|
}
|
|
@@ -96,8 +105,8 @@ export function compileCodeModeHelperInput(
|
|
|
96
105
|
* wrong.
|
|
97
106
|
*
|
|
98
107
|
* This adds that second case: the name is already `exec` so nothing was rewritten, but
|
|
99
|
-
* the body is a complete patch envelope
|
|
100
|
-
*
|
|
108
|
+
* the body is a complete patch envelope or an unambiguous structured shell call.
|
|
109
|
+
* Same inference the name-based path makes, drawn from the payload.
|
|
101
110
|
*
|
|
102
111
|
* Returns undefined for everything else, including JavaScript that merely mentions a
|
|
103
112
|
* patch envelope — that body is a real program and is forwarded byte-identical.
|
|
@@ -116,5 +125,6 @@ export function resolveCodeModeHelperName(
|
|
|
116
125
|
// `tools.apply_patch(...)` JavaScript would be the mis-route this repair exists to avoid.
|
|
117
126
|
if (!declaresCodeModeExec(declaredNames)) return undefined;
|
|
118
127
|
if (typeof argumentsText !== "string" || argumentsText === "") return undefined;
|
|
119
|
-
|
|
128
|
+
if (isCompletePatchEnvelope(unwrapFreeformToolInput(argumentsText, "exec"))) return "apply_patch";
|
|
129
|
+
return parseCodeModeShellInput(argumentsText) ? "exec_command" : undefined;
|
|
120
130
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { unwrapFreeformToolInput } from "./apply-patch-envelope";
|
|
2
|
+
import { scanFreeformWrapper } from "./freeform-wrapper-scan";
|
|
3
|
+
|
|
4
|
+
const SHELL_ARGUMENT_KEYS = new Set([
|
|
5
|
+
"cmd", "command", "workdir", "shell", "login", "tty", "yield_time_ms",
|
|
6
|
+
"max_output_tokens", "sandbox_permissions", "justification", "prefix_rule",
|
|
7
|
+
]);
|
|
8
|
+
let javascriptParser: Bun.Transpiler | undefined;
|
|
9
|
+
|
|
10
|
+
/** Recognize shell arguments, never guess a shell from an ordinary freeform program. */
|
|
11
|
+
export function parseCodeModeShellInput(argumentsText: string): Record<string, unknown> | undefined {
|
|
12
|
+
let parsed: unknown;
|
|
13
|
+
try {
|
|
14
|
+
// Only the canonical input wrapper is removed: the cmd/command object is the payload.
|
|
15
|
+
parsed = JSON.parse(unwrapFreeformToolInput(argumentsText));
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
20
|
+
const args = parsed as Record<string, unknown>;
|
|
21
|
+
if (Object.keys(args).some(key => !SHELL_ARGUMENT_KEYS.has(key))) return undefined;
|
|
22
|
+
const keys = ["cmd", "command"].filter(key => Object.hasOwn(args, key));
|
|
23
|
+
if (keys.length !== 1) return undefined;
|
|
24
|
+
const command = args[keys[0]!];
|
|
25
|
+
if (typeof command !== "string" || command.trim() === "") return undefined;
|
|
26
|
+
// cmd/command also exist as historical JavaScript fallback fields. Preserve every valid
|
|
27
|
+
// program, including ambiguous identifiers such as `ls`. Parsing never executes the source.
|
|
28
|
+
try {
|
|
29
|
+
javascriptParser ??= new Bun.Transpiler({ loader: "js" });
|
|
30
|
+
javascriptParser.scan(`async function __codeModeInput() {\n${command}\n}`);
|
|
31
|
+
return undefined;
|
|
32
|
+
} catch {
|
|
33
|
+
const { command: _alias, ...rest } = args;
|
|
34
|
+
return { ...rest, cmd: command };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Hold possible shell objects until completion can choose their executable representation. */
|
|
39
|
+
export function mayBecomeCodeModeShellInput(argumentsText: string, input: string): boolean {
|
|
40
|
+
const head = input.trimStart();
|
|
41
|
+
if (head === "" || head.startsWith("{")) return true;
|
|
42
|
+
// Canonical JavaScript streams progressively; avoid reparsing its growing wrapper on every
|
|
43
|
+
// delta. The shared prefix scanner is bounded independently of the command's size.
|
|
44
|
+
if (input === argumentsText || scanFreeformWrapper(argumentsText).kind === "input") return false;
|
|
45
|
+
try {
|
|
46
|
+
const args = JSON.parse(argumentsText);
|
|
47
|
+
// A fallback cmd value becomes visible only when the outer object closes. Do not emit
|
|
48
|
+
// that command before completion replaces it with tools.exec_command JavaScript.
|
|
49
|
+
return !!args && typeof args === "object" && !Object.hasOwn(args, "input")
|
|
50
|
+
&& (Object.hasOwn(args, "cmd") || Object.hasOwn(args, "command"));
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -241,6 +241,160 @@ function rewriteForUpstream(
|
|
|
241
241
|
return changed ? next : value;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
/** Request-layer compatibility failure. Callers map this to HTTP 400, never an unhandled 500. */
|
|
245
|
+
export class RoutedCustomToolCompatError extends Error {
|
|
246
|
+
readonly code = "custom_tool_compat";
|
|
247
|
+
constructor(
|
|
248
|
+
readonly stage: string,
|
|
249
|
+
readonly itemType: string,
|
|
250
|
+
) {
|
|
251
|
+
super(`custom_tool_compat: ${stage}: ${itemType}`);
|
|
252
|
+
this.name = "RoutedCustomToolCompatError";
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function collectDeclaredFunctionWireNames(body: unknown): Set<string> {
|
|
257
|
+
const names = new Set<string>();
|
|
258
|
+
const register = (tool: unknown, namespace?: string): void => {
|
|
259
|
+
if (!isPlainObject(tool) || tool.type !== "function" || typeof tool.name !== "string") return;
|
|
260
|
+
names.add(customToolWireName(namespace, tool.name));
|
|
261
|
+
};
|
|
262
|
+
for (const group of collectResponsesToolGroups(body)) {
|
|
263
|
+
for (const tool of group) {
|
|
264
|
+
if (!isPlainObject(tool)) continue;
|
|
265
|
+
if (tool.type === "namespace" && typeof tool.name === "string" && Array.isArray(tool.tools)) {
|
|
266
|
+
for (const child of tool.tools) register(child, tool.name);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
register(tool);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return names;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function historicalCallIdentity(
|
|
276
|
+
item: Record<string, unknown>,
|
|
277
|
+
): { name: string; namespace?: string } | undefined {
|
|
278
|
+
if (typeof item.name !== "string" || item.name.length === 0) return undefined;
|
|
279
|
+
return {
|
|
280
|
+
name: item.name,
|
|
281
|
+
...(typeof item.namespace === "string" ? { namespace: item.namespace } : {}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function sameHistoricalIdentity(
|
|
286
|
+
left: { name: string; namespace?: string },
|
|
287
|
+
right: { name: string; namespace?: string },
|
|
288
|
+
): boolean {
|
|
289
|
+
return left.name === right.name && left.namespace === right.namespace;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Convert remaining protocol-history custom items when the destination has denied native custom
|
|
294
|
+
* tools. Walks only the top-level `input` array so tool-output JSON cannot be rewritten, and does
|
|
295
|
+
* not merge historical names into the live declaration / restore sets.
|
|
296
|
+
*/
|
|
297
|
+
function rewriteHistoricalCustomItems(
|
|
298
|
+
body: unknown,
|
|
299
|
+
declaredFunctionWireNames: ReadonlySet<string>,
|
|
300
|
+
): unknown {
|
|
301
|
+
if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
|
|
302
|
+
|
|
303
|
+
const calls = new Map<string, { name: string; namespace?: string }>();
|
|
304
|
+
const historicalCustomCallIds = new Set<string>();
|
|
305
|
+
for (const item of body.input) {
|
|
306
|
+
if (!isPlainObject(item)) continue;
|
|
307
|
+
if (item.type !== "custom_tool_call" && item.type !== "function_call") continue;
|
|
308
|
+
if (typeof item.call_id !== "string" || item.call_id.length === 0) {
|
|
309
|
+
if (item.type === "custom_tool_call") {
|
|
310
|
+
throw new RoutedCustomToolCompatError("historical_item", "custom_tool_call.call_id");
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const identity = historicalCallIdentity(item);
|
|
315
|
+
if (!identity) {
|
|
316
|
+
if (item.type === "custom_tool_call") {
|
|
317
|
+
throw new RoutedCustomToolCompatError("historical_item", "custom_tool_call.name");
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const existing = calls.get(item.call_id);
|
|
322
|
+
if (existing) {
|
|
323
|
+
throw new RoutedCustomToolCompatError(
|
|
324
|
+
"historical_item",
|
|
325
|
+
sameHistoricalIdentity(existing, identity) ? "duplicate_call_id" : "call_id",
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
calls.set(item.call_id, identity);
|
|
329
|
+
if (item.type === "custom_tool_call") historicalCustomCallIds.add(item.call_id);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let changed = false;
|
|
333
|
+
const input = body.input.map(item => {
|
|
334
|
+
if (!isPlainObject(item)) return item;
|
|
335
|
+
if (item.type === "custom_tool_call") {
|
|
336
|
+
if (typeof item.call_id !== "string" || item.call_id.length === 0) {
|
|
337
|
+
throw new RoutedCustomToolCompatError("historical_item", "custom_tool_call.call_id");
|
|
338
|
+
}
|
|
339
|
+
if (typeof item.name !== "string" || item.name.length === 0) {
|
|
340
|
+
throw new RoutedCustomToolCompatError("historical_item", "custom_tool_call.name");
|
|
341
|
+
}
|
|
342
|
+
if (typeof item.input !== "string") {
|
|
343
|
+
throw new RoutedCustomToolCompatError("historical_item", "custom_tool_call.input");
|
|
344
|
+
}
|
|
345
|
+
const wireName = customToolWireName(
|
|
346
|
+
typeof item.namespace === "string" ? item.namespace : undefined,
|
|
347
|
+
item.name,
|
|
348
|
+
);
|
|
349
|
+
if (declaredFunctionWireNames.has(wireName)) {
|
|
350
|
+
throw new RoutedCustomToolCompatError("historical_collision", "declared_function_name");
|
|
351
|
+
}
|
|
352
|
+
const { input: rawInput, id: _id, ...rest } = item;
|
|
353
|
+
changed = true;
|
|
354
|
+
return {
|
|
355
|
+
...rest,
|
|
356
|
+
type: "function_call",
|
|
357
|
+
arguments: JSON.stringify({ input: rawInput }),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (
|
|
361
|
+
item.type === "custom_tool_call_output"
|
|
362
|
+
&& typeof item.call_id === "string"
|
|
363
|
+
&& historicalCustomCallIds.has(item.call_id)
|
|
364
|
+
) {
|
|
365
|
+
changed = true;
|
|
366
|
+
return { ...item, type: "function_call_output" };
|
|
367
|
+
}
|
|
368
|
+
return item;
|
|
369
|
+
});
|
|
370
|
+
return changed ? { ...body, input } : body;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function validateFinalCustomToolCompatibility(
|
|
374
|
+
body: unknown,
|
|
375
|
+
supportsResponsesCustomTools?: boolean,
|
|
376
|
+
): void {
|
|
377
|
+
if (supportsResponsesCustomTools !== false || !isPlainObject(body)) return;
|
|
378
|
+
|
|
379
|
+
const rejectCustomDeclaration = (tool: unknown): void => {
|
|
380
|
+
if (!isPlainObject(tool)) return;
|
|
381
|
+
if (tool.type === "custom") throw new RoutedCustomToolCompatError("final_guard", "custom");
|
|
382
|
+
if (tool.type === "namespace" && Array.isArray(tool.tools)) {
|
|
383
|
+
for (const child of tool.tools) rejectCustomDeclaration(child);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
for (const group of collectResponsesToolGroups(body)) {
|
|
387
|
+
for (const tool of group) rejectCustomDeclaration(tool);
|
|
388
|
+
}
|
|
389
|
+
if (!Array.isArray(body.input)) return;
|
|
390
|
+
for (const item of body.input) {
|
|
391
|
+
if (!isPlainObject(item) || typeof item.type !== "string") continue;
|
|
392
|
+
if (item.type === "custom_tool_call" || item.type === "custom_tool_call_output") {
|
|
393
|
+
throw new RoutedCustomToolCompatError("final_guard", item.type);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
244
398
|
export function rewriteRoutedCustomToolsForUpstream(
|
|
245
399
|
body: unknown,
|
|
246
400
|
supportsResponsesCustomTools?: boolean,
|
|
@@ -255,22 +409,36 @@ export function rewriteRoutedCustomToolsForUpstream(
|
|
|
255
409
|
for (const name of repairNames) {
|
|
256
410
|
if (!toolChoiceAllowsRoutedCustomTool(body, name, repairNames)) repairNames.delete(name);
|
|
257
411
|
}
|
|
258
|
-
if (conversionNames.size === 0
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
412
|
+
if (conversionNames.size === 0 && supportsResponsesCustomTools !== false) {
|
|
413
|
+
return { body, names, repairNames };
|
|
414
|
+
}
|
|
415
|
+
let next = body;
|
|
416
|
+
if (conversionNames.size > 0) {
|
|
417
|
+
const callIds = new Set<string>();
|
|
418
|
+
collectConvertedCallIds(body, conversionNames, callIds);
|
|
419
|
+
next = rewriteForUpstream(body, conversionNames, callIds);
|
|
420
|
+
}
|
|
421
|
+
if (supportsResponsesCustomTools === false) {
|
|
422
|
+
next = rewriteHistoricalCustomItems(next, collectDeclaredFunctionWireNames(body));
|
|
423
|
+
}
|
|
424
|
+
return { body: next, names, repairNames };
|
|
262
425
|
}
|
|
263
426
|
|
|
264
427
|
/**
|
|
265
428
|
* A delta result has no tool name. Without its call, lowering cannot tell whether it belongs
|
|
266
429
|
* to a converted function or a native custom tool. Request full replay instead of guessing.
|
|
430
|
+
* A destination that has denied custom tools also cannot map an orphan result when the current
|
|
431
|
+
* catalog is empty, so that case must request replay rather than forwarding the native type.
|
|
267
432
|
*/
|
|
268
433
|
export function hasUnmappedRoutedCustomToolOutput(
|
|
269
434
|
body: unknown,
|
|
270
435
|
supportsResponsesCustomTools?: boolean,
|
|
271
436
|
): boolean {
|
|
272
437
|
if (!isPlainObject(body) || !Array.isArray(body.input)) return false;
|
|
273
|
-
if (
|
|
438
|
+
if (
|
|
439
|
+
supportsResponsesCustomTools !== false
|
|
440
|
+
&& collectRoutedCustomToolNames(body, supportsResponsesCustomTools).size === 0
|
|
441
|
+
) return false;
|
|
274
442
|
const callIds = new Set<string>();
|
|
275
443
|
for (const item of body.input) {
|
|
276
444
|
if (isPlainObject(item)
|
package/src/responses/parser.ts
CHANGED
|
@@ -42,6 +42,28 @@ function replayThoughtSignatureMetadata(
|
|
|
42
42
|
return signature ? { google: { thoughtSignature: signature } } : undefined;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Repair one bounded inbound-history corruption: a JSON object literal that lost exactly
|
|
47
|
+
* its opening brace (observed as `code":"…}` after `{"` went missing, taking the key's
|
|
48
|
+
* opening quote with it). Only a text that ends with `}` and parses into an object once
|
|
49
|
+
* the brace is restored counts — anything looser keeps the tolerated-{} fallback so
|
|
50
|
+
* freeform text that merely resembles JSON is never rewritten.
|
|
51
|
+
*/
|
|
52
|
+
function repairJsonObjectEnvelope(text: string): Record<string, unknown> | undefined {
|
|
53
|
+
if (!text.endsWith("}")) return undefined;
|
|
54
|
+
// A body that still opens with a quoted key lost only `{`; the observed shape lost
|
|
55
|
+
// `{"` together, taking the key's opening quote with it. Both restorations must parse
|
|
56
|
+
// into an object, so freeform text that merely resembles JSON is never rewritten.
|
|
57
|
+
const candidate = text.startsWith('"') ? `{${text}` : `{"${text}`;
|
|
58
|
+
try {
|
|
59
|
+
const parsed: unknown = JSON.parse(candidate);
|
|
60
|
+
if (isObj(parsed)) return parsed;
|
|
61
|
+
} catch {
|
|
62
|
+
/* fall through to the tolerated-{} path */
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
|
|
46
68
|
|
|
47
69
|
function ensureAssistantPlaceholder(messages: OcxMessage[], modelId: string, now: number): OcxAssistantMessage {
|
|
@@ -94,6 +116,10 @@ function attachPendingReasoningToCallOwner(
|
|
|
94
116
|
|
|
95
117
|
const REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
96
118
|
|
|
119
|
+
export function hasValidatedActiveReasoningEffort(options: Pick<OcxRequestOptions, "reasoning">): boolean {
|
|
120
|
+
return options.reasoning !== undefined && options.reasoning !== "none";
|
|
121
|
+
}
|
|
122
|
+
|
|
97
123
|
|
|
98
124
|
export function parseRequest(
|
|
99
125
|
body: unknown,
|
|
@@ -333,7 +359,16 @@ export function parseRequest(
|
|
|
333
359
|
const parsed: unknown = JSON.parse(rawArgs);
|
|
334
360
|
if (isObj(parsed)) args = parsed;
|
|
335
361
|
} catch {
|
|
336
|
-
|
|
362
|
+
// One observed serialization corruption loses exactly the JSON object's opening
|
|
363
|
+
// brace; the closed envelope is tight enough to repair back into a call the
|
|
364
|
+
// routed model can still see and retry, instead of replaying {} forever.
|
|
365
|
+
const repaired = repairJsonObjectEnvelope(rawArgs);
|
|
366
|
+
if (repaired === undefined) {
|
|
367
|
+
console.warn(`[parser] function_call ${call.call_id} has non-JSON arguments; defaulting to {}`);
|
|
368
|
+
} else {
|
|
369
|
+
args = repaired;
|
|
370
|
+
console.warn(`[parser] function_call ${call.call_id} arguments lost the JSON opening brace; repaired from history`);
|
|
371
|
+
}
|
|
337
372
|
}
|
|
338
373
|
}
|
|
339
374
|
// Do NOT map Responses item `id` (fc_/ctc_/…) onto `thoughtSignature`. That field is
|
|
@@ -542,7 +577,8 @@ export function parseRequest(
|
|
|
542
577
|
options.reasoning = requestedEffort;
|
|
543
578
|
}
|
|
544
579
|
const summaryMode = data.reasoning?.summary;
|
|
545
|
-
|
|
580
|
+
const reasoningActive = hasValidatedActiveReasoningEffort(options);
|
|
581
|
+
if (summaryMode === "none" || (!summaryMode && !reasoningActive)) options.hideThinkingSummary = true;
|
|
546
582
|
if (data.presence_penalty !== undefined) options.presencePenalty = data.presence_penalty;
|
|
547
583
|
if (data.frequency_penalty !== undefined) options.frequencyPenalty = data.frequency_penalty;
|
|
548
584
|
if (data.service_tier !== undefined) options.serviceTier = data.service_tier;
|
|
@@ -192,6 +192,32 @@ export function reasoningReplayServingIdentityChanged(
|
|
|
192
192
|
return previous !== undefined && previous.identity !== current.identity;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Whether the conversation's last serving route kept its items in a store this route cannot
|
|
197
|
+
* read: a different durable destination or credential.
|
|
198
|
+
*
|
|
199
|
+
* A replayed reasoning item's `rs_*` id names an item in that store. Once the store differs, a
|
|
200
|
+
* stateful Responses destination resolves the id against its own store and fails the turn with
|
|
201
|
+
* `Item with id … not found` (#5583), so the id is as foreign as the blob beside it. A change of
|
|
202
|
+
* provider label, adapter or model on the same destination and credential keeps the same store,
|
|
203
|
+
* and there the id remains resolvable. Unknown, expired, or evicted provenance is not a change.
|
|
204
|
+
*/
|
|
205
|
+
export function reasoningReplayItemStoreChanged(
|
|
206
|
+
scope: OcxReasoningReplayScopeRef | undefined,
|
|
207
|
+
): boolean {
|
|
208
|
+
const current = servingIdentityFor(scope);
|
|
209
|
+
if (!current) return false;
|
|
210
|
+
const at = now();
|
|
211
|
+
sweepExpiredServingIdentities(at);
|
|
212
|
+
const previous = servingIdentities.get(current.threadId);
|
|
213
|
+
if (previous === undefined || previous.identity === current.identity) return false;
|
|
214
|
+
const storeOwner = (identity: string): string => {
|
|
215
|
+
const [, destination, , , credential] = JSON.parse(identity) as ReasoningReplayIdentityTuple;
|
|
216
|
+
return JSON.stringify([destination, credential]);
|
|
217
|
+
};
|
|
218
|
+
return storeOwner(previous.identity) !== storeOwner(current.identity);
|
|
219
|
+
}
|
|
220
|
+
|
|
195
221
|
/** Record the route only after it has successfully served the conversation. */
|
|
196
222
|
export function commitReasoningReplayServingIdentity(
|
|
197
223
|
scope: OcxReasoningReplayScopeRef | undefined,
|
package/src/router.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "./types";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { peekAuthStore } from "./oauth/store";
|
|
4
|
+
import { resolveDevinApiBaseUrl, validateDevinApiBaseUrl } from "./oauth/devin/api-base";
|
|
4
5
|
import {
|
|
5
6
|
getCombo,
|
|
6
7
|
isComboTargetInCooldown,
|
|
@@ -155,14 +156,21 @@ export function knownModelIdsForProvider(
|
|
|
155
156
|
// This callback runs only for a scoped entry, not for each provider in an alias scan.
|
|
156
157
|
const routed = routedProviderConfig(provName, prov);
|
|
157
158
|
let key = routed.apiKey;
|
|
159
|
+
let destination = routed.baseUrl;
|
|
158
160
|
if (routed.authMode === "oauth") {
|
|
159
161
|
const set = peekAuthStore()[provName];
|
|
160
162
|
const account = set?.accounts.find(row => row.id === set.activeAccountId);
|
|
161
163
|
if (!account || account.needsReauth || !Number.isFinite(account.credential.expires)
|
|
162
164
|
|| account.credential.expires <= Date.now()) return undefined;
|
|
163
165
|
key = account.credential.access;
|
|
166
|
+
if (routed.adapter === "devin") destination = validateDevinApiBaseUrl(account.credential.apiBaseUrl) ?? routed.baseUrl;
|
|
164
167
|
}
|
|
165
|
-
|
|
168
|
+
if (!key) return undefined;
|
|
169
|
+
return createHash("sha256")
|
|
170
|
+
.update(routed.adapter === "devin"
|
|
171
|
+
? JSON.stringify([key, resolveDevinApiBaseUrl(destination)])
|
|
172
|
+
: key)
|
|
173
|
+
.digest("hex");
|
|
166
174
|
});
|
|
167
175
|
for (const cached of cachedModels ?? []) ids.add(cached.id);
|
|
168
176
|
for (const model of config?.customModels ?? []) {
|
|
@@ -337,6 +345,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
337
345
|
const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
|
|
338
346
|
const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
|
|
339
347
|
const reasoningSplitModels = staticPolicy.reasoningSplitModels;
|
|
348
|
+
const inlineThinkTagModels = staticPolicy.inlineThinkTagModels;
|
|
340
349
|
const reasoningDetailsModels = staticPolicy.reasoningDetailsModels;
|
|
341
350
|
const thinkingToggleModels = staticPolicy.thinkingToggleModels;
|
|
342
351
|
const thinkingBudgetModels = staticPolicy.thinkingBudgetModels;
|
|
@@ -479,6 +488,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
479
488
|
...(preserveReasoningContentModels ? { preserveReasoningContentModels } : {}),
|
|
480
489
|
...(requiresReasoningPlaceholderModels ? { requiresReasoningPlaceholderModels } : {}),
|
|
481
490
|
...(reasoningSplitModels ? { reasoningSplitModels } : {}),
|
|
491
|
+
...(inlineThinkTagModels ? { inlineThinkTagModels } : {}),
|
|
482
492
|
...(reasoningDetailsModels ? { reasoningDetailsModels } : {}),
|
|
483
493
|
...(thinkingToggleModels ? { thinkingToggleModels } : {}),
|
|
484
494
|
...(thinkingBudgetModels ? { thinkingBudgetModels } : {}),
|
|
@@ -22,6 +22,7 @@ import { getConfigDir } from "../../config";
|
|
|
22
22
|
import { recordOwnedConfigPath } from "../../lib/config-ownership";
|
|
23
23
|
import {
|
|
24
24
|
currentUsageLogRevision,
|
|
25
|
+
encodePersistedRequestedModel,
|
|
25
26
|
normalizeUsageEntryForTest,
|
|
26
27
|
usageLogPath,
|
|
27
28
|
type PersistedUsageEntry,
|
|
@@ -157,7 +158,9 @@ function extractRow(entry: PersistedUsageEntry): Array<string | number | null> {
|
|
|
157
158
|
entry.timestamp,
|
|
158
159
|
entry.provider,
|
|
159
160
|
entry.model,
|
|
160
|
-
|
|
161
|
+
// Old canonical JSONL rows can predate bounded selector persistence. Encode
|
|
162
|
+
// the disposable projection on rebuild so exact filters match across versions.
|
|
163
|
+
typeof entry.requestedModel === "string" ? encodePersistedRequestedModel(entry.requestedModel) : null,
|
|
161
164
|
entry.status,
|
|
162
165
|
entry.surface ?? null,
|
|
163
166
|
entry.inboundProtocol ?? null,
|
|
@@ -510,7 +513,9 @@ function queryRows(
|
|
|
510
513
|
};
|
|
511
514
|
if (filters.provider !== undefined) add("provider = ?", filters.provider);
|
|
512
515
|
if (filters.model !== undefined) add("model = ?", filters.model);
|
|
513
|
-
|
|
516
|
+
// Rows store the bounded encoded form, so the lookup value must be encoded the
|
|
517
|
+
// same way — short selectors encode to themselves and still match verbatim.
|
|
518
|
+
if (filters.requestedModel !== undefined) add("requested_model = ?", encodePersistedRequestedModel(filters.requestedModel));
|
|
514
519
|
if (filters.status !== undefined) add("status = ?", filters.status);
|
|
515
520
|
if (filters.conversationId !== undefined) add("conversation_id = ?", filters.conversationId);
|
|
516
521
|
if (filters.surface !== undefined) add("surface = ?", filters.surface);
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* (ADR-1/ADR-8 in devlog/_fin/260804_router_intelligence/000_master_plan.md).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Version 2 rebuilds the derived projection so old raw requested-model selectors
|
|
10
|
+
// are replaced by the bounded encoding already used by canonical JSONL reads.
|
|
11
|
+
export const HISTORY_SCHEMA_VERSION = 2;
|
|
10
12
|
export const HISTORY_DB_FILENAME = "routing-history.sqlite";
|
|
11
13
|
|
|
12
14
|
export const HISTORY_META_KEYS = {
|
|
@@ -32,7 +32,7 @@ export function resolveWireProtocolOverride(
|
|
|
32
32
|
? { ...providerConfig, adapter: staticPolicy.model.adapter }
|
|
33
33
|
: providerConfig;
|
|
34
34
|
}
|
|
35
|
-
const pinned = pinnedWireAdapter(providerName, modelId);
|
|
35
|
+
const pinned = pinnedWireAdapter(providerName, modelId, providerConfig);
|
|
36
36
|
if (pinned && providerConfig.adapter !== pinned) {
|
|
37
37
|
return { ...providerConfig, adapter: pinned };
|
|
38
38
|
}
|
|
@@ -47,7 +47,7 @@ export function resolveWireProtocolOverride(
|
|
|
47
47
|
if (requested
|
|
48
48
|
&& MODEL_ADAPTER_OVERRIDE_ALLOWED.has(requested)
|
|
49
49
|
&& requested !== providerConfig.adapter
|
|
50
|
-
&& !isWirePinnedModel(providerName, modelId)
|
|
50
|
+
&& !isWirePinnedModel(providerName, modelId, providerConfig)
|
|
51
51
|
// A forward provider hands the caller's own credential upstream; the chat adapter
|
|
52
52
|
// only ever sends provider.apiKey, so switching wires here would drop the auth.
|
|
53
53
|
&& !isCanonicalOpenAiForwardProvider(providerConfig)) {
|
package/src/server/auth-cors.ts
CHANGED
|
@@ -818,6 +818,8 @@ export function providerManagementConfigError(
|
|
|
818
818
|
if (reasoningSummariesError) return `provider ${name} ${reasoningSummariesError}`;
|
|
819
819
|
const suppressSyntheticMaxError = booleanRecordConfigError(raw.modelSuppressSyntheticMax, "modelSuppressSyntheticMax");
|
|
820
820
|
if (suppressSyntheticMaxError) return `provider ${name} ${suppressSyntheticMaxError}`;
|
|
821
|
+
const verbositySupportError = booleanRecordConfigError(raw.modelSupportsVerbosity, "modelSupportsVerbosity");
|
|
822
|
+
if (verbositySupportError) return `provider ${name} ${verbositySupportError}`;
|
|
821
823
|
const reasoningSummaryDeliveryError = reasoningSummaryDeliveryRecordConfigError(
|
|
822
824
|
raw.modelReasoningSummaryDelivery,
|
|
823
825
|
raw.modelSupportsReasoningSummaries,
|
|
@@ -1060,6 +1062,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
1060
1062
|
transientRetryOn5xx: "editor",
|
|
1061
1063
|
retryOnReset: "editor",
|
|
1062
1064
|
reasoningSplitModels: "editor",
|
|
1065
|
+
inlineThinkTagModels: "editor",
|
|
1063
1066
|
reasoningDetailsModels: "editor",
|
|
1064
1067
|
thinkingToggleModels: "editor",
|
|
1065
1068
|
thinkingBudgetModels: "editor",
|
|
@@ -199,7 +199,20 @@ async function handleChatCompletionsWithBudget(
|
|
|
199
199
|
}
|
|
200
200
|
// Combos must enter the Responses routing path so child selection, forced default
|
|
201
201
|
// effort, failover, and per-attempt telemetry run before any native Chat send.
|
|
202
|
-
if (!route.combo && !effortRow && isNativeChatRouteEligible(route, chatBody, config))
|
|
202
|
+
if (!route.combo && !effortRow && isNativeChatRouteEligible(route, chatBody, config)) {
|
|
203
|
+
chatNativeRoute = route;
|
|
204
|
+
// Reserve an input estimate for spend without recording it as usage: native Chat attempts
|
|
205
|
+
// keep the provider-reported counts, as they did before the reservation existed.
|
|
206
|
+
if (logCtx.usageLogInputTokens === undefined) {
|
|
207
|
+
const parts = [JSON.stringify(chatBody.messages ?? [])];
|
|
208
|
+
if (chatBody.tools !== undefined) parts.push(JSON.stringify(chatBody.tools));
|
|
209
|
+
logCtx.spendInputEstimateTokens = Math.max(1, estimateTokens(parts.join("\n"), requestedModel));
|
|
210
|
+
}
|
|
211
|
+
const outputCeiling = chatBody.max_completion_tokens ?? chatBody.max_tokens;
|
|
212
|
+
if (typeof outputCeiling === "number" && outputCeiling > 0) {
|
|
213
|
+
logCtx.spendOutputCeilingTokens = Math.trunc(outputCeiling);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
203
216
|
} catch (err) {
|
|
204
217
|
if (err instanceof AdmissionModelDeniedError) {
|
|
205
218
|
logCtx.requestedModel = requestedModel;
|