@adversity/coding-tool-x 3.1.0 → 3.1.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/CHANGELOG.md +15 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +81 -12
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +39 -2
- package/src/config/loader.js +74 -8
- package/src/config/paths.js +105 -33
- package/src/index.js +64 -3
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +30 -18
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +15 -3
- package/src/server/index.js +165 -58
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +27 -18
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +64 -37
- package/src/server/services/codex-channels.js +56 -43
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +26 -12
- package/src/server/services/env-manager.js +126 -18
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +33 -44
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +132 -3
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-CO_2OFES.js +0 -1
- package/dist/web/assets/index-DI8QOi-E.js +0 -14
- package/dist/web/assets/index-uLHGdeZh.css +0 -41
- package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/oauth.js +0 -294
- package/src/server/api/permissions.js +0 -385
- package/src/server/config/oauth-providers.js +0 -68
- package/src/server/services/oauth-callback-server.js +0 -284
- package/src/server/services/oauth-service.js +0 -378
- package/src/server/services/oauth-token-storage.js +0 -135
- package/src/server/services/permission-templates-service.js +0 -308
|
@@ -12,9 +12,18 @@ const { getSchedulerState } = require('../services/channel-scheduler');
|
|
|
12
12
|
const { getChannelHealthStatus, resetChannelHealth } = require('../services/channel-health');
|
|
13
13
|
const { broadcastSchedulerState } = require('../websocket-server');
|
|
14
14
|
const { isGeminiInstalled } = require('../services/gemini-config');
|
|
15
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
testChannelSpeed,
|
|
17
|
+
getLatencyLevel,
|
|
18
|
+
sanitizeBatchConcurrency,
|
|
19
|
+
runWithConcurrencyLimit
|
|
20
|
+
} = require('../services/speed-test');
|
|
16
21
|
const { clearGeminiRedirectCache } = require('../gemini-proxy-server');
|
|
17
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
probeModelAvailability,
|
|
24
|
+
fetchModelsFromProvider
|
|
25
|
+
} = require('../services/model-detector');
|
|
26
|
+
const GEMINI_GATEWAY_SOURCE_TYPE = 'gemini';
|
|
18
27
|
|
|
19
28
|
module.exports = (config) => {
|
|
20
29
|
/**
|
|
@@ -57,11 +66,46 @@ module.exports = (config) => {
|
|
|
57
66
|
return res.status(404).json({ error: '渠道不存在' });
|
|
58
67
|
}
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
const
|
|
69
|
+
const gatewaySourceType = GEMINI_GATEWAY_SOURCE_TYPE;
|
|
70
|
+
const listResult = await fetchModelsFromProvider(channel, 'openai_compatible');
|
|
71
|
+
const listedModels = Array.isArray(listResult.models) ? listResult.models : [];
|
|
72
|
+
let result;
|
|
73
|
+
|
|
74
|
+
if (listedModels.length > 0) {
|
|
75
|
+
result = {
|
|
76
|
+
models: listedModels,
|
|
77
|
+
supported: true,
|
|
78
|
+
cached: !!listResult.cached,
|
|
79
|
+
fallbackUsed: false,
|
|
80
|
+
lastChecked: listResult.lastChecked || new Date().toISOString(),
|
|
81
|
+
error: null,
|
|
82
|
+
errorHint: null
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
const usingConfiguredProbe = !!listResult.disabledByConfig;
|
|
86
|
+
const probe = await probeModelAvailability(channel, gatewaySourceType, {
|
|
87
|
+
stopOnFirstAvailable: false
|
|
88
|
+
});
|
|
89
|
+
const probedModels = Array.isArray(probe.availableModels) ? probe.availableModels : [];
|
|
90
|
+
|
|
91
|
+
result = {
|
|
92
|
+
models: probedModels,
|
|
93
|
+
supported: probedModels.length > 0,
|
|
94
|
+
cached: !!probe.cached || !!listResult.cached,
|
|
95
|
+
fallbackUsed: false,
|
|
96
|
+
lastChecked: probe.lastChecked || listResult.lastChecked || new Date().toISOString(),
|
|
97
|
+
error: probedModels.length > 0 ? null : (listResult.error || '无法获取可用模型'),
|
|
98
|
+
errorHint: probedModels.length > 0
|
|
99
|
+
? (usingConfiguredProbe ? '已按设置跳过 /v1/models,使用默认模型探测结果' : '模型列表接口不可用,已自动切换为模型探测结果')
|
|
100
|
+
: (listResult.errorHint || (usingConfiguredProbe
|
|
101
|
+
? '已按设置跳过 /v1/models,且默认模型探测无可用结果'
|
|
102
|
+
: '模型列表接口不可用且模型探测无可用结果'))
|
|
103
|
+
};
|
|
104
|
+
}
|
|
62
105
|
|
|
63
106
|
res.json({
|
|
64
107
|
channelId: id,
|
|
108
|
+
gatewaySourceType,
|
|
65
109
|
models: result.models,
|
|
66
110
|
supported: result.supported,
|
|
67
111
|
cached: result.cached,
|
|
@@ -90,17 +134,38 @@ module.exports = (config) => {
|
|
|
90
134
|
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
91
135
|
}
|
|
92
136
|
|
|
93
|
-
const {
|
|
137
|
+
const {
|
|
138
|
+
name,
|
|
139
|
+
baseUrl,
|
|
140
|
+
apiKey,
|
|
141
|
+
model,
|
|
142
|
+
websiteUrl,
|
|
143
|
+
enabled,
|
|
144
|
+
weight,
|
|
145
|
+
maxConcurrency,
|
|
146
|
+
modelRedirects,
|
|
147
|
+
speedTestModel,
|
|
148
|
+
presetId,
|
|
149
|
+
gatewaySourceType
|
|
150
|
+
} = req.body;
|
|
151
|
+
|
|
152
|
+
if (!name || !baseUrl) {
|
|
153
|
+
return res.status(400).json({ error: 'Missing required fields: name, baseUrl' });
|
|
154
|
+
}
|
|
94
155
|
|
|
95
|
-
if (!
|
|
96
|
-
return res.status(400).json({ error: 'Missing required fields:
|
|
156
|
+
if (!apiKey) {
|
|
157
|
+
return res.status(400).json({ error: 'Missing required fields: apiKey' });
|
|
97
158
|
}
|
|
98
159
|
|
|
99
160
|
const channel = createChannel(name, baseUrl, apiKey, model || 'gemini-2.5-pro', {
|
|
100
161
|
websiteUrl,
|
|
101
162
|
enabled,
|
|
102
163
|
weight,
|
|
103
|
-
maxConcurrency
|
|
164
|
+
maxConcurrency,
|
|
165
|
+
modelRedirects: modelRedirects || [],
|
|
166
|
+
speedTestModel: speedTestModel || null,
|
|
167
|
+
presetId: presetId || null,
|
|
168
|
+
gatewaySourceType
|
|
104
169
|
});
|
|
105
170
|
res.json(channel);
|
|
106
171
|
broadcastSchedulerState('gemini', getSchedulerState('gemini'));
|
|
@@ -138,14 +203,14 @@ module.exports = (config) => {
|
|
|
138
203
|
* DELETE /api/gemini/channels/:channelId
|
|
139
204
|
* 删除渠道
|
|
140
205
|
*/
|
|
141
|
-
router.delete('/:channelId', (req, res) => {
|
|
206
|
+
router.delete('/:channelId', async (req, res) => {
|
|
142
207
|
try {
|
|
143
208
|
if (!isGeminiInstalled()) {
|
|
144
209
|
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
145
210
|
}
|
|
146
211
|
|
|
147
212
|
const { channelId } = req.params;
|
|
148
|
-
const result = deleteChannel(channelId);
|
|
213
|
+
const result = await deleteChannel(channelId);
|
|
149
214
|
res.json(result);
|
|
150
215
|
broadcastSchedulerState('gemini', getSchedulerState('gemini'));
|
|
151
216
|
} catch (err) {
|
|
@@ -215,8 +280,10 @@ module.exports = (config) => {
|
|
|
215
280
|
return res.status(404).json({ error: '渠道不存在' });
|
|
216
281
|
}
|
|
217
282
|
|
|
218
|
-
const
|
|
283
|
+
const speedTestType = GEMINI_GATEWAY_SOURCE_TYPE;
|
|
284
|
+
const result = await testChannelSpeed(channel, timeout, speedTestType);
|
|
219
285
|
result.level = getLatencyLevel(result.latency);
|
|
286
|
+
result.gatewaySourceType = speedTestType;
|
|
220
287
|
|
|
221
288
|
res.json(result);
|
|
222
289
|
} catch (error) {
|
|
@@ -235,18 +302,37 @@ module.exports = (config) => {
|
|
|
235
302
|
return res.json({ results: [], message: 'Gemini CLI not installed' });
|
|
236
303
|
}
|
|
237
304
|
|
|
238
|
-
const { timeout = 10000 } = req.body;
|
|
305
|
+
const { timeout = 10000, concurrency } = req.body || {};
|
|
239
306
|
const data = getChannels();
|
|
240
307
|
const channels = data.channels || [];
|
|
308
|
+
const safeConcurrency = sanitizeBatchConcurrency(concurrency);
|
|
241
309
|
|
|
242
310
|
if (channels.length === 0) {
|
|
243
311
|
return res.json({ results: [], message: '没有可测试的渠道' });
|
|
244
312
|
}
|
|
245
313
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
314
|
+
const results = await runWithConcurrencyLimit(
|
|
315
|
+
channels,
|
|
316
|
+
safeConcurrency,
|
|
317
|
+
async channel => {
|
|
318
|
+
const speedTestType = GEMINI_GATEWAY_SOURCE_TYPE;
|
|
319
|
+
const result = await testChannelSpeed(channel, timeout, speedTestType);
|
|
320
|
+
result.level = getLatencyLevel(result.latency);
|
|
321
|
+
result.gatewaySourceType = speedTestType;
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// 成功在前,成功结果按延迟升序
|
|
327
|
+
results.sort((a, b) => {
|
|
328
|
+
if (a.success && !b.success) return -1;
|
|
329
|
+
if (!a.success && b.success) return 1;
|
|
330
|
+
if (a.success && b.success) {
|
|
331
|
+
const aLatency = (a.latency === null || a.latency === undefined) ? Infinity : a.latency;
|
|
332
|
+
const bLatency = (b.latency === null || b.latency === undefined) ? Infinity : b.latency;
|
|
333
|
+
return aLatency - bLatency;
|
|
334
|
+
}
|
|
335
|
+
return 0;
|
|
250
336
|
});
|
|
251
337
|
|
|
252
338
|
res.json({
|
|
@@ -255,7 +341,8 @@ module.exports = (config) => {
|
|
|
255
341
|
total: results.length,
|
|
256
342
|
success: results.filter(r => r.success).length,
|
|
257
343
|
failed: results.filter(r => !r.success).length,
|
|
258
|
-
avgLatency: calculateAvgLatency(results)
|
|
344
|
+
avgLatency: calculateAvgLatency(results),
|
|
345
|
+
concurrency: safeConcurrency
|
|
259
346
|
}
|
|
260
347
|
});
|
|
261
348
|
} catch (error) {
|
|
@@ -294,7 +381,9 @@ module.exports = (config) => {
|
|
|
294
381
|
|
|
295
382
|
// 计算平均延迟
|
|
296
383
|
function calculateAvgLatency(results) {
|
|
297
|
-
const successResults = results.filter(
|
|
384
|
+
const successResults = results.filter(
|
|
385
|
+
r => r.success && r.latency !== null && r.latency !== undefined
|
|
386
|
+
);
|
|
298
387
|
if (successResults.length === 0) return null;
|
|
299
388
|
const sum = successResults.reduce((acc, r) => acc + r.latency, 0);
|
|
300
389
|
return Math.round(sum / successResults.length);
|
|
@@ -14,9 +14,9 @@ const {
|
|
|
14
14
|
hasBackup
|
|
15
15
|
} = require('../services/gemini-settings-manager');
|
|
16
16
|
const { getChannels, getEnabledChannels } = require('../services/gemini-channels');
|
|
17
|
+
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
17
18
|
const fs = require('fs');
|
|
18
19
|
const path = require('path');
|
|
19
|
-
const os = require('os');
|
|
20
20
|
|
|
21
21
|
function sanitizeChannel(channel) {
|
|
22
22
|
if (!channel) return null;
|
|
@@ -26,14 +26,24 @@ function sanitizeChannel(channel) {
|
|
|
26
26
|
|
|
27
27
|
// 保存激活渠道ID
|
|
28
28
|
function saveActiveChannelId(channelId) {
|
|
29
|
-
|
|
29
|
+
ensureStorageDirMigrated();
|
|
30
|
+
const filePath = PATHS.activeChannel.gemini;
|
|
31
|
+
const dir = path.dirname(filePath);
|
|
30
32
|
if (!fs.existsSync(dir)) {
|
|
31
33
|
fs.mkdirSync(dir, { recursive: true });
|
|
32
34
|
}
|
|
33
|
-
const filePath = path.join(dir, 'gemini-active-channel.json');
|
|
34
35
|
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
function removeActiveChannelFile() {
|
|
39
|
+
ensureStorageDirMigrated();
|
|
40
|
+
const filePath = PATHS.activeChannel.gemini;
|
|
41
|
+
if (fs.existsSync(filePath)) {
|
|
42
|
+
fs.unlinkSync(filePath);
|
|
43
|
+
console.log('[Gemini Proxy] Removed gemini-active-channel.json');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
// 获取代理状态
|
|
38
48
|
router.get('/status', (req, res) => {
|
|
39
49
|
try {
|
|
@@ -127,11 +137,7 @@ router.post('/stop', async (req, res) => {
|
|
|
127
137
|
console.log('[Gemini Proxy] Restored settings from backup');
|
|
128
138
|
|
|
129
139
|
// 删除 gemini-active-channel.json
|
|
130
|
-
|
|
131
|
-
if (fs.existsSync(activeChannelPath)) {
|
|
132
|
-
fs.unlinkSync(activeChannelPath);
|
|
133
|
-
console.log('[Gemini Proxy] Removed gemini-active-channel.json');
|
|
134
|
-
}
|
|
140
|
+
removeActiveChannelFile();
|
|
135
141
|
|
|
136
142
|
const response = {
|
|
137
143
|
success: true,
|
|
@@ -359,7 +359,7 @@ module.exports = (config) => {
|
|
|
359
359
|
|
|
360
360
|
try {
|
|
361
361
|
// 获取终端启动命令
|
|
362
|
-
const { command, terminalId, terminalName } = getTerminalLaunchCommand(projectPath, null, geminiCommand);
|
|
362
|
+
const { command, terminalId, terminalName } = getTerminalLaunchCommand(projectPath, null, 'gemini', geminiCommand);
|
|
363
363
|
|
|
364
364
|
console.log(`[Gemini] Launching terminal: ${terminalName} (${terminalId})`);
|
|
365
365
|
console.log(`[Gemini] Resuming session: ${sessionId} (index ${resumeIndex})`);
|
|
@@ -7,10 +7,11 @@ module.exports = (config) => {
|
|
|
7
7
|
/**
|
|
8
8
|
* GET /api/health-check - 健康检查所有项目
|
|
9
9
|
*/
|
|
10
|
-
router.get('/', (req, res) => {
|
|
10
|
+
router.get('/', async (req, res) => {
|
|
11
11
|
try {
|
|
12
|
-
const projects = getProjects(config);
|
|
13
|
-
const
|
|
12
|
+
const projects = await getProjects(config);
|
|
13
|
+
const projectList = Array.isArray(projects) ? projects : [];
|
|
14
|
+
const result = healthCheckAllProjects(projectList);
|
|
14
15
|
|
|
15
16
|
res.json({
|
|
16
17
|
success: true,
|
package/src/server/api/mcp.js
CHANGED
|
@@ -177,7 +177,7 @@ router.post('/import/:platform', async (req, res) => {
|
|
|
177
177
|
try {
|
|
178
178
|
const { platform } = req.params;
|
|
179
179
|
|
|
180
|
-
if (!['claude', 'codex', 'gemini'].includes(platform)) {
|
|
180
|
+
if (!['claude', 'codex', 'gemini', 'opencode'].includes(platform)) {
|
|
181
181
|
return res.status(400).json({
|
|
182
182
|
success: false,
|
|
183
183
|
error: `无效的平台: ${platform}`
|
|
@@ -283,7 +283,7 @@ router.get('/export', (req, res) => {
|
|
|
283
283
|
try {
|
|
284
284
|
const format = req.query.format || 'json';
|
|
285
285
|
|
|
286
|
-
if (!['json', 'claude', 'codex'].includes(format)) {
|
|
286
|
+
if (!['json', 'claude', 'codex', 'opencode'].includes(format)) {
|
|
287
287
|
return res.status(400).json({
|
|
288
288
|
success: false,
|
|
289
289
|
error: `无效的导出格式: ${format}`
|
|
@@ -312,7 +312,7 @@ router.get('/export/download', (req, res) => {
|
|
|
312
312
|
try {
|
|
313
313
|
const format = req.query.format || 'json';
|
|
314
314
|
|
|
315
|
-
if (!['json', 'claude', 'codex'].includes(format)) {
|
|
315
|
+
if (!['json', 'claude', 'codex', 'opencode'].includes(format)) {
|
|
316
316
|
return res.status(400).json({
|
|
317
317
|
success: false,
|
|
318
318
|
error: `无效的导出格式: ${format}`
|