Skip to content

[CI only] feat(web-search): AI/ML API Toolbox search backend - #1

Closed
hugoaimlapi wants to merge 120 commits into
mainfrom
feat/aimlapi-toolbox-web-search
Closed

hugoaimlapi wants to merge 120 commits into
mainfrom
feat/aimlapi-toolbox-web-search

Conversation

@hugoaimlapi

Copy link
Copy Markdown
Member

CI validation run for the upstream PR to Twigpine/openclaude. Not for merge; will be closed once PR Checks finish.

jatmn and others added 30 commits October 21, 2026 18:31
* fix: retire settings locks before cleanup and stabilize watcher coverage

* ci: scan fork pull requests with CodeQL

* test: allow full PowerShell permission parser retries
…gpine#2307)

Co-authored-by: OpenClaude CI Maintenance <ci@example.invalid>
* ci: drop duplicate provider test steps from PR checks

* docs(contributing): drop provider suites from required preflight
* ci: cache package downloads in PR checks

* ci: cache package downloads in the suite job

* ci: fix suite cache key and separate root and web cache prefixes
* ci: run unit test shards in parallel

* fix(ci): address shard-selection review feedback

* ci: cache package downloads in the shard and dedicated test jobs
* ci: skip unaffected pull request checks

* fix(ci): run the PR intent scan on every pull request

---------

Co-authored-by: Repository contributors <undisclosed>
* feat(gateway): add Merge Gateway provider

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix(merge-gateway): preserve scoped credentials across profiles

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix: complete Merge Gateway credential and catalog handoffs

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix: constrain Merge catalog and admit provider env-file credentials

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix: preserve Merge credential ownership across profile and host settings

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix(merge-gateway): scope ambient key adoption and per-page discovery timeouts

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

---------

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>
…pine#2318)

* fix: synchronize agent completion with output eviction

Agent task completion could become externally observable before the
output eviction and flush sequence completed, resulting in callers
observing empty output files.

Root Cause
----------
completeAgentTask() returned immediately while evictTaskOutput()
continued asynchronously. Callers could observe task completion
before the eviction/flush chain finished.

Fix
---
- Make completeAgentTask() async and await eviction
- Await completeAsyncAgent() at both call sites
- Clarify ORC-1337 vs gh-20236 in comments

Testing
-------
- AgentTool: 59/59 passing
- LocalAgentTask: 6/6 passing

Refs: ORC-1337, gh-20236

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: move output barrier before terminal state transition

Review follow-up for ORC-1337 addressing both P1 findings:

1. Ordering: updateTaskState('completed') ran before the eviction
   await, so TaskOutput(block=true) readers released by the terminal
   status could still read a flushing file. The durable-output barrier
   now precedes the state transition.

2. Sidechain transcript: local-agent output is a symlink to the agent
   transcript written by the session-storage queue
   (recordSidechainTranscript) — no DiskTaskOutput entry exists, so
   evictTaskOutput alone resolved without waiting. completeAgentTask
   now awaits flushSessionStorage() (drains the queue and in-flight
   tracked chains) before evictTaskOutput and the status flip.

Also per review:
- Remove ORC-1337-FIX.md from the PR (P3)
- Add deterministic regression test gating both barriers
  (completionBarrier.test.ts) — verified to fail on the previous
  ordering
- Update call-site comments to describe the corrected sequence

Testing: AgentTool + LocalAgentTask 67/67 (incl. 2 new); tsc clean.

Refs: ORC-1337, gh-20236

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix(agent): await scoped output writes before completion

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: cancel agent transcript waits without delaying cleanup

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: preserve agent output ownership across session changes

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: await agent output link replacement after clear

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: preserve agent output destination and finishing input admission

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix(agent): preserve output destinations for background and resumed sessions

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: preserve agent output ownership across replacement and clear

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix(agent): bind retained output links to the recording producer

Record successful sidechain append destinations by agent ID and require
retained output links to match that producer record. Preserve the record
across session clears and project overrides for retained resumes.

Cover swapped agent and project targets for captured and live writes.

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

---------

Co-authored-by: jatmn <jatmn@users.noreply.github.com>
Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>
…Twigpine#2321)

* fix(shell): honor an abort that lands before the listener is attached

* test: spawn node timer instead of sleep for Windows portability
Twigpine#2324)

* test: consolidate vendor catalog tests into per-vendor contract tables

