Files
openclaw/src/agents/auth-profiles/state.ts
2026-04-08 00:09:42 +01:00

108 lines
3.2 KiB
TypeScript

import fs from "node:fs";
import { loadJsonFile, saveJsonFile } from "../../infra/json-file.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { AUTH_STORE_VERSION } from "./constants.js";
import { resolveAuthStatePath } from "./paths.js";
import type { AuthProfileState, AuthProfileStateStore, ProfileUsageStats } from "./types.js";
function normalizeAuthProfileOrder(raw: unknown): AuthProfileState["order"] {
if (!raw || typeof raw !== "object") {
return undefined;
}
const normalized = Object.entries(raw as Record<string, unknown>).reduce(
(acc, [provider, value]) => {
if (!Array.isArray(value)) {
return acc;
}
const list = value.map((entry) => normalizeOptionalString(entry) ?? "").filter(Boolean);
if (list.length > 0) {
acc[provider] = list;
}
return acc;
},
{} as Record<string, string[]>,
);
return Object.keys(normalized).length > 0 ? normalized : undefined;
}
export function coerceAuthProfileState(raw: unknown): AuthProfileState {
if (!raw || typeof raw !== "object") {
return {};
}
const record = raw as Record<string, unknown>;
return {
order: normalizeAuthProfileOrder(record.order),
lastGood:
record.lastGood && typeof record.lastGood === "object"
? (record.lastGood as Record<string, string>)
: undefined,
usageStats:
record.usageStats && typeof record.usageStats === "object"
? (record.usageStats as Record<string, ProfileUsageStats>)
: undefined,
};
}
export function mergeAuthProfileState(
base: AuthProfileState,
override: AuthProfileState,
): AuthProfileState {
const mergeRecord = <T>(left?: Record<string, T>, right?: Record<string, T>) => {
if (!left && !right) {
return undefined;
}
if (!left) {
return { ...right };
}
if (!right) {
return { ...left };
}
return { ...left, ...right };
};
return {
order: mergeRecord(base.order, override.order),
lastGood: mergeRecord(base.lastGood, override.lastGood),
usageStats: mergeRecord(base.usageStats, override.usageStats),
};
}
export function loadPersistedAuthProfileState(agentDir?: string): AuthProfileState {
return coerceAuthProfileState(loadJsonFile(resolveAuthStatePath(agentDir)));
}
export function buildPersistedAuthProfileState(
store: AuthProfileState,
): AuthProfileStateStore | null {
const state = coerceAuthProfileState(store);
if (!state.order && !state.lastGood && !state.usageStats) {
return null;
}
return {
version: AUTH_STORE_VERSION,
...(state.order ? { order: state.order } : {}),
...(state.lastGood ? { lastGood: state.lastGood } : {}),
...(state.usageStats ? { usageStats: state.usageStats } : {}),
};
}
export function savePersistedAuthProfileState(
store: AuthProfileState,
agentDir?: string,
): AuthProfileStateStore | null {
const payload = buildPersistedAuthProfileState(store);
const statePath = resolveAuthStatePath(agentDir);
if (!payload) {
try {
fs.unlinkSync(statePath);
} catch (error) {
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") {
throw error;
}
}
return null;
}
saveJsonFile(statePath, payload);
return payload;
}