Skip to content

feat: add vertex-anthropic provider - #2178

Merged
steebchen merged 27 commits into
theopenco:mainfrom
analogpvt:feat/vertex-anthropic-provider
May 17, 2026
Merged

steebchen merged 27 commits into
theopenco:mainfrom
analogpvt:feat/vertex-anthropic-provider

Conversation

@analogpvt

@analogpvt analogpvt commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a new vertex-anthropic provider to route Claude models through Google Cloud Vertex AI
  • Uses Anthropic Messages API format via Vertex's rawPredict/streamRawPredict endpoint
  • Supports Claude Sonnet 4.5/4.6, Opus 4.5/4.6/4.7, and Haiku 4.5
  • Automatic OAuth2 token generation and caching from inline service account JSON
  • Minimal config: only LLM_VERTEX_ANTHROPIC_SERVICE_ACCOUNT_JSON + LLM_VERTEX_ANTHROPIC_REGION needed

Implementation

New provider: vertex-anthropic

  • Endpoint: https://{region}-aiplatform.googleapis.com/v1/projects/{project}/locations/{region}/publishers/anthropic/models/{model}:rawPredict (or streamRawPredict)
  • Auth: OAuth2 Bearer token, auto-generated from service account JSON

Automatic token refresh (apps/gateway/src/lib/gcp-token.ts)

  • Parses LLM_VERTEX_ANTHROPIC_SERVICE_ACCOUNT_JSON (inline JSON string, no file path needed)
  • Signs a JWT with the service account private key and exchanges it for an OAuth2 access token
  • Project ID is auto-extracted from the JSON — no separate LLM_VERTEX_ANTHROPIC_PROJECT needed
  • Tokens cached in Redis with 50-minute TTL (Google tokens expire after 60 min)
  • In-memory cache avoids Redis round-trips between refreshes
  • Works at any request rate — token generation happens at most once per 50 minutes

Config (minimal)

LLM_VERTEX_ANTHROPIC_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"...","private_key":"...","client_email":"...","token_uri":"..."}
LLM_VERTEX_ANTHROPIC_REGION=us-east5

Docs

  • Added step-by-step setup guide at apps/docs/content/guides/vertex-anthropic
  • Covers service account creation, key download, env config, token refresh explanation, regions, BYOK, and troubleshooting

Test plan

  • Streaming and non-streaming requests work for vertex-anthropic/claude-sonnet-4-6
  • Token auto-refresh verified via Redis cache (50-min TTL)
  • 73/73 e2e tests pass with TEST_MODELS="vertex-anthropic/claude-sonnet-4-6"
  • No impact on google-vertex (Gemini) — GCP token refresh scoped to vertex-anthropic only

🤖 Generated with Claude Code

Add Claude models via Google Cloud Vertex AI as a new
provider using the Anthropic Messages API format with
Vertex's rawPredict endpoint.

Models: Sonnet 4.5/4.6, Opus 4.5/4.6/4.7, Haiku 4.5

Closes theopenco#2146

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

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Maps Vertex-hosted Claude models and a new vertex-anthropic provider to Anthropic-compatible request/endpoint handling and to Anthropic-style streaming and non-streaming parsing, plus model-catalog entries and chat integration.

Changes

Vertex Anthropic (Claude on Vertex) Integration