* test: assert full descriptor limits and capabilities for nearai, longcat and xai
…les (Twigpine#2325)

* test: consolidate gateway catalog tests into per-gateway contract tables

* test: assert opencode-go catalog excludes removed model ids
…cess runs (Twigpine#2328)

Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com>
…s (TA-001 slice 3) (Twigpine#2331)

* test: restore leaked settings, config-home and path state in single-process runs (TA-001 slice 3)

* test(printMaxTurns): release shared mutation lock if setup throws
…le mocks (TA-001 slice 2) (Twigpine#2332)

* test: restore leaked provider, betas, sessionTitle and discovery module mocks (TA-001 slice 2)

* test(model): restore real providerProfiles module after model command tests

---------

Co-authored-by: jatmn <12479882.jatmn@users.noreply.github.com>
jatmn and others added 28 commits October 24, 2026 15:26
* chore(deps): upgrade zod to 4.6.7

* test: normalize tool schema fixture paths to forward slashes

* fix: omit AgentTool fields before cwd refinement and export settings schema via zodToJsonSchema
…ine#2406)

* fix(proxy): restore direct fetch routing when clearing proxies

* fix(proxy): preserve dispatcher ownership and lazy setup

* fix(proxy): retire invalidated global TLS dispatchers

* fix(proxy): preserve dispatchers retained by fetch callers
…wigpine#2407)

* refactor(scripts): use Bun subprocess APIs for development commands

* fix(scripts): pass the live process environment to captured subprocesses
* fix(git): honor discovery ceilings in worktree fixtures

* test(git): type-check subprocess status assertions

* fix(git): isolate fixtures and preserve literal ceilings

---------

Co-authored-by: jatmn <jatmn@users.noreply.github.com>
)

* test: clear lifecycle interruption traces after each case

* test: assert lifecycle trace leaks before cleanup
…Twigpine#2414)

* fix(types): narrow SDK response schema consumers

* perf(types): bound mixed Zod schema comparisons
…ctually stop thinking

buildOllamaChatBody() never set a top-level `think` field on outgoing
requests. Ollama's hybrid-thinking models (e.g. Qwen3.x) interpret the
field's absence as thinking enabled, so every Ollama request ran in
thinking mode regardless of --effort, with no way to disable it. The
existing client-side thinking filter only hides the trace from display —
the model still generates the full reasoning chain either way, so the
latency cost was paid regardless of whether it was visible.

Mirrors the effort-handling already implemented for the Anthropic/Gemini
paths just above this function: no --effort specified maps to
think: false; an explicit effort level is passed through (xhigh/max/
ultracode collapse to Ollama's "high", matching its three-tier scale).

Measured on qwen3.6:35b-a3b-coding (Ollama, Apple Silicon):
- Before: 15-21s per trivial response, full visible reasoning trace
- After: ~1.3s via the raw Ollama API with think=false, ~5s through the
  full CLI (Node startup + OpenAI<->Ollama format conversion overhead)
- No visible reasoning trace in either case after the fix
* fix(lsp): synchronize document versions across restarts

Track each open document by server generation and advance versions only after lifecycle notifications are delivered. This prevents replacement language servers from serving requests before receiving current file contents.

* fix(lsp): harden restart synchronization

Preserve active document URIs across Windows path aliases and coordinate connection replacement, failed startup cleanup, cross-generation retries, and shutdown with in-flight document work.

* fix(lsp): settle restart lifecycle races

Make failed starts and transport loss settle pending work, prevent obsolete generations from publishing responses, and keep diagnostics activity on the opened document identity.

* fix(lsp): preserve lifecycle generation boundaries

* fix(lsp): bound document recovery lifecycle

* test(lsp): harden lifecycle regression coverage

* fix(lsp): bound failed startup recovery

* test(lsp): harden lifecycle failure coverage

* test(lsp): restore current-main compatibility and isolation

---------

Co-authored-by: jatmn <the@jat.mn>
…#2416)

* fix(api): retry explicitly throttled OpenAI-compatible 403s

* fix(api): allow explicit throttling messages that name an API key

* fix(api): distinguish OAuth throttling from token faults

* fix(api): preserve auth precedence and remote throttle retry headers

Match account, key, and token faults across clause wording instead of a
20-character credential window. Preserve plural faults and structured
error codes while allowing valid key and OAuth throttle messages.

Keep classified throttle-403s out of the infrastructure-auth retry
exception so their existing header and quota checks apply in remote mode.
Cover actual shim retries, guidance, credential recovery, and native CCR
retry behavior.

* fix(api): distinguish credential predicates across comma clauses

Keep unrelated disabled features from vetoing a valid-key throttle while
preserving actual key faults continued through relative descriptions or
parentheticals. Normalize only matching boundaries and retain raw error
text. Cover the reported retry path and baseline credential-fault cases.

* fix(api): require fault predicates in credential continuations

An adverb alone does not connect a later disabled feature to an API key.
Require an auxiliary/adverb sequence to reach the fault directly, while
preserving relative descriptions and parentheticals of the credential.
Cover the unrelated-subject regression through the real retry loop.

* fix(api): exclude routing host metadata from quota detection

Keep quota keyword matching on provider evidence and category markers,
without allowing the configured hostname to suppress throttle retries.
Preserve the original error message for diagnostics and host consumers.
Cover real 403/429 retry paths and genuine quota/no-retry controls.

* fix(api): preserve throttle fault precedence and retry categories

* fix(api): recognize qualified credential absence
…gpine#2415)

* fix(vscode): align session discovery with CLI storage

* fix(vscode): discover mixed runtime session histories

* fix(vscode): preserve selected history through resume

* fix(vscode): cancel pending resume startup on abort

* fix(vscode): resolve selected transcripts before CLI launch

* fix(vscode): preserve exact-store resumes with relative config roots

* fix(vscode): skip zero-byte transcripts during session discovery

An empty exact-directory placeholder was treated as the selected session and hid a populated alternate history.
…bstring (Twigpine#2099)

* fix(provider): match MiniMax and Venice base URLs by hostname, not substring

The credential-mirroring chains route xAI, Cloudflare and Xiaomi through their
exact-hostname predicates (isXaiBaseUrl, isCloudflareBaseUrl,
isXiaomiMimoBaseUrl), but the MiniMax and Venice branches were left on raw
baseUrl.toLowerCase().includes('minimax') / includes('api.venice.ai') even
though isMiniMaxBaseUrl / isVeniceBaseUrl already exist. A proxy or lookalike
host (minimax-proxy.example.com, api.venice.ai.evil.com) therefore mirrors the
profile key into MINIMAX_API_KEY / VENICE_API_KEY; for MiniMax, providerAutoDetect
then reroutes it to the real api.minimax.io the user never configured. Route all
four sites through the hostname predicates, consistent with the neighbouring
providers.

* fix(provider): centralize dedicated-key mirroring across the profile lifecycle

The host-detected dedicated credential mapping (xAI, MiniMax, Venice,
MiMo) was reconstructed independently in three places and had drifted:

- The startup env builder's strict-env branch returned before any of
  these mirrors, so a keyed generic OpenAI-compatible profile on a
  canonical Venice/MiniMax host persisted only OPENAI_API_KEY and
  relaunched without its dedicated credential; the fallback branch had
  no MiniMax mirror at all and matched MiMo by substring.
- isProcessEnvAlignedWithProfile never checked MINIMAX_API_KEY, so once
  that key drifted after activation the env was still considered aligned
  and applyActiveProviderProfileFromConfig skipped reapplying — leaving
  MiniMax's dedicated-credential transport misrouted.

Introduce getHostDedicatedCredentialEnv as the single source of truth
for the exact-host (and, where available, route) credential mapping, and
route live application, both startup-persistence branches, and the
alignment check through it. Lookalike hosts still receive no dedicated
key.

* fix(provider): carry MiniMax dedicated key through launch reconstruction

buildLaunchEnv rebuilt the startup environment from a fixed dedicated-key
carry-over list that omitted MINIMAX_API_KEY, so a clean restart of a keyed
MiniMax profile retained the generic OpenAI variables but dropped the
dedicated credential. The MiniMax client only enters its native setup while
MINIMAX_API_KEY is present, so the persisted profile did not deliver the
intended restart behavior.

Add MINIMAX_API_KEY to the carry-over contract alongside the other
native-client vendor keys (MIMO/VENICE), preserving the same
shell-over-persisted precedence. The client's own hostname/route detection
gates activation, so an ambient key stays inert in an unrelated session.

Regressions reconstruct the launch environment from a saved profile with an
empty shell and assert both the surviving credential and the selected
env-only route (via resolveEnvOnlyProviderRouteId), not just the persisted
file: canonical MiniMax carries and reselects, a live shell key overrides
the persisted one, and a generic profile never synthesizes the credential.

* fix(provider): preserve keyless credential reconciliation

* fix(provider): scope MiniMax launch credentials to the active route

* fix(provider): admit saved Venice and MiMo keys only on their routes

* test(provider): document saved dedicated-key precedence

* fix(provider): restore test globals and scope profile docs

* fix(provider): keep explicit MiniMax route credentials across reconcile

Reconcile reapply rebuilt a generic OpenAI profile from the hostname only, so a launch that had already kept CLAUDE_CODE_PROVIDER_ROUTE_ID=minimax on a proxy lost MINIMAX_API_KEY when another env field drifted. Reapply now reuses that same-profile route instead of URL substring matching.

* fix(provider): preserve saved credentials in pane teammates

---------

Co-authored-by: jatmn <the@jat.mn>
…2193)

* '

* feat(i18n): add zh-HK locale and fix extensionless imports

* fix(i18n): restore original en/vi dictionaries and fix zh-HK exports

* fix(i18n): resolve import paths, file duplication, and locale matching

* fix(i18n): restore working import paths, localize() API, and session-based locale detection for zh-HK

* fix(i18n): use Map for locale lookup to avoid prototype-key collisions

* fix(i18n): restore working import paths, localize() API, exports, and session-based locale detection

* fix(i18n): correct onboard-github key mismatch in zh-HK dictionary

* test(i18n): clear locale env vars in beforeEach to isolate settings-based fallback tests

* fix(i18n): preserve command meaning and POSIX locale normalization

---------

Co-authored-by: jatmn <the@jat.mn>
…ine#2188)

* fix(utils): make hasNodeOption handle --flag=value and quoted NODE_OPTIONS

- Match exact flag or flag with =value suffix to support --max-old-space-size=4096, --inspect=0.0.0.0:9229 etc.
- Strip surrounding single/double quotes for quoted NODE_OPTIONS tokens
- Avoid false positives where --inspect incorrectly matched --inspect-brk
- Filter empty tokens from whitespace split

Fixes edge case where hasNodeOption('--max-old-space-size') returned false when NODE_OPTIONS contained '--max-old-space-size=4096'

* fix(utils): align hasNodeOption with Node lexer, add regression tests

- Only strip double quotes (Node's NODE_OPTIONS parser treats single quotes as literal)
  so NODE_OPTIONS="'--use-system-ca'" correctly returns false and does not
  incorrectly enable system CA roots (covers 145 vs 302 cert divergence reported
  in review)
- Keep exact / --flag=value matching, double-quoted positives, prefix boundary
  (--inspect vs --inspect-brk), whitespace filtering
- Add table-driven tests for both CA flags (--use-system-ca, --use-openssl-ca)
  covering exact, equals, double-quoted positive, single-quoted negative,
  repeated whitespace, and --inspect boundary; restores process.env.NODE_OPTIONS
  after each case

Fixes CodeRabbit docstring coverage and jatmn P2 quote-grammar issue in PR Twigpine#2188

* fix(utils): quote-aware tokenization for hasNodeOption

Parse NODE_OPTIONS respecting double quotes so option-like text inside a quoted
value does not create spurious flags (e.g. --conditions "foo --use-system-ca bar"
must not match --use-system-ca). Verified on Node 22.23.2 that --expose-gc
inside a quoted --conditions value does not enable GC while an explicit flag
does. Add table-driven cases for this grammar and keep previous single-quote
literal + double-quote handling and prefix boundary fixes.

Addresses CodeRabbit quote-grammar issue on c93a679

* fix(utils): precise NODE_OPTIONS parsing with escapes and value boundaries

- Mirror Node's ParseNodeOptionsEnvVar: handle backslash escapes only inside double quotes, strip quotes, treat single quotes as literal, and correctly handle unterminated strings
- Prevent false positives where option values look like flags: --conditions "--use-system-ca" and --conditions "foo \" --use-system-ca bar" no longer count as --use-system-ca
- ALWAYS_CONSUMES_NEXT for --conditions/-C (value may be flag-like), VALUE_TAKING for other string options only when next token does not look like flag to avoid --inspect --use-system-ca false negatives
- Add table-driven cases for exact --use-system-ca value, escaped-quote value, and equals form; 43 tests now pass

Addresses CodeRabbit probe for quoted condition values and escaped quotes

* fix(utils): fail closed on malformed NODE_OPTIONS and order CA negations

* fix(utils): match Node tab grammar and fail closed on flag-like values

* fix(utils): finish required-value validation and audit boolean option roles

- Fail closed on missing-at-EOF, empty inline (=), and flag-like
  separate values for all modeled required-value options, before any
  CA matching, in both target-before-error and error-before-target
  directions (Node rejects the whole NODE_OPTIONS stream).
- Add missing required-value aliases --loader, --inspect-port,
  --inspect-publish-uid; --conditions/-C now also fails closed on
  flag-like values (verified: --conditions -x exits 9).
- Remove --experimental-import-meta-resolve from VALUE_TAKING: it is
  a no-value boolean in Node 22, so a following CA flag stays visible.
- Update tests: 99 hasNodeOption cases + CA cache-rebuild coverage
  for EOF/empty/alias/boolean in both directions.

* fix(utils): model Node's NODE_OPTIONS grammar in hasNodeOption

Give each modeled option the value role Node gives it, and derive the CA
state from one scoped parse instead of three loosely coupled passes.

- Option roles are now explicit (boolean / value / inline value): the
  V8-owned heap flags accept an empty inline value and reject a separated
  one, while `--stack-trace-limit` requires a non-empty inline value.
- The inspector family resolves through its aliases: `--inspect-port` and
  `--debug-port` take a value, the bare `--inspect`/`--inspect-brk`/
  `--inspect-wait` forms are booleans, and the `=`-forms require a
  non-empty inline value.
- Option processing stops at the first argument that is not an option, so
  an ignored suffix neither enables a CA flag nor creates a required-value
  error.
- `--` and the negation of a non-boolean option are rejected, matching the
  streams Node refuses.

Adds a parity script that derives the expected result from the Node 22
runtime instead of from the implementation's own assumptions.

* test(utils): cover accepted heap and inspector forms through the CA selector

* fix(utils): harden the NODE_OPTIONS parity check and negated queries

- `hasNodeOption('--no-<flag>')` now reports the negation only when the
  option is actually present. Previously an unrelated option in
  NODE_OPTIONS made the negated query report `true`.
- The parity script no longer accepts a `--flag` value it cannot measure:
  the Node probe and the matrix both observe `--use-openssl-ca`.
- The parity script reports a usage error when an operand is missing or is
  another flag, instead of silently falling back to a default.
- The parity script unsets NODE_EXTRA_CA_CERTS and NODE_USE_SYSTEM_CA in
  the child environment: both add certificates to the 'default' store and
  would make the observation signal unusable.
- The parity script verifies that the runtime exposes
  `tls.getCACertificates` (Node >= 22.15) before running, counts timeouts
  separately, and fails when no case was checked, so a run that measures
  nothing can no longer report success.

* fix(utils): preserve CA flags after supported test option values

* fix(utils): resolve test-isolation alias through its value role

---------

Co-authored-by: jatmn <the@jat.mn>
…igpine#2297)

* feat(minimax): default to M3.1 Flash and refresh text suggestions

* fix(minimax): preserve explicit selections and retire stale Zen suggestions

* fix(minimax): preserve Ollama discovery and validate cached options

* fix(minimax): preserve local discovery and validate picker caches

* fix(minimax): preserve gateway-owned model availability

---------

Co-authored-by: octo-patch <266937838+octo-patch@users.noreply.github.com>
Co-authored-by: jatmn <the@jat.mn>
…gpine#1614)

* feat(gemini-vertex): wire env-flag provider routing, selection and model defaults

Builds on the native client PR. CLAUDE_CODE_USE_GEMINI_VERTEX=1 now selects
the provider end-to-end:

- integrations: gemini-vertex vendor descriptor, route/runtime metadata,
  Gemini model catalog additions, regenerated artifacts
- client.ts: route to the native Vertex client on the env flag; the
  Anthropic-on-Vertex branch is only reached for real Claude-on-Vertex
- providerConfig.ts: resolveProviderRequest Vertex model/project/location
  resolution; getAdditionalModelOptionsCacheScope returns null instead of
  'firstParty' for Gemini Vertex sessions (bootstrap stays third-party)
- model defaults / deprecation / context windows recognise the category
- startup selection: hasProviderSelectionFlags + hasCompleteProviderSelection
  recognise the flag and its concrete project/model/location vars, so an
  explicit Vertex env wins over a saved profile like every other provider
- drift sweep: auth (is3P, is1PApiCustomer), log, apiPreconnect,
  agentRouting, spawnUtils teammate forwarding, statusNotice, StartupScreen,
  onboard-github, providerFlag (--provider gemini-vertex, --model mapping),
  managedEnvConstants (managed; project ids deliberately NOT in
  SAFE_ENV_VARS, same policy as ANTHROPIC_VERTEX_PROJECT_ID),
  providerStartupOverrides, GCP credential prefetch, SDK provider type
- durable guard: providerSelectionFlags.ts is the single source of truth
  for CLAUDE_CODE_USE_* flags; enumerating call sites derive from it and a
  parity test fails when a new provider flag appears unregistered

Profile/UI integration follows in the next PR. Typecheck clean,
test:provider 703/703 plus 353 targeted tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): sanitize smuggled thought-signature tool ids on foreign wires

The Gemini Vertex client smuggles Gemini thought signatures into tool_use
ids (toolu_vertex_x~~sig~~<base64>, up to ~1.8k chars) so they survive
session persistence and can be replayed to Vertex, which requires them.
Any other provider consuming that history then echoes the giant id and the
backend rejects it - e.g. Codex: 400 "Invalid 'input[2].id': string too
long" after a /provider switch or --resume from a Vertex session.

Fix at every wire boundary, never in the stored history (Vertex keeps
reading its signatures):

- utils/toolUseIds.ts: shared dependency-free sanitizeToolUseIdForWire -
  cut at the first character outside [A-Za-z0-9_-] (no legitimate id
  contains one), cap with a deterministic FNV-1a hash tail so distinct
  overlong ids stay distinct
- codexShim.normalizeToolUseId: sanitize before building fc_/call_ ids
  (Responses API limit 64)
- openaiShim chat/completions: sanitize tool_calls[].id, tool_call_id and
  the correlation sets (OpenAI limit 40)
- normalizeMessagesForAPI: same sanitation for the Anthropic wire, gated
  on getAPIProvider() !== 'gemini-vertex'

Verified E2E on a real Vertex session containing 1.7k-char signed ids:
resumed under the Codex profile -> completes (was the 400); resumed under
the Vertex profile -> signatures preserved, completes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address CodeRabbit review on #1614

- onboard-github: clear CLAUDE_CODE_USE_GEMINI_VERTEX in the fast-path
  (applyGithubOnboardingProcessEnv + buildGithubOnboardingSettingsEnv), so
  GitHub activation resets Vertex routing on both onboarding paths, not just
  the device-flow finalize path. Covered by an assertion in the existing test.
- messages.normalizeMessages: guard against falsy/non-object slots before the
  switch reads message.type, matching the default arm's intent.
- toolUseIds.sanitizeToolUseIdForWire: keep at least one prefix char if a
  caller passes maxLength < tail length.
- providerFlag.test: add the Gemini Vertex env vars to the cleanup lists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(gemini-vertex): add profile compatibility so the preset routes natively

Addresses jatmn's P1 review on #1614: gemini-vertex is selectable in
ProviderManager (it is in ORDERED_PROVIDER_PRESETS), but without a profile
compatibility mode a saved profile fell through to the OpenAI-compatible
path and was routed through the shim to aiplatform.googleapis.com instead
of the native client.

Brings the profile env-shaping into this PR (the /provider UI forms and
launcher stay in the follow-up UI PR):
- providerProfile.ts: 'gemini-vertex' compatibility/profile types,
  buildGeminiVertexProfileEnv, managed + secret env keys.
- providerProfiles.ts: resolveProfileCompatibility mode, profile env
  application, startup-profile bootstrap, env/profile alignment.
- client.ts: route a saved gemini-vertex profile to the native client
  (gated by shouldRouteToGeminiVertexFromProfile, yields to an explicit
  selection of another provider).
- providerValidation.ts: Vertex project/credential validation messages.

So a saved or preset-selected Gemini Vertex profile now sets
CLAUDE_CODE_USE_GEMINI_VERTEX + the Vertex project/location/model env and
reaches the native client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address jatmn review on #1614 (P1 test, P2 isolation, P3 startup)

- ProviderManager.test.tsx: add 'Google Vertex AI Gemini' to PRESET_ORDER at
  its generated position (between Fireworks and Gemini) so navigation selects
  the intended preset and the ProviderManager suite is green again. (P1)
- messages.toolUseIds.test.ts: clear the full provider-selection registry in
  before/afterEach so getAPIProvider() (which checks FOUNDRY before route
  resolution) reflects only the test's own flags — proven by passing under
  CLAUDE_CODE_USE_FOUNDRY=1. (P2)
- StartupScreen detectProvider: when no Vertex project resolves, show a
  /projects/<set GEMINI_VERTEX_PROJECT>/locations/... project-required state
  instead of a project-less endpoint the native client never calls. (P3)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(gemini-vertex): make tool-id tests suite-order independent

- providerSelectionFlags parity: scan only runtime source (skip *.test.*),
  so a flag a test fixture sets but the code never reads (e.g. a placeholder
  in discoveryService.test.ts from #1620) doesn't trip the gate.
- messages.toolUseIds: mock getAPIProvider directly instead of relying on
  global env/route state, which other suite tests mutate via mock.module —
  makes the wire assertions hermetic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address CodeRabbit review on the routing PR

- providerProfile.ts: honor an explicit shell GEMINI_VERTEX_AUTH_MODE over the
  persisted/default value (same precedence as model/project).
- providerProfiles.ts: validate the active Vertex project in the profile
  alignment check so a project drift forces re-apply instead of routing to the
  wrong project (only when the profile pins a project; ADC-resolved stays free).
- messages.ts: harden isNotEmptyMessage against malformed user/assistant slots
  ({ type } / { message: {} }) that previously crashed on message.message.content;
  add a regression test.
- providerConfig.local.test.ts: clear a possibly-leaked CLAUDE_CODE_USE_OPENAI so
  the Gemini Vertex cache-scope assertion can't pass/fail for the wrong reason.

The Critical "typecheck must pass" item is a false positive: both
`bun run typecheck` and `bun run typecheck:type-tests` pass cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address jatmn re-review on #1614 (P2 content guard, P3 /provider summary)

- messages.ts: isNotEmptyMessage now narrows content to string | array before
  the indexed reads, so a non-string/non-array content (object/number) no
  longer crashes on content[0].type. Added the object/number regression cases.
- provider.tsx: buildCurrentProviderSummary now has a Gemini Vertex branch, so
  a CLAUDE_CODE_USE_GEMINI_VERTEX session reports the Vertex provider/model and
  the /projects/<project>/locations/<location> endpoint (project-required state
  when unset) instead of falling through to Anthropic. Added summary tests for
  both the resolved and project-required cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): guard normalizeMessages payloads and forward all Vertex project aliases

Addresses jatmn's third re-review on #1614:

- messages.ts: normalizeMessages now drops malformed assistant/user slots
  (missing message, or non-array/non-string content) before the branches
  dereference message.message.content — so progress-derived messages routed
  through normalizeMessages (queryHelpers) can't crash the pipeline. Added a
  regression test for the missing/non-array content paths.
- spawnUtils.ts: TEAMMATE_ENV_VARS now forwards every project alias Gemini
  Vertex accepts (adds GCLOUD_PROJECT and GOOGLE_PROJECT_ID), so an env-only
  teammate launched with any of them keeps the project hint instead of
  failing Vertex validation/routing. Added a spawn-env coverage test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): drop content arrays with malformed elements in normalizeMessages

Addresses CodeRabbit follow-up: the array guard rejected bad content
containers but arrays whose elements are null/primitive/typeless or a text
block without text still reached block.type / isNotEmptyMessage and could
crash the same malformed-history path. Drop the whole slot when any element
is malformed, with regression cases. Also assert the normalized output shape
in the well-formed cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(gemini-vertex): use toMatchObject for normalized-shape assertion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): use saved profile for project/model and validate credentials

Addresses jatmn's re-review on #1614:

- client.ts: when routing purely from the saved profile (no
  CLAUDE_CODE_USE_GEMINI_VERTEX flag), fall back to the profile's own project
  (stored in baseUrl) and model instead of only reading process.env — so a
  saved gemini-vertex profile no longer throws "project required" or routes an
  ADC session to the ambient/default project and gemini-2.5-flash. Env still
  wins when explicitly set.
- providerValidation.ts: the Gemini Vertex branch now also resolves the
  selected credential path (mirroring getAnthropicClient's auth-mode mapping)
  and fails when none is available, so an access-token profile without
  GEMINI_ACCESS_TOKEN (or a missing ADC) is caught at setup/startup instead of
  on the first request. Added a credential-missing regression test and made the
  project-alias test inject a deterministic credential.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): wire the provider into doctor, /provider GitHub switch, and profile scripts

Addresses jatmn's re-review on #1614 (provider-list drift in peripheral tools):

- scripts/system-check.ts: doctor now dispatches Gemini Vertex — env checks
  (project/model/location/credential), reachability + generation readiness
  show a Vertex-specific skip instead of reporting the Anthropic login flow,
  currentBaseUrl and serializeSafeEnvSummary cover it. Test-only export +
  tests for the dispatch and safe-env summary.
- ProviderManager.tsx: GitHub activation now clears every selection flag from
  the central PROVIDER_SELECTION_FLAGS list (including CLAUDE_CODE_USE_GEMINI_VERTEX)
  in both the settings patch and process.env, so switching to GitHub can't keep
  routing on a previously selected Vertex profile.
- scripts/provider-launch.ts + provider-bootstrap.ts: accept gemini-vertex in
  the provider allowlists, build a real gemini-vertex profile (instead of a
  broken openai one), and fail loudly when project/credentials are missing.

Verified end-to-end: `doctor:runtime` reports the Vertex provider; the launch
script errors clearly without creds; bootstrap saves a gemini-vertex profile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address CodeRabbit review on PR-2 (8 findings)

- client.ts: re-resolve the Vertex credential per request via getAccessToken
  so long-lived clients pick up refreshed ADC tokens instead of a stale one
  captured at construction (Critical); coerce geminiVertexModel to a plain
  string so the model fallback satisfies tsc and never passes undefined.
- toolUseIds.ts: coerce non-string ids before .replace so loosely typed
  history blocks (number/object/undefined) cannot throw on the wire path.
- system-check.ts / provider-launch.ts / provider-bootstrap.ts: resolve the
  Gemini Vertex project through the shared getGeminiVertexProjectId resolver
  instead of three drifting OR chains, matching the runtime validator/client
  contract and adding whitespace normalization.
- providerSelectionFlags.test.ts: keep the parity scan non-tautological by
  excluding the registry and its own test.
- bugfixes.test.ts: add a sectionBetween() helper that asserts both anchors
  resolve before slicing the drift-guard regions (a missing end anchor used
  to silently widen the slice and pass); fix the StartupScreen detectProvider
  end anchor it exposed.
- deprecation.test.ts: lock the gemini-vertex bypass so Anthropic model
  retirement dates never leak into the Vertex deprecation banner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(gemini-vertex): make deprecation bypass test hermetic

The env-based variant failed deterministically in the full CI suite:
getModelDeprecationWarning() resolves the provider through getAPIProvider(),
which reads global env/route state that other suites mutate, so setting
CLAUDE_CODE_USE_GEMINI_VERTEX on process.env did not reliably select the
gemini-vertex route. Mock getAPIProvider directly (same pattern as
messages.toolUseIds.test.ts) so the assertions are order-independent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(provider-flags): exclude all test files from the consumed-flag scan

CodeRabbit follow-up: the "registered flags are actually consumed" check only
skipped the registry and its own test, so a flag referenced solely by another
test fixture would still look consumed. Skip every *.test.* file so the
assertion proves real runtime consumption.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address jatmn review — project/ADC/preset issues

- providerProfiles.ts: never persist the Vertex API endpoint URL as the
  project id. The /provider preset seeds the shared baseUrl field with
  https://aiplatform.googleapis.com; a URL-shaped value would build invalid
  /v1/projects/https://.../locations/... requests. Add
  geminiVertexProjectFromProfile() to drop URL-shaped values and reuse it in
  both the profile-env builder and the alignment check.
- providerUiMetadata.ts: base requiresApiKey on authMode === 'api-key' instead
  of requiresAuth, so the ADC-only Gemini Vertex preset is no longer pushed
  through the generic API-key step (ADC profiles have no key to enter).
- provider-launch.ts: only require an upfront credential in access-token mode;
  ADC mode (explicit or default) relies on ambient Google ADC that only the
  runtime resolver can discover, matching doctor and the native client.
- Tests: lock the endpoint-not-project guard (both URL and real-project cases)
  and the ADC-preset API-key gating; harden providerProfiles env cleanup with
  the Vertex project/credential keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address CodeRabbit follow-up — credentials, URL guards, tests

- providerProfiles.ts / providerProfile.ts: carry the active credential forward
  when applying a saved Gemini Vertex profile. buildCompatibilityProcessEnv
  clears every managed key (incl. GEMINI_ACCESS_TOKEN /
  GOOGLE_APPLICATION_CREDENTIALS), so a profile switch could relaunch unauthed;
  re-add the access token (access-token mode) or ADC file (adc mode).
- providerProfiles.ts (startup-profile path) and provider-bootstrap.ts: extend
  the endpoint-URL guard to the remaining sites that fed baseUrl into the Vertex
  project — drop URL-shaped values via geminiVertexProjectFromProfile() and
  reject http(s) --base-url in the bootstrap script.
- Tests: add applyModelFlagFromArgs GEMINI_VERTEX_MODEL coverage; lock
  access-token / ADC credential preservation on profile apply; isolate every
  provider-selection flag in the cache-scope test via PROVIDER_SELECTION_FLAGS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address jatmn round-3 — saved-profile-only path

- messages.ts: sanitize Vertex tool_use ids by the effective provider (env flag
  OR saved active profile) via new isGeminiVertexEffectiveProvider(), not the
  env-only getAPIProvider(). On the saved-profile-only route the signed
  thought-signature ids were stripped before geminiVertexClient could decode
  them, re-triggering the 400 the smuggling avoids.
- client.ts: run the saved-profile project through geminiVertexProjectFromProfile
  (shared "URL is not a project" sanitizer) instead of using activeProfile.baseUrl
  raw, so a preset profile can't build /v1/projects/https://.../.
- providerProfiles.ts: carry the active credential into the persisted startup
  profile (buildStartupProfileFromActiveProfile), mirroring the live switch path,
  so a saved Vertex profile relaunches authenticated.
- vendors/gemini-vertex.ts: credentialEnvVars now lists only real credentials
  (GEMINI_ACCESS_TOKEN / GOOGLE_APPLICATION_CREDENTIALS), not routing/config vars,
  so generic consumers don't surface the enablement flag/project id as an apiKey;
  defaultBaseUrl is a project placeholder so the preset routes through the full
  setup form to collect the project id.
- Tests: saved-profile effective-provider preservation; startup-profile
  credential persistence (access-token + ADC); credentialEnvVars/apiKey gating;
  preset baseUrl placeholder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): doctor validates the Vertex credential before launch

The Vertex generation-readiness check unconditionally passed ("relying on
ambient ADC"), so a fresh env with a project but no resolvable ADC passed the
doctor that provider-launch runs as its preflight, then `node bin/openclaude`
failed startup validation. Reuse the runtime resolveGeminiCredential() (mapping
GEMINI_VERTEX_AUTH_MODE -> GEMINI_AUTH_MODE and clearing API keys, exactly like
the native client) and fail when the credential cannot be resolved, so the
preflight matches the provider that will actually run. Also clarify the
GEMINI_VERTEX_AUTH line that ambient ADC is validated by the readiness check.

Tests: readiness passes when a credential resolves and fails when none does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): address CodeRabbit review (in-scope findings)

- provider-bootstrap.ts: the saved-profile summary now prints GEMINI_VERTEX_MODEL
  for gemini-vertex profiles instead of falling through to a default.
- provider-launch.ts: the usage string lists gemini-vertex and no longer repeats
  mistral, matching the accepted profile arguments.
- StartupScreen.ts: detectProvider resolves Vertex model/location/project through
  the shared getGeminiVertexModel/Location/ProjectId helpers (same contract as
  runtime/provider summary) instead of manual env reads.
- StartupScreen.test.ts: lock the alias fallback (GOOGLE_CLOUD_PROJECT resolves
  the endpoint project when GEMINI_VERTEX_PROJECT is unset).
- client.test.ts: snapshot/clear/restore all three project aliases
  (GOOGLE_CLOUD_PROJECT, GCLOUD_PROJECT, GOOGLE_PROJECT_ID) so the Vertex tests
  can't leak host-environment project ids.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): thread saved-profile-only routing through provider identity

The saved-profile-only Vertex route (active gemini-vertex profile, no
CLAUDE_CODE_USE_GEMINI_VERTEX flag) was only honoured by the API client, so
other env-flag-only "is this Vertex?" checks disagreed with it:

- routeMetadata.ts: resolveActiveRouteIdFromEnv() now returns 'gemini-vertex'
  when activeProfileProvider is gemini-vertex and no provider env flag is set,
  so /model loads the Vertex catalog/discovery for the same session.
- providerConfig.ts: getAdditionalModelOptionsCacheScope() scopes a saved
  gemini-vertex profile as third-party (null) instead of 'firstParty', so
  bootstrap/model-option traffic is not fetched through the Anthropic path.
- provider.tsx: buildCurrentProviderSummary() reports the effective provider so
  a saved Vertex profile no longer shows "Anthropic"/api.anthropic.com.

Tests cover each: saved-profile route resolution (and precedence when a flag is
set), the cache-scope fallback, and the provider summary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): resolve saved Vertex profile before env-only inference

