fix: make Codex TOML base_url editing section-aware

Rewrite setCodexBaseUrl/extractCodexBaseUrl to understand TOML section
boundaries, ensuring base_url is written into the correct
[model_providers.<name>] section instead of being appended to file end.

- Add section-aware TOML helpers in providerConfigUtils.ts
- Extract shared update_codex_toml_field/remove_codex_toml_base_url_if
  in codex_config.rs, deduplicate proxy.rs TOML editing logic
- Replace scattered inline base_url regexes with extractCodexBaseUrl()
- Add comprehensive tests for both Rust and TypeScript implementations
This commit is contained in:
Jason
2026-03-12 22:20:59 +08:00
parent 7ca33ff901
commit 3568c98f57
10 changed files with 825 additions and 190 deletions
@@ -17,6 +17,7 @@ import { UniversalProviderPanel } from "@/components/universal";
import { providerPresets } from "@/config/claudeProviderPresets";
import { codexProviderPresets } from "@/config/codexProviderPresets";
import { geminiProviderPresets } from "@/config/geminiProviderPresets";
import { extractCodexBaseUrl } from "@/utils/providerConfigUtils";
import type { OpenClawSuggestedDefaults } from "@/config/openclawProviderPresets";
import type { UniversalProviderPreset } from "@/config/universalProviderPresets";
@@ -179,11 +180,9 @@ export function AddProviderDialog({
} else if (appId === "codex") {
const config = parsedConfig.config as string | undefined;
if (config) {
const baseUrlMatch = config.match(
/base_url\s*=\s*["']([^"']+)["']/,
);
if (baseUrlMatch?.[1]) {
addUrl(baseUrlMatch[1]);
const extractedBaseUrl = extractCodexBaseUrl(config);
if (extractedBaseUrl) {
addUrl(extractedBaseUrl);
}
}
} else if (appId === "gemini") {
+4 -3
View File
@@ -13,6 +13,7 @@ import { ProviderIcon } from "@/components/ProviderIcon";
import UsageFooter from "@/components/UsageFooter";
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
import { FailoverPriorityBadge } from "@/components/providers/FailoverPriorityBadge";
import { extractCodexBaseUrl } from "@/utils/providerConfigUtils";
import { useProviderHealth } from "@/lib/query/failover";
import { useUsageQuery } from "@/lib/query/queries";
@@ -76,9 +77,9 @@ const extractApiUrl = (provider: Provider, fallbackText: string) => {
const baseUrl = (config as Record<string, any>)?.config;
if (typeof baseUrl === "string" && baseUrl.includes("base_url")) {
const match = baseUrl.match(/base_url\s*=\s*['"]([^'"]+)['"]/);
if (match?.[1]) {
return match[1];
const extractedBaseUrl = extractCodexBaseUrl(baseUrl);
if (extractedBaseUrl) {
return extractedBaseUrl;
}
}
}
@@ -383,7 +383,9 @@ export function OpenClawFormFields({
className="flex items-center gap-1.5 cursor-pointer select-none"
>
<Checkbox
checked={(model.input ?? ["text"]).includes(type)}
checked={(model.input ?? ["text"]).includes(
type,
)}
onCheckedChange={(checked) => {
const current = model.input ?? ["text"];
const next = checked
@@ -285,7 +285,13 @@ export function useCodexCommonConfig({
isUpdatingFromCommonConfig.current = false;
}, 0);
},
[codexConfig, commonConfigSnippet, onConfigChange, parseCommonConfigSnippet, t],
[
codexConfig,
commonConfigSnippet,
onConfigChange,
parseCommonConfigSnippet,
t,
],
);
// 处理通用配置片段变化
@@ -171,18 +171,15 @@ export function useOpenclawFormState({
[updateOpenclawConfig],
);
const resetOpenclawState = useCallback(
(config?: OpenClawProviderConfig) => {
setOpenclawProviderKey("");
setOpenclawBaseUrl(config?.baseUrl || "");
setOpenclawApiKey(config?.apiKey || "");
setOpenclawApi(config?.api || "openai-completions");
setOpenclawModels(config?.models || []);
const ua = config?.headers ? "User-Agent" in config.headers : false;
setOpenclawUserAgent(ua);
},
[],
);
const resetOpenclawState = useCallback((config?: OpenClawProviderConfig) => {
setOpenclawProviderKey("");
setOpenclawBaseUrl(config?.baseUrl || "");
setOpenclawApiKey(config?.apiKey || "");
setOpenclawApi(config?.api || "openai-completions");
setOpenclawModels(config?.models || []);
const ua = config?.headers ? "User-Agent" in config.headers : false;
setOpenclawUserAgent(ua);
}, []);
return {
openclawProviderKey,
@@ -3,6 +3,7 @@ import type { AppId } from "@/lib/api";
import type { ProviderPreset } from "@/config/claudeProviderPresets";
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
import type { ProviderMeta, EndpointCandidate } from "@/types";
import { extractCodexBaseUrl } from "@/utils/providerConfigUtils";
type PresetEntry = {
id: string;
@@ -128,10 +129,9 @@ export function useSpeedTestEndpoints({
}
| undefined;
const configStr = initialCodexConfig?.config ?? "";
// 从 TOML 中提取 base_url
const match = /base_url\s*=\s*["']([^"']+)["']/i.exec(configStr);
if (match?.[1]) {
add(match[1]);
const extractedBaseUrl = extractCodexBaseUrl(configStr);
if (extractedBaseUrl) {
add(extractedBaseUrl);
}
// 3. 预设中的 endpointCandidates
@@ -141,11 +141,9 @@ export function useSpeedTestEndpoints({
const preset = entry.preset as CodexProviderPreset;
// 添加预设自己的 baseUrl
const presetConfig = preset.config || "";
const presetMatch = /base_url\s*=\s*["']([^"']+)["']/i.exec(
presetConfig,
);
if (presetMatch?.[1]) {
add(presetMatch[1]);
const presetBaseUrl = extractCodexBaseUrl(presetConfig);
if (presetBaseUrl) {
add(presetBaseUrl);
}
// 添加预设的候选端点
if (preset.endpointCandidates) {