protocol-proxy 2.4.0 → 2.5.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/lib/config-store.js +295 -260
- package/lib/converters/gemini-to-anthropic.js +286 -277
- package/lib/converters/gemini-to-openai.js +255 -240
- package/lib/converters/openai-to-anthropic.js +368 -329
- package/lib/proxy-server.js +636 -491
- package/package.json +1 -1
- package/public/app.js +1296 -1222
- package/public/index.html +7 -2
- package/public/style.css +1448 -1344
- package/server.js +767 -747
package/lib/config-store.js
CHANGED
|
@@ -1,260 +1,295 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const crypto = require('crypto');
|
|
5
|
-
|
|
6
|
-
const CONFIG_PATH = path.join(os.homedir(), '.protocol-proxy', 'proxies.json');
|
|
7
|
-
|
|
8
|
-
// 迁移旧配置(从包目录到用户主目录)
|
|
9
|
-
const OLD_CONFIG_PATH = process.pkg
|
|
10
|
-
? path.join(path.dirname(process.execPath), 'config', 'proxies.json')
|
|
11
|
-
: path.join(__dirname, '..', 'config', 'proxies.json');
|
|
12
|
-
|
|
13
|
-
function migrateOldConfig() {
|
|
14
|
-
if (fs.existsSync(CONFIG_PATH) || !fs.existsSync(OLD_CONFIG_PATH)) return;
|
|
15
|
-
try {
|
|
16
|
-
const dir = path.dirname(CONFIG_PATH);
|
|
17
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
18
|
-
fs.copyFileSync(OLD_CONFIG_PATH, CONFIG_PATH);
|
|
19
|
-
} catch (err) { console.error('[Config] 迁移旧配置失败:', err.message); }
|
|
20
|
-
}
|
|
21
|
-
migrateOldConfig();
|
|
22
|
-
|
|
23
|
-
let configCache = null;
|
|
24
|
-
|
|
25
|
-
// 迁移旧格式:target → providerId
|
|
26
|
-
function migrateTargetToProvider(config) {
|
|
27
|
-
if (!Array.isArray(config.proxies)) return config;
|
|
28
|
-
let changed = false;
|
|
29
|
-
|
|
30
|
-
for (const proxy of config.proxies) {
|
|
31
|
-
if (!proxy.target) continue;
|
|
32
|
-
|
|
33
|
-
// 有 target 但没有 providerId → 从 target 创建供应商
|
|
34
|
-
if (!proxy.providerId) {
|
|
35
|
-
const t = proxy.target;
|
|
36
|
-
const provider = {
|
|
37
|
-
id: crypto.randomUUID(),
|
|
38
|
-
name: t.providerName || t.providerUrl,
|
|
39
|
-
url: t.providerUrl,
|
|
40
|
-
protocol: t.protocol || 'openai',
|
|
41
|
-
apiKey: t.apiKey || '',
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
config.providers.
|
|
46
|
-
|
|
47
|
-
proxy.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
seen.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function
|
|
200
|
-
const config = loadConfig();
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
loadConfig
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
const CONFIG_PATH = path.join(os.homedir(), '.protocol-proxy', 'proxies.json');
|
|
7
|
+
|
|
8
|
+
// 迁移旧配置(从包目录到用户主目录)
|
|
9
|
+
const OLD_CONFIG_PATH = process.pkg
|
|
10
|
+
? path.join(path.dirname(process.execPath), 'config', 'proxies.json')
|
|
11
|
+
: path.join(__dirname, '..', 'config', 'proxies.json');
|
|
12
|
+
|
|
13
|
+
function migrateOldConfig() {
|
|
14
|
+
if (fs.existsSync(CONFIG_PATH) || !fs.existsSync(OLD_CONFIG_PATH)) return;
|
|
15
|
+
try {
|
|
16
|
+
const dir = path.dirname(CONFIG_PATH);
|
|
17
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
fs.copyFileSync(OLD_CONFIG_PATH, CONFIG_PATH);
|
|
19
|
+
} catch (err) { console.error('[Config] 迁移旧配置失败:', err.message); }
|
|
20
|
+
}
|
|
21
|
+
migrateOldConfig();
|
|
22
|
+
|
|
23
|
+
let configCache = null;
|
|
24
|
+
|
|
25
|
+
// 迁移旧格式:target → providerId
|
|
26
|
+
function migrateTargetToProvider(config) {
|
|
27
|
+
if (!Array.isArray(config.proxies)) return config;
|
|
28
|
+
let changed = false;
|
|
29
|
+
|
|
30
|
+
for (const proxy of config.proxies) {
|
|
31
|
+
if (!proxy.target) continue;
|
|
32
|
+
|
|
33
|
+
// 有 target 但没有 providerId → 从 target 创建供应商
|
|
34
|
+
if (!proxy.providerId) {
|
|
35
|
+
const t = proxy.target;
|
|
36
|
+
const provider = {
|
|
37
|
+
id: crypto.randomUUID(),
|
|
38
|
+
name: t.providerName || t.providerUrl,
|
|
39
|
+
url: t.providerUrl,
|
|
40
|
+
protocol: t.protocol || 'openai',
|
|
41
|
+
apiKey: t.apiKey || '',
|
|
42
|
+
apiKeys: t.apiKey ? [{ key: t.apiKey, alias: '' }] : [],
|
|
43
|
+
models: Array.isArray(t.models) ? t.models : [],
|
|
44
|
+
};
|
|
45
|
+
config.providers = config.providers || [];
|
|
46
|
+
config.providers.push(provider);
|
|
47
|
+
proxy.providerId = provider.id;
|
|
48
|
+
proxy.defaultModel = t.defaultModel || '';
|
|
49
|
+
delete proxy.target;
|
|
50
|
+
changed = true;
|
|
51
|
+
} else {
|
|
52
|
+
// 有 target 也有 providerId → 迁移 apiKey 到供应商,删除 target
|
|
53
|
+
const provider = (config.providers || []).find(p => p.id === proxy.providerId);
|
|
54
|
+
if (provider && proxy.target.apiKey && !provider.apiKey) {
|
|
55
|
+
provider.apiKey = proxy.target.apiKey;
|
|
56
|
+
changed = true;
|
|
57
|
+
}
|
|
58
|
+
delete proxy.target;
|
|
59
|
+
changed = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (changed) {
|
|
64
|
+
configCache = config;
|
|
65
|
+
saveConfig(config);
|
|
66
|
+
}
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeModels(models) {
|
|
71
|
+
if (!Array.isArray(models)) return [];
|
|
72
|
+
return Array.from(new Set(
|
|
73
|
+
models.filter(m => typeof m === 'string').map(m => m.trim()).filter(Boolean)
|
|
74
|
+
));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeRoutingStrategy(strategy) {
|
|
78
|
+
return ['primary_fallback', 'round_robin', 'weighted', 'fastest'].includes(strategy)
|
|
79
|
+
? strategy
|
|
80
|
+
: 'primary_fallback';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeProviderPool(pool) {
|
|
84
|
+
if (!Array.isArray(pool)) return [];
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
const result = [];
|
|
87
|
+
|
|
88
|
+
for (const item of pool) {
|
|
89
|
+
if (!item || typeof item !== 'object') continue;
|
|
90
|
+
const providerId = typeof item.providerId === 'string' ? item.providerId.trim() : '';
|
|
91
|
+
if (!providerId) continue;
|
|
92
|
+
const model = typeof item.model === 'string' ? item.model.trim() : '';
|
|
93
|
+
const key = `${providerId}\0${model}`;
|
|
94
|
+
if (seen.has(key)) continue;
|
|
95
|
+
seen.add(key);
|
|
96
|
+
result.push({
|
|
97
|
+
providerId,
|
|
98
|
+
model,
|
|
99
|
+
weight: Math.max(1, parseInt(item.weight, 10) || 1),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeApiKeys(provider) {
|
|
107
|
+
// apiKeys 数组优先,apiKey 作为 fallback
|
|
108
|
+
// 统一输出为 { key, alias } 格式
|
|
109
|
+
if (Array.isArray(provider.apiKeys) && provider.apiKeys.length > 0) {
|
|
110
|
+
return provider.apiKeys
|
|
111
|
+
.map(k => {
|
|
112
|
+
if (typeof k === 'string' && k.trim()) return { key: k.trim(), alias: '' };
|
|
113
|
+
if (k && typeof k === 'object' && typeof k.key === 'string' && k.key.trim()) {
|
|
114
|
+
return { key: k.key.trim(), alias: typeof k.alias === 'string' ? k.alias.trim() : '', enabled: k.enabled !== false };
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
})
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
if (provider.apiKey && typeof provider.apiKey === 'string' && provider.apiKey.trim()) {
|
|
121
|
+
return [{ key: provider.apiKey.trim(), alias: '' }];
|
|
122
|
+
}
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeProvider(provider) {
|
|
127
|
+
if (!provider) return provider;
|
|
128
|
+
return {
|
|
129
|
+
...provider,
|
|
130
|
+
models: normalizeModels(provider.models),
|
|
131
|
+
apiKeys: normalizeApiKeys(provider),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeProxy(proxy) {
|
|
136
|
+
if (!proxy) return proxy;
|
|
137
|
+
return {
|
|
138
|
+
...proxy,
|
|
139
|
+
routingStrategy: normalizeRoutingStrategy(proxy.routingStrategy),
|
|
140
|
+
providerPool: normalizeProviderPool(proxy.providerPool),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizeConfig(config) {
|
|
145
|
+
const providers = Array.isArray(config?.providers) ? config.providers : [];
|
|
146
|
+
const proxies = Array.isArray(config?.proxies) ? config.proxies : [];
|
|
147
|
+
return {
|
|
148
|
+
...config,
|
|
149
|
+
providers: providers.map(normalizeProvider),
|
|
150
|
+
proxies: proxies.map(normalizeProxy),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function loadConfig() {
|
|
155
|
+
try {
|
|
156
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
157
|
+
configCache = { providers: [], proxies: [] };
|
|
158
|
+
return configCache;
|
|
159
|
+
}
|
|
160
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
161
|
+
let config = normalizeConfig(JSON.parse(raw));
|
|
162
|
+
config = migrateTargetToProvider(config);
|
|
163
|
+
configCache = config;
|
|
164
|
+
return configCache;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error('加载配置失败:', err.message);
|
|
167
|
+
return configCache || { providers: [], proxies: [] };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function saveConfig(config) {
|
|
172
|
+
try {
|
|
173
|
+
const normalizedConfig = normalizeConfig(config);
|
|
174
|
+
const dir = path.dirname(CONFIG_PATH);
|
|
175
|
+
if (!fs.existsSync(dir)) {
|
|
176
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
const tmpPath = CONFIG_PATH + '.tmp';
|
|
179
|
+
fs.writeFileSync(tmpPath, JSON.stringify(normalizedConfig, null, 2), 'utf-8');
|
|
180
|
+
fs.renameSync(tmpPath, CONFIG_PATH);
|
|
181
|
+
configCache = normalizedConfig;
|
|
182
|
+
return true;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.error('保存配置失败:', err.message);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ==================== 供应商 CRUD ====================
|
|
190
|
+
|
|
191
|
+
function getProviders() {
|
|
192
|
+
return loadConfig().providers || [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getProviderById(id) {
|
|
196
|
+
return getProviders().find(p => p.id === id);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function addProvider(provider) {
|
|
200
|
+
const config = loadConfig();
|
|
201
|
+
config.providers = config.providers || [];
|
|
202
|
+
provider.id = provider.id || crypto.randomUUID();
|
|
203
|
+
provider.models = normalizeModels(provider.models);
|
|
204
|
+
config.providers.push(provider);
|
|
205
|
+
saveConfig(config);
|
|
206
|
+
return provider;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function updateProvider(id, updates) {
|
|
210
|
+
const config = loadConfig();
|
|
211
|
+
const idx = (config.providers || []).findIndex(p => p.id === id);
|
|
212
|
+
if (idx === -1) return null;
|
|
213
|
+
if (updates.models !== undefined) {
|
|
214
|
+
updates.models = normalizeModels(updates.models);
|
|
215
|
+
}
|
|
216
|
+
if (updates.apiKeys !== undefined) {
|
|
217
|
+
updates.apiKeys = Array.isArray(updates.apiKeys)
|
|
218
|
+
? updates.apiKeys
|
|
219
|
+
.map(k => {
|
|
220
|
+
if (typeof k === 'string' && k.trim()) return { key: k.trim(), alias: '' };
|
|
221
|
+
if (k && typeof k === 'object' && typeof k.key === 'string' && k.key.trim()) {
|
|
222
|
+
return { key: k.key.trim(), alias: typeof k.alias === 'string' ? k.alias.trim() : '', enabled: k.enabled !== false };
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
})
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
: [];
|
|
228
|
+
}
|
|
229
|
+
config.providers[idx] = { ...config.providers[idx], ...updates, id };
|
|
230
|
+
saveConfig(config);
|
|
231
|
+
return config.providers[idx];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function removeProvider(id) {
|
|
235
|
+
const config = loadConfig();
|
|
236
|
+
const idx = (config.providers || []).findIndex(p => p.id === id);
|
|
237
|
+
if (idx === -1) return null;
|
|
238
|
+
const removed = config.providers.splice(idx, 1)[0];
|
|
239
|
+
saveConfig(config);
|
|
240
|
+
return removed;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ==================== 代理 CRUD ====================
|
|
244
|
+
|
|
245
|
+
function getProxies() {
|
|
246
|
+
return loadConfig().proxies || [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getProxyById(id) {
|
|
250
|
+
return getProxies().find(p => p.id === id);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function addProxy(proxy) {
|
|
254
|
+
const config = loadConfig();
|
|
255
|
+
config.proxies = config.proxies || [];
|
|
256
|
+
proxy.id = proxy.id || crypto.randomUUID();
|
|
257
|
+
config.proxies.push(proxy);
|
|
258
|
+
saveConfig(config);
|
|
259
|
+
return proxy;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function updateProxy(id, updates) {
|
|
263
|
+
const config = loadConfig();
|
|
264
|
+
const idx = (config.proxies || []).findIndex(p => p.id === id);
|
|
265
|
+
if (idx === -1) return null;
|
|
266
|
+
config.proxies[idx] = { ...config.proxies[idx], ...updates, id };
|
|
267
|
+
saveConfig(config);
|
|
268
|
+
return config.proxies[idx];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function removeProxy(id) {
|
|
272
|
+
const config = loadConfig();
|
|
273
|
+
const idx = (config.proxies || []).findIndex(p => p.id === id);
|
|
274
|
+
if (idx === -1) return null;
|
|
275
|
+
const removed = config.proxies.splice(idx, 1)[0];
|
|
276
|
+
saveConfig(config);
|
|
277
|
+
return removed;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = {
|
|
281
|
+
loadConfig,
|
|
282
|
+
saveConfig,
|
|
283
|
+
getProviders,
|
|
284
|
+
getProviderById,
|
|
285
|
+
addProvider,
|
|
286
|
+
updateProvider,
|
|
287
|
+
removeProvider,
|
|
288
|
+
getProxies,
|
|
289
|
+
getProxyById,
|
|
290
|
+
addProxy,
|
|
291
|
+
updateProxy,
|
|
292
|
+
removeProxy,
|
|
293
|
+
normalizeRoutingStrategy,
|
|
294
|
+
normalizeProviderPool,
|
|
295
|
+
};
|