CodeRabbit follow-up: resolveActiveRouteIdFromEnv() placed the saved-profile
gemini-vertex check after resolveEnvOnlyProviderRouteId(), so an ambient
credential-only env (e.g. a stray XAI_API_KEY) won over the active profile and
disagreed with getAnthropicClient (which routes saved gemini-vertex profiles
unless a conflicting provider flag is set). Move the check above env-only
inference, guarded on CLAUDE_CODE_USE_OPENAI to mirror the conflicting-flag
rule. Add regressions: profile wins over ambient credential env, and yields to
an explicit OpenAI flag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): accept ADC-derived project; GitHub token over blank key

- providerValidation.ts: resolve the Vertex credential before failing on a
  missing project, and accept credential.projectId (ADC-derived) as the project
  — mirroring getAnthropicClient, which falls back to the ADC project. A valid
  ambient-ADC setup with no project env var no longer fails startup validation.
- provider-launch.ts: defer the project check to runtime in ADC mode (ADC can
  supply the project); access-token mode still requires an explicit project.
- openaiShim.ts: GitHub hydration uses || (not ??) so a blank exported
  OPENAI_API_KEY no longer wins over GITHUB_TOKEN/GH_TOKEN and leaves GitHub
  requests unauthenticated. Pre-existing main behaviour, fixed at maintainer
  request; matches the Gemini/Mistral branches.
