104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
export type LegacyConfigRule = {
|
|
path: string[];
|
|
message: string;
|
|
match?: (value: unknown, root: Record<string, unknown>) => boolean;
|
|
};
|
|
|
|
export type LegacyConfigMigration = {
|
|
id: string;
|
|
describe: string;
|
|
apply: (raw: Record<string, unknown>, changes: string[]) => void;
|
|
};
|
|
|
|
export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
|
|
export const getRecord = (value: unknown): Record<string, unknown> | null =>
|
|
isRecord(value) ? value : null;
|
|
|
|
export const ensureRecord = (
|
|
root: Record<string, unknown>,
|
|
key: string,
|
|
): Record<string, unknown> => {
|
|
const existing = root[key];
|
|
if (isRecord(existing)) return existing;
|
|
const next: Record<string, unknown> = {};
|
|
root[key] = next;
|
|
return next;
|
|
};
|
|
|
|
export const mergeMissing = (target: Record<string, unknown>, source: Record<string, unknown>) => {
|
|
for (const [key, value] of Object.entries(source)) {
|
|
if (value === undefined) continue;
|
|
const existing = target[key];
|
|
if (existing === undefined) {
|
|
target[key] = value;
|
|
continue;
|
|
}
|
|
if (isRecord(existing) && isRecord(value)) {
|
|
mergeMissing(existing, value);
|
|
}
|
|
}
|
|
};
|
|
|
|
const AUDIO_TRANSCRIPTION_CLI_ALLOWLIST = new Set(["whisper"]);
|
|
|
|
export const mapLegacyAudioTranscription = (value: unknown): Record<string, unknown> | null => {
|
|
const transcriber = getRecord(value);
|
|
const command = Array.isArray(transcriber?.command) ? transcriber?.command : null;
|
|
if (!command || command.length === 0) return null;
|
|
const rawExecutable = String(command[0] ?? "").trim();
|
|
if (!rawExecutable) return null;
|
|
const executableName = rawExecutable.split(/[\\/]/).pop() ?? rawExecutable;
|
|
if (!AUDIO_TRANSCRIPTION_CLI_ALLOWLIST.has(executableName)) return null;
|
|
|
|
const args = command.slice(1).map((part) => String(part));
|
|
const timeoutSeconds =
|
|
typeof transcriber?.timeoutSeconds === "number" ? transcriber?.timeoutSeconds : undefined;
|
|
|
|
const result: Record<string, unknown> = { command: rawExecutable, type: "cli" };
|
|
if (args.length > 0) result.args = args;
|
|
if (timeoutSeconds !== undefined) result.timeoutSeconds = timeoutSeconds;
|
|
return result;
|
|
};
|
|
|
|
export const getAgentsList = (agents: Record<string, unknown> | null) => {
|
|
const list = agents?.list;
|
|
return Array.isArray(list) ? list : [];
|
|
};
|
|
|
|
export const resolveDefaultAgentIdFromRaw = (raw: Record<string, unknown>) => {
|
|
const agents = getRecord(raw.agents);
|
|
const list = getAgentsList(agents);
|
|
const defaultEntry = list.find(
|
|
(entry): entry is { id: string } =>
|
|
isRecord(entry) &&
|
|
entry.default === true &&
|
|
typeof entry.id === "string" &&
|
|
entry.id.trim() !== "",
|
|
);
|
|
if (defaultEntry) return defaultEntry.id.trim();
|
|
const routing = getRecord(raw.routing);
|
|
const routingDefault =
|
|
typeof routing?.defaultAgentId === "string" ? routing.defaultAgentId.trim() : "";
|
|
if (routingDefault) return routingDefault;
|
|
const firstEntry = list.find(
|
|
(entry): entry is { id: string } =>
|
|
isRecord(entry) && typeof entry.id === "string" && entry.id.trim() !== "",
|
|
);
|
|
if (firstEntry) return firstEntry.id.trim();
|
|
return "main";
|
|
};
|
|
|
|
export const ensureAgentEntry = (list: unknown[], id: string): Record<string, unknown> => {
|
|
const normalized = id.trim();
|
|
const existing = list.find(
|
|
(entry): entry is Record<string, unknown> =>
|
|
isRecord(entry) && typeof entry.id === "string" && entry.id.trim() === normalized,
|
|
);
|
|
if (existing) return existing;
|
|
const created: Record<string, unknown> = { id: normalized };
|
|
list.push(created);
|
|
return created;
|
|
};
|