aicodeswitch 6.0.0 → 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 +67 -4
- package/bin/utils/managed-fields.js +12 -0
- package/dist/server/agent-map/activity-extractor.js +8 -0
- package/dist/server/agent-map/agent-map-service.js +10 -15
- package/dist/server/coding-plan.js +3 -0
- package/dist/server/config-managed-fields.js +20 -1
- package/dist/server/config-metadata.js +78 -1
- package/dist/server/fs-database.js +4 -1
- package/dist/server/main.js +405 -7
- package/dist/server/original-config-reader.js +71 -1
- package/dist/server/proxy-server.js +55 -8
- package/dist/server/session-migration.js +1 -0
- package/dist/ui/assets/index-BeM4AIzn.js +1187 -0
- package/dist/ui/assets/index-DqRzbMIU.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-CQVhBlBB.css +0 -1
- package/dist/ui/assets/index-QZBX5HHX.js +0 -1186
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;
|
|
@@ -599,7 +599,8 @@ class AgentMapService extends events_1.EventEmitter {
|
|
|
599
599
|
// 异步解析本地会话元信息(项目路径 + 原始标题);仅 global 来源、且未解析过
|
|
600
600
|
this.enrichSession(st);
|
|
601
601
|
}
|
|
602
|
-
/** 从本机 Claude/Codex
|
|
602
|
+
/** 从本机 Claude/Codex 会话存储读取项目路径并回填(access-key 不解析)。
|
|
603
|
+
* 注意:不在此处覆盖标题——统一沿用 proxy 抽取的标题(与会话列表一致),避免两边标题分叉。 */
|
|
603
604
|
enrichSession(st) {
|
|
604
605
|
return __awaiter(this, void 0, void 0, function* () {
|
|
605
606
|
if (st.source === 'access-key' || st.metaResolved)
|
|
@@ -607,18 +608,10 @@ class AgentMapService extends events_1.EventEmitter {
|
|
|
607
608
|
st.metaResolved = true;
|
|
608
609
|
try {
|
|
609
610
|
const meta = yield (0, session_meta_1.resolveSessionMeta)(st.sessionId, st.agent);
|
|
610
|
-
let changed = false;
|
|
611
611
|
if (meta.projectPath && !st.projectPath) {
|
|
612
612
|
st.projectPath = meta.projectPath;
|
|
613
|
-
changed = true;
|
|
614
|
-
}
|
|
615
|
-
// 原始标题更标准,命中即覆盖日志截取的标题
|
|
616
|
-
if (meta.title) {
|
|
617
|
-
st.title = meta.title;
|
|
618
|
-
changed = true;
|
|
619
|
-
}
|
|
620
|
-
if (changed)
|
|
621
613
|
this.emitSession(st);
|
|
614
|
+
}
|
|
622
615
|
}
|
|
623
616
|
catch ( /* ignore */_a) { /* ignore */ }
|
|
624
617
|
});
|
|
@@ -631,18 +624,16 @@ class AgentMapService extends events_1.EventEmitter {
|
|
|
631
624
|
if (source === 'access-key' || source === 'unknown') {
|
|
632
625
|
return { source, projectPath: st === null || st === void 0 ? void 0 : st.projectPath, title: st === null || st === void 0 ? void 0 : st.title };
|
|
633
626
|
}
|
|
634
|
-
// global
|
|
627
|
+
// global:命中缓存或现解析(仅项目路径;标题沿用 proxy 抽取值,与会话列表保持一致)
|
|
635
628
|
const meta = yield (0, session_meta_1.resolveSessionMeta)(sessionId, (st === null || st === void 0 ? void 0 : st.agent) || 'claude-code');
|
|
636
629
|
if (st) {
|
|
637
630
|
if (meta.projectPath && !st.projectPath)
|
|
638
631
|
st.projectPath = meta.projectPath;
|
|
639
|
-
if (meta.title)
|
|
640
|
-
st.title = meta.title;
|
|
641
632
|
st.metaResolved = true;
|
|
642
633
|
// legacy 会话(修复前累积、拆分从未持久化):点开详情时按日志回填输入/输出拆分
|
|
643
634
|
this.backfillTokenSplit(st).catch(err => console.error('[AgentMap] backfillTokenSplit error:', err));
|
|
644
635
|
}
|
|
645
|
-
return { source, projectPath: meta.projectPath || (st === null || st === void 0 ? void 0 : st.projectPath), title:
|
|
636
|
+
return { source, projectPath: meta.projectPath || (st === null || st === void 0 ? void 0 : st.projectPath), title: st === null || st === void 0 ? void 0 : st.title };
|
|
646
637
|
});
|
|
647
638
|
}
|
|
648
639
|
/**
|
|
@@ -736,6 +727,8 @@ class AgentMapService extends events_1.EventEmitter {
|
|
|
736
727
|
return; // 本轮已调度
|
|
737
728
|
if (st.lastStatusCode === 499)
|
|
738
729
|
return; // 用户主动取消,不弹
|
|
730
|
+
if (st.thinkingInFlight > 0)
|
|
731
|
+
return; // 仍有思考请求在途,暂不弹结束通知
|
|
739
732
|
st.notifiedForTurn = true;
|
|
740
733
|
this.scheduleNotify(st, isError);
|
|
741
734
|
}
|
|
@@ -808,7 +801,9 @@ class AgentMapService extends events_1.EventEmitter {
|
|
|
808
801
|
}
|
|
809
802
|
// 已开始产出:思考请求用更宽的 THINKING_SILENCE_MS,否则 SSE_SILENCE_MS(30s)
|
|
810
803
|
const limit = args.thinkingInFlight > 0 ? this.thinkingSilenceMs : this.sseSilenceMs;
|
|
811
|
-
|
|
804
|
+
// 静默基准用「最近一次活动」(每个下游 chunk 经 heartbeat 刷新),而非首个 chunk 时刻:
|
|
805
|
+
// 否则任何总时长超过 30s 的流式响应都会被误判为停滞,在响应仍正常输出时弹出「任务已结束」通知。
|
|
806
|
+
const since = args.lastActivityAt;
|
|
812
807
|
if (args.now - since <= limit) {
|
|
813
808
|
return { status: 'active', reason: args.thinkingInFlight > 0 ? 'thinking' : 'streaming', notifyEligible: true };
|
|
814
809
|
}
|
|
@@ -37,6 +37,9 @@ function isCodingToolRequest(body, format, headers) {
|
|
|
37
37
|
// Codex: originator header
|
|
38
38
|
if ((headers['originator'] || '').toLowerCase().includes('codex'))
|
|
39
39
|
return { isCoding: true, reason: '' };
|
|
40
|
+
// OpenCode: user-agent 包含 "opencode"
|
|
41
|
+
if (ua.includes('opencode'))
|
|
42
|
+
return { isCoding: true, reason: '' };
|
|
40
43
|
}
|
|
41
44
|
// ── Layer 2: Claude Messages API ───────────────────────────────────
|
|
42
45
|
if (format === 'claude' && Array.isArray(body === null || body === void 0 ? void 0 : body.messages)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getManagedFields = exports.CODEX_AUTH_MANAGED_FIELDS = exports.CODEX_CONFIG_MANAGED_FIELDS = exports.CLAUDE_JSON_MANAGED_FIELDS = exports.CLAUDE_SETTINGS_MANAGED_FIELDS = void 0;
|
|
3
|
+
exports.getManagedFields = exports.OPENCODE_CONFIG_MANAGED_FIELDS = exports.CODEX_AUTH_MANAGED_FIELDS = exports.CODEX_CONFIG_MANAGED_FIELDS = exports.CLAUDE_JSON_MANAGED_FIELDS = exports.CLAUDE_SETTINGS_MANAGED_FIELDS = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Claude Code settings.json 管理字段定义
|
|
6
6
|
*/
|
|
@@ -50,6 +50,20 @@ exports.CODEX_CONFIG_MANAGED_FIELDS = [
|
|
|
50
50
|
exports.CODEX_AUTH_MANAGED_FIELDS = [
|
|
51
51
|
{ path: ['OPENAI_API_KEY'] },
|
|
52
52
|
];
|
|
53
|
+
/**
|
|
54
|
+
* OpenCode opencode.json 管理字段定义
|
|
55
|
+
*
|
|
56
|
+
* OpenCode 配置为纯 JSON,使用自定义 provider 指向本代理。
|
|
57
|
+
* 托管整个 `provider.aicodeswitch` section(npm/name/options/models)
|
|
58
|
+
* 以及 `model` / `small_model` 两个模型选择字段。其余字段(其它 provider、
|
|
59
|
+
* agent、command、mcp、theme 等)一律保留给用户。
|
|
60
|
+
*/
|
|
61
|
+
exports.OPENCODE_CONFIG_MANAGED_FIELDS = [
|
|
62
|
+
{ path: ['provider', 'aicodeswitch'], isSection: true },
|
|
63
|
+
{ path: ['model'] },
|
|
64
|
+
{ path: ['small_model'], optional: true },
|
|
65
|
+
{ path: ['mcp'], isSection: true, optional: true },
|
|
66
|
+
];
|
|
53
67
|
/**
|
|
54
68
|
* 根据配置类型和文件路径获取管理字段列表
|
|
55
69
|
*/
|
|
@@ -70,6 +84,11 @@ const getManagedFields = (configType, filePath) => {
|
|
|
70
84
|
return exports.CODEX_AUTH_MANAGED_FIELDS;
|
|
71
85
|
}
|
|
72
86
|
}
|
|
87
|
+
else if (configType === 'opencode') {
|
|
88
|
+
if (filePath.endsWith('opencode.json')) {
|
|
89
|
+
return exports.OPENCODE_CONFIG_MANAGED_FIELDS;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
73
92
|
return [];
|
|
74
93
|
};
|
|
75
94
|
exports.getManagedFields = getManagedFields;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.cleanupInvalidMetadata = exports.checkCodexConfigStatus = exports.checkClaudeConfigStatus = exports.deleteMetadata = exports.loadMetadata = exports.saveMetadata = void 0;
|
|
6
|
+
exports.cleanupInvalidMetadata = exports.checkOpencodeConfigStatus = exports.getOpencodeConfigPath = exports.checkCodexConfigStatus = exports.checkClaudeConfigStatus = exports.deleteMetadata = exports.loadMetadata = exports.saveMetadata = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
@@ -270,6 +270,83 @@ const checkCodexConfigStatus = () => {
|
|
|
270
270
|
};
|
|
271
271
|
};
|
|
272
272
|
exports.checkCodexConfigStatus = checkCodexConfigStatus;
|
|
273
|
+
/**
|
|
274
|
+
* 检查 OpenCode 配置文件是否包含我们的代理特征
|
|
275
|
+
*/
|
|
276
|
+
const isOpencodeProxyConfig = (filePath) => {
|
|
277
|
+
var _a, _b;
|
|
278
|
+
try {
|
|
279
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
283
|
+
const config = JSON.parse(content);
|
|
284
|
+
// 检查是否包含我们的自定义 provider(provider.aicodeswitch)
|
|
285
|
+
const provider = (_a = config === null || config === void 0 ? void 0 : config.provider) === null || _a === void 0 ? void 0 : _a.aicodeswitch;
|
|
286
|
+
if (provider && typeof provider === 'object') {
|
|
287
|
+
const baseUrl = (_b = provider.options) === null || _b === void 0 ? void 0 : _b.baseURL;
|
|
288
|
+
// baseURL 指向本代理的 /opencode 路径
|
|
289
|
+
if (baseUrl && typeof baseUrl === 'string' && /\/opencode(\/|$)/.test(baseUrl)) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
console.error(`Failed to check OpenCode config:`, error);
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* OpenCode 配置文件路径(~/.config/opencode/opencode.json)
|
|
302
|
+
*/
|
|
303
|
+
const getOpencodeConfigPath = () => {
|
|
304
|
+
const homeDir = os_1.default.homedir();
|
|
305
|
+
return path_1.default.join(homeDir, '.config', 'opencode', 'opencode.json');
|
|
306
|
+
};
|
|
307
|
+
exports.getOpencodeConfigPath = getOpencodeConfigPath;
|
|
308
|
+
/**
|
|
309
|
+
* 检查 OpenCode 配置状态
|
|
310
|
+
*/
|
|
311
|
+
const checkOpencodeConfigStatus = () => {
|
|
312
|
+
var _a;
|
|
313
|
+
const configPath = (0, exports.getOpencodeConfigPath)();
|
|
314
|
+
const configBakPath = `${configPath}.aicodeswitch_backup`;
|
|
315
|
+
// 检查备份文件是否存在
|
|
316
|
+
const hasBackup = fs_1.default.existsSync(configBakPath);
|
|
317
|
+
// 尝试加载元数据
|
|
318
|
+
const metadata = (0, exports.loadMetadata)('opencode');
|
|
319
|
+
if (metadata) {
|
|
320
|
+
const currentHash = calculateFileHash(configPath);
|
|
321
|
+
const isProxyConfig = isOpencodeProxyConfig(configPath);
|
|
322
|
+
let isModified = false;
|
|
323
|
+
if (currentHash && ((_a = metadata.files[0]) === null || _a === void 0 ? void 0 : _a.currentHash)) {
|
|
324
|
+
isModified = currentHash !== metadata.files[0].currentHash;
|
|
325
|
+
}
|
|
326
|
+
const isOverwritten = isProxyConfig;
|
|
327
|
+
return {
|
|
328
|
+
isOverwritten,
|
|
329
|
+
isModified,
|
|
330
|
+
hasBackup,
|
|
331
|
+
metadata
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (hasBackup) {
|
|
335
|
+
const isProxyConfig = isOpencodeProxyConfig(configPath);
|
|
336
|
+
return {
|
|
337
|
+
isOverwritten: isProxyConfig,
|
|
338
|
+
isModified: false,
|
|
339
|
+
hasBackup
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const isProxyConfig = isOpencodeProxyConfig(configPath);
|
|
343
|
+
return {
|
|
344
|
+
isOverwritten: isProxyConfig,
|
|
345
|
+
isModified: false,
|
|
346
|
+
hasBackup: false
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
exports.checkOpencodeConfigStatus = checkOpencodeConfigStatus;
|
|
273
350
|
/**
|
|
274
351
|
* 清理无效的元数据
|
|
275
352
|
* 当备份文件不存在但元数据存在时,说明状态不一致,需要清理
|
|
@@ -190,6 +190,7 @@ class FileSystemDatabaseManager {
|
|
|
190
190
|
value: {
|
|
191
191
|
'claude-code': { tool: 'claude-code', routeId: null },
|
|
192
192
|
'codex': { tool: 'codex', routeId: null },
|
|
193
|
+
'opencode': { tool: 'opencode', routeId: null },
|
|
193
194
|
}
|
|
194
195
|
});
|
|
195
196
|
// 持久化统计数据
|
|
@@ -552,6 +553,7 @@ class FileSystemDatabaseManager {
|
|
|
552
553
|
this.toolBindings = {
|
|
553
554
|
'claude-code': parsed['claude-code'] || { tool: 'claude-code', routeId: null },
|
|
554
555
|
'codex': parsed['codex'] || { tool: 'codex', routeId: null },
|
|
556
|
+
'opencode': parsed['opencode'] || { tool: 'opencode', routeId: null },
|
|
555
557
|
};
|
|
556
558
|
}
|
|
557
559
|
catch (_a) {
|
|
@@ -559,6 +561,7 @@ class FileSystemDatabaseManager {
|
|
|
559
561
|
this.toolBindings = {
|
|
560
562
|
'claude-code': { tool: 'claude-code', routeId: null },
|
|
561
563
|
'codex': { tool: 'codex', routeId: null },
|
|
564
|
+
'opencode': { tool: 'opencode', routeId: null },
|
|
562
565
|
};
|
|
563
566
|
}
|
|
564
567
|
});
|
|
@@ -908,7 +911,7 @@ class FileSystemDatabaseManager {
|
|
|
908
911
|
// Only migrate routes that are explicitly active and have a targetType
|
|
909
912
|
if (route.isActive === true && route.targetType) {
|
|
910
913
|
const tool = route.targetType;
|
|
911
|
-
if (tool === 'claude-code' || tool === 'codex') {
|
|
914
|
+
if (tool === 'claude-code' || tool === 'codex' || tool === 'opencode') {
|
|
912
915
|
// Only write if tool-bindings doesn't already have a binding
|
|
913
916
|
// (avoid overwriting user's newer tool-binding choices)
|
|
914
917
|
if (!((_a = this.toolBindings[tool]) === null || _a === void 0 ? void 0 : _a.routeId)) {
|