- Tests: ADC-derived project acceptance + access-token project-required;
  GitHub-token fallback when OPENAI_API_KEY is blank.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): whitespace-safe Vertex config + ADC-only bootstrap

CodeRabbit follow-up (all on this PR's Vertex code):
- provider-bootstrap.ts: only require an explicit project for access-token
  profiles; ADC can supply credential.projectId at runtime, so ADC-only Vertex
  profiles can now be created. Pass the resolved authMode through.
- providerProfile.ts: sanitize each shell/persisted candidate before applying
  precedence (model/project/location and the access-token re-add), so a
  whitespace-only shell value (e.g. GEMINI_VERTEX_PROJECT=' ') can't drop the
  real persisted config; normalize the auth-mode check too.
- providerValidation.ts: trim GEMINI_ACCESS_TOKEN before inferring the default
  auth mode so a blank token doesn't force access-token over ADC.
- Tests: assert the concrete 'openai' route in the precedence case; isolate the
  GCLOUD_PROJECT/GOOGLE_PROJECT_ID aliases in the env-diagnostics fallback tests;
  lock the whitespace-over-persisted precedence in buildStartupEnvFromProfile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): saved profile wins over ambient env in profile-only routing

getAnthropicClient's saved-profile-only Vertex branch resolved the project/model
from process.env before falling back to the profile, contradicting its own
comment. A saved gemini-vertex profile targeting one project/model could route
to an ambient GOOGLE_CLOUD_PROJECT / stale GEMINI_VERTEX_MODEL when no
CLAUDE_CODE_USE_GEMINI_VERTEX flag is set. profileProject/profileModel are only
populated when routedFromProfileOnly, so leading the precedence with them makes
the selected profile win in profile-only mode while env still wins for an
explicit Vertex selection. Regression locks the precedence (profile project +
model win over ambient env).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): align saved-profile route resolution with defined flag rule

resolveActiveRouteIdFromEnv's saved-profile gemini-vertex branch only blocked on
a *truthy* CLAUDE_CODE_USE_OPENAI, but the client's shouldRouteToGeminiVertexFromProfile
treats any *defined* conflicting provider flag as explicit intent. With a saved
gemini-vertex profile and CLAUDE_CODE_USE_OPENAI=0, route metadata reported
gemini-vertex while the client refused the saved-profile Vertex route, so /model
loaded the Vertex catalog for a session whose requests would not use Vertex.
Mirror the client exactly via PROVIDER_SELECTION_FLAGS (any defined flag other
than the Vertex one blocks the route). Regression covers the falsey OpenAI flag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): single source of truth for Vertex auth mode + edge cases

CodeRabbit follow-up — the recurring "infer auth mode from token" logic was
duplicated inline (and inconsistently) across the client/doctor/profile paths,
so a token-only or whitespace-only config was classified differently per site.

- geminiAuth.ts: add resolveGeminiVertexAuthMode() — explicit GEMINI_VERTEX_AUTH_MODE
  (case/space-normalized) wins, else a present (sanitized) token implies
  access-token, else adc. Wire it into providerProfiles.ts (apply + startup
  builders) and system-check.ts (env check, generation-readiness, safe summary),
  fixing the token-only case that previously forced adc and dropped the bearer.
- system-check.ts: don't hard-fail a missing project in ADC mode (ADC supplies
  credential.projectId; only access-token mode requires an explicit project);
  normalize the credential-set boolean with trim instead of ??.
- statusNoticeLocalModel.ts: handle the gemini-vertex route id explicitly so a
  saved-profile Vertex route never resolves to an OPENAI base URL.
- Tests: resolveGeminiVertexAuthMode unit cases; token-only auth-mode regressions
  for apply + startup persistence; deterministic access-token validator stub;
  exact provider-summary assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(gemini-vertex): document the Gemini-on-Vertex provider and setup

The PR adds a first-class native Gemini Vertex route but the user-facing docs
didn't mention it (repo guidance requires doc updates for provider/setup changes).

- README.md: add a "Google Vertex AI (Gemini)" row to the supported-provider
  table (enable flag, ADC vs access-token auth, project aliases, model/region),
  and clarify the existing Gemini row is Google AI Studio (API key).
- docs/advanced-setup.md: add a "Gemini on Vertex AI" section with ADC and
  access-token setup, distinct from Claude-on-Vertex, and cross-link it from the
  Claude-on-Vertex section (which previously only pointed to the API-key Gemini
  provider for Gemini models).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gemini-vertex): add CLAUDE_CODE_USE_GEMINI_VERTEX to isUsing3PServices()

The exported isUsing3PServices() helper was missing the Gemini Vertex flag,
causing /login //logout to remain visible and auth status to report as
first-party when CLAUDE_CODE_USE_GEMINI_VERTEX=1. The local is3P check
inside isAnthropicAuthEnabled() was already correct; this aligns the
exported helper to match.

Adds a dedicated drift guard test targeting isUsing3PServices() directly,
since the existing test only covered the separate local is3P block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(gemini-vertex): address CodeRabbit review findings

- providerProfile.ts: use firstSanitizedProviderValue for Google alias vars
  (GOOGLE_CLOUD_PROJECT, GCLOUD_PROJECT, GOOGLE_PROJECT_ID,
  GOOGLE_APPLICATION_CREDENTIALS) so a whitespace-only shell value doesn't
  shadow a real persisted value
- providerProfiles.ts: wrap project alias env lookups with trimOrUndefined()
  to prevent whitespace-only values blocking the fallback chain
- providerProfiles.ts: add GOOGLE_APPLICATION_CREDENTIALS and
  GEMINI_VERTEX_AUTH_MODE=adc to isEnvOnlyProviderProfile so ADC-only
  configurations are correctly detected
- providerValidation.ts: use resolveGeminiVertexAuthMode() instead of
  duplicating the auth-mode inference inline
- provider.tsx: simplify Gemini Vertex branch condition to effectiveRouteId
  check only (resolveActiveRouteIdFromEnv already covers the env flag)
- system-check.ts: add project validation after credential resolution so the
  doctor catches missing project in ADC mode (mirrors getProviderValidationError)
- provider-bootstrap.ts: use resolveGeminiVertexAuthMode() to infer
  access-token mode when GEMINI_ACCESS_TOKEN is set without explicit
  GEMINI_VERTEX_AUTH_MODE
- docs/advanced-setup.md: clarify that ADC project derivation only applies in
  ADC mode; access-token mode requires an explicit project env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(gemini-vertex): use resolveGeminiVertexAuthMode in provider-launch

