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.
@@ -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
  * 当备份文件不存在但元数据存在时,说明状态不一致,需要清理
@@ -276,8 +276,16 @@ function buildTargetBody(options) {
276
276
  if (result.messages) {
277
277
  result.messages = (0, mapper_js_2.fixThinkingHistory)(result.messages, 'completions');
278
278
  }
279
- // 剥离 stream_options(reasoning_content 提供商通常不支持)
280
- delete result.stream_options;
279
+ // stream_options 剥离策略由 providerConfig.streamOptions 决定:
280
+ // 'supported' → 保留(DeepSeek 等支持 include_usage,能拿到真实 usage)
281
+ // 'unsupported' → 剥离(已知会报错的供应商)
282
+ // 'auto'/缺省 → 维持历史行为:reasoning_content 剥离
283
+ const streamOpt = providerConfig.streamOptions || 'auto';
284
+ const shouldStripStreamOptions = streamOpt === 'unsupported' ||
285
+ (streamOpt === 'auto' && isReasoningContentCompletion);
286
+ if (shouldStripStreamOptions) {
287
+ delete result.stream_options;
288
+ }
281
289
  }
282
290
  // 注入 thinking 参数(如 thinking: { type: 'enabled' })和 effort 参数
283
291
  if (providerConfig.supportsThinking || providerConfig.supportsEffort) {
@@ -6,17 +6,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DEFAULT_CONFIG = void 0;
7
7
  exports.getReasoningConfig = getReasoningConfig;
8
8
  exports.applyReasoningConfig = applyReasoningConfig;
9
+ // streamOptions 语义(决定流式请求是否保留 stream_options.include_usage):
10
+ // 'supported' → 已知支持 include_usage 的供应商,保留 → 流式可拿到真实 usage
11
+ // 'unsupported' → 已知会对 stream_options 报错的供应商,剥离
12
+ // 'auto' → 未明确,由 conversions/index.ts 按 outputFormat 兜底(reasoning_content 剥离,其余保留)
9
13
  const PROVIDER_CONFIGS = [
10
- { patterns: ['deepseek'], config: { supportsThinking: true, supportsEffort: true, thinkingParam: 'thinking', effortParam: 'reasoning_effort', effortValueMode: 'deepseek', outputFormat: 'reasoning_content' } },
11
- { patterns: ['moonshot', 'kimi'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
12
- { patterns: ['qwen', 'dashscope'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'enable_thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
13
- { patterns: ['zhipu', 'glm', 'bigmodel'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
14
+ { patterns: ['deepseek'], config: { supportsThinking: true, supportsEffort: true, thinkingParam: 'thinking', effortParam: 'reasoning_effort', effortValueMode: 'deepseek', outputFormat: 'reasoning_content', streamOptions: 'supported' } },
15
+ { patterns: ['moonshot', 'kimi'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content', streamOptions: 'supported' } },
16
+ { patterns: ['qwen', 'dashscope'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'enable_thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content', streamOptions: 'supported' } },
17
+ { patterns: ['zhipu', 'glm', 'bigmodel'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content', streamOptions: 'supported' } },
14
18
  { patterns: ['minimax'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'reasoning_split', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_details' } },
15
- { patterns: ['mimo', 'xiaomimimo'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
19
+ { patterns: ['mimo', 'xiaomimimo'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content', streamOptions: 'auto' } },
16
20
  { patterns: ['openrouter'], config: { supportsThinking: false, supportsEffort: true, thinkingParam: 'none', effortParam: 'reasoning.effort', effortValueMode: 'openrouter', outputFormat: 'reasoning' } },
17
- { patterns: ['siliconflow'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'enable_thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
21
+ { patterns: ['siliconflow'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'enable_thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content', streamOptions: 'supported' } },
18
22
  { patterns: ['stepfun', 'step'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'none', effortParam: 'reasoning_effort', effortValueMode: 'low_high', outputFormat: 'reasoning' } },
19
- { patterns: ['agnes'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'chat_template_kwargs', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
23
+ { patterns: ['agnes'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'chat_template_kwargs', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content', streamOptions: 'auto' } },
20
24
  ];
21
25
  const DEFAULT_CONFIG = {
22
26
  supportsThinking: false,
@@ -25,6 +29,7 @@ const DEFAULT_CONFIG = {
25
29
  effortParam: 'none',
26
30
  effortValueMode: 'passthrough',
27
31
  outputFormat: 'reasoning_content',
32
+ streamOptions: 'auto',
28
33
  };
29
34
  exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
30
35
  /** Detect reasoning config for a given provider */
@@ -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)) {
@@ -2323,6 +2326,8 @@ class FileSystemDatabaseManager {
2323
2326
  lastRequestAt: now,
2324
2327
  requestCount: 1,
2325
2328
  totalTokens: 0,
2329
+ inputTokens: 0,
2330
+ outputTokens: 0,
2326
2331
  };
2327
2332
  this.sessions.push(session);
2328
2333
  yield this.saveSessions();
@@ -2478,6 +2483,8 @@ class FileSystemDatabaseManager {
2478
2483
  existing.lastRequestAt = now;
2479
2484
  existing.requestCount++;
2480
2485
  existing.totalTokens += session.totalTokens || 0;
2486
+ existing.inputTokens = (existing.inputTokens || 0) + (session.inputTokens || 0);
2487
+ existing.outputTokens = (existing.outputTokens || 0) + (session.outputTokens || 0);
2481
2488
  if (session.vendorId !== undefined)
2482
2489
  existing.vendorId = session.vendorId;
2483
2490
  if (session.vendorName !== undefined)
@@ -2510,6 +2517,8 @@ class FileSystemDatabaseManager {
2510
2517
  lastRequestAt: now,
2511
2518
  requestCount: 1,
2512
2519
  totalTokens: session.totalTokens || 0,
2520
+ inputTokens: session.inputTokens || 0,
2521
+ outputTokens: session.outputTokens || 0,
2513
2522
  vendorId: session.vendorId,
2514
2523
  vendorName: session.vendorName,
2515
2524
  serviceId: session.serviceId,