Skip to content

feat(vertex): route Claude models through the AnthropicVertex SDK - #66522

Open
nickkpoon wants to merge 26 commits into
NousResearch:mainfrom
nickkpoon:feat/claude-on-vertex
Open

feat(vertex): route Claude models through the AnthropicVertex SDK#66522
nickkpoon wants to merge 26 commits into
NousResearch:mainfrom
nickkpoon:feat/claude-on-vertex

Conversation

@nickkpoon

@nickkpoon nickkpoon commented Jul 17, 2026

Copy link
Copy Markdown

What

Vertex Model Garden serves Claude over the Anthropic Messages protocol (rawPredict / streamRawPredict), not the OpenAI-compatible endpoint that Gemini and partner MaaS models use. Until now the vertex provider only spoke OpenAI-compat, so selecting a Claude model on Vertex was unreachable.

This completes the Vertex provider for Claude, mirroring the existing dual-path pattern used for AWS Bedrock (Claude → AnthropicBedrock SDK; other models → Converse). PR #8427 added the Gemini Vertex provider; this is the Claude half.

Motivation: it is the only way to bill heavy Claude usage against Google Cloud credits — Bedrock bills AWS only.

How

  • agent/vertex_adapter.pyis_anthropic_vertex_model() (detects claude-*@YYYYMMDD and the anthropic/ alias) and get_vertex_anthropic_config(), which returns the google-auth Credentials object rather than a frozen token so the Anthropic SDK self-refreshes the OAuth2 access token on expiry. No per-turn 401 refresh hook is needed for long-lived gateway sessions (unlike the OpenAI-compat Gemini path, which mints a static bearer).
  • agent/anthropic_adapter.pybuild_anthropic_vertex_client() (AnthropicVertex, max_retries=0 so the outer loop owns retry / Retry-After, common betas without the 1M-context beta which Vertex Claude does not honor).
  • hermes_cli/runtime_provider.py — dual-path the vertex branch: Claude → api_mode=anthropic_messages; Gemini/partner → chat_completions (unchanged).
  • agent/agent_init.py + run_agent.py — build/rebuild the AnthropicVertex client when provider=vertex and api_mode=anthropic_messages.
  • hermes_cli/{setup,models,model_setup_flows}.py — model picker + setup-wizard support: Claude model suggestions, model-aware endpoint preview, and Model-Garden enablement guidance.

Notes

  • Gemini/partner routing is byte-for-byte unchanged.
  • Prompt caching, interleaved thinking, and fine-grained tool streaming are preserved (that is the whole point of using the native SDK rather than a LiteLLM / OpenAI-compat shim).
  • Claude models must be enabled in the GCP Vertex Model Garden per project + region, and are typically served on a regional endpoint (e.g. us-east5) rather than global.

Tests

Adds 17 tests (all green) covering model detection, credential-object resolution, dual-path routing, client shape (max_retries=0, no 1M beta), and regional vs global base URLs. Existing Vertex + Bedrock + runtime-provider suites remain green.

Live end-to-end call against a real GCP project was not run in this environment (no GCP credentials available); everything up to the network boundary is verified against the real Anthropic SDK.

@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard provider/anthropic Anthropic native Messages API area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades duplicate This issue or pull request already exists labels Jul 17, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #3569 — both implement native AnthropicVertex routing for Claude models on Google Vertex. #55742 and #61859 are related broader competing implementations.

@nickkpoon
nickkpoon force-pushed the feat/claude-on-vertex branch 2 times, most recently from d465c2a to b47cc57 Compare July 18, 2026 12:34

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for carrying the Claude-on-Vertex path through runtime setup, the picker, and auxiliary clients. Current main still routes all Vertex requests through OpenAI chat completions (hermes_cli/runtime_provider.py:1589-1610), so the feature remains needed.

Problems

  • Recovery is incomplete: agent/agent_runtime_helpers.py:1093-1100 and :1265-1274 rebuild every anthropic_messages primary with build_anthropic_client(). After a transient recovery or fallback restoration, a Vertex Claude session would no longer use AnthropicVertex; the PR only updates initialization and run_agent.py::_rebuild_anthropic_client.
  • The new classifier accepts anthropic/claude-*, but primary normalization retains that prefix for provider vertex (hermes_cli/model_normalize.py:412-417,453-467). The PR strips it only in the auxiliary path, so main and auxiliary requests disagree on the model ID.
  • Vertex docs remain Gemini-only (website/docs/guides/google-vertex.md:9; website/docs/integrations/providers.md:385-406).

