@iislee/opencodex 2.11.0 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
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,32 @@ 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, toolAllowedByChoice } from "../types";
|
|
17
|
+
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolAllowedByChoice } 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 {
|
|
25
|
+
import { identifyRoutedModel } from "./identity";
|
|
24
26
|
import { antigravityUsesReplayCache, applyAntigravityReplay, clearAntigravityReplay, observeAntigravityReplay } from "./google-antigravity-replay";
|
|
25
27
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
28
|
+
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
29
|
+
import { lookupReplayThoughtSignature } from "../responses/thought-signature-replay";
|
|
26
30
|
import {
|
|
27
31
|
isTranslatorBudgetExceededError,
|
|
28
32
|
retainTranslatedEventBatch,
|
|
29
33
|
type TranslatorBudget,
|
|
30
34
|
} from "../lib/translator-budget";
|
|
31
35
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
32
|
-
import { mapReasoningEffort } from "../reasoning-effort";
|
|
36
|
+
import { configuredReasoningEfforts, mapReasoningEffort } from "../reasoning-effort";
|
|
33
37
|
|
|
34
38
|
// Google-family models (Gemini/Vertex/Antigravity) tend to emit long running commentary between
|
|
35
39
|
// tool calls. This steers them to keep the BETWEEN-STEP text to one line and reason internally
|
|
@@ -44,12 +48,36 @@ const GOOGLE_BREVITY_INSTRUCTION = [
|
|
|
44
48
|
"- 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
49
|
].join("\n");
|
|
46
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Some Google direct deployments expose current Gemini Flash generations with a `-tiered`
|
|
53
|
+
* wire suffix (`gemini-3.7-flash` -> `gemini-3.7-flash-tiered`). Keep the picker-visible id
|
|
54
|
+
* stable and make the mapping configurable for deployments that still serve the bare id.
|
|
55
|
+
*/
|
|
56
|
+
const GEMINI_DIRECT_WIRE_RENAMES: Record<string, string> = {
|
|
57
|
+
"gemini-3.7-flash": "gemini-3.7-flash-tiered",
|
|
58
|
+
"gemini-3.6-flash": "gemini-3.6-flash-tiered",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function resolveDirectGeminiWireModelId(modelId: string, applyRenames: boolean): string {
|
|
62
|
+
if (!applyRenames) return modelId;
|
|
63
|
+
return Object.hasOwn(GEMINI_DIRECT_WIRE_RENAMES, modelId)
|
|
64
|
+
? GEMINI_DIRECT_WIRE_RENAMES[modelId]!
|
|
65
|
+
: modelId;
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
/** Vertex API key: provider.apiKey if it looks real (not a sentinel), else GOOGLE_CLOUD_API_KEY env. */
|
|
48
69
|
function resolveVertexApiKey(optKey?: string): string | undefined {
|
|
49
70
|
const realKey = optKey && !optKey.startsWith("<") && optKey !== "N/A" ? optKey : undefined;
|
|
50
71
|
return realKey || process.env.GOOGLE_CLOUD_API_KEY;
|
|
51
72
|
}
|
|
52
73
|
|
|
74
|
+
/** Prefer Codex's stable opaque thread key; retain the existing deterministic fallback for clients
|
|
75
|
+
* that omit it. The replay store hashes this value and never retains the raw session identifier. */
|
|
76
|
+
function vertexReplaySessionId(parsed: OcxParsedRequest): string {
|
|
77
|
+
const threadId = parsed._clientThreadId?.trim();
|
|
78
|
+
return threadId || antigravitySessionId(parsed);
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
/**
|
|
54
82
|
* Stable tool-call id for the Gemini wire `functionCall.id` / `functionResponse.id` fields.
|
|
55
83
|
*
|
|
@@ -67,15 +95,9 @@ function resolveVertexApiKey(optKey?: string): string | undefined {
|
|
|
67
95
|
* the call/response pairing is preserved. Returns `undefined` for an empty id so the caller omits the
|
|
68
96
|
* field entirely rather than inventing a non-matching one.
|
|
69
97
|
*/
|
|
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
|
-
}
|
|
98
|
+
// Aliasing the stateless transform here would reintroduce the collision it cannot prevent:
|
|
99
|
+
// a rewritten id can equal a distinct raw id that already conforms. Use a request-scoped
|
|
100
|
+
// allocator, exactly as the Anthropic adapter does, so call/response pairing stays injective.
|
|
79
101
|
|
|
80
102
|
/**
|
|
81
103
|
* Inline image parts (Gemini `inline_data`) extracted from tool-result content. Only base64 data URLs
|
|
@@ -104,6 +126,7 @@ function toolResultImageParts(content: string | OcxContentPart[]): unknown[] {
|
|
|
104
126
|
*/
|
|
105
127
|
const GEMINI_EMPTY_PLACEHOLDER = "(empty)";
|
|
106
128
|
const GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER = "(empty tool output)";
|
|
129
|
+
const GEMINI_MISSING_TOOL_RESULT = "[missing tool_result for this tool_use in history]";
|
|
107
130
|
|
|
108
131
|
/** A Gemini text part, or undefined when the value cannot form a valid non-empty text block. */
|
|
109
132
|
function geminiTextPart(text: unknown): { text: string } | undefined {
|
|
@@ -122,20 +145,70 @@ function geminiToolResultText(content: string | OcxContentPart[]): string {
|
|
|
122
145
|
return hasContent ? contentPartsToText(content) : GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
|
|
123
146
|
}
|
|
124
147
|
|
|
125
|
-
function
|
|
148
|
+
function geminiToolResultParts(
|
|
149
|
+
msg: OcxToolResultMessage,
|
|
150
|
+
wireName: string,
|
|
151
|
+
wireCallId: string,
|
|
152
|
+
): unknown[] {
|
|
153
|
+
const functionResponse: Record<string, unknown> = {
|
|
154
|
+
name: wireName,
|
|
155
|
+
response: { result: geminiToolResultText(msg.content) },
|
|
156
|
+
id: wireCallId,
|
|
157
|
+
};
|
|
158
|
+
return [{ functionResponse }, ...toolResultImageParts(msg.content)];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function geminiMissingToolResultPart(wireName: string, wireCallId: string): unknown {
|
|
162
|
+
return {
|
|
163
|
+
functionResponse: {
|
|
164
|
+
name: wireName,
|
|
165
|
+
response: { result: GEMINI_MISSING_TOOL_RESULT },
|
|
166
|
+
id: wireCallId,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function geminiUnrepresentableToolCallPart(tc: OcxToolCall, wireName: string): unknown {
|
|
172
|
+
const args = typeof tc.arguments === "string" ? tc.arguments : JSON.stringify(tc.arguments);
|
|
173
|
+
return { text: `[tool_use without a usable id: ${wireName}]\n${args}` };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function geminiOrphanToolResultParts(msg: OcxToolResultMessage): unknown[] {
|
|
177
|
+
const label = msg.toolName ? `${msg.toolName} (${msg.toolCallId})` : msg.toolCallId;
|
|
178
|
+
return [
|
|
179
|
+
{ text: `[tool_result without adjacent tool_use: ${label}]\n${geminiToolResultText(msg.content)}` },
|
|
180
|
+
...toolResultImageParts(msg.content),
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function messagesToGeminiFormat(
|
|
185
|
+
parsed: OcxParsedRequest,
|
|
186
|
+
identityModelId: string,
|
|
187
|
+
): { systemInstruction?: unknown; contents: unknown[] } {
|
|
126
188
|
// Neutralize Codex's GPT-5 identity line (Gemini/Antigravity share this path) so a routed model
|
|
127
189
|
// never misreports as GPT-5/OpenAI, and never leaks the proxy identity upstream.
|
|
128
190
|
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(parsed.context.tools, parsed.options.toolChoice);
|
|
129
|
-
const systemText =
|
|
191
|
+
const systemText = identifyRoutedModel([
|
|
130
192
|
...(parsed.context.systemPrompt ?? []),
|
|
131
193
|
...(toolCatalogNudge ? [toolCatalogNudge] : []),
|
|
132
194
|
GOOGLE_BREVITY_INSTRUCTION,
|
|
133
|
-
].join("\n\n"));
|
|
195
|
+
].join("\n\n"), identityModelId);
|
|
134
196
|
const systemInstruction = { parts: [{ text: systemText }] };
|
|
135
197
|
|
|
136
198
|
const contents: unknown[] = [];
|
|
137
199
|
|
|
200
|
+
const callIds = createToolCallIdAllocator();
|
|
138
201
|
for (const msg of parsed.context.messages) {
|
|
202
|
+
if (msg.role === "assistant") {
|
|
203
|
+
for (const part of (msg as OcxAssistantMessage).content) {
|
|
204
|
+
if (part.type === "toolCall") callIds.reserve((part as OcxToolCall).id);
|
|
205
|
+
}
|
|
206
|
+
} else if (msg.role === "toolResult") {
|
|
207
|
+
callIds.reserve((msg as OcxToolResultMessage).toolCallId);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (let i = 0; i < parsed.context.messages.length; i++) {
|
|
211
|
+
const msg = parsed.context.messages[i];
|
|
139
212
|
switch (msg.role) {
|
|
140
213
|
case "user":
|
|
141
214
|
case "developer": {
|
|
@@ -162,6 +235,7 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
162
235
|
case "assistant": {
|
|
163
236
|
const aMsg = msg as OcxAssistantMessage;
|
|
164
237
|
const parts: unknown[] = [];
|
|
238
|
+
const toolCalls: Array<{ wireCallId: string; wireName: string }> = [];
|
|
165
239
|
for (const p of aMsg.content) {
|
|
166
240
|
if (p.type === "text") {
|
|
167
241
|
const textPart = geminiTextPart((p as OcxTextContent).text);
|
|
@@ -173,13 +247,31 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
173
247
|
// streaming covered by the replay cache. Only forward a REAL upstream signature — the
|
|
174
248
|
// Responses parser also stashes synthetic item ids (`fc_...`) on this field, and sending
|
|
175
249
|
// those as a thoughtSignature breaks continuity (the replay cache supplies the real one).
|
|
176
|
-
const callId =
|
|
177
|
-
const
|
|
250
|
+
const callId = callIds.allocate(tc.id);
|
|
251
|
+
const wireName = namespacedToolName(tc.namespace, tc.name);
|
|
252
|
+
if (callId === undefined) {
|
|
253
|
+
// Claude-on-Antigravity requires a usable id for every translated tool_use. An empty
|
|
254
|
+
// source id cannot be paired safely, so preserve the call as text and let its result
|
|
255
|
+
// follow the same orphan-text path instead of emitting an invalid functionCall.
|
|
256
|
+
parts.push(geminiUnrepresentableToolCallPart(tc, wireName));
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const functionCall: Record<string, unknown> = { name: wireName, args: tc.arguments };
|
|
178
260
|
// Claude-on-Antigravity maps this id to Anthropic `tool_use.id`; without it the upstream
|
|
179
261
|
// conversion 400s. Gemini accepts the optional id and pairs call/response by it.
|
|
180
|
-
|
|
262
|
+
functionCall.id = callId;
|
|
263
|
+
toolCalls.push({ wireCallId: callId, wireName });
|
|
181
264
|
const part: Record<string, unknown> = { functionCall };
|
|
182
|
-
|
|
265
|
+
// Prefer the metadata that travelled with this exact call; fall back to the legacy
|
|
266
|
+
// field for callers that have not been migrated. Never merge or synthesize.
|
|
267
|
+
// Final fallback (#1926): the durable store, read AT SERIALIZATION TIME. The
|
|
268
|
+
// Responses parser runs before the route/credential scope is bound, so its
|
|
269
|
+
// parse-time lookup can never hit; by the time this adapter serializes, the
|
|
270
|
+
// credential-scoped identity is bound and the durable lookup is meaningful.
|
|
271
|
+
const signature = tc.providerMetadata?.google?.thoughtSignature
|
|
272
|
+
?? tc.thoughtSignature
|
|
273
|
+
?? lookupReplayThoughtSignature(tc.id, parsed._reasoningReplayScope);
|
|
274
|
+
if (isLikelyRealThoughtSignature(signature)) part.thoughtSignature = signature;
|
|
183
275
|
parts.push(part);
|
|
184
276
|
}
|
|
185
277
|
}
|
|
@@ -188,21 +280,44 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
188
280
|
// adapter does for its own empty assistant content.
|
|
189
281
|
if (parts.length === 0) break;
|
|
190
282
|
contents.push({ role: "model", parts });
|
|
283
|
+
if (toolCalls.length > 0) {
|
|
284
|
+
// Gemini/Claude-on-Antigravity requires one adjacent response batch for the whole
|
|
285
|
+
// function-call turn. Replayed histories can be interrupted, reversed, duplicated, or
|
|
286
|
+
// contain an orphan result; repair only this wire boundary without inventing success.
|
|
287
|
+
const requiredIds = new Set(toolCalls.map(call => call.wireCallId));
|
|
288
|
+
const resultsById = new Map<string, OcxToolResultMessage>();
|
|
289
|
+
const orphanResults: OcxToolResultMessage[] = [];
|
|
290
|
+
let j = i + 1;
|
|
291
|
+
while (j < parsed.context.messages.length && parsed.context.messages[j].role === "toolResult") {
|
|
292
|
+
const result = parsed.context.messages[j] as OcxToolResultMessage;
|
|
293
|
+
const wireResultId = callIds.lookup(result.toolCallId);
|
|
294
|
+
if (wireResultId !== undefined && requiredIds.has(wireResultId) && !resultsById.has(wireResultId)) {
|
|
295
|
+
resultsById.set(wireResultId, result);
|
|
296
|
+
} else {
|
|
297
|
+
orphanResults.push(result);
|
|
298
|
+
}
|
|
299
|
+
j++;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const responseParts: unknown[] = [];
|
|
303
|
+
for (const call of toolCalls) {
|
|
304
|
+
const result = resultsById.get(call.wireCallId);
|
|
305
|
+
if (result) responseParts.push(...geminiToolResultParts(result, call.wireName, call.wireCallId));
|
|
306
|
+
else responseParts.push(geminiMissingToolResultPart(call.wireName, call.wireCallId));
|
|
307
|
+
}
|
|
308
|
+
for (const orphan of orphanResults) {
|
|
309
|
+
responseParts.push(...geminiOrphanToolResultParts(orphan));
|
|
310
|
+
}
|
|
311
|
+
contents.push({ role: "user", parts: responseParts });
|
|
312
|
+
i = j - 1;
|
|
313
|
+
}
|
|
191
314
|
break;
|
|
192
315
|
}
|
|
193
316
|
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 });
|
|
317
|
+
// A standalone functionResponse is invalid without an immediately preceding matching
|
|
318
|
+
// functionCall batch. Preserve the result as explicit user text (plus any representable
|
|
319
|
+
// image siblings) rather than manufacturing a successful call or sending a 400-prone shape.
|
|
320
|
+
contents.push({ role: "user", parts: geminiOrphanToolResultParts(msg as OcxToolResultMessage) });
|
|
206
321
|
break;
|
|
207
322
|
}
|
|
208
323
|
}
|
|
@@ -217,7 +332,7 @@ function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
|
217
332
|
? new Set(parsed.options.toolChoice.allowedTools)
|
|
218
333
|
: undefined;
|
|
219
334
|
const tools = allowed
|
|
220
|
-
? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
|
|
335
|
+
? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed, parsed.context.tools))
|
|
221
336
|
: parsed.context.tools;
|
|
222
337
|
if (tools.length === 0) return undefined;
|
|
223
338
|
return [{
|
|
@@ -229,6 +344,28 @@ function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
|
229
344
|
}];
|
|
230
345
|
}
|
|
231
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Client tool_choice enforcement on the wire. The catalog nudge states the same contract in
|
|
349
|
+
* prose, but without functionCallingConfig the model is free to ignore it. "auto" stays absent
|
|
350
|
+
* so the common case is byte-identical. The allowedTools variant already filters the
|
|
351
|
+
* declarations in toolsToGeminiFormat; only its "required" half needs a wire mode.
|
|
352
|
+
*/
|
|
353
|
+
function toolChoiceToGeminiToolConfig(parsed: OcxParsedRequest): Record<string, unknown> | undefined {
|
|
354
|
+
const choice = parsed.options.toolChoice;
|
|
355
|
+
if (!choice || choice === "auto") return undefined;
|
|
356
|
+
if (choice === "none") return { functionCallingConfig: { mode: "NONE" } };
|
|
357
|
+
if (choice === "required") return { functionCallingConfig: { mode: "ANY" } };
|
|
358
|
+
if (isAllowedToolChoice(choice)) {
|
|
359
|
+
return choice.mode === "required" ? { functionCallingConfig: { mode: "ANY" } } : undefined;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
functionCallingConfig: {
|
|
363
|
+
mode: "ANY",
|
|
364
|
+
allowedFunctionNames: [resolveToolChoiceWireName(parsed.context.tools, choice.name)],
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
232
369
|
function usageFromGemini(usage: Record<string, number> | undefined): OcxUsage | undefined {
|
|
233
370
|
if (!usage) return undefined;
|
|
234
371
|
return {
|
|
@@ -271,17 +408,218 @@ function artifactMarkdownUrl(filePath: string): string {
|
|
|
271
408
|
return artifactHttpUrl(filePath).replace(/([()])/g, "\\$1");
|
|
272
409
|
}
|
|
273
410
|
|
|
411
|
+
interface GoogleResponsePart {
|
|
412
|
+
text?: unknown;
|
|
413
|
+
thought?: boolean;
|
|
414
|
+
thoughtSignature?: string;
|
|
415
|
+
thought_signature?: string;
|
|
416
|
+
extra_content?: { google?: { thought_signature?: unknown } };
|
|
417
|
+
functionCall?: unknown;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
interface GoogleFunctionCall {
|
|
421
|
+
name: string;
|
|
422
|
+
args?: unknown;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Read a Gemini/Antigravity thought signature from a response part. Antigravity can place it
|
|
427
|
+
* either directly on the part (`thoughtSignature` / `thought_signature`) or inside the same
|
|
428
|
+
* nested `extra_content.google.thought_signature` shape used on the Responses wire.
|
|
429
|
+
*/
|
|
430
|
+
function googlePartThoughtSignature(part: GoogleResponsePart): string | undefined {
|
|
431
|
+
const direct = part.thoughtSignature ?? part.thought_signature;
|
|
432
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
433
|
+
const nested = part.extra_content?.google?.thought_signature;
|
|
434
|
+
return typeof nested === "string" && nested.length > 0 ? nested : undefined;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Carry a Gemini thought signature with the exact function-call part that produced it. Google
|
|
439
|
+
* validates the signature against that specific part, so it must ride the individual tool call
|
|
440
|
+
* rather than be re-matched by name/arguments later (issue #1735).
|
|
441
|
+
*/
|
|
442
|
+
function googleToolCallMetadataFromPart(
|
|
443
|
+
part: GoogleResponsePart,
|
|
444
|
+
fallbackSignature?: string,
|
|
445
|
+
): { providerMetadata: OcxProviderOpaqueToolCallMetadata } | undefined {
|
|
446
|
+
const signature = googlePartThoughtSignature(part) ?? fallbackSignature;
|
|
447
|
+
if (!isLikelyRealThoughtSignature(signature)) return undefined;
|
|
448
|
+
return { providerMetadata: { google: { thoughtSignature: signature } } };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Google marks model-internal reasoning as a normal text-bearing part plus `thought: true`.
|
|
453
|
+
* Keep that provider visibility bit authoritative here so the streaming and buffered parsers
|
|
454
|
+
* cannot accidentally expose the same hidden reasoning through different event types.
|
|
455
|
+
*/
|
|
456
|
+
function googlePartTextEvent(part: GoogleResponsePart): AdapterEvent | undefined {
|
|
457
|
+
// A malformed scalar/object is not text and must not cross the AdapterEvent boundary. Dropping
|
|
458
|
+
// only this optional field preserves the rest of the part without inventing assistant output by
|
|
459
|
+
// coercion; an empty string keeps its existing no-event behavior.
|
|
460
|
+
if (typeof part.text !== "string" || part.text.length === 0) return undefined;
|
|
461
|
+
return part.thought === true
|
|
462
|
+
? { type: "reasoning_raw_delta", text: part.text }
|
|
463
|
+
: { type: "text_delta", text: part.text };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
interface InvalidGoogleFunctionCallDiagnostic {
|
|
467
|
+
reason:
|
|
468
|
+
| "function_call_not_object"
|
|
469
|
+
| "function_call_name_invalid"
|
|
470
|
+
| "function_call_name_blank";
|
|
471
|
+
partIndex: number;
|
|
472
|
+
valueType: string;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
interface InvalidGoogleShapeDiagnostic {
|
|
476
|
+
reason:
|
|
477
|
+
| "candidates_not_array"
|
|
478
|
+
| "candidate_not_object"
|
|
479
|
+
| "content_not_object"
|
|
480
|
+
| "parts_not_array"
|
|
481
|
+
| "part_not_object";
|
|
482
|
+
partIndex?: number;
|
|
483
|
+
valueType: string;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function isGoogleRecord(value: unknown): value is Record<string, unknown> {
|
|
487
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function googleStructuralValueType(value: unknown): string {
|
|
491
|
+
if (value === null) return "null";
|
|
492
|
+
return Array.isArray(value) ? "array" : typeof value;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Gemini delivers one complete functionCall per part, so a missing name cannot be repaired by a
|
|
497
|
+
* later delta. Validate the whole parts array before observing signatures or emitting content: a
|
|
498
|
+
* malformed call must terminate the claimed response rather than enter replay state or reach the
|
|
499
|
+
* bridge as a nameless dispatch. Null remains an absence encoding, matching other optional fields.
|
|
500
|
+
*/
|
|
501
|
+
function diagnoseGoogleFunctionCalls(
|
|
502
|
+
parts: readonly GoogleResponsePart[],
|
|
503
|
+
): InvalidGoogleFunctionCallDiagnostic | undefined {
|
|
504
|
+
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
505
|
+
const functionCall = parts[partIndex]?.functionCall;
|
|
506
|
+
if (functionCall === undefined || functionCall === null) continue;
|
|
507
|
+
if (!isGoogleRecord(functionCall)) {
|
|
508
|
+
return {
|
|
509
|
+
reason: "function_call_not_object",
|
|
510
|
+
partIndex,
|
|
511
|
+
valueType: googleStructuralValueType(functionCall),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
if (typeof functionCall.name !== "string") {
|
|
515
|
+
return {
|
|
516
|
+
reason: "function_call_name_invalid",
|
|
517
|
+
partIndex,
|
|
518
|
+
valueType: googleStructuralValueType(functionCall.name),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
if (functionCall.name.trim().length === 0) {
|
|
522
|
+
return {
|
|
523
|
+
reason: "function_call_name_blank",
|
|
524
|
+
partIndex,
|
|
525
|
+
valueType: "string",
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function googleFunctionCall(part: GoogleResponsePart): GoogleFunctionCall | undefined {
|
|
533
|
+
const functionCall = part.functionCall;
|
|
534
|
+
if (!isGoogleRecord(functionCall) || typeof functionCall.name !== "string") return undefined;
|
|
535
|
+
return { name: functionCall.name, args: functionCall.args };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function invalidGoogleFunctionCallEvent(
|
|
539
|
+
diagnostic: InvalidGoogleFunctionCallDiagnostic,
|
|
540
|
+
): Extract<AdapterEvent, { type: "error" }> {
|
|
541
|
+
const subject = diagnostic.reason === "function_call_not_object"
|
|
542
|
+
? "invalid function call"
|
|
543
|
+
: diagnostic.reason === "function_call_name_blank"
|
|
544
|
+
? "blank function call name"
|
|
545
|
+
: "invalid function call name";
|
|
546
|
+
return {
|
|
547
|
+
type: "error",
|
|
548
|
+
message: `google response contained ${subject} (${diagnostic.reason}; partIndex=${diagnostic.partIndex}; valueType=${diagnostic.valueType}) — cannot dispatch`,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* A candidate's `content` is claimed model output inside a well-formed frame, so it is governed by
|
|
554
|
+
* the #1332 nested-shape rule (fail closed) rather than #1240's root-frame padding rule (skip).
|
|
555
|
+
*
|
|
556
|
+
* Absence stays legal, and so does one encoding of it: an empty array is how a JSON writer with no
|
|
557
|
+
* distinct empty-object form spells an empty `content`, and it already behaves as "no parts". A
|
|
558
|
+
* NON-empty array is the opposite case — `content?.parts` silently reads `undefined` from it, so a
|
|
559
|
+
* candidate shaped `content: [{ parts: [...] }]` dropped its own text and completed as an empty
|
|
560
|
+
* turn.
|
|
561
|
+
*/
|
|
562
|
+
function diagnoseGoogleContent(content: unknown): InvalidGoogleShapeDiagnostic | undefined {
|
|
563
|
+
if (content === undefined || content === null || isGoogleRecord(content)) return undefined;
|
|
564
|
+
if (Array.isArray(content) && content.length === 0) return undefined;
|
|
565
|
+
return { reason: "content_not_object", valueType: googleStructuralValueType(content) };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* `content.parts` sits one rung below the candidate guard added in #1332, and both parsers
|
|
570
|
+
* consumed it unchecked: `for (const part of {})` throws `{} is not iterable`, and a `[null]`
|
|
571
|
+
* element throws on `part.thoughtSignature`. A `parts` that is absent or `null` keeps its existing
|
|
572
|
+
* skip — only a present, non-null container is validated.
|
|
573
|
+
*/
|
|
574
|
+
function diagnoseGoogleParts(parts: unknown): InvalidGoogleShapeDiagnostic | undefined {
|
|
575
|
+
if (!Array.isArray(parts)) {
|
|
576
|
+
return { reason: "parts_not_array", valueType: googleStructuralValueType(parts) };
|
|
577
|
+
}
|
|
578
|
+
for (let partIndex = 0; partIndex < parts.length; partIndex++) {
|
|
579
|
+
const part: unknown = parts[partIndex];
|
|
580
|
+
if (!isGoogleRecord(part)) {
|
|
581
|
+
return { reason: "part_not_object", partIndex, valueType: googleStructuralValueType(part) };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function invalidGoogleShapeEvent(
|
|
588
|
+
diagnostic: InvalidGoogleShapeDiagnostic,
|
|
589
|
+
): Extract<AdapterEvent, { type: "error" }> {
|
|
590
|
+
const at = diagnostic.partIndex !== undefined ? `; partIndex=${diagnostic.partIndex}` : "";
|
|
591
|
+
// The subject names the rung that failed, so an operator reading a log can tell a broken
|
|
592
|
+
// candidate list from a well-formed candidate whose parts are broken. The candidate subject keeps
|
|
593
|
+
// the exact wording #1332 introduced as its prefix, so an existing grep still matches.
|
|
594
|
+
const subject = diagnostic.reason === "candidates_not_array" || diagnostic.reason === "candidate_not_object"
|
|
595
|
+
? "candidates"
|
|
596
|
+
: diagnostic.reason === "content_not_object"
|
|
597
|
+
? "content"
|
|
598
|
+
: "content parts";
|
|
599
|
+
return {
|
|
600
|
+
type: "error",
|
|
601
|
+
message: `google response contained invalid ${subject} (${diagnostic.reason}${at}; valueType=${diagnostic.valueType})`,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
274
605
|
export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
275
606
|
// Per-request closure: resolveAdapter builds a fresh adapter per request (server.ts), so buildRequest
|
|
276
607
|
// can stash the CCA model/session for parseStream's reasoning-replay observation.
|
|
277
608
|
let antigravityModel: string | undefined;
|
|
278
609
|
let antigravitySession: string | undefined;
|
|
610
|
+
// Vertex returns the same opaque Gemini thought signatures as CCA, but its replay namespace
|
|
611
|
+
// must stay transport-scoped: a signature minted by one Google backend must never be sent to
|
|
612
|
+
// another merely because the public model id and first prompt happen to match.
|
|
613
|
+
let vertexReplayModel: string | undefined;
|
|
614
|
+
let vertexReplaySession: string | undefined;
|
|
279
615
|
let restoreGoogleToolName = (name: string): string => name;
|
|
280
616
|
return {
|
|
281
617
|
name: "google",
|
|
282
618
|
|
|
283
|
-
// Vertex + Antigravity get Kiro-style retry/timeout + classified, redacted errors.
|
|
284
|
-
//
|
|
619
|
+
// Vertex + Antigravity get Kiro-style retry/timeout + classified, redacted errors.
|
|
620
|
+
// Direct AI-Studio uses the canonical server transport (fetchWithTransientRetry), which
|
|
621
|
+
// retries transient 5xx responses through providerFetch while preserving multi-key pool
|
|
622
|
+
// 429 rotation and raw error formatting.
|
|
285
623
|
...(provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
|
|
286
624
|
? {
|
|
287
625
|
fetchResponse: (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> =>
|
|
@@ -292,24 +630,49 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
292
630
|
: {}),
|
|
293
631
|
|
|
294
632
|
async buildRequest(parsed: OcxParsedRequest) {
|
|
295
|
-
const
|
|
633
|
+
const routedModelId = provider.googleMode === "cloud-code-assist"
|
|
634
|
+
? resolveAntigravityEffortWireModel(
|
|
635
|
+
parsed.modelId,
|
|
636
|
+
mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning),
|
|
637
|
+
provider.baseUrl,
|
|
638
|
+
).wireModelId
|
|
639
|
+
: provider.googleMode === "vertex"
|
|
640
|
+
? parsed.modelId
|
|
641
|
+
: resolveDirectGeminiWireModelId(parsed.modelId, provider.directGeminiWireRenames !== false);
|
|
642
|
+
// AI Studio's `-tiered` spelling is wire-only; CCA aliases may migrate to another generation.
|
|
643
|
+
const identityModelId = provider.googleMode === "cloud-code-assist" ? routedModelId : parsed.modelId;
|
|
644
|
+
const { systemInstruction, contents } = messagesToGeminiFormat(parsed, identityModelId);
|
|
296
645
|
const tools = toolsToGeminiFormat(parsed);
|
|
297
646
|
|
|
298
647
|
const body: Record<string, unknown> = { contents };
|
|
299
648
|
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
300
649
|
if (tools) body.tools = tools;
|
|
650
|
+
// Only meaningful with declarations on the wire: mode ANY with an empty
|
|
651
|
+
// catalog is a guaranteed upstream 400.
|
|
652
|
+
const toolConfig = tools ? toolChoiceToGeminiToolConfig(parsed) : undefined;
|
|
653
|
+
if (toolConfig) body.toolConfig = toolConfig;
|
|
301
654
|
|
|
302
655
|
const generationConfig: Record<string, unknown> = {};
|
|
303
656
|
if (parsed.options.maxOutputTokens) generationConfig.maxOutputTokens = parsed.options.maxOutputTokens;
|
|
304
657
|
if (parsed.options.temperature !== undefined) generationConfig.temperature = parsed.options.temperature;
|
|
305
658
|
if (parsed.options.topP !== undefined) generationConfig.topP = parsed.options.topP;
|
|
306
659
|
if (parsed.options.stopSequences) generationConfig.stopSequences = parsed.options.stopSequences;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
660
|
+
// Effort → thinkingLevel follows the configured ladder: any model advertising reasoning
|
|
661
|
+
// efforts (registry preset or user config) sends the mapped level, so a picker-selected
|
|
662
|
+
// effort actually reaches the wire (gemini-3.1-pro-preview ships a ladder). The original
|
|
663
|
+
// gemini-3.5/3.6-flash direct-mode slice stays hardcoded so unladdered configs keep their
|
|
664
|
+
// current behavior; Vertex participates only through an explicitly configured ladder (the
|
|
665
|
+
// seed google-vertex entry ships none). Image models are excluded — thinkingConfig would
|
|
666
|
+
// suppress the responseModalities fallback below. CCA maps effort on its envelope path.
|
|
667
|
+
const thinkingEligible = provider.googleMode !== "cloud-code-assist"
|
|
668
|
+
&& !isImageCapableModel(parsed.modelId)
|
|
669
|
+
&& (configuredReasoningEfforts(provider, parsed.modelId) !== undefined
|
|
670
|
+
|| (provider.googleMode !== "vertex"
|
|
671
|
+
&& (parsed.modelId === "gemini-3.5-flash" || parsed.modelId === "gemini-3.6-flash")));
|
|
672
|
+
const thinkingLevel = thinkingEligible
|
|
310
673
|
? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning)
|
|
311
674
|
: undefined;
|
|
312
|
-
if (
|
|
675
|
+
if (thinkingLevel) generationConfig.thinkingConfig = { thinkingLevel };
|
|
313
676
|
if (!generationConfig.thinkingConfig && isImageCapableModel(parsed.modelId)) {
|
|
314
677
|
generationConfig.responseModalities = ["TEXT", "IMAGE"];
|
|
315
678
|
}
|
|
@@ -331,7 +694,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
331
694
|
if (!project) throw new Error("Antigravity requires a discovered Cloud Code Assist project id (re-run `ocx login google-antigravity`).");
|
|
332
695
|
const sessionId = antigravitySessionId(parsed);
|
|
333
696
|
const mappedEffort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
334
|
-
const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(
|
|
697
|
+
const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(
|
|
698
|
+
parsed.modelId,
|
|
699
|
+
mappedEffort,
|
|
700
|
+
provider.baseUrl,
|
|
701
|
+
);
|
|
335
702
|
antigravityModel = wireModelId;
|
|
336
703
|
antigravitySession = sessionId;
|
|
337
704
|
// Effort → thinkingConfig for CCA (CLIProxyAPI proven: request.generationConfig.thinkingConfig).
|
|
@@ -349,6 +716,12 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
349
716
|
const draftRequest: Record<string, unknown> = { ...body, sessionId };
|
|
350
717
|
// Claude-on-Antigravity forces VALIDATED function calling (the real client always sets it).
|
|
351
718
|
if (/claude/i.test(wireModelId)) {
|
|
719
|
+
// VALIDATED would defeat a client's tool_choice "none": honor it by dropping the
|
|
720
|
+
// declarations instead, the wire shape of a tool-less Claude turn.
|
|
721
|
+
if (parsed.options.toolChoice === "none") {
|
|
722
|
+
delete draftRequest.tools;
|
|
723
|
+
delete draftRequest.toolConfig;
|
|
724
|
+
}
|
|
352
725
|
const existing = (draftRequest.toolConfig ?? {}) as Record<string, unknown>;
|
|
353
726
|
const fcc = (existing.functionCallingConfig ?? {}) as Record<string, unknown>;
|
|
354
727
|
draftRequest.toolConfig = { ...existing, functionCallingConfig: { ...fcc, mode: "VALIDATED" } };
|
|
@@ -364,11 +737,22 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
364
737
|
} else {
|
|
365
738
|
sanitizeAntigravityClaudeSignatures(contents);
|
|
366
739
|
}
|
|
740
|
+
// Claude-on-Antigravity rejects assistant-tail (model-tail in Gemini terms) histories
|
|
741
|
+
// as prefill: "This model does not support assistant message prefill. The conversation
|
|
742
|
+
// must end with a user message." Context compaction, previous_response_id expansion,
|
|
743
|
+
// and interrupted-turn replay can all produce a model-tail history. Append a user
|
|
744
|
+
// "(continue)" nudge, mirroring the anthropic adapter's tail guard (src/adapters/anthropic.ts).
|
|
745
|
+
if (/claude/i.test(wireModelId)) {
|
|
746
|
+
const last = contents.length > 0 ? contents[contents.length - 1] as { role?: string } : undefined;
|
|
747
|
+
if (!last || last.role === "model") {
|
|
748
|
+
contents.push({ role: "user", parts: [{ text: "(continue)" }] });
|
|
749
|
+
}
|
|
750
|
+
}
|
|
367
751
|
}
|
|
368
752
|
const envelope = {
|
|
369
753
|
model: wireModelId,
|
|
370
754
|
// The envelope's `userAgent` field is a protocol constant ("antigravity"), distinct from
|
|
371
|
-
// the HTTP `User-Agent` header (the real
|
|
755
|
+
// the HTTP `User-Agent` header (the real IDE UA). CLIProxyAPI `geminiToAntigravity` hardcodes
|
|
372
756
|
// the body field; only the header carries the versioned client string.
|
|
373
757
|
userAgent: "antigravity",
|
|
374
758
|
requestType: "agent",
|
|
@@ -384,6 +768,20 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
384
768
|
if (provider.googleMode === "vertex") {
|
|
385
769
|
const compiled = compileGoogleWireBody(body);
|
|
386
770
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
771
|
+
const vertexProject = provider.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || "api-key";
|
|
772
|
+
const vertexLocation = provider.location || process.env.GOOGLE_CLOUD_LOCATION || "global";
|
|
773
|
+
vertexReplayModel = `vertex:${vertexProject}:${vertexLocation}:${parsed.modelId}`;
|
|
774
|
+
vertexReplaySession = vertexReplaySessionId(parsed);
|
|
775
|
+
// Compile names before replay so the cache matches the exact provider-visible
|
|
776
|
+
// functionCall identity. This is the same bounded TTL/LRU store used by CCA, with the
|
|
777
|
+
// transport prefix above preventing cross-backend signature reuse (#1254).
|
|
778
|
+
if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
|
|
779
|
+
applyAntigravityReplay(
|
|
780
|
+
vertexReplayModel,
|
|
781
|
+
vertexReplaySession,
|
|
782
|
+
(compiled.body as { contents: unknown[] }).contents,
|
|
783
|
+
);
|
|
784
|
+
}
|
|
387
785
|
// Vertex AI: project/location endpoint with GCP ADC, or x-goog-api-key fast path.
|
|
388
786
|
const apiKey = resolveVertexApiKey(provider.apiKey);
|
|
389
787
|
if (apiKey) {
|
|
@@ -395,6 +793,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
395
793
|
if (!project) throw new Error("Vertex AI requires a project id (provider.project or GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT).");
|
|
396
794
|
const location = provider.location || process.env.GOOGLE_CLOUD_LOCATION;
|
|
397
795
|
if (!location) throw new Error("Vertex AI requires a location (provider.location or GOOGLE_CLOUD_LOCATION).");
|
|
796
|
+
const locationError = googleVertexLocationConfigError(location);
|
|
797
|
+
if (locationError) throw new Error(locationError);
|
|
398
798
|
const host = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`;
|
|
399
799
|
const url = `https://${host}/v1/projects/${project}/locations/${location}/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
400
800
|
const token = await getVertexAccessToken();
|
|
@@ -403,7 +803,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
403
803
|
}
|
|
404
804
|
|
|
405
805
|
// ai-studio (default): Generative Language API + x-goog-api-key.
|
|
406
|
-
const url = `${provider.baseUrl}/v1beta/models/${
|
|
806
|
+
const url = `${provider.baseUrl}/v1beta/models/${routedModelId}:${method}${streamParam}`;
|
|
407
807
|
const apiKey = provider.apiKey?.trim();
|
|
408
808
|
if (!apiKey) throw new Error("google (AI Studio) requires a non-empty API key");
|
|
409
809
|
headers["x-goog-api-key"] = apiKey;
|
|
@@ -433,6 +833,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
433
833
|
let lastFinishReason: string | undefined;
|
|
434
834
|
let sawAnyFrame = false;
|
|
435
835
|
let sawTerminalSignal = false;
|
|
836
|
+
let pendingStreamThoughtSig: string | undefined;
|
|
436
837
|
|
|
437
838
|
const handleDataLine = async function* (line: string): AsyncGenerator<AdapterEvent, "continue" | "content" | "terminate"> {
|
|
438
839
|
const payload = line.slice(5).trim();
|
|
@@ -443,13 +844,23 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
443
844
|
}
|
|
444
845
|
let emittedContentEvent = false;
|
|
445
846
|
|
|
446
|
-
let
|
|
847
|
+
let parsed: unknown;
|
|
447
848
|
try {
|
|
448
|
-
|
|
849
|
+
parsed = JSON.parse(payload);
|
|
449
850
|
} catch {
|
|
450
851
|
yield { type: "error", message: "malformed upstream SSE data frame" };
|
|
451
852
|
return "terminate";
|
|
452
853
|
}
|
|
854
|
+
// `JSON.parse("null")` returns null rather than throwing, so the catch above cannot cover
|
|
855
|
+
// it and the `chunk.error` read below crashed the stream (see openai-chat.ts). Skip such a
|
|
856
|
+
// frame rather than terminating, for the reason given there: it is padding between real
|
|
857
|
+
// frames, not a broken stream. Deliberately returns BEFORE `sawAnyFrame`, so a stream made
|
|
858
|
+
// only of non-record frames still fails the terminal-signal check below instead of
|
|
859
|
+
// completing empty. An unparseable frame stays terminal.
|
|
860
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
861
|
+
return "continue";
|
|
862
|
+
}
|
|
863
|
+
const chunk = parsed as Record<string, unknown>;
|
|
453
864
|
sawAnyFrame = true;
|
|
454
865
|
|
|
455
866
|
// Inline provider error inside a 200 stream → terminal error (see openai-chat.ts).
|
|
@@ -457,9 +868,12 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
457
868
|
const err = chunk.error as { message?: string } | undefined;
|
|
458
869
|
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
459
870
|
// Drop the cache entry so the next turn starts clean instead of re-injecting a bad sig.
|
|
460
|
-
|
|
871
|
+
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
872
|
+
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
873
|
+
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
874
|
+
&& replayModel && replaySession
|
|
461
875
|
&& /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
|
|
462
|
-
clearAntigravityReplay(
|
|
876
|
+
clearAntigravityReplay(replayModel, replaySession);
|
|
463
877
|
}
|
|
464
878
|
yield { type: "error", message: err?.message ?? "upstream error" };
|
|
465
879
|
return "terminate";
|
|
@@ -484,24 +898,89 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
484
898
|
pendingUsage = usageFromGemini(usageMeta);
|
|
485
899
|
sawTerminalSignal = true;
|
|
486
900
|
}
|
|
487
|
-
const
|
|
488
|
-
|
|
901
|
+
const rawCandidates = root.candidates;
|
|
902
|
+
// `null` is an absence encoding, not corruption, and terminating on it is the #1219
|
|
903
|
+
// failure mode one rung in: a `{"candidates":null}` frame arriving between a content
|
|
904
|
+
// delta and the finish chunk killed a turn whose answer had already fully arrived. An
|
|
905
|
+
// absent key and an empty array are already skipped here; `null` joins them. A non-null
|
|
906
|
+
// non-array container is still claimed structure the parser cannot read, and stays
|
|
907
|
+
// terminal.
|
|
908
|
+
if (rawCandidates === undefined || rawCandidates === null) return "continue";
|
|
909
|
+
if (!Array.isArray(rawCandidates)) {
|
|
910
|
+
yield invalidGoogleShapeEvent({
|
|
911
|
+
reason: "candidates_not_array",
|
|
912
|
+
valueType: googleStructuralValueType(rawCandidates),
|
|
913
|
+
});
|
|
914
|
+
return "terminate";
|
|
915
|
+
}
|
|
916
|
+
if (rawCandidates.length === 0) return "continue";
|
|
917
|
+
const rawCandidate = rawCandidates[0];
|
|
918
|
+
if (!isGoogleRecord(rawCandidate)) {
|
|
919
|
+
// Unlike a root `data: null` keepalive, this is a claimed response candidate. Treat it
|
|
920
|
+
// as terminal protocol corruption so the turn cannot complete after silently losing
|
|
921
|
+
// a candidate or tool call (#1325).
|
|
922
|
+
yield invalidGoogleShapeEvent({
|
|
923
|
+
reason: "candidate_not_object",
|
|
924
|
+
valueType: googleStructuralValueType(rawCandidate),
|
|
925
|
+
});
|
|
926
|
+
return "terminate";
|
|
927
|
+
}
|
|
928
|
+
const candidate = rawCandidate as {
|
|
929
|
+
content?: unknown;
|
|
930
|
+
finishReason?: string;
|
|
931
|
+
};
|
|
489
932
|
|
|
490
|
-
if (typeof
|
|
491
|
-
lastFinishReason =
|
|
933
|
+
if (typeof candidate.finishReason === "string" && candidate.finishReason) {
|
|
934
|
+
lastFinishReason = candidate.finishReason;
|
|
492
935
|
sawTerminalSignal = true;
|
|
493
936
|
}
|
|
494
937
|
|
|
495
|
-
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
938
|
+
// One rung below the candidate guard above, same rule: this is claimed content, not
|
|
939
|
+
// padding, so it fails closed rather than being iterated or silently dropped (#1325).
|
|
940
|
+
const rawContent: unknown = candidate.content;
|
|
941
|
+
const invalidContent = diagnoseGoogleContent(rawContent);
|
|
942
|
+
if (invalidContent) {
|
|
943
|
+
yield invalidGoogleShapeEvent(invalidContent);
|
|
944
|
+
return "terminate";
|
|
945
|
+
}
|
|
946
|
+
const rawParts: unknown = isGoogleRecord(rawContent) ? rawContent.parts : undefined;
|
|
947
|
+
let parts: GoogleResponsePart[] | undefined;
|
|
948
|
+
if (rawParts !== undefined && rawParts !== null) {
|
|
949
|
+
const invalidParts = diagnoseGoogleParts(rawParts);
|
|
950
|
+
if (invalidParts) {
|
|
951
|
+
yield invalidGoogleShapeEvent(invalidParts);
|
|
952
|
+
return "terminate";
|
|
953
|
+
}
|
|
954
|
+
parts = rawParts as GoogleResponsePart[];
|
|
955
|
+
const invalidFunctionCall = diagnoseGoogleFunctionCalls(parts);
|
|
956
|
+
if (invalidFunctionCall) {
|
|
957
|
+
yield invalidGoogleFunctionCallEvent(invalidFunctionCall);
|
|
958
|
+
return "terminate";
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
// Record Gemini thought signatures for the next stateless tool-result turn. Vertex and
|
|
962
|
+
// Antigravity use separate model namespaces so opaque provider state cannot cross routes.
|
|
963
|
+
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
964
|
+
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
965
|
+
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
966
|
+
&& parts && replayModel && replaySession) {
|
|
967
|
+
pendingStreamThoughtSig = observeAntigravityReplay(
|
|
968
|
+
replayModel,
|
|
969
|
+
replaySession,
|
|
970
|
+
parts as unknown[],
|
|
971
|
+
pendingStreamThoughtSig,
|
|
972
|
+
);
|
|
499
973
|
}
|
|
500
974
|
if (parts) {
|
|
501
975
|
for (const part of parts) {
|
|
502
|
-
|
|
976
|
+
const sig = googlePartThoughtSignature(part);
|
|
977
|
+
if (part.thought === true && sig && isLikelyRealThoughtSignature(sig)) {
|
|
978
|
+
pendingStreamThoughtSig = sig;
|
|
979
|
+
}
|
|
980
|
+
const textEvent = googlePartTextEvent(part);
|
|
981
|
+
if (textEvent) {
|
|
503
982
|
emittedContentEvent = true;
|
|
504
|
-
yield
|
|
983
|
+
yield textEvent;
|
|
505
984
|
}
|
|
506
985
|
const inline = (part as { inlineData?: { mimeType?: string; data?: string } }).inlineData;
|
|
507
986
|
if (inline && typeof inline.data === "string") {
|
|
@@ -518,12 +997,19 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
518
997
|
}
|
|
519
998
|
}
|
|
520
999
|
}
|
|
521
|
-
|
|
1000
|
+
const functionCall = googleFunctionCall(part);
|
|
1001
|
+
if (functionCall) {
|
|
522
1002
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
523
1003
|
toolCallsStarted++;
|
|
524
1004
|
emittedContentEvent = true;
|
|
525
|
-
|
|
526
|
-
yield {
|
|
1005
|
+
const restoredName = restoreGoogleToolName(functionCall.name);
|
|
1006
|
+
yield {
|
|
1007
|
+
type: "tool_call_start",
|
|
1008
|
+
id,
|
|
1009
|
+
name: restoredName,
|
|
1010
|
+
...googleToolCallMetadataFromPart(part, pendingStreamThoughtSig),
|
|
1011
|
+
};
|
|
1012
|
+
yield { type: "tool_call_delta", arguments: JSON.stringify(functionCall.args ?? {}) };
|
|
527
1013
|
yield { type: "tool_call_end" };
|
|
528
1014
|
}
|
|
529
1015
|
}
|
|
@@ -587,7 +1073,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
587
1073
|
// Fail-closed: a turn cut off mid tool call (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces
|
|
588
1074
|
// an error instead of a silently-incomplete done. Mirrors kiro-truncation.
|
|
589
1075
|
if ((provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist")
|
|
590
|
-
&&
|
|
1076
|
+
&& isVertexTruncatedTurn(lastFinishReason, toolCallsStarted)) {
|
|
591
1077
|
yield { type: "error", message: vertexTruncationErrorMessage(lastFinishReason) };
|
|
592
1078
|
return;
|
|
593
1079
|
}
|
|
@@ -703,19 +1189,60 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
703
1189
|
}
|
|
704
1190
|
const events: AdapterEvent[] = [];
|
|
705
1191
|
|
|
706
|
-
const
|
|
1192
|
+
const rawCandidates: unknown = json.candidates;
|
|
1193
|
+
// Parity with the streaming path, which has rejected a non-array `candidates` since #1332.
|
|
1194
|
+
// Buffered accepted `"abc"` outright (`"abc".length` is 3, so the emptiness check below
|
|
1195
|
+
// passed and `candidates[0]` was the character `"a"`), and reported `{}`/`5` as an absent
|
|
1196
|
+
// candidate list rather than a malformed one.
|
|
1197
|
+
if (rawCandidates !== undefined && rawCandidates !== null && !Array.isArray(rawCandidates)) {
|
|
1198
|
+
return finish([invalidGoogleShapeEvent({
|
|
1199
|
+
reason: "candidates_not_array",
|
|
1200
|
+
valueType: googleStructuralValueType(rawCandidates),
|
|
1201
|
+
})]);
|
|
1202
|
+
}
|
|
1203
|
+
const candidates = rawCandidates as { finishReason?: string }[] | undefined;
|
|
707
1204
|
if (!candidates?.length) {
|
|
708
1205
|
return finish([{ type: "error", message: "google response contained no candidates" }]);
|
|
709
1206
|
}
|
|
1207
|
+
const rawCandidate: unknown = candidates[0];
|
|
1208
|
+
if (!isGoogleRecord(rawCandidate)) {
|
|
1209
|
+
// The streaming parser already treats this as terminal protocol corruption (#1325/#1332).
|
|
1210
|
+
// Buffered returned a bare `done`, so a claimed-but-malformed candidate was reported to
|
|
1211
|
+
// the caller as a successful empty turn.
|
|
1212
|
+
return finish([invalidGoogleShapeEvent({
|
|
1213
|
+
reason: "candidate_not_object",
|
|
1214
|
+
valueType: googleStructuralValueType(rawCandidate),
|
|
1215
|
+
})]);
|
|
1216
|
+
}
|
|
1217
|
+
const candidate = rawCandidate as { content?: unknown; finishReason?: string };
|
|
710
1218
|
let toolCallsStarted = 0;
|
|
711
1219
|
const imageBudget = createImageBudget();
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1220
|
+
const rawContent: unknown = candidate.content;
|
|
1221
|
+
const invalidContent = diagnoseGoogleContent(rawContent);
|
|
1222
|
+
if (invalidContent) return finish([invalidGoogleShapeEvent(invalidContent)]);
|
|
1223
|
+
const rawParts: unknown = isGoogleRecord(rawContent) ? rawContent.parts : undefined;
|
|
1224
|
+
if (rawParts !== undefined && rawParts !== null) {
|
|
1225
|
+
const invalidParts = diagnoseGoogleParts(rawParts);
|
|
1226
|
+
if (invalidParts) return finish([invalidGoogleShapeEvent(invalidParts)]);
|
|
1227
|
+
const parts = rawParts as GoogleResponsePart[];
|
|
1228
|
+
const invalidFunctionCall = diagnoseGoogleFunctionCalls(parts);
|
|
1229
|
+
if (invalidFunctionCall) return finish([invalidGoogleFunctionCallEvent(invalidFunctionCall)]);
|
|
1230
|
+
// Non-streaming Google-family response: observe thought signatures for the next turn,
|
|
1231
|
+
// using the same transport-scoped namespace as the streaming path.
|
|
1232
|
+
const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
|
|
1233
|
+
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
1234
|
+
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1235
|
+
&& replayModel && replaySession) {
|
|
1236
|
+
observeAntigravityReplay(replayModel, replaySession, parts as unknown[]);
|
|
716
1237
|
}
|
|
717
|
-
|
|
718
|
-
|
|
1238
|
+
let pendingThoughtSig: string | undefined;
|
|
1239
|
+
for (const part of parts) {
|
|
1240
|
+
const sig = googlePartThoughtSignature(part);
|
|
1241
|
+
if (part.thought === true && sig && isLikelyRealThoughtSignature(sig)) {
|
|
1242
|
+
pendingThoughtSig = sig;
|
|
1243
|
+
}
|
|
1244
|
+
const textEvent = googlePartTextEvent(part);
|
|
1245
|
+
if (textEvent) events.push(textEvent);
|
|
719
1246
|
const inline = (part as { inlineData?: { mimeType?: string; data?: string } }).inlineData;
|
|
720
1247
|
if (inline && typeof inline.data === "string") {
|
|
721
1248
|
if (inline.data.length > MAX_ENCODED_BYTES_PER_IMAGE) {
|
|
@@ -730,11 +1257,17 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
730
1257
|
}
|
|
731
1258
|
}
|
|
732
1259
|
}
|
|
733
|
-
|
|
1260
|
+
const functionCall = googleFunctionCall(part);
|
|
1261
|
+
if (functionCall) {
|
|
734
1262
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
735
1263
|
toolCallsStarted++;
|
|
736
|
-
events.push({
|
|
737
|
-
|
|
1264
|
+
events.push({
|
|
1265
|
+
type: "tool_call_start",
|
|
1266
|
+
id,
|
|
1267
|
+
name: restoreGoogleToolName(functionCall.name),
|
|
1268
|
+
...googleToolCallMetadataFromPart(part, pendingThoughtSig),
|
|
1269
|
+
});
|
|
1270
|
+
events.push({ type: "tool_call_delta", arguments: JSON.stringify(functionCall.args ?? {}) });
|
|
738
1271
|
events.push({ type: "tool_call_end" });
|
|
739
1272
|
}
|
|
740
1273
|
}
|
|
@@ -743,14 +1276,25 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
743
1276
|
// Fail-closed truncation, same as the stream path: a non-stream turn cut off mid tool call
|
|
744
1277
|
// (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces an error instead of a silent done.
|
|
745
1278
|
if ((provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist")
|
|
746
|
-
&&
|
|
747
|
-
return finish([{ type: "error", message: vertexTruncationErrorMessage(
|
|
1279
|
+
&& isVertexTruncatedTurn(candidate.finishReason, toolCallsStarted)) {
|
|
1280
|
+
return finish([{ type: "error", message: vertexTruncationErrorMessage(candidate.finishReason) }]);
|
|
748
1281
|
}
|
|
749
1282
|
|
|
750
1283
|
const usage = json.usageMetadata as Record<string, number> | undefined;
|
|
1284
|
+
// Mirror the streaming path: a buffered turn cut off by the token limit or a content filter
|
|
1285
|
+
// must carry its stop reason, or the bridge sees a clean `done` and reports the truncated
|
|
1286
|
+
// turn as completed — and, on a compaction turn, installs the half-written summary as
|
|
1287
|
+
// replacement history (#422).
|
|
1288
|
+
const finishReason = candidate.finishReason as string | undefined;
|
|
1289
|
+
const stopReason = finishReason === "MAX_TOKENS"
|
|
1290
|
+
? "max_tokens"
|
|
1291
|
+
: ["SAFETY", "RECITATION", "BLOCKLIST", "PROHIBITED_CONTENT", "SPII"].includes(finishReason ?? "")
|
|
1292
|
+
? "content_filter"
|
|
1293
|
+
: undefined;
|
|
751
1294
|
events.push({
|
|
752
1295
|
type: "done",
|
|
753
1296
|
usage: usageFromGemini(usage),
|
|
1297
|
+
...(stopReason ? { stopReason } : {}),
|
|
754
1298
|
});
|
|
755
1299
|
return finish(events);
|
|
756
1300
|
},
|