feat(pi-ai): support ANTHROPIC_BASE_URL env var for custom proxy endpoints (#4140)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit e94cf668e2fdf28537aead642b4062cd3a22a8d3)
This commit is contained in:
parent
f1d483c304
commit
1914f31342
2 changed files with 66 additions and 2 deletions
|
|
@ -4,7 +4,7 @@ import { readFileSync } from "node:fs";
|
|||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { usesAnthropicBearerAuth } from "./anthropic.js";
|
||||
import { usesAnthropicBearerAuth, resolveAnthropicBaseUrl } from "./anthropic.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
|
|
@ -31,3 +31,49 @@ test("createClient routes Bearer-auth providers through authToken (#3783)", () =
|
|||
"Bearer-auth providers should send authToken instead",
|
||||
);
|
||||
});
|
||||
|
||||
// Minimal model stub — only the field resolveAnthropicBaseUrl cares about.
|
||||
const stubModel = { baseUrl: "https://api.anthropic.com" } as Parameters<typeof resolveAnthropicBaseUrl>[0];
|
||||
|
||||
test("resolveAnthropicBaseUrl returns model.baseUrl when ANTHROPIC_BASE_URL is unset (#4140)", (t) => {
|
||||
const saved = process.env.ANTHROPIC_BASE_URL;
|
||||
t.after(() => {
|
||||
if (saved === undefined) delete process.env.ANTHROPIC_BASE_URL;
|
||||
else process.env.ANTHROPIC_BASE_URL = saved;
|
||||
});
|
||||
|
||||
delete process.env.ANTHROPIC_BASE_URL;
|
||||
assert.equal(resolveAnthropicBaseUrl(stubModel), "https://api.anthropic.com");
|
||||
});
|
||||
|
||||
test("resolveAnthropicBaseUrl prefers ANTHROPIC_BASE_URL over model.baseUrl (#4140)", (t) => {
|
||||
const saved = process.env.ANTHROPIC_BASE_URL;
|
||||
t.after(() => {
|
||||
if (saved === undefined) delete process.env.ANTHROPIC_BASE_URL;
|
||||
else process.env.ANTHROPIC_BASE_URL = saved;
|
||||
});
|
||||
|
||||
process.env.ANTHROPIC_BASE_URL = "https://proxy.example.com";
|
||||
assert.equal(resolveAnthropicBaseUrl(stubModel), "https://proxy.example.com");
|
||||
});
|
||||
|
||||
test("resolveAnthropicBaseUrl ignores whitespace-only ANTHROPIC_BASE_URL (#4140)", (t) => {
|
||||
const saved = process.env.ANTHROPIC_BASE_URL;
|
||||
t.after(() => {
|
||||
if (saved === undefined) delete process.env.ANTHROPIC_BASE_URL;
|
||||
else process.env.ANTHROPIC_BASE_URL = saved;
|
||||
});
|
||||
|
||||
process.env.ANTHROPIC_BASE_URL = " ";
|
||||
assert.equal(resolveAnthropicBaseUrl(stubModel), "https://api.anthropic.com");
|
||||
});
|
||||
|
||||
test("createClient uses resolveAnthropicBaseUrl for all auth paths (#4140)", () => {
|
||||
const source = readFileSync(join(__dirname, "..", "..", "src", "providers", "anthropic.ts"), "utf-8");
|
||||
const directUsages = (source.match(/baseURL:\s*model\.baseUrl/g) ?? []).length;
|
||||
assert.equal(directUsages, 0, "createClient must not use model.baseUrl directly — use resolveAnthropicBaseUrl(model)");
|
||||
assert.ok(
|
||||
source.includes("baseURL: resolveAnthropicBaseUrl(model)"),
|
||||
"all createClient branches should pass baseURL through resolveAnthropicBaseUrl",
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,6 +25,24 @@ import {
|
|||
export type { AnthropicEffort, AnthropicOptions };
|
||||
export { extractRetryAfterMs };
|
||||
|
||||
/**
|
||||
* Resolve the base URL for Anthropic API requests.
|
||||
*
|
||||
* Resolution order:
|
||||
* 1. ANTHROPIC_BASE_URL environment variable (if set and non-empty after trim)
|
||||
* 2. model.baseUrl (from the model definition)
|
||||
*
|
||||
* This allows routing traffic through custom proxy endpoints (e.g. OpusMax,
|
||||
* local mirrors, corporate gateways) without modifying model definitions.
|
||||
*/
|
||||
export function resolveAnthropicBaseUrl(model: Model<"anthropic-messages">): string {
|
||||
const envBaseUrl = process.env.ANTHROPIC_BASE_URL?.trim();
|
||||
if (envBaseUrl) {
|
||||
return envBaseUrl;
|
||||
}
|
||||
return model.baseUrl;
|
||||
}
|
||||
|
||||
let _AnthropicClass: typeof Anthropic | undefined;
|
||||
async function getAnthropicClass(): Promise<typeof Anthropic> {
|
||||
if (!_AnthropicClass) {
|
||||
|
|
@ -75,7 +93,7 @@ async function createClient(
|
|||
const client = new AnthropicClass({
|
||||
apiKey: null,
|
||||
authToken: apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
baseURL: resolveAnthropicBaseUrl(model),
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: mergeHeaders(
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue