mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(google): accept Windows ADC manifest paths
Co-authored-by: openclaw-clawsweeper[bot] <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { mkdtemp, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { Model } from "@mariozechner/pi-ai";
|
||||
@@ -51,6 +51,24 @@ function buildGeminiModel(
|
||||
};
|
||||
}
|
||||
|
||||
function buildGoogleVertexModel(
|
||||
overrides: Partial<Model<"google-vertex">> = {},
|
||||
): Model<"google-vertex"> {
|
||||
return {
|
||||
id: "gemini-3.1-pro-preview",
|
||||
name: "Gemini 3.1 Pro Preview",
|
||||
api: "google-vertex",
|
||||
provider: "google-vertex",
|
||||
baseUrl: "https://{location}-aiplatform.googleapis.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildSseResponse(events: unknown[]): Response {
|
||||
const sse = `${events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("")}data: [DONE]\n\n`;
|
||||
const encoder = new TextEncoder();
|
||||
@@ -302,18 +320,7 @@ describe("google transport stream", () => {
|
||||
|
||||
expect(hasGoogleVertexAuthorizedUserAdcSync()).toBe(true);
|
||||
|
||||
const model = {
|
||||
id: "gemini-3.1-pro-preview",
|
||||
name: "Gemini 3.1 Pro Preview",
|
||||
api: "google-vertex",
|
||||
provider: "google-vertex",
|
||||
baseUrl: "https://{location}-aiplatform.googleapis.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"google-vertex">;
|
||||
const model = buildGoogleVertexModel();
|
||||
|
||||
const streamFn = createGoogleVertexTransportStreamFn();
|
||||
const stream = await Promise.resolve(
|
||||
@@ -353,6 +360,80 @@ describe("google transport stream", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("refreshes authorized_user ADC from the Windows APPDATA fallback for Google Vertex requests", async () => {
|
||||
const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-appdata-adc-"));
|
||||
const homeDir = path.join(tempDir, "home");
|
||||
const appDataDir = path.join(tempDir, "AppData", "Roaming");
|
||||
const fallbackDir = path.join(appDataDir, "gcloud");
|
||||
const credentialsPath = path.join(fallbackDir, "application_default_credentials.json");
|
||||
await mkdir(fallbackDir, { recursive: true });
|
||||
await writeFile(
|
||||
credentialsPath,
|
||||
JSON.stringify({
|
||||
type: "authorized_user",
|
||||
client_id: "client-id",
|
||||
client_secret: "client-secret",
|
||||
refresh_token: "appdata-refresh-token",
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
vi.stubEnv("GOOGLE_APPLICATION_CREDENTIALS", undefined);
|
||||
vi.stubEnv("HOME", homeDir);
|
||||
vi.stubEnv("APPDATA", appDataDir);
|
||||
vi.stubEnv("GOOGLE_CLOUD_PROJECT", "vertex-project");
|
||||
vi.stubEnv("GOOGLE_CLOUD_LOCATION", "global");
|
||||
const tokenFetchMock = vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify({ access_token: "ya29.appdata-token", expires_in: 3600 }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
);
|
||||
guardedFetchMock.mockResolvedValueOnce(
|
||||
buildSseResponse([
|
||||
{
|
||||
candidates: [{ content: { parts: [{ text: "ok" }] }, finishReason: "STOP" }],
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
expect(hasGoogleVertexAuthorizedUserAdcSync()).toBe(true);
|
||||
|
||||
const streamFn = createGoogleVertexTransportStreamFn();
|
||||
const stream = await Promise.resolve(
|
||||
streamFn(
|
||||
buildGoogleVertexModel(),
|
||||
{
|
||||
messages: [{ role: "user", content: "hello", timestamp: 0 }],
|
||||
} as Parameters<typeof streamFn>[1],
|
||||
{
|
||||
apiKey: "gcp-vertex-credentials",
|
||||
fetch: tokenFetchMock,
|
||||
} as Parameters<typeof streamFn>[2],
|
||||
),
|
||||
);
|
||||
await stream.result();
|
||||
|
||||
expect(tokenFetchMock).toHaveBeenCalledWith(
|
||||
"https://oauth2.googleapis.com/token",
|
||||
expect.objectContaining({
|
||||
body: expect.objectContaining({
|
||||
get: expect.any(Function),
|
||||
}),
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
const requestBody = tokenFetchMock.mock.calls[0]?.[1]?.body as URLSearchParams | undefined;
|
||||
expect(requestBody?.get("refresh_token")).toBe("appdata-refresh-token");
|
||||
expect(guardedFetchMock).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
Authorization: "Bearer ya29.appdata-token",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("coerces replayed malformed tool-call args to an object for Google payloads", () => {
|
||||
const params = buildGoogleGenerativeAiParams(buildGeminiModel(), {
|
||||
messages: [
|
||||
|
||||
@@ -40,8 +40,21 @@ function resolveGoogleApplicationCredentialsPath(
|
||||
return existsSync(explicit) ? explicit : undefined;
|
||||
}
|
||||
const homeDir = normalizeOptionalString(env.HOME) ?? os.homedir();
|
||||
const fallback = path.join(homeDir, ".config", "gcloud", "application_default_credentials.json");
|
||||
return existsSync(fallback) ? fallback : undefined;
|
||||
const homeFallback = path.join(
|
||||
homeDir,
|
||||
".config",
|
||||
"gcloud",
|
||||
"application_default_credentials.json",
|
||||
);
|
||||
if (existsSync(homeFallback)) {
|
||||
return homeFallback;
|
||||
}
|
||||
const appDataDir = normalizeOptionalString(env.APPDATA);
|
||||
if (!appDataDir) {
|
||||
return undefined;
|
||||
}
|
||||
const appDataFallback = path.join(appDataDir, "gcloud", "application_default_credentials.json");
|
||||
return existsSync(appDataFallback) ? appDataFallback : undefined;
|
||||
}
|
||||
|
||||
async function readGoogleAuthorizedUserCredentials(
|
||||
|
||||
Reference in New Issue
Block a user