@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
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 +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-DpdfZWMK.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-_bpvxJu0.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Stepfun</title><path d="M22.012 0h1.032v.927H24v.968h-.956V3.78h-1.032V1.896h-1.878v-.97h1.878V0zM2.6 12.371V1.87h.969v10.502h-.97zm10.423.66h10.95v.918h-6.208v9.579h-4.742V13.03zM5.629 3.333v12.356H0v4.51h10.386V8L20.859 8l-.003-4.668-15.227.001z" fill="url(#stepfun-gradient)" fill-rule="evenodd"/><defs><linearGradient id="stepfun-gradient" x1="1.646" y1="1.916" x2="18.342" y2="22.091" gradientUnits="userSpaceOnUse"><stop stop-color="#01A9FF"/><stop offset="1" stop-color="#0160FF"/></linearGradient></defs></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.61.0-preview.20260922",
|
|
4
4
|
"description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/package-main.mjs",
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
"typecheck": "bun x tsc --noEmit",
|
|
50
50
|
"audit:high": "bun audit --audit-level=high && cd gui && bun audit --audit-level=high",
|
|
51
51
|
"privacy:scan": "bun scripts/privacy-scan.ts",
|
|
52
|
+
"favicons": "bun scripts/brand-favicons.ts",
|
|
53
|
+
"favicons:check": "bun scripts/brand-favicons.ts --check",
|
|
52
54
|
"skill:surface": "bun scripts/generate-ocx-skill-surface.ts",
|
|
53
55
|
"skill:surface:check": "bun scripts/generate-ocx-skill-surface.ts --check",
|
|
54
56
|
"structure:index": "bun scripts/structure-ssot.ts --fix",
|
|
@@ -58,6 +60,8 @@
|
|
|
58
60
|
"build:gui": "cd gui && bun install --frozen-lockfile && bun run build && cd .. && bun run prepare:package",
|
|
59
61
|
"build:remote-workspace-helper": "cargo build --release --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
|
|
60
62
|
"test:remote-workspace-helper": "cargo test --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
|
|
63
|
+
"build:standalone": "bun scripts/build-standalone.ts",
|
|
64
|
+
"test:macos": "swift run --package-path app MenuBarCoreTests",
|
|
61
65
|
"prepare:package": "bun scripts/prepare-package.ts",
|
|
62
66
|
"prepack": "bun run prepare:package",
|
|
63
67
|
"prepublishOnly": "bun run audit:high && bun run typecheck && bun run build:gui",
|
|
@@ -83,6 +87,9 @@
|
|
|
83
87
|
"@types/bun": "1.4.0",
|
|
84
88
|
"typescript": "7.0.2"
|
|
85
89
|
},
|
|
90
|
+
"optionalDependencies": {
|
|
91
|
+
"wreq-js": "2.3.1"
|
|
92
|
+
},
|
|
86
93
|
"overrides": {
|
|
87
94
|
"@hono/node-server": "2.1.0",
|
|
88
95
|
"fast-uri": "^3.1.7",
|
|
@@ -41,7 +41,9 @@ export const MAX_INPUT_BASE64_LENGTH = 64 * MiB;
|
|
|
41
41
|
* would widen the adapter contract with no demonstrated need.
|
|
42
42
|
*/
|
|
43
43
|
export const IMAGE_NORMALIZE_CONCURRENCY = 4;
|
|
44
|
-
|
|
44
|
+
// Keep one decoded RGBA surface below 64 MiB. Native codecs may allocate additional
|
|
45
|
+
// working buffers, so this is paired with process-wide admission in the normalizer.
|
|
46
|
+
export const MAX_INPUT_PIXELS = 16_000_000;
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
/** Formats Anthropic accepts as-is; anything else must be transcoded or dropped. */
|
|
@@ -50,6 +52,8 @@ const PASSTHROUGH_MEDIA = new Set(["image/jpeg", "image/png", "image/gif", "imag
|
|
|
50
52
|
export interface NormalizeOptions {
|
|
51
53
|
/** Shift every image's starting ladder position down (413 retry tightening; 030). */
|
|
52
54
|
tierBias?: number;
|
|
55
|
+
/** Cancels queued native decode work and stops pulling more images. */
|
|
56
|
+
abortSignal?: AbortSignal;
|
|
53
57
|
/** Test seam: replaces the Bun.Image encode path (audit round 1, blocker 6). */
|
|
54
58
|
encode?: EncodeFn;
|
|
55
59
|
/** Test seam: replaces the pass-through decode validation (C-gate round 1, blocker 1). */
|
|
@@ -275,6 +279,9 @@ export const bunImageEncode: EncodeFn = async (input, spec, quality) => {
|
|
|
275
279
|
const meta = await image.metadata();
|
|
276
280
|
const w = typeof meta.width === "number" ? meta.width : 0;
|
|
277
281
|
const h = typeof meta.height === "number" ? meta.height : 0;
|
|
282
|
+
if (w <= 0 || h <= 0 || w * h > MAX_INPUT_PIXELS) {
|
|
283
|
+
throw new Error("image dimensions exceed the safe decode limit");
|
|
284
|
+
}
|
|
278
285
|
let pipeline = new Bun.Image(input);
|
|
279
286
|
if (w > spec.maxEdge || h > spec.maxEdge) {
|
|
280
287
|
const scale = spec.maxEdge / Math.max(w, h);
|
|
@@ -290,7 +297,14 @@ export const bunImageEncode: EncodeFn = async (input, spec, quality) => {
|
|
|
290
297
|
* instead of riding pass-through to an Anthropic 400 (C-gate round 1, blocker 1).
|
|
291
298
|
*/
|
|
292
299
|
export const bunImageValidate: ValidateFn = async input => {
|
|
293
|
-
|
|
300
|
+
const image = new Bun.Image(input);
|
|
301
|
+
const meta = await image.metadata();
|
|
302
|
+
const w = typeof meta.width === "number" ? meta.width : 0;
|
|
303
|
+
const h = typeof meta.height === "number" ? meta.height : 0;
|
|
304
|
+
if (w <= 0 || h <= 0 || w * h > MAX_INPUT_PIXELS) {
|
|
305
|
+
throw new Error("image dimensions exceed the safe decode limit");
|
|
306
|
+
}
|
|
307
|
+
await image.resize(1, 1).jpeg({ quality: 1 }).toBuffer();
|
|
294
308
|
};
|
|
295
309
|
|
|
296
310
|
/**
|
|
@@ -32,6 +32,37 @@ import { recordedEmittedPosition, recordEmittedPosition } from "./anthropic-imag
|
|
|
32
32
|
import { IMAGE_NORMALIZE_CONCURRENCY, MAX_INPUT_BASE64_LENGTH, MAX_INPUT_PIXELS } from "./anthropic-image-codec";
|
|
33
33
|
import type { NormalizeOptions } from "./anthropic-image-codec";
|
|
34
34
|
|
|
35
|
+
const IMAGE_DECODE_PROCESS_CONCURRENCY = IMAGE_NORMALIZE_CONCURRENCY;
|
|
36
|
+
let activeImageDecodes = 0;
|
|
37
|
+
const imageDecodeWaiters: Array<() => void> = [];
|
|
38
|
+
|
|
39
|
+
async function enterImageDecode(signal?: AbortSignal): Promise<() => void> {
|
|
40
|
+
if (signal?.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
41
|
+
let transferred = false;
|
|
42
|
+
if (activeImageDecodes >= IMAGE_DECODE_PROCESS_CONCURRENCY) {
|
|
43
|
+
await new Promise<void>((resolve, reject) => {
|
|
44
|
+
const admit = (): void => {
|
|
45
|
+
transferred = true;
|
|
46
|
+
signal?.removeEventListener("abort", abort);
|
|
47
|
+
resolve();
|
|
48
|
+
};
|
|
49
|
+
const abort = (): void => {
|
|
50
|
+
const index = imageDecodeWaiters.indexOf(admit);
|
|
51
|
+
if (index >= 0) imageDecodeWaiters.splice(index, 1);
|
|
52
|
+
reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
53
|
+
};
|
|
54
|
+
imageDecodeWaiters.push(admit);
|
|
55
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (!transferred) activeImageDecodes++;
|
|
59
|
+
return () => {
|
|
60
|
+
const next = imageDecodeWaiters.shift();
|
|
61
|
+
if (next) next();
|
|
62
|
+
else activeImageDecodes--;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
35
66
|
const UNDECODABLE_TEXT = "[image omitted: undecodable or corrupt image data]";
|
|
36
67
|
const BOMB_TEXT = "[image omitted: image too large to process safely]";
|
|
37
68
|
const OVERFLOW_DROP_TEXT = "[image omitted: total image payload exceeded the provider request budget; older images were dropped]";
|
|
@@ -104,8 +135,24 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
|
|
|
104
135
|
const budget = options.budget ?? TOTAL_IMAGE_BASE64_BUDGET;
|
|
105
136
|
const overflowAction = options.overflowAction ?? "none";
|
|
106
137
|
const processLimit = options.processLimit ?? Number.POSITIVE_INFINITY;
|
|
138
|
+
const abortSignal = options.abortSignal;
|
|
107
139
|
const n = targets.length;
|
|
108
140
|
|
|
141
|
+
const process = async (b64: string, pos: number, mediaType: string) => {
|
|
142
|
+
if (abortSignal?.aborted) throw abortSignal.reason ?? new DOMException("Aborted", "AbortError");
|
|
143
|
+
const leave = await enterImageDecode(abortSignal);
|
|
144
|
+
try {
|
|
145
|
+
// Re-check after admission: an abort landing between dequeue and decode-start
|
|
146
|
+
// must not begin decoding (an in-flight native decode cannot be interrupted).
|
|
147
|
+
if (abortSignal?.aborted) throw abortSignal.reason ?? new DOMException("Aborted", "AbortError");
|
|
148
|
+
const result = await processAt(b64, pos, mediaType, encode, validate);
|
|
149
|
+
if (abortSignal?.aborted) throw abortSignal.reason ?? new DOMException("Aborted", "AbortError");
|
|
150
|
+
return result;
|
|
151
|
+
} finally {
|
|
152
|
+
leave();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
109
156
|
// sourceB64/sourceMedia are the ORIGINAL input (encode source + cache identity);
|
|
110
157
|
// size always reflects the bytes currently ON the wire for this target (the core is
|
|
111
158
|
// the only mutator, so tracked size cannot drift from reality).
|
|
@@ -167,7 +214,7 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
|
|
|
167
214
|
// age tier on a cold store, which is the pre-#4532 behaviour.
|
|
168
215
|
const recorded = recordedEmittedPosition(b64, sourceMedia);
|
|
169
216
|
const pos = Math.min((recorded ?? initialPosition(newestFirstIndex, 0)) + Math.max(0, bias), TERMINAL_POS);
|
|
170
|
-
const result = await
|
|
217
|
+
const result = await process(b64, pos, sourceMedia);
|
|
171
218
|
if (result.kind === "failed") {
|
|
172
219
|
target.drop(UNDECODABLE_TEXT);
|
|
173
220
|
if (target.retainsBytesOnDrop) {
|
|
@@ -209,7 +256,7 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
|
|
|
209
256
|
while (sum > budget) {
|
|
210
257
|
const entry = entries.find((e): e is Entry => e !== null && !e.done);
|
|
211
258
|
if (!entry) break; // all terminal — overflowAction below decides
|
|
212
|
-
const result = await
|
|
259
|
+
const result = await process(entry.sourceB64, entry.pos + 1, entry.sourceMedia);
|
|
213
260
|
if (result.kind === "failed") {
|
|
214
261
|
entry.target.drop(UNDECODABLE_TEXT);
|
|
215
262
|
if (entry.target.retainsBytesOnDrop) {
|
|
@@ -39,6 +39,16 @@ function toAnthropicContentPart(p: OcxContentPart): unknown {
|
|
|
39
39
|
: { type: "image", source: { type: "url", url: p.imageUrl } };
|
|
40
40
|
}
|
|
41
41
|
if (p.type === "video") return { type: "text", text: "[video]" };
|
|
42
|
+
// The block the caller sent, rebuilt. A Messages-to-Messages route used to reduce it to its
|
|
43
|
+
// title before any adapter ran, so the model was told a document existed rather than given
|
|
44
|
+
// one, and the answer came back looking the same (#5212).
|
|
45
|
+
if (p.type === "document") {
|
|
46
|
+
return {
|
|
47
|
+
type: "document",
|
|
48
|
+
source: { type: "base64", media_type: p.mediaType, data: p.data },
|
|
49
|
+
...(p.filename !== undefined ? { title: p.filename } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
42
52
|
return { type: "text", text: p.text };
|
|
43
53
|
}
|
|
44
54
|
|
|
@@ -857,6 +867,12 @@ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (
|
|
|
857
867
|
name: toolNames.toWire(namespacedToolName(t.namespace, t.name)),
|
|
858
868
|
description: t.description,
|
|
859
869
|
input_schema: normalizeAnthropicInputSchema(t.parameters),
|
|
870
|
+
// Anthropic is the target that DEFINES both of these, and both were dropped while the
|
|
871
|
+
// OpenAI Chat adapter already forwarded strict (#5210). Only an explicit `true` is
|
|
872
|
+
// emitted: the Messages inbound records an absent strict as `false`, so a false here
|
|
873
|
+
// cannot be distinguished from silence and must not become an opt-out on the wire.
|
|
874
|
+
...(t.strict === true ? { strict: true } : {}),
|
|
875
|
+
...(t.allowedCallers !== undefined ? { allowed_callers: [...t.allowedCallers] } : {}),
|
|
860
876
|
}));
|
|
861
877
|
return converted;
|
|
862
878
|
}
|
|
@@ -945,7 +961,10 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
945
961
|
// Primary image layer: resize/re-encode to fit Anthropic limits without dropping
|
|
946
962
|
// (anthropic-image-normalize.ts); the guard below remains the deterministic backstop.
|
|
947
963
|
// imageTierBias > 0 = upstream-413 tightened retry (030): start every image one tier lower.
|
|
948
|
-
await normalizeAnthropicImages(messages, {
|
|
964
|
+
await normalizeAnthropicImages(messages, {
|
|
965
|
+
tierBias: incoming?.imageTierBias ?? 0,
|
|
966
|
+
abortSignal: incoming?.abortSignal,
|
|
967
|
+
});
|
|
949
968
|
// Anthropic rejects many-image requests (>20 images) carrying any image over
|
|
950
969
|
// 2000px per side; see anthropic-image-guard.ts for the full limit policy.
|
|
951
970
|
enforceAnthropicImageLimits(messages);
|
package/src/adapters/base.ts
CHANGED
|
@@ -31,11 +31,34 @@ export interface IncomingMeta {
|
|
|
31
31
|
* behind it (#4546).
|
|
32
32
|
*/
|
|
33
33
|
sendBudget?: RequestExecutionBudget;
|
|
34
|
+
/**
|
|
35
|
+
* Physical-send observations for runTurn adapters. Without the same callback carried by
|
|
36
|
+
* AdapterFetchContext, an adapter-owned replay spends the shared budget but remains absent
|
|
37
|
+
* from the request's sendCount.
|
|
38
|
+
*/
|
|
39
|
+
onPhysicalSend?: (send: { ordinal: number; recovery?: AttemptRecoveryKind }) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Recovery refusals for runTurn adapters. A refused replay is not a send, so this separate
|
|
42
|
+
* channel explains why recovery stopped without inflating physical-send telemetry.
|
|
43
|
+
*/
|
|
44
|
+
onRecoveryWithheld?: (withheld: { reason: AttemptRecoveryWithheld }) => void;
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
export interface ProviderAdapter {
|
|
37
48
|
name: string;
|
|
38
49
|
|
|
50
|
+
/**
|
|
51
|
+
* This adapter reports every physical inference send through `IncomingMeta.onPhysicalSend`,
|
|
52
|
+
* including its first.
|
|
53
|
+
*
|
|
54
|
+
* The caller normally logs the first send before handing control over, which is correct for a
|
|
55
|
+
* transport whose sends it can see. An adapter that admits its own sends through the shared
|
|
56
|
+
* budget can have that first send refused, and a send logged before admission is a send the
|
|
57
|
+
* log claims and the wire never made. Setting this moves the first send's accounting to the
|
|
58
|
+
* boundary where it is actually dispatched.
|
|
59
|
+
*/
|
|
60
|
+
reportsPhysicalSends?: boolean;
|
|
61
|
+
|
|
39
62
|
/**
|
|
40
63
|
* Convert an already-read provider HTTP error into client-safe text. This hook must be pure and
|
|
41
64
|
* return fully redacted output: callers may pass untrusted provider headers and payload text.
|
|
@@ -21,6 +21,30 @@ export const MAX_STREAM_LINE_BYTES = 8 * 1024 * 1024;
|
|
|
21
21
|
export const MAX_STREAM_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
22
22
|
/** Hard ceiling on projected conversation history text (characters) to prevent runaway memory. */
|
|
23
23
|
export const MAX_PROJECTED_HISTORY_CHARS = 200_000;
|
|
24
|
+
/**
|
|
25
|
+
* Chars-per-token ratio for deriving the projected-history ceiling from the model context
|
|
26
|
+
* window. Sits between the English/code (~4 chars per token) and CJK (~1.5) extremes: the
|
|
27
|
+
* ceiling is a runaway-memory bound and a coarse guard against cutting history the window can
|
|
28
|
+
* hold, not a token accounting - the caller-side compaction line stays the token authority.
|
|
29
|
+
*/
|
|
30
|
+
const PROJECTED_HISTORY_CHARS_PER_TOKEN = 3;
|
|
31
|
+
/** Absolute ceiling on a window-derived history cap, so runaway metadata cannot unbound stdin. */
|
|
32
|
+
const MAX_PROJECTED_HISTORY_DERIVED_CHARS = 4_000_000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Projected-history character ceiling for a turn, derived from the declared model context
|
|
36
|
+
* window. A missing or non-finite window keeps the legacy flat cap, and the derivation never
|
|
37
|
+
* lowers the cap below it: small windows change nothing, while large windows scale (a 1M-token
|
|
38
|
+
* model keeps 3M characters) until the hard ceiling. The flat 200k cap predates window
|
|
39
|
+
* metadata and cut long replays to roughly 50k-130k tokens of content regardless of the model.
|
|
40
|
+
*/
|
|
41
|
+
export function projectedHistoryCharLimit(contextWindowTokens: number | undefined): number {
|
|
42
|
+
if (typeof contextWindowTokens !== "number" || !Number.isFinite(contextWindowTokens) || contextWindowTokens <= 0) {
|
|
43
|
+
return MAX_PROJECTED_HISTORY_CHARS;
|
|
44
|
+
}
|
|
45
|
+
const derived = contextWindowTokens * PROJECTED_HISTORY_CHARS_PER_TOKEN;
|
|
46
|
+
return Math.min(Math.max(derived, MAX_PROJECTED_HISTORY_CHARS), MAX_PROJECTED_HISTORY_DERIVED_CHARS);
|
|
47
|
+
}
|
|
24
48
|
|
|
25
49
|
export class CodingAgentStreamLimitError extends Error {
|
|
26
50
|
constructor(message: string) {
|
|
@@ -308,7 +332,7 @@ function formatMessageForHistory(message: OcxMessage): string {
|
|
|
308
332
|
if (message.role === "user") {
|
|
309
333
|
const text = typeof message.content === "string"
|
|
310
334
|
? message.content
|
|
311
|
-
: message.content.map(p => (p.type === "text" ? p.text : `[${p.type}]`)).join("\n");
|
|
335
|
+
: message.content.map(p => (p.type === "text" || p.type === "document" ? p.text : `[${p.type}]`)).join("\n");
|
|
312
336
|
return `USER:\n${text}`;
|
|
313
337
|
}
|
|
314
338
|
if (message.role === "assistant") {
|
|
@@ -328,7 +352,7 @@ function formatMessageForHistory(message: OcxMessage): string {
|
|
|
328
352
|
if (message.role === "toolResult") {
|
|
329
353
|
const text = typeof message.content === "string"
|
|
330
354
|
? message.content
|
|
331
|
-
: message.content.map(p => (p.type === "text" ? p.text : "[image]")).join("");
|
|
355
|
+
: message.content.map(p => (p.type === "text" || p.type === "document" ? p.text : "[image]")).join("");
|
|
332
356
|
const status = message.isError ? " (error)" : "";
|
|
333
357
|
return `TOOL RESULT (call_id: ${message.toolCallId})${status}:\n${text}`;
|
|
334
358
|
}
|
|
@@ -355,6 +379,8 @@ export function buildInputLines(message: OcxMessage): string[] {
|
|
|
355
379
|
else if (part.type === "image") {
|
|
356
380
|
const image = imagePart(part.imageUrl);
|
|
357
381
|
if (image) content.push(image);
|
|
382
|
+
} else if (part.type === "document") {
|
|
383
|
+
content.push(textPart(part.text));
|
|
358
384
|
} else {
|
|
359
385
|
content.push(textPart("[video]"));
|
|
360
386
|
}
|
|
@@ -378,7 +404,7 @@ export function buildSystemPrompt(parsed: OcxParsedRequest): string | undefined
|
|
|
378
404
|
if (message.role !== "developer") continue;
|
|
379
405
|
const text = typeof message.content === "string"
|
|
380
406
|
? message.content
|
|
381
|
-
: message.content.map(part => (part.type === "text" ? part.text : "")).join("");
|
|
407
|
+
: message.content.map(part => (part.type === "text" || part.type === "document" ? part.text : "")).join("");
|
|
382
408
|
if (text.trim()) parts.push(text);
|
|
383
409
|
}
|
|
384
410
|
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
@@ -395,7 +421,7 @@ export function buildSystemPrompt(parsed: OcxParsedRequest): string | undefined
|
|
|
395
421
|
* prior conversation turns are structured as bounded context text with tool results as text,
|
|
396
422
|
* clearly demarcated from the current user request. Codex retains tool control; vendor tools are never invoked.
|
|
397
423
|
*/
|
|
398
|
-
export function buildConversationInput(parsed: OcxParsedRequest): string[] {
|
|
424
|
+
export function buildConversationInput(parsed: OcxParsedRequest, options: { maxHistoryChars?: number } = {}): string[] {
|
|
399
425
|
const nonDev = parsed.context.messages.filter(m => m.role !== "developer");
|
|
400
426
|
if (nonDev.length === 0) {
|
|
401
427
|
return [JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text: "" }] } })];
|
|
@@ -440,6 +466,8 @@ export function buildConversationInput(parsed: OcxParsedRequest): string[] {
|
|
|
440
466
|
const image = imagePart(part.imageUrl);
|
|
441
467
|
if (image) currentImageBlocks.push(image);
|
|
442
468
|
else textParts.push("[image omitted: unsupported reference]");
|
|
469
|
+
} else if (part.type === "document") {
|
|
470
|
+
textParts.push(part.text);
|
|
443
471
|
} else {
|
|
444
472
|
textParts.push("[video]");
|
|
445
473
|
}
|
|
@@ -463,6 +491,7 @@ export function buildConversationInput(parsed: OcxParsedRequest): string[] {
|
|
|
463
491
|
else segments.push("[image omitted: unsupported reference]");
|
|
464
492
|
continue;
|
|
465
493
|
}
|
|
494
|
+
if (part.type === "document") { segments.push(part.text); continue; }
|
|
466
495
|
segments.push("[video]");
|
|
467
496
|
}
|
|
468
497
|
text = segments.join("");
|
|
@@ -475,10 +504,11 @@ export function buildConversationInput(parsed: OcxParsedRequest): string[] {
|
|
|
475
504
|
|
|
476
505
|
const imageBlocks: WireContentPart[] = [...historyImageBlocks, ...currentImageBlocks];
|
|
477
506
|
|
|
507
|
+
const maxHistoryChars = options.maxHistoryChars ?? MAX_PROJECTED_HISTORY_CHARS;
|
|
478
508
|
let historyText = historyMessages.map(formatMessageForHistory).filter(Boolean).join("\n\n");
|
|
479
|
-
if (historyText.length >
|
|
509
|
+
if (historyText.length > maxHistoryChars) {
|
|
480
510
|
historyText = `[Earlier conversation history truncated for length...]\n\n` +
|
|
481
|
-
historyText.slice(historyText.length -
|
|
511
|
+
historyText.slice(historyText.length - maxHistoryChars);
|
|
482
512
|
}
|
|
483
513
|
|
|
484
514
|
const combinedText = `Prior conversation context:\n\n${historyText}\n\nCurrent user request:\n\n${currentRequestText}`;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { spawn as nodeSpawn, type ChildProcess, type SpawnOptions } from "node:child_process";
|
|
1
|
+
import { execFileSync, spawn as nodeSpawn, type ChildProcess, type SpawnOptions } from "node:child_process";
|
|
2
2
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
3
3
|
import { commandInvocation } from "../../lib/win-exec";
|
|
4
|
+
import { modelRecordValue } from "../../reasoning-effort";
|
|
4
5
|
import type { IncomingMeta } from "../base";
|
|
5
|
-
import { buildConversationInput, CodingAgentProtocolError, mapStreamMessageToEvents, readJsonLines, type StreamParseState } from "./protocol";
|
|
6
|
+
import { buildConversationInput, CodingAgentProtocolError, mapStreamMessageToEvents, projectedHistoryCharLimit, readJsonLines, type StreamParseState } from "./protocol";
|
|
6
7
|
import { resolveCodingAgentBinary, resolveProfileByBaseUrl, type CodingAgentProviderProfile, type WhichFn } from "./profile";
|
|
7
8
|
|
|
8
9
|
/** Injectable spawn for tests; production uses node:child_process. */
|
|
9
10
|
export type SpawnFn = (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
|
|
10
11
|
|
|
12
|
+
/** Injectable Windows process-tree terminator; production uses taskkill /T /F. */
|
|
13
|
+
export type KillWindowsProcessTreeFn = (pid: number) => void;
|
|
14
|
+
|
|
11
15
|
/** Per-turn injectables: spawn/which seams for tests plus wall-clock ceilings for timeout, kill grace, and bounded reap. */
|
|
12
16
|
export interface CodingAgentDeps {
|
|
13
17
|
spawn?: SpawnFn;
|
|
@@ -20,6 +24,8 @@ export interface CodingAgentDeps {
|
|
|
20
24
|
reapTimeoutMs?: number;
|
|
21
25
|
/** Test seam for Windows command-shim invocation. */
|
|
22
26
|
platform?: NodeJS.Platform;
|
|
27
|
+
/** Test seam for terminating a Windows CLI and all descendants. */
|
|
28
|
+
killWindowsProcessTree?: KillWindowsProcessTreeFn;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
@@ -27,6 +33,14 @@ const DEFAULT_KILL_GRACE_MS = 2_000;
|
|
|
27
33
|
/** Bound captured stderr so an error message can never carry an unbounded (or secret) payload. */
|
|
28
34
|
const MAX_STDERR_BYTES = 8 * 1024;
|
|
29
35
|
|
|
36
|
+
function killWindowsProcessTree(pid: number): void {
|
|
37
|
+
const taskkill = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\taskkill.exe`;
|
|
38
|
+
execFileSync(taskkill, ["/PID", String(pid), "/T", "/F"], {
|
|
39
|
+
stdio: "pipe",
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
/** Env keys a CLI needs to run; everything else is dropped so the child env is scoped and deterministic. */
|
|
31
45
|
const INHERITED_ENV_KEYS = [
|
|
32
46
|
"PATH", "HOME", "USERPROFILE", "LANG", "LC_ALL", "LC_CTYPE", "TMPDIR", "TEMP", "TMP",
|
|
@@ -93,6 +107,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
93
107
|
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
94
108
|
const killGraceMs = deps.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
95
109
|
const reapTimeoutMs = deps.reapTimeoutMs ?? (killGraceMs * 2 + 250);
|
|
110
|
+
const platform = deps.platform ?? process.platform;
|
|
96
111
|
|
|
97
112
|
if (incoming.abortSignal?.aborted) {
|
|
98
113
|
emit({ type: "error", message: "Coding-agent turn was aborted before start." });
|
|
@@ -140,7 +155,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
140
155
|
|
|
141
156
|
const args = buildArgs(profile, parsed, provider);
|
|
142
157
|
const env = buildEnv(profile, apiKey);
|
|
143
|
-
const invocation = commandInvocation(binary, args,
|
|
158
|
+
const invocation = commandInvocation(binary, args, platform, { env });
|
|
144
159
|
|
|
145
160
|
let child: ChildProcess;
|
|
146
161
|
try {
|
|
@@ -197,6 +212,12 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
197
212
|
const kill = (): void => {
|
|
198
213
|
if (killed || child.killed) return;
|
|
199
214
|
killed = true;
|
|
215
|
+
if (platform === "win32" && child.pid !== undefined) {
|
|
216
|
+
try {
|
|
217
|
+
(deps.killWindowsProcessTree ?? killWindowsProcessTree)(child.pid);
|
|
218
|
+
return;
|
|
219
|
+
} catch { /* fall back to terminating the direct child */ }
|
|
220
|
+
}
|
|
200
221
|
try { child.kill("SIGTERM"); } catch { /* already gone */ }
|
|
201
222
|
killTimer = setTimeout(() => {
|
|
202
223
|
try { child.kill("SIGKILL"); } catch { /* already gone */ }
|
|
@@ -244,7 +265,14 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
244
265
|
const stdin = child.stdin;
|
|
245
266
|
if (stdin) {
|
|
246
267
|
stdin.on("error", () => { /* EPIPE if the CLI exits early; surfaced via close/stderr */ });
|
|
247
|
-
|
|
268
|
+
// The projected history scales with the model context window on the routed provider row
|
|
269
|
+
// (catalog and config metadata merged): a 1M-token model keeps 3M characters of replay
|
|
270
|
+
// where the flat cap cut it near 50k-130k tokens of content. Absent metadata keeps the
|
|
271
|
+
// flat cap.
|
|
272
|
+
const historyCharLimit = projectedHistoryCharLimit(
|
|
273
|
+
modelRecordValue(provider.modelContextWindows, parsed.modelId) ?? provider.contextWindow,
|
|
274
|
+
);
|
|
275
|
+
for (const line of buildConversationInput(parsed, { maxHistoryChars: historyCharLimit })) stdin.write(`${line}\n`);
|
|
248
276
|
stdin.end();
|
|
249
277
|
}
|
|
250
278
|
const stdout = child.stdout;
|
|
@@ -8,7 +8,7 @@ import type { AdapterFetchContext, AdapterRequest, ProviderAdapter } from "./bas
|
|
|
8
8
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
9
9
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
10
10
|
import { debugDroppedFrame } from "../lib/debug";
|
|
11
|
-
import { configuredReasoningEfforts } from "../reasoning-effort";
|
|
11
|
+
import { configuredReasoningEfforts, modelRecordValue } from "../reasoning-effort";
|
|
12
12
|
import { commandCodeReasoningEfforts, refreshCommandCodeReasoningEfforts } from "../providers/command-code-efforts";
|
|
13
13
|
import { identifyRoutedModel } from "./identity";
|
|
14
14
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
@@ -32,7 +32,7 @@ function canonicalCommandCodeModelId(modelId: string): string {
|
|
|
32
32
|
/** Flatten tool-result content for the text-only wire output, keeping an `[image]` marker per image part in content order. */
|
|
33
33
|
function toolResultText(content: string | OcxContentPart[]): string {
|
|
34
34
|
if (typeof content === "string") return content;
|
|
35
|
-
return content.map(part => (part.type === "text" ? part.text : "[image]")).join("");
|
|
35
|
+
return content.map(part => (part.type === "text" || part.type === "document" ? part.text : "[image]")).join("");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Best-effort media type from a remote https URL extension, e.g. image/png. */
|
|
@@ -149,6 +149,7 @@ function wireMessages(messages: OcxMessage[]): Array<Record<string, unknown>> {
|
|
|
149
149
|
else for (const part of message.content) {
|
|
150
150
|
if (part.type === "text") content.push({ type: "text", text: part.text });
|
|
151
151
|
else if (part.type === "image") content.push(wireImagePart(part.imageUrl));
|
|
152
|
+
else if (part.type === "document") content.push({ type: "text", text: part.text });
|
|
152
153
|
else content.push({ type: "text", text: "[video]" });
|
|
153
154
|
}
|
|
154
155
|
out.push({ role: "user", content });
|
|
@@ -483,13 +484,51 @@ async function fetchCommandCode(request: AdapterRequest, ctx: AdapterFetchContex
|
|
|
483
484
|
}
|
|
484
485
|
}
|
|
485
486
|
|
|
487
|
+
/**
|
|
488
|
+
* Has the operator declared their own ladders authoritative for this provider?
|
|
489
|
+
*
|
|
490
|
+
* Comparing a configured row against the shipped table cannot answer this. `providerConfigSeed`
|
|
491
|
+
* copies the whole table into every materialized preset, and both enrichment and routing keep a
|
|
492
|
+
* persisted row over the current seed, so a row written by an older release keeps its old value
|
|
493
|
+
* and starts LOOKING like an operator edit the moment the shipped table is corrected — at which
|
|
494
|
+
* point the stale row would outrank the correction and disable the rejection repair below.
|
|
495
|
+
* Provenance has to be declared rather than inferred, so it is: `providerConfigSeed` never
|
|
496
|
+
* writes this flag, which makes its presence something only a human can have caused.
|
|
497
|
+
*/
|
|
498
|
+
function operatorChoseCommandCodeLadder(provider: OcxProviderConfig, canonicalId: string): boolean {
|
|
499
|
+
if (provider.modelReasoningEffortsAuthoritative !== true) return false;
|
|
500
|
+
return modelRecordValue(provider.modelReasoningEfforts, canonicalId) !== undefined;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Resolve the ladder this request may draw a wire effort from.
|
|
505
|
+
*
|
|
506
|
+
* The shipped table is a default, not a ceiling the operator cannot reach past. Until this
|
|
507
|
+
* existed the adapter read `commandCodeReasoningEfforts() ?? configuredReasoningEfforts()`, so a
|
|
508
|
+
* model with a row ignored configuration outright while a model without one honoured it — and
|
|
509
|
+
* the catalog disagreed with both, because `configuredReasoningEfforts` is what advertises the
|
|
510
|
+
* picker. An operator who widened a pinned row saw the wider ladder offered in Codex and then
|
|
511
|
+
* watched the adapter strip the rung on the way out (#5096).
|
|
512
|
+
*
|
|
513
|
+
* An operator who sets `modelReasoningEffortsAuthoritative` now resolves through the same
|
|
514
|
+
* function the catalog uses, so the picker and the wire agree, and sanitization, tier healing and
|
|
515
|
+
* learned-refusal dropping apply to it. Every other provider keeps the shipped table, including a
|
|
516
|
+
* value learned by a profile refresh.
|
|
517
|
+
*/
|
|
518
|
+
function commandCodeEffortLadder(provider: OcxProviderConfig, canonicalId: string): readonly string[] | undefined {
|
|
519
|
+
if (operatorChoseCommandCodeLadder(provider, canonicalId)) {
|
|
520
|
+
return configuredReasoningEfforts(provider, canonicalId);
|
|
521
|
+
}
|
|
522
|
+
return commandCodeReasoningEfforts(canonicalId) ?? configuredReasoningEfforts(provider, canonicalId);
|
|
523
|
+
}
|
|
524
|
+
|
|
486
525
|
function supportedCommandCodeEffort(provider: OcxProviderConfig, modelId: string, requested: string | undefined): string | undefined {
|
|
487
526
|
if (!requested || requested === "none") return undefined;
|
|
488
527
|
// Compatibility ids (deepseek-v4-flash / glm-5.2) must resolve to their canonical
|
|
489
528
|
// Command Code id before the effort lookup, or legacy requests silently lose the
|
|
490
529
|
// reasoning effort because the official table is keyed by the canonical ids.
|
|
491
530
|
const canonicalId = canonicalCommandCodeModelId(modelId);
|
|
492
|
-
const supported =
|
|
531
|
+
const supported = commandCodeEffortLadder(provider, canonicalId);
|
|
493
532
|
if (!supported) return undefined;
|
|
494
533
|
// Only remap xhigh/ultra→max for models whose official profile documents that
|
|
495
534
|
// aliasing (deepseek v4, glm-5.2). Muse Spark's upstream accepts xhigh as a
|
|
@@ -501,7 +540,11 @@ function supportedCommandCodeEffort(provider: OcxProviderConfig, modelId: string
|
|
|
501
540
|
lower === "zai-org/glm-5.2";
|
|
502
541
|
if (requested === "xhigh" && !supported.includes("xhigh") && supported.includes("max")) {
|
|
503
542
|
wire = "max";
|
|
504
|
-
} else if (requested === "ultra" && needsAlias && supported.includes("max")) {
|
|
543
|
+
} else if (requested === "ultra" && needsAlias && !supported.includes("ultra") && supported.includes("max")) {
|
|
544
|
+
// The xhigh branch above already refuses to alias a rung the ladder advertises; ultra has to
|
|
545
|
+
// match it. No shipped row offers ultra, so this changes nothing for the built-in table — but
|
|
546
|
+
// an authoritative operator ladder that does offer it would otherwise advertise ultra in the
|
|
547
|
+
// picker and quietly send max, which is the catalog/wire disagreement this file just fixed.
|
|
505
548
|
wire = "max";
|
|
506
549
|
}
|
|
507
550
|
return (supported as readonly string[]).includes(wire) ? wire : undefined;
|
|
@@ -576,6 +619,11 @@ export function createCommandCodeAdapter(provider: OcxProviderConfig): ProviderA
|
|
|
576
619
|
try { return (JSON.parse(request.body) as { params?: { model?: unknown } }).params?.model; } catch { return undefined; }
|
|
577
620
|
})();
|
|
578
621
|
if (typeof modelId !== "string") return response;
|
|
622
|
+
// An operator who wrote this ladder authorized the rung deliberately. Replaying the turn
|
|
623
|
+
// without it would answer at the provider default and hide a wrong configuration behind a
|
|
624
|
+
// successful-looking response, so the upstream rejection is what the caller gets. The
|
|
625
|
+
// downgrade below stays for the shipped table, where the rung was never the caller's idea.
|
|
626
|
+
if (operatorChoseCommandCodeLadder(provider, canonicalCommandCodeModelId(modelId))) return response;
|
|
579
627
|
const refreshed = await refreshCommandCodeReasoningEfforts(modelId, executor);
|
|
580
628
|
if (!refreshed || refreshed.includes(currentEffort)) return response;
|
|
581
629
|
const retry = requestWithoutReasoningEffort(request);
|