feat: multi-agent routing + multi-account providers

This commit is contained in:
Peter Steinberger
2026-01-06 18:25:37 +00:00
parent 50d4b17417
commit dbfa316d19
129 changed files with 3760 additions and 1126 deletions

View File

@@ -49,14 +49,14 @@ export type AuthProfileStore = {
type LegacyAuthStore = Record<string, AuthProfileCredential>;
function resolveAuthStorePath(): string {
const agentDir = resolveClawdbotAgentDir();
return path.join(agentDir, AUTH_PROFILE_FILENAME);
function resolveAuthStorePath(agentDir?: string): string {
const resolved = resolveUserPath(agentDir ?? resolveClawdbotAgentDir());
return path.join(resolved, AUTH_PROFILE_FILENAME);
}
function resolveLegacyAuthStorePath(): string {
const agentDir = resolveClawdbotAgentDir();
return path.join(agentDir, LEGACY_AUTH_FILENAME);
function resolveLegacyAuthStorePath(agentDir?: string): string {
const resolved = resolveUserPath(agentDir ?? resolveClawdbotAgentDir());
return path.join(resolved, LEGACY_AUTH_FILENAME);
}
function loadJsonFile(pathname: string): unknown {
@@ -104,8 +104,9 @@ function buildOAuthApiKey(
async function refreshOAuthTokenWithLock(params: {
profileId: string;
provider: OAuthProvider;
agentDir?: string;
}): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {
const authPath = resolveAuthStorePath();
const authPath = resolveAuthStorePath(params.agentDir);
ensureAuthStoreFile(authPath);
let release: (() => Promise<void>) | undefined;
@@ -121,7 +122,7 @@ async function refreshOAuthTokenWithLock(params: {
stale: 30_000,
});
const store = ensureAuthProfileStore();
const store = ensureAuthProfileStore(params.agentDir);
const cred = store.profiles[params.profileId];
if (!cred || cred.type !== "oauth") return null;
@@ -142,7 +143,7 @@ async function refreshOAuthTokenWithLock(params: {
...result.newCredentials,
type: "oauth",
};
saveAuthProfileStore(store);
saveAuthProfileStore(store, params.agentDir);
return result;
} finally {
if (release) {
@@ -261,13 +262,13 @@ export function loadAuthProfileStore(): AuthProfileStore {
return { version: AUTH_STORE_VERSION, profiles: {} };
}
export function ensureAuthProfileStore(): AuthProfileStore {
const authPath = resolveAuthStorePath();
export function ensureAuthProfileStore(agentDir?: string): AuthProfileStore {
const authPath = resolveAuthStorePath(agentDir);
const raw = loadJsonFile(authPath);
const asStore = coerceAuthStore(raw);
if (asStore) return asStore;
const legacyRaw = loadJsonFile(resolveLegacyAuthStorePath());
const legacyRaw = loadJsonFile(resolveLegacyAuthStorePath(agentDir));
const legacy = coerceLegacyStore(legacyRaw);
const store: AuthProfileStore = {
version: AUTH_STORE_VERSION,
@@ -307,8 +308,11 @@ export function ensureAuthProfileStore(): AuthProfileStore {
return store;
}
export function saveAuthProfileStore(store: AuthProfileStore): void {
const authPath = resolveAuthStorePath();
export function saveAuthProfileStore(
store: AuthProfileStore,
agentDir?: string,
): void {
const authPath = resolveAuthStorePath(agentDir);
const payload = {
version: AUTH_STORE_VERSION,
profiles: store.profiles,
@@ -321,10 +325,11 @@ export function saveAuthProfileStore(store: AuthProfileStore): void {
export function upsertAuthProfile(params: {
profileId: string;
credential: AuthProfileCredential;
agentDir?: string;
}): void {
const store = ensureAuthProfileStore();
const store = ensureAuthProfileStore(params.agentDir);
store.profiles[params.profileId] = params.credential;
saveAuthProfileStore(store);
saveAuthProfileStore(store, params.agentDir);
}
export function listProfilesForProvider(
@@ -354,8 +359,9 @@ export function isProfileInCooldown(
export function markAuthProfileUsed(params: {
store: AuthProfileStore;
profileId: string;
agentDir?: string;
}): void {
const { store, profileId } = params;
const { store, profileId, agentDir } = params;
if (!store.profiles[profileId]) return;
store.usageStats = store.usageStats ?? {};
@@ -365,7 +371,7 @@ export function markAuthProfileUsed(params: {
errorCount: 0,
cooldownUntil: undefined,
};
saveAuthProfileStore(store);
saveAuthProfileStore(store, agentDir);
}
export function calculateAuthProfileCooldownMs(errorCount: number): number {
@@ -383,8 +389,9 @@ export function calculateAuthProfileCooldownMs(errorCount: number): number {
export function markAuthProfileCooldown(params: {
store: AuthProfileStore;
profileId: string;
agentDir?: string;
}): void {
const { store, profileId } = params;
const { store, profileId, agentDir } = params;
if (!store.profiles[profileId]) return;
store.usageStats = store.usageStats ?? {};
@@ -399,7 +406,7 @@ export function markAuthProfileCooldown(params: {
errorCount,
cooldownUntil: Date.now() + backoffMs,
};
saveAuthProfileStore(store);
saveAuthProfileStore(store, agentDir);
}
/**
@@ -408,8 +415,9 @@ export function markAuthProfileCooldown(params: {
export function clearAuthProfileCooldown(params: {
store: AuthProfileStore;
profileId: string;
agentDir?: string;
}): void {
const { store, profileId } = params;
const { store, profileId, agentDir } = params;
if (!store.usageStats?.[profileId]) return;
store.usageStats[profileId] = {
@@ -417,7 +425,7 @@ export function clearAuthProfileCooldown(params: {
errorCount: 0,
cooldownUntil: undefined,
};
saveAuthProfileStore(store);
saveAuthProfileStore(store, agentDir);
}
export function resolveAuthProfileOrder(params: {
@@ -527,6 +535,7 @@ 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];
@@ -550,6 +559,7 @@ export async function resolveApiKeyForProfile(params: {
const result = await refreshOAuthTokenWithLock({
profileId,
provider: cred.provider,
agentDir: params.agentDir,
});
if (!result) return null;
return {
@@ -558,7 +568,7 @@ export async function resolveApiKeyForProfile(params: {
email: cred.email,
};
} catch (error) {
const refreshedStore = ensureAuthProfileStore();
const refreshedStore = ensureAuthProfileStore(params.agentDir);
const refreshed = refreshedStore.profiles[profileId];
if (refreshed?.type === "oauth" && Date.now() < refreshed.expires) {
return {
@@ -579,12 +589,13 @@ export function markAuthProfileGood(params: {
store: AuthProfileStore;
provider: string;
profileId: string;
agentDir?: string;
}): void {
const { store, provider, profileId } = params;
const { store, provider, profileId, agentDir } = params;
const profile = store.profiles[profileId];
if (!profile || profile.provider !== provider) return;
store.lastGood = { ...store.lastGood, [provider]: profileId };
saveAuthProfileStore(store);
saveAuthProfileStore(store, agentDir);
}
export function resolveAuthStorePathForDisplay(): string {