Layer / File(s) Summary
Provider Detection Helpers
apps/gateway/src/chat/chat.ts
Replace Google-query-token gating with isGoogleCompatibleProvider() and add Vertex-claude detection predicate.
Model Catalog
packages/models/src/models/anthropic.ts
Add vertex-anthropic provider entries (replace some google-vertex entries) for Claude models with pricing/limits and capability flags.
Endpoint Resolution & Request Building
packages/actions/src/get-provider-endpoint.ts, packages/actions/src/prepare-request-body.ts
Add vertex-anthropic base/endpoint resolution and route Vertex-Anthropic requests through the Anthropic branch with Vertex-specific adjustments (anthropic_version, remove top-level model, preserve cache_control, use output_config.format).
Provider Headers
packages/actions/src/get-provider-headers.ts
Add optional modelName option and conditionally omit Authorization for specific Google lite models; vertex-anthropic header behavior remains Authorization-based.
Parameter Constraints
apps/gateway/src/chat/tools/resolve-provider-context.ts
Apply Anthropic mutual-exclusivity of temperature and top_p to Vertex Claude cases as well.
Streaming Provider Remap
apps/gateway/src/chat/tools/transform-streaming-to-openai.ts
Compute effectiveProvider = "anthropic" for Vertex Claude prior to the provider switch so streaming-to-OpenAI transformations use Anthropic handlers.
Streaming Content Extraction
apps/gateway/src/chat/tools/extract-content.ts
Pre-route Vertex Claude and anthropic streaming chunks to Anthropic-style extraction (data.delta.text / content_block_delta) before the provider switch; keep Google and OpenAI branches.
Streaming Reasoning Extraction
apps/gateway/src/chat/tools/extract-reasoning.ts
Introduce typed streaming chunk interfaces and route both anthropic and vertex-anthropic through Anthropic thinking_delta/content_block_delta extraction; keep Google and OpenAI typed branches.
Streaming Tool-call Extraction
apps/gateway/src/chat/tools/extract-tool-calls.ts
Detect Vertex Claude via modelName?.startsWith("claude-") and apply Anthropic-style content_block_start/content_block_delta parsing for tool-call stubs and partial JSON args.
Non-streaming Response Parsing
apps/gateway/src/chat/tools/parse-provider-response.ts
Add an early Vertex Claude branch that parses Anthropic-style json.content/thinking/tool_use blocks, maps json.stop_reason, and extracts usage tokens before falling back to Google/Vertex candidate parsing.
Chat Processing Integration
apps/gateway/src/chat/chat.ts
Extend Anthropic-compatible chat behavior (buffer-for-healing, JSON-object healing, usage-completion inference, web-search counting, tool-call enrichment/matching, finish-reason extraction) to vertex-anthropic alongside anthropic; pass modelName into provider header calls.
Finish Reason & Response Transformation
apps/gateway/src/chat/tools/map-finish-reason-to-openai.ts, apps/gateway/src/chat/tools/transform-response-to-openai.ts
Treat vertex-anthropic like anthropic when mapping finish reasons and when constructing OpenAI-style chat.completion response objects.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • steebchen
  • smakosh
  • RATCHAW
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add vertex-anthropic provider' directly and concisely summarizes the main change: introducing a new provider for routing Anthropic models through Vertex AI.
Linked Issues check ✅ Passed The PR implements the core objective from #2146 to make Claude models work via Vertex, adding the vertex-anthropic provider with full support for streaming, tool use, and reasoning.
Out of Scope Changes check ✅ Passed All changes focus on vertex-anthropic provider implementation and related Anthropic compatibility handling; the addition of modelName parameter to getProviderHeaders is a necessary supporting change.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/gateway/src/chat/chat.ts (1)

575-577: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Decouple Google auth-token routing from Google-compatible payload logic

Line 576 currently makes isGoogleCompatibleProvider() inherit usesGoogleQueryToken(). After adding vertex-anthropic at Line 571, it now gets routed through Google-specific paths (e.g., thought-signature enrichment, Google usage adjustments, image-token accounting), which can mutate Anthropic flows and skew billing/usage behavior.

