ai-account-switch 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +672 -671
- package/README_EN.md +1191 -0
- package/package.json +1 -1
- package/src/commands/account.js +116 -3
- package/src/config.js +195 -8
- package/src/ui-server.js +46 -0
- package/CHANGELOG-v1.7.0.md +0 -176
- package/README_ZH.md +0 -1192
package/package.json
CHANGED
package/src/commands/account.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const chalk = require("chalk");
|
|
2
2
|
const inquirer = require("inquirer");
|
|
3
3
|
const ConfigManager = require("../config");
|
|
4
|
+
const { WIRE_API_MODES, DEFAULT_WIRE_API, ACCOUNT_TYPES } = require("../config");
|
|
4
5
|
const { maskApiKey } = require("./helpers");
|
|
5
6
|
const { promptForModelGroup } = require("./model");
|
|
6
7
|
|
|
@@ -53,6 +54,9 @@ async function addAccount(name, options) {
|
|
|
53
54
|
},
|
|
54
55
|
]);
|
|
55
56
|
|
|
57
|
+
// Store wire_api selection for Codex accounts
|
|
58
|
+
let wireApiSelection = null;
|
|
59
|
+
|
|
56
60
|
// Show configuration tips based on account type
|
|
57
61
|
if (typeAnswer.type === "Codex") {
|
|
58
62
|
console.log(
|
|
@@ -60,12 +64,22 @@ async function addAccount(name, options) {
|
|
|
60
64
|
);
|
|
61
65
|
console.log(
|
|
62
66
|
chalk.gray(
|
|
63
|
-
" •
|
|
67
|
+
" • For domain-only URLs (e.g., https://api.example.com), /v1 will be added automatically"
|
|
64
68
|
)
|
|
65
69
|
);
|
|
66
70
|
console.log(
|
|
67
71
|
chalk.gray(
|
|
68
|
-
"
|
|
72
|
+
" 对于仅域名的 URL (例如 https://api.example.com), 将自动添加 /v1"
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
console.log(
|
|
76
|
+
chalk.gray(
|
|
77
|
+
" • URLs with existing paths (e.g., https://api.example.com/v2) will remain unchanged"
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
console.log(
|
|
81
|
+
chalk.gray(
|
|
82
|
+
" 已有路径的 URL (例如 https://api.example.com/v2) 将保持不变"
|
|
69
83
|
)
|
|
70
84
|
);
|
|
71
85
|
console.log(
|
|
@@ -73,6 +87,44 @@ async function addAccount(name, options) {
|
|
|
73
87
|
" • Codex uses OpenAI-compatible API format (Codex 使用 OpenAI 兼容的 API 格式)\n"
|
|
74
88
|
)
|
|
75
89
|
);
|
|
90
|
+
|
|
91
|
+
// Prompt for wire_api mode selection
|
|
92
|
+
const wireApiAnswer = await inquirer.prompt([
|
|
93
|
+
{
|
|
94
|
+
type: "list",
|
|
95
|
+
name: "wireApi",
|
|
96
|
+
message: "Select wire_api mode (请选择 wire_api 模式):",
|
|
97
|
+
choices: [
|
|
98
|
+
{
|
|
99
|
+
name: "chat - Use API key in HTTP headers (OpenAI-compatible) (在 HTTP 头中使用 API key,OpenAI 兼容)",
|
|
100
|
+
value: WIRE_API_MODES.CHAT
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "responses - Use API key in auth.json (requires_openai_auth) (在 auth.json 中使用 API key)",
|
|
104
|
+
value: WIRE_API_MODES.RESPONSES
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
default: DEFAULT_WIRE_API
|
|
108
|
+
}
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
wireApiSelection = wireApiAnswer.wireApi;
|
|
112
|
+
|
|
113
|
+
// Validate input
|
|
114
|
+
if (!Object.values(WIRE_API_MODES).includes(wireApiSelection)) {
|
|
115
|
+
console.log(
|
|
116
|
+
chalk.yellow(
|
|
117
|
+
`⚠ Invalid wire_api mode, using default: ${DEFAULT_WIRE_API} (无效的模式,使用默认值)`
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
wireApiSelection = DEFAULT_WIRE_API;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(
|
|
124
|
+
chalk.cyan(
|
|
125
|
+
`\n✓ Selected wire_api mode (已选择模式): ${wireApiSelection}\n`
|
|
126
|
+
)
|
|
127
|
+
);
|
|
76
128
|
} else if (typeAnswer.type === "Droids") {
|
|
77
129
|
console.log(
|
|
78
130
|
chalk.cyan("\n📝 Droids Configuration Tips (Droids 配置提示):")
|
|
@@ -121,7 +173,11 @@ async function addAccount(name, options) {
|
|
|
121
173
|
name: "apiUrl",
|
|
122
174
|
message:
|
|
123
175
|
typeAnswer.type === "Codex"
|
|
124
|
-
? "Enter API URL (请输入 API URL)
|
|
176
|
+
? "Enter API URL (请输入 API URL)\n" +
|
|
177
|
+
" Examples (示例):\n" +
|
|
178
|
+
" https://api.example.com → will add /v1 (将添加 /v1)\n" +
|
|
179
|
+
" https://api.example.com/v2 → will keep as is (保持不变)\n" +
|
|
180
|
+
" URL:"
|
|
125
181
|
: typeAnswer.type === "CCR"
|
|
126
182
|
? "Enter API URL (请输入 API URL):"
|
|
127
183
|
: "Enter API URL (optional) (请输入 API URL,可选):",
|
|
@@ -151,6 +207,11 @@ async function addAccount(name, options) {
|
|
|
151
207
|
// Merge type into accountData
|
|
152
208
|
accountData.type = typeAnswer.type;
|
|
153
209
|
|
|
210
|
+
// Add wire_api selection for Codex accounts
|
|
211
|
+
if (typeAnswer.type === "Codex" && wireApiSelection) {
|
|
212
|
+
accountData.wireApi = wireApiSelection;
|
|
213
|
+
}
|
|
214
|
+
|
|
154
215
|
// Handle custom environment variables
|
|
155
216
|
if (accountData.addCustomEnv) {
|
|
156
217
|
accountData.customEnv = {};
|
|
@@ -440,6 +501,27 @@ async function addAccount(name, options) {
|
|
|
440
501
|
)
|
|
441
502
|
);
|
|
442
503
|
console.log(chalk.cyan(` ais use ${name}\n`));
|
|
504
|
+
|
|
505
|
+
// Show wire_api mode specific information
|
|
506
|
+
if (accountData.wireApi === WIRE_API_MODES.RESPONSES) {
|
|
507
|
+
console.log(
|
|
508
|
+
chalk.yellow(
|
|
509
|
+
" ⚠ Note: This account uses 'responses' mode (此账号使用 'responses' 模式)"
|
|
510
|
+
)
|
|
511
|
+
);
|
|
512
|
+
console.log(
|
|
513
|
+
chalk.white(
|
|
514
|
+
" Your API key will be stored in ~/.codex/auth.json (API key 将存储在 ~/.codex/auth.json)\n"
|
|
515
|
+
)
|
|
516
|
+
);
|
|
517
|
+
} else {
|
|
518
|
+
console.log(
|
|
519
|
+
chalk.cyan(
|
|
520
|
+
" ✓ This account uses 'chat' mode (此账号使用 'chat' 模式)\n"
|
|
521
|
+
)
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
443
525
|
console.log(
|
|
444
526
|
chalk.white(
|
|
445
527
|
"2. Use Codex with the generated profile (使用生成的配置文件运行 Codex):"
|
|
@@ -548,6 +630,7 @@ function listAccounts() {
|
|
|
548
630
|
console.log(`${marker}${nameDisplay}`);
|
|
549
631
|
console.log(` Type: ${account.type}`);
|
|
550
632
|
console.log(` API Key: ${maskApiKey(account.apiKey)}`);
|
|
633
|
+
if (account.apiUrl) console.log(` API URL: ${account.apiUrl}`);
|
|
551
634
|
if (account.email) console.log(` Email: ${account.email}`);
|
|
552
635
|
if (account.description)
|
|
553
636
|
console.log(` Description: ${account.description}`);
|
|
@@ -575,6 +658,11 @@ function listAccounts() {
|
|
|
575
658
|
) {
|
|
576
659
|
console.log(` Model: ${account.model}`);
|
|
577
660
|
}
|
|
661
|
+
// Display wire_api configuration for Codex accounts
|
|
662
|
+
if (account.type === ACCOUNT_TYPES.CODEX) {
|
|
663
|
+
const wireApi = account.wireApi || `${DEFAULT_WIRE_API} (default)`;
|
|
664
|
+
console.log(` Wire API: ${wireApi}`);
|
|
665
|
+
}
|
|
578
666
|
console.log(
|
|
579
667
|
` Created: ${new Date(account.createdAt).toLocaleString()}`
|
|
580
668
|
);
|
|
@@ -674,6 +762,27 @@ async function useAccount(name) {
|
|
|
674
762
|
`✓ Codex profile created (Codex 配置文件已创建): ${profileName}`
|
|
675
763
|
)
|
|
676
764
|
);
|
|
765
|
+
|
|
766
|
+
// Display wire_api mode information
|
|
767
|
+
if (account.wireApi === WIRE_API_MODES.RESPONSES) {
|
|
768
|
+
console.log(
|
|
769
|
+
chalk.yellow(
|
|
770
|
+
`✓ Wire API mode: ${WIRE_API_MODES.RESPONSES} (使用 ${WIRE_API_MODES.RESPONSES} 模式)`
|
|
771
|
+
)
|
|
772
|
+
);
|
|
773
|
+
console.log(
|
|
774
|
+
chalk.yellow(
|
|
775
|
+
`✓ API key stored in ~/.codex/auth.json (API key 已存储在 ~/.codex/auth.json)`
|
|
776
|
+
)
|
|
777
|
+
);
|
|
778
|
+
} else {
|
|
779
|
+
console.log(
|
|
780
|
+
chalk.cyan(
|
|
781
|
+
`✓ Wire API mode: ${WIRE_API_MODES.CHAT} (使用 ${WIRE_API_MODES.CHAT} 模式)`
|
|
782
|
+
)
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
677
786
|
console.log("");
|
|
678
787
|
console.log(chalk.bold.cyan("📖 Next Steps (下一步):"));
|
|
679
788
|
console.log(
|
|
@@ -864,6 +973,10 @@ function showInfo() {
|
|
|
864
973
|
) {
|
|
865
974
|
console.log(`${chalk.cyan("Model:")} ${projectAccount.model}`);
|
|
866
975
|
}
|
|
976
|
+
// Display wire_api configuration for Codex accounts
|
|
977
|
+
if (projectAccount.type === ACCOUNT_TYPES.CODEX && projectAccount.wireApi) {
|
|
978
|
+
console.log(`${chalk.cyan("Wire API:")} ${projectAccount.wireApi}`);
|
|
979
|
+
}
|
|
867
980
|
console.log(
|
|
868
981
|
`${chalk.cyan("Set At:")} ${new Date(
|
|
869
982
|
projectAccount.setAt
|
package/src/config.js
CHANGED
|
@@ -2,6 +2,22 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
|
|
5
|
+
// Constants for wire API modes
|
|
6
|
+
const WIRE_API_MODES = {
|
|
7
|
+
CHAT: 'chat',
|
|
8
|
+
RESPONSES: 'responses'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const DEFAULT_WIRE_API = WIRE_API_MODES.CHAT;
|
|
12
|
+
|
|
13
|
+
// Constants for account types
|
|
14
|
+
const ACCOUNT_TYPES = {
|
|
15
|
+
CLAUDE: 'Claude',
|
|
16
|
+
CODEX: 'Codex',
|
|
17
|
+
CCR: 'CCR',
|
|
18
|
+
DROIDS: 'Droids'
|
|
19
|
+
};
|
|
20
|
+
|
|
5
21
|
/**
|
|
6
22
|
* Cross-platform configuration manager
|
|
7
23
|
* Stores global accounts in user home directory
|
|
@@ -556,10 +572,23 @@ class ConfigManager {
|
|
|
556
572
|
profileConfig += `model = "${account.model}"\n`;
|
|
557
573
|
}
|
|
558
574
|
|
|
559
|
-
//
|
|
575
|
+
// Smart /v1 path handling
|
|
560
576
|
let baseUrl = account.apiUrl || '';
|
|
561
|
-
if (baseUrl
|
|
562
|
-
|
|
577
|
+
if (baseUrl) {
|
|
578
|
+
// Remove trailing slashes
|
|
579
|
+
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
580
|
+
|
|
581
|
+
// Check if URL already has a path beyond the domain
|
|
582
|
+
// Pattern: protocol://domain or protocol://domain:port (no path)
|
|
583
|
+
const isDomainOnly = baseUrl.match(/^https?:\/\/[^\/]+$/);
|
|
584
|
+
|
|
585
|
+
// Only add /v1 if:
|
|
586
|
+
// 1. URL is domain-only (no path), OR
|
|
587
|
+
// 2. URL explicitly ends with /v1 already (ensure consistency)
|
|
588
|
+
if (isDomainOnly) {
|
|
589
|
+
baseUrl += '/v1';
|
|
590
|
+
}
|
|
591
|
+
// If URL has a path (e.g., /v2, /custom, /api), keep it as is
|
|
563
592
|
}
|
|
564
593
|
|
|
565
594
|
// Remove existing provider if it exists (simpler than updating)
|
|
@@ -574,11 +603,26 @@ class ConfigManager {
|
|
|
574
603
|
profileConfig += `base_url = "${baseUrl}"\n`;
|
|
575
604
|
}
|
|
576
605
|
|
|
577
|
-
// Determine wire_api
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
606
|
+
// Determine wire_api based on account configuration (default to chat for backward compatibility)
|
|
607
|
+
const wireApi = account.wireApi || DEFAULT_WIRE_API;
|
|
608
|
+
|
|
609
|
+
if (wireApi === WIRE_API_MODES.CHAT) {
|
|
610
|
+
// Chat mode: use HTTP headers for authentication
|
|
611
|
+
profileConfig += `wire_api = "${WIRE_API_MODES.CHAT}"\n`;
|
|
612
|
+
profileConfig += `http_headers = { "Authorization" = "Bearer ${account.apiKey}" }\n`;
|
|
613
|
+
|
|
614
|
+
// Note: We do NOT clear auth.json here because:
|
|
615
|
+
// 1. auth.json is a global file shared by all projects
|
|
616
|
+
// 2. Other projects may be using responses mode and need the API key
|
|
617
|
+
// 3. Chat mode doesn't use auth.json anyway, so no conflict exists
|
|
618
|
+
} else if (wireApi === WIRE_API_MODES.RESPONSES) {
|
|
619
|
+
// Responses mode: use auth.json for authentication
|
|
620
|
+
profileConfig += `wire_api = "${WIRE_API_MODES.RESPONSES}"\n`;
|
|
621
|
+
profileConfig += `requires_openai_auth = true\n`;
|
|
622
|
+
|
|
623
|
+
// Update auth.json with API key
|
|
624
|
+
this.updateCodexAuthJson(account.apiKey);
|
|
625
|
+
}
|
|
582
626
|
}
|
|
583
627
|
|
|
584
628
|
// Remove all old profiles with the same name (including duplicates)
|
|
@@ -607,6 +651,146 @@ class ConfigManager {
|
|
|
607
651
|
fs.writeFileSync(helperScript, profileName, 'utf8');
|
|
608
652
|
}
|
|
609
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Read auth.json file
|
|
656
|
+
* @private
|
|
657
|
+
* @returns {Object} Parsed auth data or empty object
|
|
658
|
+
*/
|
|
659
|
+
_readAuthJson(authJsonFile) {
|
|
660
|
+
if (!fs.existsSync(authJsonFile)) {
|
|
661
|
+
return {};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
const content = fs.readFileSync(authJsonFile, 'utf8');
|
|
666
|
+
return JSON.parse(content);
|
|
667
|
+
} catch (parseError) {
|
|
668
|
+
const chalk = require('chalk');
|
|
669
|
+
console.warn(
|
|
670
|
+
chalk.yellow(
|
|
671
|
+
`⚠ Warning: Could not parse existing auth.json, will create new file (警告: 无法解析现有 auth.json,将创建新文件)`
|
|
672
|
+
)
|
|
673
|
+
);
|
|
674
|
+
return {};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Write auth.json file atomically with proper permissions
|
|
680
|
+
* Uses atomic write (temp file + rename) to prevent corruption from concurrent access
|
|
681
|
+
* @private
|
|
682
|
+
* @param {string} authJsonFile - Path to auth.json
|
|
683
|
+
* @param {Object} authData - Auth data to write
|
|
684
|
+
*/
|
|
685
|
+
_writeAuthJson(authJsonFile, authData) {
|
|
686
|
+
const chalk = require('chalk');
|
|
687
|
+
const tempFile = `${authJsonFile}.tmp.${process.pid}`;
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
// Write to temporary file first (atomic operation)
|
|
691
|
+
fs.writeFileSync(tempFile, JSON.stringify(authData, null, 2), 'utf8');
|
|
692
|
+
|
|
693
|
+
// Set file permissions to 600 (owner read/write only) for security
|
|
694
|
+
if (process.platform !== 'win32') {
|
|
695
|
+
fs.chmodSync(tempFile, 0o600);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Atomically rename temp file to actual file
|
|
699
|
+
// This is atomic on POSIX systems and prevents corruption
|
|
700
|
+
fs.renameSync(tempFile, authJsonFile);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
// Clean up temp file if it exists
|
|
703
|
+
if (fs.existsSync(tempFile)) {
|
|
704
|
+
try {
|
|
705
|
+
fs.unlinkSync(tempFile);
|
|
706
|
+
} catch (cleanupError) {
|
|
707
|
+
// Ignore cleanup errors
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Clear OPENAI_API_KEY in ~/.codex/auth.json for chat mode
|
|
716
|
+
* @deprecated This method is no longer called automatically.
|
|
717
|
+
* Chat mode doesn't require clearing auth.json since it doesn't use it.
|
|
718
|
+
*/
|
|
719
|
+
clearCodexAuthJson() {
|
|
720
|
+
const chalk = require('chalk');
|
|
721
|
+
const codexDir = path.join(os.homedir(), '.codex');
|
|
722
|
+
const authJsonFile = path.join(codexDir, 'auth.json');
|
|
723
|
+
|
|
724
|
+
try {
|
|
725
|
+
// Ensure .codex directory exists
|
|
726
|
+
if (!fs.existsSync(codexDir)) {
|
|
727
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Read existing auth data
|
|
731
|
+
const authData = this._readAuthJson(authJsonFile);
|
|
732
|
+
|
|
733
|
+
// Clear OPENAI_API_KEY (set to empty string)
|
|
734
|
+
authData.OPENAI_API_KEY = "";
|
|
735
|
+
|
|
736
|
+
// Write atomically with proper permissions
|
|
737
|
+
this._writeAuthJson(authJsonFile, authData);
|
|
738
|
+
|
|
739
|
+
console.log(
|
|
740
|
+
chalk.cyan(
|
|
741
|
+
`✓ Cleared OPENAI_API_KEY in auth.json (chat mode) (已清空 auth.json 中的 OPENAI_API_KEY)`
|
|
742
|
+
)
|
|
743
|
+
);
|
|
744
|
+
} catch (error) {
|
|
745
|
+
console.error(
|
|
746
|
+
chalk.yellow(
|
|
747
|
+
`⚠ Warning: Failed to clear auth.json: ${error.message} (警告: 清空 auth.json 失败)`
|
|
748
|
+
)
|
|
749
|
+
);
|
|
750
|
+
// Don't throw error, just warn - this is not critical for chat mode
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Update ~/.codex/auth.json with API key for responses mode
|
|
756
|
+
* @param {string} apiKey - API key to store in auth.json
|
|
757
|
+
* @throws {Error} If file operations fail
|
|
758
|
+
*/
|
|
759
|
+
updateCodexAuthJson(apiKey) {
|
|
760
|
+
const chalk = require('chalk');
|
|
761
|
+
const codexDir = path.join(os.homedir(), '.codex');
|
|
762
|
+
const authJsonFile = path.join(codexDir, 'auth.json');
|
|
763
|
+
|
|
764
|
+
try {
|
|
765
|
+
// Ensure .codex directory exists
|
|
766
|
+
if (!fs.existsSync(codexDir)) {
|
|
767
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Read existing auth data
|
|
771
|
+
const authData = this._readAuthJson(authJsonFile);
|
|
772
|
+
|
|
773
|
+
// Update OPENAI_API_KEY
|
|
774
|
+
authData.OPENAI_API_KEY = apiKey;
|
|
775
|
+
|
|
776
|
+
// Write atomically with proper permissions
|
|
777
|
+
this._writeAuthJson(authJsonFile, authData);
|
|
778
|
+
|
|
779
|
+
console.log(
|
|
780
|
+
chalk.green(
|
|
781
|
+
`✓ Updated auth.json at: ${authJsonFile} (已更新 auth.json)`
|
|
782
|
+
)
|
|
783
|
+
);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
console.error(
|
|
786
|
+
chalk.red(
|
|
787
|
+
`✗ Failed to update auth.json: ${error.message} (更新 auth.json 失败)`
|
|
788
|
+
)
|
|
789
|
+
);
|
|
790
|
+
throw error;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
610
794
|
/**
|
|
611
795
|
* Get current project's active account
|
|
612
796
|
* Searches upwards from current directory to find project root
|
|
@@ -1147,3 +1331,6 @@ class ConfigManager {
|
|
|
1147
1331
|
}
|
|
1148
1332
|
|
|
1149
1333
|
module.exports = ConfigManager;
|
|
1334
|
+
module.exports.WIRE_API_MODES = WIRE_API_MODES;
|
|
1335
|
+
module.exports.DEFAULT_WIRE_API = DEFAULT_WIRE_API;
|
|
1336
|
+
module.exports.ACCOUNT_TYPES = ACCOUNT_TYPES;
|
package/src/ui-server.js
CHANGED
|
@@ -2,6 +2,7 @@ const http = require('http');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const ConfigManager = require('./config');
|
|
5
|
+
const { WIRE_API_MODES, DEFAULT_WIRE_API } = require('./config');
|
|
5
6
|
|
|
6
7
|
class UIServer {
|
|
7
8
|
constructor(port = null) {
|
|
@@ -1491,6 +1492,18 @@ class UIServer {
|
|
|
1491
1492
|
<label for="apiUrl" data-i18n="apiUrl">API URL (可选)</label>
|
|
1492
1493
|
<input type="text" id="apiUrl" data-i18n-placeholder="apiUrlPlaceholder" placeholder="https://api.anthropic.com">
|
|
1493
1494
|
</div>
|
|
1495
|
+
<!-- Wire API selection for Codex accounts -->
|
|
1496
|
+
<div class="form-group" id="wireApiGroup" style="display: none;">
|
|
1497
|
+
<label for="wireApi">Wire API 模式</label>
|
|
1498
|
+
<select id="wireApi">
|
|
1499
|
+
<option value="chat">chat - HTTP Headers 认证 (OpenAI 兼容)</option>
|
|
1500
|
+
<option value="responses">responses - auth.json 认证 (requires_openai_auth)</option>
|
|
1501
|
+
</select>
|
|
1502
|
+
<small style="color: #666; display: block; margin-top: 5px;">
|
|
1503
|
+
chat: API key 存储在 HTTP headers 中<br>
|
|
1504
|
+
responses: API key 存储在 ~/.codex/auth.json 中
|
|
1505
|
+
</small>
|
|
1506
|
+
</div>
|
|
1494
1507
|
<div class="form-group">
|
|
1495
1508
|
<label for="email" data-i18n="email">邮箱 (可选)</label>
|
|
1496
1509
|
<input type="email" id="email" data-i18n-placeholder="emailPlaceholder" placeholder="user@example.com">
|
|
@@ -1615,6 +1628,13 @@ class UIServer {
|
|
|
1615
1628
|
</div>
|
|
1616
1629
|
|
|
1617
1630
|
<script>
|
|
1631
|
+
// Constants for wire API modes (injected from backend)
|
|
1632
|
+
const WIRE_API_MODES = {
|
|
1633
|
+
CHAT: '${WIRE_API_MODES.CHAT}',
|
|
1634
|
+
RESPONSES: '${WIRE_API_MODES.RESPONSES}'
|
|
1635
|
+
};
|
|
1636
|
+
const DEFAULT_WIRE_API = '${DEFAULT_WIRE_API}';
|
|
1637
|
+
|
|
1618
1638
|
// i18n translations
|
|
1619
1639
|
const translations = {
|
|
1620
1640
|
zh: {
|
|
@@ -2021,6 +2041,12 @@ class UIServer {
|
|
|
2021
2041
|
<div class="info-value">\${data.model}</div>
|
|
2022
2042
|
</div>
|
|
2023
2043
|
\` : ''}
|
|
2044
|
+
\${data.type === 'Codex' ? \`
|
|
2045
|
+
<div class="account-info">
|
|
2046
|
+
<div class="info-label">Wire API</div>
|
|
2047
|
+
<div class="info-value">\${data.wireApi || (DEFAULT_WIRE_API + ' (default)')}</div>
|
|
2048
|
+
</div>
|
|
2049
|
+
\` : ''}
|
|
2024
2050
|
\${data.type === 'CCR' && data.ccrConfig ? \`
|
|
2025
2051
|
<div class="account-info">
|
|
2026
2052
|
<div class="info-label">CCR Provider</div>
|
|
@@ -2051,6 +2077,12 @@ class UIServer {
|
|
|
2051
2077
|
const simpleModelGroup = document.getElementById('simpleModelGroup');
|
|
2052
2078
|
const claudeModelGroup = document.getElementById('claudeModelGroup');
|
|
2053
2079
|
const ccrModelGroup = document.getElementById('ccrModelGroup');
|
|
2080
|
+
const wireApiGroup = document.getElementById('wireApiGroup');
|
|
2081
|
+
|
|
2082
|
+
// Show/hide wire_api field for Codex accounts
|
|
2083
|
+
if (wireApiGroup) {
|
|
2084
|
+
wireApiGroup.style.display = accountType === 'Codex' ? 'block' : 'none';
|
|
2085
|
+
}
|
|
2054
2086
|
|
|
2055
2087
|
if (accountType === 'Codex' || accountType === 'Droids') {
|
|
2056
2088
|
simpleModelGroup.style.display = 'block';
|
|
@@ -2117,6 +2149,12 @@ class UIServer {
|
|
|
2117
2149
|
if (account.type === 'Codex' || account.type === 'Droids') {
|
|
2118
2150
|
// Load simple model field
|
|
2119
2151
|
document.getElementById('simpleModel').value = account.model || '';
|
|
2152
|
+
|
|
2153
|
+
// Load wire_api for Codex accounts
|
|
2154
|
+
if (account.type === 'Codex') {
|
|
2155
|
+
document.getElementById('wireApi').value = account.wireApi || DEFAULT_WIRE_API;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2120
2158
|
// Clear model groups and CCR config
|
|
2121
2159
|
document.getElementById('modelGroupsList').innerHTML = '';
|
|
2122
2160
|
modelGroupCount = 0;
|
|
@@ -2379,6 +2417,14 @@ class UIServer {
|
|
|
2379
2417
|
if (simpleModel) {
|
|
2380
2418
|
accountData.model = simpleModel;
|
|
2381
2419
|
}
|
|
2420
|
+
|
|
2421
|
+
// Add wire_api for Codex accounts
|
|
2422
|
+
if (accountType === 'Codex') {
|
|
2423
|
+
const wireApi = document.getElementById('wireApi').value;
|
|
2424
|
+
if (wireApi) {
|
|
2425
|
+
accountData.wireApi = wireApi;
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2382
2428
|
} else if (accountType === 'CCR') {
|
|
2383
2429
|
// Collect CCR config
|
|
2384
2430
|
const providerName = document.getElementById('ccrProviderName').value.trim();
|