feat: add credential priority controls and OAuth defaults

This commit is contained in:
lzt404 committed 2026-07-30 01:21:48 +08:00
1 parent 0f3e5093e8
commit bbe22a96eb
13 files changed
+582 -416

No files matched your search

+61
View File
@@ -2,6 +2,7 @@ import { readBoolean, readString } from './managementApi';
import { getCurrentLocale, translate } from '../i18n';
export type AuthFileRecord = Record<string, unknown>;
export type AuthFileSnapshot = Map<string, string>;
export const authFileName = (file: AuthFileRecord) =>
readString(file, 'name') || translate(getCurrentLocale(), 'authFiles.unnamed');
@@ -9,6 +10,66 @@ export const authFileName = (file: AuthFileRecord) =>
export const isRuntimeOnlyAuthFile = (file: AuthFileRecord) =>
readBoolean(file, 'runtime_only', 'runtimeOnly');
export const parseAuthFilePriority = (value: unknown): number | undefined => {
if (typeof value === 'number') {
return Number.isSafeInteger(value) ? value : undefined;
}
if (typeof value !== 'string') return undefined;
const normalized = value.trim();
if (!/^-?\d+$/.test(normalized)) return undefined;
const priority = Number(normalized);
return Number.isSafeInteger(priority) ? priority : undefined;
};
export const normalizeAuthFilePriorityInput = (value: string): number | null => {
const normalized = value.trim();
if (!normalized) return 0;
return parseAuthFilePriority(normalized) ?? null;
};
const normalizeOAuthProvider = (value: string) => {
const provider = value.trim().toLowerCase();
if (provider === 'anthropic') return 'claude';
if (provider === 'openai') return 'codex';
return provider;
};
const authFileProvider = (file: AuthFileRecord) =>
normalizeOAuthProvider(readString(file, 'provider', 'type'));
export const snapshotAuthFiles = (files: AuthFileRecord[]): AuthFileSnapshot => {
const grouped = new Map<string, string[]>();
files.forEach((file) => {
const name = readString(file, 'name');
if (!name) return;
const fingerprints = grouped.get(name) ?? [];
fingerprints.push(JSON.stringify(file));
grouped.set(name, fingerprints);
});
return new Map(Array.from(grouped, ([name, fingerprints]) => [
name,
fingerprints.sort().join('\n'),
]));
};
export const changedOAuthAuthFileNames = (
before: AuthFileSnapshot,
files: AuthFileRecord[],
provider: string,
) => {
const expectedProvider = normalizeOAuthProvider(provider);
const after = snapshotAuthFiles(files);
const names = new Set<string>();
files.forEach((file) => {
const name = readString(file, 'name');
if (!name || authFileProvider(file) !== expectedProvider) return;
const priority = parseAuthFilePriority(file.priority);
if (priority !== undefined && priority !== 0) return;
if (before.get(name) !== after.get(name)) names.add(name);
});
return Array.from(names);
};
const hasMeaningfulValue = (value: unknown) => {
if (value === null || value === undefined) return false;
if (typeof value === 'string') return value.trim().length > 0;
+1 -1
View File
@@ -58,7 +58,7 @@ export const managementApi = {
data,
});
},
downloadAuthFile: (name: string) => invoke<string>('download_auth_file', { name }),
openAuthFilesDirectory: () => invoke<void>('open_auth_files_directory'),
};
export function isRecord(value: unknown): value is Record<string, unknown> {
+6 -41
View File
@@ -540,24 +540,8 @@ export const quotaRowsFor = (provider: QuotaProvider, payload: unknown): QuotaRo
});
};
const resolveProjectId = async (file: AuthFile): Promise<string> => {
const direct = readString(file, 'project_id', 'projectId');
if (direct) return direct;
try {
const content = await managementApi.downloadAuthFile(fileName(file));
const parsed = JSON.parse(content) as Record<string, unknown>;
const direct = readString(parsed, 'project_id', 'projectId');
if (direct) return direct;
for (const container of [parsed.installed, parsed.web]) {
if (!isRecord(container)) continue;
const nested = readString(container, 'project_id', 'projectId');
if (nested) return nested;
}
return '';
} catch {
return '';
}
};
const resolveProjectId = (file: AuthFile): string =>
readString(file, 'project_id', 'projectId');
const decodeJwtPayload = (value: unknown): Record<string, unknown> | null => {
if (isRecord(value)) return value;
@@ -614,18 +598,8 @@ const codexAccountIdFromRecord = (record: Record<string, unknown>): string => {
return '';
};
const resolveCodexAccountId = async (file: AuthFile): Promise<string> => {
const direct = codexAccountIdFromRecord(file);
if (direct) return direct;
try {
const content = await managementApi.downloadAuthFile(fileName(file));
const parsed = JSON.parse(content);
return isRecord(parsed) ? codexAccountIdFromRecord(parsed) : '';
} catch {
return '';
}
};
const resolveCodexAccountId = (file: AuthFile): string =>
codexAccountIdFromRecord(file);
const xaiUserIdFromRecord = (record: Record<string, unknown>): string => {
const nestedRecords = [record, record.metadata, record.attributes]
@@ -642,17 +616,8 @@ const xaiUserIdFromRecord = (record: Record<string, unknown>): string => {
return '';
};
const resolveXaiUserId = async (file: AuthFile): Promise<string> => {
const direct = xaiUserIdFromRecord(file);
if (direct) return direct;
try {
const content = await managementApi.downloadAuthFile(fileName(file));
const parsed = JSON.parse(content);
return isRecord(parsed) ? xaiUserIdFromRecord(parsed) : '';
} catch {
return '';
}
};
const resolveXaiUserId = (file: AuthFile): string =>
xaiUserIdFromRecord(file);
const requestQuotaPayload = async (
authIndex: string,