Replace duplicated inline auth-mode inference with the shared helper,
consistent with provider-bootstrap and system-check (CodeRabbit #3417084093).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(gemini-vertex): persist Vertex creds, sanitize project fallback, harden env test

Round-3 CodeRabbit findings on #1614:

- provider-bootstrap.ts: carry the active credential into the saved profile.
  PROFILE_ENV_KEYS clears GEMINI_ACCESS_TOKEN / GOOGLE_APPLICATION_CREDENTIALS
  on relaunch, so an access-token profile must persist the bearer (and fail
  fast if absent) and an ADC profile must persist the credentials file —
  otherwise the saved profile relaunches unauthed (thread 3460713116).
- providerProfiles.ts: sanitize each Vertex project candidate with
  trimOrUndefined() + ?? at the startup-profile site so a whitespace-only
  GEMINI_VERTEX_PROJECT no longer shadows a real GOOGLE_CLOUD_PROJECT /
  GCLOUD_PROJECT / GOOGLE_PROJECT_ID (thread 3460713146; the compatibility
  builder site was already fixed in 0501694).
- security-hardening.test.ts: assert env classification via runtime
  isProviderManagedEnvVar() / SAFE_ENV_VARS.has() instead of slicing source
  text, so the policy test tracks behavior, not formatting (thread 3460713129).

Validation: tsc --noEmit, type-tests, security-hardening.test.ts (13/13),
knip — all green. providerProfiles getAPIProvider() timeouts are pre-existing
(identical on the unmodified tree).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(provider-flags): derive flag-agnostic checks from the registry

Address jatmn review 4558518184 on PR #1614:

- Export hasAnyTruthyProviderSelectionFlag / hasConflictingProviderFlag from
  providerSelectionFlags.ts so the flag-agnostic call sites derive from the
  single source of truth instead of hand-enumerating CLAUDE_CODE_USE_* flags.
  Adopted by hasExplicitProviderSelection, hasNoExplicitNonOpenAICompatibleProvider,
  isUsing3PServices, and hasConflictingProviderFlagsForProfile (via an exhaustive
  activeProviderFlagForCompatibilityMode switch that preserves the
  github-enterprise -> CLAUDE_CODE_USE_GITHUB mapping and is compile-checked).
  Per-provider checks (hasConcreteProviderSelection, hasCompleteProviderSelection)
  stay as-is since they are not flag-agnostic.
- Give the two source-scanning parity tests an explicit 30s timeout so they no
  longer flake under parallel test load.
- Add unit tests for both helpers covering every registered flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): align access-token startup precedence and use isEnvTruthy

Address jatmn review 4565330236 on PR #1614:

- [P2] providerProfiles.ts: hasCompleteProviderSelection now treats
  GEMINI_ACCESS_TOKEN (and GEMINI_VERTEX_AUTH_MODE='access-token') as a
  concrete Gemini Vertex selection, matching the sibling
  hasConcreteProviderSelection. Without this, CLAUDE_CODE_USE_GEMINI_VERTEX=1
  + GEMINI_ACCESS_TOKEN (no project) was not "complete", so a saved profile
  could override the user's explicit access-token env selection at startup —
  unlike the ADC path. Regressions added for both the token and the
  auth-mode signals.
- [P3] providerFlag.ts (applyModelFlagFromArgs) and StartupScreen.ts
  (detectProvider): recognize the Vertex flag via isEnvTruthy() instead of an
  exact '1'/'true' match, so 'yes'/'on'/whitespace-normalized values get the
  same model routing and startup banner as the rest of the codebase.
  Regressions added for both sites.

Validation: tsc --noEmit clean; providerFlag, StartupScreen, providerProfiles,
client, routeMetadata, compatibility, providerSelectionFlags suites green.
Branch rebased on latest main (integrations artifacts regenerated).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(gemini-vertex): lock isUsing3PServices drift guard to the flag registry

After the round-4 refactor (jatmn review 4558518184) isUsing3PServices()
derives from hasAnyTruthyProviderSelectionFlag() instead of hand-enumerating
CLAUDE_CODE_USE_* flags, so the source-text drift guard that scanned the
function body for the literal CLAUDE_CODE_USE_GEMINI_VERTEX no longer matched
and failed under test:full.

Update the guard to lock the real post-refactor guarantee: (1) the function
routes through the registry helper, and (2) the Vertex flag is registered in
PROVIDER_SELECTION_FLAGS (the single source of truth). This tracks the
behavior the refactor introduced instead of regressing to the literal-scan
drift it eliminated.

Validation: bugfixes.test.ts Gemini Vertex guards 5/5 green; tsc --noEmit
clean. Remaining local test:full failures are environmental only (FR-locale
Intl thousands separator and the CLI-bundle build artifact) and pass on CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): wire flag into env-file allowlist, test scrubs and truthiness

Addresses maintainer review on PR #1614:

- envFile.ts: add CLAUDE_CODE_USE_GEMINI_VERTEX plus GEMINI_VERTEX_* /
  GOOGLE_APPLICATION_CREDENTIALS / GCLOUD_PROJECT / GOOGLE_PROJECT_ID to
  ALLOWED_ENV_FILE_KEYS so --provider-env-file .env no longer silently
  rejects the documented Vertex setup. Adds a PROVIDER_SELECTION_FLAGS-derived
  parity test plus a full Vertex .env load test.
- Add CLAUDE_CODE_USE_GEMINI_VERTEX to the provider env scrub/restore lists in
  betas, conversationRecovery, model, withRetry and promptCacheBreakDetection
  tests to prevent cross-test flag leakage.
- providerFlag.applyModelFlagFromArgs and StartupScreen.detectProvider: use
  isEnvTruthy for all provider flags instead of literal '1'/'true' checks,
  removing the Vertex-only inconsistency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): address CodeRabbit review — drift, malformed-history and credential hardening

Verified each CodeRabbit finding against current code; applied the valid ones,
skipped two with documented reasons.

Provider-flag drift / registry adoption:
- auth.ts: isAnthropicAuthEnabled's is3P gate now derives from
  hasAnyTruthyProviderSelectionFlag() instead of a hand-maintained 8-flag list.
- routeMetadata.ts: reuse the shared hasConflictingProviderFlag() helper instead
  of an inline PROVIDER_SELECTION_FLAGS scan.
- withRetry/promptCacheBreakDetection tests: derive env scrub lists from
  PROVIDER_SELECTION_FLAGS so new flags are covered automatically.
- bugfixes drift guard updated to assert the registry derivation rather than the
  now-removed literal in auth.ts.

Credential / managed-env correctness:
- managedEnvConstants.ts: treat GOOGLE_APPLICATION_CREDENTIALS as provider-managed
  so host-managed sessions can't have ADC creds swapped via settings.
- providerStartupOverrides.ts: clear the Google ADC/project aliases
  (GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_CLOUD_PROJECT, GCLOUD_PROJECT,
  GOOGLE_PROJECT_ID) so a cleared Vertex override can't stay partially active.
- agentRouting.ts: clear the Vertex-specific selectors before forcing an
  OpenAI-compatible override, mirroring the existing GEMINI_MODEL/BASE_URL clears.
- providerProfile.ts: treat GEMINI_VERTEX_AUTH_MODE as a concrete Vertex signal in
  hasConcreteProviderSelection (mirrors hasCompleteProviderSelection).
- client.ts: reuse resolveGeminiVertexAuthMode() instead of an inline auth-mode
  computation (trims whitespace tokens, validates the mode).
- system-check.ts: use getGeminiVertexLocation() so doctor output matches runtime.
- openaiShim.ts: pick the first non-blank credential so a whitespace-only
  OPENAI_API_KEY no longer suppresses a real GITHUB_TOKEN/GH_TOKEN (+regression).

Malformed-history hardening:
- messages.ts: validate the first content block before reading .type/.text;
  sanitize non-string tool_result.tool_use_id to mirror the assistant id path.
- normalizeMessages malformed test: cover falsy slots and unknown message types.
- providerConfig.envDiagnostics test: clear CLAUDE_CODE_USE_GITHUB in Vertex tests.

Skipped (documented):
- log.ts saved-profile gate: importing isGeminiVertexEffectiveProvider would create
  a log -> providerProfiles -> config -> log cycle (config.ts imports log.ts).
- providerProfiles.ts hasCompleteProviderSelection access-token gate: CodeRabbit
  asked to require a project, which contradicts the maintainer's explicit P2
  request to treat a bare access-token Vertex selection as complete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): require project for token-only, gate logs, validate region

Addresses jatmn's round-7 review (6 findings):

- P1 hasCompleteProviderSelection: an access-token / token-only Vertex env
  (GEMINI_ACCESS_TOKEN or GEMINI_VERTEX_AUTH_MODE=access-token) with no project
  is unusable — the native client throws "project is required" since only ADC
  can derive a project. Such a stale shell no longer counts as complete, so it
  stops suppressing the saved active profile. Reverses the round-5 tests that
  encoded the opposite (now corrected).
- P1 logError gate: use isGeminiVertexEffectiveProvider() so a saved-profile-only
  Vertex session (no env flag) gets the same no-error-reporting treatment.
- P2 buildCurrentProviderSummary: merge the saved Vertex profile env so the
  /provider summary shows the saved project/model/location in profile-only mode.
- P2 detectProvider: honor profile-only Vertex routing via the effective
  provider/profile logic (mirrors getAnthropicClient) instead of the raw flag.
- P2 global-only models: reject gemini-3.5-flash on a regional GEMINI_VERTEX_
  LOCATION via isGlobalOnlyVertexModel() rather than building an unusable URL.
- P3 isNotEmptyMessage: reject typeless first content blocks ({ foo: 'bar' })
  before the non-text branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): tighten ADC completeness, neutral project placeholder, hermetic tests

Addresses CodeRabbit review on 6236de2 (5 findings):

- hasCompleteProviderSelection: use the *effective* auth mode
  (resolveGeminiVertexAuthMode) so a GEMINI_ACCESS_TOKEN alongside a
  GOOGLE_APPLICATION_CREDENTIALS file (which resolves to access-token and still
  needs a project) is no longer treated as complete. The credentials file only
  counts when the effective mode stays ADC. +2 regressions (token+creds, ADC).
