From bb45ddf901b1170914fb546c97179e4e3e9be3ec Mon Sep 17 00:00:00 2001 From: NOXX - Commiter Date: Tue, 16 Jun 2026 01:40:47 +0300 Subject: [PATCH] fix(vertex): add generative-language scope so SA-JSON model discovery works (#3922) Integrated into release/v3.8.26 --- open-sse/executors/vertex.ts | 14 +++++++++++- tests/unit/vertex-oauth-scopes.test.ts | 31 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/unit/vertex-oauth-scopes.test.ts diff --git a/open-sse/executors/vertex.ts b/open-sse/executors/vertex.ts index 02994026d0..4ca1a1ab05 100644 --- a/open-sse/executors/vertex.ts +++ b/open-sse/executors/vertex.ts @@ -13,6 +13,18 @@ interface ServiceAccount { const TOKEN_CACHE = new Map(); +// 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 { 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); diff --git a/tests/unit/vertex-oauth-scopes.test.ts b/tests/unit/vertex-oauth-scopes.test.ts new file mode 100644 index 0000000000..2446fa4409 --- /dev/null +++ b/tests/unit/vertex-oauth-scopes.test.ts @@ -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" + ); +});