@iislee/opencodex 2.11.0 → 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/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -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 +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -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 +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- 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 +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- 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 +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- 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 +618 -74
- package/src/adapters/identity.ts +39 -6
- 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 +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- 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 +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- 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 +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- 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 +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -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 +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- 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 +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/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- 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 +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -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 +80 -0
- package/src/codex/journal.ts +66 -4
- 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 +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 +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 +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 +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- 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 +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -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 +152 -29
- package/src/images/plan.ts +23 -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.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/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- 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 +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- 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/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- 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/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 +202 -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 +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -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/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- 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/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -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 +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -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 +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -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 +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- 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 +220 -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 +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- 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 +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- 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 +865 -181
- package/src/server/lifecycle.ts +284 -13
- 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 +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -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 +101 -32
- package/src/server/management/model-routes.ts +189 -131
- 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 +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- 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 +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 +288 -32
- 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 +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- 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 +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -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 +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -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 +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 +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -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 +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- 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 +845 -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 +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- 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 +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- 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 +9 -3
- 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 +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- 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
|
@@ -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;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "../types";
|
|
2
|
-
import { getValidAccessToken } from "../oauth";
|
|
2
|
+
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
3
3
|
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic";
|
|
4
4
|
import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fingerprint";
|
|
5
5
|
import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
|
|
6
|
-
import { redactSecretString } from "../lib/redact";
|
|
7
6
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
8
7
|
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
9
8
|
import type { WebSearchSource } from "./parse";
|
|
@@ -127,7 +126,7 @@ export async function runAnthropicWebSearch(
|
|
|
127
126
|
try {
|
|
128
127
|
token = await getValidAccessToken(providerName);
|
|
129
128
|
} catch (e) {
|
|
130
|
-
return { text: "", sources: [], error: `anthropic sidecar auth failed: ${
|
|
129
|
+
return { text: "", sources: [], error: `anthropic sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(e)}` };
|
|
131
130
|
}
|
|
132
131
|
const headers: Record<string, string> = {
|
|
133
132
|
"Content-Type": "application/json",
|
|
@@ -166,13 +165,20 @@ export async function runAnthropicWebSearch(
|
|
|
166
165
|
() => fetch(url, { method: "POST", headers, body: JSON.stringify(body), signal: linkedSignal.signal }),
|
|
167
166
|
{ abortSignal: linkedSignal.signal, label: "web-search-sidecar-anthropic" },
|
|
168
167
|
);
|
|
168
|
+
// Guard before any branch reads the body: the failure branch's `res.text()` ran ahead of
|
|
169
|
+
// the success-path guard, reopening the fetch-resolution-to-reader-attach race
|
|
170
|
+
// (found investigating #1419).
|
|
171
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
169
172
|
if (!res.ok) {
|
|
170
173
|
const t = await res.text().catch(() => "");
|
|
174
|
+
detachBodyGuard();
|
|
171
175
|
console.warn(`[web-search] anthropic sidecar HTTP ${res.status} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
|
|
172
|
-
|
|
173
|
-
|
|
176
|
+
if (res.status === 401) {
|
|
177
|
+
return { text: "", sources: [], error: `anthropic sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(t))}` };
|
|
178
|
+
}
|
|
179
|
+
// Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics.
|
|
180
|
+
return { text: "", sources: [], error: `sidecar HTTP ${res.status}` };
|
|
174
181
|
}
|
|
175
|
-
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
176
182
|
try {
|
|
177
183
|
return await parseAnthropicSidecarSSE(res);
|
|
178
184
|
} finally {
|
|
@@ -181,7 +187,7 @@ export async function runAnthropicWebSearch(
|
|
|
181
187
|
} catch (e) {
|
|
182
188
|
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
183
189
|
console.warn(`[web-search] anthropic sidecar ${kind} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
|
|
184
|
-
return { text: "", sources: [], error:
|
|
190
|
+
return { text: "", sources: [], error: `anthropic sidecar ${kind}` };
|
|
185
191
|
} finally {
|
|
186
192
|
sidecarExit();
|
|
187
193
|
linkedSignal.cleanup();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which backends may RUN a hosted web_search, and which candidate rows each
|
|
3
|
+
* can run (#2188 rule 2 for web-search: ∩ probed backend with an executor).
|
|
4
|
+
*
|
|
5
|
+
* A backend registers here only when BOTH hold:
|
|
6
|
+
* - an executor exists in this repository (src/web-search/executor.ts or
|
|
7
|
+
* anthropic-executor.ts today), and
|
|
8
|
+
* - its liveness probe passes. For the two shipped backends the probe IS
|
|
9
|
+
* auth presence — the ChatGPT forward path and the stored Anthropic OAuth
|
|
10
|
+
* path fail closed without a credential, so a live credential is the
|
|
11
|
+
* strongest pre-flight signal short of spending a search.
|
|
12
|
+
*
|
|
13
|
+
* Future backends (Gemini google_search, Grok web_search, Zen hosted search,
|
|
14
|
+
* Exa-class vendors) stay OUT of this table until a live probe and an
|
|
15
|
+
* executor land — the research and probe contracts are recorded in
|
|
16
|
+
* devlog/_plan/260820_sidecar_selection_unification/002 and 031. Documenting
|
|
17
|
+
* a tool is not the same as being able to run it.
|
|
18
|
+
*/
|
|
19
|
+
import type { OcxConfig } from "../types";
|
|
20
|
+
import { AUTH_SLOT_MODELS, type SidecarAuthState } from "../sidecar/auth";
|
|
21
|
+
import type { SidecarCandidate } from "../sidecar/candidates";
|
|
22
|
+
import { getAccountSet } from "../oauth/store";
|
|
23
|
+
import type { WebSearchBackendId } from "./index";
|
|
24
|
+
|
|
25
|
+
export interface WebSearchBackendDescriptor {
|
|
26
|
+
backend: WebSearchBackendId;
|
|
27
|
+
/** Liveness signal for this backend (auth presence for the shipped two). */
|
|
28
|
+
isActive(auth: SidecarAuthState, config: OcxConfig): boolean;
|
|
29
|
+
/** Which candidate rows this backend's executor can actually run. */
|
|
30
|
+
eligibleModel(candidate: SidecarCandidate, auth: SidecarAuthState): boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const WEB_SEARCH_BACKENDS: readonly WebSearchBackendDescriptor[] = [
|
|
34
|
+
{
|
|
35
|
+
backend: "openai",
|
|
36
|
+
isActive: auth => auth.isCodexAuth,
|
|
37
|
+
// The ChatGPT forward executor runs BARE native slugs and the Codex auth
|
|
38
|
+
// slot — settings.model is POSTed verbatim to the forward /responses, so
|
|
39
|
+
// an account-bound "selector/slug" row (model-rows emits those as
|
|
40
|
+
// provider "openai", native true) or a custom openai-keyed row would
|
|
41
|
+
// persist an id the executor cannot run (review F1). The sidecar never
|
|
42
|
+
// calls routeModel; there is no prefix-stripping on this path.
|
|
43
|
+
eligibleModel: candidate => candidate.provider === "openai"
|
|
44
|
+
&& (candidate.native === true || candidate.authSlot === true)
|
|
45
|
+
&& !candidate.id.includes("/"),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
backend: "anthropic",
|
|
49
|
+
isActive: auth => auth.isAnthropicAuth,
|
|
50
|
+
// The stored-OAuth Messages executor dispatches through exactly ONE
|
|
51
|
+
// provider — the one the shared auth module resolved. Same-adapter keyed
|
|
52
|
+
// rows are unreachable, mirroring visionBackendForCandidate's stance.
|
|
53
|
+
eligibleModel: (candidate, auth) => candidate.provider === auth.anthropicProviderName,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
backend: "xai",
|
|
57
|
+
// Probe = stored Grok OAuth usable: enabled oauth-mode "xai" provider whose
|
|
58
|
+
// active account is not marked for reauth — the same predicate
|
|
59
|
+
// findXaiSidecarProvider applies at plan time (L7).
|
|
60
|
+
isActive: (_auth, config) => {
|
|
61
|
+
const provider = config.providers["xai"];
|
|
62
|
+
if (!provider || provider.disabled === true || provider.authMode !== "oauth") return false;
|
|
63
|
+
const set = getAccountSet("xai");
|
|
64
|
+
const active = set?.accounts.find(account => account.id === set.activeAccountId);
|
|
65
|
+
return !!active && active.needsReauth !== true;
|
|
66
|
+
},
|
|
67
|
+
eligibleModel: candidate => candidate.provider === "xai",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
backend: "gemini",
|
|
71
|
+
// Probe = usable Antigravity OAuth + discovered projectId (findGeminiSidecarProvider's predicate).
|
|
72
|
+
isActive: (_auth, config) => {
|
|
73
|
+
const provider = config.providers["google-antigravity"];
|
|
74
|
+
if (!provider || provider.disabled === true || provider.authMode !== "oauth") return false;
|
|
75
|
+
const set = getAccountSet("google-antigravity");
|
|
76
|
+
const active = set?.accounts.find(account => account.id === set.activeAccountId);
|
|
77
|
+
if (!active || active.needsReauth === true) return false;
|
|
78
|
+
return !!(active.credential as { projectId?: string } | undefined)?.projectId;
|
|
79
|
+
},
|
|
80
|
+
eligibleModel: candidate => candidate.provider === "google-antigravity",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
backend: "exa",
|
|
84
|
+
// Probe = operator key present. Exa is not an LLM: no candidate models ever
|
|
85
|
+
// match, so the GUI's model list stays untouched by this backend.
|
|
86
|
+
isActive: (_auth, config) => !!config.webSearchSidecar?.exaApiKey,
|
|
87
|
+
eligibleModel: () => false,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* (picker-visible ∪ auth slots) ∩ (active backend able to run the row).
|
|
93
|
+
* The auth slots always survive their own side's activation: a logged-in
|
|
94
|
+
* side keeps Luna/Haiku even when the picker hides them.
|
|
95
|
+
*/
|
|
96
|
+
export function webSearchSidecarCandidates(
|
|
97
|
+
config: OcxConfig,
|
|
98
|
+
auth: SidecarAuthState,
|
|
99
|
+
all: readonly SidecarCandidate[],
|
|
100
|
+
): SidecarCandidate[] {
|
|
101
|
+
const active = WEB_SEARCH_BACKENDS.filter(descriptor => descriptor.isActive(auth, config));
|
|
102
|
+
return all.filter(candidate => active.some(descriptor => descriptor.eligibleModel(candidate, auth)));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** True when the id is one of the fixed auth-slot models (#2188 write-gate exception). */
|
|
106
|
+
export function isWebSearchAuthSlotModel(id: string): boolean {
|
|
107
|
+
return id === AUTH_SLOT_MODELS.codex || id === AUTH_SLOT_MODELS.anthropic;
|
|
108
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute ONE web search via the Exa Search API (#2188 L9) — the non-LLM lane.
|
|
3
|
+
*
|
|
4
|
+
* Exa returns ranked result JSON, not a prose answer: the outcome's text is a
|
|
5
|
+
* per-result digest the routed model synthesizes from. Probe-verified 2026-08-21
|
|
6
|
+
* (devlog 002): POST https://api.exa.ai/search with x-api-key returns
|
|
7
|
+
* {requestId, results[{title,url,publishedDate,text}], costDollars}.
|
|
8
|
+
* redirect: "manual" because Bun forwards custom headers across redirects.
|
|
9
|
+
* Never throws; every error string passes redactSecretString.
|
|
10
|
+
*/
|
|
11
|
+
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
12
|
+
import { signalWithTimeout } from "../lib/abort";
|
|
13
|
+
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
14
|
+
import { redactSecretString } from "../lib/redact";
|
|
15
|
+
import type { WebSearchSource } from "./parse";
|
|
16
|
+
import type { SidecarOutcome, SidecarSettings } from "./executor";
|
|
17
|
+
|
|
18
|
+
const EXA_SEARCH_URL = "https://api.exa.ai/search";
|
|
19
|
+
const EXA_NUM_RESULTS = 5;
|
|
20
|
+
const EXA_SNIPPET_CHARS = 1000;
|
|
21
|
+
|
|
22
|
+
function isRec(v: unknown): v is Record<string, unknown> {
|
|
23
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function runExaWebSearch(
|
|
27
|
+
query: string,
|
|
28
|
+
apiKey: string,
|
|
29
|
+
settings: SidecarSettings,
|
|
30
|
+
abortSignal?: AbortSignal,
|
|
31
|
+
): Promise<SidecarOutcome> {
|
|
32
|
+
if (!apiKey) return { text: "", sources: [], error: "exa backend selected without an exaApiKey" };
|
|
33
|
+
// The executor KNOWS the secret — pattern-based redaction cannot be trusted to
|
|
34
|
+
// recognize an arbitrary operator key, so scrub the literal value explicitly.
|
|
35
|
+
const scrub = (s: string) => redactSecretString(s.split(apiKey).join("[redacted-exa-key]"));
|
|
36
|
+
const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
|
|
37
|
+
const sidecarExit = sidecarEnter("web-search");
|
|
38
|
+
const t0 = Date.now();
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetchWithResetRetry(
|
|
41
|
+
() => fetch(EXA_SEARCH_URL, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json", "x-api-key": apiKey },
|
|
44
|
+
body: JSON.stringify({ query, numResults: EXA_NUM_RESULTS, contents: { text: { maxCharacters: EXA_SNIPPET_CHARS } } }),
|
|
45
|
+
signal: linkedSignal.signal,
|
|
46
|
+
redirect: "manual",
|
|
47
|
+
}),
|
|
48
|
+
{ abortSignal: linkedSignal.signal, label: "exa-web-search-sidecar" },
|
|
49
|
+
);
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const t = await res.text().catch(() => "");
|
|
52
|
+
// Scrub BEFORE truncating: slicing first can cut the literal key at the
|
|
53
|
+
// boundary, leaving an unscrubbable key prefix in the surviving text.
|
|
54
|
+
return { text: "", sources: [], error: `exa sidecar HTTP ${res.status}: ${scrub(t).slice(0, 200)}` };
|
|
55
|
+
}
|
|
56
|
+
const payload = await res.json().catch(() => null);
|
|
57
|
+
return mapExaSearchResponse(payload);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
60
|
+
console.warn(`[web-search] exa sidecar ${kind} (${Date.now() - t0}ms)`);
|
|
61
|
+
return { text: "", sources: [], error: scrub(e instanceof Error ? e.message : String(e)) };
|
|
62
|
+
} finally {
|
|
63
|
+
sidecarExit();
|
|
64
|
+
linkedSignal.cleanup();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Map an Exa /search payload to a digest the routed model can synthesize from. */
|
|
69
|
+
export function mapExaSearchResponse(payload: unknown): SidecarOutcome {
|
|
70
|
+
if (!isRec(payload) || !Array.isArray(payload.results)) {
|
|
71
|
+
return { text: "", sources: [], error: "exa sidecar returned a non-JSON or shapeless body" };
|
|
72
|
+
}
|
|
73
|
+
const sources: WebSearchSource[] = [];
|
|
74
|
+
const lines: string[] = [];
|
|
75
|
+
const seen = new Set<string>();
|
|
76
|
+
for (const result of payload.results) {
|
|
77
|
+
if (!isRec(result) || typeof result.url !== "string" || result.url.length === 0) continue;
|
|
78
|
+
if (seen.has(result.url)) continue;
|
|
79
|
+
seen.add(result.url);
|
|
80
|
+
const title = typeof result.title === "string" && result.title.length > 0 ? result.title : result.url;
|
|
81
|
+
sources.push({ url: result.url, ...(title !== result.url ? { title } : {}) });
|
|
82
|
+
const snippet = typeof result.text === "string" ? result.text.trim().slice(0, EXA_SNIPPET_CHARS) : "";
|
|
83
|
+
const dated = typeof result.publishedDate === "string" ? ` (${result.publishedDate.slice(0, 10)})` : "";
|
|
84
|
+
lines.push(`- ${title}${dated}: ${snippet || "(no excerpt)"} [${result.url}]`);
|
|
85
|
+
}
|
|
86
|
+
if (lines.length === 0) return { text: "", sources: [], error: "exa sidecar returned no results" };
|
|
87
|
+
return { text: `Search results:\n${lines.join("\n")}`, sources };
|
|
88
|
+
}
|
|
@@ -62,8 +62,8 @@ export async function runWebSearch(
|
|
|
62
62
|
tool_choice: "auto",
|
|
63
63
|
reasoning: { effort: settings.reasoning },
|
|
64
64
|
// NOTE: the ChatGPT (codex) backend rejects `max_output_tokens` ("Unsupported parameter") and
|
|
65
|
-
// requires `store: false` — keep this body minimal.
|
|
66
|
-
//
|
|
65
|
+
// requires `store: false` — keep this body minimal. The shared SSE parser bounds raw response
|
|
66
|
+
// bytes before format-result applies its smaller display clamp.
|
|
67
67
|
store: false,
|
|
68
68
|
stream: true,
|
|
69
69
|
};
|
|
@@ -78,16 +78,24 @@ export async function runWebSearch(
|
|
|
78
78
|
headers,
|
|
79
79
|
body: JSON.stringify(body),
|
|
80
80
|
signal: linkedSignal.signal,
|
|
81
|
+
// Credential-bearing: do not follow a cross-origin 3xx. Bun strips `Authorization`
|
|
82
|
+
// across origins but forwards nonstandard headers such as `chatgpt-account-id`,
|
|
83
|
+
// `session_id`, and `x-codex-turn-metadata` to the redirect target.
|
|
84
|
+
redirect: "manual",
|
|
81
85
|
}),
|
|
82
86
|
{ abortSignal: linkedSignal.signal, label: "web-search-sidecar" },
|
|
83
87
|
);
|
|
84
88
|
recordOutcome?.(res.status);
|
|
89
|
+
// Attach the body guard before ANY branch reads it. The success path guarded itself below,
|
|
90
|
+
// but the failure branch's `res.text()` runs first, so a cancel landing between fetch
|
|
91
|
+
// resolution and reader attach orphaned the internal rejection (found investigating #1419).
|
|
92
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
85
93
|
if (!res.ok) {
|
|
86
94
|
const t = await res.text().catch(() => "");
|
|
95
|
+
detachBodyGuard();
|
|
87
96
|
console.warn(`[web-search] sidecar HTTP ${res.status} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
|
|
88
97
|
return { text: "", sources: [], error: `sidecar HTTP ${res.status}: ${redactSecretString(t.slice(0, 200))}` };
|
|
89
98
|
}
|
|
90
|
-
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
91
99
|
try {
|
|
92
100
|
return await parseSidecarSSE(res);
|
|
93
101
|
} finally {
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute ONE web search via Gemini google_search grounding on the Antigravity
|
|
3
|
+
* Cloud Code Assist transport (#2188 L8). Live-probed 2026-08-20/21 (devlog 002):
|
|
4
|
+
* the CCA envelope with tools [{google_search:{}}] returns a grounded answer
|
|
5
|
+
* plus groundingMetadata; a non-IDE User-Agent gets 404, so the request reuses
|
|
6
|
+
* the adapter's fingerprint constants. The OAuth bearer only ever travels to
|
|
7
|
+
* the REGISTRY-pinned endpoint — a config-level baseUrl override is never
|
|
8
|
+
* trusted for token transmission (same rule as src/server/images.ts).
|
|
9
|
+
* Never throws — returns {error} so the caller injects a graceful tool result.
|
|
10
|
+
*/
|
|
11
|
+
import type { OcxProviderConfig } from "../types";
|
|
12
|
+
import { getValidAccessTokenSnapshot, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
13
|
+
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
14
|
+
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
15
|
+
import { readBoundedResponseBytes } from "../lib/bounded-body";
|
|
16
|
+
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
17
|
+
import { redactSecretString } from "../lib/redact";
|
|
18
|
+
import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
|
|
19
|
+
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
20
|
+
import { getProviderRegistryEntry } from "../providers/registry";
|
|
21
|
+
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
22
|
+
import { BASE_INSTRUCTION, IMAGE_INSTRUCTION, type SidecarOutcome, type SidecarSettings } from "./executor";
|
|
23
|
+
|
|
24
|
+
const CCA_FALLBACK_BASE = "https://daily-cloudcode-pa.googleapis.com";
|
|
25
|
+
|
|
26
|
+
function isRec(v: unknown): v is Record<string, unknown> {
|
|
27
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runGeminiWebSearch(
|
|
31
|
+
query: string,
|
|
32
|
+
providerName: string,
|
|
33
|
+
_provider: OcxProviderConfig,
|
|
34
|
+
settings: SidecarSettings,
|
|
35
|
+
abortSignal?: AbortSignal,
|
|
36
|
+
): Promise<SidecarOutcome> {
|
|
37
|
+
let token: string;
|
|
38
|
+
let project: string | undefined;
|
|
39
|
+
try {
|
|
40
|
+
const snapshot = await getValidAccessTokenSnapshot(providerName);
|
|
41
|
+
token = snapshot.accessToken;
|
|
42
|
+
project = snapshot.projectId;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { text: "", sources: [], error: `gemini sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(e)}` };
|
|
45
|
+
}
|
|
46
|
+
if (!project) {
|
|
47
|
+
return { text: "", sources: [], error: "gemini sidecar missing Cloud Code Assist project id — re-run ocx login google-antigravity" };
|
|
48
|
+
}
|
|
49
|
+
// Destination pinned to the registry endpoint (see module doc).
|
|
50
|
+
const base = getProviderRegistryEntry("google-antigravity")?.baseUrl ?? CCA_FALLBACK_BASE;
|
|
51
|
+
const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(settings.model, settings.reasoning, base);
|
|
52
|
+
const instruction = settings.describeImages ? BASE_INSTRUCTION + IMAGE_INSTRUCTION : BASE_INSTRUCTION;
|
|
53
|
+
const envelope = {
|
|
54
|
+
model: wireModelId,
|
|
55
|
+
userAgent: "antigravity",
|
|
56
|
+
requestType: "agent",
|
|
57
|
+
project,
|
|
58
|
+
requestId: `agent-${crypto.randomUUID()}`,
|
|
59
|
+
request: {
|
|
60
|
+
systemInstruction: { role: "user", parts: [{ text: instruction }] },
|
|
61
|
+
contents: [{ role: "user", parts: [{ text: query }] }],
|
|
62
|
+
tools: [{ google_search: {} }],
|
|
63
|
+
sessionId: crypto.randomUUID(),
|
|
64
|
+
...(thinkingLevel ? { generationConfig: { thinkingConfig: { thinkingLevel } } } : {}),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
|
|
68
|
+
const sidecarExit = sidecarEnter("web-search");
|
|
69
|
+
const t0 = Date.now();
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetchWithResetRetry(
|
|
72
|
+
() => fetch(`${base}/v1internal:generateContent`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
"Authorization": `Bearer ${token}`,
|
|
77
|
+
"User-Agent": ANTIGRAVITY_REQUEST_UA,
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify(envelope),
|
|
80
|
+
signal: linkedSignal.signal,
|
|
81
|
+
redirect: "manual",
|
|
82
|
+
}),
|
|
83
|
+
{ abortSignal: linkedSignal.signal, label: "gemini-web-search-sidecar" },
|
|
84
|
+
);
|
|
85
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
86
|
+
try {
|
|
87
|
+
const bounded = await readBoundedResponseBytes(res, {
|
|
88
|
+
maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
|
|
89
|
+
signal: linkedSignal.signal,
|
|
90
|
+
});
|
|
91
|
+
if (bounded.oversized) {
|
|
92
|
+
const prefix = res.ok ? "gemini sidecar response" : `gemini sidecar HTTP ${res.status} response`;
|
|
93
|
+
return { text: "", sources: [], error: `${prefix} exceeded byte bound` };
|
|
94
|
+
}
|
|
95
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bounded.bytes);
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
return { text: "", sources: [], error: `gemini sidecar HTTP ${res.status}: ${redactSecretString(text.slice(0, 200))}` };
|
|
98
|
+
}
|
|
99
|
+
let payload: unknown = null;
|
|
100
|
+
try {
|
|
101
|
+
payload = JSON.parse(text);
|
|
102
|
+
} catch {
|
|
103
|
+
// The mapper owns the stable malformed/empty JSON outcome.
|
|
104
|
+
}
|
|
105
|
+
return mapCcaGroundedResponse(payload);
|
|
106
|
+
} finally {
|
|
107
|
+
detachBodyGuard();
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
111
|
+
console.warn(`[web-search] gemini sidecar ${kind} (${Date.now() - t0}ms)`);
|
|
112
|
+
return { text: "", sources: [], error: redactSecretString(e instanceof Error ? e.message : String(e)) };
|
|
113
|
+
} finally {
|
|
114
|
+
sidecarExit();
|
|
115
|
+
linkedSignal.cleanup();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Map a CCA generateContent payload (possibly wrapped in {response}) to text + grounding sources. */
|
|
120
|
+
export function mapCcaGroundedResponse(payload: unknown): SidecarOutcome {
|
|
121
|
+
const root = isRec(payload) && isRec(payload.response) ? payload.response : payload;
|
|
122
|
+
if (!isRec(root)) return { text: "", sources: [], error: "gemini sidecar returned a non-JSON or empty body" };
|
|
123
|
+
const candidate = Array.isArray(root.candidates) && isRec(root.candidates[0]) ? root.candidates[0] : undefined;
|
|
124
|
+
if (!candidate) return { text: "", sources: [], error: "gemini sidecar returned no candidates" };
|
|
125
|
+
const parts = isRec(candidate.content) && Array.isArray(candidate.content.parts) ? candidate.content.parts : [];
|
|
126
|
+
const text = parts.map(p => (isRec(p) && typeof p.text === "string" ? p.text : "")).join("");
|
|
127
|
+
const sources: WebSearchSource[] = [];
|
|
128
|
+
const seen = new Set<string>();
|
|
129
|
+
const gm = isRec(candidate.groundingMetadata) ? candidate.groundingMetadata : undefined;
|
|
130
|
+
if (gm && Array.isArray(gm.groundingChunks)) {
|
|
131
|
+
for (const chunk of gm.groundingChunks) {
|
|
132
|
+
const web = isRec(chunk) && isRec(chunk.web) ? chunk.web : undefined;
|
|
133
|
+
const uri = web && typeof web.uri === "string" ? web.uri : undefined;
|
|
134
|
+
if (!uri || seen.has(uri)) continue;
|
|
135
|
+
seen.add(uri);
|
|
136
|
+
sources.push({ url: uri, ...(typeof web?.title === "string" && web.title.length > 0 ? { title: web.title } : {}) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (text.length === 0) return { text: "", sources, error: "gemini sidecar returned no text" };
|
|
140
|
+
return { text, sources };
|
|
141
|
+
}
|