feat(codex): preserve OAuth login state during third-party provider switching

Codex provider switches now only write config.toml for third-party providers,
injecting the API key as experimental_bearer_token. The user's auth.json
(ChatGPT OAuth tokens) is preserved. Official providers with login material
still write auth.json normally. Backfill restores bearer tokens into stored
provider auth.OPENAI_API_KEY to maintain canonical shape.
This commit is contained in:
Jason
2026-05-25 17:46:23 +08:00
parent 88ba908bc4
commit 95f2dd4126
17 changed files with 1664 additions and 154 deletions
+138 -7
View File
@@ -1,12 +1,14 @@
import { describe, expect, it } from "vitest";
import {
extractCodexBaseUrl,
extractCodexExperimentalBearerToken,
extractCodexModelName,
extractCodexTopLevelInt,
removeCodexTopLevelField,
setCodexBaseUrl,
setCodexModelName,
setCodexTopLevelInt,
updateCodexExperimentalBearerToken,
} from "@/utils/providerConfigUtils";
describe("Codex TOML utils", () => {
@@ -177,15 +179,10 @@ describe("Codex TOML utils", () => {
128000,
);
expect(
extractCodexTopLevelInt(
withCompact,
"model_auto_compact_token_limit",
),
extractCodexTopLevelInt(withCompact, "model_auto_compact_token_limit"),
).toBe(90000);
expect(withCompact).toMatch(/^model_context_window = 128000$/m);
expect(withCompact).toMatch(
/^model_auto_compact_token_limit = 90000$/m,
);
expect(withCompact).toMatch(/^model_auto_compact_token_limit = 90000$/m);
const removed = removeCodexTopLevelField(
withCompact,
@@ -197,4 +194,138 @@ describe("Codex TOML utils", () => {
).toBeUndefined();
expect(removed).toContain("[model_providers.custom]");
});
// P3 回归: 不能在 config 没用 bearer token 模式时, 误为它新增一行
it("updateCodexExperimentalBearerToken leaves config without the token alone", () => {
const input = [
'model_provider = "openai"',
'base_url = "https://api.example.com/v1"',
"",
].join("\n");
expect(updateCodexExperimentalBearerToken(input, "new-key")).toBe(input);
expect(updateCodexExperimentalBearerToken(input, "")).toBe(input);
});
// P3 回归: bearer 模式下清空 API key 必须真正擦掉 token, 让 pickCodexApiKey 的 fallback 找不到
it("updateCodexExperimentalBearerToken removes the token line when set to empty", () => {
const input = [
'model_provider = "thirdparty"',
"",
"[model_providers.thirdparty]",
'name = "Thirdparty"',
'base_url = "https://thirdparty.example/v1"',
'experimental_bearer_token = "old-key"',
"requires_openai_auth = true",
"",
].join("\n");
const cleared = updateCodexExperimentalBearerToken(input, "");
expect(extractCodexExperimentalBearerToken(cleared)).toBeUndefined();
expect(cleared).toMatch(/requires_openai_auth = true/);
expect(cleared).toMatch(/base_url = "https:\/\/thirdparty\.example\/v1"/);
});
it("updateCodexExperimentalBearerToken replaces the token inside the active model_providers section", () => {
const input = [
'model_provider = "thirdparty"',
"",
"[model_providers.thirdparty]",
'experimental_bearer_token = "old-key"',
"",
].join("\n");
const updated = updateCodexExperimentalBearerToken(input, "new-key");
expect(extractCodexExperimentalBearerToken(updated)).toBe("new-key");
expect(updated).not.toMatch(/old-key/);
});
it("updateCodexExperimentalBearerToken escapes basic TOML strings and keeps comments", () => {
const input = [
'model_provider = "thirdparty"',
"",
"[model_providers.thirdparty]",
'experimental_bearer_token = "old-key" # vendor token',
"",
].join("\n");
const updated = updateCodexExperimentalBearerToken(
input,
'abc"def\\ghi',
);
expect(updated).toContain(
'experimental_bearer_token = "abc\\"def\\\\ghi" # vendor token',
);
expect(extractCodexExperimentalBearerToken(updated)).toBe('abc"def\\ghi');
});
it("updateCodexExperimentalBearerToken escapes all TOML control characters", () => {
const input = [
'model_provider = "thirdparty"',
"",
"[model_providers.thirdparty]",
'experimental_bearer_token = "old-key"',
"",
].join("\n");
const updated = updateCodexExperimentalBearerToken(
input,
"a\u0000b\u0001c\u001fd",
);
expect(updated).toContain(
'experimental_bearer_token = "a\\u0000b\\u0001c\\u001fd"',
);
expect(extractCodexExperimentalBearerToken(updated)).toBe(
"a\u0000b\u0001c\u001fd",
);
});
it("updateCodexExperimentalBearerToken can replace an already escaped basic string", () => {
const input = [
'model_provider = "thirdparty"',
"",
"[model_providers.thirdparty]",
'experimental_bearer_token = "old\\"key" # vendor token',
"",
].join("\n");
const updated = updateCodexExperimentalBearerToken(input, "new-key");
expect(updated).toContain(
'experimental_bearer_token = "new-key" # vendor token',
);
expect(extractCodexExperimentalBearerToken(updated)).toBe("new-key");
});
it("extractCodexExperimentalBearerToken ignores reserved provider tables", () => {
const input = [
'model_provider = "openai"',
'experimental_bearer_token = "top-level-key"',
"",
"[model_providers.openai]",
'experimental_bearer_token = "stale-table-key"',
"",
].join("\n");
expect(extractCodexExperimentalBearerToken(input)).toBe("top-level-key");
});
it("extractCodexExperimentalBearerToken reads only top-level model_provider", () => {
const input = [
'experimental_bearer_token = "top-level-key"',
"",
"[profiles.work]",
'model_provider = "fake"',
"",
"[model_providers.fake]",
'experimental_bearer_token = "wrong-key"',
"",
].join("\n");
expect(extractCodexExperimentalBearerToken(input)).toBe("top-level-key");
});
});