refactor(auth): unify external CLI credential sync

This commit is contained in:
Peter Steinberger
2026-03-24 10:01:46 -07:00
parent 27448c3113
commit 8e9e2d2f4e
5 changed files with 401 additions and 216 deletions

View File

@@ -21,7 +21,7 @@ type CachedValue<T> = {
value: T | null;
readAt: number;
cacheKey: string;
sourceMtimeMs?: number | null;
sourceFingerprint?: number | string | null;
};
let claudeCliCache: CachedValue<ClaudeCliCredential> | null = null;
@@ -157,6 +157,45 @@ function readFileMtimeMs(filePath: string): number | null {
}
}
function readCachedCliCredential<T>(options: {
ttlMs: number;
cache: CachedValue<T> | null;
cacheKey: string;
read: () => T | null;
setCache: (next: CachedValue<T> | null) => void;
readSourceFingerprint?: () => number | string | null;
}): T | null {
const { ttlMs, cache, cacheKey, read, setCache, readSourceFingerprint } = options;
if (ttlMs <= 0) {
return read();
}
const now = Date.now();
const sourceFingerprint = readSourceFingerprint?.();
if (
cache &&
cache.cacheKey === cacheKey &&
cache.sourceFingerprint === sourceFingerprint &&
now - cache.readAt < ttlMs
) {
return cache.value;
}
const value = read();
const cachedSourceFingerprint = readSourceFingerprint?.();
if (!readSourceFingerprint || cachedSourceFingerprint === sourceFingerprint) {
setCache({
value,
readAt: now,
cacheKey,
sourceFingerprint: cachedSourceFingerprint,
});
} else {
setCache(null);
}
return value;
}
function computeCodexKeychainAccount(codexHome: string) {
const hash = createHash("sha256").update(codexHome).digest("hex");
return `cli|${hash.slice(0, 16)}`;
@@ -334,27 +373,21 @@ export function readClaudeCliCredentialsCached(options?: {
homeDir?: string;
execSync?: ExecSyncFn;
}): ClaudeCliCredential | null {
const ttlMs = options?.ttlMs ?? 0;
const now = Date.now();
const cacheKey = resolveClaudeCliCredentialsPath(options?.homeDir);
if (
ttlMs > 0 &&
claudeCliCache &&
claudeCliCache.cacheKey === cacheKey &&
now - claudeCliCache.readAt < ttlMs
) {
return claudeCliCache.value;
}
const value = readClaudeCliCredentials({
allowKeychainPrompt: options?.allowKeychainPrompt,
platform: options?.platform,
homeDir: options?.homeDir,
execSync: options?.execSync,
return readCachedCliCredential({
ttlMs: options?.ttlMs ?? 0,
cache: claudeCliCache,
cacheKey: resolveClaudeCliCredentialsPath(options?.homeDir),
read: () =>
readClaudeCliCredentials({
allowKeychainPrompt: options?.allowKeychainPrompt,
platform: options?.platform,
homeDir: options?.homeDir,
execSync: options?.execSync,
}),
setCache: (next) => {
claudeCliCache = next;
},
});
if (ttlMs > 0) {
claudeCliCache = { value, readAt: now, cacheKey };
}
return value;
}
export function writeClaudeCliKeychainCredentials(
@@ -533,78 +566,53 @@ export function readCodexCliCredentialsCached(options?: {
platform?: NodeJS.Platform;
execSync?: ExecSyncFn;
}): CodexCliCredential | null {
const ttlMs = options?.ttlMs ?? 0;
const now = Date.now();
const authPath = resolveCodexCliAuthPath();
const cacheKey = `${options?.platform ?? process.platform}|${authPath}`;
const sourceMtimeMs = readFileMtimeMs(authPath);
if (
ttlMs > 0 &&
codexCliCache &&
codexCliCache.cacheKey === cacheKey &&
codexCliCache.sourceMtimeMs === sourceMtimeMs &&
now - codexCliCache.readAt < ttlMs
) {
return codexCliCache.value;
}
const value = readCodexCliCredentials({
platform: options?.platform,
execSync: options?.execSync,
return readCachedCliCredential({
ttlMs: options?.ttlMs ?? 0,
cache: codexCliCache,
cacheKey: `${options?.platform ?? process.platform}|${authPath}`,
read: () =>
readCodexCliCredentials({
platform: options?.platform,
execSync: options?.execSync,
}),
setCache: (next) => {
codexCliCache = next;
},
readSourceFingerprint: () => readFileMtimeMs(authPath),
});
const cachedSourceMtimeMs = readFileMtimeMs(authPath);
if (ttlMs > 0 && cachedSourceMtimeMs === sourceMtimeMs) {
codexCliCache = {
value,
readAt: now,
cacheKey,
sourceMtimeMs: cachedSourceMtimeMs,
};
} else if (ttlMs > 0) {
codexCliCache = null;
}
return value;
}
export function readQwenCliCredentialsCached(options?: {
ttlMs?: number;
homeDir?: string;
}): QwenCliCredential | null {
const ttlMs = options?.ttlMs ?? 0;
const now = Date.now();
const cacheKey = resolveQwenCliCredentialsPath(options?.homeDir);
if (
ttlMs > 0 &&
qwenCliCache &&
qwenCliCache.cacheKey === cacheKey &&
now - qwenCliCache.readAt < ttlMs
) {
return qwenCliCache.value;
}
const value = readQwenCliCredentials({ homeDir: options?.homeDir });
if (ttlMs > 0) {
qwenCliCache = { value, readAt: now, cacheKey };
}
return value;
const credPath = resolveQwenCliCredentialsPath(options?.homeDir);
return readCachedCliCredential({
ttlMs: options?.ttlMs ?? 0,
cache: qwenCliCache,
cacheKey: credPath,
read: () => readQwenCliCredentials({ homeDir: options?.homeDir }),
setCache: (next) => {
qwenCliCache = next;
},
readSourceFingerprint: () => readFileMtimeMs(credPath),
});
}
export function readMiniMaxCliCredentialsCached(options?: {
ttlMs?: number;
homeDir?: string;
}): MiniMaxCliCredential | null {
const ttlMs = options?.ttlMs ?? 0;
const now = Date.now();
const cacheKey = resolveMiniMaxCliCredentialsPath(options?.homeDir);
if (
ttlMs > 0 &&
minimaxCliCache &&
minimaxCliCache.cacheKey === cacheKey &&
now - minimaxCliCache.readAt < ttlMs
) {
return minimaxCliCache.value;
}
const value = readMiniMaxCliCredentials({ homeDir: options?.homeDir });
if (ttlMs > 0) {
minimaxCliCache = { value, readAt: now, cacheKey };
}
return value;
const credPath = resolveMiniMaxCliCredentialsPath(options?.homeDir);
return readCachedCliCredential({
ttlMs: options?.ttlMs ?? 0,
cache: minimaxCliCache,
cacheKey: credPath,
read: () => readMiniMaxCliCredentials({ homeDir: options?.homeDir }),
setCache: (next) => {
minimaxCliCache = next;
},
readSourceFingerprint: () => readFileMtimeMs(credPath),
});
}