aicodeswitch 5.2.13 → 6.0.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/bin/restore.js CHANGED
@@ -5,7 +5,7 @@ const chalk = require('chalk');
5
5
  const boxen = require('boxen');
6
6
  const ora = require('ora');
7
7
  const { parseToml, stringifyToml, mergeJsonSettings, mergeTomlSettings, atomicWriteFile } = require('./utils/config-helpers');
8
- const { CLAUDE_SETTINGS_MANAGED_FIELDS, CLAUDE_JSON_MANAGED_FIELDS, CODEX_CONFIG_MANAGED_FIELDS, CODEX_AUTH_MANAGED_FIELDS } = require('./utils/managed-fields');
8
+ const { CLAUDE_SETTINGS_MANAGED_FIELDS, CLAUDE_JSON_MANAGED_FIELDS, CODEX_CONFIG_MANAGED_FIELDS, CODEX_AUTH_MANAGED_FIELDS, OPENCODE_CONFIG_MANAGED_FIELDS } = require('./utils/managed-fields');
9
9
  const { isServerRunning, getServerInfo } = require('./utils/get-server');
10
10
  const { findPidByPort } = require('./utils/port-utils');
11
11
 
@@ -240,10 +240,57 @@ const restoreCodexConfig = () => {
240
240
  return results;
241
241
  };
242
242
 
243
+ // 恢复 OpenCode 配置(使用智能合并)
244
+ const restoreOpencodeConfig = () => {
245
+ const results = {
246
+ restored: [],
247
+ notFound: [],
248
+ errors: []
249
+ };
250
+
251
+ try {
252
+ const homeDir = os.homedir();
253
+ const opencodeConfigPath = path.join(homeDir, '.config', 'opencode', 'opencode.json');
254
+ const opencodeConfigBakPath = `${opencodeConfigPath}.aicodeswitch_backup`;
255
+
256
+ if (fs.existsSync(opencodeConfigBakPath)) {
257
+ try {
258
+ const backupConfig = JSON.parse(fs.readFileSync(opencodeConfigBakPath, 'utf-8'));
259
+ let currentConfig = {};
260
+ if (fs.existsSync(opencodeConfigPath)) {
261
+ try {
262
+ currentConfig = JSON.parse(fs.readFileSync(opencodeConfigPath, 'utf-8'));
263
+ } catch (e) {
264
+ // 忽略解析错误
265
+ }
266
+ }
267
+
268
+ const mergedConfig = mergeJsonSettings(
269
+ backupConfig,
270
+ currentConfig,
271
+ OPENCODE_CONFIG_MANAGED_FIELDS
272
+ );
273
+
274
+ atomicWriteFile(opencodeConfigPath, JSON.stringify(mergedConfig, null, 2));
275
+ fs.unlinkSync(opencodeConfigBakPath);
276
+ results.restored.push('opencode.json');
277
+ } catch (error) {
278
+ results.errors.push({ file: 'opencode.json', error: error.message });
279
+ }
280
+ } else {
281
+ results.notFound.push('opencode.json.aicodeswitch_backup');
282
+ }
283
+ } catch (err) {
284
+ results.errors.push({ file: 'opencode.json', error: err.message });
285
+ }
286
+
287
+ return results;
288
+ };
289
+
243
290
  // 显示恢复结果
