140 lines
4.6 KiB
TypeScript
140 lines
4.6 KiB
TypeScript
import type { ClawdbotConfig } from "../../config/config.js";
|
|
import { updateSessionStore, type SessionEntry } from "../../config/sessions.js";
|
|
import { normalizeProviderId } from "../model-selection.js";
|
|
import {
|
|
ensureAuthProfileStore,
|
|
isProfileInCooldown,
|
|
resolveAuthProfileOrder,
|
|
} from "../auth-profiles.js";
|
|
|
|
function isProfileForProvider(params: {
|
|
provider: string;
|
|
profileId: string;
|
|
store: ReturnType<typeof ensureAuthProfileStore>;
|
|
}): boolean {
|
|
const entry = params.store.profiles[params.profileId];
|
|
if (!entry?.provider) return false;
|
|
return normalizeProviderId(entry.provider) === normalizeProviderId(params.provider);
|
|
}
|
|
|
|
export async function clearSessionAuthProfileOverride(params: {
|
|
sessionEntry: SessionEntry;
|
|
sessionStore: Record<string, SessionEntry>;
|
|
sessionKey: string;
|
|
storePath?: string;
|
|
}) {
|
|
const { sessionEntry, sessionStore, sessionKey, storePath } = params;
|
|
delete sessionEntry.authProfileOverride;
|
|
delete sessionEntry.authProfileOverrideSource;
|
|
delete sessionEntry.authProfileOverrideCompactionCount;
|
|
sessionEntry.updatedAt = Date.now();
|
|
sessionStore[sessionKey] = sessionEntry;
|
|
if (storePath) {
|
|
await updateSessionStore(storePath, (store) => {
|
|
store[sessionKey] = sessionEntry;
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function resolveSessionAuthProfileOverride(params: {
|
|
cfg: ClawdbotConfig;
|
|
provider: string;
|
|
agentDir: string;
|
|
sessionEntry?: SessionEntry;
|
|
sessionStore?: Record<string, SessionEntry>;
|
|
sessionKey?: string;
|
|
storePath?: string;
|
|
isNewSession: boolean;
|
|
}): Promise<string | undefined> {
|
|
const {
|
|
cfg,
|
|
provider,
|
|
agentDir,
|
|
sessionEntry,
|
|
sessionStore,
|
|
sessionKey,
|
|
storePath,
|
|
isNewSession,
|
|
} = params;
|
|
if (!sessionEntry || !sessionStore || !sessionKey) return sessionEntry?.authProfileOverride;
|
|
|
|
const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
|
const order = resolveAuthProfileOrder({ cfg, store, provider });
|
|
let current = sessionEntry.authProfileOverride?.trim();
|
|
|
|
if (current && !store.profiles[current]) {
|
|
await clearSessionAuthProfileOverride({ sessionEntry, sessionStore, sessionKey, storePath });
|
|
current = undefined;
|
|
}
|
|
|
|
if (current && !isProfileForProvider({ provider, profileId: current, store })) {
|
|
await clearSessionAuthProfileOverride({ sessionEntry, sessionStore, sessionKey, storePath });
|
|
current = undefined;
|
|
}
|
|
|
|
if (current && order.length > 0 && !order.includes(current)) {
|
|
await clearSessionAuthProfileOverride({ sessionEntry, sessionStore, sessionKey, storePath });
|
|
current = undefined;
|
|
}
|
|
|
|
if (order.length === 0) return undefined;
|
|
|
|
const pickFirstAvailable = () =>
|
|
order.find((profileId) => !isProfileInCooldown(store, profileId)) ?? order[0];
|
|
const pickNextAvailable = (active: string) => {
|
|
const startIndex = order.indexOf(active);
|
|
if (startIndex < 0) return pickFirstAvailable();
|
|
for (let offset = 1; offset <= order.length; offset += 1) {
|
|
const candidate = order[(startIndex + offset) % order.length];
|
|
if (!isProfileInCooldown(store, candidate)) return candidate;
|
|
}
|
|
return order[startIndex] ?? order[0];
|
|
};
|
|
|
|
const compactionCount = sessionEntry.compactionCount ?? 0;
|
|
const storedCompaction =
|
|
typeof sessionEntry.authProfileOverrideCompactionCount === "number"
|
|
? sessionEntry.authProfileOverrideCompactionCount
|
|
: compactionCount;
|
|
|
|
const source =
|
|
sessionEntry.authProfileOverrideSource ??
|
|
(typeof sessionEntry.authProfileOverrideCompactionCount === "number"
|
|
? "auto"
|
|
: current
|
|
? "user"
|
|
: undefined);
|
|
if (source === "user" && current && !isNewSession) {
|
|
return current;
|
|
}
|
|
|
|
let next = current;
|
|
if (isNewSession) {
|
|
next = current ? pickNextAvailable(current) : pickFirstAvailable();
|
|
} else if (current && compactionCount > storedCompaction) {
|
|
next = pickNextAvailable(current);
|
|
} else if (!current || isProfileInCooldown(store, current)) {
|
|
next = pickFirstAvailable();
|
|
}
|
|
|
|
if (!next) return current;
|
|
const shouldPersist =
|
|
next !== sessionEntry.authProfileOverride ||
|
|
sessionEntry.authProfileOverrideSource !== "auto" ||
|
|
sessionEntry.authProfileOverrideCompactionCount !== compactionCount;
|
|
if (shouldPersist) {
|
|
sessionEntry.authProfileOverride = next;
|
|
sessionEntry.authProfileOverrideSource = "auto";
|
|
sessionEntry.authProfileOverrideCompactionCount = compactionCount;
|
|
sessionEntry.updatedAt = Date.now();
|
|
sessionStore[sessionKey] = sessionEntry;
|
|
if (storePath) {
|
|
await updateSessionStore(storePath, (store) => {
|
|
store[sessionKey] = sessionEntry;
|
|
});
|
|
}
|
|
}
|
|
|
|
return next;
|
|
}
|