- StartupScreen / buildCurrentProviderSummary: show a neutral
  "<ADC-resolved project>" placeholder when an explicit ADC credential is
  present (the client derives credential.projectId at runtime) instead of the
  imperative "<set GEMINI_VERTEX_PROJECT>"; keep the actionable hint for
  access-token/ambient setups. +startup regression.
- client.test.ts: add an ADC-only routing regression asserting the generated
  Vertex URL uses the credential-discovered project (no env/profile project).
- Test isolation: snapshot a structuredClone of the global config before
  mutating it (StartupScreen), and save/restore DISABLE_ERROR_REPORTING +
  essential-traffic env so the logError gate control case is hermetic in CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(gemini-vertex): centralize ADC-resolves-project check, fix placeholder drift

Addresses CodeRabbit re-review on 365cc2e (2 findings, same class as the prior
raw-vs-effective auth-mode issue):

The ADC project placeholder in StartupScreen.detectProvider and
buildCurrentProviderSummary checked GOOGLE_APPLICATION_CREDENTIALS directly, so
a GEMINI_ACCESS_TOKEN alongside a credentials file (effective mode: access-token,
which still needs an explicit project) wrongly showed "<ADC-resolved project>".

- Extract geminiVertexAdcWillResolveProject(env) in utils/geminiAuth.ts as the
  single source of truth: explicit auth_mode=adc, or effective mode resolves to
  ADC AND a credentials file is present (a bare/ambient flag is not ADC-
  resolvable). Reused by hasCompleteProviderSelection (replacing the inline copy)
  and both placeholders, so the gate and the display cannot drift apart.
- StartupScreen regression: GEMINI_ACCESS_TOKEN + GOOGLE_APPLICATION_CREDENTIALS
  with no project keeps the actionable "<set GEMINI_VERTEX_PROJECT>" hint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): honor explicit model override and make profile tests order-independent

Addresses jatmn's round-7c review (3 findings):

- P1 smoke-and-tests isolation: the full suite failed with provider-detection
  assertions returning "Gemini Vertex" for unrelated providers because
  providerProfiles.test.ts installs mock.module('./config.js') and bun's
  mock.restore() does NOT revert mock.module(), so the dead config mock leaked
  into every later test file in the process. Add an afterAll that re-installs the
  real config module. StartupScreen.test.ts and log.errorReportingGate.test.ts
  stop mutating the shared global config and instead spyOn
  getActiveProviderProfile / isGeminiVertexEffectiveProvider, so their
  saved-profile-only assertions no longer depend on a possibly-leaked config mock
  and become order-independent across OSes.
- P2 client.ts model precedence: in saved-profile-only Vertex routing an explicit
  model override (runtime/--model) now wins over the profile's saved model,
  matching the startup display and the other provider paths; the saved model
  still wins over ambient GEMINI_VERTEX_MODEL. Env-flag routing keeps an explicit
  GEMINI_VERTEX_MODEL ahead of a generic model arg.
- P2 runtimeMetadata.test.ts: add the no-env-flag case where
  activeProfileProvider: 'gemini-vertex' alone selects the Vertex catalog limits,
  protecting the saved-profile-only path this PR relies on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(test): stop ProviderManager providerProfiles mock from leaking across files

Root cause of the failing smoke-and-tests full-suite run (jatmn round-7c P1):
ProviderManager.test.tsx installs a partial mock.module('../utils/
providerProfiles.js') via mockProviderProfilesModule() and only calls
mock.restore() in afterEach. bun's mock.restore() does NOT revert mock.module(),
so the partial stub stayed installed for every later test file in the process.

The stub omits exports the gemini-vertex routing now depends on (notably
isGeminiVertexEffectiveProvider) and returns getActiveProviderProfile() = null,
so once ProviderManager ran before the provider-detection suites the leaked stub
turned StartupScreen.detectProvider, the logError reporting gate and client.ts
Vertex routing order-dependent — the exact "provider detection returns Gemini
Vertex / wrong provider" failures jatmn saw in CI (and that pass in focused
runs). main stayed green only because its detectProvider didn't yet read those
exports.

- Capture the real providerProfiles module before any mock runs and spread it
  into the stub, so exports the tests don't explicitly override keep their real
  implementations during ProviderManager's own tests.
- Add an afterAll that re-installs the real module, mirroring the restore pattern
  in providerProfiles.test.ts / messages.toolUseIds.test.ts / user.test.ts.

Verified: ProviderManager + StartupScreen + log gate + client run sequentially
(the CI failure order) now pass 108/0; ProviderManager alone stays 24/0; tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(gemini-vertex): pin getActiveProviderProfile in StartupScreen detectProvider specs

The full-suite smoke-and-tests run failed because StartupScreen.detectProvider
specs depended on shared global-config state to decide Gemini Vertex routing. A
prior test file can leave a mock.module('./config.js') installed (bun's
mock.restore() does not revert mock.module()), silently turning the
saveGlobalConfig()-based setup into a no-op and making detection order-dependent.

Spy getActiveProviderProfile() in beforeEach to return "no active profile" by
default so a leaked gemini-vertex profile can't make these assertions return
"Gemini Vertex"; the profile-only spec overrides it for its single case.
afterEach restores the spy. detectProvider's real routing still runs.

Verified: ProviderManager + StartupScreen + log gate + client (the CI failure
order) pass 108/0; all detectProvider-bearing files + polluter pass 145/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(gemini-vertex): fix provider-profile mock leak with spyOn

messages.toolUseIds.test.ts mocked isGeminiVertexEffectiveProvider via
mock.module(), then "restored" by re-spreading the `import * as` namespace.
Because that namespace is a live binding, after the mock it already reflects
the mocked exports, so the restore re-installs the mock instead of reverting
it. Bun's mock.module() is also not undone by mock.restore(), so the mocked
isGeminiVertexEffectiveProvider (pinned true) leaked into every later test
file in the full suite.

When that leak reaches StartupScreen.test.ts, detectProvider() reads the
leaked isGeminiVertexEffectiveProvider() === true and labels every provider
as "Gemini Vertex", which is the source of the smoke-and-tests failures
(30 detectProvider specs + the client/log-gate Vertex specs) seen only in
CI's Linux file-discovery order.

Switch to spyOn(...).mockReturnValue() restored via mockRestore() in
afterEach — the same cross-module-binding-safe pattern StartupScreen.test.ts
uses for getActiveProviderProfile. spyOn is properly restorable and never
calls mock.module, so the file can no longer leak the provider-profile
module. Verified: the leak reproduces deterministically on bun 1.3.9 and
1.3.13; the spyOn version restores cleanly on both.

* test: stop mock.module restore leaks via static real-module snapshots

Two more test files reinstalled their real modules through an `import * as`
namespace, which is a live binding: once mock.module() has run, the namespace
already reflects the mocked exports, so "restoring" by returning that namespace
re-installs the stub. bun's mock.restore() does not revert mock.module() either,
so the stub leaks into every later file in the full suite (only manifesting in
CI's Linux file-discovery order).

- ProviderManager.test.tsx: the leaked partial providerProfiles stub left
  getActiveProviderProfile() returning null, so client routing could not see a
  saved Gemini Vertex profile and fell back to the generic OPENAI_MODEL — failing
  "Gemini Vertex provider uses GEMINI_VERTEX_MODEL instead of generic OPENAI_MODEL".
- mcpValidation.test.ts: the leaked log.js stub ({ logError: () => {} }) made
  logError a no-op, so the logError error-reporting gate recorded nothing and the
  control case ("records errors when not routing to a cloud provider") failed.

Capture a frozen { ...realModule } snapshot at import time, before any
mock.module() mutates the live binding, and restore from that snapshot. Verified
with isolated repros that the live-binding restore leaks on bun 1.3.9/1.3.13 and
the snapshot restore reverts cleanly; both files' own suites still pass.

* test(model): reinstall real providerProfiles in afterAll to stop mock leak

model.test.tsx mock.module()'d providerProfiles.js with a partial stub
(getActiveProviderProfile -> undefined) and only called mock.restore() in
afterEach. Since bun's mock.restore() does not revert mock.module(), the stub
leaked into later files in the full suite; client.test.ts then could not resolve
the saved Gemini Vertex profile and fell back to the generic OPENAI_MODEL,
failing "Gemini Vertex provider uses GEMINI_VERTEX_MODEL instead of generic
OPENAI_MODEL" (the last remaining smoke-and-tests failure after the earlier
ProviderManager/mcpValidation fixes).

Capture a frozen { ...realProviderProfilesModule } snapshot at import time and
reinstall it via mock.module() in a new afterAll, matching the pattern now used
in ProviderManager.test.tsx. model.test.tsx's own 37 tests still pass.

* test(client): isolate GEMINI_VERTEX_AUTH_MODE across Gemini Vertex specs

The "ADC-only Vertex routing" test set process.env.GEMINI_VERTEX_AUTH_MODE='adc'
but the suite's beforeEach/afterEach never cleared or restored it (only
GEMINI_AUTH_MODE was managed). The very next test, "Gemini Vertex provider uses
GEMINI_VERTEX_MODEL instead of generic OPENAI_MODEL", then inherited
GEMINI_VERTEX_AUTH_MODE='adc'. resolveGeminiVertexAuthMode() treats an explicit
'adc' as authoritative over a present GEMINI_ACCESS_TOKEN, so resolveGeminiCredential
took the ADC path, found no ADC credentials, and threw "Gemini Vertex
authentication requires GEMINI_ACCESS_TOKEN or Google ADC credentials" — the
last remaining smoke-and-tests failure on CI (on Windows the same leak instead
hangs the ADC credential lookup until the 5s test timeout).

