Commit Graph
975 Commits
Author SHA1 Message Date
gitdevineandClaude Opus 4.8 365cc2e13b 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>
2026-06-27 21:50:17 +02:00
gitdevineandClaude Opus 4.8 6236de2500 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>
2026-06-27 21:05:29 +02:00
gitdevineandClaude Opus 4.8 ac896eede2 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>
2026-06-26 13:05:08 +02:00
gitdevineandClaude Opus 4.8 5e975fff48 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>
2026-06-26 11:37:41 +02:00
gitdevineandClaude Opus 4.8 1ac5ab6171 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>
2026-06-26 11:37:41 +02:00
gitdevineandClaude Opus 4.8 0736422bfc 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>
2026-06-26 11:37:41 +02:00
gitdevineandClaude Opus 4.8 2566cd7906 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Opus 4.8 ce14db1865 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Sonnet 4.6 630ec4cfa0 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Sonnet 4.6 1b8d902b59 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Sonnet 4.6 6fce68b90c 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 f4f6a27f97 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 a2cb9a059a 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 9a61e69756 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 eff1b9c0f2 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 a65f9b220c 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 f71d0a6d4b 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 4c406924e8 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 817d388209 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>
2026-06-26 11:37:40 +02:00
gitdevineandClaude Fable 5 92701ee54f 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 8fb16561ed 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 80583a5ed7 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 ba2bb11901 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 660e6d3845 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 ef5ae3a359 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 0ec44b310f 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 ea573c25aa 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 73059a266b 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 fe154b63c3 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 d85d4e55f3 test(gemini-vertex): use toMatchObject for normalized-shape assertion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 33bdb8545d 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 b6abb5788e 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>
2026-06-26 11:37:39 +02:00
gitdevineandClaude Fable 5 8e9ba86f39 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>
2026-06-26 11:37:38 +02:00
gitdevineandClaude Fable 5 e2e12c9201 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>
2026-06-26 11:37:38 +02:00
gitdevineandClaude Fable 5 c97d9ff5fd 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>
2026-06-26 11:37:38 +02:00
gitdevineandClaude Fable 5 2bf0248d37 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>
2026-06-26 11:37:38 +02:00
gitdevineandClaude Fable 5 b9c8cbf6b7 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>
2026-06-26 11:37:38 +02:00
gitdevineandClaude Fable 5 3086459441 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>
2026-06-26 11:37:38 +02:00
gitdevineandClaude Fable 5 d96cdded6f 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>
2026-06-26 11:37:38 +02:00
gitdevineandClaude Fable 5 fb9496530b 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>
2026-06-26 11:35:56 +02:00
2083d1cdff fix(openai-shim): recover GLM/Qwen XML tool calls emitted as text (#1791)
* fix(openai-shim): recover GLM/Qwen XML tool calls emitted as text

GLM/Qwen-family models routed through OpenAI-compatible gateways emit
tool calls as XML text (`<tool_call><function=…>`) instead of structured
`tool_calls`. The shim had no parser, so these leaked into visible prose
and never executed — the turn ended with no tool_use block and the agent
appeared to "forget" and stop mid-task.

Add `parseXmlToolCalls` covering the three dialects seen in the wild
(function/parameter, GLM-native arg_key/arg_value, Hermes JSON), wired
into both the streaming and non-streaming paths. The streaming path
holds back text from `<tool_call>` onward (incl. an opener split across
SSE deltas), converts it to tool_use blocks at finalize, and flips the
finish reason to tool_calls — mirroring the existing Ollama fallback.
Structured tool_calls and false-positive prose are handled losslessly.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* Update src/services/api/openaiShim.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-26 10:48:57 +08:00
JATMN 618e901dd8 fix(hicap): improve model catalog and effort routing (#1790)
* Improve Hicap model catalog and effort routing

Update the Hicap gateway default to claude-opus-4.8 and add verified static catalog entries for DeepSeek V4 Pro, GLM 5.2, GPT-5.4, GPT-5.5, Grok 4.3, Kimi K2.7 Code, and MiniMax M3 with context, output, reasoning, and transport metadata.

Force Hicap GPT-5.4/GPT-5.5 through the Responses transport when chat completions would reject reasoning_effort, and shape GLM 5.2 requests with the Z.AI-compatible thinking/tool-streaming behavior.

Canonicalize discovered route aliases against static catalog entries so GLM and other aliased models do not duplicate or restart with the wrong API model, while preserving the session-level /model contract without mutating provider profile model lists.

Merge static route catalog options with scoped/profile OpenAI-compatible discovery caches and ignore stale legacy OpenAI model caches for env-only Hicap routes. Add focused Atlas Cloud and OpenRouter regressions so these catalog changes do not break other gateways.

Validation run locally: bun test --timeout 10000 --feature=UNATTENDED_RETRY src/utils/providerProfiles.test.ts src/utils/model/modelOptions.gateways.test.ts src/utils/model/modelOptions.hicap.test.ts src/utils/model/modelOptions.xiaomi-mimo.test.ts src/utils/model/modelOptions.github.test.ts; bun test --timeout 15000 --feature=UNATTENDED_RETRY src/commands/model/model.test.tsx src/commands/provider/provider.test.tsx src/services/api/client.test.ts; bun test --timeout 10000 --feature=UNATTENDED_RETRY src/services/api/providerConfig.test.ts src/services/api/providerConfig.local.test.ts src/integrations/runtimeMetadata.test.ts src/integrations/compatibility.test.ts src/integrations/routeMetadata.test.ts src/utils/providerFlag.test.ts src/utils/providerValidation.test.ts src/utils/effort.codex.test.ts; bun run typecheck; git diff --check.

* Address Hicap PR review findings

Rename the discovered model helper to reflect production use, seed ModelPicker focus from the canonical option value, normalize focused/selected picker aliases through the option list, and simplify requested API format support logic.

Validation: bun test src/components/ModelPicker.test.tsx src/services/api/bootstrap.test.ts src/services/api/providerConfig.test.ts src/services/api/providerConfig.local.test.ts; bun run typecheck

* Fix Hicap catalog id responses routing

Resolve route catalog aliases before OpenAI shim runtime/support checks so Hicap catalog ids such as hicap-gpt-5.5 are probed as gpt-5.5 and keep their required Responses API transport.

Validation: bun test src/services/api/providerConfig.local.test.ts src/services/api/providerConfig.test.ts src/services/api/openaiShim.test.ts; bun run typecheck

* Add bootstrap regression for Hicap errored discovery

Exercise fetchLocalOpenAIModelOptions with an errored Hicap discovery result so canonical catalog option mapping and duplicate removal are covered at the bootstrap payload level.

Validation: bun test src/services/api/bootstrap.test.ts; bun run typecheck
2026-06-26 10:45:52 +08:00
LM40R4Y a5a8ccc378 fix: auto-continuation overly biased toward Claude-style output — breaks with non-Claude models (#1713)
* fix: auto-continuation overly biased toward Claude-style output

When using non-Claude models (OpenAI, Gemini, local models, etc.), the
continuation detection frequently fails, causing the agent to stop after
each turn and wait for manual user input.

Changes:
- Expand CONTINUATION_SIGNALS verb list to include common verbs used by
  non-Claude models: process, download, upload, compile, train, evaluate,
  test, continue, generate, extract, merge, deploy, install, configure,
  refactor, optimize (plus their -ing forms)
- Fix COMPLETION_MARKERS check to only block continuation when no
  continuation signal is present nearby (prevents false positives when
  words like 'complete' or 'done' appear mid-sentence)
- Add soft fallback nudging when text has no terminal punctuation and
  no explicit completion signal (assumes model intends to continue by
  default)
- Increase MAX_CONTINUATION_NUDGES from 3 to 20 for longer multi-step
  tasks
- Pass 'interrupt' reason to abortController.abort() in the TUI control
  interrupt handler for consistent signal propagation

Fixes #1707

* fix: add 429 retry, disable microcompact, disable command-queue abort

- Add 429 rate limit retry with backoff for non-Claude providers
  (src/services/api/withRetry.ts)
- Disable microcompact to prevent aggressive conversation trimming
  (src/query.ts)
- Disable command-queue abort to prevent interrupting running tools
  (src/cli/print.ts)

* fix: address PR review feedback

- 429 retry: use separate counter instead of manipulating attempt
- Soft fallback: remove aggressive fallback nudging all unpunctuated text
- Microcompact: honor maxMessagesCompactionThreshold 'off' setting
- Fix typecheck issues (pendingCacheEdits, imports)

* fix: address second round of PR review

- Remove 429 retry policy (out of scope for continuation fix)
- Add presentProgressive check for 'complete. Now processing...' case
- Restore command queue preemption (split to separate PR)
- Fix PendingCacheEdits type for typecheck

* fix: address third round of PR review

- Fix completion guard condition (inverted logic — was blocking with continuation signals present)
- Narrow presentProgressive to only 'Now [verb]ing' (avoid gerund false positives)
- Restore original microcompact call (remove config gate)
- Add 'interrupt' reason to permission deny abort calls

* fix: address fourth round of PR review

- [P1] Add 'continuing with' and 'proceeding to' to strongIntent regex
  so punctuated transition phrases survive terminal-punctuation gate
- [P2] Pass 'interrupt' reason to abortController.abort() in
  PermissionContext.runHooks() for interactive permission hooks

* fix: address fifth round of PR review

- [P1] Add missing -ing gerund forms to line 15 (creating, writing,
  editing, running, checking, building, etc.) so the late gerund
  transition fix covers the original action verbs too
- [P2] Add imperative/declarative signals for bare patterns:
  'need to <verb>' ('Need to process files'),
  'now <verb>' ('Now process files', with negative lookahead
  to avoid 'Now you' false positives),
  'next (i|we) <verb>' ('Next I process files')

* fix: address sixth round of PR review

Addresses all 5 findings from jatmn's CHANGES_REQUESTED review:

1. (code-quality) Extract verb list to shared ACTION_VERBS array;
   build all continuation regexes from it via buildContinuationSignals()
2. (minor) Restrict presentProgressive to gerund forms of the
   same verb list instead of broad \w+ing
3. (nit) Remove accidental 'nul' entry from .gitignore
4. (medium) Add focused tests for new verbs, imperative patterns,
   present-progressive fallback, completion-marker guard, and
   verb-list deduplication
5. (minor) Add tests verifying MAX_CONTINUATION_NUDGES = 20
   and the guard comparison

* fix: address seventh round of PR review

Addresses all 4 findings from jatmn's CHANGES_REQUESTED review:

1. [P1] Remove dead 'take' branch from VERB_ING gerund map
2. [P2] Pass 'interrupt' reason to abort() in print.ts SIGINT handler
   and PermissionContext.ts cancelAndAbort path
3. [P3] Fix tab indentation in query.ts,
   PermissionPromptToolResultSchema.ts, and permissions.ts
4. [P3] Update PR description to match actual code changes

* fix: address PR review feedback (indentation, filter-based verb exclusion)

- Fix indentation (2-space style) in PermissionPromptToolResultSchema.ts
  else-if block (jatmn review finding 1)
- Use ACTION_VERBS.filter() instead of fragile v.replace(/^do\|/, '')
  for 'time to' regex (finding 2)
- Clarify default fallback behavior in PR description (finding 4)
- Note: queryLifecycle cleanup (finding 3) not applicable — field
  does not exist in this codebase

* fix: punctuated imperative/declarative patterns now signal continuation intent

The new imperative patterns (need to <verb>, now <verb>, next i/we <verb>)
matched in the late-window signal check but were silently dropped when
the text had terminal punctuation, because the punctuated branch only
checked strongIntent / presentProgressive / endsWithColon.

This left examples like 'Need to process files.' and 'Now create the
component.' returning shouldNudge: false, even though the bare variants
correctly returned true.

Fix: add hasImperativeSignal to the punctuated gate, re-testing lowerText
against the imperative patterns so punctuated action-intent signals are
recognized.

Also narrows the #1707 closure claim in the PR description: the default
fallback remains shouldNudge: false (the broader inversion suggested in
the issue introduced false positives in earlier rounds).

Tests added for punctuated imperative variants.

* fix: address ninth round of PR review

- [Medium] Restore abortController.abort('interrupt') in onInterrupt()
  bridge/SDK callback to keep interrupt-reason fix consistent
- [Minor] Normalize indent (tabs -> 2-space) in imperative/declarative
  test block in bugfixes.test.ts
- [Nit] Use lateText (last 120 chars) for hasImperativeSignal check
  for consistency with surrounding late-window logic
- queryLifecycle removal is intentional cleanup from earlier round;
  no longer referenced in callModel options

* fix: tighten need-to pattern to exclude subject-led advice (You need to...)

CodeRabbit review flagged that the `need to` continuation pattern was
overmatching subject-led advice like "You need to update..." causing
false-positive nudges. Added negative lookbehind `(?<!\b(?:you|i|we|...)\s+)`
to both `CONTINUATION_SIGNALS` and `hasImperativeSignal` so only bare
imperatives (no subject) trigger continuation. Added regression tests.

Also confirmed three earlier findings were already fixed:
- print.ts onInterrupt() already uses abort('interrupt') ✅
- Indentation in bugfixes.test.ts:172-200 already 2-space ✅
- hasImperativeSignal already uses lateText ✅
- queryLifecycle removal was intentional cleanup ✅
2026-06-26 10:14:34 +08:00
GravireiandGravirei 22fa5b4227 fix(copilot): auto-refresh Copilot token on 401 instead of only showing re-auth hint (#1766)
* fix(copilot): auto-refresh Copilot token on 401 instead of only showing re-auth hint

When GitHub Copilot returns 401 'token expired', the existing flow only
showed a hint to run /onboard-github (the #1042 fix) but never actually
refreshed the token mid-session.

Now _doRequest detects the 401 + 'token expired' body in GitHub mode,
calls refreshCopilotTokenOn401() to exchange the stored OAuth token for
a fresh Copilot token, updates process.env, and retries the request.

Fixes #1746

* fix: promote refreshedCopilotToken in auth precedence and add 401 retry integration test

- Fix auth value precedence in buildHeadersForAttempt so refreshedCopilotToken
  takes effect even when a CredentialPool is active (single key still creates
  a pool, shadowing the refreshed token on retry)
- Add integration test verifying GitHub Copilot 401 'token expired' triggers
  refreshCopilotTokenOn401() and retries chat_completions with the new token
  (P2 for PR #1766 review)

* test: add codex_responses 401 retry integration test (CodeRabbit P2)

Adds a new test 'GitHub Copilot 401 codex_responses retries with refreshed
token' that verifies the 401 retry path for the codex_responses transport
(lines 2349-2390). Mocks performCodexRequest to throw APIError(401, 'token
expired') on first call and return a valid SSE response on second, verifies
refreshCopilotTokenOn401 is invoked and the retry uses the refreshed token.

* review: shared token-expired helper, credential-pool test, edge case tests

Finding 1: Promote token-expired detection into isCopilotTokenExpiredError()
helper used by both chat_completions and codex_responses paths.

Finding 2: Add credential-pool integration test verifying that
refreshedCopilotToken takes precedence over credentialPool.next() when
OPENAI_API_KEYS is active.

Finding 3: Add edge case tests - 'token has expired' variant, 401 without
expired substring (no refresh), and same-token refresh (retries with original
token, exhausted, then fails).

* review: credential source check in codex retry path, fix mock leak

- Add credential source gate (apiKey === process.env.OPENAI_API_KEY) to
  codex_responses 401 retry path, matching the chat_completions path guard
- Add same-token guard (newApiKey !== apiKey) after refresh in codex path
- Remove leaking mock.module('./codexShim.js') from providerOverride test,
  use fetch mock instead so performCodexRequest converts 401 to APIError
- Remove duplicate old test that was left behind by previous edit
- Add providerOverride test verifying the credential source gate blocks refresh

* review: P2 refreshedCopilotCodexToken, P3 guard continue on last attempt

P2: Add refreshedCopilotCodexToken variable that takes precedence over
providerOverride.apiKey on retry, mirroring the chat path's refreshedCopilotToken
in buildHeadersForAttempt. Prevents stale override key from being reused after
a successful refresh.

P3: Guard the chat path's continue with attempt < maxAttempts - 1 so the
original 401 is surfaced when the 401 lands on the final retry slot, instead
of falling through to the generic 500 exit error.

* review: P1 chat path credential gate, P3 log refresh failures

P1: Add oldToken === (process.env.OPENAI_API_KEY ?? '') check in chat
path before calling refreshCopilotTokenOn401(), matching the codex path
guard. Prevents credential substitution when providerOverride.apiKey,
route credential, or custom auth header was the failing credential.
Add regression test for chat path with mismatched providerOverride.

P3: Log caught error with logForDebugging before returning false in
refreshCopilotTokenOn401() catch block, so secure storage read failures,
token exchange rejections, and persistence failures are visible in debug
logs instead of silently swallowed.

* fix: restrict codex refresh path to Copilot/GHE endpoints only

isGithubWithCodexTransport was using isGithubMode which is true for all
GitHub modes including GitHub Models API and custom routes. Changed to
isGithubCopilotEndpoint that checks githubEndpointType === 'copilot' ||
githubEndpointType === 'ghe', matching the chat path's isGithubCopilot
guard. Prevents COPILOT_HEADERS and refreshCopilotTokenOn401 from being
triggered for non-Copilot endpoints.

* fix: move didRefreshCopilotToken assignment after credential source check

The refresh flag was set before confirming oldToken matches
process.env.OPENAI_API_KEY, burning the single refresh chance when the
credential pool rotates to a non-matching key first. Moved inside the
credential source check so it's only consumed for eligible credentials.

* fix: add oldToken guard to chat-path credential source check

Prevents '' === '' match when both Authorization and OPENAI_API_KEY are
empty from burning the refresh flag on an unauthenticated request.

---------

Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
2026-06-26 06:53:19 +08:00
JATMN 0071454479 Update Xiaomi MiMo v2.5 catalog reasoning (#1788)
Limit the direct Xiaomi MiMo and token-plan catalogs to the supported v2.5 chat models, removing the deprecated v2/flash entries.

Add OpenAI-compatible reasoning_effort metadata for low/medium/high on Xiaomi MiMo v2.5 models while keeping the provider on the existing OpenAI-compatible path.

Add tests covering the token-plan catalog, raw api-key auth header, and reasoning_effort request serialization.

Validation: bun run build; bun run smoke; bun run check; bun run test:full; bun run test:provider; bun run test:provider-recommendation; bun run typecheck; bun run typecheck:type-tests; python -m pytest -q python/tests; bun run integrations:check; focused Xiaomi/OpenAI shim tests; git diff --check; bun run scripts/pr-intent-scan.ts --base upstream/main.
2026-06-25 23:32:39 +08:00
JATMN 40faf256db fix(atlas-cloud): vendor to gateway catalog correction and added reasoning support (#1785)
* Fix Atlas Cloud gateway catalog

Move Atlas Cloud from a direct vendor descriptor to a hosted OpenAI-compatible gateway while preserving the atlas-cloud preset, dedicated ATLAS_CLOUD_API_KEY handling, model env defaults, and generated integration metadata.

Add shared upstream model descriptors needed by the Atlas gateway catalog so Atlas entries do not own model metadata or resolve through NearAI-scoped descriptors.

Add conservative Atlas reasoning metadata for verified Kimi effort levels and Grok Build always-on/no-wire reasoning, plus resolver and OpenAI shim tests for top-level reasoning_effort serialization and no-wire behavior.

* Add verified Atlas reasoning effort metadata

Update the Atlas Cloud gateway catalog with route-specific reasoning controls verified through live Atlas probes.

Enable top-level reasoning_effort metadata for models that accepted low/medium/high/xhigh, limit Doubao routes to low/medium/high, keep Grok Build as always-on no-wire reasoning, and leave OWL without effort controls after probes showed no reasoning output.

Expand effort resolver coverage and OpenAI shim serialization tests so Atlas metadata exposes the expected effort levels and strips reasoning_effort for no-wire models.

Validation: bun install; bun run build; bun run smoke; bun run check; bun run test:provider; bun run test:provider-recommendation; python -m pytest -q python/tests; bun run typecheck; bun run typecheck:type-tests; bun run doctor:runtime; bun run security:pr-scan -- --base HEAD --head 1878a1f05f7fcce7ab7098c890ff3091dafcaf66.

* Add explicit Atlas catalog aliases

Introduce opt-in catalog aliases for model entries and resolve those aliases through the active route catalog before sending OpenAI-compatible requests.

Add Atlas aliases where descriptor IDs differ from Atlas API model IDs, including GLM, Claude coding variants, GPT, Kimi, MiniMax, Qwen, and Grok entries.

Teach effort metadata lookup to use the same explicit aliases so /effort support matches routed model IDs.

Cover Atlas alias routing plus OpenRouter and Hicap no-op routing in providerConfig tests, and add Atlas GLM alias effort coverage.

* Strengthen Atlas routing regression tests

Assert the Atlas gateway catalog resolves at least one shared model before checking for NearAI descriptor leakage.

Add an Atlas full API-name fallback assertion so catalog alias routing cannot over-match provider-qualified model IDs.
2026-06-25 23:25:41 +08:00
0xfandom 82fd23798e fix(env-file): collapse escaped backslashes in quoted values (#1773)
* fix(env-file): collapse escaped backslashes in quoted values

unescapeQuotedValue only collapsed an escaped quote (\" -> "), but the
closing-quote scanner (findClosingQuote/isEscapedQuote) already treats a
quoted value as backslash-escaped: an odd-length backslash run escapes the
following quote, so a \\ pair is consumed as a single escaped backslash
when locating the closing quote. Because the unescaper left \\ untouched,
the two stages disagreed and a value such as "a\\b" round-tripped to the
doubled "a\\b" instead of "a\b".

Collapse \\ to \ in unescapeQuotedValue so both stages share one escaping
rule. Lone backslashes before ordinary characters are still preserved.

* test(env-file): cover escaped backslash adjacent to closing quote

Locks down the terminator interaction between findClosingQuote/isEscapedQuote
and unescapeQuotedValue: a value written as "a\\" (two backslashes before
the closing quote) parses to a single trailing backslash.
2026-06-25 23:23:43 +08:00
Kevin CodexandOpenClaude 13f7401541 perf(repl): batch streaming text, cache normalize, coalesce config writes (#1744)
Three independent runtime hot-path wins for long sessions and fast streams:

- Streaming text (REPL.tsx): the Ink root is a LegacyRoot, so every
  per-delta setStreamingText committed a synchronous REPL render. The
  preview hides the in-progress trailing line, so deltas between newlines
  changed nothing on screen yet still re-rendered. Hold the full text in a
  ref and publish to state only when the newline-truncated preview changes
  (or on clear), dropping those no-op renders under 100-300 delta/sec
  streams. Displayed text is byte-identical; the Esc-interrupt path reads
  the ref so no trailing partial line is lost.

- Incremental normalize (messages.ts + Messages.tsx): normalizeMessages
  flat-maps the whole transcript on every append (O(n) over 2,800+
  messages). normalizeMessagesCached memoizes per-message output keyed on
  (message identity, isNewChain entry flag) in a WeakMap, so unchanged
  messages are reused with stable object identity (reviving downstream
  memo/WeakMap bailouts and cutting GC pressure). It is an allocation/
  identity optimization — the call still scans the list, it is not an O(1)
  append. Proven equivalent to normalizeMessages in tests.

- Coalesced config writes (config.ts): saveGlobalConfig does a sync
  lock+reread+backup+fsync per call. saveGlobalConfigDeferred queues the
  updater, write-throughs the cache for read coherence, and folds the
  batch into one locked write on a 500ms debounce (flushed on cleanup and
  process exit). Auth-loss guard, lock and backup stay on the disk path.

Also fixes handleMessageFromStream's input_json_delta to update tool input
in place instead of reordering the updated tool to the array tail.

Review fixes (CodeRabbit + jatmn):
- REPL onStreamingText updates streamingTextRef BEFORE the showStreamingText
  guard so reduced-motion / cursor-up-yank-bug terminals preserve partial
  assistant output on Esc. The publish decision is extracted to a pure
  helper (streamingTextPublish.ts) with streamingTextPublish.test.ts, and
  replStreamingTextClear.test.ts source-scans REPL.tsx to assert both
  turn-boundary paths fully clear the streaming refs (no stale re-append).
- saveGlobalConfigDeferred primes the cache (getGlobalConfig) before
  enqueueing, and a direct saveGlobalConfig flushes pending deferred writes
  first. Together these keep same-process reads coherent: the first deferred
  counter update is visible immediately even on a cold cache, and a direct
  save can no longer clobber a queued delta with a disk snapshot. Regressions
  in deferredConfigWrites.test.ts.
- Extracted the deferred-write queue/debounce/write-through/drain into a
  generic, disk-free engine (deferredConfigWrites.ts) with injected
  storage/scheduler; deferredConfigWrites.test.ts exercises the real branch.
- config.deferredWrite.test.ts loads config via a query-suffixed specifier
  so a leaked mock.module('./config.js') can no longer silently turn its
  assertions into no-ops; it always exercises the real path.
- messages.streamingToolUses.test.ts covers interleaved input_json_delta
  order preservation; messages.normalizeCached.test.ts now covers the
  reused-message entryFlag true<->false cache transition.
- Corrected the normalizeMessagesCached comment (O(n) scan / allocation
  optimization, not an O(1) append).

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-06-25 14:06:46 +08:00
Kevin CodexandOpenClaude a723540163 perf(build): minify the CLI bundle (whitespace + syntax, keep identifiers) (#1743)
dist/cli.mjs shipped unminified at 21.7MB; whitespace+syntax minification
cuts it to ~16MB (-26%) and shaves V8 parse time on every invocation.
Identifier mangling stays off because the codebase matches
constructor.name (errors.ts, toolExecution.ts, useCanUseTool). The SDK
bundle stays unminified — its React/Ink leak check greps import syntax
that minification would rewrite.

The bundle guard's missing-module tripwire relied on Bun's
`// missing-module-stub:<path>` module-boundary comments, which
minification strips. The stub loader now also emits the marker as a
side-effecting string push (survives treeshaking and syntax-minify), and
the guard parses both forms.

Review fix (CodeRabbit + jatmn): the marker parser previously truncated
paths at the first backslash or space, so a JSON-escaped Windows marker
like "missing-module-stub:C:\\Users\\Jane Doe\\...\\src\\...\\foo.js" was
captured as a useless `C:` (or `C:\\Users\\Jane`) fragment and canonicalized
to the wrong key — letting a newly stubbed module slip past the tripwire on
Windows/spaced build hosts. Parse each marker form to its correct
terminator instead: the string literal runs to its matching (back-ref)
closing quote consuming escaped pairs, and Bun's comment runs to end of
line. Extract canonicalStub() + the parser into scripts/stubMarkerGuard.ts
so the logic is unit-testable, and add regression tests for Windows,
spaced, comment-form, and multi-marker-per-line cases.

Verified: build green, bundle ~16MB minified, guard passes against the real
bundle, stub-guard tests pass, --version works through the minified bundle.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-06-25 12:34:59 +08:00
Kevin CodexandOpenClaude a1a3cfc31e perf(integrations): load descriptor catalog lazily on first registry read (#1742)
Split the generated output into integrationManifest.generated.ts (plain
preset metadata, type-only deps) and integrationArtifacts.generated.ts
(the ~10k-line descriptor graph). Add a setRegistryLazyLoader() hook in
registry.ts so read getters evaluate the catalog on first access instead
of eagerly at module load — cutting startup cost on paths that never read
the registry (index.ts previously eval'd 60+ descriptor modules at import).

Review fixes:
- registry.ts ensureLoaded() keeps the lazy loader armed until it
  succeeds (re-entrancy guard + clear-on-success), so a throwing lazy
  import retries on the next read instead of leaving the registry empty.
- providerSecrets.ts no longer statically imports the heavy descriptor
  artifacts. It sat on the bootstrap startup chain
  (bootstrap -> providerConfig -> providerProfile -> providerSecrets), so
  the static import undercut the lazy-loading goal. Import
  PROVIDER_PRESET_MANIFEST from the lightweight manifest module and lazily
  require() the descriptor arrays inside readDescriptorCredentialEnvKeys()
  (cached by getKnownProviderSecretEnvKeys(), so it runs at most once).
- artifactGenerator.test.ts resolves artifacts by full
  src/integrations/generated/<name> path instead of tuple order; the last
  order-dependent test now uses splitGeneratedArtifacts().
- Added registry + providerSecrets regression tests (loader retry on
  failure; no static descriptor import on the bootstrap path).

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-06-25 11:22:13 +08:00