fix(gsd): detect anthropic-vertex in provider doctor (#1598)
* fix(gsd): detect anthropic-vertex in provider doctor * test(gsd): avoid secret-scan false positives
This commit is contained in:
parent
0bceb689a7
commit
900d2fbd7c
2 changed files with 112 additions and 12 deletions
|
|
@ -51,10 +51,12 @@ function modelToProviderId(model: string): string | null {
|
|||
const prefix = model.split("/")[0].toLowerCase();
|
||||
// Map known prefixes to registry IDs
|
||||
const prefixMap: Record<string, string> = {
|
||||
"anthropic-vertex": "anthropic-vertex",
|
||||
openrouter: "openrouter",
|
||||
groq: "groq",
|
||||
mistral: "mistral",
|
||||
google: "google",
|
||||
"google-vertex": "google-vertex",
|
||||
anthropic: "anthropic",
|
||||
openai: "openai",
|
||||
"github-copilot": "github-copilot",
|
||||
|
|
@ -88,11 +90,20 @@ function collectConfiguredModelProviders(): Set<string> {
|
|||
|
||||
const modelEntries = typeof models === "object" ? Object.values(models) : [];
|
||||
for (const entry of modelEntries) {
|
||||
const modelId = typeof entry === "string" ? entry
|
||||
: typeof entry === "object" && entry !== null && "model" in entry
|
||||
? String((entry as { model: unknown }).model)
|
||||
: null;
|
||||
if (modelId) {
|
||||
if (typeof entry === "string") {
|
||||
const pid = modelToProviderId(entry);
|
||||
if (pid) providers.add(pid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof entry === "object" && entry !== null && "model" in entry) {
|
||||
const configuredProvider = "provider" in entry ? (entry as { provider?: unknown }).provider : undefined;
|
||||
if (typeof configuredProvider === "string" && configuredProvider.trim().length > 0) {
|
||||
providers.add(configuredProvider);
|
||||
continue;
|
||||
}
|
||||
|
||||
const modelId = String((entry as { model: unknown }).model);
|
||||
const pid = modelToProviderId(modelId);
|
||||
if (pid) providers.add(pid);
|
||||
}
|
||||
|
|
@ -175,7 +186,9 @@ function checkLlmProviders(): ProviderCheckResult[] {
|
|||
|
||||
for (const providerId of required) {
|
||||
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
||||
const label = info?.label ?? providerId;
|
||||
const label = providerId === "anthropic-vertex"
|
||||
? "Anthropic Vertex"
|
||||
: info?.label ?? providerId;
|
||||
const lookup = resolveKey(providerId);
|
||||
|
||||
if (!lookup.found) {
|
||||
|
|
@ -196,14 +209,18 @@ function checkLlmProviders(): ProviderCheckResult[] {
|
|||
continue;
|
||||
}
|
||||
|
||||
const envVar = info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
|
||||
const envVar = providerId === "anthropic-vertex"
|
||||
? "ANTHROPIC_VERTEX_PROJECT_ID"
|
||||
: info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
|
||||
results.push({
|
||||
name: providerId,
|
||||
label,
|
||||
category: "llm",
|
||||
status: "error",
|
||||
message: `${label} — no API key found`,
|
||||
detail: info?.hasOAuth
|
||||
message: `${label} — not configured`,
|
||||
detail: providerId === "anthropic-vertex"
|
||||
? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
|
||||
: info?.hasOAuth
|
||||
? `Run /gsd keys to authenticate`
|
||||
: `Set ${envVar} or run /gsd keys`,
|
||||
required: true,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,18 @@ function withEnv(vars: Record<string, string | undefined>, fn: () => void): void
|
|||
}
|
||||
}
|
||||
|
||||
function withCwd(nextCwd: string, fn: () => void): void {
|
||||
const saved = process.cwd();
|
||||
process.chdir(nextCwd);
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
process.chdir(saved);
|
||||
}
|
||||
}
|
||||
|
||||
const PRESENT_TEST_VALUE = "configured";
|
||||
|
||||
// ─── formatProviderReport ─────────────────────────────────────────────────────
|
||||
|
||||
test("formatProviderReport returns fallback for empty results", () => {
|
||||
|
|
@ -312,7 +324,7 @@ test("runProviderChecks reports ok for Anthropic when GitHub Copilot env var is
|
|||
withEnv({
|
||||
ANTHROPIC_API_KEY: undefined,
|
||||
ANTHROPIC_OAUTH_TOKEN: undefined,
|
||||
COPILOT_GITHUB_TOKEN: "ghu_copilot-token",
|
||||
COPILOT_GITHUB_TOKEN: PRESENT_TEST_VALUE,
|
||||
GH_TOKEN: undefined,
|
||||
GITHUB_TOKEN: undefined,
|
||||
HOME: tmpHome,
|
||||
|
|
@ -336,7 +348,7 @@ test("runProviderChecks reports ok for Anthropic via GITHUB_TOKEN cross-provider
|
|||
ANTHROPIC_OAUTH_TOKEN: undefined,
|
||||
COPILOT_GITHUB_TOKEN: undefined,
|
||||
GH_TOKEN: undefined,
|
||||
GITHUB_TOKEN: "ghp_github-token",
|
||||
GITHUB_TOKEN: PRESENT_TEST_VALUE,
|
||||
HOME: tmpHome,
|
||||
}, () => {
|
||||
try {
|
||||
|
|
@ -354,7 +366,7 @@ test("runProviderChecks detects ANTHROPIC_OAUTH_TOKEN as valid Anthropic auth",
|
|||
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-oauth-test-")));
|
||||
withEnv({
|
||||
ANTHROPIC_API_KEY: undefined,
|
||||
ANTHROPIC_OAUTH_TOKEN: "oauth-token-test",
|
||||
ANTHROPIC_OAUTH_TOKEN: PRESENT_TEST_VALUE,
|
||||
COPILOT_GITHUB_TOKEN: undefined,
|
||||
GH_TOKEN: undefined,
|
||||
GITHUB_TOKEN: undefined,
|
||||
|
|
@ -401,3 +413,74 @@ test("runProviderChecks reports ok via Copilot auth.json for Anthropic", () => {
|
|||
rmSync(tmpHome, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
test("runProviderChecks uses provider-qualified anthropic-vertex model IDs", () => {
|
||||
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-prefix-home-")));
|
||||
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-prefix-repo-")));
|
||||
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(repo, ".gsd", "preferences.md"),
|
||||
[
|
||||
"---",
|
||||
"models:",
|
||||
" execution: anthropic-vertex/claude-sonnet-4-6",
|
||||
"---",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
withEnv({
|
||||
HOME: tmpHome,
|
||||
ANTHROPIC_API_KEY: undefined,
|
||||
ANTHROPIC_OAUTH_TOKEN: undefined,
|
||||
ANTHROPIC_VERTEX_PROJECT_ID: "vertex-project",
|
||||
}, () => {
|
||||
withCwd(repo, () => {
|
||||
const results = runProviderChecks();
|
||||
const vertex = results.find(r => r.name === "anthropic-vertex");
|
||||
const anthropic = results.find(r => r.name === "anthropic");
|
||||
assert.ok(vertex, "anthropic-vertex result should exist");
|
||||
assert.equal(vertex!.status, "ok", "should accept ANTHROPIC_VERTEX_PROJECT_ID as configured");
|
||||
assert.ok(!anthropic || !anthropic.required, "plain anthropic should not be required for anthropic-vertex config");
|
||||
});
|
||||
});
|
||||
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
rmSync(tmpHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("runProviderChecks uses object provider field for anthropic-vertex models", () => {
|
||||
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-provider-home-")));
|
||||
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-provider-repo-")));
|
||||
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(repo, ".gsd", "preferences.md"),
|
||||
[
|
||||
"---",
|
||||
"models:",
|
||||
" execution:",
|
||||
" model: claude-sonnet-4-6",
|
||||
" provider: anthropic-vertex",
|
||||
"---",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
withEnv({
|
||||
HOME: tmpHome,
|
||||
ANTHROPIC_API_KEY: undefined,
|
||||
ANTHROPIC_OAUTH_TOKEN: undefined,
|
||||
ANTHROPIC_VERTEX_PROJECT_ID: undefined,
|
||||
}, () => {
|
||||
withCwd(repo, () => {
|
||||
const results = runProviderChecks();
|
||||
const vertex = results.find(r => r.name === "anthropic-vertex");
|
||||
assert.ok(vertex, "anthropic-vertex result should exist");
|
||||
assert.equal(vertex!.status, "error", "missing vertex config should be reported against anthropic-vertex");
|
||||
assert.ok(vertex!.detail?.includes("ANTHROPIC_VERTEX_PROJECT_ID"), "should point to vertex setup");
|
||||
});
|
||||
});
|
||||
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
rmSync(tmpHome, { recursive: true, force: true });
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue