fix(vertex): add generative-language scope so SA-JSON model discovery works (#3922)

Integrated into release/v3.8.26
This commit is contained in:
NOXX - Commiter
2026-06-16 01:40:47 +03:00
committed by GitHub
parent a03426d5ca
commit bb45ddf901
2 changed files with 44 additions and 1 deletions

View File

@@ -13,6 +13,18 @@ interface ServiceAccount {
const TOKEN_CACHE = new Map<string, { token: string; expiresAt: number }>();
// OAuth scopes minted into the Vertex SA access token.
// - cloud-platform authorizes Vertex AI (aiplatform.googleapis.com) for chat/image execution.
// - generative-language.retriever is ADDITIONALLY required so model discovery can list the live
// catalog from generativelanguage.googleapis.com/v1beta/models — without it that listing returns
// 403 ACCESS_TOKEN_SCOPE_INSUFFICIENT and discovery silently falls back to the static ~10-model
// registry list. The extra scope is harmless for execution (cloud-platform still present) and for
// projects where it isn't needed (the mint never validates scope availability).
export const VERTEX_OAUTH_SCOPES = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/generative-language.retriever",
] as const;
export function parseSAFromApiKey(apiKey: string): ServiceAccount {
try {
return JSON.parse(apiKey);
@@ -67,7 +79,7 @@ export async function getAccessToken(sa: ServiceAccount): Promise<string> {
aud: "https://oauth2.googleapis.com/token",
iat: now,
exp: now + 3600,
scope: "https://www.googleapis.com/auth/cloud-platform",
scope: VERTEX_OAUTH_SCOPES.join(" "),
})
.setProtectedHeader({ alg: "RS256", kid: sa.private_key_id })
.sign(privateKey);

View File

@@ -0,0 +1,31 @@
import test from "node:test";
import assert from "node:assert/strict";
// #3922 — the Vertex SA access token must carry BOTH cloud-platform (chat/image
// execution on aiplatform.googleapis.com) AND generative-language.retriever
// (live model discovery on generativelanguage.googleapis.com). Dropping the
// second scope makes discovery return 403 ACCESS_TOKEN_SCOPE_INSUFFICIENT and
// silently fall back to the static ~10-model registry list. This guards both.
const { VERTEX_OAUTH_SCOPES } = await import("../../open-sse/executors/vertex.ts");
test("Vertex OAuth scopes include cloud-platform (execution)", () => {
assert.ok(
VERTEX_OAUTH_SCOPES.includes("https://www.googleapis.com/auth/cloud-platform"),
"cloud-platform scope is required for Vertex AI execution"
);
});
test("Vertex OAuth scopes include generative-language.retriever (discovery)", () => {
assert.ok(
VERTEX_OAUTH_SCOPES.includes("https://www.googleapis.com/auth/generative-language.retriever"),
"generative-language.retriever scope is required for live model discovery"
);
});
test("Vertex OAuth scopes serialize as a space-delimited string for the JWT", () => {
const serialized = VERTEX_OAUTH_SCOPES.join(" ");
assert.equal(
serialized,
"https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/generative-language.retriever"
);
});