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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
- 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>
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>
- 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>
- 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>
- 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>
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>
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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
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>
- 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>
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>
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(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>
* 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
* 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 ✅
* 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>
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.
* 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.
* 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.
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>
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>
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>