Capture, clear (beforeEach) and restore (afterEach) GEMINI_VERTEX_AUTH_MODE the
same way GEMINI_AUTH_MODE is already handled, so each spec starts from a clean
auth mode. Verified: the model spec now passes even when the ADC spec runs
immediately before it.

* test(client): assert explicit model overrides saved Vertex profile model

Covers the saved-profile-only Gemini Vertex precedence contract at the client
boundary: with CLAUDE_CODE_USE_GEMINI_VERTEX unset and an active gemini-vertex
profile, an explicit model passed to getAnthropicClient() must drive the native
Vertex request instead of the profile's saved model (addresses CodeRabbit's
requested regression).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(status): render Gemini Vertex provider route in /status

getAPIProvider() can return 'gemini-vertex' (env flag or saved-profile routing),
but /status had no API_PROVIDER_LABELS entry or render branch, so it showed an
'API provider' row with an undefined value and no endpoint/model. Add the label
and a Vertex branch mirroring the /provider summary: project/location endpoint,
model, and credential (access token vs Google ADC).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): stop saved Vertex profile hijacking an explicit OpenAI flag

resolveActiveRouteIdFromEnv's saved-profile guard already rejects a
gemini-vertex profile when a conflicting provider flag is defined, but the
two generic profile-route fallbacks below it re-resolved the profile
provider without re-checking that conflict. With CLAUDE_CODE_USE_OPENAI set,
resolveProfileRoute('gemini-vertex') returned 'gemini-vertex', so an explicit
OpenAI flag (truthy) yielded 'gemini-vertex' instead of 'openai', and a
defined-but-falsey flag (=0) yielded 'gemini-vertex' instead of falling back
to 'anthropic'. gemini-vertex is a native (non-OpenAI-compatible) route reached
only via its explicit flag or the guarded saved-profile branch, so exclude it
from both generic fallbacks. Mirrors shouldRouteToGeminiVertexFromProfile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(gemini-vertex): preserve routing and privacy on current main

* fix(gemini-vertex): address review routing and credential findings

* fix(gemini-vertex): preserve routing and request contracts

* fix(gemini-vertex): restore saved configuration and routing precedence

Restore matching active startup settings before marking plural profiles applied,
reuse saved Vertex bearers, and honor complete custom Anthropic selection.
Align native model and source-launch precedence, normalize doctor location,
and restore diagnostics test environment state with regression coverage.

* fix(gemini-vertex): preserve credential identity and routing boundaries

* fix: isolate Vertex credentials when switching to keyless profiles

* fix(gemini-vertex): reject global-only models on regional locations before the first request

Startup validation and doctor used a narrower Vertex readiness check than the native client, so a global-only model with a regional location looked ready and then failed on the first call. Share one location rule across the client, validator, and doctor.

* fix(gemini-vertex): complete model fallbacks and redact shared diagnostics

* test(gemini-vertex): isolate default-model regression environment

* test(provider): remove duplicate environment snapshot import

* fix(gemini-vertex): preserve profile identity and native payloads

Keep valid project IDs and ADC locators during native profile shaping, honor
prospective user-profile ADC files, and reject implicit workspace filename
adoption before credential validation. Preserve image/PDF bytes at native
wire conversion and resolve installed diagnostic reports from native fields.

* fix(gemini-vertex): validate native URL components and profile provenance

Reject endpoint and resource-path injection at native request and validation
boundaries while preserving project numbers and domain-scoped identifiers.
Keep the loaded profile's source through startup so concurrent user-profile
writes cannot admit a workspace ADC filename.

* fix(gemini-vertex): align validation and saved profile switching

* fix(gemini-vertex): restore original env-only routing scope

* fix(gemini-vertex): preserve ambient ADC selection and tool identities

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: jatmn <the@jat.mn>
Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com>
* commit1

* fix(attachments): resolve reminder thresholds per field and validate env strictly

- Resolve turnsSinceWrite/turnsBetweenReminders independently with
  per-field precedence (settings -> env -> default) so partial
  todoReminder settings are honored instead of discarded.
- Parse OPENCLAUDE_TODO_REMINDER_* via whole-string positive-safe-integer
  validation so suffixed, fractional, scientific, and oversized values
  fall back to the default instead of being truncated.
- Degrade invalid todoReminder thresholds or a malformed object to
  undefined in the settings schema so one bad value cannot invalidate
  the entire settings source.
- Replace the copy-pasted JSDoc above getEnvNumber with an accurate one.
- Extend focused tests: settings precedence, partial/mixed settings,
  malformed env values, and schema fallback behavior.

* test(attachments): isolate reminder settings and cover consumer boundaries

---------

Co-authored-by: jatmn <the@jat.mn>
…igpine#2066) (Twigpine#2171)

* fix(opencode): promote bare Codex alias launches to the Codex backend endpoint

* fix(opencode): normalize [1m] tag before Codex alias classification (Twigpine#2171)

* fix(opencode): centralize [1m] tag normalization inside isCodexAlias (Twigpine#2171)

* chore: remove pnpm lockfile (repo uses bun)

* fix(provider): share Codex model identity parsing for tagged routes

---------

Co-authored-by: jatmn <the@jat.mn>
…ine#2255) (Twigpine#2256)

* fix(launcher): tolerate missing bin helpers on distro installs (Twigpine#2255)

* test(launcher): add heap fallback/canonical parity plus robust siblingless fixture

* test(launcher): isolate heap env in launcher fixture

---------

Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
…tor (Twigpine#2244)

* fix(sdk): wire options.hooks callbacks into the in-process hook executor

QueryOptions.hooks was declared but never consumed by the in-process
query() path — SDK consumers passing PreToolUse/PostToolUse/Stop
callbacks got zero invocations, silently.

The kernel hook executor already supports callback-form hooks:
utils/hooks.ts merges getRegisteredHooks() into every hook event, and
bootstrap/state.ts exposes registerHookCallbacks() for exactly this
purpose (the CLI path wires the same mechanism via the control
protocol in print.ts).

This registers options.hooks (converted to HookCallbackMatcher form)
during query() setup so in-process SDK consumers get the same hook
semantics as CLI users:

- PreToolUse deny/ask decisions now gate tool execution
- Stop hooks fire when the agent attempts to finish
- All HookEvents flow through the standard executor

Verified against dist/sdk.mjs with GLM gateway end-to-end:
- PreToolUse callback fires and its deny decision blocks the tool call
- Stop callback fires on result

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sdk): isolate query hook callbacks and clean up their lifecycle

Keep options.hooks on each query's existing async context instead of
registering callbacks globally. Release callbacks on close and terminal
iteration, and cover matching, denial output, concurrent queries, and
cleanup without affecting process-level hooks.

* fix(sdk): validate hook matchers and preserve callback arguments

Skip malformed matcher values before registration without broadening them
to wildcards. Preserve omitted, undefined, and empty matchers, and forward
the executor's full callback tuple including cancellation and state access.

Cover both boundaries through the SDK query lifecycle and real hook
executor, including live abort propagation and valid optional matchers.

* test(sdk): isolate query hook fixtures from persisted settings

---------

Co-authored-by: renjie.yin <renjie.yin@ronds.com.cn>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: jatmn <the@jat.mn>
* fix(hooks): reject unsupported command hook args

* fix(settings): preserve forbidden hook args diagnostics

---------

Co-authored-by: jatmn <the@jat.mn>
Add an `aimlapi` web search provider that runs the AI/ML API Toolbox tool
`web/search` (POST https://tools.aimlapi.com/v1/tools/run). It reuses the
key the AI/ML API model provider already has (AIMLAPI_API_KEY, or the
route's OPENAI_API_KEY fallback while OPENAI_BASE_URL is the canonical
AI/ML API endpoint), so a user on that provider has nothing to paste.

Every run is billed to the user's AI/ML API balance, so the backend is
explicit-only: it is not in the auto chain, and an AIMLAPI_API_KEY alone
never routes searches through it. Each run carries max_cost_usd 0.025.

- Map output.results[] (url/title/snippet/published_at) onto search hits,
  prefixing the snippet with the publication date.
- Send allowed_domains as include_domains; apply both domain lists to the
  results like the other backends (the tool has no exclude field).
- Poll GET /v1/tools/runs/{id} after a 202 until the run ends, within the
  web search timeout; report is_error runs and HTTP error codes.
- Send the key and the X-AIMLAPI-Source / X-AIMLAPI-Partner-ID attribution
  headers only to tools.aimlapi.com, checked by exact hostname.
- /search aimlapi selects it only when the key is present and states the
  billing; /search key|remove-key aimlapi refuse, because saving
  AIMLAPI_API_KEY to the global env would also switch the model provider.
- Document the backend next to the other search providers.
…olbox

An aimlapi provider profile that points at the user's own proxy exports
the proxy's credential as AIMLAPI_API_KEY, with
CLAUDE_CODE_PROVIDER_ROUTE_ID=aimlapi and OPENAI_BASE_URL set to the proxy.
The search provider returned AIMLAPI_API_KEY unconditionally, so
/search aimlapi would have sent that proxy credential to tools.aimlapi.com.

Resolve no key in that case: the backend reports itself as not
configured, /search aimlapi refuses the selection, and a search fails
with the not-configured error before any request is made.
assertNoLineBreak(command);
// launchCommand is trusted user configuration (and can include fixed flags).
// Dynamic CLI values must remain arguments, including through npm's %* shim.
return `"${[command, ...args.map(quoteWindowsArgument)].join(' ')}"`;
assertNoLineBreak(command);
// launchCommand is trusted user configuration (and can include fixed flags).
// Dynamic CLI values must remain arguments, including through npm's %* shim.
return `"${[command, ...args.map(quoteWindowsArgument)].join(' ')}"`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.