@bitkyc08/opencodex 2.80.0 → 2.82.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/README.md +3 -3
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +17 -5
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +90 -18
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +68 -10
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +85 -14
- package/src/server/management/context.ts +2 -2
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/shared.ts +3 -3
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -7
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
|
@@ -48,6 +48,7 @@ import { stripMuseSparkUnsupportedWebSearchFields, stripOpenAiOnlyWebSearchField
|
|
|
48
48
|
import { observeOutbound } from "../../usage/cache-diagnostic";
|
|
49
49
|
import { normalizeForwardedClientHeaderName } from "../../lib/provider-client-headers";
|
|
50
50
|
import { normalizeMuseToolChoice } from "./muse-tool-choice";
|
|
51
|
+
import { renderCompactionSearchHistoryItems } from "./compaction-search-history";
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* Identifies DeepSeek's strict Responses replay contract: tool-bearing continuations need
|
|
@@ -133,17 +134,21 @@ function stripInputImagesDeep(value: unknown): unknown {
|
|
|
133
134
|
* be applied here. Images go too: a summary needs no pixels, and a text-only
|
|
134
135
|
* gateway would reject them.
|
|
135
136
|
*/
|
|
136
|
-
function buildRoutedCompactionBody(body: unknown): unknown {
|
|
137
|
+
function buildRoutedCompactionBody(body: unknown, responsesLite: boolean): unknown {
|
|
137
138
|
if (!isPlainObject(body)) return body;
|
|
138
139
|
// `text` goes with the tool fields: the summary must be prose, not schema-constrained JSON.
|
|
139
140
|
const { tools: _tools, tool_choice: _toolChoice, parallel_tool_calls: _parallel, text: _text, ...rest } = body;
|
|
140
141
|
const input = Array.isArray(body.input) ? body.input : [];
|
|
141
|
-
|
|
142
|
+
// Bridge restoration has already recovered any available real call/result pairs. Remaining
|
|
143
|
+
// hosted cells require tools, so they become bounded assistant reference notes.
|
|
144
|
+
const kept = renderCompactionSearchHistoryItems(input.filter(item => !isPlainObject(item)
|
|
142
145
|
// `additional_tools` is how Codex Desktop's responses-lite shape carries tools;
|
|
143
146
|
// leaving it in would break the no-tools invariant even with `tools` removed.
|
|
144
|
-
|| (item.type !== "compaction_trigger" && item.type !== "additional_tools"));
|
|
147
|
+
|| (item.type !== "compaction_trigger" && item.type !== "additional_tools")));
|
|
145
148
|
return {
|
|
146
149
|
...rest,
|
|
150
|
+
// Lite validates this flag even when the summarizer has no callable tools.
|
|
151
|
+
...(responsesLite ? { parallel_tool_calls: false } : {}),
|
|
147
152
|
input: [
|
|
148
153
|
...(stripInputImagesDeep(kept) as unknown[]),
|
|
149
154
|
{ type: "message", role: "user", content: [{ type: "input_text", text: COMPACT_PROMPT }] },
|
|
@@ -466,7 +471,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
466
471
|
// sanitizers below can still run after it.
|
|
467
472
|
outBody = normalizeResponsesCodeMode(outBody, parsed, provider);
|
|
468
473
|
if (parsed._compactionRequest === true && (!isCanonicalOpenAiForwardProvider(provider) || parsed._portableCompaction === true)) {
|
|
469
|
-
outBody = buildRoutedCompactionBody(outBody);
|
|
474
|
+
outBody = buildRoutedCompactionBody(outBody, new Headers(headers).get(CODEX_RESPONSES_LITE_HEADER) === "true");
|
|
470
475
|
}
|
|
471
476
|
// Run after routed compaction so nested input_image parts are replaced before a malformed
|
|
472
477
|
// tool output is flattened to text and can no longer be inspected structurally.
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { EMPTY_TOOL_OUTPUT_ANNOTATION, isWhitespaceOnlyTextPartArray } from "../empty-tool-output-annotation";
|
|
3
3
|
import { isPlainObject } from "./internal";
|
|
4
4
|
import { peekBridgeSearchReplay } from "../../responses/bridge-search-replay-cache";
|
|
5
|
+
import { externalTaskInputResponsesContent } from "../../responses/task-input";
|
|
5
6
|
|
|
6
7
|
const MAX_RESPONSES_CALL_ID_LENGTH = 64;
|
|
7
8
|
|
|
@@ -169,6 +170,16 @@ export function repairUnidentifiedToolOutputItems(body: unknown): unknown {
|
|
|
169
170
|
|| (typeof item.call_id === "string" && item.call_id.length > 0)) {
|
|
170
171
|
return item;
|
|
171
172
|
}
|
|
173
|
+
// #6764: an external task envelope is user input, not an orphaned result. Recognize it
|
|
174
|
+
// before the generic repair so the summarizer and the destination see a plain user turn.
|
|
175
|
+
// Any nonempty string call_id stays a tool result, as on the parsed path: the request schema
|
|
176
|
+
// accepts a whitespace call_id as a function_call_output and strips the envelope fields
|
|
177
|
+
// (src/responses/schema.ts), so both paths must agree that it is not task input.
|
|
178
|
+
const taskInput = externalTaskInputResponsesContent(item);
|
|
179
|
+
if (taskInput) {
|
|
180
|
+
changed = true;
|
|
181
|
+
return { type: "message", role: "user", content: taskInput };
|
|
182
|
+
}
|
|
172
183
|
if (!isRepairableToolOutput(item.output)) return item;
|
|
173
184
|
changed = true;
|
|
174
185
|
return {
|
|
@@ -380,6 +391,15 @@ export function repairOrphanedInputItems(
|
|
|
380
391
|
const isCustomOutput = item.type === "custom_tool_call_output";
|
|
381
392
|
if (isFnOutput || isCustomOutput) {
|
|
382
393
|
flushPendingSyntheticOutputs();
|
|
394
|
+
// Same rule as repairUnidentifiedToolOutputItems: a nonempty string call_id is a tool result
|
|
395
|
+
// on the parsed path (the schema strips the envelope fields), so it is never task input here.
|
|
396
|
+
const hasStringCallId = typeof item.call_id === "string" && item.call_id.length > 0;
|
|
397
|
+
const taskInput = isFnOutput && !hasStringCallId ? externalTaskInputResponsesContent(item) : undefined;
|
|
398
|
+
if (taskInput) {
|
|
399
|
+
changed = true;
|
|
400
|
+
repaired.push({ type: "message", role: "user", content: taskInput });
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
383
403
|
const callId = typeof item.call_id === "string" ? item.call_id : "";
|
|
384
404
|
const paired = isFnOutput ? functionCallIds.has(callId) : customCallIds.has(callId);
|
|
385
405
|
const usableOutput = isRepairableToolOutput(item.output);
|
|
@@ -36,11 +36,14 @@ export function createAdapterPhysicalSend(ctx: AdapterFetchContext = {}, fallbac
|
|
|
36
36
|
const physicalExecutor = (async (input, init) => {
|
|
37
37
|
if (ctx.abortSignal?.aborted) throw abortError(ctx.abortSignal);
|
|
38
38
|
if (init?.signal?.aborted) throw abortError(init.signal);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const run = async (): Promise<Response> => {
|
|
40
|
+
if (dispatched || (permit && !permit.use())) throw new SendBudgetExhaustedError(options.url);
|
|
41
|
+
dispatched = true;
|
|
42
|
+
ordinal += 1;
|
|
43
|
+
ctx.onPhysicalSend?.({ ordinal, ...(options.recovery ? { recovery: options.recovery } : {}) });
|
|
44
|
+
return (executor.unpacedFetch ?? executor)(input, init);
|
|
45
|
+
};
|
|
46
|
+
return permit?.execute ? permit.execute(run) : run();
|
|
44
47
|
}) as typeof globalThis.fetch;
|
|
45
48
|
let slot: ProviderRequestSlot | undefined;
|
|
46
49
|
try {
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import type { AdapterRequest, ProviderAdapter } from "../base";
|
|
3
4
|
import { mapReasoningEffort } from "../../reasoning-effort";
|
|
4
|
-
import { buildSystemPrompt } from "../coding-agent/protocol";
|
|
5
|
-
import {
|
|
5
|
+
import { buildSystemPrompt, MAX_TOOL_BLOCK_STARTS } from "../coding-agent/protocol";
|
|
6
|
+
import { buildCodingAgentToolCatalog, CodingAgentToolCatalogError } from "../coding-agent/tool-catalog";
|
|
7
|
+
import { baseScopedEnv, runCodingAgentTurn, type CodingAgentDeps, type CodingAgentToolBridgeInput } from "../coding-agent/turn";
|
|
6
8
|
import { QODER_PROFILES, type QoderProfile } from "./profiles";
|
|
7
9
|
import { QoderScaffoldFilter, QODER_SCAFFOLD_ERROR_CODE, qoderScaffoldErrorMessage } from "./scaffold-guard";
|
|
8
10
|
|
|
9
11
|
export type QoderAdapterDeps = CodingAgentDeps;
|
|
10
12
|
|
|
13
|
+
const QODER_MCP_SERVER_NAME = "opencodex";
|
|
14
|
+
const QODER_MCP_SERVER_PATH = fileURLToPath(new URL("../coding-agent/mcp-server.ts", import.meta.url));
|
|
15
|
+
|
|
11
16
|
export function buildQoderChildEnv(profile: QoderProfile, apiKey: string, systemPrompt?: string): Record<string, string> {
|
|
12
17
|
const promptEnv = profile.region === "cn" ? "QODERCN_APPEND_SYSTEM_PROMPT" : "QODER_APPEND_SYSTEM_PROMPT";
|
|
13
18
|
return { ...baseScopedEnv(), NO_COLOR: "1", [profile.tokenEnv]: apiKey,
|
|
14
19
|
...(systemPrompt ? { [promptEnv]: systemPrompt } : {}) };
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
/**
|
|
22
|
+
/** Disable Qoder's built-in tools; only a request-scoped MCP catalog can be exposed. */
|
|
18
23
|
export function buildQoderArgs(parsed: OcxParsedRequest, provider: OcxProviderConfig): string[] {
|
|
19
24
|
const args = [
|
|
20
25
|
"-p",
|
|
@@ -35,8 +40,8 @@ export function buildQoderArgs(parsed: OcxParsedRequest, provider: OcxProviderCo
|
|
|
35
40
|
/**
|
|
36
41
|
* Wrap the turn's outbound channel with the scaffolding guard (#4190).
|
|
37
42
|
*
|
|
38
|
-
* The vendor CLI can put its own agent layer into the text channel despite
|
|
39
|
-
*
|
|
43
|
+
* The vendor CLI can put its own agent layer into the text channel despite built-in tools
|
|
44
|
+
* being disabled, and the shared stream-json parser forwards a text delta
|
|
40
45
|
* without inspecting it. This is the last point that is still qoder-specific, so the guard
|
|
41
46
|
* sits here rather than in the parser every coding-agent CLI shares.
|
|
42
47
|
*
|
|
@@ -123,7 +128,36 @@ export function createQoderAdapter(provider: OcxProviderConfig, deps: QoderAdapt
|
|
|
123
128
|
});
|
|
124
129
|
return;
|
|
125
130
|
}
|
|
126
|
-
|
|
131
|
+
let catalog: ReturnType<typeof buildCodingAgentToolCatalog>;
|
|
132
|
+
try {
|
|
133
|
+
catalog = buildCodingAgentToolCatalog(parsed, QODER_MCP_SERVER_NAME);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (!(error instanceof CodingAgentToolCatalogError)) throw error;
|
|
136
|
+
emit({
|
|
137
|
+
type: "error",
|
|
138
|
+
message: "Invalid Qoder tool catalog.",
|
|
139
|
+
status: 400,
|
|
140
|
+
errorType: "invalid_request_error",
|
|
141
|
+
code: "tool_catalog_invalid",
|
|
142
|
+
retryable: false,
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const toolBridge: CodingAgentToolBridgeInput | undefined = catalog.tools.length > 0 || catalog.requireToolCall
|
|
147
|
+
? {
|
|
148
|
+
serverName: QODER_MCP_SERVER_NAME,
|
|
149
|
+
serverModulePath: QODER_MCP_SERVER_PATH,
|
|
150
|
+
tools: catalog.tools,
|
|
151
|
+
emittedNameMap: catalog.emittedNameMap,
|
|
152
|
+
maxTurnToolCalls: MAX_TOOL_BLOCK_STARTS,
|
|
153
|
+
projectHistoryToolNames: true,
|
|
154
|
+
completeAssistantToolUse: true,
|
|
155
|
+
toolTurnCompletionSignal: "assistant_tool_use_stop",
|
|
156
|
+
allowedToolsFlag: "--allowed-tools",
|
|
157
|
+
standaloneEntrypoint: "__qoder-mcp",
|
|
158
|
+
requireToolCall: catalog.requireToolCall,
|
|
159
|
+
}
|
|
160
|
+
: undefined;
|
|
127
161
|
// Keep the folded prompt out of argv and never inherit an ambient vendor prompt.
|
|
128
162
|
const system = buildSystemPrompt(parsed);
|
|
129
163
|
await runCodingAgentTurn({
|
|
@@ -135,6 +169,7 @@ export function createQoderAdapter(provider: OcxProviderConfig, deps: QoderAdapt
|
|
|
135
169
|
buildArgs: (_profile, req, prov) => buildQoderArgs(req, prov),
|
|
136
170
|
buildEnv: (profile, apiKey) => buildQoderChildEnv(profile as QoderProfile, apiKey, system),
|
|
137
171
|
deps,
|
|
172
|
+
...(toolBridge ? { toolBridge } : {}),
|
|
138
173
|
});
|
|
139
174
|
},
|
|
140
175
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vendor-scaffolding guard for the qoder route (#4190).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The vendor CLI does not always honour that. It has been observed emitting its own agent
|
|
4
|
+
* Even with built-in tools disabled and strict MCP isolation, Codex keeps tool execution
|
|
5
|
+
* ownership. The vendor CLI does not always honour that. It has been observed emitting its own agent
|
|
7
6
|
* layer into the assistant text channel — an MCP lazy-loading `<system-reminder>` block
|
|
8
7
|
* listing the local machine's configured MCP servers, and framework tool-call markup with a
|
|
9
8
|
* mismatched closer. Both reached the client verbatim, because the shared stream-json parser
|
package/src/adapters/zed.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createAnthropicAdapter } from "./anthropic";
|
|
|
4
4
|
import { createGoogleAdapter } from "./google";
|
|
5
5
|
import { createOpenAIChatAdapter } from "./openai-chat";
|
|
6
6
|
import { createResponsesPassthroughAdapter } from "./openai-responses";
|
|
7
|
+
import { createAdapterPhysicalSend } from "./physical-send";
|
|
7
8
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
8
9
|
import { createTranslatorBudget, releaseTranslatedEvent, retainTranslatedEvent, type TranslatorBudget } from "../lib/translator-budget";
|
|
9
10
|
import { releaseCompactionCiphertextLease } from "../responses/compaction";
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
resolveZedModels,
|
|
14
15
|
scrubZedCredentials,
|
|
15
16
|
zedLlmFetch,
|
|
17
|
+
ZED_CLOUD_BASE_URL,
|
|
16
18
|
ZED_HEADERS,
|
|
17
19
|
type ZedCredentials,
|
|
18
20
|
} from "../providers/zed";
|
|
@@ -340,8 +342,21 @@ export function createZedAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
|
340
342
|
async fetchResponse(request, ctx) {
|
|
341
343
|
if (!credentials) throw new Error("Zed request credentials were not initialized");
|
|
342
344
|
const fetchFn = ctx?.executor ?? globalThis.fetch;
|
|
345
|
+
const send = createAdapterPhysicalSend(ctx);
|
|
346
|
+
let completionAttempts = 0;
|
|
343
347
|
return zedLlmFetch(credentials, "/completions", {
|
|
344
|
-
fetchFn,
|
|
348
|
+
fetchFn: (async (input, init) => {
|
|
349
|
+
const url = input instanceof Request ? input.url : new URL(String(input)).href;
|
|
350
|
+
// Token exchange/account lookup are support traffic; both completion attempts
|
|
351
|
+
// share admission and physical ordinals, including the provider's 401 replay.
|
|
352
|
+
if (url === `${ZED_CLOUD_BASE_URL}/client/users/me`
|
|
353
|
+
|| url === `${ZED_CLOUD_BASE_URL}/client/llm_tokens`) return fetchFn(input, init);
|
|
354
|
+
const recovery = completionAttempts++ > 0;
|
|
355
|
+
return send({ url, sendClass: recovery ? "auth-recovery" : "initial",
|
|
356
|
+
...(recovery ? { recovery: "oauth-401" } : {}),
|
|
357
|
+
dispatch: executor => executor(input, init),
|
|
358
|
+
});
|
|
359
|
+
}) as typeof globalThis.fetch,
|
|
345
360
|
signal: ctx?.abortSignal,
|
|
346
361
|
baseUrl: provider.baseUrl,
|
|
347
362
|
fetchInit: {
|
|
@@ -15,7 +15,8 @@ import { lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileS
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import type { OcxConfig } from "../types";
|
|
17
17
|
import { renameAtomicFile } from "../lib/windows-atomic-replace";
|
|
18
|
-
import {
|
|
18
|
+
import { assertNotRealClaudeConfigUnderTest } from "../lib/test-home-guard";
|
|
19
|
+
import { accountsAt200k, entryParts, SAFE_AGENT_MODEL_ID, withSubagentContextMarker } from "./subagent-model";
|
|
19
20
|
import { stripOneMillionMarker } from "./context-windows";
|
|
20
21
|
import { claudeConfigDir } from "./gateway-cache";
|
|
21
22
|
import { DEFAULT_SUBAGENT_MODELS } from "../config";
|
|
@@ -97,7 +98,7 @@ export function buildClaudeAgentDefs(
|
|
|
97
98
|
// Generated defs mark [1m] on the authoritative window only — never the
|
|
98
99
|
// main-session auto-context predicate (a 372K route marked [1m] would be
|
|
99
100
|
// accounted at 1M with no compaction safety net in the subagent).
|
|
100
|
-
const model = withSubagentContextMarker(alias, windows);
|
|
101
|
+
const model = withSubagentContextMarker(alias, windows, accountsAt200k(config));
|
|
101
102
|
const bare = alias.toLowerCase();
|
|
102
103
|
if (coveredModels.has(bare)) return;
|
|
103
104
|
coveredModels.add(bare);
|
|
@@ -130,7 +131,7 @@ export function buildClaudeAgentDefs(
|
|
|
130
131
|
// the next launch sync — documented limit. No resolvable default -> no self def.
|
|
131
132
|
const selfModel = pickerDefaultModel(configDir) ?? (config.claudeCode?.model?.trim() || null);
|
|
132
133
|
if (selfModel) {
|
|
133
|
-
const marked = withSubagentContextMarker(selfModel, windows);
|
|
134
|
+
const marked = withSubagentContextMarker(selfModel, windows, accountsAt200k(config));
|
|
134
135
|
defs.push({
|
|
135
136
|
file: `${OWNED_PREFIX}self.md`,
|
|
136
137
|
name: `${OWNED_PREFIX}self`,
|
|
@@ -203,6 +204,16 @@ function isOwnedFile(path: string): boolean {
|
|
|
203
204
|
* atomic (tmp + rename). Best-effort — returns null on any failure.
|
|
204
205
|
*/
|
|
205
206
|
export function syncClaudeAgentDefs(defs: readonly ClaudeAgentDef[], configDir = claudeConfigDir()): string[] | null {
|
|
207
|
+
// Outside the best-effort catch: under an armed test process a write or prune of the
|
|
208
|
+
// real Claude agents directory must throw, never degrade to "returned null" (#6775).
|
|
209
|
+
// Every path written is judged by where it resolves, including a pre-placed link named like
|
|
210
|
+
// a definition or its temporary file.
|
|
211
|
+
const agentsDir = join(configDir, "agents");
|
|
212
|
+
assertNotRealClaudeConfigUnderTest(
|
|
213
|
+
configDir,
|
|
214
|
+
agentsDir,
|
|
215
|
+
...defs.flatMap(def => [join(agentsDir, def.file), `${join(agentsDir, def.file)}.tmp-${process.pid}`]),
|
|
216
|
+
);
|
|
206
217
|
try {
|
|
207
218
|
const dir = join(configDir, "agents");
|
|
208
219
|
if (defs.length === 0) {
|
|
@@ -15,8 +15,9 @@ import { desktop3pAlias } from "./desktop-3p";
|
|
|
15
15
|
import { nativeOpenAiContextWindow, type CatalogModel, type NativeContextLimitsInput } from "../codex/catalog";
|
|
16
16
|
import { ANTHROPIC_MODEL_CONTEXT_WINDOWS } from "../providers/registry/model-seeds";
|
|
17
17
|
import type { OcxClaudeCodeConfig } from "../types";
|
|
18
|
+
import { AUTO_COMPACT_WINDOW_DEFAULT, AUTO_CONTEXT_FLOOR, ONE_MILLION } from "./long-context";
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
export { AUTO_COMPACT_WINDOW_DEFAULT, AUTO_CONTEXT_FLOOR } from "./long-context";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* The native id each Claude Code tier alias resolves to (2.1.282: `--model opus` sends
|
|
@@ -27,21 +28,6 @@ const ONE_MILLION = 1_000_000;
|
|
|
27
28
|
*/
|
|
28
29
|
const CLAUDE_CODE_NATIVE_TIERS = { opus: "claude-opus-5-5", sonnet: "claude-sonnet-5", fable: "claude-fable-5-1" } as const;
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
-
* Auto-context defaults (devlog 260712 020, user-approved).
|
|
32
|
-
*
|
|
33
|
-
* The compact window is the token count at which Claude Code starts compacting, and it is
|
|
34
|
-
* also the floor `shouldMarkOneMillion` uses — a model may only carry the marker if it can
|
|
35
|
-
* host this window. 350,000 was chosen when the widest native row advertised 372,000.
|
|
36
|
-
*
|
|
37
|
-
* It now matches the auto-compaction limit the Codex catalog ships for the same models
|
|
38
|
-
* (`nativeAutoCompactLimit`: 829,800 against the 922,000 native window). Leaving the two
|
|
39
|
-
* apart meant one model compacting at 350k under Claude Code and at 829,800 under Codex.
|
|
40
|
-
* The value stays clear of the measured 922,000 ceiling by ~92k, so compaction still has
|
|
41
|
-
* room to run before the upstream refuses.
|
|
42
|
-
*/
|
|
43
|
-
export const AUTO_COMPACT_WINDOW_DEFAULT = 829_800;
|
|
44
|
-
export const AUTO_CONTEXT_FLOOR = 200_000;
|
|
45
31
|
/** Binary-verified accepted range for CLAUDE_CODE_AUTO_COMPACT_WINDOW (2.1.207: pSo=1e5, yDs=1e6). */
|
|
46
32
|
export const AUTO_COMPACT_WINDOW_MIN = 100_000;
|
|
47
33
|
export const AUTO_COMPACT_WINDOW_MAX = ONE_MILLION;
|
|
@@ -59,14 +45,27 @@ export interface AutoContextMode {
|
|
|
59
45
|
enabled: boolean;
|
|
60
46
|
/** Effective CLAUDE_CODE_AUTO_COMPACT_WINDOW value (tokens). */
|
|
61
47
|
compactWindow: number;
|
|
48
|
+
/** claudeCode.contextAccounting "200k": nothing is marked [1m] automatically, whatever its window. */
|
|
49
|
+
accounting200k?: true;
|
|
62
50
|
}
|
|
63
51
|
|
|
64
52
|
export const AUTO_CONTEXT_OFF: AutoContextMode = { enabled: false, compactWindow: AUTO_COMPACT_WINDOW_DEFAULT };
|
|
65
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Marking mode for surfaces whose runner may not inherit the compaction env (Desktop pickers,
|
|
56
|
+
* discovery, generated subagents). shouldMarkOneMillion under it equals isLongContextWindow
|
|
57
|
+
* (long-context.ts); kept as a mode so the existing marker helpers serve every surface.
|
|
58
|
+
*/
|
|
59
|
+
export const UNPAIRED_AUTO_CONTEXT: AutoContextMode = { enabled: true, compactWindow: AUTO_COMPACT_WINDOW_DEFAULT };
|
|
60
|
+
|
|
61
|
+
/** The "200k" opt-in: no widening, no compact-window injection, no automatic marker. */
|
|
62
|
+
export const ACCOUNTING_200K: AutoContextMode = { enabled: false, compactWindow: AUTO_COMPACT_WINDOW_DEFAULT, accounting200k: true };
|
|
63
|
+
|
|
66
64
|
interface AutoContextConfigSlice {
|
|
67
65
|
autoContext?: boolean;
|
|
68
66
|
autoCompactWindow?: number;
|
|
69
67
|
maxContextTokens?: number;
|
|
68
|
+
contextAccounting?: string;
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
function inAutoCompactRange(value: number): boolean {
|
|
@@ -90,6 +89,8 @@ function inAutoCompactRange(value: number): boolean {
|
|
|
90
89
|
* to AUTO_COMPACT_WINDOW_DEFAULT (the management API rejects them; this guards hand-edits).
|
|
91
90
|
*/
|
|
92
91
|
export function resolveAutoContext(claudeCode: AutoContextConfigSlice | undefined, envOverride?: string): AutoContextMode {
|
|
92
|
+
// The 200k opt-in wins over every other lever, a user-exported compact window included.
|
|
93
|
+
if (claudeCode?.contextAccounting === "200k") return ACCOUNTING_200K;
|
|
93
94
|
if (claudeCode?.autoContext === false) return AUTO_CONTEXT_OFF;
|
|
94
95
|
const maxCtx = claudeCode?.maxContextTokens;
|
|
95
96
|
if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) return AUTO_CONTEXT_OFF;
|
|
@@ -132,6 +133,7 @@ export function claudeToolSearchEnv(value: boolean | string | undefined): string
|
|
|
132
133
|
*/
|
|
133
134
|
export function shouldMarkOneMillion(window: number | undefined, auto: AutoContextMode): boolean {
|
|
134
135
|
if (typeof window !== "number" || window <= 0) return false;
|
|
136
|
+
if (auto.accounting200k) return false;
|
|
135
137
|
if (window >= ONE_MILLION) return true;
|
|
136
138
|
return auto.enabled && window > AUTO_CONTEXT_FLOOR && window >= auto.compactWindow;
|
|
137
139
|
}
|
|
@@ -248,7 +250,7 @@ export interface ClaudeTierModels {
|
|
|
248
250
|
* value injected into BOTH haiku variables.
|
|
249
251
|
*/
|
|
250
252
|
export function effectiveModelEnv(
|
|
251
|
-
claudeCode: { model?: string; smallFastModel?: string; tierModels?: ClaudeTierModels; autoContext?: boolean; autoCompactWindow?: number; maxContextTokens?: number } | undefined,
|
|
253
|
+
claudeCode: { model?: string; smallFastModel?: string; tierModels?: ClaudeTierModels; autoContext?: boolean; autoCompactWindow?: number; maxContextTokens?: number; contextAccounting?: string } | undefined,
|
|
252
254
|
windows: Record<string, number>,
|
|
253
255
|
autoOverride?: AutoContextMode,
|
|
254
256
|
): Record<string, string> {
|
package/src/claude/desktop-3p.ts
CHANGED
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
} from "./desktop-3p-library";
|
|
11
11
|
export { resolveDesktop3pConfigLibraryPath, type Desktop3pConfigLibraryOptions } from "./desktop-3p-library";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, lstatSync, unlinkSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { atomicWriteFile, readConfigDiagnostics, withConfigMutationLockSync } from "../config";
|
|
16
|
+
import { atomicWriteFileNoFollow } from "../config/atomic-write";
|
|
16
17
|
import { claudeDesktopIntegrationEnabled } from "../codex/desired-state";
|
|
17
18
|
import type { OcxClaudeDesktopProfile } from "../types";
|
|
18
19
|
import {
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
import { nativeOpenAiContextWindow, type NativeContextLimitsInput } from "../codex/catalog";
|
|
25
26
|
import { localAdmissionToken, localInferenceDestination } from "../lib/local-destinations";
|
|
26
27
|
import { assertDesktop3pModelsValid } from "./desktop-3p-guard";
|
|
28
|
+
import { claudeSurfaceSupportsOneMillion, ONE_MILLION } from "./long-context";
|
|
27
29
|
|
|
28
30
|
export interface Desktop3pModelEntry {
|
|
29
31
|
name: string;
|
|
@@ -55,12 +57,12 @@ export interface Desktop3pRoutedModel {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
/**
|
|
58
|
-
* 1M
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* The genuine-1M threshold real Anthropic rows still need. Every other row follows the
|
|
61
|
+
* long-window rule; the writer, the profile renderer and the dashboard DTO all decide through
|
|
62
|
+
* `claudeSurfaceSupportsOneMillion` (long-context.ts), so the 1M chip cannot disagree with the
|
|
63
|
+
* written config.
|
|
62
64
|
*/
|
|
63
|
-
export const DESKTOP_SUPPORTS_1M_THRESHOLD =
|
|
65
|
+
export const DESKTOP_SUPPORTS_1M_THRESHOLD = ONE_MILLION;
|
|
64
66
|
|
|
65
67
|
/** CLI arg parsing for `ocx claude desktop` mode flags (mutually exclusive). */
|
|
66
68
|
export function parseDesktop3pModeArgs(flags: string[]): { mode: Desktop3pConfigMode } | { error: string } {
|
|
@@ -251,7 +253,7 @@ function collectDesktop3pModels(
|
|
|
251
253
|
for (const { provider, id, contextWindow } of candidates) {
|
|
252
254
|
const route = `${provider}/${id}`;
|
|
253
255
|
const alias = desktop3pAlias(provider, id);
|
|
254
|
-
const supports1m =
|
|
256
|
+
const supports1m = claudeSurfaceSupportsOneMillion(provider, id, contextWindow)
|
|
255
257
|
? { supports1m: true as const }
|
|
256
258
|
: {};
|
|
257
259
|
if (alias === id) {
|
|
@@ -317,16 +319,30 @@ export function buildDesktop3pRegistry(
|
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
/** Generate Claude Desktop 3P model entries from the proxy's available models. */
|
|
322
|
+
export interface Desktop3pModelOptions {
|
|
323
|
+
/**
|
|
324
|
+
* false under claudeCode.contextAccounting "200k": a long-window model stays selectable at 1M
|
|
325
|
+
* (`supports1m`) but no longer defaults to it (`prefer1m` dropped).
|
|
326
|
+
*/
|
|
327
|
+
preferOneMillion?: boolean;
|
|
328
|
+
}
|
|
329
|
+
|
|
320
330
|
export function generateDesktop3pModels(
|
|
321
331
|
nativeSlugs: string[],
|
|
322
332
|
routedModels: Array<Desktop3pRoutedModel>,
|
|
323
333
|
profile?: OcxClaudeDesktopProfile,
|
|
324
334
|
nativeContextCap?: NativeContextLimitsInput,
|
|
335
|
+
options: Desktop3pModelOptions = {},
|
|
325
336
|
): Desktop3pModelEntry[] {
|
|
326
337
|
const { models, registry, realAnthropicIds } = collectDesktop3pModels(nativeSlugs, routedModels, profile, nativeContextCap);
|
|
327
338
|
desktop3pRegistry = registry;
|
|
328
339
|
desktop3pRealAnthropicIds = realAnthropicIds;
|
|
329
|
-
return models;
|
|
340
|
+
return options.preferOneMillion === false ? models.map(({ prefer1m: _prefer1m, ...model }) => model) : models;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The Desktop model options a config asks for (one place, so writer and export agree). */
|
|
344
|
+
export function desktop3pModelOptions(claudeCode: { contextAccounting?: string } | undefined): Desktop3pModelOptions {
|
|
345
|
+
return { preferOneMillion: claudeCode?.contextAccounting !== "200k" };
|
|
330
346
|
}
|
|
331
347
|
|
|
332
348
|
/** Resolve an alias using the most recently generated Desktop model registry. */
|
|
@@ -382,6 +398,7 @@ export function generateDesktop3pConfig(
|
|
|
382
398
|
mode: Desktop3pConfigMode = "static",
|
|
383
399
|
profile?: OcxClaudeDesktopProfile,
|
|
384
400
|
nativeContextCap?: NativeContextLimitsInput,
|
|
401
|
+
options: Desktop3pModelOptions = {},
|
|
385
402
|
): object {
|
|
386
403
|
const base = {
|
|
387
404
|
inferenceProvider: "gateway",
|
|
@@ -398,7 +415,7 @@ export function generateDesktop3pConfig(
|
|
|
398
415
|
...base,
|
|
399
416
|
modelDiscoveryEnabled: mode === "hybrid",
|
|
400
417
|
inferenceModels: (() => {
|
|
401
|
-
const models = generateDesktop3pModels(nativeSlugs, routedModels, profile, nativeContextCap);
|
|
418
|
+
const models = generateDesktop3pModels(nativeSlugs, routedModels, profile, nativeContextCap, options);
|
|
402
419
|
// Fail loud at the write boundary rather than ship a config Desktop rejects:
|
|
403
420
|
// the output counterpart of the request-path guards.
|
|
404
421
|
assertDesktop3pModelsValid(models);
|
|
@@ -662,6 +679,7 @@ export function writeDesktop3pConfig(
|
|
|
662
679
|
profile?: OcxClaudeDesktopProfile,
|
|
663
680
|
nativeContextCap?: NativeContextLimitsInput,
|
|
664
681
|
lifecycleLockDeps?: ClientLifecycleLockDeps,
|
|
682
|
+
refreshOnly?: { appliedFingerprint: string; admit: () => boolean },
|
|
665
683
|
): { written: boolean; path: string; reason?: string; fingerprint?: string } {
|
|
666
684
|
try {
|
|
667
685
|
return withClientLifecycleSync(() => withConfigMutationLockSync(() => {
|
|
@@ -674,9 +692,29 @@ export function writeDesktop3pConfig(
|
|
|
674
692
|
if (!claudeDesktopIntegrationEnabled(latest.config)) {
|
|
675
693
|
return { written: false, path: resolveDesktop3pConfigLibraryPath(), reason: "desired_state_changed" };
|
|
676
694
|
}
|
|
695
|
+
if (refreshOnly && !desktopRefreshPathsSafe(resolveDesktop3pConfigLibraryPath())) {
|
|
696
|
+
return { written: false, path: resolveDesktop3pConfigLibraryPath(), reason: "desktop_refresh_only_skipped" };
|
|
697
|
+
}
|
|
677
698
|
if (connection.kind === "connected" || inspectRemoteDesktopCleanup().kind !== "absent") {
|
|
678
699
|
return { written: false, path: resolveDesktop3pConfigLibraryPath(), reason: "desktop_remote_store_active" };
|
|
679
700
|
}
|
|
701
|
+
let requiredId: string | undefined;
|
|
702
|
+
if (refreshOnly) {
|
|
703
|
+
const refused = { written: false, path: resolveDesktop3pConfigLibraryPath(), reason: "desktop_refresh_only_skipped" };
|
|
704
|
+
// Ownership and bytes must still match under L→C; discovery outside these locks is not authority.
|
|
705
|
+
if (!refreshOnly.admit() || latest.config.claudeCode?.desktopProfile?.appliedFingerprint !== refreshOnly.appliedFingerprint) return refused;
|
|
706
|
+
const inspected = inspectDesktop3pConfigLibrary({ appliedFingerprint: refreshOnly.appliedFingerprint });
|
|
707
|
+
if (inspected.kind !== "gateway_ours" || !inspected.appliedId || !inspected.selectedProfilePath) return refused;
|
|
708
|
+
if (!desktopRefreshPathsSafe(inspected.libraryPath, inspected.selectedProfilePath)) return refused;
|
|
709
|
+
const metadata = parseMetadata(join(inspected.libraryPath, "_meta.json"));
|
|
710
|
+
if (!isOwnedDesktopGatewayEntry(metadata.entries.find(entry => entry.id === inspected.appliedId))) return refused;
|
|
711
|
+
try {
|
|
712
|
+
const selected = JSON.parse(readFileSync(inspected.selectedProfilePath, "utf8")) as Record<string, unknown>;
|
|
713
|
+
mode = selected.modelDiscoveryEnabled !== true ? "static"
|
|
714
|
+
: Array.isArray(selected.inferenceModels) ? "hybrid" : "discovery";
|
|
715
|
+
} catch { return refused; }
|
|
716
|
+
requiredId = inspected.appliedId;
|
|
717
|
+
}
|
|
680
718
|
// Claude Desktop runs on this machine, so it dials the unauthenticated loopback listener
|
|
681
719
|
// when one is enabled — on such a hub that is the only credential-free local socket
|
|
682
720
|
// (#4236) — and otherwise the bind address, which answers but demands data-plane
|
|
@@ -700,8 +738,9 @@ export function writeDesktop3pConfig(
|
|
|
700
738
|
);
|
|
701
739
|
}
|
|
702
740
|
return writeDesktop3pConfigWithGenerator(() => (
|
|
703
|
-
generateDesktop3pConfig(destination.origin, nativeSlugs, routedModels, gatewayKey, mode, profile, nativeContextCap
|
|
704
|
-
|
|
741
|
+
generateDesktop3pConfig(destination.origin, nativeSlugs, routedModels, gatewayKey, mode, profile, nativeContextCap,
|
|
742
|
+
desktop3pModelOptions(latest.config.claudeCode))
|
|
743
|
+
), requiredId && refreshOnly ? { requiredId, expectedFingerprint: refreshOnly.appliedFingerprint, admit: refreshOnly.admit } : undefined);
|
|
705
744
|
}), lifecycleLockDeps);
|
|
706
745
|
} catch { return { written: false, path: resolveDesktop3pConfigLibraryPath(), reason: "desktop_lifecycle_busy_or_unsafe" }; }
|
|
707
746
|
}
|
|
@@ -734,17 +773,38 @@ export function writeRemoteDesktop3pConfig(options: {
|
|
|
734
773
|
} catch { return { written: false, path: "", reason: "desktop_lifecycle_busy_or_unsafe" }; }
|
|
735
774
|
}
|
|
736
775
|
|
|
776
|
+
/** Refresh-only writes never follow links, including a dangling backup link. */
|
|
777
|
+
function desktopRefreshPathsSafe(libraryPath: string, configPath?: string): boolean {
|
|
778
|
+
try {
|
|
779
|
+
if (!lstatSync(libraryPath).isDirectory() || !lstatSync(join(libraryPath, "_meta.json")).isFile()) return false;
|
|
780
|
+
if (configPath) {
|
|
781
|
+
if (!lstatSync(configPath).isFile()) return false;
|
|
782
|
+
try { if (lstatSync(`${configPath}.bak`).isSymbolicLink()) return false; }
|
|
783
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") return false; }
|
|
784
|
+
}
|
|
785
|
+
return true;
|
|
786
|
+
} catch { return false; }
|
|
787
|
+
}
|
|
788
|
+
|
|
737
789
|
function writeDesktop3pConfigWithGenerator(
|
|
738
790
|
generate: () => object,
|
|
791
|
+
refreshOnly?: { requiredId: string; expectedFingerprint: string; admit: () => boolean },
|
|
739
792
|
): { written: boolean; path: string; reason?: string; fingerprint?: string } {
|
|
740
793
|
const libraryPath = resolveDesktop3pConfigLibraryPath();
|
|
741
794
|
const metadataPath = join(libraryPath, "_meta.json");
|
|
742
795
|
let configPath = libraryPath;
|
|
743
796
|
|
|
744
797
|
try {
|
|
745
|
-
|
|
798
|
+
if (refreshOnly && !desktopRefreshPathsSafe(libraryPath)) {
|
|
799
|
+
return { written: false, path: libraryPath, reason: "desktop_refresh_only_skipped" };
|
|
800
|
+
}
|
|
801
|
+
if (!refreshOnly) mkdirSync(libraryPath, { recursive: true, mode: 0o700 });
|
|
746
802
|
const metadata = parseMetadata(metadataPath);
|
|
747
803
|
const selected = metadata.entries.find(entry => entry?.id === metadata.appliedId && isOwnedDesktopGatewayEntry(entry));
|
|
804
|
+
// A refresh may reuse only the applied entry; fallback allocation would silently enroll again.
|
|
805
|
+
if (refreshOnly && selected?.id !== refreshOnly.requiredId) {
|
|
806
|
+
return { written: false, path: libraryPath, reason: "desktop_refresh_only_skipped" };
|
|
807
|
+
}
|
|
748
808
|
const existing = selected ?? metadata.entries.find(entry => isOwnedDesktopGatewayEntry(entry) && typeof entry.id === "string");
|
|
749
809
|
const id = existing?.id ?? randomUUID();
|
|
750
810
|
configPath = profilePath(libraryPath, id);
|
|
@@ -757,6 +817,22 @@ function writeDesktop3pConfigWithGenerator(
|
|
|
757
817
|
const preserved = readDesktopProfileForeignKeys(configPath);
|
|
758
818
|
const configJson = JSON.stringify({ ...preserved, ...generated }, null, 2) + "\n";
|
|
759
819
|
const fingerprint = createHash("sha256").update(configJson).digest("hex").slice(0, 16);
|
|
820
|
+
if (refreshOnly) {
|
|
821
|
+
atomicWriteFileNoFollow(configPath, configJson, undefined, {
|
|
822
|
+
validateBeforeRename: () => {
|
|
823
|
+
try {
|
|
824
|
+
if (!refreshOnly.admit() || !desktopRefreshPathsSafe(libraryPath, configPath)) throw new Error();
|
|
825
|
+
const freshMetadata = parseMetadata(metadataPath);
|
|
826
|
+
if (freshMetadata.appliedId !== refreshOnly.requiredId
|
|
827
|
+
|| !isOwnedDesktopGatewayEntry(freshMetadata.entries.find(entry => entry.id === refreshOnly.requiredId))
|
|
828
|
+
|| createHash("sha256").update(readFileSync(configPath)).digest("hex").slice(0, 16) !== refreshOnly.expectedFingerprint) {
|
|
829
|
+
throw new Error();
|
|
830
|
+
}
|
|
831
|
+
} catch { throw new Error("desktop_refresh_only_skipped"); }
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
return { written: true, path: configPath, fingerprint };
|
|
835
|
+
}
|
|
760
836
|
const { backupPath } = atomicReplaceDesktopConfig(configPath, configJson);
|
|
761
837
|
try {
|
|
762
838
|
atomicWriteFile(metadataPath, JSON.stringify({ ...metadata, appliedId: id, entries }, null, 2) + "\n");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { routeSupportsOneMillion } from "./long-context";
|
|
2
3
|
import type {
|
|
3
4
|
OcxClaudeDesktopAssignment,
|
|
4
5
|
OcxClaudeDesktopFamily,
|
|
@@ -359,7 +360,7 @@ export function renderDesktopProfile(
|
|
|
359
360
|
name: isRealAnthropicRoute(route) ? assignment.alias : desktopProfileWireAlias(assignment.alias),
|
|
360
361
|
family: assignment.family,
|
|
361
362
|
isFamilyDefault: effectiveDefaults[assignment.family] === route,
|
|
362
|
-
supports1m:
|
|
363
|
+
supports1m: routeSupportsOneMillion(route, model.contextWindow),
|
|
363
364
|
};
|
|
364
365
|
});
|
|
365
366
|
}
|
|
@@ -17,6 +17,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { localAdmissionToken, localInferenceDestination } from "../lib/local-destinations";
|
|
20
|
+
import { assertNotRealClaudeConfigUnderTest } from "../lib/test-home-guard";
|
|
20
21
|
import type { OcxConfig } from "../types";
|
|
21
22
|
|
|
22
23
|
export interface GatewayModelRow {
|
|
@@ -46,11 +47,27 @@ export interface GatewayModelTarget {
|
|
|
46
47
|
/** Claude Code config dir (CLAUDE_CONFIG_DIR override honored, like the CLI). */
|
|
47
48
|
export function claudeConfigDir(): string {
|
|
48
49
|
const custom = process.env.CLAUDE_CONFIG_DIR;
|
|
49
|
-
return custom && custom.length > 0 ? custom : join(
|
|
50
|
+
return custom && custom.length > 0 ? custom : join(currentUserHome(), ".claude");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The home Claude Code itself resolves. Claude Code runs on Node, whose `os.homedir()`
|
|
55
|
+
* consults HOME (POSIX) or USERPROFILE (Windows) at call time when it is set; Bun's
|
|
56
|
+
* `os.homedir()` keeps the value it read at startup. In production the two agree. Under
|
|
57
|
+
* the test preload, which rewrites HOME after Bun has started, only this one follows the
|
|
58
|
+
* sandbox — the cached value is the developer's real home (#6775). Exported for tests.
|
|
59
|
+
*/
|
|
60
|
+
export function currentUserHome(env: NodeJS.ProcessEnv = process.env, platform: NodeJS.Platform = process.platform): string {
|
|
61
|
+
const fromEnv = platform === "win32" ? env.USERPROFILE : env.HOME;
|
|
62
|
+
return fromEnv !== undefined && fromEnv !== "" ? fromEnv : homedir();
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
/** Write the cache file; returns its path or null (best-effort, never throws). */
|
|
53
66
|
export function writeGatewayModelCache(baseUrl: string, models: readonly GatewayModelRow[], configDir = claudeConfigDir()): string | null {
|
|
67
|
+
// Outside the best-effort catch: an armed test process must not write here, and must
|
|
68
|
+
// fail loudly rather than degrade to "returned null".
|
|
69
|
+
// The file itself too: writeFileSync follows a link at gateway-models.json.
|
|
70
|
+
assertNotRealClaudeConfigUnderTest(configDir, join(configDir, "cache"), join(configDir, "cache", "gateway-models.json"));
|
|
54
71
|
try {
|
|
55
72
|
// Mirror the CLI's usable-id filter so our file matches what it would cache.
|
|
56
73
|
const usable = models.filter(m => /(claude|anthropic)/i.test(m.id));
|