mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 15:08:25 +00:00
refactor(src): split oversized modules
This commit is contained in:
224
src/agents/auth-profiles/oauth.ts
Normal file
224
src/agents/auth-profiles/oauth.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import {
|
||||
getOAuthApiKey,
|
||||
type OAuthCredentials,
|
||||
type OAuthProvider,
|
||||
} from "@mariozechner/pi-ai";
|
||||
import lockfile from "proper-lockfile";
|
||||
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { refreshChutesTokens } from "../chutes-oauth.js";
|
||||
import { writeClaudeCliCredentials } from "../cli-credentials.js";
|
||||
import { AUTH_STORE_LOCK_OPTIONS, CLAUDE_CLI_PROFILE_ID } from "./constants.js";
|
||||
import { formatAuthDoctorHint } from "./doctor.js";
|
||||
import { ensureAuthStoreFile, resolveAuthStorePath } from "./paths.js";
|
||||
import { suggestOAuthProfileIdForLegacyDefault } from "./repair.js";
|
||||
import { ensureAuthProfileStore, saveAuthProfileStore } from "./store.js";
|
||||
import type { AuthProfileStore } from "./types.js";
|
||||
|
||||
function buildOAuthApiKey(
|
||||
provider: string,
|
||||
credentials: OAuthCredentials,
|
||||
): string {
|
||||
const needsProjectId =
|
||||
provider === "google-gemini-cli" || provider === "google-antigravity";
|
||||
return needsProjectId
|
||||
? JSON.stringify({
|
||||
token: credentials.access,
|
||||
projectId: credentials.projectId,
|
||||
})
|
||||
: credentials.access;
|
||||
}
|
||||
|
||||
async function refreshOAuthTokenWithLock(params: {
|
||||
profileId: string;
|
||||
agentDir?: string;
|
||||
}): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {
|
||||
const authPath = resolveAuthStorePath(params.agentDir);
|
||||
ensureAuthStoreFile(authPath);
|
||||
|
||||
let release: (() => Promise<void>) | undefined;
|
||||
try {
|
||||
release = await lockfile.lock(authPath, {
|
||||
...AUTH_STORE_LOCK_OPTIONS,
|
||||
});
|
||||
|
||||
const store = ensureAuthProfileStore(params.agentDir);
|
||||
const cred = store.profiles[params.profileId];
|
||||
if (!cred || cred.type !== "oauth") return null;
|
||||
|
||||
if (Date.now() < cred.expires) {
|
||||
return {
|
||||
apiKey: buildOAuthApiKey(cred.provider, cred),
|
||||
newCredentials: cred,
|
||||
};
|
||||
}
|
||||
|
||||
const oauthCreds: Record<string, OAuthCredentials> = {
|
||||
[cred.provider]: cred,
|
||||
};
|
||||
|
||||
const result =
|
||||
String(cred.provider) === "chutes"
|
||||
? await (async () => {
|
||||
const newCredentials = await refreshChutesTokens({
|
||||
credential: cred,
|
||||
});
|
||||
return { apiKey: newCredentials.access, newCredentials };
|
||||
})()
|
||||
: await getOAuthApiKey(cred.provider as OAuthProvider, oauthCreds);
|
||||
if (!result) return null;
|
||||
store.profiles[params.profileId] = {
|
||||
...cred,
|
||||
...result.newCredentials,
|
||||
type: "oauth",
|
||||
};
|
||||
saveAuthProfileStore(store, params.agentDir);
|
||||
|
||||
// Sync refreshed credentials back to Claude CLI if this is the claude-cli profile
|
||||
// This ensures Claude Code continues to work after ClawdBot refreshes the token
|
||||
if (
|
||||
params.profileId === CLAUDE_CLI_PROFILE_ID &&
|
||||
cred.provider === "anthropic"
|
||||
) {
|
||||
writeClaudeCliCredentials(result.newCredentials);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
if (release) {
|
||||
try {
|
||||
await release();
|
||||
} catch {
|
||||
// ignore unlock errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function tryResolveOAuthProfile(params: {
|
||||
cfg?: ClawdbotConfig;
|
||||
store: AuthProfileStore;
|
||||
profileId: string;
|
||||
agentDir?: string;
|
||||
}): Promise<{ apiKey: string; provider: string; email?: string } | null> {
|
||||
const { cfg, store, profileId } = params;
|
||||
const cred = store.profiles[profileId];
|
||||
if (!cred || cred.type !== "oauth") return null;
|
||||
const profileConfig = cfg?.auth?.profiles?.[profileId];
|
||||
if (profileConfig && profileConfig.provider !== cred.provider) return null;
|
||||
if (profileConfig && profileConfig.mode !== cred.type) return null;
|
||||
|
||||
if (Date.now() < cred.expires) {
|
||||
return {
|
||||
apiKey: buildOAuthApiKey(cred.provider, cred),
|
||||
provider: cred.provider,
|
||||
email: cred.email,
|
||||
};
|
||||
}
|
||||
|
||||
const refreshed = await refreshOAuthTokenWithLock({
|
||||
profileId,
|
||||
agentDir: params.agentDir,
|
||||
});
|
||||
if (!refreshed) return null;
|
||||
return {
|
||||
apiKey: refreshed.apiKey,
|
||||
provider: cred.provider,
|
||||
email: cred.email,
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveApiKeyForProfile(params: {
|
||||
cfg?: ClawdbotConfig;
|
||||
store: AuthProfileStore;
|
||||
profileId: string;
|
||||
agentDir?: string;
|
||||
}): Promise<{ apiKey: string; provider: string; email?: string } | null> {
|
||||
const { cfg, store, profileId } = params;
|
||||
const cred = store.profiles[profileId];
|
||||
if (!cred) return null;
|
||||
const profileConfig = cfg?.auth?.profiles?.[profileId];
|
||||
if (profileConfig && profileConfig.provider !== cred.provider) return null;
|
||||
if (profileConfig && profileConfig.mode !== cred.type) {
|
||||
// Compatibility: treat "oauth" config as compatible with stored token profiles.
|
||||
if (!(profileConfig.mode === "oauth" && cred.type === "token")) return null;
|
||||
}
|
||||
|
||||
if (cred.type === "api_key") {
|
||||
return { apiKey: cred.key, provider: cred.provider, email: cred.email };
|
||||
}
|
||||
if (cred.type === "token") {
|
||||
const token = cred.token?.trim();
|
||||
if (!token) return null;
|
||||
if (
|
||||
typeof cred.expires === "number" &&
|
||||
Number.isFinite(cred.expires) &&
|
||||
cred.expires > 0 &&
|
||||
Date.now() >= cred.expires
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return { apiKey: token, provider: cred.provider, email: cred.email };
|
||||
}
|
||||
if (Date.now() < cred.expires) {
|
||||
return {
|
||||
apiKey: buildOAuthApiKey(cred.provider, cred),
|
||||
provider: cred.provider,
|
||||
email: cred.email,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await refreshOAuthTokenWithLock({
|
||||
profileId,
|
||||
agentDir: params.agentDir,
|
||||
});
|
||||
if (!result) return null;
|
||||
return {
|
||||
apiKey: result.apiKey,
|
||||
provider: cred.provider,
|
||||
email: cred.email,
|
||||
};
|
||||
} catch (error) {
|
||||
const refreshedStore = ensureAuthProfileStore(params.agentDir);
|
||||
const refreshed = refreshedStore.profiles[profileId];
|
||||
if (refreshed?.type === "oauth" && Date.now() < refreshed.expires) {
|
||||
return {
|
||||
apiKey: buildOAuthApiKey(refreshed.provider, refreshed),
|
||||
provider: refreshed.provider,
|
||||
email: refreshed.email ?? cred.email,
|
||||
};
|
||||
}
|
||||
const fallbackProfileId = suggestOAuthProfileIdForLegacyDefault({
|
||||
cfg,
|
||||
store: refreshedStore,
|
||||
provider: cred.provider,
|
||||
legacyProfileId: profileId,
|
||||
});
|
||||
if (fallbackProfileId && fallbackProfileId !== profileId) {
|
||||
try {
|
||||
const fallbackResolved = await tryResolveOAuthProfile({
|
||||
cfg,
|
||||
store: refreshedStore,
|
||||
profileId: fallbackProfileId,
|
||||
agentDir: params.agentDir,
|
||||
});
|
||||
if (fallbackResolved) return fallbackResolved;
|
||||
} catch {
|
||||
// keep original error
|
||||
}
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const hint = formatAuthDoctorHint({
|
||||
cfg,
|
||||
store: refreshedStore,
|
||||
provider: cred.provider,
|
||||
profileId,
|
||||
});
|
||||
throw new Error(
|
||||
`OAuth token refresh failed for ${cred.provider}: ${message}. ` +
|
||||
"Please try again or re-authenticate." +
|
||||
(hint ? `\n\n${hint}` : ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user