feat: apply common config as runtime overlay instead of materialized merge

Common config snippets are now dynamically overlaid when writing live
files, rather than being pre-merged into provider snapshots at edit time.
This ensures that updating a snippet immediately takes effect for the
current provider and automatically propagates to other providers on
their next switch.

Key changes:
- Add write_live_with_common_config() overlay pipeline
- Strip common config from live before backfilling provider snapshots
- Normalize provider snapshots on save to keep them snippet-free
- Add explicit commonConfigEnabled flag in ProviderMeta (Option<bool>)
- Migrate legacy providers on snippet save (infer flag from subset check)
- Add Codex TOML snippet validation in set_common_config_snippet
- Stabilize onConfigChange callbacks with useCallback in ProviderForm
This commit is contained in:
Jason
2026-03-08 21:45:20 +08:00
parent fc6f2af4c6
commit 6d078e7f33
10 changed files with 982 additions and 34 deletions
@@ -19,6 +19,7 @@ interface UseGeminiCommonConfigProps {
initialData?: {
settingsConfig?: Record<string, unknown>;
};
initialEnabled?: boolean;
selectedPresetId?: string;
}
@@ -43,6 +44,7 @@ export function useGeminiCommonConfig({
envStringToObj,
envObjToString,
initialData,
initialEnabled,
selectedPresetId,
}: UseGeminiCommonConfigProps) {
const { t } = useTranslation();
@@ -58,11 +60,14 @@ export function useGeminiCommonConfig({
const isUpdatingFromCommonConfig = useRef(false);
// 用于跟踪新建模式是否已初始化默认勾选
const hasInitializedNewMode = useRef(false);
// 用于跟踪编辑模式是否已初始化显式开关/预览
const hasInitializedEditMode = useRef(false);
// 当预设变化时,重置初始化标记,使新预设能够重新触发初始化逻辑
useEffect(() => {
hasInitializedNewMode.current = false;
}, [selectedPresetId]);
hasInitializedEditMode.current = false;
}, [selectedPresetId, initialEnabled]);
const parseSnippetEnv = useCallback(
(
@@ -220,20 +225,46 @@ export function useGeminiCommonConfig({
: {};
const parsed = parseSnippetEnv(commonConfigSnippet);
if (parsed.error) return;
const hasCommon = hasEnvCommonConfigSnippet(
const inferredHasCommon = hasEnvCommonConfigSnippet(
env,
parsed.env as Record<string, string>,
);
const hasCommon = initialEnabled ?? inferredHasCommon;
setUseCommonConfig(hasCommon);
if (
hasCommon &&
!inferredHasCommon &&
!hasInitializedEditMode.current
) {
hasInitializedEditMode.current = true;
const currentEnv = envStringToObj(envValue);
const merged = applySnippetToEnv(currentEnv, parsed.env);
const nextEnvString = envObjToString(merged);
isUpdatingFromCommonConfig.current = true;
onEnvChange(nextEnvString);
setTimeout(() => {
isUpdatingFromCommonConfig.current = false;
}, 0);
} else {
hasInitializedEditMode.current = true;
}
} catch {
// ignore parse error
}
}
}, [
applySnippetToEnv,
commonConfigSnippet,
envObjToString,
envStringToObj,
envValue,
hasEnvCommonConfigSnippet,
initialData,
initialEnabled,
isLoading,
onEnvChange,
parseSnippetEnv,
]);