Suggested fix
 function usesGoogleQueryToken(provider: string): boolean {
 	return (
 		provider === "google-ai-studio" ||
 		provider === "glacier" ||
 		provider === "google-vertex" ||
 		provider === "quartz" ||
 		provider === "vertex-anthropic"
 	);
 }

 function isGoogleCompatibleProvider(provider: string): boolean {
-	return usesGoogleQueryToken(provider);
+	return (
+		provider === "google-ai-studio" ||
+		provider === "glacier" ||
+		provider === "google-vertex" ||
+		provider === "quartz"
+	);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/chat/chat.ts` around lines 575 - 577, The helper
isGoogleCompatibleProvider incorrectly proxies to usesGoogleQueryToken which
causes non-Google providers like "vertex-anthropic" to be treated as Google for
downstream routing (thought-signature enrichment, Google usage adjustments,
image-token accounting); change isGoogleCompatibleProvider to implement explicit
compatibility checks (e.g., whitelist Google-specific providers only) rather
than calling usesGoogleQueryToken, so that usesGoogleQueryToken remains the
auth-token routing predicate and isGoogleCompatibleProvider only returns true
for providers that should receive Google-specific payload handling (update the
isGoogleCompatibleProvider implementation and any callers to ensure
vertex-anthropic is excluded).
🧹 Nitpick comments (1)
packages/actions/src/prepare-request-body.ts (1)

931-933: 💤 Low value

Vertex-Anthropic request body preparation looks correct.

  • vertex-anthropic is correctly added to providerHandlesCacheControl (the Anthropic Messages API cache_control format is used for Vertex AI too).
  • Sharing the anthropic case branch is the right approach.
  • anthropic_version: "vertex-2023-10-16" and delete requestBody.model align with Vertex AI's rawPredict/streamRawPredict contract.

Minor note: lines 1621–1623 (if (stream) { requestBody.stream = true; }) are a no-op — requestBody.stream is already initialised to stream at line 994. The block can be removed without any behavioral change.

🧹 Remove redundant stream assignment
 		if (usedProvider === "vertex-anthropic") {
 			requestBody.anthropic_version = "vertex-2023-10-16";
 			delete requestBody.model;
-			if (stream) {
-				requestBody.stream = true;
-			}
 		}

Also applies to: 1327-1328, 1618-1624

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/actions/src/prepare-request-body.ts` around lines 931 - 933, The if
(stream) { requestBody.stream = true; } blocks are redundant because
requestBody.stream is already initialized to stream (see the requestBody
initialization around line 994); remove these redundant assignments (occurrences
around the blocks that reference requestBody and stream at the locations you
noted, e.g., the blocks near lines 1327–1328 and 1618–1624) so the code relies
on the single initial assignment and avoid duplicate/no-op reassignments in
prepare-request-body.ts (look for the requestBody and stream variables in the
prepareRequestBody logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/actions/src/get-provider-endpoint.ts`:
- Around line 359-389: The region for Vertex Anthropic is being computed twice
(vaRegion inside the "vertex-anthropic" case and vertexAnthropicRegion in the
URL switch), causing mismatches when an explicit baseUrl is provided; refactor
to compute the region once (e.g., hoist a single vaRegion or
vertexAnthropicRegion via getProviderEnvValue/providerKeyOptions before the URL
switch) and then reuse that same variable when building both the baseUrl (or
validating/extracting region from an explicit baseUrl) and the path segment used
in vaBaseEndpoint, ensuring the region in the hostname and the
/locations/${vaRegion}/ path always agree.

In `@packages/models/src/models/anthropic.ts`:
- Around line 962-980: The anthropic provider entries for claude-opus-4-6 and
claude-opus-4-7 include stale pricingTiers that overbill long-context requests;
remove the pricingTiers property from the anthropic entries for
"claude-opus-4-6" and "claude-opus-4-7" (the objects where providerId ===
"anthropic" and modelName matches those strings) so their billing matches the
new vertex-anthropic flat-rate behavior—ensure you delete the entire
pricingTiers key (and any related tier-specific fields) from those model objects
and run tests/lint to confirm no references remain.

---

Outside diff comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 575-577: The helper isGoogleCompatibleProvider incorrectly proxies
to usesGoogleQueryToken which causes non-Google providers like
"vertex-anthropic" to be treated as Google for downstream routing
(thought-signature enrichment, Google usage adjustments, image-token
accounting); change isGoogleCompatibleProvider to implement explicit
compatibility checks (e.g., whitelist Google-specific providers only) rather
than calling usesGoogleQueryToken, so that usesGoogleQueryToken remains the
auth-token routing predicate and isGoogleCompatibleProvider only returns true
for providers that should receive Google-specific payload handling (update the
isGoogleCompatibleProvider implementation and any callers to ensure
vertex-anthropic is excluded).

---

Nitpick comments:
In `@packages/actions/src/prepare-request-body.ts`:
- Around line 931-933: The if (stream) { requestBody.stream = true; } blocks are
redundant because requestBody.stream is already initialized to stream (see the
requestBody initialization around line 994); remove these redundant assignments
(occurrences around the blocks that reference requestBody and stream at the
locations you noted, e.g., the blocks near lines 1327–1328 and 1618–1624) so the
code relies on the single initial assignment and avoid duplicate/no-op
reassignments in prepare-request-body.ts (look for the requestBody and stream
variables in the prepareRequestBody logic).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b71a32f2-8810-41ad-b916-bd9c7476ca53

📥 Commits

Reviewing files that changed from the base of the PR and between be07f3a and c765eb1.

📒 Files selected for processing (13)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/extract-content.ts
  • apps/gateway/src/chat/tools/extract-reasoning.ts
  • apps/gateway/src/chat/tools/extract-tool-calls.ts
  • apps/gateway/src/chat/tools/parse-provider-response.ts
  • apps/gateway/src/chat/tools/resolve-provider-context.ts
  • apps/gateway/src/chat/tools/transform-streaming-to-openai.ts
  • packages/actions/src/get-provider-endpoint.ts
  • packages/actions/src/get-provider-headers.ts
  • packages/actions/src/prepare-request-body.ts
  • packages/db/src/schema.ts
  • packages/models/src/models/anthropic.ts
  • packages/models/src/providers.ts

Comment thread packages/actions/src/get-provider-endpoint.ts
Comment thread packages/models/src/models/anthropic.ts
@analogpvt
analogpvt force-pushed the feat/vertex-anthropic-provider branch from 2cf4f75 to c765eb1 Compare May 7, 2026 08:36

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/gateway/src/chat/chat.ts (1)

583-588: ⚡ Quick win

Consider centralizing Anthropic-compatible provider detection.

You now repeat usedProvider === "anthropic" || isVertexClaudeModel(...) across many branches. A single helper (e.g. isAnthropicCompatibleProvider) would reduce drift risk and keep future provider extensions safer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/chat/chat.ts` around lines 583 - 588, You repeat the same
condition in many places; add a single helper
isAnthropicCompatibleProvider(provider: string, modelName?: string) that returns
true when provider === "anthropic" OR when isVertexClaudeModel(provider,
modelName) is true, replace all occurrences of (usedProvider === "anthropic" ||
isVertexClaudeModel(...)) with calls to this new helper, and update any call
sites that only pass provider to pass modelName where needed so the helper can
evaluate both inputs (keep existing isVertexClaudeModel intact and call it from
the new helper).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/gateway/src/chat/tools/extract-reasoning.ts`:
- Line 9: Replace the loosened parameter type "data: any" with a discriminated
union covering the provider-specific response shapes used in this file (e.g.,
Anthropic responses with delta.thinking, Google responses with
candidates[].content.parts, and OpenAI responses with
choices[].delta.reasoning); update the function signature in
extract-reasoning.ts (the parameter named data) to use that union and add narrow
type guards (by checking provider tags or presence of unique fields) before
accessing provider-specific properties so all accesses are type-safe and the
compiler can infer correct types for Anthropic/Google/OpenAI branches.

In `@packages/actions/src/get-provider-endpoint.ts`:
- Around line 348-352: The code uses modelName ?? "claude-sonnet-4-6@20250514"
inside the if (modelName?.startsWith("claude-")) branch which makes the fallback
unreachable and also embeds the wrong model ID; change claudeModel to use the
narrowed modelName directly (const claudeModel = modelName) and remove the
nullish fallback, or replace the fallback with the correct Vertex model ID
constant if you intend a default outside this branch; update references to
claudeModel/claudeBaseEndpoint accordingly.

In `@packages/models/src/models/anthropic.ts`:
- Around line 394-395: The model entry that sets providerId: "google-vertex"
currently uses an incorrect modelName "claude-sonnet-4-6@20250514" which will
produce 404s; update that modelName to the correct Vertex AI identifier
"claude-sonnet-4-6" in the same object (the Claude Sonnet 4.6 model definition)
so it matches the releasedAt metadata and official Vertex AI docs.

---

Nitpick comments:
In `@apps/gateway/src/chat/chat.ts`:
- Around line 583-588: You repeat the same condition in many places; add a
single helper isAnthropicCompatibleProvider(provider: string, modelName?:
string) that returns true when provider === "anthropic" OR when
isVertexClaudeModel(provider, modelName) is true, replace all occurrences of
(usedProvider === "anthropic" || isVertexClaudeModel(...)) with calls to this
new helper, and update any call sites that only pass provider to pass modelName
where needed so the helper can evaluate both inputs (keep existing
isVertexClaudeModel intact and call it from the new helper).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cafd6e67-7157-4f0c-a3b3-044103d44656

📥 Commits

Reviewing files that changed from the base of the PR and between c765eb1 and 2cf4f75.

📒 Files selected for processing (10)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/extract-content.ts
  • apps/gateway/src/chat/tools/extract-reasoning.ts
  • apps/gateway/src/chat/tools/extract-tool-calls.ts
  • apps/gateway/src/chat/tools/parse-provider-response.ts
  • apps/gateway/src/chat/tools/resolve-provider-context.ts
  • apps/gateway/src/chat/tools/transform-streaming-to-openai.ts
  • packages/actions/src/get-provider-endpoint.ts
  • packages/actions/src/prepare-request-body.ts
  • packages/models/src/models/anthropic.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/gateway/src/chat/tools/parse-provider-response.ts
  • apps/gateway/src/chat/tools/resolve-provider-context.ts
  • apps/gateway/src/chat/tools/transform-streaming-to-openai.ts
  • apps/gateway/src/chat/tools/extract-tool-calls.ts
  • packages/actions/src/prepare-request-body.ts

Comment thread apps/gateway/src/chat/tools/extract-reasoning.ts Outdated
Comment thread packages/actions/src/get-provider-endpoint.ts Outdated
Comment thread packages/models/src/models/anthropic.ts Outdated
- Decouple isGoogleCompatibleProvider from usesGoogleQueryToken
  (vertex-anthropic uses Bearer auth, not Google query token)
- Remove API key query param from vertex-anthropic endpoint
  (rawPredict requires OAuth2 Bearer token via header)
- Fix model IDs: remove date suffixes per Vertex AI docs
- Type extractReasoning parameter (replace `any` with union)
- Remove redundant stream assignment in prepare-request-body

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

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
apps/gateway/src/chat/tools/extract-reasoning.ts (1)

38-39: ⚡ Quick win

Prefer type guards over as assertions here.

StreamingChunk is a good step, but these casts sidestep compiler narrowing and can mask shape regressions. Narrow by provider/field presence instead of asserting.

Suggested refactor
+function isAnthropicStreamChunk(data: StreamingChunk): data is AnthropicStreamChunk {
+	return "type" in data || "delta" in data;
+}
+
+function isGoogleStreamChunk(data: StreamingChunk): data is GoogleStreamChunk {
+	return "candidates" in data;
+}
+
+function isOpenAIStreamChunk(data: StreamingChunk): data is OpenAIStreamChunk {
+	return "choices" in data;
+}
+
 export function extractReasoning(
 	data: StreamingChunk,
 	provider: Provider,
 ): string {
 	switch (provider) {
 		case "anthropic":
 		case "vertex-anthropic": {
-			const chunk = data as AnthropicStreamChunk;
+			if (!isAnthropicStreamChunk(data)) return "";
+			const chunk = data;
 			if (
 				chunk.type === "content_block_delta" &&
 				chunk.delta?.type === "thinking_delta" &&
 				chunk.delta?.thinking
 			) {
 				return chunk.delta.thinking;
 			}
 			return "";
 		}
@@
 		case "quartz": {
-			const chunk = data as GoogleStreamChunk;
+			if (!isGoogleStreamChunk(data)) return "";
+			const chunk = data;
 			const parts = chunk.candidates?.[0]?.content?.parts ?? [];
 			const reasoningParts = parts.filter((part) => part.thought);
 			return reasoningParts.map((part) => part.text).join("") ?? "";
 		}
 		default: {
-			const chunk = data as OpenAIStreamChunk;
+			if (!isOpenAIStreamChunk(data)) return "";
+			const chunk = data;
 			return (
 				chunk.choices?.[0]?.delta?.reasoning ??

Also applies to: 52-53, 58-60

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/chat/tools/extract-reasoning.ts` around lines 38 - 39,
Replace the unsafe "as" assertions (e.g., "const chunk = data as
AnthropicStreamChunk") with proper type guards that narrow the union by checking
provider-specific discriminators/required fields instead of casting; add
functions like isAnthropicStreamChunk(obj): obj is AnthropicStreamChunk and
isStreamingChunk(obj): obj is StreamingChunk that test for the presence and
shape of the provider/field(s) you rely on (e.g., provider/type/delta fields)
and use those guards in the code paths where you currently use the casts (the
"chunk" assignments and the other casts around the StreamingChunk checks) so the
compiler can safely narrow types and catch shape regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/gateway/src/chat/tools/extract-reasoning.ts`:
- Around line 38-39: Replace the unsafe "as" assertions (e.g., "const chunk =
data as AnthropicStreamChunk") with proper type guards that narrow the union by
checking provider-specific discriminators/required fields instead of casting;
add functions like isAnthropicStreamChunk(obj): obj is AnthropicStreamChunk and
isStreamingChunk(obj): obj is StreamingChunk that test for the presence and
shape of the provider/field(s) you rely on (e.g., provider/type/delta fields)
and use those guards in the code paths where you currently use the casts (the
"chunk" assignments and the other casts around the StreamingChunk checks) so the
compiler can safely narrow types and catch shape regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 551c5ad2-0c83-4437-bbdd-b08231c4a5a6

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf4f75 and a15874f.

📒 Files selected for processing (5)
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/extract-reasoning.ts
  • packages/actions/src/get-provider-endpoint.ts
  • packages/actions/src/prepare-request-body.ts
  • packages/models/src/models/anthropic.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/actions/src/prepare-request-body.ts
  • packages/actions/src/get-provider-endpoint.ts
  • apps/gateway/src/chat/chat.ts

- Add vertex-anthropic to non-streaming response transform
- Add vertex-anthropic to finish reason mapping
- Migrate output_format to output_config.format (deprecated)

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

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
apps/gateway/src/chat/tools/transform-response-to-openai.ts (1)

277-381: ⚖️ Poor tradeoff

Consider extracting a shared buildChatCompletionResponse helper to reduce duplication.

The google-ai-studio/google-vertex block (lines 281–329) and the new anthropic/vertex-anthropic block (lines 333–380) are structurally identical. The only difference is that the Google block spreads images into the message. Extracting a small helper would eliminate the duplication and make future provider additions one-liners.

♻️ Sketch of the extraction
+function buildChatCompletionResponse(params: {
+	usedProvider: Provider;
+	baseModelName: string;
+	content: string | null;
+	reasoningContent: string | null;
+	toolResults: any;
+	images?: ImageObject[];
+	annotations: Annotation[] | null;
+	finishReason: string | null;
+	promptTokens: number | null;
+	completionTokens: number | null;
+	totalTokens: number | null;
+	reasoningTokens: number | null;
+	cachedTokens: number | null;
+	costs: CostData | null;
+	showUpgradeMessage: boolean;
+	cacheCreationTokens: number | null;
+	imageInputTokens: number | null;
+	imageOutputTokens: number | null;
+	requestedModel: string;
+	requestedProvider: string | null;
+	usedModel: string;
+	requestId: string;
+	routing: RoutingAttempt[] | null;
+	usedRegion?: string;
+}) { ... }

 case "google-ai-studio":
 case "glacier":
 case "google-vertex":
-case "quartz": { ... }
+case "quartz":
+	transformedResponse = buildChatCompletionResponse({ ...params, images });
+	break;

 case "anthropic":
-case "vertex-anthropic": { ... }
+case "vertex-anthropic":
+	transformedResponse = buildChatCompletionResponse({ ...params });
+	break;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/gateway/src/chat/tools/transform-response-to-openai.ts` around lines 277
- 381, The two case blocks in transform-response-to-openai.ts create identical
chat completion objects (same id/object/created/model/choices/usage/metadata)
except the google block may include images, so extract a shared helper (e.g.,
buildChatCompletionResponse) that takes parameters like content,
reasoningContent, toolResults, annotations, images, finishReason, usedProvider,
baseModelName, usedModel, requestId, routing, usedRegion and token/cost params
passed to buildUsageObject; use mapFinishReasonToOpenai, buildUsageObject and
buildMetadata inside the helper and add an includeImages flag (or pass images
array) so both the google-ai-studio/glacier/google-vertex/quartz and
anthropic/vertex-anthropic cases call this helper to set transformedResponse,
removing the duplicated object construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/gateway/src/chat/tools/transform-response-to-openai.ts`:
- Around line 277-381: The two case blocks in transform-response-to-openai.ts
create identical chat completion objects (same
id/object/created/model/choices/usage/metadata) except the google block may
include images, so extract a shared helper (e.g., buildChatCompletionResponse)
that takes parameters like content, reasoningContent, toolResults, annotations,
images, finishReason, usedProvider, baseModelName, usedModel, requestId,
routing, usedRegion and token/cost params passed to buildUsageObject; use
mapFinishReasonToOpenai, buildUsageObject and buildMetadata inside the helper
and add an includeImages flag (or pass images array) so both the
google-ai-studio/glacier/google-vertex/quartz and anthropic/vertex-anthropic
cases call this helper to set transformedResponse, removing the duplicated
object construction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ebc0e94a-5f6e-4135-95a8-77747f85d065

📥 Commits

Reviewing files that changed from the base of the PR and between a15874f and 78c3d04.

📒 Files selected for processing (3)
  • apps/gateway/src/chat/tools/map-finish-reason-to-openai.ts
  • apps/gateway/src/chat/tools/transform-response-to-openai.ts
  • packages/actions/src/prepare-request-body.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/actions/src/prepare-request-body.ts

Lite models (gemini-2.0-flash-lite, gemini-2.5-flash-lite) use key=
query param on /v1/publishers/ path. All project-based models
(non-lite Gemini, Claude) use OAuth2 Bearer auth header.

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/actions/src/get-provider-headers.ts`:
- Around line 43-52: The validation flow calls getProviderHeaders(provider,
token) without passing modelName, causing lite models to incorrectly get an
Authorization header; update the call site in validate-provider-key.ts to pass
the resolved validationModel via the options parameter (e.g.,
getProviderHeaders(provider, token, { modelName: validationModel })) so
getProviderHeaders can detect gemini-*-flash-lite/quartz lite models and omit
the Bearer header accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6ae6c724-ecfc-4420-9dc7-02fc282e4af7

📥 Commits

Reviewing files that changed from the base of the PR and between 78c3d04 and 408f54c.

📒 Files selected for processing (3)
  • apps/gateway/src/chat/chat.ts
  • packages/actions/src/get-provider-endpoint.ts
  • packages/actions/src/get-provider-headers.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/actions/src/get-provider-endpoint.ts
  • apps/gateway/src/chat/chat.ts

Comment thread packages/actions/src/get-provider-headers.ts
analogpvt and others added 11 commits May 7, 2026 12:07
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generates OAuth2 Bearer tokens from a GCP service account JSON file
(GCP_SERVICE_ACCOUNT_KEY_FILE env var). Tokens are cached in Redis
with a 50-minute TTL (GCP tokens expire after 60 min) and in-memory
as a fast-path fallback. Eliminates manual token management for
Vertex AI providers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Step-by-step guide for running Claude models on Vertex AI
with automatic OAuth2 token refresh via service account.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace GCP_SERVICE_ACCOUNT_KEY_FILE + LLM_VERTEX_ANTHROPIC_PROJECT
with LLM_VERTEX_ANTHROPIC_SERVICE_ACCOUNT_JSON. Project ID auto-
extracted from JSON. GCP token refresh scoped to vertex-anthropic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add step showing how to convert SA JSON to single-line env var.
Fix BYOK callout to reference inline JSON instead of file path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@steebchen
steebchen enabled auto-merge May 12, 2026 13:37
@steebchen
steebchen disabled auto-merge May 12, 2026 13:37
@analogpvt
analogpvt force-pushed the feat/vertex-anthropic-provider branch from 9aed57c to 0f15778 Compare May 14, 2026 17:45
@analogpvt
analogpvt force-pushed the feat/vertex-anthropic-provider branch 2 times, most recently from 426c835 to 4ac56ec Compare May 14, 2026 17:54
@analogpvt
analogpvt force-pushed the feat/vertex-anthropic-provider branch from 0467093 to 7121ddd Compare May 14, 2026 18:08
steebchen and others added 5 commits May 17, 2026 12:46
Move the vertex-anthropic doc from guides/ to integrations/ and adapt
it to the provider integration format (Dashboard Provider Keys flow
with self-host env vars as a secondary section).

Wire LLM_VERTEX_ANTHROPIC_SERVICE_ACCOUNT_JSON and
LLM_VERTEX_ANTHROPIC_REGION into the e2e workflow so the new provider
is exercised in CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Anthropic recommends the global endpoint: dynamic routing, no pricing
premium. Regional/multi-region endpoints add a 10% premium for Sonnet
4.5+. Users who need data residency or provisioned throughput can
still set LLM_VERTEX_ANTHROPIC_REGION (or the Provider Key region
field) to override.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Google's global endpoint is at aiplatform.googleapis.com (no region
prefix), not global-aiplatform.googleapis.com. The latter returns 404
for the rawPredict path. Confirmed by hitting Vertex AI directly in
e2e tests: requests to https://global-aiplatform.googleapis.com/v1/...
returned the generic Google 404 HTML, while requests to
https://aiplatform.googleapis.com/v1/projects/.../locations/global/...
succeed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants