@yansigit/opencodex 2.31.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/LICENSE +21 -0
- package/README.md +303 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +587 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
- package/gui/dist/assets/index-CGoDO3uO.css +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +2 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +108 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +1327 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +121 -0
- package/src/adapters/client-fingerprint.ts +65 -0
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +288 -0
- package/src/adapters/cursor/discovery.ts +333 -0
- package/src/adapters/cursor/effort-map.ts +151 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -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/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +269 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1653 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +76 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +547 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +663 -0
- package/src/adapters/cursor/protobuf-events.ts +1381 -0
- package/src/adapters/cursor/protobuf-request.ts +1032 -0
- package/src/adapters/cursor/request-builder.ts +461 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +735 -0
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +79 -0
- package/src/adapters/cursor/types.ts +79 -0
- package/src/adapters/cursor.ts +322 -0
- package/src/adapters/google-antigravity-hosts.ts +48 -0
- package/src/adapters/google-antigravity-replay.ts +827 -0
- package/src/adapters/google-antigravity-tools.ts +105 -0
- package/src/adapters/google-antigravity-wire.ts +141 -0
- package/src/adapters/google-errors.ts +92 -0
- package/src/adapters/google-http.ts +460 -0
- package/src/adapters/google-tool-schema.ts +238 -0
- package/src/adapters/google-truncation.ts +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +1377 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +112 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1936 -0
- package/src/adapters/mimo-free.ts +280 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1980 -0
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1911 -0
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +154 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +1986 -0
- package/src/chat/inbound.ts +319 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +266 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +205 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +615 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +107 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +578 -0
- package/src/claude/model-info.ts +174 -0
- package/src/claude/outbound.ts +926 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +302 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +737 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +299 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +290 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +213 -0
- package/src/cli/claude.ts +355 -0
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +47 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +127 -0
- package/src/cli/config-command.ts +209 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/dispatch.ts +585 -0
- package/src/cli/doctor.ts +1202 -0
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +213 -0
- package/src/cli/help.ts +101 -0
- package/src/cli/index.ts +973 -0
- package/src/cli/init.ts +211 -0
- package/src/cli/integrations.ts +260 -0
- package/src/cli/interactive-confirm.ts +133 -0
- 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 +245 -0
- package/src/cli/models.ts +422 -0
- package/src/cli/observe.ts +206 -0
- package/src/cli/opencode.ts +588 -0
- package/src/cli/provider-replit.ts +232 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +492 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +422 -0
- package/src/cli/replit-gateway-key-input.ts +138 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +328 -0
- package/src/cli/system-command.ts +112 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +199 -0
- package/src/cli/v2.ts +268 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1704 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +47 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +544 -0
- package/src/codex/account-usability.ts +43 -0
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/agent-roles-sync.ts +225 -0
- package/src/codex/agent-roles.ts +238 -0
- package/src/codex/app-server-processes.ts +1143 -0
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +2147 -0
- package/src/codex/auth-collision.ts +109 -0
- package/src/codex/auth-context.ts +665 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +436 -0
- package/src/codex/catalog/bundled.ts +549 -0
- package/src/codex/catalog/effort.ts +446 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +664 -0
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +650 -0
- package/src/codex/catalog/provider-fetch.ts +2064 -0
- package/src/codex/catalog/sync.ts +1883 -0
- 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 +14 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1566 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +108 -0
- package/src/codex/history-provider.ts +979 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1733 -0
- package/src/codex/injected-marker.ts +106 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +225 -0
- package/src/codex/log-guard/inspect.ts +506 -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 +56 -0
- package/src/codex/main-account.ts +68 -0
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +273 -0
- package/src/codex/model-entitlements.ts +353 -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 +492 -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 +682 -0
- package/src/codex/paths.ts +144 -0
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +425 -0
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +573 -0
- package/src/codex/refresh.ts +62 -0
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +1888 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +2170 -0
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +784 -0
- package/src/codex/sync.ts +319 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +298 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +160 -0
- package/src/combos/index.ts +45 -0
- package/src/combos/request.ts +94 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +398 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +4041 -0
- package/src/fork/register.ts +3 -0
- package/src/generated/compatibility-version.json +3116 -0
- package/src/generated/model-metadata.ts +106 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +530 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +955 -0
- package/src/images/plan.ts +143 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- 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.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -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/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +195 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +346 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +184 -0
- package/src/lib/bun-stream-caps.ts +130 -0
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +364 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +380 -0
- package/src/lib/errors.ts +406 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -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/open-url.ts +25 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +270 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-outbound.ts +210 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +521 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +25 -0
- package/src/lib/shadow-call.ts +61 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +119 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/token-estimate.ts +86 -0
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +400 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +392 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-atomic-replace.ts +156 -0
- package/src/lib/windows-elevation.ts +773 -0
- package/src/lib/windows-secret-acl.ts +854 -0
- 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 +403 -0
- 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-routing.ts +594 -0
- package/src/oauth/anthropic.ts +188 -0
- package/src/oauth/antigravity-routing.ts +151 -0
- package/src/oauth/callback-server.ts +300 -0
- package/src/oauth/chatgpt.ts +161 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +252 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +262 -0
- package/src/oauth/health.ts +407 -0
- package/src/oauth/index.ts +1504 -0
- package/src/oauth/key-providers.ts +124 -0
- package/src/oauth/kimi.ts +227 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -0
- package/src/oauth/local-token-detect.ts +130 -0
- package/src/oauth/log.ts +50 -0
- package/src/oauth/login-cli.ts +223 -0
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +728 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +695 -0
- package/src/providers/antigravity-quota.ts +216 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +74 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +82 -0
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +586 -0
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +449 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +243 -0
- package/src/providers/openai-tier-startup.ts +56 -0
- package/src/providers/openai-tiers.ts +423 -0
- package/src/providers/openai-virtual-models.ts +83 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +185 -0
- package/src/providers/quota.ts +2345 -0
- package/src/providers/registry.ts +2918 -0
- package/src/providers/replit/constants.ts +27 -0
- package/src/providers/replit/derive.ts +85 -0
- package/src/providers/replit/headers.ts +28 -0
- package/src/providers/replit/origin.ts +55 -0
- package/src/providers/replit/pair-install-response.ts +72 -0
- package/src/providers/replit/probe.ts +199 -0
- package/src/providers/replit/setup.ts +350 -0
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +103 -0
- 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 +148 -0
- package/src/reasoning-effort.ts +183 -0
- package/src/responses/compaction.ts +142 -0
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +838 -0
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +165 -0
- package/src/responses/spill-store.ts +459 -0
- package/src/responses/state.ts +1433 -0
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +761 -0
- package/src/routing/analytics.ts +378 -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 +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +53 -0
- package/src/server/auth-cors.ts +751 -0
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +442 -0
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +1030 -0
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +190 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +568 -0
- package/src/server/index.ts +1813 -0
- package/src/server/lifecycle.ts +498 -0
- package/src/server/live.ts +717 -0
- 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 +1433 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +193 -0
- package/src/server/management/body.ts +41 -0
- package/src/server/management/combo-routes.ts +263 -0
- package/src/server/management/config-routes.ts +835 -0
- package/src/server/management/context.ts +113 -0
- 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 +586 -0
- package/src/server/management/model-routes.ts +560 -0
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +637 -0
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +1033 -0
- package/src/server/management/replit-provider-routes.ts +86 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +385 -0
- package/src/server/management/shared.ts +286 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- 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 +435 -0
- package/src/server/management/system-routes.ts +194 -0
- package/src/server/management/usage-summary-cache.ts +94 -0
- 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 +314 -0
- package/src/server/management-auth.ts +482 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +328 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1209 -0
- package/src/server/request-decompress.ts +239 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1259 -0
- 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/collaboration.ts +551 -0
- package/src/server/responses/compact.ts +771 -0
- package/src/server/responses/core.ts +5389 -0
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +331 -0
- package/src/server/responses/fetch-helpers.ts +232 -0
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +251 -0
- package/src/server/responses/upstream-error.ts +53 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +272 -0
- 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 +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +25 -0
- package/src/server/search.ts +201 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +315 -0
- package/src/server/startup-health-cache.ts +131 -0
- package/src/server/system-env.ts +484 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +472 -0
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +3575 -0
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +364 -0
- package/src/tray/windows.ts +757 -0
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +876 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +106 -0
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +415 -0
- package/src/update/job.ts +1887 -0
- package/src/update/notify.ts +263 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +625 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +416 -0
- package/src/usage/log.ts +1223 -0
- package/src/usage/summary.ts +753 -0
- package/src/usage/totals.ts +14 -0
- 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 +189 -0
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +131 -0
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +681 -0
- 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 +195 -0
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +113 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +331 -0
- package/src/web-search/loop.ts +896 -0
- package/src/web-search/parse.ts +315 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/synthetic-tool.ts +47 -0
- package/src/web-search/xai-executor.ts +219 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fingerprint";
|
|
3
|
+
import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
|
|
4
|
+
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
5
|
+
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
6
|
+
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
7
|
+
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic";
|
|
8
|
+
import type { DescribeOutcome, VisionSettings } from "./describe";
|
|
9
|
+
|
|
10
|
+
const ANTHROPIC_VISION_MAX_TOKENS = 1024;
|
|
11
|
+
const ALLOWED_IMAGE_MIME = new Set(["image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif"]);
|
|
12
|
+
const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
13
|
+
const DESCRIBE_INSTRUCTION =
|
|
14
|
+
"You are a vision describer for a text-only model that cannot see the image. Describe the image " +
|
|
15
|
+
"thoroughly and factually so that model can fully reason about it: transcribe any visible text " +
|
|
16
|
+
"verbatim, and note UI/layout, colors, branding/logos, charts, and notable details. Focus on " +
|
|
17
|
+
"what's relevant to the user's request. Output only the description.";
|
|
18
|
+
|
|
19
|
+
type AnthropicImageBlock =
|
|
20
|
+
| { type: "image"; source: { type: "base64"; media_type: string; data: string } }
|
|
21
|
+
| { type: "image"; source: { type: "url"; url: string } };
|
|
22
|
+
|
|
23
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildImageBlock(imageUrl: string): { block?: AnthropicImageBlock; error?: string } {
|
|
28
|
+
if (imageUrl.startsWith("data:")) {
|
|
29
|
+
// Anthropic's base64 image source requires actual base64 bytes, so a non-base64 data URL
|
|
30
|
+
// (e.g. `data:image/png,raw`) is rejected here. This is intentionally stricter than the OpenAI
|
|
31
|
+
// vision executor, which forwards the raw data URL to `image_url` (review F3, documented delta).
|
|
32
|
+
const match = /^data:([^;,]+?)(;base64)?,(.*)$/s.exec(imageUrl);
|
|
33
|
+
if (!match || !match[2]) return { error: "malformed data URL" };
|
|
34
|
+
const mime = match[1].toLowerCase();
|
|
35
|
+
if (!ALLOWED_IMAGE_MIME.has(mime)) return { error: `unsupported image type "${mime}"` };
|
|
36
|
+
const bytes = Math.floor((match[3].length * 3) / 4);
|
|
37
|
+
if (bytes > MAX_IMAGE_BYTES) return { error: `image too large (~${Math.round(bytes / 1024 / 1024)}MB)` };
|
|
38
|
+
return { block: { type: "image", source: { type: "base64", media_type: mime, data: match[3] } } };
|
|
39
|
+
}
|
|
40
|
+
if (imageUrl.startsWith("https://")) {
|
|
41
|
+
return { block: { type: "image", source: { type: "url", url: imageUrl } } };
|
|
42
|
+
}
|
|
43
|
+
return { error: "unsupported image URL scheme (expected data: or https:)" };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Fold Anthropic Messages text deltas into one description. Malformed frames are ignored. */
|
|
47
|
+
export async function parseAnthropicVisionSSE(res: Response): Promise<DescribeOutcome> {
|
|
48
|
+
if (!res.body) return { text: "", error: "anthropic vision sidecar returned no response body" };
|
|
49
|
+
|
|
50
|
+
let text = "";
|
|
51
|
+
let terminalError = "";
|
|
52
|
+
const decoder = new TextDecoder();
|
|
53
|
+
const reader = res.body.getReader();
|
|
54
|
+
let buffer = "";
|
|
55
|
+
|
|
56
|
+
const processFrame = (rawFrame: string): void => {
|
|
57
|
+
let dataLine = "";
|
|
58
|
+
for (const line of rawFrame.split("\n")) {
|
|
59
|
+
if (line.startsWith("data:")) dataLine += line.slice(line.startsWith("data: ") ? 6 : 5);
|
|
60
|
+
}
|
|
61
|
+
if (!dataLine || dataLine === "[DONE]") return;
|
|
62
|
+
let data: unknown;
|
|
63
|
+
try { data = JSON.parse(dataLine); } catch { return; }
|
|
64
|
+
if (!isRecord(data)) return;
|
|
65
|
+
|
|
66
|
+
if (data.type === "content_block_delta") {
|
|
67
|
+
const delta = isRecord(data.delta) ? data.delta : {};
|
|
68
|
+
if (delta.type === "text_delta" && typeof delta.text === "string") text += delta.text;
|
|
69
|
+
} else if (data.type === "error") {
|
|
70
|
+
// Provider-authored stream errors can contain credentials, paths, or response bodies.
|
|
71
|
+
terminalError = "anthropic vision sidecar stream error";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
for (;;) {
|
|
77
|
+
const { done, value } = await reader.read();
|
|
78
|
+
if (done) break;
|
|
79
|
+
buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n/g, "\n");
|
|
80
|
+
let separator: number;
|
|
81
|
+
while ((separator = buffer.indexOf("\n\n")) !== -1) {
|
|
82
|
+
processFrame(buffer.slice(0, separator));
|
|
83
|
+
buffer = buffer.slice(separator + 2);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
buffer = (buffer + decoder.decode()).replace(/\r\n/g, "\n");
|
|
87
|
+
if (buffer.trim()) processFrame(buffer);
|
|
88
|
+
} catch {
|
|
89
|
+
// A mid-stream read/decode failure after partial text is NOT a usable description. Mark it
|
|
90
|
+
// terminal so the caller returns an error and never caches an incomplete result (review F1).
|
|
91
|
+
if (!terminalError) terminalError = "anthropic vision sidecar stream ended abnormally";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const trimmed = text.trim();
|
|
95
|
+
// A terminal error (an in-stream `error` frame OR an abnormal body failure) invalidates any partial
|
|
96
|
+
// text: return an error outcome so vision/index.ts never caches an incomplete description (review F1).
|
|
97
|
+
if (terminalError) return { text: "", error: terminalError };
|
|
98
|
+
if (!trimmed) return { text: "", error: "anthropic vision sidecar produced no description" };
|
|
99
|
+
return { text: trimmed };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Describe one image through a stored Anthropic OAuth credential. Never throws. */
|
|
103
|
+
export async function describeImageAnthropic(
|
|
104
|
+
imageUrl: string,
|
|
105
|
+
detail: string | undefined,
|
|
106
|
+
contextText: string,
|
|
107
|
+
providerName: string,
|
|
108
|
+
provider: OcxProviderConfig,
|
|
109
|
+
settings: VisionSettings,
|
|
110
|
+
abortSignal?: AbortSignal,
|
|
111
|
+
): Promise<DescribeOutcome> {
|
|
112
|
+
const image = buildImageBlock(imageUrl);
|
|
113
|
+
if (!image.block) return { text: "", error: image.error ?? "invalid image" };
|
|
114
|
+
|
|
115
|
+
let token: string;
|
|
116
|
+
try {
|
|
117
|
+
token = await getValidAccessToken(providerName);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(error)}` };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const headers: Record<string, string> = {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
"anthropic-version": "2023-06-01",
|
|
125
|
+
"Accept": "text/event-stream",
|
|
126
|
+
"User-Agent": "@anthropic-ai/sdk/0.74.0",
|
|
127
|
+
"Authorization": `Bearer ${token}`,
|
|
128
|
+
"anthropic-beta": ANTHROPIC_OAUTH_BETA,
|
|
129
|
+
...CLAUDE_CODE_HEADERS,
|
|
130
|
+
"X-Claude-Code-Session-Id": claudeCodeSessionId(token),
|
|
131
|
+
"x-client-request-id": crypto.randomUUID(),
|
|
132
|
+
};
|
|
133
|
+
if (provider.headers) Object.assign(headers, provider.headers);
|
|
134
|
+
|
|
135
|
+
const content: unknown[] = [];
|
|
136
|
+
if (contextText) content.push({ type: "text", text: `The user's request about this image: ${contextText}` });
|
|
137
|
+
content.push(image.block);
|
|
138
|
+
const body = {
|
|
139
|
+
model: settings.model,
|
|
140
|
+
max_tokens: ANTHROPIC_VISION_MAX_TOKENS,
|
|
141
|
+
thinking: { type: "disabled" },
|
|
142
|
+
system: [
|
|
143
|
+
{ type: "text", text: CLAUDE_CODE_SYSTEM_INSTRUCTION },
|
|
144
|
+
{ type: "text", text: DESCRIBE_INSTRUCTION },
|
|
145
|
+
],
|
|
146
|
+
messages: [{ role: "user", content }],
|
|
147
|
+
stream: true,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Anthropic image blocks have no detail field, but detail remains part of the cache identity.
|
|
151
|
+
void detail;
|
|
152
|
+
const base = provider.baseUrl.replace(/\/v1\/?$/, "");
|
|
153
|
+
const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
|
|
154
|
+
const sidecarExit = sidecarEnter("vision");
|
|
155
|
+
const startedAt = Date.now();
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetchWithResetRetry(
|
|
158
|
+
() => fetch(`${base}/v1/messages`, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers,
|
|
161
|
+
body: JSON.stringify(body),
|
|
162
|
+
signal: linkedSignal.signal,
|
|
163
|
+
}),
|
|
164
|
+
{ abortSignal: linkedSignal.signal, label: "vision-sidecar-anthropic" },
|
|
165
|
+
);
|
|
166
|
+
if (!res.ok) {
|
|
167
|
+
const responseText = await res.text().catch(() => "");
|
|
168
|
+
console.warn(`[vision] anthropic sidecar HTTP ${res.status} (${Date.now() - startedAt}ms)`);
|
|
169
|
+
if (res.status === 401) {
|
|
170
|
+
return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(responseText))}` };
|
|
171
|
+
}
|
|
172
|
+
// Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics.
|
|
173
|
+
return { text: "", error: `anthropic vision sidecar HTTP ${res.status}` };
|
|
174
|
+
}
|
|
175
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
176
|
+
try {
|
|
177
|
+
return await parseAnthropicVisionSSE(res);
|
|
178
|
+
} finally {
|
|
179
|
+
detachBodyGuard();
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const kind = error instanceof Error && error.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
183
|
+
console.warn(`[vision] anthropic sidecar ${kind} (${Date.now() - startedAt}ms)`);
|
|
184
|
+
return { text: "", error: `anthropic vision sidecar ${kind}` };
|
|
185
|
+
} finally {
|
|
186
|
+
sidecarExit();
|
|
187
|
+
linkedSignal.cleanup();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which backends may DESCRIBE images for the vision sidecar, and which
|
|
3
|
+
* candidate rows each can describe through (#2188 vision rules; roadmap 170
|
|
4
|
+
* REVISED: the "routed" backend).
|
|
5
|
+
*
|
|
6
|
+
* A SIBLING of WEB_SEARCH_BACKENDS, not a shared table: vision has no
|
|
7
|
+
* per-model probe (rule 2 is "− provably text-only", enforced by
|
|
8
|
+
* modelAcceptsImageInput, not here), carries per-side baseline models, and
|
|
9
|
+
* excludes non-LLM backends like exa.
|
|
10
|
+
*
|
|
11
|
+
* Three backends, not one per provider: "openai" and "anthropic" carry auth
|
|
12
|
+
* semantics loopback routing cannot replicate (forwarded ChatGPT headers,
|
|
13
|
+
* OAuth beta fences) and their defaults must not drift. Every OTHER
|
|
14
|
+
* picker-visible provider row reaches the describer through "routed" — a
|
|
15
|
+
* loopback self-fetch of the proxy's own /v1/chat/completions, where the
|
|
16
|
+
* router and adapters already speak each provider's wire. That is what makes
|
|
17
|
+
* this table closed under provider growth: a new provider needs no new
|
|
18
|
+
* describe executor.
|
|
19
|
+
*/
|
|
20
|
+
import type { OcxConfig } from "../types";
|
|
21
|
+
import type { SidecarAuthState } from "../sidecar/auth";
|
|
22
|
+
import { listOpenAiForwardSidecarCandidates } from "../providers/openai-sidecar";
|
|
23
|
+
import type { VisionCandidateModel, VisionSidecarBackend } from "./eligibility";
|
|
24
|
+
|
|
25
|
+
export interface VisionBackendDescriptor {
|
|
26
|
+
backend: VisionSidecarBackend;
|
|
27
|
+
/** Liveness signal for this backend. */
|
|
28
|
+
isActive(auth: SidecarAuthState, config: OcxConfig): boolean;
|
|
29
|
+
/** Which candidate rows this backend's describe executor can actually run. */
|
|
30
|
+
candidateMatch(candidate: VisionCandidateModel, auth: SidecarAuthState): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Default entry for this side: cheap, image-capable, present in every
|
|
33
|
+
* deployment. Only the two universal sides carry one — "routed" has no
|
|
34
|
+
* universal model to name.
|
|
35
|
+
*/
|
|
36
|
+
baseline?: string;
|
|
37
|
+
/** Stable option ordering (baselines first within a side). */
|
|
38
|
+
rank: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const VISION_BACKENDS: readonly VisionBackendDescriptor[] = [
|
|
42
|
+
{
|
|
43
|
+
backend: "openai",
|
|
44
|
+
// The OpenAI describer needs a CANONICAL ChatGPT forward provider, not
|
|
45
|
+
// merely a provider keyed "openai" — same predicate the runtime sidecar
|
|
46
|
+
// resolver uses. Deliberately NOT auth.isCodexAuth: tightening to a live
|
|
47
|
+
// credential here would change which options a fresh install sees, and
|
|
48
|
+
// the options list is a suggestion surface, not the write gate.
|
|
49
|
+
isActive: (_auth, config) => listOpenAiForwardSidecarCandidates(config).length > 0,
|
|
50
|
+
candidateMatch: candidate => candidate.native === true || candidate.provider === "openai",
|
|
51
|
+
baseline: "gpt-5.6-luna",
|
|
52
|
+
rank: 0,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
backend: "anthropic",
|
|
56
|
+
isActive: auth => auth.isAnthropicAuth,
|
|
57
|
+
// The runtime dispatches through exactly ONE Anthropic provider — the
|
|
58
|
+
// resolved OAuth row. Same-adapter keyed rows are unreachable (see
|
|
59
|
+
// visionBackendForCandidate's original stance).
|
|
60
|
+
candidateMatch: (candidate, auth) => candidate.provider === auth.anthropicProviderName,
|
|
61
|
+
baseline: "claude-haiku-4-5",
|
|
62
|
+
rank: 1,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
backend: "routed",
|
|
66
|
+
// Always offered: options only materialize when a matching picker row
|
|
67
|
+
// exists, and the row's own provider config is the liveness signal — the
|
|
68
|
+
// loopback request fails closed through ordinary routing errors.
|
|
69
|
+
isActive: () => true,
|
|
70
|
+
// Any row the other two executors do NOT own. Auth-slot rows are
|
|
71
|
+
// entitlements of the openai/anthropic sides and never route here.
|
|
72
|
+
candidateMatch: (candidate, auth) =>
|
|
73
|
+
candidate.native !== true
|
|
74
|
+
&& candidate.provider !== "openai"
|
|
75
|
+
&& candidate.provider !== auth.anthropicProviderName,
|
|
76
|
+
rank: 2,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export function visionBackendDescriptor(backend: VisionSidecarBackend): VisionBackendDescriptor {
|
|
81
|
+
const descriptor = VISION_BACKENDS.find(entry => entry.backend === backend);
|
|
82
|
+
if (!descriptor) throw new Error(`unknown vision backend "${backend}"`);
|
|
83
|
+
return descriptor;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The active backend set for option generation. Falls back to the two
|
|
88
|
+
* UNIVERSAL sides when neither is active (fresh install: picker stays
|
|
89
|
+
* populated, permissive-unknown rule); "routed" is active by construction.
|
|
90
|
+
*/
|
|
91
|
+
export function activeVisionBackends(auth: SidecarAuthState, config: OcxConfig): VisionSidecarBackend[] {
|
|
92
|
+
const active = VISION_BACKENDS.filter(entry => entry.isActive(auth, config)).map(entry => entry.backend);
|
|
93
|
+
return active.includes("openai") || active.includes("anthropic")
|
|
94
|
+
? active
|
|
95
|
+
: ["openai", "anthropic", ...active.filter(backend => backend === "routed")];
|
|
96
|
+
}
|
|
97
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
import type { VisionReasoningEffort } from "../reasoning-effort";
|
|
3
|
+
import { FORWARD_HEADERS } from "../adapters/openai-responses";
|
|
4
|
+
import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
|
|
5
|
+
import { redactSecretString } from "../lib/redact";
|
|
6
|
+
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
7
|
+
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
8
|
+
import { parseSidecarSSE } from "../web-search/parse";
|
|
9
|
+
import type { SidecarOutcomeRecorder } from "../web-search/executor";
|
|
10
|
+
|
|
11
|
+
export interface VisionSettings {
|
|
12
|
+
model: string;
|
|
13
|
+
reasoning: VisionReasoningEffort;
|
|
14
|
+
timeoutMs: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A description, or an `error` string when it couldn't run (caller injects a graceful marker). */
|
|
18
|
+
export type DescribeOutcome = { text: string; error?: string };
|
|
19
|
+
|
|
20
|
+
const ALLOWED_IMAGE_MIME = new Set(["image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif"]);
|
|
21
|
+
/** ~20 MB — generous enough for screenshots; rejects pathological payloads before forwarding. */
|
|
22
|
+
const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate an image URL before forwarding. Data URLs are checked for an allowed media type and a sane
|
|
26
|
+
* decoded size (a malformed/huge/unsupported one would otherwise 400 at the backend or waste tokens).
|
|
27
|
+
* Remote https URLs are passed through — the ChatGPT backend fetches them, not this proxy (so there's
|
|
28
|
+
* no SSRF surface here). Returns an error string when the URL must be rejected, else null.
|
|
29
|
+
*/
|
|
30
|
+
function validateImageUrl(url: string): string | null {
|
|
31
|
+
if (url.startsWith("data:")) {
|
|
32
|
+
const m = /^data:([^;,]+?)(;base64)?,(.*)$/s.exec(url);
|
|
33
|
+
if (!m) return "malformed data URL";
|
|
34
|
+
const mime = m[1].toLowerCase();
|
|
35
|
+
if (!ALLOWED_IMAGE_MIME.has(mime)) return `unsupported image type "${mime}"`;
|
|
36
|
+
if (m[2]) {
|
|
37
|
+
const bytes = Math.floor((m[3].length * 3) / 4);
|
|
38
|
+
if (bytes > MAX_IMAGE_BYTES) return `image too large (~${Math.round(bytes / 1024 / 1024)}MB)`;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (url.startsWith("https://")) return null;
|
|
43
|
+
return "unsupported image URL scheme (expected data: or https:)";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Describe ONE image via a gpt vision model through the ChatGPT forward backend — the path that has
|
|
48
|
+
* native image input. Reuses selected forwarded OAuth headers. The user's own request text is
|
|
49
|
+
* passed as context so the description is focused. Never throws — returns `{error}` on failure.
|
|
50
|
+
*/
|
|
51
|
+
export async function describeImage(
|
|
52
|
+
imageUrl: string,
|
|
53
|
+
detail: string | undefined,
|
|
54
|
+
contextText: string,
|
|
55
|
+
forwardProvider: OcxProviderConfig,
|
|
56
|
+
selectedForwardHeaders: Headers,
|
|
57
|
+
settings: VisionSettings,
|
|
58
|
+
abortSignal?: AbortSignal,
|
|
59
|
+
recordOutcome?: SidecarOutcomeRecorder,
|
|
60
|
+
): Promise<DescribeOutcome> {
|
|
61
|
+
const invalid = validateImageUrl(imageUrl);
|
|
62
|
+
if (invalid) return { text: "", error: invalid };
|
|
63
|
+
|
|
64
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
65
|
+
if (forwardProvider.headers) Object.assign(headers, forwardProvider.headers);
|
|
66
|
+
for (const h of FORWARD_HEADERS) {
|
|
67
|
+
const v = selectedForwardHeaders.get(h);
|
|
68
|
+
if (v) headers[h] = v;
|
|
69
|
+
}
|
|
70
|
+
const content: unknown[] = [];
|
|
71
|
+
if (contextText) content.push({ type: "input_text", text: `The user's request about this image: ${contextText}` });
|
|
72
|
+
content.push({ type: "input_image", image_url: imageUrl, detail: detail ?? "high" });
|
|
73
|
+
|
|
74
|
+
const body = {
|
|
75
|
+
model: settings.model,
|
|
76
|
+
instructions:
|
|
77
|
+
"You are a vision describer for a text-only model that cannot see the image. Describe the image " +
|
|
78
|
+
"thoroughly and factually so that model can fully reason about it: transcribe any visible text " +
|
|
79
|
+
"verbatim, and note UI/layout, colors, branding/logos, charts, and notable details. Focus on " +
|
|
80
|
+
"what's relevant to the user's request. Output only the description.",
|
|
81
|
+
input: [{ type: "message", role: "user", content }],
|
|
82
|
+
reasoning: { effort: settings.reasoning },
|
|
83
|
+
// The ChatGPT (codex) backend rejects `max_output_tokens` ("Unsupported parameter"); the shared
|
|
84
|
+
// SSE parser bounds raw response bytes before DESC_MAX_CHARS applies its display clamp.
|
|
85
|
+
store: false,
|
|
86
|
+
stream: true,
|
|
87
|
+
};
|
|
88
|
+
const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
|
|
89
|
+
const sidecarExit = sidecarEnter("vision");
|
|
90
|
+
const t0 = Date.now();
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetchWithResetRetry(
|
|
93
|
+
() => fetch(`${forwardProvider.baseUrl}/responses`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers,
|
|
96
|
+
body: JSON.stringify(body),
|
|
97
|
+
signal: linkedSignal.signal,
|
|
98
|
+
// Credential-bearing: do not follow a cross-origin 3xx. Bun strips `Authorization`
|
|
99
|
+
// across origins but forwards nonstandard headers such as `chatgpt-account-id`,
|
|
100
|
+
// `session_id`, and `x-codex-turn-metadata` to the redirect target.
|
|
101
|
+
redirect: "manual",
|
|
102
|
+
}),
|
|
103
|
+
{ abortSignal: linkedSignal.signal, label: "vision-sidecar" },
|
|
104
|
+
);
|
|
105
|
+
recordOutcome?.(res.status);
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const t = await res.text().catch(() => "");
|
|
108
|
+
console.warn(`[vision] sidecar HTTP ${res.status} (${Date.now() - t0}ms)`);
|
|
109
|
+
return { text: "", error: `vision sidecar HTTP ${res.status}: ${redactSecretString(t.slice(0, 200))}` };
|
|
110
|
+
}
|
|
111
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = await parseSidecarSSE(res);
|
|
115
|
+
} finally {
|
|
116
|
+
detachBodyGuard();
|
|
117
|
+
}
|
|
118
|
+
// The backend can return HTTP 200 then stream a `response.failed`/`error` event with no text;
|
|
119
|
+
// surface that as a describe error instead of an empty (silently-blank) description.
|
|
120
|
+
if (!parsed.text.trim() && parsed.error) return { text: "", error: parsed.error };
|
|
121
|
+
return { text: parsed.text };
|
|
122
|
+
} catch (e) {
|
|
123
|
+
recordOutcome?.(e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error");
|
|
124
|
+
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
125
|
+
console.warn(`[vision] sidecar ${kind} (${Date.now() - t0}ms)`);
|
|
126
|
+
return { text: "", error: e instanceof Error ? e.message : String(e) };
|
|
127
|
+
} finally {
|
|
128
|
+
sidecarExit();
|
|
129
|
+
linkedSignal.cleanup();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which models may serve AS the vision sidecar (the describer), as opposed to the
|
|
3
|
+
* models the sidecar describes FOR.
|
|
4
|
+
*
|
|
5
|
+
* Three rules make this non-obvious, and all are load-bearing:
|
|
6
|
+
*
|
|
7
|
+
* 1. `provider.noVisionModels` marks models the proxy describes images for, and
|
|
8
|
+
* `applyProviderConfigHints` deliberately ADDS "image" to their advertised
|
|
9
|
+
* input modalities so the Codex app does not block the attachment client-side.
|
|
10
|
+
* A blind model therefore advertises image input. Membership in that list is a
|
|
11
|
+
* hard disqualifier here, checked BEFORE the modality list it rewrote.
|
|
12
|
+
* 2. The catalog enriches configured providers from the registry before it
|
|
13
|
+
* applies that rule. Eligibility must use the same effective provider policy,
|
|
14
|
+
* including a `noVisionModels` list learned after the config was persisted.
|
|
15
|
+
* 3. Catalog rows frequently omit `inputModalities` entirely (the live
|
|
16
|
+
* `/api/models` response carries none for either openai or anthropic rows).
|
|
17
|
+
* Unknown is not zero: when no source can speak, the model stays eligible
|
|
18
|
+
* rather than silently vanishing from the picker.
|
|
19
|
+
* Native OpenAI metadata is likewise authoritative only for a native row or
|
|
20
|
+
* the canonical OpenAI provider; a routed row with the same id owns its own
|
|
21
|
+
* explicit modalities.
|
|
22
|
+
*/
|
|
23
|
+
import { modelInList, type OcxConfig, type OcxProviderConfig } from "../types";
|
|
24
|
+
import { getModelMetadataCaseInsensitive, resolveMetadataProvider } from "../generated/model-metadata";
|
|
25
|
+
import { nativeInputModalities } from "../codex/catalog/metadata";
|
|
26
|
+
import { SUPPORTED_NATIVE_OPENAI_SLUGS } from "../codex/catalog/native-models";
|
|
27
|
+
import { enrichProviderFromRegistry } from "../providers/derive";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The wire protocols `planVisionSidecar` can dispatch to (#2188 roadmap 170
|
|
31
|
+
* REVISED). "routed" describes through the proxy's OWN router via loopback —
|
|
32
|
+
* one executor for every non-forward, non-OAuth-Anthropic provider row.
|
|
33
|
+
*/
|
|
34
|
+
export type VisionSidecarBackend = "openai" | "anthropic" | "routed";
|
|
35
|
+
|
|
36
|
+
/** The two sides every deployment has; also the empty-auth fallback set. */
|
|
37
|
+
export type UniversalVisionBackend = "openai" | "anthropic";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default entry per backend: cheap, image-capable, and present in every deployment. Offered
|
|
41
|
+
* whenever its side is enabled, and withheld only when that provider explicitly lists it as a
|
|
42
|
+
* model the sidecar describes FOR — never merely because a metadata table stayed silent.
|
|
43
|
+
*
|
|
44
|
+
* Keyed by the UNIVERSAL subset on purpose (roadmap 170, audit blocker A):
|
|
45
|
+
* xai/gemini are auth-gated sides whose catalogs are present whenever the side
|
|
46
|
+
* is, so they carry no baseline, and a narrow-key total record documents that
|
|
47
|
+
* without sprinkling non-null assertions at the consumers.
|
|
48
|
+
*/
|
|
49
|
+
export const BASELINE_VISION_MODELS: Record<UniversalVisionBackend, string> = {
|
|
50
|
+
openai: "gpt-5.6-luna",
|
|
51
|
+
anthropic: "claude-haiku-4-5",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface VisionCandidateModel {
|
|
55
|
+
provider: string;
|
|
56
|
+
id: string;
|
|
57
|
+
inputModalities?: string[];
|
|
58
|
+
native?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface VisionModelOption {
|
|
62
|
+
value: string;
|
|
63
|
+
label: string;
|
|
64
|
+
backend: VisionSidecarBackend;
|
|
65
|
+
/** True when the row is a guaranteed baseline rather than a catalog discovery. */
|
|
66
|
+
baseline?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type EnrichedProviderCache = Map<string, OcxProviderConfig>;
|
|
70
|
+
|
|
71
|
+
function advertisesImageInput(modalities: readonly string[] | undefined): boolean | undefined {
|
|
72
|
+
if (!modalities || modalities.length === 0) return undefined;
|
|
73
|
+
return modalities.includes("image");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Vendor-table modalities for a routed row, or undefined when the table has no opinion. */
|
|
77
|
+
function metadataImageInput(provider: string, modelId: string): boolean | undefined {
|
|
78
|
+
const resolved = resolveMetadataProvider(provider) ?? provider;
|
|
79
|
+
const meta = getModelMetadataCaseInsensitive(resolved, modelId);
|
|
80
|
+
return advertisesImageInput(meta?.input);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mirrors the catalog's registry enrichment without mutating saved configuration. The picker
|
|
85
|
+
* checks many rows from one provider, so its caller shares this small call-local cache.
|
|
86
|
+
*/
|
|
87
|
+
function enrichedProviderForVision(
|
|
88
|
+
config: Pick<OcxConfig, "providers">,
|
|
89
|
+
providerName: string,
|
|
90
|
+
cache: EnrichedProviderCache,
|
|
91
|
+
): OcxProviderConfig | undefined {
|
|
92
|
+
const cached = cache.get(providerName);
|
|
93
|
+
if (cached) return cached;
|
|
94
|
+
const configured = config.providers?.[providerName];
|
|
95
|
+
if (!configured) return undefined;
|
|
96
|
+
const enriched = structuredClone(configured);
|
|
97
|
+
enrichProviderFromRegistry(providerName, enriched);
|
|
98
|
+
cache.set(providerName, enriched);
|
|
99
|
+
return enriched;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isVisionSidecarConsumerWithCache(
|
|
103
|
+
config: Pick<OcxConfig, "providers">,
|
|
104
|
+
providerName: string,
|
|
105
|
+
modelId: string,
|
|
106
|
+
cache: EnrichedProviderCache,
|
|
107
|
+
): boolean {
|
|
108
|
+
return modelInList(enrichedProviderForVision(config, providerName, cache)?.noVisionModels, modelId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Is this model listed as one the sidecar describes FOR? Such a model cannot be
|
|
113
|
+
* the describer, and its advertised modalities are untrustworthy.
|
|
114
|
+
*/
|
|
115
|
+
export function isVisionSidecarConsumer(config: Pick<OcxConfig, "providers">, providerName: string, modelId: string): boolean {
|
|
116
|
+
return isVisionSidecarConsumerWithCache(config, providerName, modelId, new Map());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Can this model accept an image on the wire? Sources are consulted in descending
|
|
121
|
+
* trustworthiness; the first that speaks wins. `undefined` means nothing knows,
|
|
122
|
+
* which callers treat as eligible.
|
|
123
|
+
*/
|
|
124
|
+
export function modelAcceptsImageInput(
|
|
125
|
+
config: Pick<OcxConfig, "providers">,
|
|
126
|
+
candidate: VisionCandidateModel,
|
|
127
|
+
): boolean | undefined {
|
|
128
|
+
return modelAcceptsImageInputWithCache(config, candidate, new Map());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function modelAcceptsImageInputWithCache(
|
|
132
|
+
config: Pick<OcxConfig, "providers">,
|
|
133
|
+
candidate: VisionCandidateModel,
|
|
134
|
+
cache: EnrichedProviderCache,
|
|
135
|
+
): boolean | undefined {
|
|
136
|
+
if (isVisionSidecarConsumerWithCache(config, candidate.provider, candidate.id, cache)) return false;
|
|
137
|
+
if (candidate.native === true || (candidate.provider === "openai" && SUPPORTED_NATIVE_OPENAI_SLUGS.has(candidate.id))) {
|
|
138
|
+
return advertisesImageInput(nativeInputModalities(candidate.id)) ?? true;
|
|
139
|
+
}
|
|
140
|
+
const fromRow = advertisesImageInput(candidate.inputModalities);
|
|
141
|
+
if (fromRow !== undefined) return fromRow;
|
|
142
|
+
return metadataImageInput(candidate.provider, candidate.id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Eligible = not a sidecar consumer, and not positively known to be text-only. */
|
|
146
|
+
export function isVisionEligibleModel(
|
|
147
|
+
config: Pick<OcxConfig, "providers">,
|
|
148
|
+
candidate: VisionCandidateModel,
|
|
149
|
+
): boolean {
|
|
150
|
+
return isVisionEligibleModelWithCache(config, candidate, new Map());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function isVisionEligibleModelWithCache(
|
|
154
|
+
config: Pick<OcxConfig, "providers">,
|
|
155
|
+
candidate: VisionCandidateModel,
|
|
156
|
+
cache: EnrichedProviderCache,
|
|
157
|
+
): boolean {
|
|
158
|
+
return modelAcceptsImageInputWithCache(config, candidate, cache) !== false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Which executor can describe through this row. */
|
|
162
|
+
export function visionBackendForCandidate(
|
|
163
|
+
config: Pick<OcxConfig, "providers">,
|
|
164
|
+
candidate: VisionCandidateModel,
|
|
165
|
+
anthropicProviderName?: string,
|
|
166
|
+
): VisionSidecarBackend | undefined {
|
|
167
|
+
if (candidate.native || candidate.provider === "openai") return "openai";
|
|
168
|
+
// The runtime dispatches through exactly ONE Anthropic provider — the enabled OAuth row
|
|
169
|
+
// with a healthy active account that `findAnthropicVisionProvider` picks. Speaking the
|
|
170
|
+
// Messages wire is not enough: a key-auth row of the same adapter is unreachable, and an
|
|
171
|
+
// option that cannot be dispatched is worse than a missing one, because selecting it fails
|
|
172
|
+
// at describe time rather than at pick time.
|
|
173
|
+
if (anthropicProviderName !== undefined && candidate.provider === anthropicProviderName) {
|
|
174
|
+
return "anthropic";
|
|
175
|
+
}
|
|
176
|
+
// EVERY other provider row describes through the proxy's own router
|
|
177
|
+
// (roadmap 170 revised): the loopback executor covers all provider wires,
|
|
178
|
+
// so no row is left without an executor.
|
|
179
|
+
return "routed";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function baselineCandidate(
|
|
183
|
+
backend: UniversalVisionBackend,
|
|
184
|
+
anthropicProviderName: string | undefined,
|
|
185
|
+
): VisionCandidateModel {
|
|
186
|
+
return {
|
|
187
|
+
provider: backend === "openai" ? "openai" : anthropicProviderName ?? "anthropic",
|
|
188
|
+
id: BASELINE_VISION_MODELS[backend],
|
|
189
|
+
// Both baselines are known image-capable. This makes their only exclusion path the
|
|
190
|
+
// provider's explicit consumer list, never a silent or stale metadata table.
|
|
191
|
+
inputModalities: ["text", "image"],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The picker's option list: every eligible row reachable by an enabled
|
|
197
|
+
* executor, plus each enabled universal side's baseline unless that baseline
|
|
198
|
+
* is explicitly excluded, de-duplicated and stably ordered (side rank order,
|
|
199
|
+
* baselines first within a side). Anthropic rows must belong to the OAuth
|
|
200
|
+
* provider that would actually execute them, so `anthropicProviderName` is
|
|
201
|
+
* what makes that side's catalog rows eligible at all; xai/gemini rows map by
|
|
202
|
+
* provider identity and appear only when the caller enabled those backends.
|
|
203
|
+
*
|
|
204
|
+
* This is the SUGGESTION list (narrow): it emits only rows an executor can reach
|
|
205
|
+
* and some source has heard of. It is deliberately NOT the same set as the write
|
|
206
|
+
* gate. `modelAcceptsImageInput` answers "can we prove this model cannot see?"
|
|
207
|
+
* and is the only input to rejection. Absence from this list must never imply
|
|
208
|
+
* rejection — an unknown id stays eligible via the undefined → eligible fallback.
|
|
209
|
+
*
|
|
210
|
+
* De-duplication is by BARE model id, first eligible row wins. Two providers of
|
|
211
|
+
* the same adapter family can expose the same id; they resolve to the same
|
|
212
|
+
* backend, and only `value` reaches the client, so first-wins costs nothing.
|
|
213
|
+
*/
|
|
214
|
+
export function visionEligibleModelOptions(
|
|
215
|
+
config: Pick<OcxConfig, "providers">,
|
|
216
|
+
candidates: readonly VisionCandidateModel[],
|
|
217
|
+
enabledBackends: readonly VisionSidecarBackend[],
|
|
218
|
+
anthropicProviderName?: string,
|
|
219
|
+
): VisionModelOption[] {
|
|
220
|
+
const enabled = new Set(enabledBackends);
|
|
221
|
+
const byValue = new Map<string, VisionModelOption>();
|
|
222
|
+
const enrichedProviders: EnrichedProviderCache = new Map();
|
|
223
|
+
|
|
224
|
+
for (const backend of Object.keys(BASELINE_VISION_MODELS) as UniversalVisionBackend[]) {
|
|
225
|
+
if (!enabled.has(backend)) continue;
|
|
226
|
+
const candidate = baselineCandidate(backend, anthropicProviderName);
|
|
227
|
+
if (!isVisionEligibleModelWithCache(config, candidate, enrichedProviders)) continue;
|
|
228
|
+
const id = candidate.id;
|
|
229
|
+
byValue.set(id, { value: id, label: id, backend, baseline: true });
|
|
230
|
+
}
|
|
231
|
+
for (const candidate of candidates) {
|
|
232
|
+
const backend = visionBackendForCandidate(config, candidate, anthropicProviderName);
|
|
233
|
+
if (!backend || !enabled.has(backend)) continue;
|
|
234
|
+
if (!isVisionEligibleModelWithCache(config, candidate, enrichedProviders)) continue;
|
|
235
|
+
// Routed rows carry NAMESPACED values ("provider/model") so the loopback
|
|
236
|
+
// dispatch is unambiguous under routeModel; the legacy sides keep bare ids
|
|
237
|
+
// (GUI current-value compatibility, and the forward/OAuth executors POST
|
|
238
|
+
// the string verbatim). De-dup stays keyed by the emitted value.
|
|
239
|
+
const value = backend === "routed" ? `${candidate.provider}/${candidate.id}` : candidate.id;
|
|
240
|
+
if (byValue.has(value)) continue;
|
|
241
|
+
byValue.set(value, { value, label: value, backend });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Two slots per side (baseline first), ranked openai < anthropic < routed
|
|
245
|
+
// so widening the union appends rather than interleaves (roadmap 170).
|
|
246
|
+
const sideRank: Record<VisionSidecarBackend, number> = { openai: 0, anthropic: 2, routed: 4 };
|
|
247
|
+
const order = (option: VisionModelOption) =>
|
|
248
|
+
sideRank[option.backend] + (option.baseline ? 0 : 1);
|
|
249
|
+
return [...byValue.values()].sort((a, b) => order(a) - order(b) || a.value.localeCompare(b.value));
|
|
250
|
+
}
|