revert: restore full config overwrite + Common Config Snippet (revert 992dda5c)

Revert the partial key-field merging refactoring introduced in 992dda5c,
along with two dependent commits (24fa8a18, 87604b18) that referenced
the now-removed ClaudeQuickToggles component.

The whitelist-based partial merge approach had critical issues:
- Non-whitelisted custom fields were lost during provider switching
- Backfill permanently stripped non-key fields from the database
- Whitelist required constant maintenance to track upstream changes

This restores the proven "full config overwrite + Common Config Snippet"
architecture where each provider stores its complete configuration and
shared settings are managed via a separate snippet mechanism.

Reverted commits:
- 24fa8a18: context-aware JSON editor hint + hide quick toggles
- 87604b18: hide ClaudeQuickToggles when creating
- 992dda5c: partial key-field merging refactoring

Restored:
- Full config snapshot write (write_live_snapshot) for Claude/Codex/Gemini
- Full config backfill (settings_config = live_config)
- Common Config Snippet UI and backend commands
- 6 frontend components/hooks for common config editing
- configApi barrel export and DB snippet methods

Removed:
- ClaudeQuickToggles component
- write_live_partial / backfill_key_fields / patch_claude_live
- All KEY_FIELDS constants
This commit is contained in:
Jason
2026-02-27 23:12:34 +08:00
parent b2b20dadd7
commit 859f413756
39 changed files with 3285 additions and 857 deletions
+94 -108
View File
@@ -14,7 +14,6 @@ import type {
ProviderTestConfig,
ProviderProxyConfig,
ClaudeApiFormat,
ClaudeApiKeyField,
} from "@/types";
import {
providerPresets,
@@ -47,9 +46,9 @@ import {
import { mergeProviderMeta } from "@/utils/providerMetaUtils";
import { getCodexCustomTemplate } from "@/config/codexTemplates";
import CodexConfigEditor from "./CodexConfigEditor";
import { CommonConfigEditor } from "./CommonConfigEditor";
import GeminiConfigEditor from "./GeminiConfigEditor";
import JsonEditor from "@/components/JsonEditor";
import { ClaudeQuickToggles, jsonMergePatch } from "./ClaudeQuickToggles";
import { Label } from "@/components/ui/label";
import { ProviderPresetSelector } from "./ProviderPresetSelector";
import { BasicFormFields } from "./BasicFormFields";
@@ -70,9 +69,12 @@ import {
useCodexConfigState,
useApiKeyLink,
useTemplateValues,
useCommonConfigSnippet,
useCodexCommonConfig,
useSpeedTestEndpoints,
useCodexTomlValidation,
useGeminiConfigState,
useGeminiCommonConfig,
useOmoModelSource,
useOpencodeFormState,
useOmoDraftState,
@@ -116,7 +118,6 @@ interface ProviderFormProps {
iconColor?: string;
};
showButtons?: boolean;
isCurrent?: boolean;
}
export function ProviderForm({
@@ -129,7 +130,6 @@ export function ProviderForm({
onManageUniversalProviders,
initialData,
showButtons = true,
isCurrent = false,
}: ProviderFormProps) {
const { t } = useTranslation();
const isEditMode = Boolean(initialData);
@@ -237,55 +237,6 @@ export function ProviderForm({
mode: "onSubmit",
});
const [localApiFormat, setLocalApiFormat] = useState<ClaudeApiFormat>(() => {
if (appId !== "claude") return "anthropic";
return initialData?.meta?.apiFormat ?? "anthropic";
});
const handleApiFormatChange = useCallback((format: ClaudeApiFormat) => {
setLocalApiFormat(format);
}, []);
const [localApiKeyField, setLocalApiKeyField] = useState<ClaudeApiKeyField>(
() => {
if (appId !== "claude") return "ANTHROPIC_AUTH_TOKEN";
if (initialData?.meta?.apiKeyField) return initialData.meta.apiKeyField;
try {
const config = initialData?.settingsConfig;
if (
config?.env &&
(config.env as Record<string, unknown>).ANTHROPIC_API_KEY !==
undefined
)
return "ANTHROPIC_API_KEY";
} catch {}
return "ANTHROPIC_AUTH_TOKEN";
},
);
const handleApiKeyFieldChange = useCallback(
(field: ClaudeApiKeyField) => {
setLocalApiKeyField(field);
try {
const config = JSON.parse(form.getValues("settingsConfig") || "{}") as {
env?: Record<string, unknown>;
};
const env = (config.env ?? {}) as Record<string, unknown>;
const oldField =
field === "ANTHROPIC_API_KEY"
? "ANTHROPIC_AUTH_TOKEN"
: "ANTHROPIC_API_KEY";
if (oldField in env) {
env[field] = env[oldField];
delete env[oldField];
config.env = env;
form.setValue("settingsConfig", JSON.stringify(config, null, 2));
}
} catch {}
},
[form],
);
const {
apiKey,
handleApiKeyChange,
@@ -296,7 +247,6 @@ export function ProviderForm({
selectedPresetId,
category,
appType: appId,
apiKeyField: appId === "claude" ? localApiKeyField : undefined,
});
const { baseUrl, handleClaudeBaseUrlChange } = useBaseUrlState({
@@ -320,6 +270,15 @@ export function ProviderForm({
onConfigChange: (config) => form.setValue("settingsConfig", config),
});
const [localApiFormat, setLocalApiFormat] = useState<ClaudeApiFormat>(() => {
if (appId !== "claude") return "anthropic";
return initialData?.meta?.apiFormat ?? "anthropic";
});
const handleApiFormatChange = useCallback((format: ClaudeApiFormat) => {
setLocalApiFormat(format);
}, []);
const {
codexAuth,
codexConfig,
@@ -417,6 +376,37 @@ export function ProviderForm({
onConfigChange: (config) => form.setValue("settingsConfig", config),
});
const {
useCommonConfig,
commonConfigSnippet,
commonConfigError,
handleCommonConfigToggle,
handleCommonConfigSnippetChange,
isExtracting: isClaudeExtracting,
handleExtract: handleClaudeExtract,
} = useCommonConfigSnippet({
settingsConfig: form.getValues("settingsConfig"),
onConfigChange: (config) => form.setValue("settingsConfig", config),
initialData: appId === "claude" ? initialData : undefined,
selectedPresetId: selectedPresetId ?? undefined,
enabled: appId === "claude",
});
const {
useCommonConfig: useCodexCommonConfigFlag,
commonConfigSnippet: codexCommonConfigSnippet,
commonConfigError: codexCommonConfigError,
handleCommonConfigToggle: handleCodexCommonConfigToggle,
handleCommonConfigSnippetChange: handleCodexCommonConfigSnippetChange,
isExtracting: isCodexExtracting,
handleExtract: handleCodexExtract,
} = useCodexCommonConfig({
codexConfig,
onConfigChange: handleCodexConfigChange,
initialData: appId === "codex" ? initialData : undefined,
selectedPresetId: selectedPresetId ?? undefined,
});
const {
geminiEnv,
geminiConfig,
@@ -432,6 +422,7 @@ export function ProviderForm({
handleGeminiConfigChange,
resetGeminiConfig,
envStringToObj,
envObjToString,
} = useGeminiConfigState({
initialData: appId === "gemini" ? initialData : undefined,
});
@@ -482,6 +473,23 @@ export function ProviderForm({
[originalHandleGeminiModelChange, updateGeminiEnvField],
);
const {
useCommonConfig: useGeminiCommonConfigFlag,
commonConfigSnippet: geminiCommonConfigSnippet,
commonConfigError: geminiCommonConfigError,
handleCommonConfigToggle: handleGeminiCommonConfigToggle,
handleCommonConfigSnippetChange: handleGeminiCommonConfigSnippetChange,
isExtracting: isGeminiExtracting,
handleExtract: handleGeminiExtract,
} = useGeminiCommonConfig({
envValue: geminiEnv,
onEnvChange: handleGeminiEnvChange,
envStringToObj,
envObjToString,
initialData: appId === "gemini" ? initialData : undefined,
selectedPresetId: selectedPresetId ?? undefined,
});
// ── Extracted hooks: OpenCode / OMO / OpenClaw ─────────────────────
const {
@@ -520,6 +528,8 @@ export function ProviderForm({
getSettingsConfig: () => form.getValues("settingsConfig"),
});
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
const handleSubmit = (values: ProviderFormData) => {
if (appId === "claude" && templateValueEntries.length > 0) {
const validation = validateTemplateValues();
@@ -813,10 +823,6 @@ export function ProviderForm({
appId === "claude" && category !== "official"
? localApiFormat
: undefined,
apiKeyField:
appId === "claude" && category !== "official"
? localApiKeyField
: undefined,
};
onSubmit(payload);
@@ -1055,12 +1061,6 @@ export function ProviderForm({
setLocalApiFormat("anthropic");
}
if (preset.apiKeyField) {
setLocalApiKeyField(preset.apiKeyField);
} else {
setLocalApiKeyField("ANTHROPIC_AUTH_TOKEN");
}
form.reset({
name: preset.name,
websiteUrl: preset.websiteUrl ?? "",
@@ -1266,8 +1266,6 @@ export function ProviderForm({
speedTestEndpoints={speedTestEndpoints}
apiFormat={localApiFormat}
onApiFormatChange={handleApiFormatChange}
apiKeyField={localApiKeyField}
onApiKeyFieldChange={handleApiKeyFieldChange}
/>
)}
@@ -1394,8 +1392,15 @@ export function ProviderForm({
configValue={codexConfig}
onAuthChange={setCodexAuth}
onConfigChange={handleCodexConfigChange}
useCommonConfig={useCodexCommonConfigFlag}
onCommonConfigToggle={handleCodexCommonConfigToggle}
commonConfigSnippet={codexCommonConfigSnippet}
onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange}
commonConfigError={codexCommonConfigError}
authError={codexAuthError}
configError={codexConfigError}
onExtract={handleCodexExtract}
isExtracting={isCodexExtracting}
/>
{settingsConfigErrorField}
</>
@@ -1406,8 +1411,17 @@ export function ProviderForm({
configValue={geminiConfig}
onEnvChange={handleGeminiEnvChange}
onConfigChange={handleGeminiConfigChange}
useCommonConfig={useGeminiCommonConfigFlag}
onCommonConfigToggle={handleGeminiCommonConfigToggle}
commonConfigSnippet={geminiCommonConfigSnippet}
onCommonConfigSnippetChange={
handleGeminiCommonConfigSnippetChange
}
commonConfigError={geminiCommonConfigError}
envError={envError}
configError={geminiConfigError}
onExtract={handleGeminiExtract}
isExtracting={isGeminiExtracting}
/>
{settingsConfigErrorField}
</>
@@ -1477,48 +1491,20 @@ export function ProviderForm({
</>
) : (
<>
<div className="space-y-2">
<Label htmlFor="settingsConfig">
{t("claudeConfig.configLabel")}
</Label>
{isEditMode && isCurrent && (
<ClaudeQuickToggles
onPatchApplied={(patch) => {
try {
const cfg = JSON.parse(
form.getValues("settingsConfig") || "{}",
);
jsonMergePatch(cfg, patch);
form.setValue(
"settingsConfig",
JSON.stringify(cfg, null, 2),
);
} catch {
// invalid JSON in editor — skip mirror
}
}}
/>
)}
<JsonEditor
value={form.watch("settingsConfig")}
onChange={(value) => form.setValue("settingsConfig", value)}
placeholder={`{
"env": {
"ANTHROPIC_AUTH_TOKEN": "your-api-key-here"
}
}`}
rows={14}
showValidation={true}
language="json"
/>
<p className="text-xs text-muted-foreground">
{t(
isCurrent
? "claudeConfig.fullSettingsHint"
: "claudeConfig.fragmentSettingsHint",
)}
</p>
</div>
<CommonConfigEditor
value={form.getValues("settingsConfig")}
onChange={(value) => form.setValue("settingsConfig", value)}
useCommonConfig={useCommonConfig}
onCommonConfigToggle={handleCommonConfigToggle}
commonConfigSnippet={commonConfigSnippet}
onCommonConfigSnippetChange={handleCommonConfigSnippetChange}
commonConfigError={commonConfigError}
onEditClick={() => setIsCommonConfigModalOpen(true)}
isModalOpen={isCommonConfigModalOpen}
onModalClose={() => setIsCommonConfigModalOpen(false)}
onExtract={handleClaudeExtract}
isExtracting={isClaudeExtracting}
/>
{settingsConfigErrorField}
</>
)}