@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
package/src/vision/index.ts
CHANGED
|
@@ -1,22 +1,62 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { OcxConfig, OcxContentPart, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxTextContent } from "../types";
|
|
3
3
|
import { modelInList } from "../types";
|
|
4
|
+
import { modelRecordValue } from "../reasoning-effort";
|
|
5
|
+
import type { VisionReasoningEffort } from "../reasoning-effort";
|
|
4
6
|
import { describeImage, type DescribeOutcome, type VisionSettings } from "./describe";
|
|
5
7
|
import { describeImageAnthropic } from "./anthropic-describe";
|
|
8
|
+
import { describeImageRouted } from "./routed-describe";
|
|
9
|
+
import { modelAcceptsImageInput } from "./eligibility";
|
|
10
|
+
import { normalizeVisionReasoningForModel } from "./reasoning";
|
|
6
11
|
import type { CodexAuthContext } from "../codex/auth-context";
|
|
7
|
-
import {
|
|
12
|
+
import { resolveSidecarAuth } from "../sidecar/auth";
|
|
8
13
|
import type { ResolvedOpenAiForwardSidecar } from "../providers/openai-sidecar";
|
|
9
14
|
import type { SidecarOutcomeRecorder } from "../web-search/executor";
|
|
10
15
|
import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory";
|
|
11
16
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_VISION_TIMEOUT_MS,
|
|
19
|
+
MAX_VISION_TIMEOUT_MS,
|
|
20
|
+
MIN_VISION_TIMEOUT_MS,
|
|
21
|
+
} from "./timeout-bounds";
|
|
12
22
|
|
|
13
23
|
export { describeImage } from "./describe";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* True when the model is explicitly known to be text-only — either listed in
|
|
27
|
+
* `noVisionModels` or declared with `modelInputModalities` that exclude "image".
|
|
28
|
+
* Returns false for unknown models (no evidence either way) so they fall through
|
|
29
|
+
* to native image passthrough, which is the safe default for an unclassified model.
|
|
30
|
+
*/
|
|
31
|
+
export function isModelTextOnly(
|
|
32
|
+
provider: OcxProviderConfig,
|
|
33
|
+
modelId: string,
|
|
34
|
+
): boolean {
|
|
35
|
+
if (modelInList(provider.noVisionModels, modelId)) return true;
|
|
36
|
+
const modalities = modelRecordValue(provider.modelInputModalities, modelId);
|
|
37
|
+
if (Array.isArray(modalities) && modalities.length > 0 && !modalities.includes("image")) return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
14
40
|
export { describeImageAnthropic, parseAnthropicVisionSSE } from "./anthropic-describe";
|
|
41
|
+
export {
|
|
42
|
+
BASELINE_VISION_MODELS,
|
|
43
|
+
isVisionEligibleModel,
|
|
44
|
+
isVisionSidecarConsumer,
|
|
45
|
+
modelAcceptsImageInput,
|
|
46
|
+
visionBackendForCandidate,
|
|
47
|
+
visionEligibleModelOptions,
|
|
48
|
+
} from "./eligibility";
|
|
49
|
+
export type { VisionCandidateModel, VisionModelOption, VisionSidecarBackend } from "./eligibility";
|
|
50
|
+
export {
|
|
51
|
+
DEFAULT_VISION_TIMEOUT_MS,
|
|
52
|
+
MAX_VISION_TIMEOUT_MS,
|
|
53
|
+
MIN_VISION_TIMEOUT_MS,
|
|
54
|
+
};
|
|
15
55
|
|
|
16
56
|
const DEFAULT_VISION_MODEL = "gpt-5.4-mini";
|
|
17
57
|
const DEFAULT_ANTHROPIC_VISION_MODEL = "claude-sonnet-5";
|
|
18
|
-
const
|
|
19
|
-
const DEFAULT_MAX_DESCRIPTIONS_PER_TURN = 8;
|
|
58
|
+
const DEFAULT_REASONING: VisionReasoningEffort = "low";
|
|
59
|
+
export const DEFAULT_MAX_DESCRIPTIONS_PER_TURN = 8;
|
|
20
60
|
const DESCRIPTION_CACHE_MAX_ENTRIES = 256;
|
|
21
61
|
export const VISION_DESCRIPTION_CACHE_MAX_BYTES = 1024 * 1024;
|
|
22
62
|
const descriptionEncoder = new TextEncoder();
|
|
@@ -142,6 +182,18 @@ export function resolveMaxDescriptionsPerTurn(value: unknown): number {
|
|
|
142
182
|
: DEFAULT_MAX_DESCRIPTIONS_PER_TURN;
|
|
143
183
|
}
|
|
144
184
|
|
|
185
|
+
export function isValidVisionTimeoutMs(value: unknown): value is number {
|
|
186
|
+
return typeof value === "number"
|
|
187
|
+
&& Number.isInteger(value)
|
|
188
|
+
&& value >= MIN_VISION_TIMEOUT_MS
|
|
189
|
+
&& value <= MAX_VISION_TIMEOUT_MS;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Runtime config is permissive: malformed or out-of-range values fall back to the default. */
|
|
193
|
+
export function resolveVisionTimeoutMs(value: unknown): number {
|
|
194
|
+
return isValidVisionTimeoutMs(value) ? value : DEFAULT_VISION_TIMEOUT_MS;
|
|
195
|
+
}
|
|
196
|
+
|
|
145
197
|
/** Run `worker` over `items` with bounded concurrency, preserving input order in the result array. */
|
|
146
198
|
async function runBounded<T, R>(items: T[], limit: number, worker: (item: T) => Promise<R>): Promise<R[]> {
|
|
147
199
|
const results = new Array<R>(items.length);
|
|
@@ -165,25 +217,52 @@ export interface AnthropicVisionProvider {
|
|
|
165
217
|
provider: OcxProviderConfig;
|
|
166
218
|
}
|
|
167
219
|
|
|
168
|
-
/**
|
|
220
|
+
/**
|
|
221
|
+
* First enabled Anthropic OAuth provider whose active stored account is not marked for reauth.
|
|
222
|
+
* Delegates to the shared sidecar auth module (#2188) — same predicate as web-search.
|
|
223
|
+
*/
|
|
169
224
|
export function findAnthropicVisionProvider(config: OcxConfig): AnthropicVisionProvider | undefined {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const active = accountSet?.accounts.find(account => account.id === accountSet.activeAccountId);
|
|
174
|
-
if (active && active.needsReauth !== true) return { providerName, provider };
|
|
175
|
-
}
|
|
176
|
-
return undefined;
|
|
225
|
+
const auth = resolveSidecarAuth(config);
|
|
226
|
+
if (!auth.isAnthropicAuth || !auth.anthropicProviderName || !auth.anthropicProvider) return undefined;
|
|
227
|
+
return { providerName: auth.anthropicProviderName, provider: auth.anthropicProvider };
|
|
177
228
|
}
|
|
178
229
|
|
|
179
230
|
export function resolveVisionBackend(
|
|
180
|
-
explicit: "openai" | "anthropic" | undefined,
|
|
231
|
+
explicit: "openai" | "anthropic" | "routed" | undefined,
|
|
181
232
|
anthropicSidecar: AnthropicVisionProvider | undefined,
|
|
182
233
|
): "openai" | "anthropic" {
|
|
183
234
|
if (explicit === "openai" || explicit === "anthropic") return explicit;
|
|
235
|
+
// "routed" collapses to the legacy default order until its describe executor
|
|
236
|
+
// lands (roadmap 170 → 180 revised): a persisted routed backend without a
|
|
237
|
+
// dispatchable arm degrades exactly like unset rather than crashing. wp3
|
|
238
|
+
// replaces this collapse with the real routed arm in planVisionSidecar.
|
|
184
239
|
return anthropicSidecar ? "anthropic" : "openai";
|
|
185
240
|
}
|
|
186
241
|
|
|
242
|
+
/** Native model used by the OpenAI vision helper, including its bounded default. */
|
|
243
|
+
export function resolveOpenAiVisionModel(config: Pick<OcxConfig, "visionSidecar">): string {
|
|
244
|
+
const configured = config.visionSidecar?.model;
|
|
245
|
+
// Namespaced routed ids never reach the forward executor (see
|
|
246
|
+
// resolveEffectiveVisionModel).
|
|
247
|
+
return configured && !configured.includes("/") ? configured : DEFAULT_VISION_MODEL;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Effective describer model for the backend `planVisionSidecar` selected. */
|
|
251
|
+
export function resolveEffectiveVisionModel(
|
|
252
|
+
config: Pick<OcxConfig, "visionSidecar">,
|
|
253
|
+
backend: "openai" | "anthropic",
|
|
254
|
+
): string {
|
|
255
|
+
const configured = config.visionSidecar?.model;
|
|
256
|
+
// A namespaced "provider/model" id belongs to the routed backend only; the
|
|
257
|
+
// forward/OAuth executors POST the model string verbatim, so it falls back
|
|
258
|
+
// to the side's default here (PUT coherence rejects new writes of this
|
|
259
|
+
// shape, but a legacy or hand-edited config must not break the executor).
|
|
260
|
+
const usable = configured && !configured.includes("/") ? configured : undefined;
|
|
261
|
+
return backend === "anthropic"
|
|
262
|
+
? usable || DEFAULT_ANTHROPIC_VISION_MODEL
|
|
263
|
+
: usable || DEFAULT_VISION_MODEL;
|
|
264
|
+
}
|
|
265
|
+
|
|
187
266
|
/** A user/developer/toolResult message can carry images (toolResult: e.g. Codex view_image output). */
|
|
188
267
|
function carriesImages(role: string): boolean {
|
|
189
268
|
return role === "user" || role === "developer" || role === "toolResult";
|
|
@@ -197,19 +276,23 @@ function messagesHaveImage(parsed: OcxParsedRequest): boolean {
|
|
|
197
276
|
export function shouldResolveOpenAiVisionSidecar(
|
|
198
277
|
config: OcxConfig,
|
|
199
278
|
provider: OcxProviderConfig,
|
|
200
|
-
|
|
201
|
-
|
|
279
|
+
modelId: string,
|
|
280
|
+
parsed: OcxParsedRequest,
|
|
202
281
|
): boolean {
|
|
203
|
-
if (!
|
|
282
|
+
if (!isModelTextOnly(provider, modelId) || !messagesHaveImage(parsed)) return false;
|
|
204
283
|
const cfg = config.visionSidecar ?? {};
|
|
205
284
|
if (cfg.enabled === false) return false;
|
|
206
285
|
return resolveVisionBackend(cfg.backend, findAnthropicVisionProvider(config)) === "openai";
|
|
207
286
|
}
|
|
208
287
|
|
|
209
288
|
export interface VisionPlan {
|
|
210
|
-
backend: "openai" | "anthropic";
|
|
289
|
+
backend: "openai" | "anthropic" | "routed";
|
|
211
290
|
forwardSidecar?: ResolvedOpenAiForwardSidecar;
|
|
212
291
|
anthropicSidecar?: AnthropicVisionProvider;
|
|
292
|
+
/** Namespaced "provider/model" describer for the routed backend (roadmap 180). */
|
|
293
|
+
routedModel?: string;
|
|
294
|
+
/** Loopback dispatch inputs for the routed backend. */
|
|
295
|
+
routedConfig?: Pick<OcxConfig, "port" | "apiKeys">;
|
|
213
296
|
settings: VisionSettings;
|
|
214
297
|
maxDescriptionsPerTurn: number;
|
|
215
298
|
}
|
|
@@ -227,12 +310,50 @@ export function planVisionSidecar(
|
|
|
227
310
|
parsed: OcxParsedRequest,
|
|
228
311
|
openAiSidecar?: ResolvedOpenAiForwardSidecar,
|
|
229
312
|
): VisionPlan | undefined {
|
|
230
|
-
if (!
|
|
313
|
+
if (!isModelTextOnly(provider, modelId)) return undefined;
|
|
231
314
|
if (!messagesHaveImage(parsed)) return undefined;
|
|
232
315
|
const cfg = config.visionSidecar ?? {};
|
|
233
316
|
if (cfg.enabled === false) return undefined;
|
|
317
|
+
|
|
318
|
+
// Routed arm (roadmap 180 revised): explicit backend + NAMESPACED explicit
|
|
319
|
+
// model only — never inferred from credential availability. Plan-time
|
|
320
|
+
// fence: the target must not be provably blind, and must not itself be a
|
|
321
|
+
// model this planner would re-enter for (belt; the terminal marker on the
|
|
322
|
+
// loopback request is the braces).
|
|
323
|
+
if (cfg.backend === "routed") {
|
|
324
|
+
const routedModel = cfg.model;
|
|
325
|
+
const sep = routedModel ? routedModel.indexOf("/") : -1;
|
|
326
|
+
if (routedModel && sep > 0) {
|
|
327
|
+
const targetProvider = routedModel.slice(0, sep);
|
|
328
|
+
const targetId = routedModel.slice(sep + 1);
|
|
329
|
+
const targetProviderConfig = config.providers?.[targetProvider];
|
|
330
|
+
const targetVisible = modelAcceptsImageInput(config, { provider: targetProvider, id: targetId }) !== false
|
|
331
|
+
&& !(targetProviderConfig && isModelTextOnly(targetProviderConfig, targetId));
|
|
332
|
+
if (targetVisible) {
|
|
333
|
+
return {
|
|
334
|
+
backend: "routed",
|
|
335
|
+
routedModel,
|
|
336
|
+
routedConfig: { port: config.port, ...(config.apiKeys ? { apiKeys: config.apiKeys } : {}) },
|
|
337
|
+
settings: {
|
|
338
|
+
model: routedModel,
|
|
339
|
+
reasoning: DEFAULT_REASONING,
|
|
340
|
+
timeoutMs: resolveVisionTimeoutMs(cfg.timeoutMs),
|
|
341
|
+
},
|
|
342
|
+
maxDescriptionsPerTurn: resolveMaxDescriptionsPerTurn(cfg.maxDescriptionsPerTurn),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Misconfigured routed backend (bare id, unknown provider, or provably
|
|
347
|
+
// blind target): fall through to the legacy default order below rather
|
|
348
|
+
// than dispatching a describe that cannot work.
|
|
349
|
+
}
|
|
350
|
+
|
|
234
351
|
const anthropicSidecar = findAnthropicVisionProvider(config);
|
|
235
352
|
const backend = resolveVisionBackend(cfg.backend, anthropicSidecar);
|
|
353
|
+
// A namespaced routed model must never reach the forward/OAuth executors
|
|
354
|
+
// (they POST the string verbatim); the effective-model resolver falls back
|
|
355
|
+
// to each side's default in that case.
|
|
356
|
+
const model = resolveEffectiveVisionModel(config, backend);
|
|
236
357
|
const maxDescriptionsPerTurn = resolveMaxDescriptionsPerTurn(cfg.maxDescriptionsPerTurn);
|
|
237
358
|
|
|
238
359
|
if (backend === "anthropic") {
|
|
@@ -240,7 +361,11 @@ export function planVisionSidecar(
|
|
|
240
361
|
return {
|
|
241
362
|
backend,
|
|
242
363
|
anthropicSidecar,
|
|
243
|
-
settings: {
|
|
364
|
+
settings: {
|
|
365
|
+
model,
|
|
366
|
+
reasoning: normalizeVisionReasoningForModel(model, cfg.reasoning) ?? DEFAULT_REASONING,
|
|
367
|
+
timeoutMs: resolveVisionTimeoutMs(cfg.timeoutMs),
|
|
368
|
+
},
|
|
244
369
|
maxDescriptionsPerTurn,
|
|
245
370
|
};
|
|
246
371
|
}
|
|
@@ -249,7 +374,11 @@ export function planVisionSidecar(
|
|
|
249
374
|
return {
|
|
250
375
|
backend,
|
|
251
376
|
forwardSidecar: openAiSidecar,
|
|
252
|
-
settings: {
|
|
377
|
+
settings: {
|
|
378
|
+
model,
|
|
379
|
+
reasoning: normalizeVisionReasoningForModel(model, cfg.reasoning) ?? DEFAULT_REASONING,
|
|
380
|
+
timeoutMs: resolveVisionTimeoutMs(cfg.timeoutMs),
|
|
381
|
+
},
|
|
253
382
|
maxDescriptionsPerTurn,
|
|
254
383
|
};
|
|
255
384
|
}
|
|
@@ -270,6 +399,72 @@ function renderDescription(out: { text: string; error?: string }): OcxTextConten
|
|
|
270
399
|
};
|
|
271
400
|
}
|
|
272
401
|
|
|
402
|
+
const IMAGE_OMITTED_TEXT = "[image omitted: this model is text-only and the vision sidecar is unavailable (no ChatGPT login)]";
|
|
403
|
+
|
|
404
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
405
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Keep the native Responses passthrough body aligned with image replacements made in the parsed
|
|
410
|
+
* message graph. The passthrough adapter serializes `_rawBody`, while translated adapters serialize
|
|
411
|
+
* `context.messages`; updating only the latter would send the original pixels to a text-only
|
|
412
|
+
* Responses upstream even after the vision sidecar produced a caption.
|
|
413
|
+
*
|
|
414
|
+
* Rewrites only image-bearing user/developer messages and tool outputs. All other native Responses
|
|
415
|
+
* items (reasoning, calls, ids, compaction, and provider-specific metadata) remain byte-structurally
|
|
416
|
+
* untouched.
|
|
417
|
+
*/
|
|
418
|
+
function syncRawBodyImageDescriptions(parsed: OcxParsedRequest, descriptions: readonly string[]): void {
|
|
419
|
+
const rawBody = parsed._rawBody;
|
|
420
|
+
if (!isPlainRecord(rawBody) || !Array.isArray(rawBody.input)) return;
|
|
421
|
+
|
|
422
|
+
let nextDescription = 0;
|
|
423
|
+
const rewriteImages = (value: unknown, nonEmptyImageUrlsOnly: boolean): unknown => {
|
|
424
|
+
if (Array.isArray(value)) {
|
|
425
|
+
let changed = false;
|
|
426
|
+
const rewritten = value.map(entry => {
|
|
427
|
+
const next = rewriteImages(entry, nonEmptyImageUrlsOnly);
|
|
428
|
+
if (next !== entry) changed = true;
|
|
429
|
+
return next;
|
|
430
|
+
});
|
|
431
|
+
return changed ? rewritten : value;
|
|
432
|
+
}
|
|
433
|
+
if (!isPlainRecord(value)) return value;
|
|
434
|
+
if (value.type === "input_image" && typeof value.image_url === "string") {
|
|
435
|
+
if (nonEmptyImageUrlsOnly && value.image_url.length === 0) {
|
|
436
|
+
return { type: "input_text", text: IMAGE_OMITTED_TEXT };
|
|
437
|
+
}
|
|
438
|
+
const description = descriptions[nextDescription++];
|
|
439
|
+
return { type: "input_text", text: description ?? IMAGE_OMITTED_TEXT };
|
|
440
|
+
}
|
|
441
|
+
return value;
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
let changed = false;
|
|
445
|
+
const input = rawBody.input.map(item => {
|
|
446
|
+
if (!isPlainRecord(item)) return item;
|
|
447
|
+
const type = typeof item.type === "string" ? item.type : (typeof item.role === "string" ? "message" : "");
|
|
448
|
+
const role = typeof item.role === "string" ? item.role : "";
|
|
449
|
+
const isMessageContent = (
|
|
450
|
+
(type === "message" && (role === "user" || role === "developer"))
|
|
451
|
+
|| type === "agent_message"
|
|
452
|
+
);
|
|
453
|
+
const field = isMessageContent
|
|
454
|
+
? "content"
|
|
455
|
+
: (type === "function_call_output" || type === "custom_tool_call_output")
|
|
456
|
+
? "output"
|
|
457
|
+
: undefined;
|
|
458
|
+
if (!field) return item;
|
|
459
|
+
const rewritten = rewriteImages(item[field], isMessageContent);
|
|
460
|
+
if (rewritten === item[field]) return item;
|
|
461
|
+
changed = true;
|
|
462
|
+
return { ...item, [field]: rewritten };
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
if (changed) rawBody.input = input;
|
|
466
|
+
}
|
|
467
|
+
|
|
273
468
|
function sha256(value: string | Uint8Array): string {
|
|
274
469
|
return createHash("sha256").update(value).digest("hex");
|
|
275
470
|
}
|
|
@@ -292,6 +487,7 @@ function descriptionIdentity(job: ImageJob, plan: VisionPlan): { key: string; pe
|
|
|
292
487
|
key: JSON.stringify([
|
|
293
488
|
plan.backend,
|
|
294
489
|
plan.settings.model,
|
|
490
|
+
...(plan.backend === "openai" ? [plan.settings.reasoning] : []),
|
|
295
491
|
job.detail ?? "high",
|
|
296
492
|
imageHash,
|
|
297
493
|
sha256(normalizedContext(job.contextText)),
|
|
@@ -307,6 +503,18 @@ async function executeDescription(
|
|
|
307
503
|
abortSignal?: AbortSignal,
|
|
308
504
|
recordSidecarOutcome?: SidecarOutcomeRecorder,
|
|
309
505
|
): Promise<DescribeOutcome> {
|
|
506
|
+
if (plan.backend === "routed") {
|
|
507
|
+
if (!plan.routedModel || !plan.routedConfig) return { text: "", error: "routed vision sidecar is unavailable" };
|
|
508
|
+
return describeImageRouted(
|
|
509
|
+
job.imageUrl,
|
|
510
|
+
job.detail,
|
|
511
|
+
job.contextText,
|
|
512
|
+
plan.routedModel,
|
|
513
|
+
plan.routedConfig,
|
|
514
|
+
plan.settings,
|
|
515
|
+
abortSignal,
|
|
516
|
+
);
|
|
517
|
+
}
|
|
310
518
|
if (plan.backend === "anthropic") {
|
|
311
519
|
const sidecar = plan.anthropicSidecar;
|
|
312
520
|
if (!sidecar) return { text: "", error: "anthropic vision sidecar is unavailable" };
|
|
@@ -347,7 +555,6 @@ export async function describeImagesInPlace(
|
|
|
347
555
|
recordSidecarOutcome?: SidecarOutcomeRecorder,
|
|
348
556
|
translatorBudget?: TranslatorBudget,
|
|
349
557
|
): Promise<void> {
|
|
350
|
-
// 1. Gather every image part across messages, each with its own message's text as context.
|
|
351
558
|
const jobs: ImageJob[] = [];
|
|
352
559
|
const targets: { msg: OcxMessage; parts: OcxContentPart[] }[] = [];
|
|
353
560
|
for (const msg of parsed.context.messages) {
|
|
@@ -364,9 +571,11 @@ export async function describeImagesInPlace(
|
|
|
364
571
|
}
|
|
365
572
|
targets.push({ msg, parts });
|
|
366
573
|
}
|
|
367
|
-
if (jobs.length === 0)
|
|
574
|
+
if (jobs.length === 0) {
|
|
575
|
+
syncRawBodyImageDescriptions(parsed, []);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
368
578
|
|
|
369
|
-
// 2. Admit misses in source order. Cache hits and same-turn waiters do not consume the cap.
|
|
370
579
|
const inFlight = new Map<string, Promise<DescribeOutcome>>();
|
|
371
580
|
const executions: Array<() => Promise<void>> = [];
|
|
372
581
|
const outcomePromises: Array<Promise<DescribeOutcome>> = [];
|
|
@@ -418,8 +627,8 @@ export async function describeImagesInPlace(
|
|
|
418
627
|
await runBounded(executions, VISION_CONCURRENCY, execute => execute());
|
|
419
628
|
const outcomes = await Promise.all(outcomePromises);
|
|
420
629
|
|
|
421
|
-
// 3. Rebuild each message, replacing image parts with their descriptions in order.
|
|
422
630
|
let oi = 0;
|
|
631
|
+
const descriptions: string[] = [];
|
|
423
632
|
for (const { msg, parts } of targets) {
|
|
424
633
|
const newParts: OcxContentPart[] = [];
|
|
425
634
|
for (const p of parts) {
|
|
@@ -428,6 +637,7 @@ export async function describeImagesInPlace(
|
|
|
428
637
|
continue;
|
|
429
638
|
}
|
|
430
639
|
const replacement = renderDescription(outcomes[oi++]);
|
|
640
|
+
descriptions.push(replacement.text);
|
|
431
641
|
const reservation = translatorBudget?.reserveTransient(
|
|
432
642
|
descriptionEncoder.encode(replacement.text).byteLength,
|
|
433
643
|
{ kind: "request_copies" },
|
|
@@ -437,6 +647,7 @@ export async function describeImagesInPlace(
|
|
|
437
647
|
}
|
|
438
648
|
msg.content = newParts;
|
|
439
649
|
}
|
|
650
|
+
syncRawBodyImageDescriptions(parsed, descriptions);
|
|
440
651
|
}
|
|
441
652
|
|
|
442
653
|
/**
|
|
@@ -447,13 +658,15 @@ export async function describeImagesInPlace(
|
|
|
447
658
|
*/
|
|
448
659
|
export function stripImagesInPlace(parsed: OcxParsedRequest, translatorBudget?: TranslatorBudget): boolean {
|
|
449
660
|
let stripped = false;
|
|
661
|
+
const descriptions: string[] = [];
|
|
450
662
|
for (const msg of parsed.context.messages) {
|
|
451
663
|
if (!carriesImages(msg.role) || !Array.isArray(msg.content)) continue;
|
|
452
664
|
const parts = msg.content as OcxContentPart[];
|
|
453
665
|
if (!parts.some(p => p.type === "image")) continue;
|
|
454
666
|
msg.content = parts.map(p => {
|
|
455
667
|
if (p.type !== "image") return p;
|
|
456
|
-
const replacement = { type: "text", text:
|
|
668
|
+
const replacement = { type: "text", text: IMAGE_OMITTED_TEXT } as OcxContentPart;
|
|
669
|
+
descriptions.push((replacement as OcxTextContent).text);
|
|
457
670
|
const reservation = translatorBudget?.reserveTransient(
|
|
458
671
|
descriptionEncoder.encode((replacement as OcxTextContent).text).byteLength,
|
|
459
672
|
{ kind: "request_copies" },
|
|
@@ -463,5 +676,6 @@ export function stripImagesInPlace(parsed: OcxParsedRequest, translatorBudget?:
|
|
|
463
676
|
});
|
|
464
677
|
stripped = true;
|
|
465
678
|
}
|
|
679
|
+
syncRawBodyImageDescriptions(parsed, descriptions);
|
|
466
680
|
return stripped;
|
|
467
681
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NATIVE_OPENAI_MODELS, nativeReasoningEfforts } from "../codex/catalog/metadata";
|
|
2
|
+
import {
|
|
3
|
+
VISION_REASONING_EFFORTS,
|
|
4
|
+
sanitizeVisionReasoning,
|
|
5
|
+
type VisionReasoningEffort,
|
|
6
|
+
} from "../reasoning-effort";
|
|
7
|
+
|
|
8
|
+
/** Keep the public config type aligned without replacing the heavily-changed shared types file. */
|
|
9
|
+
declare module "../types" {
|
|
10
|
+
interface OcxVisionSidecarConfig {
|
|
11
|
+
/** OpenAI Responses reasoning effort used by the vision describer. Default: low. */
|
|
12
|
+
reasoning?: VisionReasoningEffort;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const NATIVE_VISION_MODELS = new Set(NATIVE_OPENAI_MODELS);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Return the known vision reasoning ladder for a native OpenAI model.
|
|
20
|
+
* Unknown/custom models deliberately return undefined so callers stay permissive when reliable
|
|
21
|
+
* capability metadata is unavailable.
|
|
22
|
+
*/
|
|
23
|
+
export function nativeVisionReasoningEfforts(modelId: string): VisionReasoningEffort[] | undefined {
|
|
24
|
+
if (!NATIVE_VISION_MODELS.has(modelId)) return undefined;
|
|
25
|
+
const advertised = new Set(nativeReasoningEfforts(modelId));
|
|
26
|
+
const supported = VISION_REASONING_EFFORTS.filter(effort => advertised.has(effort));
|
|
27
|
+
return supported.length > 0 ? [...supported] : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Normalize one configured effort against a model's known ladder. Invalid enum values degrade to
|
|
32
|
+
* undefined; valid values on unknown/custom models are preserved. A known native model clamps to
|
|
33
|
+
* the highest supported rung at or below the requested value.
|
|
34
|
+
*/
|
|
35
|
+
export function normalizeVisionReasoningForModel(
|
|
36
|
+
modelId: string,
|
|
37
|
+
value: unknown,
|
|
38
|
+
): VisionReasoningEffort | undefined {
|
|
39
|
+
const requested = sanitizeVisionReasoning(value);
|
|
40
|
+
if (!requested) return undefined;
|
|
41
|
+
const supported = nativeVisionReasoningEfforts(modelId);
|
|
42
|
+
if (!supported || supported.includes(requested)) return requested;
|
|
43
|
+
|
|
44
|
+
const requestedRank = VISION_REASONING_EFFORTS.indexOf(requested);
|
|
45
|
+
let best = supported[0];
|
|
46
|
+
let bestRank = VISION_REASONING_EFFORTS.indexOf(best);
|
|
47
|
+
for (const effort of supported) {
|
|
48
|
+
const rank = VISION_REASONING_EFFORTS.indexOf(effort);
|
|
49
|
+
if (rank <= requestedRank && rank >= bestRank) {
|
|
50
|
+
best = effort;
|
|
51
|
+
bestRank = rank;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return best;
|
|
55
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describe ONE image via a ROUTED model through the proxy's own
|
|
3
|
+
* /v1/chat/completions on loopback (#2188 roadmap 180 revised).
|
|
4
|
+
*
|
|
5
|
+
* One executor for every provider the router can reach: the chat inbound
|
|
6
|
+
* translates image_url parts and each adapter compiles its own wire
|
|
7
|
+
* (Anthropic blocks, Antigravity inlineData, xai Responses input_image, plain
|
|
8
|
+
* openai-chat), so provider coverage is the router's job, not this file's.
|
|
9
|
+
*
|
|
10
|
+
* Recursion fence: the request carries `x-opencodex-vision-describe: 1`.
|
|
11
|
+
* The Chat surface detects the raw header before its bridge rebuilds headers
|
|
12
|
+
* and carries it into handleResponses as `visionDescribeTerminal`; a marked
|
|
13
|
+
* request STRIPS images instead of planning another describe (depth cap 1,
|
|
14
|
+
* holds under predicate drift and combo re-resolution — audit rounds 2-4).
|
|
15
|
+
*
|
|
16
|
+
* Admission ladder (audit round 3): configuredApiAuthToken() (env token) ||
|
|
17
|
+
* service token file || first config.apiKeys entry, sent as
|
|
18
|
+
* `x-opencodex-api-key` — never Authorization (gateway-cache.ts rule: an
|
|
19
|
+
* admission secret in a forwardable header is a forwarding hazard). Loopback
|
|
20
|
+
* binds require no token at all (resolveApiAuth admits loopback).
|
|
21
|
+
*
|
|
22
|
+
* Known limitation (recorded in roadmap 170): a bindHost where 127.0.0.1
|
|
23
|
+
* does not answer cannot reach its own loopback — same latent limitation
|
|
24
|
+
* gateway-cache has.
|
|
25
|
+
*/
|
|
26
|
+
import type { OcxConfig } from "../types";
|
|
27
|
+
import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
|
|
28
|
+
import { redactSecretString } from "../lib/redact";
|
|
29
|
+
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
30
|
+
import { configuredApiAuthToken, configuredPort } from "../server/auth-cors";
|
|
31
|
+
import { loadServiceTokenFromFile } from "../lib/service-secrets";
|
|
32
|
+
import type { DescribeOutcome, VisionSettings } from "./describe";
|
|
33
|
+
|
|
34
|
+
export const VISION_DESCRIBE_TERMINAL_HEADER = "x-opencodex-vision-describe";
|
|
35
|
+
|
|
36
|
+
const ALLOWED_IMAGE_MIME = new Set(["image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif"]);
|
|
37
|
+
const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
38
|
+
/** Bound the loopback JSON response; descriptions are clamped to ~2k chars by the caller anyway. */
|
|
39
|
+
const MAX_ROUTED_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
40
|
+
|
|
41
|
+
const DESCRIBE_INSTRUCTION =
|
|
42
|
+
"You are a vision describer for a text-only model that cannot see the image. Describe the image "
|
|
43
|
+
+ "thoroughly and factually so that model can fully reason about it: transcribe any visible text "
|
|
44
|
+
+ "verbatim, and note UI/layout, colors, branding/logos, charts, and notable details. Focus on "
|
|
45
|
+
+ "what's relevant to the user's request. Output only the description.";
|
|
46
|
+
|
|
47
|
+
function validateImageUrl(url: string): string | null {
|
|
48
|
+
if (url.startsWith("data:")) {
|
|
49
|
+
const match = /^data:([^;,]+?)(;base64)?,(.*)$/s.exec(url);
|
|
50
|
+
if (!match) return "malformed data URL";
|
|
51
|
+
const mime = match[1].toLowerCase();
|
|
52
|
+
if (!ALLOWED_IMAGE_MIME.has(mime)) return `unsupported image type "${mime}"`;
|
|
53
|
+
if (match[2]) {
|
|
54
|
+
const bytes = Math.floor((match[3].length * 3) / 4);
|
|
55
|
+
if (bytes > MAX_IMAGE_BYTES) return `image too large (~${Math.round(bytes / 1024 / 1024)}MB)`;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (url.startsWith("https://")) return null;
|
|
60
|
+
return "unsupported image URL scheme (expected data: or https:)";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The admission ladder: env token, service token file, first configured API key. */
|
|
64
|
+
export function routedDescribeAdmissionToken(config: Pick<OcxConfig, "apiKeys">): string | undefined {
|
|
65
|
+
const envToken = configuredApiAuthToken();
|
|
66
|
+
if (envToken) return envToken;
|
|
67
|
+
const fileToken = loadServiceTokenFromFile(process.env);
|
|
68
|
+
if (fileToken) return fileToken;
|
|
69
|
+
const first = config.apiKeys?.[0]?.key?.trim();
|
|
70
|
+
return first || undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Base URL seam for tests; production always self-fetches loopback. */
|
|
74
|
+
export function routedDescribeBaseUrl(config: Pick<OcxConfig, "port">): string {
|
|
75
|
+
// config.port can be 0 (ephemeral bind, tests) or stale after a live port
|
|
76
|
+
// override; the server records its ACTUAL bound port via setCorsOrigin at
|
|
77
|
+
// startup, so prefer that when config carries no positive port.
|
|
78
|
+
const port = config.port && config.port > 0 ? String(config.port) : configuredPort();
|
|
79
|
+
return `http://127.0.0.1:${port}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function describeImageRouted(
|
|
83
|
+
imageUrl: string,
|
|
84
|
+
_detail: string | undefined,
|
|
85
|
+
contextText: string,
|
|
86
|
+
routedModel: string,
|
|
87
|
+
config: Pick<OcxConfig, "port" | "apiKeys">,
|
|
88
|
+
settings: VisionSettings,
|
|
89
|
+
abortSignal?: AbortSignal,
|
|
90
|
+
baseUrlOverride?: string,
|
|
91
|
+
): Promise<DescribeOutcome> {
|
|
92
|
+
const invalid = validateImageUrl(imageUrl);
|
|
93
|
+
if (invalid) return { text: "", error: invalid };
|
|
94
|
+
|
|
95
|
+
const headers: Record<string, string> = {
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
[VISION_DESCRIBE_TERMINAL_HEADER]: "1",
|
|
98
|
+
};
|
|
99
|
+
const admission = routedDescribeAdmissionToken(config);
|
|
100
|
+
if (admission) headers["x-opencodex-api-key"] = admission;
|
|
101
|
+
|
|
102
|
+
const requestBody = {
|
|
103
|
+
model: routedModel,
|
|
104
|
+
stream: false,
|
|
105
|
+
messages: [
|
|
106
|
+
{ role: "system", content: DESCRIBE_INSTRUCTION },
|
|
107
|
+
{
|
|
108
|
+
role: "user",
|
|
109
|
+
content: [
|
|
110
|
+
...(contextText ? [{ type: "text", text: `User's request context: ${contextText}` }] : []),
|
|
111
|
+
{ type: "image_url", image_url: { url: imageUrl } },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
|
|
118
|
+
const sidecarExit = sidecarEnter("vision");
|
|
119
|
+
const t0 = Date.now();
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetch(`${baseUrlOverride ?? routedDescribeBaseUrl(config)}/v1/chat/completions`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers,
|
|
124
|
+
body: JSON.stringify(requestBody),
|
|
125
|
+
signal: linkedSignal.signal,
|
|
126
|
+
redirect: "manual",
|
|
127
|
+
});
|
|
128
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
129
|
+
try {
|
|
130
|
+
const raw = await res.text();
|
|
131
|
+
if (raw.length > MAX_ROUTED_RESPONSE_BYTES) {
|
|
132
|
+
return { text: "", error: "routed describe response exceeded byte bound" };
|
|
133
|
+
}
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
return { text: "", error: `routed describe HTTP ${res.status}: ${redactSecretString(raw.slice(0, 200))}` };
|
|
136
|
+
}
|
|
137
|
+
let payload: unknown;
|
|
138
|
+
try { payload = JSON.parse(raw); } catch {
|
|
139
|
+
return { text: "", error: "routed describe returned non-JSON" };
|
|
140
|
+
}
|
|
141
|
+
const content = extractChatContent(payload);
|
|
142
|
+
if (!content) return { text: "", error: "routed describe returned no text" };
|
|
143
|
+
return { text: content };
|
|
144
|
+
} finally {
|
|
145
|
+
detachBodyGuard();
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
149
|
+
console.warn(`[vision] routed describe ${kind} (${Date.now() - t0}ms)`);
|
|
150
|
+
return { text: "", error: redactSecretString(e instanceof Error ? e.message : String(e)) };
|
|
151
|
+
} finally {
|
|
152
|
+
sidecarExit();
|
|
153
|
+
linkedSignal.cleanup();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function extractChatContent(payload: unknown): string | undefined {
|
|
158
|
+
if (!payload || typeof payload !== "object") return undefined;
|
|
159
|
+
const choices = (payload as { choices?: unknown }).choices;
|
|
160
|
+
if (!Array.isArray(choices) || choices.length === 0) return undefined;
|
|
161
|
+
const message = (choices[0] as { message?: unknown })?.message;
|
|
162
|
+
if (!message || typeof message !== "object") return undefined;
|
|
163
|
+
const content = (message as { content?: unknown }).content;
|
|
164
|
+
if (typeof content === "string" && content.trim().length > 0) return content;
|
|
165
|
+
// Some adapters emit content parts; join text parts.
|
|
166
|
+
if (Array.isArray(content)) {
|
|
167
|
+
const joined = content
|
|
168
|
+
.map(part => (part && typeof part === "object" && typeof (part as { text?: unknown }).text === "string"
|
|
169
|
+
? (part as { text: string }).text
|
|
170
|
+
: ""))
|
|
171
|
+
.join("");
|
|
172
|
+
if (joined.trim().length > 0) return joined;
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inclusive integer bounds for `visionSidecar.timeoutMs`.
|
|
3
|
+
* The ceiling is the 32-bit timer delay used by `signalWithTimeout` / `setTimeout`.
|
|
4
|
+
* This module is the single authority: the runtime, management API, and Dashboard
|
|
5
|
+
* import these numbers rather than restating them.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_VISION_TIMEOUT_MS = 45_000;
|
|
8
|
+
export const MIN_VISION_TIMEOUT_MS = 1;
|
|
9
|
+
export const MAX_VISION_TIMEOUT_MS = 2_147_483_647;
|