



















11// Google provider module implements model/runtime integration.
2+import fs from "node:fs";
23import type {
34OpenClawPluginApi,
45ProviderAuthContext,
@@ -7,11 +8,15 @@ import type {
78import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth-result";
89import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
910import { fetchGeminiUsage } from "openclaw/plugin-sdk/provider-usage";
11+import {
12+GOOGLE_GEMINI_CLI_PROVIDER_ID,
13+resolveGeminiCliProfileCredentialsPath,
14+} from "./gemini-cli-auth-home.js";
1015import { formatGoogleOauthApiKey, parseGoogleUsageToken } from "./oauth-token-shared.js";
1116import { GOOGLE_GEMINI_PROVIDER_HOOKS } from "./provider-hooks.js";
1217import { isModernGoogleModel, resolveGoogleGeminiForwardCompatModel } from "./provider-models.js";
131814-const PROVIDER_ID = "google-gemini-cli";
19+const PROVIDER_ID = GOOGLE_GEMINI_CLI_PROVIDER_ID;
1520const PROVIDER_LABEL = "Gemini CLI OAuth";
1621const DEFAULT_MODEL = "google/gemini-3.1-pro-preview";
1722const ENV_VARS = [
@@ -22,6 +27,9 @@ const ENV_VARS = [
2227] as const;
23282429let oauthRuntimeModulePromise: Promise<typeof import("./oauth.runtime.js")> | null = null;
30+type GeminiCliExternalAuthContext = Parameters<
31+NonNullable<ProviderPlugin["resolveExternalAuthProfiles"]>
32+>[0];
25332634const loadOauthRuntimeModule = async () => {
2735oauthRuntimeModulePromise ??= import("./oauth.runtime.js");
@@ -32,6 +40,76 @@ async function fetchGeminiCliUsage(ctx: ProviderFetchUsageSnapshotContext) {
3240return await fetchGeminiUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn, PROVIDER_ID);
3341}
344243+function normalizeString(value: string | undefined): string | undefined {
44+const trimmed = value?.trim();
45+return trimmed ? trimmed : undefined;
46+}
47+48+function decodeJwtPayload(token: string): Record<string, unknown> {
49+const payload = token.split(".")[1];
50+if (!payload) {
51+return {};
52+}
53+try {
54+const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as unknown;
55+return parsed && typeof parsed === "object" && !Array.isArray(parsed)
56+ ? (parsed as Record<string, unknown>)
57+ : {};
58+} catch {
59+return {};
60+}
61+}
62+63+function readGeminiCliProfileCredential(agentDir: string, profileId: string) {
64+const credentialsPath = resolveGeminiCliProfileCredentialsPath(agentDir, profileId);
65+let raw: unknown;
66+try {
67+raw = JSON.parse(fs.readFileSync(credentialsPath, "utf8")) as unknown;
68+} catch {
69+return null;
70+}
71+if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
72+return null;
73+}
74+const data = raw as Record<string, unknown>;
75+const access = normalizeString(typeof data.access_token === "string" ? data.access_token : "");
76+const refresh = normalizeString(typeof data.refresh_token === "string" ? data.refresh_token : "");
77+const expires = data.expiry_date;
78+if (!access || !refresh || typeof expires !== "number" || !Number.isFinite(expires)) {
79+return null;
80+}
81+82+const idToken = normalizeString(typeof data.id_token === "string" ? data.id_token : "");
83+const identity = idToken ? decodeJwtPayload(idToken) : {};
84+const email = normalizeString(typeof identity.email === "string" ? identity.email : "");
85+const accountId = normalizeString(typeof identity.sub === "string" ? identity.sub : "");
86+return {
87+type: "oauth" as const,
88+provider: PROVIDER_ID,
89+ access,
90+ refresh,
91+ expires,
92+ ...(idToken ? { idToken } : {}),
93+ ...(email ? { email } : {}),
94+ ...(accountId ? { accountId } : {}),
95+};
96+}
97+98+function resolveConfiguredGeminiCliOAuthProfileIds(ctx: GeminiCliExternalAuthContext): string[] {
99+const profileIds = new Set<string>();
100+for (const [profileId, profile] of Object.entries(ctx.config?.auth?.profiles ?? {})) {
101+if (profile.provider === PROVIDER_ID && profile.mode === "oauth") {
102+profileIds.add(profileId);
103+}
104+}
105+for (const [profileId, credential] of Object.entries(ctx.store.profiles)) {
106+if (credential.provider === PROVIDER_ID && credential.type === "oauth") {
107+profileIds.add(profileId);
108+}
109+}
110+return [...profileIds].toSorted();
111+}
112+35113export function buildGoogleGeminiCliProvider(): ProviderPlugin {
36114return {
37115id: PROVIDER_ID,
@@ -126,6 +204,16 @@ export function buildGoogleGeminiCliProvider(): ProviderPlugin {
126204providerId: PROVIDER_ID,
127205 ctx,
128206}),
207+resolveExternalAuthProfiles: (ctx) => {
208+const agentDir = normalizeString(ctx.agentDir);
209+if (!agentDir) {
210+return [];
211+}
212+return resolveConfiguredGeminiCliOAuthProfileIds(ctx).flatMap((profileId) => {
213+const credential = readGeminiCliProfileCredential(agentDir, profileId);
214+return credential ? [{ profileId, credential, persistence: "runtime-only" as const }] : [];
215+});
216+},
129217 ...GOOGLE_GEMINI_PROVIDER_HOOKS,
130218isModernModelRef: ({ modelId }) => isModernGoogleModel(modelId),
131219formatApiKey: (cred) => formatGoogleOauthApiKey(cred),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。