fix(config): preserve api key storage structure

This commit is contained in:
Supra4E8C
2026-03-07 14:15:41 +08:00
Unverified
parent 6da473ed0f
commit 033be261f5
+126 -4
View File
@@ -47,9 +47,100 @@ function parseApiKeysText(raw: unknown): string {
return keys.join('\n');
}
function replaceApiKeyValue(entry: unknown, apiKey: string): unknown {
const record = asRecord(entry);
if (!record) return apiKey;
if ('api-key' in record) return { ...record, 'api-key': apiKey };
if ('apiKey' in record) return { ...record, apiKey };
if ('key' in record) return { ...record, key: apiKey };
if ('Key' in record) return { ...record, Key: apiKey };
return { ...record, 'api-key': apiKey };
}
function buildApiKeyEntries(
apiKeys: string[],
metadata: ApiKeysStorageMetadata
): Array<string | Record<string, unknown>> {
return apiKeys.map((apiKey, index) => {
const originalEntry = metadata.originalEntries[index];
if (metadata.entryMode === 'object') {
const replaced = replaceApiKeyValue(originalEntry, apiKey);
return asRecord(replaced) ?? { 'api-key': apiKey };
}
const record = asRecord(originalEntry);
return record ? ({ ...record, ...(replaceApiKeyValue(record, apiKey) as Record<string, unknown>) }) : apiKey;
});
}
function resolveApiKeysStorage(parsed: Record<string, unknown>): {
text: string;
metadata: ApiKeysStorageMetadata;
} {
const legacyEntries = Array.isArray(parsed['api-keys']) ? parsed['api-keys'] : [];
const auth = asRecord(parsed.auth);
const providers = asRecord(auth?.providers);
const configApiKeyProvider = asRecord(providers?.['config-api-key']);
if (configApiKeyProvider) {
const providerEntries = Array.isArray(configApiKeyProvider['api-key-entries'])
? configApiKeyProvider['api-key-entries']
: Array.isArray(configApiKeyProvider['api-keys'])
? configApiKeyProvider['api-keys']
: [];
const providerListKey = Array.isArray(configApiKeyProvider['api-key-entries'])
? 'api-key-entries'
: 'api-keys';
return {
text: parseApiKeysText(providerEntries),
metadata: {
source: 'auth-provider',
providerListKey,
entryMode:
providerListKey === 'api-key-entries' || providerEntries.some((entry) => Boolean(asRecord(entry)))
? 'object'
: 'string',
originalEntries: providerEntries,
syncLegacy: legacyEntries.length > 0,
},
};
}
return {
text: parseApiKeysText(legacyEntries),
metadata: {
source: 'legacy',
entryMode: legacyEntries.some((entry) => Boolean(asRecord(entry))) ? 'object' : 'string',
originalEntries: legacyEntries,
syncLegacy: false,
},
};
}
type YamlDocument = ReturnType<typeof parseDocument>;
type YamlPath = string[];
type ApiKeysStorageMode = 'legacy' | 'auth-provider';
type ApiKeysEntryMode = 'string' | 'object';
type ApiKeysStorageMetadata = {
source: ApiKeysStorageMode;
providerListKey?: 'api-keys' | 'api-key-entries';
entryMode: ApiKeysEntryMode;
originalEntries: unknown[];
syncLegacy: boolean;
};
const DEFAULT_API_KEYS_STORAGE_METADATA: ApiKeysStorageMetadata = {
source: 'legacy',
entryMode: 'string',
originalEntries: [],
syncLegacy: false,
};
function docHas(doc: YamlDocument, path: YamlPath): boolean {
return doc.hasIn(path);
}
@@ -348,6 +439,8 @@ export function useVisualConfig() {
...DEFAULT_VISUAL_VALUES,
});
const [visualParseError, setVisualParseError] = useState<string | null>(null);
const [apiKeysStorageMetadata, setApiKeysStorageMetadata] =
useState<ApiKeysStorageMetadata>(DEFAULT_API_KEYS_STORAGE_METADATA);
const visualValidationErrors = useMemo(
() => getVisualConfigValidationErrors(visualValues),
[visualValues]
@@ -378,6 +471,7 @@ export function useVisualConfig() {
const routing = asRecord(parsed.routing);
const payload = asRecord(parsed.payload);
const streaming = asRecord(parsed.streaming);
const apiKeysStorage = resolveApiKeysStorage(parsed);
const newValues: VisualConfigValues = {
host: typeof parsed.host === 'string' ? parsed.host : '',
@@ -399,7 +493,7 @@ export function useVisualConfig() {
: '',
authDir: typeof parsed['auth-dir'] === 'string' ? parsed['auth-dir'] : '',
apiKeysText: parseApiKeysText(parsed['api-keys']),
apiKeysText: apiKeysStorage.text,
debug: Boolean(parsed.debug),
commercialMode: Boolean(parsed['commercial-mode']),
@@ -434,6 +528,7 @@ export function useVisualConfig() {
setVisualValuesState(newValues);
setBaselineValues(deepClone(newValues));
setApiKeysStorageMetadata(apiKeysStorage.metadata);
setVisualParseError(null);
return { ok: true as const };
} catch (error: unknown) {
@@ -497,8 +592,35 @@ export function useVisualConfig() {
.split('\n')
.map((key) => key.trim())
.filter(Boolean);
if (apiKeys.length > 0) {
doc.setIn(['api-keys'], apiKeys);
const apiKeyEntries = buildApiKeyEntries(apiKeys, apiKeysStorageMetadata);
if (apiKeysStorageMetadata.source === 'auth-provider') {
ensureMapInDoc(doc, ['auth']);
ensureMapInDoc(doc, ['auth', 'providers']);
ensureMapInDoc(doc, ['auth', 'providers', 'config-api-key']);
const providerListKey = apiKeysStorageMetadata.providerListKey ?? 'api-key-entries';
const providerPath = ['auth', 'providers', 'config-api-key', providerListKey];
if (apiKeys.length > 0) {
doc.setIn(providerPath, apiKeyEntries);
} else if (docHas(doc, providerPath)) {
doc.deleteIn(providerPath);
}
deleteIfMapEmpty(doc, ['auth', 'providers', 'config-api-key']);
deleteIfMapEmpty(doc, ['auth', 'providers']);
deleteIfMapEmpty(doc, ['auth']);
if (apiKeysStorageMetadata.syncLegacy) {
if (apiKeys.length > 0) {
doc.setIn(['api-keys'], apiKeys);
} else if (docHas(doc, ['api-keys'])) {
doc.deleteIn(['api-keys']);
}
}
} else if (apiKeys.length > 0) {
doc.setIn(['api-keys'], apiKeyEntries);
} else if (docHas(doc, ['api-keys'])) {
doc.deleteIn(['api-keys']);
}
@@ -600,7 +722,7 @@ export function useVisualConfig() {
return currentYaml;
}
},
[baselineValues, visualValues]
[apiKeysStorageMetadata, baselineValues, visualValues]
);
const setVisualValues = useCallback((newValues: Partial<VisualConfigValues>) => {