aicodeswitch 3.9.3 → 4.0.0
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 +37 -36
- package/UPGRADE.md +5 -3
- package/bin/restore.js +126 -22
- package/bin/start.js +29 -41
- package/bin/stop.js +3 -3
- package/bin/utils/config-helpers.js +198 -0
- package/dist/server/config-managed-fields.js +69 -0
- package/dist/server/config-merge.js +260 -0
- package/dist/server/database-factory.js +11 -181
- package/dist/server/fs-database.js +211 -31
- package/dist/server/main.js +380 -241
- package/dist/server/original-config-reader.js +154 -88
- package/dist/server/proxy-server.js +993 -385
- package/dist/server/rules-status-service.js +285 -54
- package/dist/server/transformers/chunk-collector.js +26 -4
- package/dist/server/transformers/streaming.js +2334 -280
- package/dist/server/transformers/transformers.js +1765 -0
- package/dist/ui/assets/index-GQBwe1Rm.js +514 -0
- package/dist/ui/index.html +1 -1
- package/package.json +4 -8
- package/schema/claude.schema.md +15 -14
- package/schema/deepseek-chat.schema.md +799 -0
- package/schema/{openai.schema.md → openai-chat-completions.schema.md} +9 -1083
- package/schema/openai-responses.schema.md +226196 -0
- package/schema/stream.md +2592 -0
- package/dist/server/database.js +0 -1609
- package/dist/server/migrate-to-fs.js +0 -353
- package/dist/server/transformers/claude-openai.js +0 -868
- package/dist/server/transformers/gemini.js +0 -625
- package/dist/ui/assets/index-DNtgPQMm.js +0 -511
|
@@ -8,79 +8,103 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const types_1 = require("../types");
|
|
11
|
+
const toml_1 = __importDefault(require("@iarna/toml"));
|
|
11
12
|
/**
|
|
12
|
-
* TOML
|
|
13
|
+
* TOML 解析器(使用 @iarna/toml 库)
|
|
13
14
|
*/
|
|
14
15
|
const parseToml = (content) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
16
|
+
try {
|
|
17
|
+
return toml_1.default.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.warn('Failed to parse TOML file:', error);
|
|
21
|
+
return {}; // 返回空对象以保持兼容性
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const normalizeApiUrl = (value) => {
|
|
25
|
+
return value.replace(/\/+$/, '');
|
|
26
|
+
};
|
|
27
|
+
const inferSourceTypeFromBaseUrlAndWireApi = (baseUrl, wireApi) => {
|
|
28
|
+
const lowerBaseUrl = baseUrl.toLowerCase();
|
|
29
|
+
const normalizedWireApi = (wireApi || '').toLowerCase();
|
|
30
|
+
if (lowerBaseUrl.includes('anthropic')) {
|
|
31
|
+
return normalizedWireApi === 'chat' ? 'claude-chat' : 'claude';
|
|
32
|
+
}
|
|
33
|
+
if (lowerBaseUrl.includes('generativelanguage.googleapis.com') ||
|
|
34
|
+
lowerBaseUrl.includes('aiplatform.googleapis.com') ||
|
|
35
|
+
lowerBaseUrl.includes('vertexai')) {
|
|
36
|
+
return normalizedWireApi === 'chat' ? 'gemini-chat' : 'gemini';
|
|
37
|
+
}
|
|
38
|
+
if (lowerBaseUrl.includes('deepseek')) {
|
|
39
|
+
return 'deepseek-reasoning-chat';
|
|
40
|
+
}
|
|
41
|
+
if (normalizedWireApi === 'chat') {
|
|
42
|
+
return 'openai-chat';
|
|
43
|
+
}
|
|
44
|
+
return 'openai';
|
|
45
|
+
};
|
|
46
|
+
const inferAuthTypeFromSource = (sourceType) => {
|
|
47
|
+
if (sourceType === 'gemini' || sourceType === 'gemini-chat') {
|
|
48
|
+
return types_1.AuthType.G_API_KEY;
|
|
49
|
+
}
|
|
50
|
+
if (sourceType === 'claude' || sourceType === 'claude-chat') {
|
|
51
|
+
return types_1.AuthType.API_KEY;
|
|
52
|
+
}
|
|
53
|
+
return types_1.AuthType.AUTH_TOKEN;
|
|
54
|
+
};
|
|
55
|
+
const getProviderCandidateKeys = (providerName) => {
|
|
56
|
+
if (!providerName) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const upper = providerName.toUpperCase().replace(/[^A-Z0-9]+/g, '_');
|
|
60
|
+
const lower = providerName.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
61
|
+
return [
|
|
62
|
+
`${upper}_API_KEY`,
|
|
63
|
+
`${upper}_AUTH_TOKEN`,
|
|
64
|
+
`${lower}_api_key`,
|
|
65
|
+
`${lower}_auth_token`,
|
|
66
|
+
];
|
|
67
|
+
};
|
|
68
|
+
const pickApiKey = (authConfig, candidates) => {
|
|
69
|
+
for (const key of candidates) {
|
|
70
|
+
const value = authConfig[key];
|
|
71
|
+
if (typeof value === 'string' && value.trim()) {
|
|
72
|
+
return value.trim();
|
|
55
73
|
}
|
|
56
74
|
}
|
|
57
|
-
return
|
|
75
|
+
return '';
|
|
58
76
|
};
|
|
59
77
|
/**
|
|
60
78
|
* 读取 Claude Code 原始配置
|
|
61
|
-
*
|
|
79
|
+
* fallback 场景下仅从备份文件读取
|
|
62
80
|
*/
|
|
63
81
|
const readClaudeOriginalConfig = () => {
|
|
64
|
-
var _a, _b, _c;
|
|
82
|
+
var _a, _b, _c, _d, _e, _f;
|
|
65
83
|
try {
|
|
66
84
|
const homeDir = os_1.default.homedir();
|
|
67
|
-
const settingsPath = path_1.default.join(homeDir, '.claude/settings.json');
|
|
68
85
|
const settingsBakPath = path_1.default.join(homeDir, '.claude/settings.json.aicodeswitch_backup');
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
configPath = settingsPath;
|
|
74
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
75
|
-
console.log('No Claude config file found');
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
86
|
+
// fallback 只读取备份文件,确保使用真实上游配置
|
|
87
|
+
if (!fs_1.default.existsSync(settingsBakPath)) {
|
|
88
|
+
console.log('No Claude backup config file found');
|
|
89
|
+
return null;
|
|
78
90
|
}
|
|
79
|
-
const content = fs_1.default.readFileSync(
|
|
91
|
+
const content = fs_1.default.readFileSync(settingsBakPath, 'utf-8');
|
|
80
92
|
const config = JSON.parse(content);
|
|
81
93
|
// 提取配置信息
|
|
82
|
-
const
|
|
83
|
-
const
|
|
94
|
+
const baseUrlRaw = ((_a = config.env) === null || _a === void 0 ? void 0 : _a.ANTHROPIC_BASE_URL) || 'https://api.anthropic.com';
|
|
95
|
+
const baseUrl = normalizeApiUrl(baseUrlRaw);
|
|
96
|
+
const authToken = typeof ((_b = config.env) === null || _b === void 0 ? void 0 : _b.ANTHROPIC_AUTH_TOKEN) === 'string' ? config.env.ANTHROPIC_AUTH_TOKEN : '';
|
|
97
|
+
const apiKeyValue = typeof ((_c = config.env) === null || _c === void 0 ? void 0 : _c.ANTHROPIC_API_KEY) === 'string' ? config.env.ANTHROPIC_API_KEY : '';
|
|
98
|
+
const apiKey = (authToken || apiKeyValue).trim();
|
|
99
|
+
const defaultHaikuModel = typeof ((_d = config.env) === null || _d === void 0 ? void 0 : _d.ANTHROPIC_DEFAULT_HAIKU_MODEL) === 'string'
|
|
100
|
+
? config.env.ANTHROPIC_DEFAULT_HAIKU_MODEL.trim()
|
|
101
|
+
: '';
|
|
102
|
+
const defaultSonnetModel = typeof ((_e = config.env) === null || _e === void 0 ? void 0 : _e.ANTHROPIC_DEFAULT_SONNET_MODEL) === 'string'
|
|
103
|
+
? config.env.ANTHROPIC_DEFAULT_SONNET_MODEL.trim()
|
|
104
|
+
: '';
|
|
105
|
+
const defaultOpusModel = typeof ((_f = config.env) === null || _f === void 0 ? void 0 : _f.ANTHROPIC_DEFAULT_OPUS_MODEL) === 'string'
|
|
106
|
+
? config.env.ANTHROPIC_DEFAULT_OPUS_MODEL.trim()
|
|
107
|
+
: '';
|
|
84
108
|
if (!apiKey) {
|
|
85
109
|
console.log('No API key found in Claude config');
|
|
86
110
|
return null;
|
|
@@ -88,8 +112,13 @@ const readClaudeOriginalConfig = () => {
|
|
|
88
112
|
return {
|
|
89
113
|
apiUrl: baseUrl,
|
|
90
114
|
apiKey: apiKey,
|
|
91
|
-
authType: types_1.AuthType.AUTH_TOKEN,
|
|
115
|
+
authType: authToken ? types_1.AuthType.AUTH_TOKEN : types_1.AuthType.API_KEY,
|
|
92
116
|
sourceType: 'claude',
|
|
117
|
+
claudeDefaultModels: {
|
|
118
|
+
haiku: defaultHaikuModel || undefined,
|
|
119
|
+
sonnet: defaultSonnetModel || undefined,
|
|
120
|
+
opus: defaultOpusModel || undefined,
|
|
121
|
+
},
|
|
93
122
|
};
|
|
94
123
|
}
|
|
95
124
|
catch (error) {
|
|
@@ -100,63 +129,100 @@ const readClaudeOriginalConfig = () => {
|
|
|
100
129
|
exports.readClaudeOriginalConfig = readClaudeOriginalConfig;
|
|
101
130
|
/**
|
|
102
131
|
* 读取 Codex 原始配置
|
|
103
|
-
*
|
|
132
|
+
* fallback 场景下仅从备份文件读取
|
|
104
133
|
*/
|
|
105
134
|
const readCodexOriginalConfig = () => {
|
|
106
135
|
try {
|
|
107
136
|
const homeDir = os_1.default.homedir();
|
|
108
|
-
const configPath = path_1.default.join(homeDir, '.codex/config.toml');
|
|
109
137
|
const configBakPath = path_1.default.join(homeDir, '.codex/config.toml.aicodeswitch_backup');
|
|
110
|
-
const
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
tomlPath = configPath;
|
|
116
|
-
if (!fs_1.default.existsSync(tomlPath)) {
|
|
117
|
-
console.log('No Codex config file found');
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
138
|
+
const authBakPath = path_1.default.join(homeDir, '.codex/auth.json.aicodeswitch_backup');
|
|
139
|
+
// fallback 只读取备份文件,确保使用真实上游配置
|
|
140
|
+
if (!fs_1.default.existsSync(configBakPath)) {
|
|
141
|
+
console.log('No Codex backup config file found');
|
|
142
|
+
return null;
|
|
120
143
|
}
|
|
121
|
-
const tomlContent = fs_1.default.readFileSync(
|
|
144
|
+
const tomlContent = fs_1.default.readFileSync(configBakPath, 'utf-8');
|
|
122
145
|
const config = parseToml(tomlContent);
|
|
123
|
-
// 提取
|
|
146
|
+
// 提取 provider 配置
|
|
124
147
|
let baseUrl = '';
|
|
125
|
-
let
|
|
148
|
+
let wireApi = '';
|
|
149
|
+
const model = typeof config.model === 'string' ? config.model : undefined;
|
|
150
|
+
const providerName = typeof config.model_provider === 'string' ? config.model_provider : undefined;
|
|
126
151
|
// 从 model_providers 中查找配置
|
|
127
|
-
if (config.model_providers) {
|
|
128
|
-
const
|
|
129
|
-
if (providerName &&
|
|
130
|
-
|
|
152
|
+
if (config.model_providers && typeof config.model_providers === 'object') {
|
|
153
|
+
const providers = config.model_providers;
|
|
154
|
+
if (providerName && providers[providerName]) {
|
|
155
|
+
const providerConfig = providers[providerName];
|
|
156
|
+
if (typeof (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.base_url) === 'string') {
|
|
157
|
+
baseUrl = providerConfig.base_url;
|
|
158
|
+
}
|
|
159
|
+
if (typeof (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.wire_api) === 'string') {
|
|
160
|
+
wireApi = providerConfig.wire_api;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const firstProvider = Object.values(providers).find(provider => typeof (provider === null || provider === void 0 ? void 0 : provider.base_url) === 'string');
|
|
165
|
+
if (firstProvider) {
|
|
166
|
+
baseUrl = firstProvider.base_url;
|
|
167
|
+
if (typeof firstProvider.wire_api === 'string') {
|
|
168
|
+
wireApi = firstProvider.wire_api;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!baseUrl) {
|
|
174
|
+
if (typeof config.base_url === 'string' && config.base_url.trim()) {
|
|
175
|
+
baseUrl = config.base_url;
|
|
131
176
|
}
|
|
132
177
|
}
|
|
133
178
|
if (!baseUrl) {
|
|
134
179
|
console.log('No base_url found in Codex config');
|
|
135
180
|
return null;
|
|
136
181
|
}
|
|
137
|
-
|
|
182
|
+
const normalizedBaseUrl = normalizeApiUrl(baseUrl);
|
|
183
|
+
const sourceType = inferSourceTypeFromBaseUrlAndWireApi(normalizedBaseUrl, wireApi);
|
|
184
|
+
// 读取 API key(从 auth.json.aicodeswitch_backup)
|
|
138
185
|
let apiKey = '';
|
|
139
|
-
if (fs_1.default.existsSync(
|
|
186
|
+
if (fs_1.default.existsSync(authBakPath)) {
|
|
140
187
|
try {
|
|
141
|
-
const authContent = fs_1.default.readFileSync(
|
|
188
|
+
const authContent = fs_1.default.readFileSync(authBakPath, 'utf-8');
|
|
142
189
|
const authConfig = JSON.parse(authContent);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
190
|
+
const providerCandidateKeys = getProviderCandidateKeys(providerName);
|
|
191
|
+
const sourceTypeKeys = sourceType === 'claude' || sourceType === 'claude-chat'
|
|
192
|
+
? ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY']
|
|
193
|
+
: sourceType === 'gemini' || sourceType === 'gemini-chat'
|
|
194
|
+
? ['GEMINI_API_KEY', 'GOOGLE_API_KEY']
|
|
195
|
+
: ['OPENAI_API_KEY', 'DEEPSEEK_API_KEY'];
|
|
196
|
+
const commonKeys = ['api_key', 'openai_api_key', 'key'];
|
|
197
|
+
apiKey = pickApiKey(authConfig, [
|
|
198
|
+
...providerCandidateKeys,
|
|
199
|
+
...sourceTypeKeys,
|
|
200
|
+
...commonKeys,
|
|
201
|
+
]);
|
|
146
202
|
}
|
|
147
203
|
catch (error) {
|
|
148
|
-
console.error('Failed to read Codex auth
|
|
204
|
+
console.error('Failed to read Codex auth backup:', error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log('No Codex backup auth file found');
|
|
209
|
+
}
|
|
210
|
+
// 某些配置会把 key 内联在 provider 中
|
|
211
|
+
if (!apiKey && config.model_providers && providerName) {
|
|
212
|
+
const providerConfig = config.model_providers[providerName];
|
|
213
|
+
if (providerConfig && typeof providerConfig.api_key === 'string' && providerConfig.api_key.trim()) {
|
|
214
|
+
apiKey = providerConfig.api_key.trim();
|
|
149
215
|
}
|
|
150
216
|
}
|
|
151
217
|
if (!apiKey) {
|
|
152
|
-
console.log('No API key found in Codex auth
|
|
218
|
+
console.log('No API key found in Codex backup config/auth');
|
|
153
219
|
return null;
|
|
154
220
|
}
|
|
155
221
|
return {
|
|
156
|
-
apiUrl:
|
|
222
|
+
apiUrl: normalizedBaseUrl,
|
|
157
223
|
apiKey: apiKey,
|
|
158
|
-
authType:
|
|
159
|
-
sourceType
|
|
224
|
+
authType: inferAuthTypeFromSource(sourceType),
|
|
225
|
+
sourceType,
|
|
160
226
|
model: model,
|
|
161
227
|
};
|
|
162
228
|
}
|