mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 04:22:58 +08:00
fix: prevent duplicate codex base_url entries (#4316)
This commit is contained in:
@@ -400,7 +400,7 @@ export const hasTomlCommonConfigSnippet = (
|
||||
|
||||
const TOML_SECTION_HEADER_PATTERN = /^\s*\[([^\]\r\n]+)\]\s*$/;
|
||||
const TOML_BASE_URL_PATTERN =
|
||||
/^\s*base_url\s*=\s*(["'])([^"'\r\n]+)\1\s*(?:#.*)?$/;
|
||||
/^\s*base_url\s*=\s*(?:"((?:\\.|[^"\\\r\n])*)"|'([^'\r\n]*)')\s*(?:#.*)?$/;
|
||||
const TOML_EXPERIMENTAL_BEARER_TOKEN_PATTERN =
|
||||
/^\s*experimental_bearer_token\s*=\s*(["'])([^"'\r\n]+)\1\s*(?:#.*)?$/;
|
||||
const TOML_EXPERIMENTAL_BEARER_TOKEN_REPLACE_PATTERN =
|
||||
@@ -553,11 +553,12 @@ const findTomlAssignmentInRange = (
|
||||
): TomlAssignmentMatch | undefined => {
|
||||
for (let index = startIndex; index < endIndex; index += 1) {
|
||||
const match = lines[index].match(pattern);
|
||||
if (match?.[2]) {
|
||||
const value = match?.[2] ?? match?.[1];
|
||||
if (value) {
|
||||
return {
|
||||
index,
|
||||
sectionName,
|
||||
value: match[2],
|
||||
value,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -565,6 +566,26 @@ const findTomlAssignmentInRange = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const findTomlAssignmentsInRange = (
|
||||
lines: string[],
|
||||
pattern: RegExp,
|
||||
startIndex: number,
|
||||
endIndex: number,
|
||||
sectionName?: string,
|
||||
): TomlAssignmentMatch[] => {
|
||||
const matches: TomlAssignmentMatch[] = [];
|
||||
|
||||
for (let index = startIndex; index < endIndex; index += 1) {
|
||||
const match = lines[index].match(pattern);
|
||||
const value = match?.[2] ?? match?.[1];
|
||||
if (value) {
|
||||
matches.push({ index, sectionName, value });
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
const findTomlLineInRange = (
|
||||
lines: string[],
|
||||
pattern: RegExp,
|
||||
@@ -595,14 +616,15 @@ const findTomlAssignments = (
|
||||
}
|
||||
|
||||
const match = line.match(pattern);
|
||||
if (!match?.[2]) {
|
||||
const value = match?.[2] ?? match?.[1];
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
assignments.push({
|
||||
index,
|
||||
sectionName: currentSectionName,
|
||||
value: match[2],
|
||||
value,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1228,18 +1250,20 @@ export const setCodexBaseUrl = (
|
||||
|
||||
if (targetSectionName) {
|
||||
const sectionRange = getTomlSectionRange(lines, targetSectionName);
|
||||
const targetMatch = sectionRange
|
||||
? findTomlAssignmentInRange(
|
||||
const targetMatches = sectionRange
|
||||
? findTomlAssignmentsInRange(
|
||||
lines,
|
||||
TOML_BASE_URL_PATTERN,
|
||||
sectionRange.bodyStartIndex,
|
||||
sectionRange.bodyEndIndex,
|
||||
targetSectionName,
|
||||
)
|
||||
: undefined;
|
||||
: [];
|
||||
|
||||
if (targetMatch) {
|
||||
lines.splice(targetMatch.index, 1);
|
||||
if (targetMatches.length > 0) {
|
||||
for (const match of [...targetMatches].reverse()) {
|
||||
lines.splice(match.index, 1);
|
||||
}
|
||||
return finalizeTomlText(lines);
|
||||
}
|
||||
}
|
||||
@@ -1257,18 +1281,22 @@ export const setCodexBaseUrl = (
|
||||
|
||||
if (targetSectionName) {
|
||||
let targetSectionRange = getTomlSectionRange(lines, targetSectionName);
|
||||
const targetMatch = targetSectionRange
|
||||
? findTomlAssignmentInRange(
|
||||
const targetMatches = targetSectionRange
|
||||
? findTomlAssignmentsInRange(
|
||||
lines,
|
||||
TOML_BASE_URL_PATTERN,
|
||||
targetSectionRange.bodyStartIndex,
|
||||
targetSectionRange.bodyEndIndex,
|
||||
targetSectionName,
|
||||
)
|
||||
: undefined;
|
||||
: [];
|
||||
|
||||
if (targetMatch) {
|
||||
lines[targetMatch.index] = replacementLine;
|
||||
if (targetMatches.length > 0) {
|
||||
const [firstMatch, ...duplicateMatches] = targetMatches;
|
||||
lines[firstMatch.index] = replacementLine;
|
||||
for (const match of [...duplicateMatches].reverse()) {
|
||||
lines.splice(match.index, 1);
|
||||
}
|
||||
return finalizeTomlText(lines);
|
||||
}
|
||||
|
||||
@@ -1291,14 +1319,18 @@ export const setCodexBaseUrl = (
|
||||
}
|
||||
|
||||
const topLevelEndIndex = getTopLevelEndIndex(lines);
|
||||
const topLevelMatch = findTomlAssignmentInRange(
|
||||
const topLevelMatches = findTomlAssignmentsInRange(
|
||||
lines,
|
||||
TOML_BASE_URL_PATTERN,
|
||||
0,
|
||||
topLevelEndIndex,
|
||||
);
|
||||
if (topLevelMatch) {
|
||||
lines[topLevelMatch.index] = replacementLine;
|
||||
if (topLevelMatches.length > 0) {
|
||||
const [firstMatch, ...duplicateMatches] = topLevelMatches;
|
||||
lines[firstMatch.index] = replacementLine;
|
||||
for (const match of [...duplicateMatches].reverse()) {
|
||||
lines.splice(match.index, 1);
|
||||
}
|
||||
return finalizeTomlText(lines);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,48 @@ describe("Codex TOML utils", () => {
|
||||
expect(extractCodexModelName(output2)).toBe("new-model");
|
||||
});
|
||||
|
||||
it("updates a double-quoted base_url containing single quotes without duplicating it", () => {
|
||||
const input = [
|
||||
'model_provider = "custom"',
|
||||
'model = "gpt-5.4"',
|
||||
"",
|
||||
"[model_providers.custom]",
|
||||
'name = "custom"',
|
||||
"base_url = \"https://su'us.codes/v1\"",
|
||||
'wire_api = "responses"',
|
||||
'requires_openai_auth = true',
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const output = setCodexBaseUrl(input, "https://su'us'd.codes/v1");
|
||||
|
||||
expect(extractCodexBaseUrl(output)).toBe("https://su'us'd.codes/v1");
|
||||
expect(output.match(/^\s*base_url\s*=/gm)).toHaveLength(1);
|
||||
expect(output).toContain("base_url = \"https://su'us'd.codes/v1\"");
|
||||
});
|
||||
|
||||
it("collapses duplicate base_url lines when editing the active provider section", () => {
|
||||
const input = [
|
||||
'model_provider = "custom"',
|
||||
'model = "gpt-5.4"',
|
||||
"",
|
||||
"[model_providers.custom]",
|
||||
'name = "custom"',
|
||||
'base_url = "https://old.example/v1"',
|
||||
'base_url = "https://older.example/v1"',
|
||||
'wire_api = "responses"',
|
||||
'requires_openai_auth = true',
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const output = setCodexBaseUrl(input, "https://new.example/v1");
|
||||
|
||||
expect(extractCodexBaseUrl(output)).toBe("https://new.example/v1");
|
||||
expect(output.match(/^\s*base_url\s*=/gm)).toHaveLength(1);
|
||||
expect(output).toContain('base_url = "https://new.example/v1"');
|
||||
expect(output).not.toContain("older.example");
|
||||
});
|
||||
|
||||
it("reads and writes base_url in the active provider section", () => {
|
||||
const input = [
|
||||
'model_provider = "custom"',
|
||||
|
||||
Reference in New Issue
Block a user