244
291
  const showRestoreResult = (target, results) => {
245
- const targetName = target === 'claude-code' ? 'Claude Code' : 'Codex';
246
- const targetColor = target === 'claude-code' ? chalk.cyan : chalk.magenta;
292
+ const targetName = target === 'claude-code' ? 'Claude Code' : target === 'opencode' ? 'OpenCode' : 'Codex';
293
+ const targetColor = target === 'claude-code' ? chalk.cyan : target === 'opencode' ? chalk.blue : chalk.magenta;
247
294
 
248
295
  let message = targetColor.bold(`${targetName} Configuration Restore\n\n`);
249
296
 
@@ -276,6 +323,8 @@ const showRestoreResult = (target, results) => {
276
323
  message += chalk.yellow.bold('⚠️ Important:\n\n');
277
324
  if (target === 'claude-code') {
278
325
  message += chalk.white('Please restart ') + chalk.cyan.bold('Claude Code') + chalk.white(' to apply the restored configuration.\n');
326
+ } else if (target === 'opencode') {
327
+ message += chalk.white('Please restart ') + chalk.blue.bold('OpenCode') + chalk.white(' to apply the restored configuration.\n');
279
328
  } else {
280
329
  message += chalk.white('Please restart ') + chalk.magenta.bold('Codex') + chalk.white(' to apply the restored configuration.\n');
281
330
  }
@@ -307,7 +356,7 @@ const restore = async () => {
307
356
 
308
357
  console.log('\n');
309
358
 
310
- const validTargets = ['claude-code', 'codex', undefined];
359
+ const validTargets = ['claude-code', 'codex', 'opencode', undefined];
311
360
 
312
361
  if (target && !validTargets.includes(target)) {
313
362
  console.log(boxen(
@@ -316,6 +365,7 @@ const restore = async () => {
316
365
  chalk.white('Targets:\n') +
317
366
  chalk.white(' claude-code Restore Claude Code configuration\n') +
318
367
  chalk.white(' codex Restore Codex configuration\n') +
368
+ chalk.white(' opencode Restore OpenCode configuration\n') +
319
369
  chalk.white(' (no arg) Restore all configurations\n'),
320
370
  {
321
371
  padding: 1,
@@ -379,6 +429,19 @@ const restore = async () => {
379
429
  showRestoreResult('codex', codexResults);
380
430
  }
381
431
 
432
+ if (target === 'opencode' || !target) {
433
+ if (!target) console.log('');
434
+
435
+ const spinner = ora({
436
+ text: chalk.blue('Restoring OpenCode configuration...'),
437
+ color: 'blue'
438
+ }).start();
439
+
440
+ const opencodeResults = restoreOpencodeConfig();
441
+ spinner.succeed(chalk.green('OpenCode restore complete'));
442
+ showRestoreResult('opencode', opencodeResults);
443
+ }
444
+
382
445
  // 停用所有激活的路由
383
446
  console.log('');
384
447
  const routesSpinner = ora({
@@ -56,9 +56,21 @@ const CODEX_AUTH_MANAGED_FIELDS = [
56
56
  'OPENAI_API_KEY',
57
57
  ];
58
58
 
59
+ /**
60
+ * OpenCode opencode.json 管理字段列表
61
+ * 托管整个 provider.aicodeswitch 段与 model/small_model/mcp
62
+ */
63
+ const OPENCODE_CONFIG_MANAGED_FIELDS = [
64
+ 'provider.aicodeswitch',
65
+ 'model',
66
+ 'small_model',
67
+ 'mcp',
68
+ ];
69
+
59
70
  module.exports = {
60
71
  CLAUDE_SETTINGS_MANAGED_FIELDS,
61
72
  CLAUDE_JSON_MANAGED_FIELDS,
62
73
  CODEX_CONFIG_MANAGED_FIELDS,
63
74
  CODEX_AUTH_MANAGED_FIELDS,
75
+ OPENCODE_CONFIG_MANAGED_FIELDS,
64
76
  };
@@ -314,6 +314,14 @@ function detectTurnEnd(agent, downstream, responseBody) {
314
314
  return true;
315
315
  return null;
316
316
  }
317
+ if (agent === 'opencode') {
318
+ // OpenAI Chat Completions:tool_calls 仍在 → 继续;finish_reason=stop / [DONE] → 本轮结束
319
+ if (/"finish_reason"\s*:\s*"tool_calls"/.test(raw))
320
+ return false;
321
+ if (/"finish_reason"\s*:\s*"(stop|length|content_filter)"/.test(raw) || /\[DONE\]/.test(raw))
322
+ return true;
323
+ return null;
324
+ }
317
325
  // Claude:看 stop_reason(流式 message_delta 或非流式 JSON 都带该字段)
318
326
  const m = raw.match(/"stop_reason"\s*:\s*"([a-z_]+)"/);
319
327
  const stopReason = m ? m[1] : null;