@iislee/opencodex 2.11.0 → 2.35.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/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +622 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMeta, ProviderAdapter } from "./base";
|
|
2
|
+
import { createToolCallIdAllocator, type ToolCallIdAllocator } from "./tool-call-id";
|
|
2
3
|
import { debugDroppedFrame } from "../lib/debug";
|
|
3
4
|
import type {
|
|
4
5
|
AdapterEvent,
|
|
@@ -13,16 +14,20 @@ import type {
|
|
|
13
14
|
OcxToolResultMessage,
|
|
14
15
|
OcxUsage,
|
|
15
16
|
} from "../types";
|
|
16
|
-
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName,
|
|
17
|
+
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
|
|
17
18
|
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION, applyClaudeToolPrefix, stripClaudeToolPrefix } from "../oauth/anthropic";
|
|
18
19
|
import { parseDataUrl } from "./image";
|
|
19
20
|
import { enforceAnthropicImageLimits } from "./anthropic-image-guard";
|
|
20
21
|
import { normalizeAnthropicImages } from "./anthropic-image-normalize";
|
|
21
|
-
import {
|
|
22
|
+
import { normalizeAnthropicOutputSchema } from "./anthropic-output-schema";
|
|
23
|
+
import { stripResponsesOnlyEncryptedMarker } from "./responses-tool-schema";
|
|
24
|
+
import { identifyRoutedModel } from "./identity";
|
|
25
|
+
import { redactSecretString } from "../lib/redact";
|
|
22
26
|
import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "./client-fingerprint";
|
|
23
27
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
24
28
|
import { decodeServerSentEvents } from "../lib/sse-decoder";
|
|
25
29
|
import { isTranslatorBudgetExceededError, retainTranslatedEventBatch, type TranslatorBudget } from "../lib/translator-budget";
|
|
30
|
+
import { isReasoningEffortOmitted, modelRecordValue } from "../reasoning-effort";
|
|
26
31
|
|
|
27
32
|
/** Map a user content part to an Anthropic content block (text or image source). */
|
|
28
33
|
function toAnthropicContentPart(p: OcxContentPart): unknown {
|
|
@@ -32,6 +37,7 @@ function toAnthropicContentPart(p: OcxContentPart): unknown {
|
|
|
32
37
|
? { type: "image", source: { type: "base64", media_type: data.mediaType, data: data.base64 } }
|
|
33
38
|
: { type: "image", source: { type: "url", url: p.imageUrl } };
|
|
34
39
|
}
|
|
40
|
+
if (p.type === "video") return { type: "text", text: "[video]" };
|
|
35
41
|
return { type: "text", text: p.text };
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -243,6 +249,76 @@ function isLikelyRealAnthropicThinkingSignature(signature: string | undefined):
|
|
|
243
249
|
return /^[A-Za-z0-9+/_=-]+$/.test(signature);
|
|
244
250
|
}
|
|
245
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Bridge error fidelity (web-search/images loops): extract a display-safe summary from an
|
|
254
|
+
* Anthropic JSON error envelope so `Provider error <status>` carries the upstream reason.
|
|
255
|
+
* JSON-only extraction — HTML/non-JSON bodies yield "" so raw markup is never echoed.
|
|
256
|
+
*/
|
|
257
|
+
export function formatAnthropicErrorBody(status: number, _headers: Headers, payloadText: string): string {
|
|
258
|
+
let parsed: unknown;
|
|
259
|
+
try {
|
|
260
|
+
parsed = JSON.parse(payloadText);
|
|
261
|
+
} catch {
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
const detail = extractAnthropicErrorDetail(parsed);
|
|
265
|
+
if (!detail) return "";
|
|
266
|
+
return redactSecretString(detail).slice(0, 400);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isAnthropicRecord(value: unknown): value is Record<string, unknown> {
|
|
270
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function anthropicStructuralValueType(value: unknown): string {
|
|
274
|
+
if (value === null) return "null";
|
|
275
|
+
return Array.isArray(value) ? "array" : typeof value;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface InvalidAnthropicShapeDiagnostic {
|
|
279
|
+
reason: "content_not_array" | "content_block_not_object";
|
|
280
|
+
blockIndex?: number;
|
|
281
|
+
valueType: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Structured refusal for a malformed buffered response body, shaped like the google adapter's
|
|
286
|
+
* `invalidGoogleShapeEvent` so an operator reading a log can tell which rung failed: the content
|
|
287
|
+
* container, or one block inside an otherwise well-formed container.
|
|
288
|
+
*/
|
|
289
|
+
function invalidAnthropicShapeEvent(
|
|
290
|
+
diagnostic: InvalidAnthropicShapeDiagnostic,
|
|
291
|
+
): Extract<AdapterEvent, { type: "error" }> {
|
|
292
|
+
const at = diagnostic.blockIndex !== undefined ? `; blockIndex=${diagnostic.blockIndex}` : "";
|
|
293
|
+
const subject = diagnostic.reason === "content_not_array" ? "content" : "content block";
|
|
294
|
+
return {
|
|
295
|
+
type: "error",
|
|
296
|
+
message: `anthropic response contained invalid ${subject} (${diagnostic.reason}${at}; valueType=${diagnostic.valueType})`,
|
|
297
|
+
status: 502,
|
|
298
|
+
errorType: "upstream_error",
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function extractAnthropicErrorDetail(parsed: unknown): string | undefined {
|
|
303
|
+
if (typeof parsed === "string") return parsed.trim() || undefined;
|
|
304
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
305
|
+
const obj = parsed as Record<string, unknown>;
|
|
306
|
+
// Anthropic envelope: { type: "error", error: { type, message } }; tolerate a bare
|
|
307
|
+
// { error: { message } } and a string error field.
|
|
308
|
+
const err = obj.error;
|
|
309
|
+
if (typeof err === "string" && err.trim()) return err.trim();
|
|
310
|
+
if (err !== null && typeof err === "object" && !Array.isArray(err)) {
|
|
311
|
+
const e = err as Record<string, unknown>;
|
|
312
|
+
const msg = e.message;
|
|
313
|
+
if (typeof msg !== "string" || !msg.trim()) return undefined;
|
|
314
|
+
const type = e.type;
|
|
315
|
+
return typeof type === "string" && type.trim()
|
|
316
|
+
? `${type.trim()}: ${msg.trim()}`
|
|
317
|
+
: msg.trim();
|
|
318
|
+
}
|
|
319
|
+
return undefined;
|
|
320
|
+
}
|
|
321
|
+
|
|
246
322
|
function usesNativeAnthropicEndpoint(provider: OcxProviderConfig): boolean {
|
|
247
323
|
try {
|
|
248
324
|
return new URL(provider.baseUrl).hostname === "api.anthropic.com";
|
|
@@ -277,7 +353,69 @@ function usableToolUseId(id: unknown): string {
|
|
|
277
353
|
return typeof id === "string" && id.trim() ? id : synthesizeToolUseId();
|
|
278
354
|
}
|
|
279
355
|
|
|
280
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Bound repair for a malformed tool-arguments string under the compatibility profile (#658):
|
|
358
|
+
* a gateway such as AgentRouter can concatenate JSON objects (`{}{"value":42}`). Find the
|
|
359
|
+
* last parseable JSON object by scanning suffixes from each object-open brace and prefixes
|
|
360
|
+
* ending at each object-close brace. Both scans walk backwards from the end trying at most
|
|
361
|
+
* `maxCandidates` positions, so no offset index is ever materialized: a brace-dense hostile
|
|
362
|
+
* input costs at most 2 × maxCandidates bounded JSON.parse attempts and no extra storage.
|
|
363
|
+
* Inputs above MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES are not repaired at all.
|
|
364
|
+
*/
|
|
365
|
+
const MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES = 1024 * 1024;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Whether `input` encodes to more than `max` UTF-8 bytes, with an early exit so the check
|
|
369
|
+
* itself never allocates a copy of a hostile string. `string.length` counts UTF-16 code
|
|
370
|
+
* units, which undercounts astral text by 2x against a byte budget.
|
|
371
|
+
*/
|
|
372
|
+
function utf8BytesExceed(input: string, max: number): boolean {
|
|
373
|
+
let bytes = 0;
|
|
374
|
+
for (let i = 0; i < input.length; i++) {
|
|
375
|
+
const code = input.charCodeAt(i);
|
|
376
|
+
if (code < 0x80) bytes += 1;
|
|
377
|
+
else if (code < 0x800) bytes += 2;
|
|
378
|
+
else if (code >= 0xd800 && code <= 0xdbff && i + 1 < input.length
|
|
379
|
+
&& input.charCodeAt(i + 1) >= 0xdc00 && input.charCodeAt(i + 1) <= 0xdfff) {
|
|
380
|
+
// A complete surrogate pair is one 4-byte scalar. Anything else — a high surrogate
|
|
381
|
+
// followed by another high surrogate or a non-surrogate — encodes as two separate
|
|
382
|
+
// U+FFFD replacements, so the next unit must NOT be skipped.
|
|
383
|
+
bytes += 4;
|
|
384
|
+
i++;
|
|
385
|
+
} else bytes += 3; // lone surrogates encode as U+FFFD (3 bytes)
|
|
386
|
+
if (bytes > max) return true;
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function lastValidJsonObject(input: string, maxCandidates: number): string | undefined {
|
|
392
|
+
const tryParseObject = (candidate: string): string | undefined => {
|
|
393
|
+
try {
|
|
394
|
+
const parsed = JSON.parse(candidate) as unknown;
|
|
395
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return candidate;
|
|
396
|
+
} catch { /* keep scanning */ }
|
|
397
|
+
return undefined;
|
|
398
|
+
};
|
|
399
|
+
let scanFrom = input.length - 1;
|
|
400
|
+
for (let tried = 0; tried < maxCandidates && scanFrom >= 0; tried++) {
|
|
401
|
+
const open = input.lastIndexOf("{", scanFrom);
|
|
402
|
+
if (open === -1) break;
|
|
403
|
+
const repaired = tryParseObject(input.slice(open));
|
|
404
|
+
if (repaired !== undefined) return repaired;
|
|
405
|
+
scanFrom = open - 1;
|
|
406
|
+
}
|
|
407
|
+
scanFrom = input.length - 1;
|
|
408
|
+
for (let tried = 0; tried < maxCandidates && scanFrom >= 0; tried++) {
|
|
409
|
+
const close = input.lastIndexOf("}", scanFrom);
|
|
410
|
+
if (close === -1) break;
|
|
411
|
+
const repaired = tryParseObject(input.slice(0, close + 1));
|
|
412
|
+
if (repaired !== undefined) return repaired;
|
|
413
|
+
scanFrom = close - 1;
|
|
414
|
+
}
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function toolUseArguments(input: unknown, lenient = false): string {
|
|
281
419
|
if (typeof input === "string") {
|
|
282
420
|
const trimmed = input.trim();
|
|
283
421
|
if (!trimmed) return "{}";
|
|
@@ -285,6 +423,10 @@ function toolUseArguments(input: unknown): string {
|
|
|
285
423
|
JSON.parse(trimmed);
|
|
286
424
|
return trimmed;
|
|
287
425
|
} catch {
|
|
426
|
+
if (lenient && !utf8BytesExceed(trimmed, MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES)) {
|
|
427
|
+
const repaired = lastValidJsonObject(trimmed, 32);
|
|
428
|
+
if (repaired !== undefined) return repaired;
|
|
429
|
+
}
|
|
288
430
|
// A tool call's arguments must be a JSON object. Re-encoding an unparseable string as a
|
|
289
431
|
// JSON *string* is the double-encoding #765 reports: the caller then receives
|
|
290
432
|
// `"get weather"` where an object was required and the tool call is unusable either way.
|
|
@@ -342,16 +484,68 @@ const ADAPTIVE_THINKING_FAMILY_MINIMUMS: Record<string, readonly [major: number,
|
|
|
342
484
|
fable: [0, 0],
|
|
343
485
|
};
|
|
344
486
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
487
|
+
/**
|
|
488
|
+
* Family/version parse for a Claude model id, tolerant of a routing prefix.
|
|
489
|
+
*
|
|
490
|
+
* `parsed.modelId` is not always bare, and the slash can fall on either side.
|
|
491
|
+
* A `modelMap` entry may point at a routed destination such as
|
|
492
|
+
* `anthropic/claude-sonnet-5` (prefix), while a custom provider may expose a
|
|
493
|
+
* native id such as `claude-sonnet-5/variant` (suffix); both survive routing's
|
|
494
|
+
* known-id decoding. So this matches the segment that actually begins with
|
|
495
|
+
* `claude-` rather than assuming it is the first or the last one. A capability
|
|
496
|
+
* predicate that quietly returns false is worse than one that throws — the
|
|
497
|
+
* request just goes out wrong.
|
|
498
|
+
*
|
|
499
|
+
* Minor is 1-2 digits with a non-digit lookahead so date-pinned ids
|
|
500
|
+
* ("claude-opus-4-20250514") parse as minor 0 instead of minor 20250514;
|
|
501
|
+
* suffixed ids ("claude-opus-4-8[1m]") still match.
|
|
502
|
+
*/
|
|
503
|
+
function claudeFamilyVersion(modelId: string): { family: string; major: number; minor: number } | undefined {
|
|
504
|
+
// Find the segment that actually starts with `claude-`, rather than assuming it is either
|
|
505
|
+
// the first (breaks `anthropic/claude-sonnet-5`) or the last (breaks `claude-sonnet-5/variant`,
|
|
506
|
+
// where the slash carries a vendor suffix rather than a routing prefix).
|
|
507
|
+
const match = /(?:^|\/)claude-([a-z]+)-(\d+)(?:[.-](\d{1,2}))?(?!\d)/i.exec(modelId);
|
|
508
|
+
if (!match) return undefined;
|
|
509
|
+
return {
|
|
510
|
+
family: match[1]!.toLowerCase(),
|
|
511
|
+
major: Number(match[2]),
|
|
512
|
+
minor: match[3] === undefined ? 0 : Number(match[3]),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function meetsFamilyMinimum(
|
|
517
|
+
modelId: string,
|
|
518
|
+
minimums: Record<string, readonly [major: number, minor: number]>,
|
|
519
|
+
): boolean {
|
|
520
|
+
const parsed = claudeFamilyVersion(modelId);
|
|
521
|
+
if (!parsed) return false;
|
|
522
|
+
const minimum = minimums[parsed.family];
|
|
351
523
|
if (!minimum) return false;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
524
|
+
return parsed.major > minimum[0] || (parsed.major === minimum[0] && parsed.minor >= minimum[1]);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function usesAdaptiveThinking(modelId: string): boolean {
|
|
528
|
+
return meetsFamilyMinimum(modelId, ADAPTIVE_THINKING_FAMILY_MINIMUMS);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Claude families that (a) think by DEFAULT when the request omits `thinking`,
|
|
533
|
+
* and (b) accept an explicit `thinking: {type: "disabled"}` to turn it off.
|
|
534
|
+
*
|
|
535
|
+
* Deliberately NOT `usesAdaptiveThinking()`, which answers a different question
|
|
536
|
+
* (which wire shape a family accepts). The two sets differ in both directions:
|
|
537
|
+
* Fable always thinks and REJECTS an explicit disable, while Opus 4.7/4.8 use
|
|
538
|
+
* the adaptive wire but leave thinking off when the field is omitted, so they
|
|
539
|
+
* need no disable at all. Seeded with the family where the defect reproduces
|
|
540
|
+
* (#545); widen only with vendor evidence, since a wrong entry here turns a
|
|
541
|
+
* silent truncation into a 400.
|
|
542
|
+
*/
|
|
543
|
+
const EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS: Record<string, readonly [major: number, minor: number]> = {
|
|
544
|
+
sonnet: [5, 0],
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
function supportsExplicitThinkingDisable(modelId: string): boolean {
|
|
548
|
+
return meetsFamilyMinimum(modelId, EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS);
|
|
355
549
|
}
|
|
356
550
|
|
|
357
551
|
/** `output_config.effort` accepts low|medium|high|xhigh|max — "minimal" is rejected with a 400. */
|
|
@@ -359,6 +553,18 @@ function adaptiveEffort(effort: string): string {
|
|
|
359
553
|
return effort === "minimal" ? "low" : effort;
|
|
360
554
|
}
|
|
361
555
|
|
|
556
|
+
function defaultReasoningEffort(provider: OcxProviderConfig, modelId: string): string | undefined {
|
|
557
|
+
const value = modelRecordValue(provider.modelDefaultReasoningEfforts, modelId);
|
|
558
|
+
if (typeof value !== "string") return undefined;
|
|
559
|
+
const trimmed = value.trim();
|
|
560
|
+
// `__omit__` means "send no reasoning field", not "an effort literally named
|
|
561
|
+
// __omit__". Without this the sentinel reached the wire as
|
|
562
|
+
// `output_config.effort: "__omit__"` on adaptive models, and enabled budget
|
|
563
|
+
// thinking on the rest — the opposite of what it asks for (#2432).
|
|
564
|
+
if (!trimmed || isReasoningEffortOmitted(trimmed)) return undefined;
|
|
565
|
+
return trimmed;
|
|
566
|
+
}
|
|
567
|
+
|
|
362
568
|
function usageFromAnthropic(usage: Record<string, number> | undefined): OcxUsage | undefined {
|
|
363
569
|
if (!usage) return undefined;
|
|
364
570
|
const hasCache = usage.cache_read_input_tokens !== undefined || usage.cache_creation_input_tokens !== undefined;
|
|
@@ -401,7 +607,7 @@ function buildToolNameTransforms(provider: OcxProviderConfig): { toWire: (name:
|
|
|
401
607
|
return { toWire: (name) => name, fromWire: (name) => name };
|
|
402
608
|
}
|
|
403
609
|
|
|
404
|
-
function toAnthropicToolResult(msg: OcxToolResultMessage): Record<string, unknown> {
|
|
610
|
+
function toAnthropicToolResult(msg: OcxToolResultMessage, wireCallId: string): Record<string, unknown> {
|
|
405
611
|
// Anthropic tool_result accepts a string OR content blocks — render images natively
|
|
406
612
|
// (e.g. Codex view_image output) instead of dropping them.
|
|
407
613
|
let content: string | unknown[];
|
|
@@ -416,12 +622,17 @@ function toAnthropicToolResult(msg: OcxToolResultMessage): Record<string, unknow
|
|
|
416
622
|
}
|
|
417
623
|
return {
|
|
418
624
|
type: "tool_result",
|
|
419
|
-
tool_use_id:
|
|
625
|
+
tool_use_id: wireCallId,
|
|
420
626
|
content,
|
|
421
627
|
...(msg.isError ? { is_error: true } : {}),
|
|
422
628
|
};
|
|
423
629
|
}
|
|
424
630
|
|
|
631
|
+
function unrepresentableToolCallText(tc: OcxToolCall, wireName: string): string {
|
|
632
|
+
const args = typeof tc.arguments === "string" ? tc.arguments : JSON.stringify(tc.arguments);
|
|
633
|
+
return `[tool_use without a usable id: ${wireName}]\n${args}`;
|
|
634
|
+
}
|
|
635
|
+
|
|
425
636
|
function orphanToolResultText(msg: OcxToolResultMessage): string {
|
|
426
637
|
const label = msg.toolName ? `${msg.toolName} (${msg.toolCallId})` : msg.toolCallId;
|
|
427
638
|
const content = typeof msg.content === "string"
|
|
@@ -430,10 +641,80 @@ function orphanToolResultText(msg: OcxToolResultMessage): string {
|
|
|
430
641
|
return `[tool_result without adjacent tool_use: ${label}]\n${content}`;
|
|
431
642
|
}
|
|
432
643
|
|
|
644
|
+
/**
|
|
645
|
+
* AgentRouter answers 400 `content-blocked` when the first user message is not in English
|
|
646
|
+
* (#2074), while the same request in English returns 200. The gateway is inspecting the opening
|
|
647
|
+
* user content, so an Anthropic `system` string cannot reach it — the framing has to sit in the
|
|
648
|
+
* first user turn.
|
|
649
|
+
*/
|
|
650
|
+
const AGENTROUTER_LANGUAGE_PREAMBLE =
|
|
651
|
+
"[Instruction: Process the user request below and respond in the appropriate language.]";
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Exact host match, not a substring.
|
|
655
|
+
*
|
|
656
|
+
* A `hostname.includes("agentrouter")` test also matches `notagentrouter.example` and
|
|
657
|
+
* `agentrouter.org.attacker.example`, which would let an unrelated destination silently
|
|
658
|
+
* receive an injected instruction block. A prompt mutation keyed on a provider's identity
|
|
659
|
+
* must be keyed on that identity exactly.
|
|
660
|
+
*/
|
|
661
|
+
function isAgentRouterEndpoint(baseUrl: string): boolean {
|
|
662
|
+
try {
|
|
663
|
+
const { hostname } = new URL(baseUrl);
|
|
664
|
+
return hostname === "agentrouter.org" || hostname.endsWith(".agentrouter.org");
|
|
665
|
+
} catch {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Prepend the framing as its OWN text block instead of splicing it into the user's string.
|
|
672
|
+
*
|
|
673
|
+
* The distinction matters: rewriting `content` to `${marker}\n\n${original}` edits what the
|
|
674
|
+
* user wrote, and every downstream consumer — logs, retries, an upstream that echoes the turn —
|
|
675
|
+
* then sees a sentence the user never typed as if they had. A separate leading block carries the
|
|
676
|
+
* same signal to the filter while the original text survives byte-for-byte.
|
|
677
|
+
*
|
|
678
|
+
* Only the first user turn is framed, because only the first is what the gateway rejects.
|
|
679
|
+
*/
|
|
680
|
+
function applyAgentRouterLanguageFraming(messages: unknown[]): void {
|
|
681
|
+
const firstUser = messages.find(
|
|
682
|
+
(m): m is { role: string; content: unknown } =>
|
|
683
|
+
typeof m === "object" && m !== null && (m as { role?: unknown }).role === "user",
|
|
684
|
+
);
|
|
685
|
+
if (!firstUser) return;
|
|
686
|
+
const preamble = { type: "text", text: AGENTROUTER_LANGUAGE_PREAMBLE };
|
|
687
|
+
if (typeof firstUser.content === "string") {
|
|
688
|
+
firstUser.content = firstUser.content === ""
|
|
689
|
+
? [preamble]
|
|
690
|
+
: [preamble, { type: "text", text: firstUser.content }];
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (!Array.isArray(firstUser.content)) return;
|
|
694
|
+
// Idempotence is keyed on the LEADING block being exactly the marker. A substring test would
|
|
695
|
+
// let a user who quotes the marker later in their own prompt suppress the framing entirely.
|
|
696
|
+
const [head] = firstUser.content as { type?: unknown; text?: unknown }[];
|
|
697
|
+
if (head?.type === "text" && head.text === AGENTROUTER_LANGUAGE_PREAMBLE) return;
|
|
698
|
+
(firstUser.content as unknown[]).unshift(preamble);
|
|
699
|
+
}
|
|
700
|
+
|
|
433
701
|
function messagesToAnthropicFormat(
|
|
434
702
|
parsed: OcxParsedRequest,
|
|
435
703
|
toolNames: { toWire: (name: string) => string },
|
|
436
704
|
): { system: string | undefined; messages: unknown[] } {
|
|
705
|
+
// One allocator for the whole request: a tool_result must resolve to the SAME wire id its
|
|
706
|
+
// call got, and two distinct raw ids must never collapse into one. Conforming ids are claimed
|
|
707
|
+
// first so a rewritten id can never squat on an id another call legitimately owns.
|
|
708
|
+
const callIds = createToolCallIdAllocator();
|
|
709
|
+
for (const message of parsed.context.messages) {
|
|
710
|
+
if (message.role === "assistant") {
|
|
711
|
+
for (const part of (message as OcxAssistantMessage).content) {
|
|
712
|
+
if (part.type === "toolCall") callIds.reserve((part as OcxToolCall).id);
|
|
713
|
+
}
|
|
714
|
+
} else if (message.role === "toolResult") {
|
|
715
|
+
callIds.reserve((message as OcxToolResultMessage).toolCallId);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
437
718
|
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(
|
|
438
719
|
parsed.context.tools,
|
|
439
720
|
parsed.options.toolChoice,
|
|
@@ -441,7 +722,7 @@ function messagesToAnthropicFormat(
|
|
|
441
722
|
);
|
|
442
723
|
const systemParts = [...(parsed.context.systemPrompt ?? []), ...(toolCatalogNudge ? [toolCatalogNudge] : [])];
|
|
443
724
|
const system = systemParts.length
|
|
444
|
-
?
|
|
725
|
+
? identifyRoutedModel(systemParts.join("\n\n"), parsed.modelId) || undefined
|
|
445
726
|
: undefined;
|
|
446
727
|
const messages: unknown[] = [];
|
|
447
728
|
|
|
@@ -485,8 +766,17 @@ function messagesToAnthropicFormat(
|
|
|
485
766
|
} else if (part.type === "toolCall") {
|
|
486
767
|
const tc = part as OcxToolCall;
|
|
487
768
|
const flatName = namespacedToolName(tc.namespace, tc.name);
|
|
488
|
-
|
|
489
|
-
|
|
769
|
+
// Normalized here, and identically for the matching tool_result above, so a history
|
|
770
|
+
// replayed from another provider path keeps its call/result pairing (#1767).
|
|
771
|
+
// No raw fallback: restoring an empty/unusable id puts a value on the wire Anthropic
|
|
772
|
+
// rejects. An unrepresentable call becomes text instead, and its result follows it there.
|
|
773
|
+
const wireCallId = callIds.allocate(tc.id);
|
|
774
|
+
if (wireCallId === undefined) {
|
|
775
|
+
preface.push({ type: "text", text: unrepresentableToolCallText(tc, toolNames.toWire(flatName)) });
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
toolUseIds.push(wireCallId);
|
|
779
|
+
toolUses.push({ type: "tool_use", id: wireCallId, name: toolNames.toWire(flatName), input: tc.arguments });
|
|
490
780
|
}
|
|
491
781
|
}
|
|
492
782
|
// Anthropic treats text/thinking after tool_use as ending the tool turn, which makes
|
|
@@ -502,9 +792,13 @@ function messagesToAnthropicFormat(
|
|
|
502
792
|
let j = i + 1;
|
|
503
793
|
while (j < parsed.context.messages.length && parsed.context.messages[j].role === "toolResult") {
|
|
504
794
|
const tr = parsed.context.messages[j] as OcxToolResultMessage;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
795
|
+
// Match on the WIRE id. requiredIds holds normalized ids, so comparing the raw result id
|
|
796
|
+
// made every rewritten pair lose its result to orphan text and gain a synthetic
|
|
797
|
+
// missing-result block. lookup() never mints an id: a result with no call stays orphan.
|
|
798
|
+
const wireResultId = callIds.lookup(tr.toolCallId);
|
|
799
|
+
if (wireResultId !== undefined && requiredIds.has(wireResultId) && !seen.has(wireResultId)) {
|
|
800
|
+
resultBlocks.push(toAnthropicToolResult(tr, wireResultId));
|
|
801
|
+
seen.add(wireResultId);
|
|
508
802
|
} else {
|
|
509
803
|
orphanBlocks.push({ type: "text", text: orphanToolResultText(tr) });
|
|
510
804
|
}
|
|
@@ -549,11 +843,8 @@ function messagesToAnthropicFormat(
|
|
|
549
843
|
|
|
550
844
|
function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (name: string) => string }): unknown[] | undefined {
|
|
551
845
|
if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
|
|
552
|
-
const
|
|
553
|
-
?
|
|
554
|
-
: undefined;
|
|
555
|
-
const tools = allowed
|
|
556
|
-
? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
|
|
846
|
+
const tools = isAllowedToolChoice(parsed.options.toolChoice)
|
|
847
|
+
? parsed.context.tools.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools))
|
|
557
848
|
: parsed.context.tools;
|
|
558
849
|
if (tools.length === 0) return undefined;
|
|
559
850
|
const converted = tools.map(t => ({
|
|
@@ -564,35 +855,8 @@ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (
|
|
|
564
855
|
return converted;
|
|
565
856
|
}
|
|
566
857
|
|
|
567
|
-
// Codex multi-agent v2 stamps a Responses-only `encrypted: true` marker on
|
|
568
|
-
// collaboration tool schemas (openai/codex 5f4d06ef; issue #85). It is an
|
|
569
|
-
// annotation for the ChatGPT backend only. Anthropic input_schema is strict
|
|
570
|
-
// JSON Schema; strip the marker defensively everywhere it can appear as a
|
|
571
|
-
// schema keyword, while preserving properties literally named "encrypted".
|
|
572
|
-
const ENCRYPTED_MARKER_NAME_BAG_KEYS = new Set(["properties", "patternProperties", "$defs", "definitions"]);
|
|
573
|
-
const ENCRYPTED_MARKER_LITERAL_VALUE_KEYS = new Set(["const", "default", "enum", "examples"]);
|
|
574
|
-
|
|
575
|
-
function stripEncryptedMarker(node: unknown, inNameBag = false): unknown {
|
|
576
|
-
if (Array.isArray(node)) return node.map(item => stripEncryptedMarker(item));
|
|
577
|
-
if (!node || typeof node !== "object") return node;
|
|
578
|
-
|
|
579
|
-
const out: Record<string, unknown> = {};
|
|
580
|
-
|
|
581
|
-
for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
|
|
582
|
-
if (inNameBag) {
|
|
583
|
-
out[key] = stripEncryptedMarker(value);
|
|
584
|
-
} else if (key !== "encrypted") {
|
|
585
|
-
out[key] = ENCRYPTED_MARKER_LITERAL_VALUE_KEYS.has(key)
|
|
586
|
-
? value
|
|
587
|
-
: stripEncryptedMarker(value, ENCRYPTED_MARKER_NAME_BAG_KEYS.has(key));
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
return out;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
858
|
function normalizeAnthropicInputSchema(schema: unknown): Record<string, unknown> {
|
|
595
|
-
const stripped =
|
|
859
|
+
const stripped = stripResponsesOnlyEncryptedMarker(schema);
|
|
596
860
|
const obj = stripped && typeof stripped === "object" && !Array.isArray(stripped)
|
|
597
861
|
? stripped as Record<string, unknown>
|
|
598
862
|
: {};
|
|
@@ -659,6 +923,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
659
923
|
return {
|
|
660
924
|
name: "anthropic",
|
|
661
925
|
|
|
926
|
+
formatErrorBody: formatAnthropicErrorBody,
|
|
927
|
+
|
|
662
928
|
async buildRequest(parsed: OcxParsedRequest, incoming?: IncomingMeta) {
|
|
663
929
|
if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
|
|
664
930
|
if (isOAuth) {
|
|
@@ -668,6 +934,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
668
934
|
}
|
|
669
935
|
|
|
670
936
|
const { system, messages } = messagesToAnthropicFormat(parsed, toolNames);
|
|
937
|
+
// Before image normalization, so the framing block is present for every downstream pass.
|
|
938
|
+
if (isAgentRouterEndpoint(provider.baseUrl)) applyAgentRouterLanguageFraming(messages);
|
|
671
939
|
// Primary image layer: resize/re-encode to fit Anthropic limits without dropping
|
|
672
940
|
// (anthropic-image-normalize.ts); the guard below remains the deterministic backstop.
|
|
673
941
|
// imageTierBias > 0 = upstream-413 tightened retry (030): start every image one tier lower.
|
|
@@ -700,16 +968,25 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
700
968
|
// `reasoning` is a Codex effort string; "none" is the disable sentinel (see parser.ts
|
|
701
969
|
// REASONING_EFFORTS). A bare truthy check would treat "none" as truthy and wrongly enable
|
|
702
970
|
// extended thinking (and strip temperature/top_p), so gate on a real, non-disable effort.
|
|
703
|
-
|
|
971
|
+
//
|
|
972
|
+
// "none" is not the same as absent. Omitting `thinking` lets a default-on model think
|
|
973
|
+
// anyway, and thinking shares the caller's `max_tokens` — which truncates a small-budget
|
|
974
|
+
// request before it can emit its stop sequence (#545). Say "disabled" out loud where the
|
|
975
|
+
// model both defaults to thinking and accepts being told not to.
|
|
976
|
+
const effectiveReasoning = parsed.options.reasoning ?? defaultReasoningEffort(provider, parsed.modelId);
|
|
977
|
+
if (effectiveReasoning === "none" && supportsExplicitThinkingDisable(parsed.modelId)) {
|
|
978
|
+
body.thinking = { type: "disabled" };
|
|
979
|
+
} else if (typeof effectiveReasoning === "string" && effectiveReasoning !== "none") {
|
|
704
980
|
if (usesAdaptiveThinking(parsed.modelId)) {
|
|
705
981
|
// Adaptive-thinking models replace the token budget with an effort knob and reject
|
|
706
982
|
// `thinking.type: "enabled"` outright. `max_tokens` still caps thinking plus visible
|
|
707
983
|
// output, so high effort needs the same total-token headroom as budget thinking or a
|
|
708
984
|
// default 8192-token request can spend everything on thought and return empty text.
|
|
709
985
|
body.thinking = { type: "adaptive" };
|
|
710
|
-
|
|
986
|
+
const effort = adaptiveEffort(effectiveReasoning);
|
|
987
|
+
body.output_config = { effort };
|
|
711
988
|
const explicitMaxOut = parsed.options.maxOutputTokens;
|
|
712
|
-
const wantBudget = reasoningBudget(
|
|
989
|
+
const wantBudget = reasoningBudget(effort);
|
|
713
990
|
const floor = wantBudget + OUTPUT_HEADROOM;
|
|
714
991
|
// Preserve explicit caller limits as-is; for omitted limits use the adaptive ceiling
|
|
715
992
|
// so effort=max (budget=32k) still leaves OUTPUT_HEADROOM tokens for visible output.
|
|
@@ -722,7 +999,7 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
722
999
|
// 400s ("max_tokens must be greater than thinking.budget_tokens"). Size them so max_tokens
|
|
723
1000
|
// always exceeds the budget within a model-safe ceiling, reserving room for visible output.
|
|
724
1001
|
const maxOut = parsed.options.maxOutputTokens ?? DEFAULT_MAX_TOKENS;
|
|
725
|
-
const wantBudget = reasoningBudget(
|
|
1002
|
+
const wantBudget = reasoningBudget(effectiveReasoning);
|
|
726
1003
|
const maxTokens = Math.min(REASONING_MAX_TOKENS_CEILING, Math.max(maxOut, wantBudget + OUTPUT_HEADROOM));
|
|
727
1004
|
const budget = Math.max(MIN_THINKING_BUDGET, Math.min(wantBudget, maxTokens - OUTPUT_FLOOR));
|
|
728
1005
|
body.max_tokens = maxTokens;
|
|
@@ -733,6 +1010,20 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
733
1010
|
delete body.top_p;
|
|
734
1011
|
}
|
|
735
1012
|
|
|
1013
|
+
const textFormat = parsed.options.textFormat;
|
|
1014
|
+
if (textFormat?.type === "json_schema" && textFormat.schema) {
|
|
1015
|
+
const outputConfig = body.output_config;
|
|
1016
|
+
body.output_config = {
|
|
1017
|
+
...(outputConfig && typeof outputConfig === "object" && !Array.isArray(outputConfig)
|
|
1018
|
+
? outputConfig
|
|
1019
|
+
: {}),
|
|
1020
|
+
format: {
|
|
1021
|
+
type: "json_schema",
|
|
1022
|
+
schema: normalizeAnthropicOutputSchema(textFormat.schema),
|
|
1023
|
+
},
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
736
1027
|
if (parsed.options.toolChoice && (tools || parsed.options.toolChoice === "none")) {
|
|
737
1028
|
const tc = parsed.options.toolChoice;
|
|
738
1029
|
if (tc === "auto") body.tool_choice = { type: "auto" };
|
|
@@ -798,10 +1089,23 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
798
1089
|
let pendingUsage: Record<string, number> | undefined;
|
|
799
1090
|
let pendingStopReason: string | undefined;
|
|
800
1091
|
let emittedDone = false;
|
|
1092
|
+
let sawVisibleText = false;
|
|
801
1093
|
|
|
802
1094
|
const emitDone = function* (): Generator<AdapterEvent> {
|
|
803
1095
|
if (emittedDone) return;
|
|
804
1096
|
emittedDone = true;
|
|
1097
|
+
// An `error` stop reason is a failed generation, not a stop. Forwarding it as `done`
|
|
1098
|
+
// lets the turn report success and install replacement history on a compaction turn.
|
|
1099
|
+
if (pendingStopReason === "error") {
|
|
1100
|
+
yield {
|
|
1101
|
+
type: "error",
|
|
1102
|
+
message: "upstream ended the turn with stop_reason \"error\"",
|
|
1103
|
+
status: 502,
|
|
1104
|
+
errorType: "upstream_error",
|
|
1105
|
+
usage: usageFromAnthropic(pendingUsage),
|
|
1106
|
+
};
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
805
1109
|
yield {
|
|
806
1110
|
type: "done",
|
|
807
1111
|
usage: usageFromAnthropic(pendingUsage),
|
|
@@ -818,13 +1122,21 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
818
1122
|
const payload = record.data.trim();
|
|
819
1123
|
if (!payload) continue;
|
|
820
1124
|
|
|
821
|
-
let
|
|
1125
|
+
let parsed: unknown;
|
|
822
1126
|
try {
|
|
823
|
-
|
|
1127
|
+
parsed = JSON.parse(payload);
|
|
824
1128
|
} catch {
|
|
825
1129
|
debugDroppedFrame("anthropic", payload);
|
|
826
1130
|
continue;
|
|
827
1131
|
}
|
|
1132
|
+
// `JSON.parse("null")` returns null instead of throwing, so the catch above cannot cover
|
|
1133
|
+
// it and the `data.type` read below crashed the stream. Drop a non-record frame the same
|
|
1134
|
+
// way an unparseable one is dropped, so the message_stop check still governs the outcome.
|
|
1135
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1136
|
+
debugDroppedFrame("anthropic", payload);
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
const data = parsed as Record<string, unknown>;
|
|
828
1140
|
|
|
829
1141
|
switch (record.event || data.type) {
|
|
830
1142
|
case "message_start": {
|
|
@@ -853,6 +1165,10 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
853
1165
|
const delta = data.delta as Record<string, unknown> | undefined;
|
|
854
1166
|
if (!delta) break;
|
|
855
1167
|
if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
1168
|
+
// Only non-empty text proves the upstream produced usable output; an empty
|
|
1169
|
+
// delta followed by EOF must stay a truncation error even on the tolerant
|
|
1170
|
+
// profile, or a cut-off turn would surface as a successful empty answer.
|
|
1171
|
+
if (delta.text.length > 0) sawVisibleText = true;
|
|
856
1172
|
yield { type: "text_delta", text: delta.text };
|
|
857
1173
|
} else if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
|
|
858
1174
|
yield { type: "thinking_delta", thinking: delta.thinking };
|
|
@@ -934,12 +1250,31 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
934
1250
|
code: "translation_buffer_limit",
|
|
935
1251
|
message: "upstream translation buffer exceeded the safe limit",
|
|
936
1252
|
};
|
|
1253
|
+
// The budget error IS the terminal event for this stream. Falling through to the
|
|
1254
|
+
// EOF handling below could append tool_call_end/done after it, violating the
|
|
1255
|
+
// one-terminal-event contract for consumers that keep draining the generator.
|
|
1256
|
+
return;
|
|
937
1257
|
} finally {
|
|
938
1258
|
if (currentToolCallId) budget.closeCall(currentToolCallId);
|
|
939
1259
|
}
|
|
940
1260
|
if (!emittedDone) {
|
|
941
1261
|
// Fail closed on transport EOF. Compatible providers may omit message_stop after message_delta.stop_reason.
|
|
942
1262
|
if (pendingStopReason !== undefined) {
|
|
1263
|
+
// Same rule as emitDone: an `error` stop reason is a failed generation, not a stop.
|
|
1264
|
+
// This branch bypasses emitDone entirely (it exists for providers that close after
|
|
1265
|
+
// message_delta without message_stop), so the check has to be repeated here or the
|
|
1266
|
+
// EOF route silently reports success.
|
|
1267
|
+
if (pendingStopReason === "error") {
|
|
1268
|
+
emittedDone = true;
|
|
1269
|
+
yield {
|
|
1270
|
+
type: "error",
|
|
1271
|
+
message: "upstream ended the turn with stop_reason \"error\"",
|
|
1272
|
+
status: 502,
|
|
1273
|
+
errorType: "upstream_error",
|
|
1274
|
+
usage: usageFromAnthropic(pendingUsage),
|
|
1275
|
+
};
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
943
1278
|
const stopReason = pendingStopReason === "max_tokens"
|
|
944
1279
|
? "max_tokens"
|
|
945
1280
|
: pendingStopReason === "refusal" || pendingStopReason === "content_filter"
|
|
@@ -951,6 +1286,26 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
951
1286
|
usage: usageFromAnthropic(pendingUsage),
|
|
952
1287
|
...(stopReason ? { stopReason } : {}),
|
|
953
1288
|
};
|
|
1289
|
+
} else if (provider.anthropicEofTolerance === true) {
|
|
1290
|
+
// AgentRouter-style compatibility profile (#658): the upstream can close the stream
|
|
1291
|
+
// after valid content without terminal frames. Complete only when visible text was
|
|
1292
|
+
// received or an open tool call has complete JSON-object arguments; everything else
|
|
1293
|
+
// (incomplete tool JSON, no usable content, transport failure) stays a truncation
|
|
1294
|
+
// error, matching the strict default.
|
|
1295
|
+
if (currentToolCallId) {
|
|
1296
|
+
if (streamedToolArgumentsParse(currentToolCallJson)) {
|
|
1297
|
+
budget.closeCall(currentToolCallId);
|
|
1298
|
+
currentToolCallId = "";
|
|
1299
|
+
yield { type: "tool_call_end" };
|
|
1300
|
+
yield* emitDone();
|
|
1301
|
+
} else {
|
|
1302
|
+
yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
|
|
1303
|
+
}
|
|
1304
|
+
} else if (sawVisibleText) {
|
|
1305
|
+
yield* emitDone();
|
|
1306
|
+
} else {
|
|
1307
|
+
yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
|
|
1308
|
+
}
|
|
954
1309
|
} else {
|
|
955
1310
|
yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
|
|
956
1311
|
}
|
|
@@ -958,12 +1313,55 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
958
1313
|
},
|
|
959
1314
|
|
|
960
1315
|
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
961
|
-
const
|
|
1316
|
+
const parsed: unknown = await response.json();
|
|
1317
|
+
// `response.json()` resolves a body of `null` to `null` without throwing, so the cast below
|
|
1318
|
+
// used to reach `json.content` on it — the #1219 defect at the buffered body root. The
|
|
1319
|
+
// streaming parser has skipped a non-record frame since #1240, but a buffered body has no
|
|
1320
|
+
// next frame to recover into, so this fails closed instead.
|
|
1321
|
+
if (!isAnthropicRecord(parsed)) {
|
|
1322
|
+
return [{
|
|
1323
|
+
type: "error",
|
|
1324
|
+
message: `anthropic response was not a JSON object (${anthropicStructuralValueType(parsed)})`,
|
|
1325
|
+
status: 502,
|
|
1326
|
+
errorType: "upstream_error",
|
|
1327
|
+
}];
|
|
1328
|
+
}
|
|
1329
|
+
const json = parsed;
|
|
962
1330
|
const responseBytes = new TextEncoder().encode(JSON.stringify(json)).byteLength;
|
|
963
1331
|
budget.chargeRetained(responseBytes, { kind: "retained_collectors" });
|
|
964
1332
|
try {
|
|
965
1333
|
const events: AdapterEvent[] = [];
|
|
966
|
-
|
|
1334
|
+
// Same retain-then-return tail every other terminal branch in this function uses; naming it
|
|
1335
|
+
// keeps the two shape guards below from drifting out of step with the accounting.
|
|
1336
|
+
const finishWithEvents = (batch: AdapterEvent[]): AdapterEvent[] => {
|
|
1337
|
+
retainTranslatedEventBatch(batch, budget);
|
|
1338
|
+
return batch;
|
|
1339
|
+
};
|
|
1340
|
+
const rawContent: unknown = json.content;
|
|
1341
|
+
// `content` is claimed model output inside a well-formed response, so it is governed by the
|
|
1342
|
+
// #1332 nested-shape rule (fail closed) rather than #1240's root-frame padding rule (skip).
|
|
1343
|
+
// Absence stays legal in both encodings. A present non-array was silently accepted and is
|
|
1344
|
+
// the dangerous case: a string is iterable, so `for (const block of "text")` walked it one
|
|
1345
|
+
// CHARACTER at a time, read `undefined` from every `block.type`, and completed the turn as a
|
|
1346
|
+
// successful empty response — the #2231 failure mode on a different adapter.
|
|
1347
|
+
if (rawContent !== undefined && rawContent !== null && !Array.isArray(rawContent)) {
|
|
1348
|
+
return finishWithEvents([invalidAnthropicShapeEvent({
|
|
1349
|
+
reason: "content_not_array",
|
|
1350
|
+
valueType: anthropicStructuralValueType(rawContent),
|
|
1351
|
+
})]);
|
|
1352
|
+
}
|
|
1353
|
+
if (Array.isArray(rawContent)) {
|
|
1354
|
+
for (let blockIndex = 0; blockIndex < rawContent.length; blockIndex++) {
|
|
1355
|
+
if (!isAnthropicRecord(rawContent[blockIndex])) {
|
|
1356
|
+
return finishWithEvents([invalidAnthropicShapeEvent({
|
|
1357
|
+
reason: "content_block_not_object",
|
|
1358
|
+
blockIndex,
|
|
1359
|
+
valueType: anthropicStructuralValueType(rawContent[blockIndex]),
|
|
1360
|
+
})]);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
const content = rawContent as { type: string; text?: string; id?: string; name?: string; input?: unknown; thinking?: string; reasoning?: string; signature?: string; data?: string }[] | undefined;
|
|
967
1365
|
if (content) {
|
|
968
1366
|
for (const block of content) {
|
|
969
1367
|
if (block.type === "text" && block.text) {
|
|
@@ -980,13 +1378,28 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
980
1378
|
} else if (block.type === "tool_use") {
|
|
981
1379
|
const id = usableToolUseId(block.id);
|
|
982
1380
|
events.push({ type: "tool_call_start", id, name: toolNames.fromWire(block.name ?? "") });
|
|
983
|
-
events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input) });
|
|
1381
|
+
events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input, provider.anthropicEofTolerance === true) });
|
|
984
1382
|
events.push({ type: "tool_call_end" });
|
|
985
1383
|
}
|
|
986
1384
|
}
|
|
987
1385
|
}
|
|
988
1386
|
const usage = json.usage as Record<string, number> | undefined;
|
|
989
1387
|
const stopReason = typeof json.stop_reason === "string" ? json.stop_reason : undefined;
|
|
1388
|
+
// An Anthropic-compatible upstream can forward an `error` stop reason verbatim. As a
|
|
1389
|
+
// `done` it reads as a clean completion, so the turn reports success and — on a compaction
|
|
1390
|
+
// turn — installs its partial summary as replacement history (#422). Usage is preserved:
|
|
1391
|
+
// a failed turn still consumed tokens.
|
|
1392
|
+
if (stopReason === "error") {
|
|
1393
|
+
events.push({
|
|
1394
|
+
type: "error",
|
|
1395
|
+
message: "upstream ended the turn with stop_reason \"error\"",
|
|
1396
|
+
status: 502,
|
|
1397
|
+
errorType: "upstream_error",
|
|
1398
|
+
usage: usageFromAnthropic(usage),
|
|
1399
|
+
});
|
|
1400
|
+
retainTranslatedEventBatch(events, budget);
|
|
1401
|
+
return events;
|
|
1402
|
+
}
|
|
990
1403
|
events.push({
|
|
991
1404
|
type: "done",
|
|
992
1405
|
usage: usageFromAnthropic(usage),
|