

























@@ -1,5 +1,8 @@
1+import { mkdtemp, writeFile } from "node:fs/promises";
2+import os from "node:os";
3+import path from "node:path";
14import type { Model } from "@mariozechner/pi-ai";
2-import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
5+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
3647const { buildGuardedModelFetchMock, guardedFetchMock } = vi.hoisted(() => ({
58buildGuardedModelFetchMock: vi.fn(),
@@ -13,6 +16,8 @@ vi.mock("openclaw/plugin-sdk/provider-transport-runtime", async (importOriginal)
13161417let buildGoogleGenerativeAiParams: typeof import("./transport-stream.js").buildGoogleGenerativeAiParams;
1518let createGoogleGenerativeAiTransportStreamFn: typeof import("./transport-stream.js").createGoogleGenerativeAiTransportStreamFn;
19+let createGoogleVertexTransportStreamFn: typeof import("./transport-stream.js").createGoogleVertexTransportStreamFn;
20+let hasGoogleVertexAuthorizedUserAdcSync: typeof import("./vertex-adc.js").hasGoogleVertexAuthorizedUserAdcSync;
16211722const MODEL_PROVIDER_REQUEST_TRANSPORT_SYMBOL = Symbol.for(
1823"openclaw.modelProviderRequestTransport",
@@ -63,8 +68,12 @@ function buildSseResponse(events: unknown[]): Response {
63686469describe("google transport stream", () => {
6570beforeAll(async () => {
66-({ buildGoogleGenerativeAiParams, createGoogleGenerativeAiTransportStreamFn } =
67-await import("./transport-stream.js"));
71+({
72+ buildGoogleGenerativeAiParams,
73+ createGoogleGenerativeAiTransportStreamFn,
74+ createGoogleVertexTransportStreamFn,
75+} = await import("./transport-stream.js"));
76+({ hasGoogleVertexAuthorizedUserAdcSync } = await import("./vertex-adc.js"));
6877});
69787079beforeEach(() => {
@@ -73,6 +82,10 @@ describe("google transport stream", () => {
7382buildGuardedModelFetchMock.mockReturnValue(guardedFetchMock);
7483});
758485+afterEach(() => {
86+vi.unstubAllEnvs();
87+});
88+7689it("uses the guarded fetch transport and parses Gemini SSE output", async () => {
7790guardedFetchMock.mockResolvedValueOnce(
7891buildSseResponse([
@@ -257,6 +270,89 @@ describe("google transport stream", () => {
257270);
258271});
259272273+it("refreshes authorized_user ADC before Google Vertex requests", async () => {
274+const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-adc-"));
275+const credentialsPath = path.join(tempDir, "application_default_credentials.json");
276+await writeFile(
277+credentialsPath,
278+JSON.stringify({
279+type: "authorized_user",
280+client_id: "client-id",
281+client_secret: "client-secret",
282+refresh_token: "refresh-token",
283+}),
284+"utf8",
285+);
286+vi.stubEnv("GOOGLE_APPLICATION_CREDENTIALS", credentialsPath);
287+vi.stubEnv("GOOGLE_CLOUD_PROJECT", "vertex-project");
288+vi.stubEnv("GOOGLE_CLOUD_LOCATION", "global");
289+const tokenFetchMock = vi.fn().mockResolvedValue(
290+new Response(JSON.stringify({ access_token: "ya29.vertex-token", expires_in: 3600 }), {
291+status: 200,
292+headers: { "content-type": "application/json" },
293+}),
294+);
295+guardedFetchMock.mockResolvedValueOnce(
296+buildSseResponse([
297+{
298+candidates: [{ content: { parts: [{ text: "ok" }] }, finishReason: "STOP" }],
299+},
300+]),
301+);
302+303+expect(hasGoogleVertexAuthorizedUserAdcSync()).toBe(true);
304+305+const model = {
306+id: "gemini-3.1-pro-preview",
307+name: "Gemini 3.1 Pro Preview",
308+api: "google-vertex",
309+provider: "google-vertex",
310+baseUrl: "https://{location}-aiplatform.googleapis.com",
311+reasoning: true,
312+input: ["text"],
313+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
314+contextWindow: 128000,
315+maxTokens: 8192,
316+} satisfies Model<"google-vertex">;
317+318+const streamFn = createGoogleVertexTransportStreamFn();
319+const stream = await Promise.resolve(
320+streamFn(
321+model,
322+{
323+messages: [{ role: "user", content: "hello", timestamp: 0 }],
324+} as Parameters<typeof streamFn>[1],
325+{
326+apiKey: "gcp-vertex-credentials",
327+fetch: tokenFetchMock,
328+} as Parameters<typeof streamFn>[2],
329+),
330+);
331+const result = await stream.result();
332+333+expect(tokenFetchMock).toHaveBeenCalledWith(
334+"https://oauth2.googleapis.com/token",
335+expect.objectContaining({ method: "POST" }),
336+);
337+expect(guardedFetchMock).toHaveBeenCalledWith(
338+"https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/publishers/google/models/gemini-3.1-pro-preview:streamGenerateContent?alt=sse",
339+expect.objectContaining({
340+method: "POST",
341+headers: expect.objectContaining({
342+Authorization: "Bearer ya29.vertex-token",
343+"Content-Type": "application/json",
344+accept: "text/event-stream",
345+}),
346+}),
347+);
348+expect(result).toMatchObject({
349+api: "google-vertex",
350+provider: "google-vertex",
351+stopReason: "stop",
352+content: [{ type: "text", text: "ok" }],
353+});
354+});
355+260356it("coerces replayed malformed tool-call args to an object for Google payloads", () => {
261357const params = buildGoogleGenerativeAiParams(buildGeminiModel(), {
262358messages: [
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。