Suggested changes

  • Centralize Vertex-Anthropic client construction and use it in recovery/restore/switch paths; add regression tests for those paths.
  • Normalize the supported Anthropic alias to the bare Vertex publisher ID before primary requests, with a main-agent test.
  • Document the Claude protocol path, Model Garden enablement, and regional behavior.

Automated hermes-sweeper review.

Comment thread agent/agent_init.py
@@ -808,6 +813,36 @@ def init_agent(
agent._client_kwargs = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch can be reached by anthropic/claude-*, because the new classifier accepts that alias, but the primary model normalizer does not strip an anthropic/ prefix for provider vertex (hermes_cli/model_normalize.py:412-417,453-467). Normalize the model to the bare Vertex publisher ID before constructing the client, and add a main-agent alias regression test; the auxiliary branch already performs that strip.

@nickkpoon
nickkpoon force-pushed the feat/claude-on-vertex branch from b47cc57 to b728bf1 Compare July 18, 2026 23:22
@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 18, 2026
@nickkpoon
nickkpoon force-pushed the feat/claude-on-vertex branch from eb185ed to 8231298 Compare July 20, 2026 05:43
@nickkpoon

Copy link
Copy Markdown
Author

Addressed all three review findings in 656820d4ac (plus 82312987a4 for the picker and a1b285d9ca for the update flow, also filed standalone as #67884):

  1. Recovery completeness — added build_anthropic_client_for_provider() as the single provider-aware chokepoint (bedrock → AnthropicBedrock, vertex → AnthropicVertex with credential re-resolve + agent-cache fallback, else the plain client). try_recover_primary_transport, restore_primary_runtime, switch_model, and AIAgent._rebuild_anthropic_client all build through it now; regression tests cover each path (tests/agent/test_vertex_client_recovery.py, 19 tests, no SDK/network).

  2. Alias normalizationnormalize_model_for_provider(…, "vertex") strips the anthropic/ prefix (and only that prefix — Gemini/partner-MaaS IDs keep their required publisher/ form), so primary and auxiliary agree on the wire ID. Parametrized tests incl. @YYYYMMDD and case-insensitive prefix.

  3. Docsgoogle-vertex.md now documents the Claude protocol split, bare publisher IDs, Model Garden enablement (enable + data-sharing 403 + zero-default-quota 429), and regional behavior; providers.md blurb updated.

While fixing (1) we caught a fourth issue in the same family, live: determine_api_mode("vertex") is provider-level chat_completions, and the desktop model-set flow persisted that into the session override — a fresh session on a vertex Claude model then built an OpenAI client against the Anthropic base_url and 404'd every request. determine_api_mode now takes an optional model and returns anthropic_messages for Claude-on-vertex; the switch paths pass it, and tui_gateway stops persisted overrides from clobbering an authoritative vertex_anthropic runtime.

@nickkpoon
nickkpoon force-pushed the feat/claude-on-vertex branch from fcdce4e to 8c5bb15 Compare July 22, 2026 23:52
@lczupryn-tibco

Copy link
Copy Markdown

This is great — thanks for carrying the full Claude-on-Vertex path through
runtime, picker, and aux clients. One additive gap I'd flag for enterprise
deployments:

build_anthropic_vertex_client() pins the SDK to Google's public
aiplatform.googleapis.com host
(it passes only project_id / region /
credentials, no base_url). That's correct for direct-to-Google Vertex, but
a lot of corporate GCP setups front Vertex with a private gateway / reverse
proxy
(VPC-SC egress proxies, Apigee, an internal *.corp AI gateway, etc.).
Those need the AnthropicVertex client pointed at a configurable base URL while
still authenticating with ADC — the SDK appends
/projects/.../publishers/anthropic/models/{model}:rawPredict to whatever base
it's given, so it works transparently once the host is overridable.

This is a small, non-competing follow-on rather than anything that should block
this PR. Proposed minimal addition (backward-compatible — no signature change to
your public return tuples):

  • vertex_adapter: resolve an optional base URL from
    ANTHROPIC_VERTEX_BASE_URL (env) → vertex.anthropic_base_url (config.yaml)
    None (falls back to Google's public endpoint, i.e. today's behavior).
  • build_anthropic_vertex_client(..., base_url=None): when a base URL is
    resolved, pass it through as the AnthropicVertex(base_url=...) kwarg
    (preserving the trailing /v1, since the SDK appends the rawPredict path).

I have this working against a corporate EU Vertex gateway and am happy to open
it as a tiny PR on top of yours once this merges (or fold it in here if you'd
prefer). Nothing about it changes the Google-direct path — it's None by
default. Let me know which you'd rather.

@alt-glitch alt-glitch added comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) needs-decision Awaiting maintainer decision before any implementation sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages and removed duplicate This issue or pull request already exists labels Jul 24, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Correction: this is not a duplicate of #3569 on its current head. It now adds distinct recovery, model-aware API-mode, session/TUI override, and auxiliary-client coverage; it is related to #3569, #55742, and #61859 for maintainer comparison.

@nickkpoon

Copy link
Copy Markdown
Author

Rebased onto current main (was 581 commits behind; had drifted into merge-conflict state against 3 files touched by a parallel same-day PR that also registered Vertex in PROVIDER_REGISTRY/HERMES_OVERLAYS).

Conflict resolution notes:

  • agent/agent_runtime_helpers.py, hermes_cli/auth.py, hermes_cli/model_switch.py, hermes_cli/providers.py, tests/hermes_cli/test_vertex_provider.py: combined both sides rather than picking one — kept this PR's provider-aware build_anthropic_client_for_provider() chokepoint alongside main's new MoA-facade restore branch, kept both docstrings, dropped this branch's stale static aiplatform.googleapis.com base_url override in favor of main's correct region-computed one (Vertex Claude is often served on regional endpoints per the PR description itself).
  • Two bugs surfaced only after the text-level auto-merge (no conflict markers, so easy to miss): a duplicate _PROVIDER_MODELS["vertex"] dict literal in hermes_cli/models.py where main's parallel Gemini-only entry silently clobbered this PR's Claude entries (broke all short-alias picker tests), and two competing vertex branches in _refresh_provider_credentials() where this PR's newer host-based-eviction branch shadowed main's simpler one into dead code. Fixed both; removed now-redundant tests for the dead branch (already covered by tests/agent/test_auxiliary_client_vertex_recovery.py).
  • Updated one main-only test (test_vertex_has_curated_model_list) that asserted every vertex model carries the google/ prefix — no longer true with Claude's bare publisher IDs curated alongside it.

Full vertex/bedrock/model_switch/model_normalize/runtime_provider/auxiliary test subset (1410 passed) now shows the same 16–17 pre-existing failures as a clean main checkout with zero vertex changes applied, confirming these are unrelated flaky/broken tests, not regressions from this PR. Ready for another look.

Vertex Model Garden serves Claude over the Anthropic Messages protocol
(rawPredict / streamRawPredict), not the OpenAI-compatible endpoint that
Gemini and partner MaaS models use. Until now the `vertex` provider only
spoke OpenAI-compat, so selecting a Claude model on Vertex was unreachable
— the one way to bill heavy Claude usage to Google Cloud credits (Bedrock
bills AWS only).

This mirrors the existing salvaged Bedrock dual-path (NousResearch#8427 added the
Gemini Vertex provider; this completes it for Claude):

- agent/vertex_adapter.py: add `is_anthropic_vertex_model()` (detects
  `claude-*@YYYYMMDD` and the `anthropic/` alias) and
  `get_vertex_anthropic_config()`, which returns the google-auth
  Credentials object rather than a frozen token so the Anthropic SDK
  self-refreshes the OAuth2 access token on expiry — no per-turn 401
  refresh hook needed for long-lived gateway sessions.
- agent/anthropic_adapter.py: add `build_anthropic_vertex_client()`
  (AnthropicVertex, max_retries=0 so hermes owns retry, common betas
  without the 1M-context beta which Vertex Claude does not honor).
- hermes_cli/runtime_provider.py: dual-path the vertex branch — Claude
  → api_mode=anthropic_messages; Gemini/partner → chat_completions.
- agent/agent_init.py + run_agent.py: build/rebuild the AnthropicVertex
  client when provider=vertex and api_mode=anthropic_messages.
- hermes_cli/{setup,models,model_setup_flows}.py: picker + setup wizard
  support (Claude model suggestions, model-aware endpoint preview,
  Model-Garden enablement guidance).

Gemini/partner routing is unchanged. Adds 17 tests covering model
detection, credential-object resolution, dual-path routing, client
shape, and regional base URLs.
nickkpoon and others added 23 commits August 15, 2026 17:35
User ADC (authorized_user credentials) requires the x-goog-user-project
header on aiplatform requests — without it Vertex returns 403 'requires
a quota project'. google-auth's own transports attach it automatically,
but the Anthropic SDK uses its own httpx client, so set it explicitly
as a default header. Service accounts tolerate the header harmlessly.

Found during live verification against a real GCP project.
Vertex shared-capacity preview MaaS endpoints (e.g. deepseek-v3.2-maas)
return 429 RESOURCE_EXHAUSTED 'too many concurrent requests' on cold-start
bursts, then serve normally within seconds. _is_payment_error() matched the
'resource exhausted' substring and classified this as permanent quota
exhaustion -> no retry -> the advisor was silently dropped from every MoA
reference fan-out on cold turns (where provider fallback is meaningless).

- New _is_transient_concurrency_throttle(): fires on transient 'too many
  concurrent'/'try again later' 429s, never on daily/weekly/quota/billing
  wording (those still fall through to the payment/fallback path unchanged).
- Wired into all 3 transient-retry gates (sync + both async) so the cold 429
  now retries with exponential backoff via auxiliary.transient_retries
  (default 2 -> up to 3 attempts) before any fallback.
- Async path upgraded from single-retry to the same bounded backoff loop.

Reproduced with a raw curl to Vertex (429 -> 200 on retry); 6-case detector
test isolates concurrency bounce from genuine quota exhaustion.
…x models

Vertex has no /models discovery endpoint and fetch_models() returns None
by design, but _PROVIDER_MODELS had no vertex entry — so the picker's
vertex row always enumerated zero models and the configured model only
appeared via unrelated cache paths (intermittently).
…y/MoA path

resolve_provider_client() previously had no 'vertex' entry in
PROVIDER_REGISTRY (plugin auto-extend only picks up api_key providers),
so every auxiliary.<task> and MoA slot with provider: vertex returned
(None, None) -> 'no API key was found' (NousResearch#61852), and Claude-on-Vertex
slots that did resolve a base_url 404'd on /v1/messages.

Mirrors the bedrock aws_sdk dual-path pattern:
- hermes_cli/auth.py: register vertex ProviderConfig (auth_type=vertex)
- agent/auxiliary_client.py: vertex branch now splits Claude ->
  AnthropicVertex SDK (AnthropicAuxiliaryClient wrapper, credentials
  object, self-refreshing tokens) vs Gemini/MaaS -> OpenAI-compat.
- hermes_cli/providers.py: vertex overlay so get_provider('vertex')
  resolves and base_url forwarding preserves provider identity instead
  of collapsing to 'custom'.

Live-verified: title_generation on vertex config, MoA slot
vertex/claude-fable-5 (1.7s), vertex/gpt-oss-120b-maas regression OK.
Pre-existing test failures (14) reproduce identically on clean tree.
…opicCompletionsAdapter

The adapter's synthesized usage renamed input_tokens->prompt_tokens and
dropped cache_read/cache_creation entirely, so normalize_usage(
api_mode=anthropic_messages) — which reads the native field names —
returned all-zero CanonicalUsage for every MoA advisor and auxiliary
call routed through an Anthropic-SDK-backed client (native, custom
anthropic_messages, Vertex, Bedrock). The whole reference fan-out was
invisible to cost tracking. Expose both shapes: native names for the
anthropic_messages branch, OpenAI prompt_tokens (cache-inclusive per
OpenAI convention) for the fallback branch.

Also: vertex Gemini aux branch now uses _create_openai_client so SDK
max_retries defaults to 0 (Hermes owns retry policy, NousResearch#54465).
…redentials exist

Vertex has auth_type="vertex" and env_vars=() — no API key to detect — so
list_authenticated_providers never marked it authenticated and the picker
omitted the provider row whenever vertex wasn't the configured
model.provider. Regression surfaced when model.provider switched to moa:
"Google Vertex AI" (and claude-fable-5 with it) vanished from the desktop
picker despite working ADC.

- model_switch: add _has_vertex_creds_for_listing() mirroring bedrock's
  aws_sdk special case, in both the overlay and canonical-provider loops
- auth: treat the non-secret vertex: config section (project_id) or a
  credentials-path env var as explicit configuration, so explicit-only
  desktop pickers keep the row (vertex has no key for check 3 to find)
- providers: add "Google Vertex AI" display label
- tests: picker visibility with/without credentials + explicit-config
  signals

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

Addresses the three PR-review findings, plus a live failure the review
predicted (a fresh desktop session on vertex/claude-fable-5 built an
OpenAI chat_completions client against the Anthropic base_url → HTTP 404
on every request, then took the same 404 on the vertex fallback model).

1. Recovery/restore/switch completeness: new
   build_anthropic_client_for_provider() in anthropic_adapter is the
   single provider-aware chokepoint (bedrock → AnthropicBedrock, vertex →
   AnthropicVertex re-resolving credentials with the agent's cached
   _vertex_* attrs as fallback, else plain client). Used by
   try_recover_primary_transport, restore_primary_runtime, switch_model,
   and AIAgent._rebuild_anthropic_client (now a thin delegation).
   switch_model also pins the SDK placeholder keys (aws-sdk /
   vertex-oauth) so a stale previous-provider key can't leak in.

2. Alias normalization agreement: normalize_model_for_provider(vertex)
   now strips the anthropic/ prefix (and only that prefix — Gemini and
   partner MaaS IDs keep their required publisher/ form), so primary and
   auxiliary requests agree on the wire ID.

3. Model-aware api_mode: determine_api_mode() takes an optional model and
   returns anthropic_messages for Claude-on-vertex; the two
   model_switch.py call sites and agent-side switch_model pass the model.
   tui_gateway additionally stops persisted session api_mode/base_url/key
   overrides from clobbering an authoritative vertex_anthropic runtime —
   the exact clobber behind the live 404.

4. Docs: google-vertex.md gains Claude model IDs, the protocol split,
   Model Garden enablement (enable + data-sharing 403 + zero-quota 429),
   regional behavior, and troubleshooting entries; providers.md blurb
   updated.

Tests: 19 new in tests/agent/test_vertex_client_recovery.py (chokepoint
dispatch, recovery/restore/switch routing, api_mode determination incl.
the empty-api_mode switch regression) + 9 normalization cases in
test_vertex_provider.py. Suites touched all green (174+51+87).

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

The live failure the previous commit missed: _create_request_anthropic_client
— the per-request streaming client the stale/interrupt watchdog ownership
contract requires — special-cased bedrock but not vertex, so EVERY streamed
request on a Claude-on-Vertex session built a plain Anthropic client and
POSTed {aiplatform_host}/v1/messages (Google's HTML 404), starting with the
first call of a brand-new session. The shared client built at init was
correct, which is why direct construction tested fine while real desktop
sessions failed.

Route it through build_anthropic_client_for_provider, along with the three
remaining direct build_anthropic_client sites on the primary path:
- chat_completion_helpers fallback activation (a vertex fallback target died
  the same way the primary just had — observed live: fable-5 404 → fallback
  claude-sonnet-5 404), with SDK placeholder keys pinned
- credential-refresh rebuild and credential-pool swap in run_agent
  (defense-in-depth; vertex/bedrock have no bearer to refresh)

Also harden the gateway session-override path: session model overrides
persisted by older builds (or echoed from cached desktop client state) can
carry api_mode chat_completions / a stale bearer key for a Claude-on-Vertex
model. _sanitize_vertex_claude_runtime repairs them at both consumption
points (fast path + _apply_session_model_override).

Tests: per-request client routing for vertex + plain-provider preservation
(21 total in test_vertex_client_recovery.py); verified live end-to-end
through _create_request_anthropic_client on the real project.

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

try_activate_fallback has its own api_mode detection block, independent of
determine_api_mode (fixed for the primary path in 656820d4ac). It had cases
for openai-codex/anthropic/azure/openai/bedrock but none for Claude-on-
Vertex, so a vertex fallback model defaulted to chat_completions, skipped
the anthropic_messages client build, and 404'd on {host}/v1/messages.

Observed live: the primary claude-fable-5 (now correctly on AnthropicVertex
after da03dca51b) hit a transient Anthropic 'Overloaded' (529), which
triggered fallback to claude-sonnet-5 — and the fallback 404'd because of
this gap, so the whole turn still failed.

Add a vertex-alias + is_anthropic_vertex_model() branch mirroring
determine_api_mode's split, so the fallback reaches the (already provider-
aware) anthropic_messages client build. Verified live: sonnet-5 fallback
resolves anthropic_messages → AnthropicVertex → serves the request.

Test: integration test drives try_activate_fallback for a vertex Claude
fallback and asserts api_mode=anthropic_messages + chokepoint build.

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

MODEL_ALIASES had sonnet/opus/haiku/claude but not fable. Typing
'/model fable' (e.g. from the Discord gateway) fell through alias
resolution and sent the literal string 'fable' to the wire. On Vertex,
is_anthropic_vertex_model('fable') is False (no 'claude' prefix), so
the request skipped the AnthropicVertex SDK path and hit the
OpenAI-compatible /openapi endpoint, which 400s with:
  Malformed publisher model (model: 'fable'); expected '<publisher>/<model>'

Adds the alias plus a regression test asserting the short Claude
aliases present in Vertex's curated catalog resolve to bare IDs the
AnthropicVertex classifier recognizes.
…token expiry (401 ACCESS_TOKEN_TYPE_UNSUPPORTED)

The Vertex Gemini/openapi auxiliary path bakes a frozen OAuth2 bearer
token into the OpenAI client at build time. After the ~1h token lifetime
every auxiliary call (compression, title_generation, background_review)
401s until process restart — observed live as hours of context_compressor
failures in long-lived desktop sessions.

- vertex_adapter.refresh_vertex_credentials(): forced token re-mint with
  thread-safe single-flight cooldown; clears the creds cache and warns
  when the re-mint returns the same (wedged) token.
- _refresh_provider_credentials(): new vertex branch (all 5 accepted
  spellings) that re-mints then evicts stale clients.
- _evict_cached_vertex_clients(): host-based sweep so clients cached
  under 'auto'/task labels are evicted too, not just 'vertex'-keyed ones.
- _auth_refresh_provider_for_route(): maps aiplatform.googleapis.com
  hosts (global + regional prefix form) to 'vertex' so auto-routed
  clients recover.
- tests: adapter re-mint semantics + recovery wiring + cache sweep.
Verified live against the Vertex publisher endpoint (global region):
claude-opus-4-8 serves; opus-4-7 / opus-4-5 404 (not offered on this
surface). With the catalog entry, the existing 'opus' short alias now
resolves on vertex (alias machinery already handled it — the catalog
entry was the missing piece). Extends the short-alias regression test
to cover 'opus'.
…h messages on resume

1) anthropic_adapter: _ensure_leading_user_turn prepended a single-space
   text block when history starts with an assistant turn (branch spine).
   Anthropic rejects whitespace-only text blocks -> HTTP 400 on the
   branch's first query. Placeholder is now non-whitespace '(continued)'.

2) hermes_state: _session_lineage_root_to_tip now stops at a
   _branched_from boundary. Branches persist a full seeded copy of the
   pre-branch transcript, so walking into the parent concatenated the
   parent's rows on top of the branch's own copy — every pre-branch
   message rendered twice in resume/display projections. Compression
   continuations (no copy) still walk to their ancestors.

Regression tests: adapter placeholder contract + branch-boundary lineage.
The merge in the previous commit (8ccd000c8e) auto-merged cleanly at
the text level but left two real bugs behind, both invisible to git
because they didn't produce conflict markers:

1. hermes_cli/models.py: two independent commits each added their own
   `"vertex": [...]` entry to the same _PROVIDER_MODELS dict literal
   (this branch's Claude+Gemini list, upstream's Gemini-only list from
   a parallel PR). Python silently keeps the last literal — upstream's
   Gemini-only list clobbered the Claude entries, breaking the /model
   picker and all four 'opus'/'sonnet'/'fable'/'claude' short-alias
   resolution tests on vertex. Merged into one entry carrying both.

2. agent/auxiliary_client.py::_refresh_provider_credentials(): the
   merge left two competing vertex branches — this branch's
   `_VERTEX_PROVIDER_NAMES` branch (calls refresh_vertex_credentials(),
   evicts by base_url host so aliased/auto-routed clients are caught
   too) shadowed upstream's simpler, permanently-dead `== "vertex"`
   branch (get_vertex_config()-based, provider-name-only eviction).
   Removed the dead duplicate; its behavior is already fully covered
   by the dedicated tests/agent/test_auxiliary_client_vertex_recovery.py
   suite (40/40 passing), so removed its two now-obsolete inline tests
   in test_auxiliary_client.py as well.

Also fixed tests/hermes_cli/test_vertex_model_picker.py's
test_vertex_has_curated_model_list, an upstream-only test written
before this PR existed that asserted every vertex model carries the
'google/' publisher prefix — no longer true once Claude's bare
publisher IDs (AnthropicVertex SDK path) are curated alongside
Gemini/partner-MaaS vendor-prefixed IDs.

Verified: tests/hermes_cli/ + tests/agent/ vertex/bedrock/model_switch/
model_normalize/runtime_provider/auxiliary subset now shows the same
17 failures as a clean upstream/main checkout (pre-existing flaky/
broken tests unrelated to vertex, reproduced with zero vertex changes
applied) and zero vertex-specific failures.
claude-opus-5 was absent from DEFAULT_CONTEXT_LENGTHS and
BEDROCK_CONTEXT_LENGTHS, so it fell through to the generic
"claude": 200000 catch-all. The agent then compressed context at
~200K on a model that serves 1M, silently discarding context the
user already paid to load.

Verified against a live Vertex endpoint, which reports:
  prompt is too long: 8460056 tokens > 1000000 maximum

Both tables are matched longest-key-first, so the new entries do not
shadow the existing opus-4-* (200K) or haiku-4-5 (200K) entries.
…alog

Adding claude-opus-5 alongside claude-opus-4-8 made the bare `/model opus`
shorthand raise AmbiguousAliasError: upstream's resolve_alias() refuses to
guess among multiple family matches rather than silently version-sorting.
Drop opus-4-8 (superseded; still reachable via an explicit model_aliases
entry) so each Claude family resolves to exactly one catalog id.

Split the bare-"claude" case out of the resolve-happy-path parametrize into
its own test asserting the raise, pinning the no-silent-selection contract.
…ontract

switch_model() now calls _ensure_lmstudio_runtime_loaded(context_intent),
_lmstudio_load_was_unverified(runtime_len) and
_effective_lmstudio_context_length(intent, runtime_len). The fork's stub
predates all three, so the vertex chokepoint tests died on a TypeError
before reaching their assertions.
…rtex_config

The vertex branch of _refresh_provider_credentials now delegates to
vertex_adapter.refresh_vertex_credentials(); patching get_vertex_config
left the real refresh running, so the unminted case returned True and the
success case never asserted its dependency.
…e, curated vertex catalog, suppressed-key pool guard
…t, intro pricing, fast-model family, default aux)
@nickkpoon
nickkpoon force-pushed the feat/claude-on-vertex branch from 3d1a705 to cfb4c9e Compare August 16, 2026 00:41
@alt-glitch alt-glitch added the comp/plugins Plugin system and bundled plugins label Aug 16, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #3569 and the other open Claude-on-Vertex implementations. This PR has concrete broader recovery, picker, and runtime-integration deltas, so it is a competing implementation rather than a duplicate.

…ing entries

Grok on Vertex Model Garden was falling into the provider='google' branch of
resolve_billing_route, so every call looked up a ('google','grok-*') key that
does not exist and was silently recorded as $0.00 / cost_status=unknown -- the
same bug class the Claude-on-Vertex branch already fixes. Adds the xai branch
plus pricing for the 4.1-fast and 4.20/4.3 tiers, and lists
grok-4.1-fast-non-reasoning in the curated vertex catalog.
@nickkpoon

Copy link
Copy Markdown
Author

Thanks @lczupryn-tibco for the suggestion regarding corporate proxies and VPC-SC egress gateways.

I have pushed an update that implements the base_url passthrough for the native AnthropicVertex client. It now resolves an optional base URL from the ANTHROPIC_VERTEX_BASE_URL environment variable, falling back to vertex.anthropic_base_url in config.yaml. If neither is set, it defaults to Google's public endpoint just like before.

The central chokepoint (build_anthropic_client_for_provider) handles the resolution, and test mocks have been updated accordingly. This should fully support enterprise gateway routing while preserving the default behavior. Ready for review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have provider/anthropic Anthropic native Messages API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants