@iislee/opencodex 2.11.0 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +622 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
package/src/adapters/google.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AdapterFetchContext, AdapterRequest, ProviderAdapter } from "./base";
|
|
2
2
|
import { debugDroppedFrame } from "../lib/debug";
|
|
3
|
-
import {
|
|
3
|
+
import { createToolCallIdAllocator } from "./tool-call-id";
|
|
4
4
|
import { createImageBudget, materializeInlineImage, MAX_ENCODED_BYTES_PER_IMAGE, artifactHttpUrl } from "../images/artifacts";
|
|
5
5
|
import type {
|
|
6
6
|
AdapterEvent,
|
|
@@ -8,28 +8,38 @@ import type {
|
|
|
8
8
|
OcxContentPart,
|
|
9
9
|
OcxParsedRequest,
|
|
10
10
|
OcxProviderConfig,
|
|
11
|
+
OcxProviderOpaqueToolCallMetadata,
|
|
11
12
|
OcxTextContent,
|
|
12
13
|
OcxToolCall,
|
|
14
|
+
OcxToolResultMessage,
|
|
13
15
|
OcxUsage,
|
|
14
16
|
} from "../types";
|
|
15
|
-
import { isAllowedToolChoice, namespacedToolName,
|
|
17
|
+
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
|
|
16
18
|
import { contentPartsToText, parseDataUrl } from "./image";
|
|
17
19
|
import { getVertexAccessToken } from "../lib/gcp-adc";
|
|
18
20
|
import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
|
|
19
21
|
import { safeAntigravityHttpErrorMessage, safeVertexHttpErrorMessage } from "./google-errors";
|
|
20
|
-
import {
|
|
22
|
+
import { isVertexTruncatedTurn, vertexTruncationErrorMessage } from "./google-truncation";
|
|
21
23
|
import { ANTIGRAVITY_REQUEST_UA, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
|
|
22
24
|
import { compileGoogleWireBody } from "./google-wire-compiler";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
+
import { identifyRoutedModel } from "./identity";
|
|
26
|
+
import {
|
|
27
|
+
antigravityUsesReplayCache,
|
|
28
|
+
applyAntigravityReplay,
|
|
29
|
+
applyAntigravityThoughtSignatureFallback,
|
|
30
|
+
clearAntigravityReplay,
|
|
31
|
+
observeAntigravityReplay,
|
|
32
|
+
} from "./google-antigravity-replay";
|
|
25
33
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
34
|
+
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
35
|
+
import { forgetThoughtSignatureForReplay, lookupReplayThoughtSignature } from "../responses/thought-signature-replay";
|
|
26
36
|
import {
|
|
27
37
|
isTranslatorBudgetExceededError,
|
|
28
38
|
retainTranslatedEventBatch,
|
|
29
39
|
type TranslatorBudget,
|
|
30
40
|
} from "../lib/translator-budget";
|
|
31
41
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
32
|
-
import { mapReasoningEffort } from "../reasoning-effort";
|
|
42
|
+
import { configuredReasoningEfforts, mapReasoningEffort } from "../reasoning-effort";
|
|
33
43
|
|
|
34
44
|
// Google-family models (Gemini/Vertex/Antigravity) tend to emit long running commentary between
|
|
35
45
|
// tool calls. This steers them to keep the BETWEEN-STEP text to one line and reason internally
|
|
@@ -44,12 +54,73 @@ const GOOGLE_BREVITY_INSTRUCTION = [
|
|
|
44
54
|
"- This applies only to intermediate progress text. Your final answer after the work is done is exempt: write it in full and at whatever length the task requires.",
|
|
45
55
|
].join("\n");
|
|
46
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Documented output ceiling for a Google-surface model, or `undefined` when the id is not
|
|
59
|
+
* recognized.
|
|
60
|
+
*
|
|
61
|
+
* Unknown ids return `undefined` deliberately. An earlier revision returned a 16,384 floor for
|
|
62
|
+
* anything unmatched, which silently truncated aliases, gateway ids, and any model added after
|
|
63
|
+
* this table was written — the operator asked for N tokens and got 16,384 with no signal. A cap
|
|
64
|
+
* we cannot justify is worse than no cap: `structure/02_config-and-codex-home.md` is explicit
|
|
65
|
+
* that an explicit request value wins, so an unrecognized model passes through untouched and the
|
|
66
|
+
* upstream remains the authority on its own limit.
|
|
67
|
+
*
|
|
68
|
+
* Matching is prefix/family based rather than substring based for the same reason: `includes("pro")`
|
|
69
|
+
* matched any id containing "pro" (`my-prototype-model`), and `includes("oss")` matched any id
|
|
70
|
+
* containing "oss" (`crossover-v2`).
|
|
71
|
+
*/
|
|
72
|
+
export function maxOutputTokensForGoogleModel(modelId: string): number | undefined {
|
|
73
|
+
const lower = modelId.toLowerCase().trim();
|
|
74
|
+
if (lower.startsWith("gemini")) {
|
|
75
|
+
// Pro tops out one token below the flash/other Gemini ceiling; both are documented values.
|
|
76
|
+
return /(^|[-.])pro([-.]|$)/.test(lower) ? 65535 : 65536;
|
|
77
|
+
}
|
|
78
|
+
if (lower.startsWith("claude")) return 64000;
|
|
79
|
+
if (lower.startsWith("gpt-oss")) return 32768;
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function clampGoogleMaxOutputTokens(
|
|
84
|
+
modelId: string,
|
|
85
|
+
requestedTokens?: number,
|
|
86
|
+
): number | undefined {
|
|
87
|
+
if (requestedTokens === undefined || requestedTokens <= 0) return undefined;
|
|
88
|
+
const modelMax = maxOutputTokensForGoogleModel(modelId);
|
|
89
|
+
// Unknown model: honour the request as-is rather than inventing a ceiling for it.
|
|
90
|
+
if (modelMax === undefined) return requestedTokens;
|
|
91
|
+
return Math.min(requestedTokens, modelMax);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Some Google direct deployments expose current Gemini Flash generations with a `-tiered`
|
|
96
|
+
* wire suffix (`gemini-3.7-flash` -> `gemini-3.7-flash-tiered`). Keep the picker-visible id
|
|
97
|
+
* stable and make the mapping configurable for deployments that still serve the bare id.
|
|
98
|
+
*/
|
|
99
|
+
const GEMINI_DIRECT_WIRE_RENAMES: Record<string, string> = {
|
|
100
|
+
"gemini-3.7-flash": "gemini-3.7-flash-tiered",
|
|
101
|
+
"gemini-3.6-flash": "gemini-3.6-flash-tiered",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
function resolveDirectGeminiWireModelId(modelId: string, applyRenames: boolean): string {
|
|
105
|
+
if (!applyRenames) return modelId;
|
|
106
|
+
return Object.hasOwn(GEMINI_DIRECT_WIRE_RENAMES, modelId)
|
|
107
|
+
? GEMINI_DIRECT_WIRE_RENAMES[modelId]!
|
|
108
|
+
: modelId;
|
|
109
|
+
}
|
|
110
|
+
|
|
47
111
|
/** Vertex API key: provider.apiKey if it looks real (not a sentinel), else GOOGLE_CLOUD_API_KEY env. */
|
|
48
112
|
function resolveVertexApiKey(optKey?: string): string | undefined {
|
|
49
113
|
const realKey = optKey && !optKey.startsWith("<") && optKey !== "N/A" ? optKey : undefined;
|
|
50
114
|
return realKey || process.env.GOOGLE_CLOUD_API_KEY;
|
|
51
115
|
}
|
|
52
116
|
|
|
117
|
+
/** Prefer Codex's stable opaque thread key; retain the existing deterministic fallback for clients
|
|
118
|
+
* that omit it. The replay store hashes this value and never retains the raw session identifier. */
|
|
119
|
+
function vertexReplaySessionId(parsed: OcxParsedRequest): string {
|
|
120
|
+
const threadId = parsed._clientThreadId?.trim();
|
|
121
|
+
return threadId || antigravitySessionId(parsed);
|
|
122
|
+
}
|
|
123
|
+
|
|
53
124
|
/**
|
|
54
125
|
* Stable tool-call id for the Gemini wire `functionCall.id` / `functionResponse.id` fields.
|
|
55
126
|
*
|
|
@@ -67,15 +138,9 @@ function resolveVertexApiKey(optKey?: string): string | undefined {
|
|
|
67
138
|
* the call/response pairing is preserved. Returns `undefined` for an empty id so the caller omits the
|
|
68
139
|
* field entirely rather than inventing a non-matching one.
|
|
69
140
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const cleaned = raw.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
74
|
-
if (cleaned === raw) return cleaned;
|
|
75
|
-
// Lossy rewrite happened: disambiguate with a deterministic suffix derived from the raw id.
|
|
76
|
-
const suffix = createHash("sha256").update(raw).digest("hex").slice(0, 8);
|
|
77
|
-
return `${cleaned}_${suffix}`;
|
|
78
|
-
}
|
|
141
|
+
// Aliasing the stateless transform here would reintroduce the collision it cannot prevent:
|
|
142
|
+
// a rewritten id can equal a distinct raw id that already conforms. Use a request-scoped
|
|
143
|
+
// allocator, exactly as the Anthropic adapter does, so call/response pairing stays injective.
|
|
79
144
|
|
|
80
145
|
/**
|
|
81
146
|
* Inline image parts (Gemini `inline_data`) extracted from tool-result content. Only base64 data URLs
|
|
@@ -104,6 +169,7 @@ function toolResultImageParts(content: string | OcxContentPart[]): unknown[] {
|
|
|
104
169
|
*/
|
|
105
170
|
const GEMINI_EMPTY_PLACEHOLDER = "(empty)";
|
|
106
171
|
const GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER = "(empty tool output)";
|
|
172
|
+
const GEMINI_MISSING_TOOL_RESULT = "[missing tool_result for this tool_use in history]";
|
|
107
173
|
|
|
108
174
|
/** A Gemini text part, or undefined when the value cannot form a valid non-empty text block. */
|
|
109
175
|
function geminiTextPart(text: unknown): { text: string } | undefined {
|
|
@@ -118,24 +184,75 @@ function geminiTextPart(text: unknown): { text: string } | undefined {
|
|
|
118
184
|
*/
|
|
119
185
|
function geminiToolResultText(content: string | OcxContentPart[]): string {
|
|
120
186
|
if (typeof content === "string") return content || GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
|
|
121
|
-
const hasContent = content.some(p => p.type
|
|
187
|
+
const hasContent = content.some(p => p.type !== "text" || p.text.length > 0);
|
|
122
188
|
return hasContent ? contentPartsToText(content) : GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
|
|
123
189
|
}
|
|
124
190
|
|
|
125
|
-
function
|
|
191
|
+
function geminiToolResultParts(
|
|
192
|
+
msg: OcxToolResultMessage,
|
|
193
|
+
wireName: string,
|
|
194
|
+
wireCallId: string,
|
|
195
|
+
): unknown[] {
|
|
196
|
+
const functionResponse: Record<string, unknown> = {
|
|
197
|
+
name: wireName,
|
|
198
|
+
response: { result: geminiToolResultText(msg.content) },
|
|
199
|
+
id: wireCallId,
|
|
200
|
+
};
|
|
201
|
+
return [{ functionResponse }, ...toolResultImageParts(msg.content)];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function geminiMissingToolResultPart(wireName: string, wireCallId: string): unknown {
|
|
205
|
+
return {
|
|
206
|
+
functionResponse: {
|
|
207
|
+
name: wireName,
|
|
208
|
+
response: { result: GEMINI_MISSING_TOOL_RESULT },
|
|
209
|
+
id: wireCallId,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function geminiUnrepresentableToolCallPart(tc: OcxToolCall, wireName: string): unknown {
|
|
215
|
+
const args = typeof tc.arguments === "string" ? tc.arguments : JSON.stringify(tc.arguments);
|
|
216
|
+
return { text: `[tool_use without a usable id: ${wireName}]\n${args}` };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function geminiOrphanToolResultParts(msg: OcxToolResultMessage): unknown[] {
|
|
220
|
+
const label = msg.toolName ? `${msg.toolName} (${msg.toolCallId})` : msg.toolCallId;
|
|
221
|
+
return [
|
|
222
|
+
{ text: `[tool_result without adjacent tool_use: ${label}]\n${geminiToolResultText(msg.content)}` },
|
|
223
|
+
...toolResultImageParts(msg.content),
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function messagesToGeminiFormat(
|
|
228
|
+
parsed: OcxParsedRequest,
|
|
229
|
+
identityModelId: string,
|
|
230
|
+
): { systemInstruction?: unknown; contents: unknown[]; replayedCallIds: string[] } {
|
|
126
231
|
// Neutralize Codex's GPT-5 identity line (Gemini/Antigravity share this path) so a routed model
|
|
127
232
|
// never misreports as GPT-5/OpenAI, and never leaks the proxy identity upstream.
|
|
128
233
|
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(parsed.context.tools, parsed.options.toolChoice);
|
|
129
|
-
const systemText =
|
|
234
|
+
const systemText = identifyRoutedModel([
|
|
130
235
|
...(parsed.context.systemPrompt ?? []),
|
|
131
236
|
...(toolCatalogNudge ? [toolCatalogNudge] : []),
|
|
132
237
|
GOOGLE_BREVITY_INSTRUCTION,
|
|
133
|
-
].join("\n\n"));
|
|
238
|
+
].join("\n\n"), identityModelId);
|
|
134
239
|
const systemInstruction = { parts: [{ text: systemText }] };
|
|
135
240
|
|
|
136
241
|
const contents: unknown[] = [];
|
|
242
|
+
const replayedCallIds: string[] = [];
|
|
137
243
|
|
|
244
|
+
const callIds = createToolCallIdAllocator();
|
|
138
245
|
for (const msg of parsed.context.messages) {
|
|
246
|
+
if (msg.role === "assistant") {
|
|
247
|
+
for (const part of (msg as OcxAssistantMessage).content) {
|
|
248
|
+
if (part.type === "toolCall") callIds.reserve((part as OcxToolCall).id);
|
|
249
|
+
}
|
|
250
|
+
} else if (msg.role === "toolResult") {
|
|
251
|
+
callIds.reserve((msg as OcxToolResultMessage).toolCallId);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (let i = 0; i < parsed.context.messages.length; i++) {
|
|
255
|
+
const msg = parsed.context.messages[i];
|
|
139
256
|
switch (msg.role) {
|
|
140
257
|
case "user":
|
|
141
258
|
case "developer": {
|
|
@@ -151,6 +268,13 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
151
268
|
parts.push(data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[image: ${p.imageUrl}]` });
|
|
152
269
|
continue;
|
|
153
270
|
}
|
|
271
|
+
if (p.type === "video") {
|
|
272
|
+
const data = parseDataUrl(p.videoUrl);
|
|
273
|
+
// Gemini accepts inline video bytes in the same Part union as images. Arbitrary
|
|
274
|
+
// remote URLs are not valid fileData references, so retain only a short marker.
|
|
275
|
+
parts.push(data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[video: ${p.videoUrl}]` });
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
154
278
|
// Drop empty/malformed text instead of emitting `{ text: "" }` or a bare `{}` part.
|
|
155
279
|
const textPart = geminiTextPart(p.text);
|
|
156
280
|
if (textPart) parts.push(textPart);
|
|
@@ -162,6 +286,7 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
162
286
|
case "assistant": {
|
|
163
287
|
const aMsg = msg as OcxAssistantMessage;
|
|
164
288
|
const parts: unknown[] = [];
|
|
289
|
+
const toolCalls: Array<{ wireCallId: string; wireName: string }> = [];
|
|
165
290
|
for (const p of aMsg.content) {
|
|
166
291
|
if (p.type === "text") {
|
|
167
292
|
const textPart = geminiTextPart((p as OcxTextContent).text);
|
|
@@ -173,13 +298,34 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
173
298
|
// streaming covered by the replay cache. Only forward a REAL upstream signature — the
|
|
174
299
|
// Responses parser also stashes synthetic item ids (`fc_...`) on this field, and sending
|
|
175
300
|
// those as a thoughtSignature breaks continuity (the replay cache supplies the real one).
|
|
176
|
-
const callId =
|
|
177
|
-
const
|
|
301
|
+
const callId = callIds.allocate(tc.id);
|
|
302
|
+
const wireName = namespacedToolName(tc.namespace, tc.name);
|
|
303
|
+
if (callId === undefined) {
|
|
304
|
+
// Claude-on-Antigravity requires a usable id for every translated tool_use. An empty
|
|
305
|
+
// source id cannot be paired safely, so preserve the call as text and let its result
|
|
306
|
+
// follow the same orphan-text path instead of emitting an invalid functionCall.
|
|
307
|
+
parts.push(geminiUnrepresentableToolCallPart(tc, wireName));
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const functionCall: Record<string, unknown> = { name: wireName, args: tc.arguments };
|
|
178
311
|
// Claude-on-Antigravity maps this id to Anthropic `tool_use.id`; without it the upstream
|
|
179
312
|
// conversion 400s. Gemini accepts the optional id and pairs call/response by it.
|
|
180
|
-
|
|
313
|
+
functionCall.id = callId;
|
|
314
|
+
toolCalls.push({ wireCallId: callId, wireName });
|
|
181
315
|
const part: Record<string, unknown> = { functionCall };
|
|
182
|
-
|
|
316
|
+
// Prefer the metadata that travelled with this exact call; fall back to the legacy
|
|
317
|
+
// field for callers that have not been migrated. Never merge or synthesize.
|
|
318
|
+
// Final fallback (#1926): the durable store, read AT SERIALIZATION TIME. The
|
|
319
|
+
// Responses parser runs before the route/credential scope is bound, so its
|
|
320
|
+
// parse-time lookup can never hit; by the time this adapter serializes, the
|
|
321
|
+
// credential-scoped identity is bound and the durable lookup is meaningful.
|
|
322
|
+
const signature = tc.providerMetadata?.google?.thoughtSignature
|
|
323
|
+
?? tc.thoughtSignature
|
|
324
|
+
?? lookupReplayThoughtSignature(tc.id, parsed._reasoningReplayScope);
|
|
325
|
+
if (isLikelyRealThoughtSignature(signature)) {
|
|
326
|
+
part.thoughtSignature = signature;
|
|
327
|
+
replayedCallIds.push(tc.id);
|
|
328
|
+
}
|
|
183
329
|
parts.push(part);
|
|
184
330
|
}
|
|
185
331
|
}
|
|
@@ -188,36 +334,56 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
188
334
|
// adapter does for its own empty assistant content.
|
|
189
335
|
if (parts.length === 0) break;
|
|
190
336
|
contents.push({ role: "model", parts });
|
|
337
|
+
if (toolCalls.length > 0) {
|
|
338
|
+
// Gemini/Claude-on-Antigravity requires one adjacent response batch for the whole
|
|
339
|
+
// function-call turn. Replayed histories can be interrupted, reversed, duplicated, or
|
|
340
|
+
// contain an orphan result; repair only this wire boundary without inventing success.
|
|
341
|
+
const requiredIds = new Set(toolCalls.map(call => call.wireCallId));
|
|
342
|
+
const resultsById = new Map<string, OcxToolResultMessage>();
|
|
343
|
+
const orphanResults: OcxToolResultMessage[] = [];
|
|
344
|
+
let j = i + 1;
|
|
345
|
+
while (j < parsed.context.messages.length && parsed.context.messages[j].role === "toolResult") {
|
|
346
|
+
const result = parsed.context.messages[j] as OcxToolResultMessage;
|
|
347
|
+
const wireResultId = callIds.lookup(result.toolCallId);
|
|
348
|
+
if (wireResultId !== undefined && requiredIds.has(wireResultId) && !resultsById.has(wireResultId)) {
|
|
349
|
+
resultsById.set(wireResultId, result);
|
|
350
|
+
} else {
|
|
351
|
+
orphanResults.push(result);
|
|
352
|
+
}
|
|
353
|
+
j++;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const responseParts: unknown[] = [];
|
|
357
|
+
for (const call of toolCalls) {
|
|
358
|
+
const result = resultsById.get(call.wireCallId);
|
|
359
|
+
if (result) responseParts.push(...geminiToolResultParts(result, call.wireName, call.wireCallId));
|
|
360
|
+
else responseParts.push(geminiMissingToolResultPart(call.wireName, call.wireCallId));
|
|
361
|
+
}
|
|
362
|
+
for (const orphan of orphanResults) {
|
|
363
|
+
responseParts.push(...geminiOrphanToolResultParts(orphan));
|
|
364
|
+
}
|
|
365
|
+
contents.push({ role: "user", parts: responseParts });
|
|
366
|
+
i = j - 1;
|
|
367
|
+
}
|
|
191
368
|
break;
|
|
192
369
|
}
|
|
193
370
|
case "toolResult": {
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
const responseId = geminiToolCallId(msg.toolCallId);
|
|
199
|
-
const functionResponse: Record<string, unknown> = { name: namespacedToolName(msg.toolNamespace, msg.toolName), response: { result: geminiToolResultText(msg.content) } };
|
|
200
|
-
// Mirror the matching functionCall id so Claude-on-Antigravity can pair this result with its
|
|
201
|
-
// `tool_use` block (-> Anthropic `tool_result.tool_use_id`).
|
|
202
|
-
if (responseId !== undefined) functionResponse.id = responseId;
|
|
203
|
-
const parts: unknown[] = [{ functionResponse }];
|
|
204
|
-
for (const part of toolResultImageParts(msg.content)) parts.push(part);
|
|
205
|
-
contents.push({ role: "user", parts });
|
|
371
|
+
// A standalone functionResponse is invalid without an immediately preceding matching
|
|
372
|
+
// functionCall batch. Preserve the result as explicit user text (plus any representable
|
|
373
|
+
// image siblings) rather than manufacturing a successful call or sending a 400-prone shape.
|
|
374
|
+
contents.push({ role: "user", parts: geminiOrphanToolResultParts(msg as OcxToolResultMessage) });
|
|
206
375
|
break;
|
|
207
376
|
}
|
|
208
377
|
}
|
|
209
378
|
}
|
|
210
379
|
|
|
211
|
-
return { systemInstruction, contents };
|
|
380
|
+
return { systemInstruction, contents, replayedCallIds };
|
|
212
381
|
}
|
|
213
382
|
|
|
214
383
|
function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
215
384
|
if (!parsed.context.tools?.length) return undefined;
|
|
216
|
-
const
|
|
217
|
-
?
|
|
218
|
-
: undefined;
|
|
219
|
-
const tools = allowed
|
|
220
|
-
? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
|
|
385
|
+
const tools = isAllowedToolChoice(parsed.options.toolChoice)
|
|
386
|
+
? parsed.context.tools.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools))
|
|
221
387
|
: parsed.context.tools;
|
|
222
388
|
if (tools.length === 0) return undefined;
|
|
223
389
|
return [{
|
|
@@ -229,6 +395,28 @@ function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
|
229
395
|
}];
|
|
230
396
|
}
|
|
231
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Client tool_choice enforcement on the wire. The catalog nudge states the same contract in
|
|
400
|
+
* prose, but without functionCallingConfig the model is free to ignore it. "auto" stays absent
|
|
401
|
+
* so the common case is byte-identical. The allowedTools variant already filters the
|
|
402
|
+
* declarations in toolsToGeminiFormat; only its "required" half needs a wire mode.
|
|
403
|
+
*/
|
|
404
|
+
function toolChoiceToGeminiToolConfig(parsed: OcxParsedRequest): Record<string, unknown> | undefined {
|
|
405
|
+
const choice = parsed.options.toolChoice;
|
|
406
|
+
if (!choice || choice === "auto") return undefined;
|
|
407
|
+
if (choice === "none") return { functionCallingConfig: { mode: "NONE" } };
|
|
408
|
+
if (choice === "required") return { functionCallingConfig: { mode: "ANY" } };
|
|
409
|
+
if (isAllowedToolChoice(choice)) {
|
|
410
|
+
return choice.mode === "required" ? { functionCallingConfig: { mode: "ANY" } } : undefined;
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
functionCallingConfig: {
|
|
414
|
+
mode: "ANY",
|
|
415
|
+
allowedFunctionNames: [resolveToolChoiceWireName(parsed.context.tools, choice.name)],
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
232
420
|
function usageFromGemini(usage: Record<string, number> | undefined): OcxUsage | undefined {
|
|
233
421
|
if (!usage) return undefined;
|
|
234
422
|
return {
|
|
@@ -271,17 +459,259 @@ function artifactMarkdownUrl(filePath: string): string {
|
|
|
271
459
|
return artifactHttpUrl(filePath).replace(/([()])/g, "\\$1");
|
|
272
460
|
}
|
|
273
461
|
|
|
462
|
+
interface GoogleResponsePart {
|
|
463
|
+
text?: unknown;
|
|
464
|
+
thought?: boolean;
|
|
465
|
+
thoughtSignature?: string;
|
|
466
|
+
thought_signature?: string;
|
|
467
|
+
extra_content?: { google?: { thought_signature?: unknown } };
|
|
468
|
+
functionCall?: unknown;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
interface GoogleFunctionCall {
|
|
472
|
+
name: string;
|
|
473
|
+
args?: unknown;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Read a Gemini/Antigravity thought signature from a response part. Antigravity can place it
|
|
478
|
+
* either directly on the part (`thoughtSignature` / `thought_signature`) or inside the same
|
|
479
|
+
* nested `extra_content.google.thought_signature` shape used on the Responses wire.
|
|
480
|
+
*/
|
|
481
|
+
function googlePartThoughtSignature(part: GoogleResponsePart): string | undefined {
|
|
482
|
+
const direct = part.thoughtSignature ?? part.thought_signature;
|
|
483
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
484
|
+
const nested = part.extra_content?.google?.thought_signature;
|
|
485
|
+
return typeof nested === "string" && nested.length > 0 ? nested : undefined;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Carry a Gemini thought signature with the exact function-call part that produced it. Google
|
|
490
|
+
* validates the signature against that specific part, so it must ride the individual tool call
|
|
491
|
+
* rather than be re-matched by name/arguments later (issue #1735).
|
|
492
|
+
*/
|
|
493
|
+
function googleToolCallMetadataFromPart(
|
|
494
|
+
part: GoogleResponsePart,
|
|
495
|
+
fallbackSignature?: string,
|
|
496
|
+
): { providerMetadata: OcxProviderOpaqueToolCallMetadata } | undefined {
|
|
497
|
+
const signature = googlePartThoughtSignature(part) ?? fallbackSignature;
|
|
498
|
+
if (!isLikelyRealThoughtSignature(signature)) return undefined;
|
|
499
|
+
return { providerMetadata: { google: { thoughtSignature: signature } } };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Google marks model-internal reasoning as a normal text-bearing part plus `thought: true`.
|
|
504
|
+
* Keep that provider visibility bit authoritative here so the streaming and buffered parsers
|
|
505
|
+
* cannot accidentally expose the same hidden reasoning through different event types.
|
|
506
|
+
*/
|
|
507
|
+
function googlePartTextEvent(part: GoogleResponsePart): AdapterEvent | undefined {
|
|
508
|
+
// A malformed scalar/object is not text and must not cross the AdapterEvent boundary. Dropping
|
|
509
|
+
// only this optional field preserves the rest of the part without inventing assistant output by
|
|
510
|
+
// coercion; an empty string keeps its existing no-event behavior.
|
|
511
|
+
if (typeof part.text !== "string" || part.text.length === 0) return undefined;
|
|
512
|
+
return part.thought === true
|
|
513
|
+
? { type: "reasoning_raw_delta", text: part.text }
|
|
514
|
+
: { type: "text_delta", text: part.text };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
interface InvalidGoogleFunctionCallDiagnostic {
|
|
518
|
+
reason:
|
|
519
|
+
| "function_call_not_object"
|
|
520
|
+
| "function_call_name_invalid"
|
|
521
|
+
| "function_call_name_blank";
|
|
522
|
+
partIndex: number;
|
|
523
|
+
valueType: string;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
interface InvalidGoogleShapeDiagnostic {
|
|
527
|
+
reason:
|
|
528
|
+
| "candidates_not_array"
|
|
529
|
+
| "candidate_not_object"
|
|
530
|
+
| "content_not_object"
|
|
531
|
+
| "parts_not_array"
|
|
532
|
+
| "part_not_object";
|
|
533
|
+
partIndex?: number;
|
|
534
|
+
valueType: string;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function isGoogleRecord(value: unknown): value is Record<string, unknown> {
|
|
538
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function googleStructuralValueType(value: unknown): string {
|
|
542
|
+
if (value === null) return "null";
|
|
543
|
+
return Array.isArray(value) ? "array" : typeof value;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Gemini delivers one complete functionCall per part, so a missing name cannot be repaired by a
|
|
548
|
+
* later delta. Validate the whole parts array before observing signatures or emitting content: a
|
|
549
|
+
* malformed call must terminate the claimed response rather than enter replay state or reach the
|
|
550
|
+
* bridge as a nameless dispatch. Null remains an absence encoding, matching other optional fields.
|
|
551
|
+
*/
|
|
552
|
+
function diagnoseGoogleFunctionCalls(
|
|
553
|
+
parts: readonly GoogleResponsePart[],
|
|
554
|
+
): InvalidGoogleFunctionCallDiagnostic | undefined {
|
|
555
|
+
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
556
|
+
const functionCall = parts[partIndex]?.functionCall;
|
|
557
|
+
if (functionCall === undefined || functionCall === null) continue;
|
|
558
|
+
if (!isGoogleRecord(functionCall)) {
|
|
559
|
+
return {
|
|
560
|
+
reason: "function_call_not_object",
|
|
561
|
+
partIndex,
|
|
562
|
+
valueType: googleStructuralValueType(functionCall),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
if (typeof functionCall.name !== "string") {
|
|
566
|
+
return {
|
|
567
|
+
reason: "function_call_name_invalid",
|
|
568
|
+
partIndex,
|
|
569
|
+
valueType: googleStructuralValueType(functionCall.name),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
if (functionCall.name.trim().length === 0) {
|
|
573
|
+
return {
|
|
574
|
+
reason: "function_call_name_blank",
|
|
575
|
+
partIndex,
|
|
576
|
+
valueType: "string",
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function googleFunctionCall(part: GoogleResponsePart): GoogleFunctionCall | undefined {
|
|
584
|
+
const functionCall = part.functionCall;
|
|
585
|
+
if (!isGoogleRecord(functionCall) || typeof functionCall.name !== "string") return undefined;
|
|
586
|
+
return { name: functionCall.name, args: functionCall.args };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function invalidGoogleFunctionCallEvent(
|
|
590
|
+
diagnostic: InvalidGoogleFunctionCallDiagnostic,
|
|
591
|
+
): Extract<AdapterEvent, { type: "error" }> {
|
|
592
|
+
const subject = diagnostic.reason === "function_call_not_object"
|
|
593
|
+
? "invalid function call"
|
|
594
|
+
: diagnostic.reason === "function_call_name_blank"
|
|
595
|
+
? "blank function call name"
|
|
596
|
+
: "invalid function call name";
|
|
597
|
+
return {
|
|
598
|
+
type: "error",
|
|
599
|
+
message: `google response contained ${subject} (${diagnostic.reason}; partIndex=${diagnostic.partIndex}; valueType=${diagnostic.valueType}) — cannot dispatch`,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* A candidate's `content` is claimed model output inside a well-formed frame, so it is governed by
|
|
605
|
+
* the #1332 nested-shape rule (fail closed) rather than #1240's root-frame padding rule (skip).
|
|
606
|
+
*
|
|
607
|
+
* Absence stays legal, and so does one encoding of it: an empty array is how a JSON writer with no
|
|
608
|
+
* distinct empty-object form spells an empty `content`, and it already behaves as "no parts". A
|
|
609
|
+
* NON-empty array is the opposite case — `content?.parts` silently reads `undefined` from it, so a
|
|
610
|
+
* candidate shaped `content: [{ parts: [...] }]` dropped its own text and completed as an empty
|
|
611
|
+
* turn.
|
|
612
|
+
*/
|
|
613
|
+
function diagnoseGoogleContent(content: unknown): InvalidGoogleShapeDiagnostic | undefined {
|
|
614
|
+
if (content === undefined || content === null || isGoogleRecord(content)) return undefined;
|
|
615
|
+
if (Array.isArray(content) && content.length === 0) return undefined;
|
|
616
|
+
return { reason: "content_not_object", valueType: googleStructuralValueType(content) };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* `content.parts` sits one rung below the candidate guard added in #1332, and both parsers
|
|
621
|
+
* consumed it unchecked: `for (const part of {})` throws `{} is not iterable`, and a `[null]`
|
|
622
|
+
* element throws on `part.thoughtSignature`. A `parts` that is absent or `null` keeps its existing
|
|
623
|
+
* skip — only a present, non-null container is validated.
|
|
624
|
+
*/
|
|
625
|
+
function diagnoseGoogleParts(parts: unknown): InvalidGoogleShapeDiagnostic | undefined {
|
|
626
|
+
if (!Array.isArray(parts)) {
|
|
627
|
+
return { reason: "parts_not_array", valueType: googleStructuralValueType(parts) };
|
|
628
|
+
}
|
|
629
|
+
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
630
|
+
const part: unknown = parts[partIndex];
|
|
631
|
+
if (!isGoogleRecord(part)) {
|
|
632
|
+
return { reason: "part_not_object", partIndex, valueType: googleStructuralValueType(part) };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return undefined;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function invalidGoogleShapeEvent(
|
|
639
|
+
diagnostic: InvalidGoogleShapeDiagnostic,
|
|
640
|
+
): Extract<AdapterEvent, { type: "error" }> {
|
|
641
|
+
const at = diagnostic.partIndex !== undefined ? `; partIndex=${diagnostic.partIndex}` : "";
|
|
642
|
+
// The subject names the rung that failed, so an operator reading a log can tell a broken
|
|
643
|
+
// candidate list from a well-formed candidate whose parts are broken. The candidate subject keeps
|
|
644
|
+
// the exact wording #1332 introduced as its prefix, so an existing grep still matches.
|
|
645
|
+
const subject = diagnostic.reason === "candidates_not_array" || diagnostic.reason === "candidate_not_object"
|
|
646
|
+
? "candidates"
|
|
647
|
+
: diagnostic.reason === "content_not_object"
|
|
648
|
+
? "content"
|
|
649
|
+
: "content parts";
|
|
650
|
+
return {
|
|
651
|
+
type: "error",
|
|
652
|
+
message: `google response contained invalid ${subject} (${diagnostic.reason}${at}; valueType=${diagnostic.valueType})`,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
274
656
|
export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
275
657
|
// Per-request closure: resolveAdapter builds a fresh adapter per request (server.ts), so buildRequest
|
|
276
658
|
// can stash the CCA model/session for parseStream's reasoning-replay observation.
|
|
277
659
|
let antigravityModel: string | undefined;
|
|
278
660
|
let antigravitySession: string | undefined;
|
|
661
|
+
// Vertex returns the same opaque Gemini thought signatures as CCA, but its replay namespace
|
|
662
|
+
// must stay transport-scoped: a signature minted by one Google backend must never be sent to
|
|
663
|
+
// another merely because the public model id and first prompt happen to match.
|
|
664
|
+
let vertexReplayModel: string | undefined;
|
|
665
|
+
let vertexReplaySession: string | undefined;
|
|
279
666
|
let restoreGoogleToolName = (name: string): string => name;
|
|
667
|
+
let lastInjectedCallIds: string[] = [];
|
|
668
|
+
let lastReasoningReplayScope: OcxParsedRequest["_reasoningReplayScope"];
|
|
669
|
+
|
|
670
|
+
// Conservative batch invalidation: upstream Gemini/Antigravity errors (e.g.
|
|
671
|
+
// "Function call is missing a thought_signature in functionCall parts") do not specify which
|
|
672
|
+
// specific call_id was rejected. When a request containing replayed signatures is rejected,
|
|
673
|
+
// we evict all callIds injected in that turn (lastInjectedCallIds) from the durable store
|
|
674
|
+
// and clear the session replay cache, preventing poisoned-signature loops while allowing
|
|
675
|
+
// subsequent turns to re-accumulate valid signatures. Unrelated calls from other turns remain intact.
|
|
676
|
+
//
|
|
677
|
+
// Memory-cache clearing stays broad (any invalid-argument/signature error can poison the
|
|
678
|
+
// session replay cache), but durable-store eviction is intentionally narrower: it only runs
|
|
679
|
+
// when the error text explicitly mentions a signature, so a generic tool-schema
|
|
680
|
+
// INVALID_ARGUMENT does not destroy valid durable signatures.
|
|
681
|
+
function handleSignatureRejection(errorMessage?: string) {
|
|
682
|
+
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
683
|
+
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
684
|
+
const text = errorMessage ?? "";
|
|
685
|
+
const isInvalidArgument = /invalid_argument|invalid argument/i.test(text);
|
|
686
|
+
const isSignatureError = /signature|thought_signature|thoughtSignature/i.test(text);
|
|
687
|
+
// The in-memory Antigravity replay cache only exists for CCA/Vertex, so clearing it stays
|
|
688
|
+
// scoped to those modes (replayModel/replaySession are undefined elsewhere anyway).
|
|
689
|
+
if (
|
|
690
|
+
(provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
691
|
+
&& replayModel && replaySession && (isInvalidArgument || isSignatureError)
|
|
692
|
+
) {
|
|
693
|
+
clearAntigravityReplay(replayModel, replaySession);
|
|
694
|
+
}
|
|
695
|
+
// The DURABLE store is not mode-scoped: signatures are remembered through
|
|
696
|
+
// rememberAndSerializeExtraContent and read back by lookupReplayThoughtSignature on every
|
|
697
|
+
// Google mode, including AI Studio. Gating eviction on CCA/Vertex therefore left AI Studio
|
|
698
|
+
// with rejected signatures cached forever, replaying them into every subsequent turn — the
|
|
699
|
+
// store poisons itself and the request keeps failing. Eviction follows the same scope the
|
|
700
|
+
// write does.
|
|
701
|
+
if (isSignatureError) {
|
|
702
|
+
for (const callId of lastInjectedCallIds) {
|
|
703
|
+
forgetThoughtSignatureForReplay(callId, lastReasoningReplayScope);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
280
708
|
return {
|
|
281
709
|
name: "google",
|
|
282
710
|
|
|
283
|
-
// Vertex + Antigravity get Kiro-style retry/timeout + classified, redacted errors.
|
|
284
|
-
//
|
|
711
|
+
// Vertex + Antigravity get Kiro-style retry/timeout + classified, redacted errors.
|
|
712
|
+
// Direct AI-Studio uses the canonical server transport (fetchWithTransientRetry), which
|
|
713
|
+
// retries transient 5xx responses through providerFetch while preserving multi-key pool
|
|
714
|
+
// 429 rotation and raw error formatting.
|
|
285
715
|
...(provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
286
716
|
? {
|
|
287
717
|
fetchResponse: (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> =>
|
|
@@ -292,24 +722,52 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
292
722
|
: {}),
|
|
293
723
|
|
|
294
724
|
async buildRequest(parsed: OcxParsedRequest) {
|
|
295
|
-
const
|
|
725
|
+
const routedModelId = provider.googleMode === "cloud-code-assist"
|
|
726
|
+
? resolveAntigravityEffortWireModel(
|
|
727
|
+
parsed.modelId,
|
|
728
|
+
mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning),
|
|
729
|
+
provider.baseUrl,
|
|
730
|
+
).wireModelId
|
|
731
|
+
: provider.googleMode === "vertex"
|
|
732
|
+
? parsed.modelId
|
|
733
|
+
: resolveDirectGeminiWireModelId(parsed.modelId, provider.directGeminiWireRenames !== false);
|
|
734
|
+
// AI Studio's `-tiered` spelling is wire-only; CCA aliases may migrate to another generation.
|
|
735
|
+
const identityModelId = provider.googleMode === "cloud-code-assist" ? routedModelId : parsed.modelId;
|
|
736
|
+
const { systemInstruction, contents, replayedCallIds } = messagesToGeminiFormat(parsed, identityModelId);
|
|
737
|
+
lastInjectedCallIds = [...replayedCallIds];
|
|
738
|
+
lastReasoningReplayScope = parsed._reasoningReplayScope;
|
|
296
739
|
const tools = toolsToGeminiFormat(parsed);
|
|
297
740
|
|
|
298
741
|
const body: Record<string, unknown> = { contents };
|
|
299
742
|
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
300
743
|
if (tools) body.tools = tools;
|
|
744
|
+
// Only meaningful with declarations on the wire: mode ANY with an empty
|
|
745
|
+
// catalog is a guaranteed upstream 400.
|
|
746
|
+
const toolConfig = tools ? toolChoiceToGeminiToolConfig(parsed) : undefined;
|
|
747
|
+
if (toolConfig) body.toolConfig = toolConfig;
|
|
301
748
|
|
|
302
749
|
const generationConfig: Record<string, unknown> = {};
|
|
303
|
-
|
|
750
|
+
const clampedMaxOutputTokens = clampGoogleMaxOutputTokens(identityModelId, parsed.options.maxOutputTokens);
|
|
751
|
+
if (clampedMaxOutputTokens !== undefined) generationConfig.maxOutputTokens = clampedMaxOutputTokens;
|
|
304
752
|
if (parsed.options.temperature !== undefined) generationConfig.temperature = parsed.options.temperature;
|
|
305
753
|
if (parsed.options.topP !== undefined) generationConfig.topP = parsed.options.topP;
|
|
306
754
|
if (parsed.options.stopSequences) generationConfig.stopSequences = parsed.options.stopSequences;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
755
|
+
// Effort → thinkingLevel follows the configured ladder: any model advertising reasoning
|
|
756
|
+
// efforts (registry preset or user config) sends the mapped level, so a picker-selected
|
|
757
|
+
// effort actually reaches the wire (gemini-3.1-pro-preview ships a ladder). The original
|
|
758
|
+
// gemini-3.5/3.6-flash direct-mode slice stays hardcoded so unladdered configs keep their
|
|
759
|
+
// current behavior; Vertex participates only through an explicitly configured ladder (the
|
|
760
|
+
// seed google-vertex entry ships none). Image models are excluded — thinkingConfig would
|
|
761
|
+
// suppress the responseModalities fallback below. CCA maps effort on its envelope path.
|
|
762
|
+
const thinkingEligible = provider.googleMode !== "cloud-code-assist"
|
|
763
|
+
&& !isImageCapableModel(parsed.modelId)
|
|
764
|
+
&& (configuredReasoningEfforts(provider, parsed.modelId) !== undefined
|
|
765
|
+
|| (provider.googleMode !== "vertex"
|
|
766
|
+
&& (parsed.modelId === "gemini-3.5-flash" || parsed.modelId === "gemini-3.6-flash")));
|
|
767
|
+
const thinkingLevel = thinkingEligible
|
|
310
768
|
? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning)
|
|
311
769
|
: undefined;
|
|
312
|
-
if (
|
|
770
|
+
if (thinkingLevel) generationConfig.thinkingConfig = { thinkingLevel };
|
|
313
771
|
if (!generationConfig.thinkingConfig && isImageCapableModel(parsed.modelId)) {
|
|
314
772
|
generationConfig.responseModalities = ["TEXT", "IMAGE"];
|
|
315
773
|
}
|
|
@@ -331,7 +789,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
331
789
|
if (!project) throw new Error("Antigravity requires a discovered Cloud Code Assist project id (re-run `ocx login google-antigravity`).");
|
|
332
790
|
const sessionId = antigravitySessionId(parsed);
|
|
333
791
|
const mappedEffort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
334
|
-
const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(
|
|
792
|
+
const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(
|
|
793
|
+
parsed.modelId,
|
|
794
|
+
mappedEffort,
|
|
795
|
+
provider.baseUrl,
|
|
796
|
+
);
|
|
335
797
|
antigravityModel = wireModelId;
|
|
336
798
|
antigravitySession = sessionId;
|
|
337
799
|
// Effort → thinkingConfig for CCA (CLIProxyAPI proven: request.generationConfig.thinkingConfig).
|
|
@@ -349,6 +811,12 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
349
811
|
const draftRequest: Record<string, unknown> = { ...body, sessionId };
|
|
350
812
|
// Claude-on-Antigravity forces VALIDATED function calling (the real client always sets it).
|
|
351
813
|
if (/claude/i.test(wireModelId)) {
|
|
814
|
+
// VALIDATED would defeat a client's tool_choice "none": honor it by dropping the
|
|
815
|
+
// declarations instead, the wire shape of a tool-less Claude turn.
|
|
816
|
+
if (parsed.options.toolChoice === "none") {
|
|
817
|
+
delete draftRequest.tools;
|
|
818
|
+
delete draftRequest.toolConfig;
|
|
819
|
+
}
|
|
352
820
|
const existing = (draftRequest.toolConfig ?? {}) as Record<string, unknown>;
|
|
353
821
|
const fcc = (existing.functionCallingConfig ?? {}) as Record<string, unknown>;
|
|
354
822
|
draftRequest.toolConfig = { ...existing, functionCallingConfig: { ...fcc, mode: "VALIDATED" } };
|
|
@@ -364,11 +832,26 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
364
832
|
} else {
|
|
365
833
|
sanitizeAntigravityClaudeSignatures(contents);
|
|
366
834
|
}
|
|
835
|
+
// After replay, not instead of it: a real signature always wins, and the sentinel only
|
|
836
|
+
// fills a first functionCall that replay could not sign. Outside the cache branch too,
|
|
837
|
+
// because the turn still needs a signature when no session was ever recorded.
|
|
838
|
+
applyAntigravityThoughtSignatureFallback(wireModelId, contents);
|
|
839
|
+
// Claude-on-Antigravity rejects assistant-tail (model-tail in Gemini terms) histories
|
|
840
|
+
// as prefill: "This model does not support assistant message prefill. The conversation
|
|
841
|
+
// must end with a user message." Context compaction, previous_response_id expansion,
|
|
842
|
+
// and interrupted-turn replay can all produce a model-tail history. Append a user
|
|
843
|
+
// "(continue)" nudge, mirroring the anthropic adapter's tail guard (src/adapters/anthropic.ts).
|
|
844
|
+
if (/claude/i.test(wireModelId)) {
|
|
845
|
+
const last = contents.length > 0 ? contents[contents.length - 1] as { role?: string } : undefined;
|
|
846
|
+
if (!last || last.role === "model") {
|
|
847
|
+
contents.push({ role: "user", parts: [{ text: "(continue)" }] });
|
|
848
|
+
}
|
|
849
|
+
}
|
|
367
850
|
}
|
|
368
851
|
const envelope = {
|
|
369
852
|
model: wireModelId,
|
|
370
853
|
// The envelope's `userAgent` field is a protocol constant ("antigravity"), distinct from
|
|
371
|
-
// the HTTP `User-Agent` header (the real
|
|
854
|
+
// the HTTP `User-Agent` header (the real IDE UA). CLIProxyAPI `geminiToAntigravity` hardcodes
|
|
372
855
|
// the body field; only the header carries the versioned client string.
|
|
373
856
|
userAgent: "antigravity",
|
|
374
857
|
requestType: "agent",
|
|
@@ -384,6 +867,24 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
384
867
|
if (provider.googleMode === "vertex") {
|
|
385
868
|
const compiled = compileGoogleWireBody(body);
|
|
386
869
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
870
|
+
const vertexProject = provider.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || "api-key";
|
|
871
|
+
const vertexLocation = provider.location || process.env.GOOGLE_CLOUD_LOCATION || "global";
|
|
872
|
+
vertexReplayModel = `vertex:${vertexProject}:${vertexLocation}:${parsed.modelId}`;
|
|
873
|
+
vertexReplaySession = vertexReplaySessionId(parsed);
|
|
874
|
+
// Compile names before replay so the cache matches the exact provider-visible
|
|
875
|
+
// functionCall identity. This is the same bounded TTL/LRU store used by CCA, with the
|
|
876
|
+
// transport prefix above preventing cross-backend signature reuse (#1254).
|
|
877
|
+
if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
|
|
878
|
+
applyAntigravityReplay(
|
|
879
|
+
vertexReplayModel,
|
|
880
|
+
vertexReplaySession,
|
|
881
|
+
(compiled.body as { contents: unknown[] }).contents,
|
|
882
|
+
);
|
|
883
|
+
applyAntigravityThoughtSignatureFallback(
|
|
884
|
+
vertexReplayModel,
|
|
885
|
+
(compiled.body as { contents: unknown[] }).contents,
|
|
886
|
+
);
|
|
887
|
+
}
|
|
387
888
|
// Vertex AI: project/location endpoint with GCP ADC, or x-goog-api-key fast path.
|
|
388
889
|
const apiKey = resolveVertexApiKey(provider.apiKey);
|
|
389
890
|
if (apiKey) {
|
|
@@ -395,6 +896,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
395
896
|
if (!project) throw new Error("Vertex AI requires a project id (provider.project or GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT).");
|
|
396
897
|
const location = provider.location || process.env.GOOGLE_CLOUD_LOCATION;
|
|
397
898
|
if (!location) throw new Error("Vertex AI requires a location (provider.location or GOOGLE_CLOUD_LOCATION).");
|
|
899
|
+
const locationError = googleVertexLocationConfigError(location);
|
|
900
|
+
if (locationError) throw new Error(locationError);
|
|
398
901
|
const host = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`;
|
|
399
902
|
const url = `https://${host}/v1/projects/${project}/locations/${location}/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
400
903
|
const token = await getVertexAccessToken();
|
|
@@ -403,7 +906,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
403
906
|
}
|
|
404
907
|
|
|
405
908
|
// ai-studio (default): Generative Language API + x-goog-api-key.
|
|
406
|
-
const url = `${provider.baseUrl}/v1beta/models/${
|
|
909
|
+
const url = `${provider.baseUrl}/v1beta/models/${routedModelId}:${method}${streamParam}`;
|
|
407
910
|
const apiKey = provider.apiKey?.trim();
|
|
408
911
|
if (!apiKey) throw new Error("google (AI Studio) requires a non-empty API key");
|
|
409
912
|
headers["x-goog-api-key"] = apiKey;
|
|
@@ -433,6 +936,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
433
936
|
let lastFinishReason: string | undefined;
|
|
434
937
|
let sawAnyFrame = false;
|
|
435
938
|
let sawTerminalSignal = false;
|
|
939
|
+
let pendingStreamThoughtSig: string | undefined;
|
|
436
940
|
|
|
437
941
|
const handleDataLine = async function* (line: string): AsyncGenerator<AdapterEvent, "continue" | "content" | "terminate"> {
|
|
438
942
|
const payload = line.slice(5).trim();
|
|
@@ -443,24 +947,32 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
443
947
|
}
|
|
444
948
|
let emittedContentEvent = false;
|
|
445
949
|
|
|
446
|
-
let
|
|
950
|
+
let parsed: unknown;
|
|
447
951
|
try {
|
|
448
|
-
|
|
952
|
+
parsed = JSON.parse(payload);
|
|
449
953
|
} catch {
|
|
450
954
|
yield { type: "error", message: "malformed upstream SSE data frame" };
|
|
451
955
|
return "terminate";
|
|
452
956
|
}
|
|
957
|
+
// `JSON.parse("null")` returns null rather than throwing, so the catch above cannot cover
|
|
958
|
+
// it and the `chunk.error` read below crashed the stream (see openai-chat.ts). Skip such a
|
|
959
|
+
// frame rather than terminating, for the reason given there: it is padding between real
|
|
960
|
+
// frames, not a broken stream. Deliberately returns BEFORE `sawAnyFrame`, so a stream made
|
|
961
|
+
// only of non-record frames still fails the terminal-signal check below instead of
|
|
962
|
+
// completing empty. An unparseable frame stays terminal.
|
|
963
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
964
|
+
return "continue";
|
|
965
|
+
}
|
|
966
|
+
const chunk = parsed as Record<string, unknown>;
|
|
453
967
|
sawAnyFrame = true;
|
|
454
968
|
|
|
455
969
|
// Inline provider error inside a 200 stream → terminal error (see openai-chat.ts).
|
|
456
970
|
if (chunk.error) {
|
|
457
971
|
const err = chunk.error as { message?: string } | undefined;
|
|
458
972
|
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
459
|
-
// Drop the cache entry
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
clearAntigravityReplay(antigravityModel, antigravitySession);
|
|
463
|
-
}
|
|
973
|
+
// Drop the cache entry and durable store entry for rejected calls so the next turn
|
|
974
|
+
// starts clean instead of re-injecting a bad sig.
|
|
975
|
+
handleSignatureRejection(err?.message);
|
|
464
976
|
yield { type: "error", message: err?.message ?? "upstream error" };
|
|
465
977
|
return "terminate";
|
|
466
978
|
}
|
|
@@ -484,24 +996,91 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
484
996
|
pendingUsage = usageFromGemini(usageMeta);
|
|
485
997
|
sawTerminalSignal = true;
|
|
486
998
|
}
|
|
487
|
-
const
|
|
488
|
-
|
|
999
|
+
const rawCandidates = root.candidates;
|
|
1000
|
+
// `null` is an absence encoding, not corruption, and terminating on it is the #1219
|
|
1001
|
+
// failure mode one rung in: a `{"candidates":null}` frame arriving between a content
|
|
1002
|
+
// delta and the finish chunk killed a turn whose answer had already fully arrived. An
|
|
1003
|
+
// absent key and an empty array are already skipped here; `null` joins them. A non-null
|
|
1004
|
+
// non-array container is still claimed structure the parser cannot read, and stays
|
|
1005
|
+
// terminal.
|
|
1006
|
+
if (rawCandidates === undefined || rawCandidates === null) return "continue";
|
|
1007
|
+
if (!Array.isArray(rawCandidates)) {
|
|
1008
|
+
yield invalidGoogleShapeEvent({
|
|
1009
|
+
reason: "candidates_not_array",
|
|
1010
|
+
valueType: googleStructuralValueType(rawCandidates),
|
|
1011
|
+
});
|
|
1012
|
+
return "terminate";
|
|
1013
|
+
}
|
|
1014
|
+
if (rawCandidates.length === 0) return "continue";
|
|
1015
|
+
const rawCandidate = rawCandidates[0];
|
|
1016
|
+
if (!isGoogleRecord(rawCandidate)) {
|
|
1017
|
+
// Unlike a root `data: null` keepalive, this is a claimed response candidate. Treat it
|
|
1018
|
+
// as terminal protocol corruption so the turn cannot complete after silently losing
|
|
1019
|
+
// a candidate or tool call (#1325).
|
|
1020
|
+
yield invalidGoogleShapeEvent({
|
|
1021
|
+
reason: "candidate_not_object",
|
|
1022
|
+
valueType: googleStructuralValueType(rawCandidate),
|
|
1023
|
+
});
|
|
1024
|
+
return "terminate";
|
|
1025
|
+
}
|
|
1026
|
+
const candidate = rawCandidate as {
|
|
1027
|
+
content?: unknown;
|
|
1028
|
+
finishReason?: string;
|
|
1029
|
+
};
|
|
489
1030
|
|
|
490
|
-
if (typeof
|
|
491
|
-
lastFinishReason =
|
|
1031
|
+
if (typeof candidate.finishReason === "string" && candidate.finishReason) {
|
|
1032
|
+
lastFinishReason = candidate.finishReason;
|
|
492
1033
|
sawTerminalSignal = true;
|
|
493
1034
|
}
|
|
494
1035
|
|
|
495
|
-
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
1036
|
+
// One rung below the candidate guard above, same rule: this is claimed content, not
|
|
1037
|
+
// padding, so it fails closed rather than being iterated or silently dropped (#1325).
|
|
1038
|
+
const rawContent: unknown = candidate.content;
|
|
1039
|
+
const invalidContent = diagnoseGoogleContent(rawContent);
|
|
1040
|
+
if (invalidContent) {
|
|
1041
|
+
yield invalidGoogleShapeEvent(invalidContent);
|
|
1042
|
+
return "terminate";
|
|
1043
|
+
}
|
|
1044
|
+
const rawParts: unknown = isGoogleRecord(rawContent) ? rawContent.parts : undefined;
|
|
1045
|
+
let parts: GoogleResponsePart[] | undefined;
|
|
1046
|
+
if (rawParts !== undefined && rawParts !== null) {
|
|
1047
|
+
const invalidParts = diagnoseGoogleParts(rawParts);
|
|
1048
|
+
if (invalidParts) {
|
|
1049
|
+
yield invalidGoogleShapeEvent(invalidParts);
|
|
1050
|
+
return "terminate";
|
|
1051
|
+
}
|
|
1052
|
+
parts = rawParts as GoogleResponsePart[];
|
|
1053
|
+
const invalidFunctionCall = diagnoseGoogleFunctionCalls(parts);
|
|
1054
|
+
if (invalidFunctionCall) {
|
|
1055
|
+
yield invalidGoogleFunctionCallEvent(invalidFunctionCall);
|
|
1056
|
+
return "terminate";
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
// Record Gemini thought signatures for the next stateless tool-result turn. Vertex and
|
|
1060
|
+
// Antigravity use separate model namespaces so opaque provider state cannot cross routes.
|
|
1061
|
+
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
1062
|
+
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
1063
|
+
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1064
|
+
&& parts && replayModel && replaySession) {
|
|
1065
|
+
// Observation may scan the whole frame, so use it only for replay-cache side effects.
|
|
1066
|
+
// The source-order loop below exclusively owns stream carry and cannot pair backwards.
|
|
1067
|
+
observeAntigravityReplay(
|
|
1068
|
+
replayModel,
|
|
1069
|
+
replaySession,
|
|
1070
|
+
parts as unknown[],
|
|
1071
|
+
pendingStreamThoughtSig,
|
|
1072
|
+
);
|
|
499
1073
|
}
|
|
500
1074
|
if (parts) {
|
|
501
1075
|
for (const part of parts) {
|
|
502
|
-
|
|
1076
|
+
const sig = googlePartThoughtSignature(part);
|
|
1077
|
+
if (part.thought === true && sig && isLikelyRealThoughtSignature(sig)) {
|
|
1078
|
+
pendingStreamThoughtSig = sig;
|
|
1079
|
+
}
|
|
1080
|
+
const textEvent = googlePartTextEvent(part);
|
|
1081
|
+
if (textEvent) {
|
|
503
1082
|
emittedContentEvent = true;
|
|
504
|
-
yield
|
|
1083
|
+
yield textEvent;
|
|
505
1084
|
}
|
|
506
1085
|
const inline = (part as { inlineData?: { mimeType?: string; data?: string } }).inlineData;
|
|
507
1086
|
if (inline && typeof inline.data === "string") {
|
|
@@ -518,12 +1097,19 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
518
1097
|
}
|
|
519
1098
|
}
|
|
520
1099
|
}
|
|
521
|
-
|
|
1100
|
+
const functionCall = googleFunctionCall(part);
|
|
1101
|
+
if (functionCall) {
|
|
522
1102
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
523
1103
|
toolCallsStarted++;
|
|
524
1104
|
emittedContentEvent = true;
|
|
525
|
-
|
|
526
|
-
yield {
|
|
1105
|
+
const restoredName = restoreGoogleToolName(functionCall.name);
|
|
1106
|
+
yield {
|
|
1107
|
+
type: "tool_call_start",
|
|
1108
|
+
id,
|
|
1109
|
+
name: restoredName,
|
|
1110
|
+
...googleToolCallMetadataFromPart(part, pendingStreamThoughtSig),
|
|
1111
|
+
};
|
|
1112
|
+
yield { type: "tool_call_delta", arguments: JSON.stringify(functionCall.args ?? {}) };
|
|
527
1113
|
yield { type: "tool_call_end" };
|
|
528
1114
|
}
|
|
529
1115
|
}
|
|
@@ -587,7 +1173,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
587
1173
|
// Fail-closed: a turn cut off mid tool call (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces
|
|
588
1174
|
// an error instead of a silently-incomplete done. Mirrors kiro-truncation.
|
|
589
1175
|
if ((provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist")
|
|
590
|
-
&&
|
|
1176
|
+
&& isVertexTruncatedTurn(lastFinishReason, toolCallsStarted)) {
|
|
591
1177
|
yield { type: "error", message: vertexTruncationErrorMessage(lastFinishReason) };
|
|
592
1178
|
return;
|
|
593
1179
|
}
|
|
@@ -673,7 +1259,18 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
673
1259
|
let raw: Record<string, unknown>;
|
|
674
1260
|
let rawBytes = 0;
|
|
675
1261
|
try {
|
|
676
|
-
|
|
1262
|
+
const parsedRaw: unknown = JSON.parse(rawText);
|
|
1263
|
+
// `JSON.parse("null")` returns null instead of throwing, so the catch below cannot see it
|
|
1264
|
+
// and the `raw.error` read crashed the turn — #1219 at the buffered body root, which #1240
|
|
1265
|
+
// never reached because that audit swept SSE frame parsers only. There is no next frame to
|
|
1266
|
+
// recover into here, so unlike a stream frame this fails closed, matching the
|
|
1267
|
+
// unparseable-body branch just below and the buffered candidate guards added in #2232.
|
|
1268
|
+
if (!isGoogleRecord(parsedRaw)) {
|
|
1269
|
+
budget.releaseRetained(rawTextBytes, { kind: "retained_collectors" });
|
|
1270
|
+
const valueType = googleStructuralValueType(parsedRaw);
|
|
1271
|
+
return [{ type: "error", message: `google response was not a JSON object (${valueType})` }];
|
|
1272
|
+
}
|
|
1273
|
+
raw = parsedRaw;
|
|
677
1274
|
rawBytes = new TextEncoder().encode(JSON.stringify(raw)).byteLength;
|
|
678
1275
|
const rawReservation = budget.reserveTransient(rawBytes, { kind: "retained_collectors" });
|
|
679
1276
|
rawReservation.commitRetained();
|
|
@@ -690,6 +1287,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
690
1287
|
};
|
|
691
1288
|
if (raw.error) {
|
|
692
1289
|
const err = raw.error as { message?: string };
|
|
1290
|
+
handleSignatureRejection(err.message);
|
|
693
1291
|
return finish([{ type: "error", message: err.message ?? "upstream error" }]);
|
|
694
1292
|
}
|
|
695
1293
|
// Antigravity (CCA) nests the standard Gemini payload under `response`; unwrap it.
|
|
@@ -703,19 +1301,60 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
703
1301
|
}
|
|
704
1302
|
const events: AdapterEvent[] = [];
|
|
705
1303
|
|
|
706
|
-
const
|
|
1304
|
+
const rawCandidates: unknown = json.candidates;
|
|
1305
|
+
// Parity with the streaming path, which has rejected a non-array `candidates` since #1332.
|
|
1306
|
+
// Buffered accepted `"abc"` outright (`"abc".length` is 3, so the emptiness check below
|
|
1307
|
+
// passed and `candidates[0]` was the character `"a"`), and reported `{}`/`5` as an absent
|
|
1308
|
+
// candidate list rather than a malformed one.
|
|
1309
|
+
if (rawCandidates !== undefined && rawCandidates !== null && !Array.isArray(rawCandidates)) {
|
|
1310
|
+
return finish([invalidGoogleShapeEvent({
|
|
1311
|
+
reason: "candidates_not_array",
|
|
1312
|
+
valueType: googleStructuralValueType(rawCandidates),
|
|
1313
|
+
})]);
|
|
1314
|
+
}
|
|
1315
|
+
const candidates = rawCandidates as { finishReason?: string }[] | undefined;
|
|
707
1316
|
if (!candidates?.length) {
|
|
708
1317
|
return finish([{ type: "error", message: "google response contained no candidates" }]);
|
|
709
1318
|
}
|
|
1319
|
+
const rawCandidate: unknown = candidates[0];
|
|
1320
|
+
if (!isGoogleRecord(rawCandidate)) {
|
|
1321
|
+
// The streaming parser already treats this as terminal protocol corruption (#1325/#1332).
|
|
1322
|
+
// Buffered returned a bare `done`, so a claimed-but-malformed candidate was reported to
|
|
1323
|
+
// the caller as a successful empty turn.
|
|
1324
|
+
return finish([invalidGoogleShapeEvent({
|
|
1325
|
+
reason: "candidate_not_object",
|
|
1326
|
+
valueType: googleStructuralValueType(rawCandidate),
|
|
1327
|
+
})]);
|
|
1328
|
+
}
|
|
1329
|
+
const candidate = rawCandidate as { content?: unknown; finishReason?: string };
|
|
710
1330
|
let toolCallsStarted = 0;
|
|
711
1331
|
const imageBudget = createImageBudget();
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1332
|
+
const rawContent: unknown = candidate.content;
|
|
1333
|
+
const invalidContent = diagnoseGoogleContent(rawContent);
|
|
1334
|
+
if (invalidContent) return finish([invalidGoogleShapeEvent(invalidContent)]);
|
|
1335
|
+
const rawParts: unknown = isGoogleRecord(rawContent) ? rawContent.parts : undefined;
|
|
1336
|
+
if (rawParts !== undefined && rawParts !== null) {
|
|
1337
|
+
const invalidParts = diagnoseGoogleParts(rawParts);
|
|
1338
|
+
if (invalidParts) return finish([invalidGoogleShapeEvent(invalidParts)]);
|
|
1339
|
+
const parts = rawParts as GoogleResponsePart[];
|
|
1340
|
+
const invalidFunctionCall = diagnoseGoogleFunctionCalls(parts);
|
|
1341
|
+
if (invalidFunctionCall) return finish([invalidGoogleFunctionCallEvent(invalidFunctionCall)]);
|
|
1342
|
+
// Non-streaming Google-family response: observe thought signatures for the next turn,
|
|
1343
|
+
// using the same transport-scoped namespace as the streaming path.
|
|
1344
|
+
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
1345
|
+
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
1346
|
+
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1347
|
+
&& replayModel && replaySession) {
|
|
1348
|
+
observeAntigravityReplay(replayModel, replaySession, parts as unknown[]);
|
|
716
1349
|
}
|
|
717
|
-
|
|
718
|
-
|
|
1350
|
+
let pendingThoughtSig: string | undefined;
|
|
1351
|
+
for (const part of parts) {
|
|
1352
|
+
const sig = googlePartThoughtSignature(part);
|
|
1353
|
+
if (part.thought === true && sig && isLikelyRealThoughtSignature(sig)) {
|
|
1354
|
+
pendingThoughtSig = sig;
|
|
1355
|
+
}
|
|
1356
|
+
const textEvent = googlePartTextEvent(part);
|
|
1357
|
+
if (textEvent) events.push(textEvent);
|
|
719
1358
|
const inline = (part as { inlineData?: { mimeType?: string; data?: string } }).inlineData;
|
|
720
1359
|
if (inline && typeof inline.data === "string") {
|
|
721
1360
|
if (inline.data.length > MAX_ENCODED_BYTES_PER_IMAGE) {
|
|
@@ -730,11 +1369,17 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
730
1369
|
}
|
|
731
1370
|
}
|
|
732
1371
|
}
|
|
733
|
-
|
|
1372
|
+
const functionCall = googleFunctionCall(part);
|
|
1373
|
+
if (functionCall) {
|
|
734
1374
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
735
1375
|
toolCallsStarted++;
|
|
736
|
-
events.push({
|
|
737
|
-
|
|
1376
|
+
events.push({
|
|
1377
|
+
type: "tool_call_start",
|
|
1378
|
+
id,
|
|
1379
|
+
name: restoreGoogleToolName(functionCall.name),
|
|
1380
|
+
...googleToolCallMetadataFromPart(part, pendingThoughtSig),
|
|
1381
|
+
});
|
|
1382
|
+
events.push({ type: "tool_call_delta", arguments: JSON.stringify(functionCall.args ?? {}) });
|
|
738
1383
|
events.push({ type: "tool_call_end" });
|
|
739
1384
|
}
|
|
740
1385
|
}
|
|
@@ -743,14 +1388,25 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
743
1388
|
// Fail-closed truncation, same as the stream path: a non-stream turn cut off mid tool call
|
|
744
1389
|
// (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces an error instead of a silent done.
|
|
745
1390
|
if ((provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist")
|
|
746
|
-
&&
|
|
747
|
-
return finish([{ type: "error", message: vertexTruncationErrorMessage(
|
|
1391
|
+
&& isVertexTruncatedTurn(candidate.finishReason, toolCallsStarted)) {
|
|
1392
|
+
return finish([{ type: "error", message: vertexTruncationErrorMessage(candidate.finishReason) }]);
|
|
748
1393
|
}
|
|
749
1394
|
|
|
750
1395
|
const usage = json.usageMetadata as Record<string, number> | undefined;
|
|
1396
|
+
// Mirror the streaming path: a buffered turn cut off by the token limit or a content filter
|
|
1397
|
+
// must carry its stop reason, or the bridge sees a clean `done` and reports the truncated
|
|
1398
|
+
// turn as completed — and, on a compaction turn, installs the half-written summary as
|
|
1399
|
+
// replacement history (#422).
|
|
1400
|
+
const finishReason = candidate.finishReason as string | undefined;
|
|
1401
|
+
const stopReason = finishReason === "MAX_TOKENS"
|
|
1402
|
+
? "max_tokens"
|
|
1403
|
+
: ["SAFETY", "RECITATION", "BLOCKLIST", "PROHIBITED_CONTENT", "SPII"].includes(finishReason ?? "")
|
|
1404
|
+
? "content_filter"
|
|
1405
|
+
: undefined;
|
|
751
1406
|
events.push({
|
|
752
1407
|
type: "done",
|
|
753
1408
|
usage: usageFromGemini(usage),
|
|
1409
|
+
...(stopReason ? { stopReason } : {}),
|
|
754
1410
|
});
|
|
755
1411
|
return finish(events);
|
|
756
1412
|
},
|