mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:20:44 +00:00
fix(anthropic-vertex): resolve model discovery and auth for GCP ADC (openclaw#65716)
Verified: - pnpm test extensions/anthropic-vertex/index.test.ts extensions/anthropic-vertex/region.adc.test.ts src/plugins/manifest-registry.test.ts src/plugins/provider-runtime.synthetic-auth-discovery.test.ts Co-authored-by: feiskyer <676637+feiskyer@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
|
||||
|
||||
const { hasAnthropicVertexAvailableAuthMock } = vi.hoisted(() => ({
|
||||
hasAnthropicVertexAvailableAuthMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./api.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./api.js")>();
|
||||
return {
|
||||
...actual,
|
||||
hasAnthropicVertexAvailableAuth: hasAnthropicVertexAvailableAuthMock,
|
||||
};
|
||||
});
|
||||
|
||||
import anthropicVertexPlugin from "./index.js";
|
||||
|
||||
describe("anthropic-vertex provider plugin", () => {
|
||||
beforeEach(() => {
|
||||
hasAnthropicVertexAvailableAuthMock.mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("resolves the ADC marker through the provider hook", async () => {
|
||||
const provider = await registerSingleProviderPlugin(anthropicVertexPlugin);
|
||||
|
||||
@@ -77,4 +98,34 @@ describe("anthropic-vertex provider plugin", () => {
|
||||
allowSyntheticToolResults: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves synthetic auth when ADC is available", async () => {
|
||||
hasAnthropicVertexAvailableAuthMock.mockReturnValue(true);
|
||||
const provider = await registerSingleProviderPlugin(anthropicVertexPlugin);
|
||||
|
||||
const result = provider.resolveSyntheticAuth?.({
|
||||
provider: "anthropic-vertex",
|
||||
config: undefined,
|
||||
providerConfig: undefined,
|
||||
} as never);
|
||||
|
||||
expect(result).toEqual({
|
||||
apiKey: "gcp-vertex-credentials",
|
||||
source: "gcp-vertex-credentials (ADC)",
|
||||
mode: "api-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined when ADC is not available", async () => {
|
||||
hasAnthropicVertexAvailableAuthMock.mockReturnValue(false);
|
||||
const provider = await registerSingleProviderPlugin(anthropicVertexPlugin);
|
||||
|
||||
const result = provider.resolveSyntheticAuth?.({
|
||||
provider: "anthropic-vertex",
|
||||
config: undefined,
|
||||
providerConfig: undefined,
|
||||
} as never);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import { NATIVE_ANTHROPIC_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import {
|
||||
hasAnthropicVertexAvailableAuth,
|
||||
mergeImplicitAnthropicVertexProvider,
|
||||
resolveAnthropicVertexConfigApiKey,
|
||||
resolveImplicitAnthropicVertexProvider,
|
||||
} from "./api.js";
|
||||
|
||||
const PROVIDER_ID = "anthropic-vertex";
|
||||
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
@@ -37,6 +40,21 @@ export default definePluginEntry({
|
||||
},
|
||||
resolveConfigApiKey: ({ env }) => resolveAnthropicVertexConfigApiKey(env),
|
||||
...NATIVE_ANTHROPIC_REPLAY_HOOKS,
|
||||
resolveSyntheticAuth: () => {
|
||||
if (!hasAnthropicVertexAvailableAuth()) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
apiKey: GCP_VERTEX_CREDENTIALS_MARKER,
|
||||
source: "gcp-vertex-credentials (ADC)",
|
||||
mode: "api-key",
|
||||
};
|
||||
},
|
||||
augmentModelCatalog: ({ config }) =>
|
||||
readConfiguredProviderCatalogEntries({
|
||||
config,
|
||||
providerId: PROVIDER_ID,
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"enabledByDefault": true,
|
||||
"providers": ["anthropic-vertex"],
|
||||
"providerDiscoveryEntry": "./provider-discovery.ts",
|
||||
"syntheticAuthRefs": ["anthropic-vertex"],
|
||||
"nonSecretAuthMarkers": ["gcp-vertex-credentials"],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { buildAnthropicVertexProvider } from "./provider-catalog.js";
|
||||
import { hasAnthropicVertexAvailableAuth, resolveAnthropicVertexConfigApiKey } from "./region.js";
|
||||
|
||||
const PROVIDER_ID = "anthropic-vertex";
|
||||
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
||||
|
||||
type AnthropicVertexProviderPlugin = {
|
||||
id: string;
|
||||
@@ -15,6 +16,13 @@ type AnthropicVertexProviderPlugin = {
|
||||
run: (ctx: ProviderCatalogContext) => ReturnType<typeof runAnthropicVertexCatalog>;
|
||||
};
|
||||
resolveConfigApiKey: (params: { env: NodeJS.ProcessEnv }) => string | undefined;
|
||||
resolveSyntheticAuth: () =>
|
||||
| {
|
||||
apiKey: string;
|
||||
source: string;
|
||||
mode: "api-key";
|
||||
}
|
||||
| undefined;
|
||||
};
|
||||
|
||||
function mergeImplicitAnthropicVertexProvider(params: {
|
||||
@@ -69,6 +77,16 @@ export const anthropicVertexProviderDiscovery: AnthropicVertexProviderPlugin = {
|
||||
run: runAnthropicVertexCatalog,
|
||||
},
|
||||
resolveConfigApiKey: ({ env }) => resolveAnthropicVertexConfigApiKey(env),
|
||||
resolveSyntheticAuth: () => {
|
||||
if (!hasAnthropicVertexAvailableAuth()) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
apiKey: GCP_VERTEX_CREDENTIALS_MARKER,
|
||||
source: "gcp-vertex-credentials (ADC)",
|
||||
mode: "api-key",
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default anthropicVertexProviderDiscovery;
|
||||
|
||||
@@ -46,4 +46,28 @@ describe("anthropic-vertex ADC reads", () => {
|
||||
expect(existsSyncMock).not.toHaveBeenCalled();
|
||||
expect(readFileSyncMock).toHaveBeenCalledWith("/tmp/vertex-adc.json", "utf8");
|
||||
});
|
||||
|
||||
it("respects HOME when probing the default ADC path from a copied env snapshot", () => {
|
||||
const env = {
|
||||
HOME: "/tmp/vertex-home",
|
||||
} as NodeJS.ProcessEnv;
|
||||
|
||||
readFileSyncMock.mockImplementation((pathname, options) =>
|
||||
String(pathname) === "/tmp/vertex-home/.config/gcloud/application_default_credentials.json"
|
||||
? '{"project_id":"vertex-project"}'
|
||||
: String(pathname) === "/tmp/vertex-adc.json"
|
||||
? '{"project_id":"vertex-project"}'
|
||||
: (() => {
|
||||
throw new Error(`unexpected readFileSync(${String(pathname)}, ${String(options)})`);
|
||||
})(),
|
||||
);
|
||||
|
||||
expect(resolveAnthropicVertexProjectId(env)).toBe("vertex-project");
|
||||
expect(hasAnthropicVertexAvailableAuth(env)).toBe(true);
|
||||
expect(existsSyncMock).not.toHaveBeenCalled();
|
||||
expect(readFileSyncMock).toHaveBeenCalledWith(
|
||||
"/tmp/vertex-home/.config/gcloud/application_default_credentials.json",
|
||||
"utf8",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,12 +7,6 @@ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtim
|
||||
const ANTHROPIC_VERTEX_DEFAULT_REGION = "global";
|
||||
const ANTHROPIC_VERTEX_REGION_RE = /^[a-z0-9-]+$/;
|
||||
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
||||
const GCLOUD_DEFAULT_ADC_PATH = join(
|
||||
homedir(),
|
||||
".config",
|
||||
"gcloud",
|
||||
"application_default_credentials.json",
|
||||
);
|
||||
|
||||
type AdcProjectFile = {
|
||||
project_id?: unknown;
|
||||
@@ -71,14 +65,28 @@ function hasAnthropicVertexMetadataServerAdc(env: NodeJS.ProcessEnv = process.en
|
||||
);
|
||||
}
|
||||
|
||||
function resolveAnthropicVertexHomeDir(env: NodeJS.ProcessEnv = process.env): string {
|
||||
return (
|
||||
normalizeOptionalSecretInput(env.HOME) ||
|
||||
normalizeOptionalSecretInput(env.USERPROFILE) ||
|
||||
homedir()
|
||||
);
|
||||
}
|
||||
|
||||
function resolveAnthropicVertexDefaultAdcPath(env: NodeJS.ProcessEnv = process.env): string {
|
||||
return platform() === "win32"
|
||||
? join(
|
||||
env.APPDATA ?? join(homedir(), "AppData", "Roaming"),
|
||||
normalizeOptionalSecretInput(env.APPDATA) ??
|
||||
join(resolveAnthropicVertexHomeDir(env), "AppData", "Roaming"),
|
||||
"gcloud",
|
||||
"application_default_credentials.json",
|
||||
)
|
||||
: GCLOUD_DEFAULT_ADC_PATH;
|
||||
: join(
|
||||
resolveAnthropicVertexHomeDir(env),
|
||||
".config",
|
||||
"gcloud",
|
||||
"application_default_credentials.json",
|
||||
);
|
||||
}
|
||||
|
||||
function resolveAnthropicVertexAdcCredentialsPathCandidate(
|
||||
@@ -88,9 +96,6 @@ function resolveAnthropicVertexAdcCredentialsPathCandidate(
|
||||
if (explicit) {
|
||||
return explicit;
|
||||
}
|
||||
if (env !== process.env) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveAnthropicVertexDefaultAdcPath(env);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user