




















@@ -4,21 +4,40 @@ import path from "node:path";
44import type { Model } from "@earendil-works/pi-ai";
55import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
667-const { buildGuardedModelFetchMock, guardedFetchMock } = vi.hoisted(() => ({
8-buildGuardedModelFetchMock: vi.fn(),
9-guardedFetchMock: vi.fn(),
10-}));
7+const {
8+ buildGuardedModelFetchMock,
9+ guardedFetchMock,
10+ googleAuthGetAccessTokenMock,
11+ googleAuthMock,
12+} = vi.hoisted(() => {
13+const googleAuthGetAccessTokenMock = vi.fn();
14+return {
15+buildGuardedModelFetchMock: vi.fn(),
16+guardedFetchMock: vi.fn(),
17+ googleAuthGetAccessTokenMock,
18+googleAuthMock: vi.fn(function GoogleAuthMock() {
19+return {
20+getAccessToken: googleAuthGetAccessTokenMock,
21+};
22+}),
23+};
24+});
11251226vi.mock("openclaw/plugin-sdk/provider-transport-runtime", async (importOriginal) => ({
1327 ...(await importOriginal()),
1428buildGuardedModelFetch: buildGuardedModelFetchMock,
1529}));
163031+vi.mock("google-auth-library", () => ({
32+GoogleAuth: googleAuthMock,
33+}));
34+1735let buildGoogleGenerativeAiParams: typeof import("./transport-stream.js").buildGoogleGenerativeAiParams;
1836let buildGoogleGemini3FirstResponseRetryParams: typeof import("./transport-stream.js").buildGoogleGemini3FirstResponseRetryParams;
1937let createGoogleGenerativeAiTransportStreamFn: typeof import("./transport-stream.js").createGoogleGenerativeAiTransportStreamFn;
2038let createGoogleVertexTransportStreamFn: typeof import("./transport-stream.js").createGoogleVertexTransportStreamFn;
2139let hasGoogleVertexAuthorizedUserAdcSync: typeof import("./vertex-adc.js").hasGoogleVertexAuthorizedUserAdcSync;
40+let resolveGoogleVertexAuthorizedUserHeaders: typeof import("./vertex-adc.js").resolveGoogleVertexAuthorizedUserHeaders;
2241let resetGoogleVertexAuthorizedUserTokenCacheForTest: typeof import("./vertex-adc.js").resetGoogleVertexAuthorizedUserTokenCacheForTest;
23422443const MODEL_PROVIDER_REQUEST_TRANSPORT_SYMBOL = Symbol.for(
@@ -254,13 +273,18 @@ describe("google transport stream", () => {
254273 createGoogleGenerativeAiTransportStreamFn,
255274 createGoogleVertexTransportStreamFn,
256275} = await import("./transport-stream.js"));
257-({ hasGoogleVertexAuthorizedUserAdcSync, resetGoogleVertexAuthorizedUserTokenCacheForTest } =
258-await import("./vertex-adc.js"));
276+({
277+ hasGoogleVertexAuthorizedUserAdcSync,
278+ resolveGoogleVertexAuthorizedUserHeaders,
279+ resetGoogleVertexAuthorizedUserTokenCacheForTest,
280+} = await import("./vertex-adc.js"));
259281});
260282261283beforeEach(() => {
262284buildGuardedModelFetchMock.mockReset();
263285guardedFetchMock.mockReset();
286+googleAuthGetAccessTokenMock.mockReset();
287+googleAuthMock.mockClear();
264288buildGuardedModelFetchMock.mockReturnValue(guardedFetchMock);
265289resetGoogleVertexAuthorizedUserTokenCacheForTest();
266290});
@@ -271,6 +295,7 @@ describe("google transport stream", () => {
271295272296afterAll(() => {
273297vi.doUnmock("openclaw/plugin-sdk/provider-transport-runtime");
298+vi.doUnmock("google-auth-library");
274299vi.resetModules();
275300});
276301@@ -695,6 +720,89 @@ describe("google transport stream", () => {
695720});
696721});
697722723+it("detects supported Vertex ADC sources synchronously", async () => {
724+const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-adc-detect-"));
725+for (const type of ["authorized_user", "external_account", "service_account"]) {
726+const credentialsPath = path.join(tempDir, `${type}.json`);
727+await writeFile(credentialsPath, JSON.stringify({ type }), "utf8");
728+729+expect(
730+hasGoogleVertexAuthorizedUserAdcSync({
731+GOOGLE_APPLICATION_CREDENTIALS: credentialsPath,
732+}),
733+).toBe(true);
734+}
735+736+expect(
737+hasGoogleVertexAuthorizedUserAdcSync({
738+HOME: path.join(tempDir, "empty-home"),
739+KUBERNETES_SERVICE_HOST: "10.0.0.1",
740+}),
741+).toBe(false);
742+});
743+744+it("resolves non-file Vertex ADC through google-auth-library without OAuth refresh fetch", async () => {
745+const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-authlib-"));
746+vi.stubEnv("GOOGLE_APPLICATION_CREDENTIALS", "");
747+vi.stubEnv("HOME", path.join(tempDir, "home"));
748+vi.stubEnv("APPDATA", "");
749+googleAuthGetAccessTokenMock.mockResolvedValueOnce("ya29.google-auth-token");
750+const tokenFetchMock = vi.fn();
751+752+await expect(resolveGoogleVertexAuthorizedUserHeaders(tokenFetchMock)).resolves.toEqual({
753+Authorization: "Bearer ya29.google-auth-token",
754+});
755+756+expect(googleAuthMock).toHaveBeenCalledWith({
757+scopes: ["https://www.googleapis.com/auth/cloud-platform"],
758+});
759+expect(googleAuthGetAccessTokenMock).toHaveBeenCalledTimes(1);
760+expect(tokenFetchMock).not.toHaveBeenCalled();
761+});
762+763+it("uses google-auth-library bearer auth for Google Vertex credential marker requests", async () => {
764+const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-authlib-stream-"));
765+vi.stubEnv("GOOGLE_APPLICATION_CREDENTIALS", "");
766+vi.stubEnv("HOME", path.join(tempDir, "home"));
767+vi.stubEnv("APPDATA", "");
768+vi.stubEnv("GOOGLE_CLOUD_PROJECT", "vertex-project");
769+vi.stubEnv("GOOGLE_CLOUD_LOCATION", "us-central1");
770+googleAuthGetAccessTokenMock.mockResolvedValueOnce("ya29.transport-token");
771+const tokenFetchMock = vi.fn();
772+guardedFetchMock.mockResolvedValueOnce(
773+buildSseResponse([
774+{
775+candidates: [{ content: { parts: [{ text: "ok" }] }, finishReason: "STOP" }],
776+},
777+]),
778+);
779+780+const streamFn = createGoogleVertexTransportStreamFn();
781+const stream = await Promise.resolve(
782+streamFn(
783+buildGoogleVertexModel(),
784+{
785+messages: [{ role: "user", content: "hello", timestamp: 0 }],
786+} as Parameters<typeof streamFn>[1],
787+{
788+apiKey: "gcp-vertex-credentials",
789+fetch: tokenFetchMock,
790+} as Parameters<typeof streamFn>[2],
791+),
792+);
793+await stream.result();
794+795+expect(tokenFetchMock).not.toHaveBeenCalled();
796+const guardedCall = requireMockCall(guardedFetchMock, 0, "guarded fetch");
797+const guardedInit = requireRequestInit(guardedCall, "guarded fetch");
798+expectHeaders(guardedInit, {
799+Authorization: "Bearer ya29.transport-token",
800+"Content-Type": "application/json",
801+accept: "text/event-stream",
802+});
803+expect(new Headers(guardedInit.headers).has("x-goog-api-key")).toBe(false);
804+});
805+698806it("refreshes authorized_user ADC before Google Vertex requests", async () => {
699807const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-adc-"));
700808const credentialsPath = path.join(tempDir, "application_default_credentials.json");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。