protocol-proxy 2.0.1 → 2.0.3

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.
@@ -21,6 +21,50 @@ migrateOldConfig();
21
21
 
22
22
  let configCache = null;
23
23
 
24
+ // 迁移旧格式:target → providerId
25
+ function migrateTargetToProvider(config) {
26
+ if (!Array.isArray(config.proxies)) return config;
27
+ let changed = false;
28
+
29
+ for (const proxy of config.proxies) {
30
+ if (!proxy.target) continue;
31
+
32
+ // 有 target 但没有 providerId → 从 target 创建供应商
33
+ if (!proxy.providerId) {
34
+ const t = proxy.target;
35
+ const provider = {
36
+ id: 'provider-' + Date.now(),
37
+ name: t.providerName || t.providerUrl,
38
+ url: t.providerUrl,
39
+ protocol: t.protocol || 'openai',
40
+ apiKey: t.apiKey || '',
41
+ models: Array.isArray(t.models) ? t.models : [],
42
+ };
43
+ config.providers = config.providers || [];
44
+ config.providers.push(provider);
45
+ proxy.providerId = provider.id;
46
+ proxy.defaultModel = t.defaultModel || '';
47
+ delete proxy.target;
48
+ changed = true;
49
+ } else {
50
+ // 有 target 也有 providerId → 迁移 apiKey 到供应商,删除 target
51
+ const provider = (config.providers || []).find(p => p.id === proxy.providerId);
52
+ if (provider && proxy.target.apiKey && !provider.apiKey) {
53
+ provider.apiKey = proxy.target.apiKey;
54
+ changed = true;
55
+ }
56
+ delete proxy.target;
57
+ changed = true;
58
+ }
59
+ }
60
+
61
+ if (changed) {
62
+ configCache = config;
63
+ saveConfig(config);
64
+ }
65
+ return config;
66
+ }
67
+
24
68
  function normalizeModels(models) {
25
69
  if (!Array.isArray(models)) return [];
26
70
  return Array.from(new Set(
@@ -58,7 +102,9 @@ function loadConfig() {
58
102
  return configCache;
59
103
  }
60
104
  const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
61
- configCache = normalizeConfig(JSON.parse(raw));
105
+ let config = normalizeConfig(JSON.parse(raw));
106
+ config = migrateTargetToProvider(config);
107
+ configCache = config;
62
108
  return configCache;
63
109
  } catch (err) {
64
110
  console.error('加载配置失败:', err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "protocol-proxy",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "OpenAI / Anthropic 协议转换透明代理",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -40,14 +40,19 @@ function initProviderDropdown() {
40
40
  const dropdown = document.getElementById('provider-dropdown');
41
41
  const addNameInput = document.getElementById('provider-add-name');
42
42
  const addUrlInput = document.getElementById('provider-add-url');
43
+ const addKeyInput = document.getElementById('provider-add-key');
43
44
  const addBtn = document.getElementById('provider-add-btn');
44
45
 
45
46
  trigger.addEventListener('click', (e) => {
46
47
  e.stopPropagation();
47
48
  dropdown.classList.toggle('open');
48
49
  if (dropdown.classList.contains('open')) {
50
+ editingProviderId = null;
49
51
  addNameInput.value = '';
50
52
  addUrlInput.value = '';
53
+ addKeyInput.value = '';
54
+ addUrlInput.disabled = false;
55
+ addBtn.textContent = '添加';
51
56
  renderProviderOptions();
52
57
  addNameInput.focus();
53
58
  }
@@ -62,27 +67,45 @@ function initProviderDropdown() {
62
67
  addBtn.addEventListener('click', async () => {
63
68
  const name = addNameInput.value.trim();
64
69
  const url = addUrlInput.value.trim();
70
+ const apiKey = addKeyInput.value.trim();
65
71
  if (!name || !url) {
66
72
  showToast('请填写供应商名称和地址', true);
67
73
  return;
68
74
  }
69
75
  try {
70
- const res = await fetch('/api/providers', {
71
- method: 'POST',
72
- headers: { 'Content-Type': 'application/json' },
73
- body: JSON.stringify({ name, url }),
74
- });
76
+ let res;
77
+ if (editingProviderId) {
78
+ // 更新模式
79
+ res = await fetch(`/api/providers/${editingProviderId}`, {
80
+ method: 'PUT',
81
+ headers: { 'Content-Type': 'application/json' },
82
+ body: JSON.stringify({ name, apiKey }),
83
+ });
84
+ } else {
85
+ // 新增模式
86
+ res = await fetch('/api/providers', {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ name, url, apiKey }),
90
+ });
91
+ }
75
92
  if (!res.ok) {
76
93
  const err = await res.json();
77
- showToast(err.error || '添加失败', true);
94
+ showToast(err.error || '操作失败', true);
78
95
  return;
79
96
  }
80
97
  const provider = await res.json();
98
+ editingProviderId = null;
99
+ addUrlInput.disabled = false;
100
+ addBtn.textContent = '添加';
101
+ addNameInput.value = '';
102
+ addUrlInput.value = '';
103
+ addKeyInput.value = '';
81
104
  await loadProviders();
82
105
  selectProvider(provider.id);
83
- dropdown.classList.remove('open');
106
+ renderProviderOptions();
84
107
  } catch (err) {
85
- showToast('添加失败: ' + err.message, true);
108
+ showToast('操作失败: ' + err.message, true);
86
109
  }
87
110
  });
88
111
 
@@ -95,6 +118,8 @@ function initProviderDropdown() {
95
118
  });
96
119
  }
97
120
 
121
+ let editingProviderId = null;
122
+
98
123
  function renderProviderOptions() {
99
124
  const container = document.getElementById('provider-dropdown-options');
100
125
  const currentId = document.getElementById('provider-id').value;
@@ -103,7 +128,10 @@ function renderProviderOptions() {
103
128
  <div class="model-option${p.id === currentId ? ' selected' : ''}" data-id="${escapeHtml(p.id)}">
104
129
  <span class="model-option-name">${escapeHtml(p.name)}</span>
105
130
  ${p.name !== p.url ? `<span style="color:#64748b;font-size:12px;margin-left:4px">${escapeHtml(p.url)}</span>` : ''}
106
- <button type="button" class="model-option-delete" data-delete-id="${escapeHtml(p.id)}" title="删除此供应商">&times;</button>
131
+ <span style="margin-left:auto;display:flex;gap:4px">
132
+ <button type="button" class="model-option-delete" data-edit-id="${escapeHtml(p.id)}" title="编辑此供应商" style="color:#60a5fa">&times;</button>
133
+ <button type="button" class="model-option-delete" data-delete-id="${escapeHtml(p.id)}" title="删除此供应商">&times;</button>
134
+ </span>
107
135
  </div>
108
136
  `).join('');
109
137
 
@@ -119,7 +147,29 @@ function renderProviderOptions() {
119
147
  });
120
148
  });
121
149
 
122
- container.querySelectorAll('.model-option-delete').forEach(btn => {
150
+ // 编辑供应商
151
+ container.querySelectorAll('[data-edit-id]').forEach(btn => {
152
+ btn.addEventListener('click', async (e) => {
153
+ e.stopPropagation();
154
+ const id = btn.dataset.editId;
155
+ try {
156
+ const res = await fetch(`/api/providers/${id}`);
157
+ if (!res.ok) throw new Error('加载失败');
158
+ const p = await res.json();
159
+ editingProviderId = id;
160
+ document.getElementById('provider-add-name').value = p.name;
161
+ document.getElementById('provider-add-url').value = p.url;
162
+ document.getElementById('provider-add-key').value = p.apiKey || '';
163
+ document.getElementById('provider-add-url').disabled = true;
164
+ document.getElementById('provider-add-btn').textContent = '更新';
165
+ } catch (err) {
166
+ showToast('加载供应商失败: ' + err.message, true);
167
+ }
168
+ });
169
+ });
170
+
171
+ // 删除供应商
172
+ container.querySelectorAll('[data-delete-id]').forEach(btn => {
123
173
  btn.addEventListener('click', async (e) => {
124
174
  e.stopPropagation();
125
175
  const id = btn.dataset.deleteId;
package/public/index.html CHANGED
@@ -85,6 +85,7 @@
85
85
  <div class="model-add-section">
86
86
  <input type="text" class="model-add-input" id="provider-add-name" placeholder="供应商名称">
87
87
  <input type="url" class="model-add-input" id="provider-add-url" placeholder="https://api.example.com">
88
+ <input type="password" class="model-add-input" id="provider-add-key" placeholder="API Key">
88
89
  <button type="button" class="btn btn-primary btn-sm" id="provider-add-btn">添加</button>
89
90
  </div>
90
91
  </div>
package/server.js CHANGED
@@ -198,7 +198,7 @@ async function init() {
198
198
  if (req.body.name !== undefined) updates.name = req.body.name;
199
199
  if (req.body.url !== undefined) updates.url = req.body.url;
200
200
  if (req.body.protocol !== undefined) updates.protocol = req.body.protocol;
201
- if (req.body.apiKey !== undefined) updates.apiKey = req.body.apiKey;
201
+ if (req.body.apiKey !== undefined && req.body.apiKey !== '') updates.apiKey = req.body.apiKey;
202
202
  if (req.body.models !== undefined) updates.models = req.body.models;
203
203
 
204
204
  const updated = configStore.updateProvider(req.params.id, updates);
@@ -235,7 +235,12 @@ async function init() {
235
235
  const proxies = configStore.getProxies().map(p => {
236
236
  const provider = configStore.getProviderById(p.providerId);
237
237
  return {
238
- ...p,
238
+ id: p.id,
239
+ name: p.name,
240
+ port: p.port,
241
+ requireAuth: p.requireAuth,
242
+ authToken: p.authToken,
243
+ providerId: p.providerId,
239
244
  providerName: provider?.name || '',
240
245
  providerUrl: provider?.url || '',
241
246
  protocol: provider?.protocol || '',