fix(anthropic-vertex): correct multi-region endpoints (#116757)

Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
Peter Steinberger
2026-07-31 04:50:24 -07:00
committed by GitHub
parent c75e435e30
commit 754fddbc79
6 changed files with 80 additions and 3 deletions

View File

@@ -102,6 +102,19 @@ describe("anthropic-vertex provider plugin", () => {
expect(result.provider.models[4]?.thinkingLevelMap).toEqual({ xhigh: null, max: "max" });
});
it.each([
{ region: "global", baseUrl: "https://aiplatform.googleapis.com" },
{ region: "us", baseUrl: "https://aiplatform.us.rep.googleapis.com" },
{ region: "eu", baseUrl: "https://aiplatform.eu.rep.googleapis.com" },
{ region: "us-east5", baseUrl: "https://us-east5-aiplatform.googleapis.com" },
])("publishes the SDK endpoint for the $region location", ({ region, baseUrl }) => {
expect(
buildAnthropicVertexProvider({
env: { GOOGLE_CLOUD_LOCATION: region },
}).baseUrl,
).toBe(baseUrl);
});
it.each(["global", "us", "eu"])("publishes Opus 5 for the %s endpoint", (region) => {
const provider = buildAnthropicVertexProvider({
env: { GOOGLE_CLOUD_LOCATION: region },
@@ -194,7 +207,7 @@ describe("anthropic-vertex provider plugin", () => {
name: "Claude Sonnet 5",
api: "anthropic-messages",
provider: "anthropic-vertex",
baseUrl: "https://us-aiplatform.googleapis.com",
baseUrl: "https://aiplatform.us.rep.googleapis.com",
reasoning: true,
input: ["text", "image"],
contextWindow: 1_000_000,
@@ -235,7 +248,7 @@ describe("anthropic-vertex provider plugin", () => {
name: "Claude Opus 5",
api: "anthropic-messages",
provider: "anthropic-vertex",
baseUrl: "https://us-aiplatform.googleapis.com",
baseUrl: "https://aiplatform.us.rep.googleapis.com",
reasoning: false,
input: ["text"],
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },

View File

@@ -238,7 +238,9 @@ export function buildAnthropicVertexProvider(params?: {
const baseUrl =
normalizeLowercaseStringOrEmpty(region) === "global"
? "https://aiplatform.googleapis.com"
: `https://${region}-aiplatform.googleapis.com`;
: region === "us" || region === "eu"
? `https://aiplatform.${region}.rep.googleapis.com`
: `https://${region}-aiplatform.googleapis.com`;
return {
baseUrl,

View File

@@ -25,6 +25,12 @@ describe("anthropic vertex region helpers", () => {
).toBe("europe-west4");
});
it.each(["us", "eu"])("parses the %s multi-region Vertex endpoint", (region) => {
expect(
resolveAnthropicVertexRegionFromBaseUrl(`https://aiplatform.${region}.rep.googleapis.com`),
).toBe(region);
});
it("treats the global Vertex endpoint as global", () => {
expect(resolveAnthropicVertexRegionFromBaseUrl("https://aiplatform.googleapis.com")).toBe(
"global",

View File

@@ -589,6 +589,24 @@ describe("createAnthropicVertexStreamFn", () => {
});
describe("createAnthropicVertexStreamFnForModel", () => {
it.each(["us", "eu"])("preserves the %s multi-region SDK endpoint", (region) => {
const { deps, anthropicVertexCtorMock, googleAuthClient } = createStreamDeps();
const streamFn = createAnthropicVertexStreamFnForModel(
{ baseUrl: `https://aiplatform.${region}.rep.googleapis.com` },
{ GOOGLE_CLOUD_PROJECT_ID: "vertex-project" } as NodeJS.ProcessEnv,
deps,
);
void streamFn(makeModel({ id: "claude-sonnet-5", maxTokens: 128_000 }), { messages: [] }, {});
expect(anthropicVertexCtorMock).toHaveBeenCalledWith({
googleAuth: googleAuthClient,
projectId: "vertex-project",
region,
baseURL: `https://aiplatform.${region}.rep.googleapis.com/v1`,
});
});
it("derives project and region from the model and env", () => {
const { deps, anthropicVertexCtorMock, googleAuthClient } = createStreamDeps();
const streamFn = createAnthropicVertexStreamFnForModel(

View File

@@ -0,0 +1,36 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { resolveAnthropicEphemeralCacheControl } from "./anthropic-payload-policy.js";
describe("resolveAnthropicEphemeralCacheControl", () => {
afterEach(() => {
vi.unstubAllEnvs();
});
it.each([
"https://aiplatform.googleapis.com",
"https://us-east5-aiplatform.googleapis.com",
"https://aiplatform.us.rep.googleapis.com",
"https://aiplatform.eu.rep.googleapis.com",
])("preserves env-configured long retention for the official %s endpoint", (baseUrl) => {
vi.stubEnv("OPENCLAW_CACHE_RETENTION", "long");
expect(resolveAnthropicEphemeralCacheControl(baseUrl, undefined)).toEqual({
type: "ephemeral",
ttl: "1h",
});
});
it("keeps env-configured long retention restricted for custom proxy endpoints", () => {
vi.stubEnv("OPENCLAW_CACHE_RETENTION", "long");
expect(
resolveAnthropicEphemeralCacheControl("https://proxy.example.test/vertex", undefined),
).toEqual({ type: "ephemeral" });
});
it("preserves explicitly configured long retention for custom proxy endpoints", () => {
expect(
resolveAnthropicEphemeralCacheControl("https://proxy.example.test/vertex", "long"),
).toEqual({ type: "ephemeral", ttl: "1h" });
});
});

View File

@@ -56,6 +56,8 @@ function isLongTtlEligibleEndpoint(baseUrl: string | undefined): boolean {
return (
hostname === "api.anthropic.com" ||
hostname === "aiplatform.googleapis.com" ||
hostname === "aiplatform.us.rep.googleapis.com" ||
hostname === "aiplatform.eu.rep.googleapis.com" ||
hostname.endsWith("-aiplatform.googleapis.com")
);
}