@x-all-in-one/coding-helper 0.3.2 → 0.4.0

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,5 +1,7 @@
1
+ import type { PlanType } from './plans.js';
1
2
  export interface ChelperConfig {
2
3
  lang: string;
4
+ plan?: PlanType;
3
5
  api_key?: string;
4
6
  haikuModel?: string;
5
7
  sonnetModel?: string;
@@ -46,5 +48,8 @@ export declare class ConfigManager {
46
48
  }): void;
47
49
  getCodexModel(): string | undefined;
48
50
  setCodexModel(model: string): void;
51
+ getPlan(): PlanType;
52
+ setPlan(plan: PlanType): void;
53
+ get baseUrl(): string;
49
54
  }
50
55
  export declare const configManager: ConfigManager;
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import * as yaml from 'js-yaml';
5
+ import { DEFAULT_PLAN, isValidPlan, PLAN_DEFINITIONS } from './plans.js';
5
6
  export class ConfigManager {
6
7
  static instance;
7
8
  configDir;
@@ -108,5 +109,18 @@ export class ConfigManager {
108
109
  setCodexModel(model) {
109
110
  this.updateConfig({ codexModel: model });
110
111
  }
112
+ getPlan() {
113
+ const plan = this.config.plan;
114
+ if (plan && isValidPlan(plan)) {
115
+ return plan;
116
+ }
117
+ return DEFAULT_PLAN;
118
+ }
119
+ setPlan(plan) {
120
+ this.updateConfig({ plan });
121
+ }
122
+ get baseUrl() {
123
+ return PLAN_DEFINITIONS[this.getPlan()].baseUrl;
124
+ }
111
125
  }
112
126
  export const configManager = ConfigManager.getInstance();
@@ -0,0 +1,9 @@
1
+ export type PlanType = 'international' | 'china';
2
+ export interface PlanDefinition {
3
+ id: PlanType;
4
+ displayName: string;
5
+ baseUrl: string;
6
+ }
7
+ export declare const PLAN_DEFINITIONS: Record<PlanType, PlanDefinition>;
8
+ export declare const DEFAULT_PLAN: PlanType;
9
+ export declare function isValidPlan(plan: string): plan is PlanType;
@@ -0,0 +1,16 @@
1
+ export const PLAN_DEFINITIONS = {
2
+ international: {
3
+ id: 'international',
4
+ displayName: 'International (海外版)',
5
+ baseUrl: 'code-api.x-aio.ai',
6
+ },
7
+ china: {
8
+ id: 'china',
9
+ displayName: 'China (内地版)',
10
+ baseUrl: 'code-api.x-aio.com',
11
+ },
12
+ };
13
+ export const DEFAULT_PLAN = 'china';
14
+ export function isValidPlan(plan) {
15
+ return plan === 'international' || plan === 'china';
16
+ }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { dirname, join } from 'node:path';
4
+ import { configManager } from '../config.js';
4
5
  import { modelService } from '../model-service.js';
5
6
  import { BaseTool } from './base-tool.js';
6
7
  // 默认配置
@@ -150,7 +151,7 @@ export class ClaudeCodeTool extends BaseTool {
150
151
  ...currentSettings,
151
152
  env: {
152
153
  ...cleanedEnv,
153
- ANTHROPIC_BASE_URL: modelConfig.baseUrl || DEFAULT_CONFIG.ANTHROPIC_BASE_URL,
154
+ ANTHROPIC_BASE_URL: `https://${configManager.baseUrl}/anthropic`,
154
155
  ANTHROPIC_API_KEY: modelConfig.apiKey,
155
156
  ANTHROPIC_AUTH_TOKEN: modelConfig.apiKey,
156
157
  ANTHROPIC_DEFAULT_HAIKU_MODEL: modelConfig.haikuModel,
@@ -3,9 +3,9 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { parse, stringify } from 'smol-toml';
6
+ import { configManager } from '../config.js';
6
7
  import { modelService } from '../model-service.js';
7
8
  import { BaseTool } from './base-tool.js';
8
- const DEFAULT_BASE_URL = 'https://code-api.x-aio.com/v1';
9
9
  const X_AIO_CODE_API_KEY_ENV = 'X_AIO_CODE_API_KEY';
10
10
  export class CodexTool extends BaseTool {
11
11
  static instance;
@@ -66,7 +66,7 @@ export class CodexTool extends BaseTool {
66
66
  ...currentConfig.model_providers,
67
67
  [providerKey]: {
68
68
  name: 'X-AIO',
69
- base_url: modelConfig.baseUrl || DEFAULT_BASE_URL,
69
+ base_url: `https://${configManager.baseUrl}/v1`,
70
70
  env_key: X_AIO_CODE_API_KEY_ENV,
71
71
  wire_api: 'responses',
72
72
  },
@@ -16,6 +16,8 @@ const OH_MY_OPENCODE_AGENTS = [
16
16
  'document-writer',
17
17
  'multimodal-looker',
18
18
  ];
19
+ // Unified output token limit for all models
20
+ const OUTPUT_TOKEN_LIMIT = 32000;
19
21
  // Default configuration
20
22
  export const OPENCODE_DEFAULT_CONFIG = {
21
23
  BASE_URL: 'https://code-api.x-aio.com/v1',
@@ -24,10 +26,10 @@ export const OPENCODE_DEFAULT_CONFIG = {
24
26
  DEFAULT_SMALL_MODEL: 'Qwen3-Coder-30B-A3B-Instruct',
25
27
  // Default limits (hardcoded for v1, will be fetched from API in future)
26
28
  DEFAULT_CONTEXT: 128000,
27
- DEFAULT_OUTPUT: 64000,
29
+ DEFAULT_OUTPUT: OUTPUT_TOKEN_LIMIT,
28
30
  // Vision model limits
29
31
  VISION_CONTEXT: 64000,
30
- VISION_OUTPUT: 32000,
32
+ VISION_OUTPUT: OUTPUT_TOKEN_LIMIT,
31
33
  };
32
34
  /**
33
35
  * OpenCode 工具实现
@@ -118,11 +120,11 @@ export class OpenCodeTool extends BaseTool {
118
120
  if (info.name && info.name !== info.id) {
119
121
  entry.name = info.name;
120
122
  }
121
- // Set limits (use API values if available, otherwise use defaults)
123
+ // Set limits (context from API, output always use unified limit)
122
124
  if (isVision) {
123
125
  entry.limit = {
124
126
  context: info.context_length || OPENCODE_DEFAULT_CONFIG.VISION_CONTEXT,
125
- output: info.max_output_tokens || OPENCODE_DEFAULT_CONFIG.VISION_OUTPUT,
127
+ output: OUTPUT_TOKEN_LIMIT,
126
128
  };
127
129
  // Add modalities for vision models
128
130
  entry.modalities = {
@@ -133,7 +135,7 @@ export class OpenCodeTool extends BaseTool {
133
135
  else {
134
136
  entry.limit = {
135
137
  context: info.context_length || OPENCODE_DEFAULT_CONFIG.DEFAULT_CONTEXT,
136
- output: info.max_output_tokens || OPENCODE_DEFAULT_CONFIG.DEFAULT_OUTPUT,
138
+ output: OUTPUT_TOKEN_LIMIT,
137
139
  };
138
140
  }
139
141
  models[info.id] = entry;
@@ -163,7 +165,7 @@ export class OpenCodeTool extends BaseTool {
163
165
  npm: '@ai-sdk/openai-compatible',
164
166
  name: OPENCODE_DEFAULT_CONFIG.PROVIDER_NAME,
165
167
  options: {
166
- baseURL: modelConfig.baseUrl || OPENCODE_DEFAULT_CONFIG.BASE_URL,
168
+ baseURL: `https://${configManager.baseUrl}/v1`,
167
169
  apiKey: modelConfig.apiKey,
168
170
  },
169
171
  models: modelsConfig,
@@ -246,7 +248,7 @@ export class OpenCodeTool extends BaseTool {
246
248
  npm: '@ai-sdk/openai-compatible',
247
249
  name: OPENCODE_DEFAULT_CONFIG.PROVIDER_NAME,
248
250
  options: {
249
- baseURL: OPENCODE_DEFAULT_CONFIG.BASE_URL,
251
+ baseURL: `https://${configManager.baseUrl}/v1`,
250
252
  apiKey,
251
253
  },
252
254
  models: {},
@@ -0,0 +1,7 @@
1
+ export declare class PlanSelectionFlow {
2
+ private static instance;
3
+ private constructor();
4
+ static getInstance(): PlanSelectionFlow;
5
+ run(): Promise<'back' | void>;
6
+ }
7
+ export declare const planSelectionFlow: PlanSelectionFlow;
@@ -0,0 +1,57 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import { configManager } from '../../config.js';
4
+ import { i18n } from '../../i18n.js';
5
+ import { DEFAULT_PLAN, PLAN_DEFINITIONS } from '../../plans.js';
6
+ import { promptHelper } from '../ui/prompt-helper.js';
7
+ import { uiRenderer } from '../ui/ui-renderer.js';
8
+ import { apiKeyFlow } from './api-key-flow.js';
9
+ export class PlanSelectionFlow {
10
+ static instance;
11
+ constructor() { }
12
+ static getInstance() {
13
+ if (!PlanSelectionFlow.instance) {
14
+ PlanSelectionFlow.instance = new PlanSelectionFlow();
15
+ }
16
+ return PlanSelectionFlow.instance;
17
+ }
18
+ async run() {
19
+ uiRenderer.resetScreen();
20
+ uiRenderer.createBox(i18n.t('wizard.select_plan'));
21
+ const currentPlan = configManager.getPlan();
22
+ const { plan } = await promptHelper.promptWithHints([
23
+ {
24
+ type: 'list',
25
+ name: 'plan',
26
+ message: `✨ ${i18n.t('wizard.select_plan')}`,
27
+ choices: [
28
+ {
29
+ name: `${i18n.t('wizard.plan_international')} - ${PLAN_DEFINITIONS.international.baseUrl}${currentPlan === 'international' ? chalk.green(` ✓ (${i18n.t('wizard.current_active')})`) : ''}`,
30
+ value: 'international',
31
+ },
32
+ {
33
+ name: `${i18n.t('wizard.plan_china')} - ${PLAN_DEFINITIONS.china.baseUrl}${currentPlan === 'china' ? chalk.green(` ✓ (${i18n.t('wizard.current_active')})`) : ''}`,
34
+ value: 'china',
35
+ },
36
+ new inquirer.Separator(),
37
+ { name: `<- ${i18n.t('wizard.nav_return')}`, value: 'back' },
38
+ { name: `x ${i18n.t('wizard.nav_exit')}`, value: 'exit' },
39
+ ],
40
+ default: currentPlan || DEFAULT_PLAN,
41
+ },
42
+ ]);
43
+ if (plan === 'exit') {
44
+ console.log(chalk.green(`\n👋 ${i18n.t('wizard.goodbye_message')}`));
45
+ process.exit(0);
46
+ }
47
+ if (plan === 'back') {
48
+ return 'back';
49
+ }
50
+ configManager.setPlan(plan);
51
+ const planDef = PLAN_DEFINITIONS[plan];
52
+ console.log(chalk.green(`\n✓ ${i18n.t('wizard.plan_changed', { plan: planDef.displayName })}`));
53
+ console.log(chalk.blue(`ℹ ${i18n.t('wizard.plan_reload_hint')}`));
54
+ await apiKeyFlow.run();
55
+ }
56
+ }
57
+ export const planSelectionFlow = PlanSelectionFlow.getInstance();
@@ -9,11 +9,13 @@ export declare class MainMenu {
9
9
  * 显示主菜单
10
10
  * @param handlers - 菜单操作处理器
11
11
  * @param handlers.onLanguage - 语言配置回调
12
+ * @param handlers.onPlan - 套餐配置回调
12
13
  * @param handlers.onApiKey - API Key 配置回调
13
14
  * @param handlers.onTool - 工具配置回调
14
15
  */
15
16
  show(handlers: {
16
17
  onLanguage: () => Promise<void>;
18
+ onPlan: () => Promise<void>;
17
19
  onApiKey: () => Promise<void>;
18
20
  onTool: () => Promise<void>;
19
21
  }): Promise<void>;
@@ -5,6 +5,7 @@ import ora from 'ora';
5
5
  import terminalLink from 'terminal-link';
6
6
  import { configManager } from '../../config.js';
7
7
  import { i18n } from '../../i18n.js';
8
+ import { PLAN_DEFINITIONS } from '../../plans.js';
8
9
  import { promptHelper } from '../ui/prompt-helper.js';
9
10
  import { uiRenderer } from '../ui/ui-renderer.js';
10
11
  // 常量定义
@@ -25,6 +26,7 @@ export class MainMenu {
25
26
  * 显示主菜单
26
27
  * @param handlers - 菜单操作处理器
27
28
  * @param handlers.onLanguage - 语言配置回调
29
+ * @param handlers.onPlan - 套餐配置回调
28
30
  * @param handlers.onApiKey - API Key 配置回调
29
31
  * @param handlers.onTool - 工具配置回调
30
32
  */
@@ -35,7 +37,11 @@ export class MainMenu {
35
37
  uiRenderer.resetScreen();
36
38
  const currentCfg = configManager.getConfig();
37
39
  uiRenderer.createBox(i18n.t('wizard.main_menu_title'));
38
- // 显示当前配置状态(仅 API Key)
40
+ // 显示当前配置状态
41
+ const currentPlan = configManager.getPlan();
42
+ const planDef = PLAN_DEFINITIONS[currentPlan];
43
+ console.log(chalk.gray(` ${i18n.t('wizard.current_plan')}: `)
44
+ + chalk.cyan(planDef?.displayName || currentPlan));
39
45
  console.log(chalk.gray(` ${i18n.t('wizard.config_api_key')}: `)
40
46
  + (currentCfg.api_key
41
47
  ? chalk.gray(`${i18n.t('wizard.api_key_set')} (${currentCfg.api_key.slice(0, 4)}****)`)
@@ -43,6 +49,7 @@ export class MainMenu {
43
49
  console.log('');
44
50
  const choices = [
45
51
  { name: `> ${i18n.t('wizard.menu_config_language')}`, value: 'lang' },
52
+ { name: `> ${i18n.t('wizard.menu_config_plan')}`, value: 'plan' },
46
53
  { name: `> ${i18n.t('wizard.menu_config_api_key')}`, value: 'apikey' },
47
54
  { name: `> ${i18n.t('wizard.menu_config_tool')}`, value: 'tool' },
48
55
  new inquirer.Separator(),
@@ -67,6 +74,9 @@ export class MainMenu {
67
74
  else if (action === 'lang') {
68
75
  await handlers.onLanguage();
69
76
  }
77
+ else if (action === 'plan') {
78
+ await handlers.onPlan();
79
+ }
70
80
  else if (action === 'apikey') {
71
81
  await handlers.onApiKey();
72
82
  }
@@ -55,5 +55,6 @@ export declare class ToolMenu {
55
55
  * 刷新模型列表
56
56
  */
57
57
  private refreshModelList;
58
+ private detectPlanFromUrl;
58
59
  }
59
60
  export declare const toolMenu: ToolMenu;
@@ -6,6 +6,7 @@ import ora from 'ora';
6
6
  import { configManager } from '../../config.js';
7
7
  import { i18n } from '../../i18n.js';
8
8
  import { modelService } from '../../model-service.js';
9
+ import { PLAN_DEFINITIONS } from '../../plans.js';
9
10
  import { toolManager, toolRegistry } from '../../tool-manager.js';
10
11
  import { DEFAULT_CONFIG } from '../../tools/claude-code-tool.js';
11
12
  import { OPENCODE_DEFAULT_CONFIG, openCodeTool } from '../../tools/opencode-tool.js';
@@ -150,6 +151,12 @@ export class ToolMenu {
150
151
  if (tool) {
151
152
  detectedConfig = tool.getModelConfig();
152
153
  }
154
+ // 显示当前套餐
155
+ const currentPlan = configManager.getPlan();
156
+ const planDef = PLAN_DEFINITIONS[currentPlan];
157
+ console.log(chalk.gray(` ${i18n.t('wizard.current_plan')}: `)
158
+ + chalk.cyan(planDef?.displayName || currentPlan));
159
+ console.log('');
153
160
  // 显示 chelper 配置
154
161
  console.log(chalk.cyan.bold(`${i18n.t('wizard.chelper_config_title')}:`));
155
162
  if (chelperConfig.api_key) {
@@ -183,6 +190,11 @@ export class ToolMenu {
183
190
  console.log(chalk.yellow.bold(`${toolConfigTitle}:`));
184
191
  if (detectedConfig) {
185
192
  console.log(chalk.gray(` ${i18n.t('wizard.config_api_key')}: `) + chalk.gray(`${i18n.t('wizard.api_key_set')} (${detectedConfig.apiKey.slice(0, 4)}****)`));
193
+ if (detectedConfig.baseUrl) {
194
+ const planName = this.detectPlanFromUrl(detectedConfig.baseUrl);
195
+ console.log(chalk.gray(` ${i18n.t('wizard.current_endpoint')}: `)
196
+ + chalk.cyan(planName ? `${planName} (${detectedConfig.baseUrl})` : detectedConfig.baseUrl));
197
+ }
186
198
  if (toolName === 'opencode') {
187
199
  console.log(chalk.gray(` ${i18n.t('wizard.current_model')}: `) + chalk.green(detectedConfig.model));
188
200
  console.log(chalk.gray(` ${i18n.t('wizard.current_small_model')}: `) + chalk.green(detectedConfig.smallModel));
@@ -516,6 +528,14 @@ export class ToolMenu {
516
528
  }
517
529
  await new Promise(resolve => setTimeout(resolve, 1500));
518
530
  }
531
+ detectPlanFromUrl(url) {
532
+ for (const [, plan] of Object.entries(PLAN_DEFINITIONS)) {
533
+ if (url.includes(plan.baseUrl)) {
534
+ return plan.displayName;
535
+ }
536
+ }
537
+ return null;
538
+ }
519
539
  }
520
540
  // 单例导出
521
541
  export const toolMenu = ToolMenu.getInstance();
@@ -15,6 +15,10 @@ export declare class Wizard {
15
15
  * 配置语言
16
16
  */
17
17
  configLanguage(): Promise<void>;
18
+ /**
19
+ * 配置套餐
20
+ */
21
+ configPlan(): Promise<void>;
18
22
  /**
19
23
  * 配置 API Key
20
24
  */
@@ -8,6 +8,7 @@ import { OPENCODE_DEFAULT_CONFIG, openCodeTool } from '../tools/opencode-tool.js
8
8
  import { apiKeyFlow } from './flows/api-key-flow.js';
9
9
  import { languageFlow } from './flows/language-flow.js';
10
10
  import { modelSelectionFlow } from './flows/model-selection-flow.js';
11
+ import { planSelectionFlow } from './flows/plan-selection-flow.js';
11
12
  import { mainMenu } from './menus/main-menu.js';
12
13
  import { pluginMenu } from './menus/plugin-menu.js';
13
14
  import { toolMenu } from './menus/tool-menu.js';
@@ -29,15 +30,16 @@ export class Wizard {
29
30
  * 运行首次配置流程
30
31
  */
31
32
  async runFirstTimeSetup() {
32
- // 清屏并显示欢迎信息
33
33
  uiRenderer.resetScreen();
34
34
  console.log(chalk.cyan.bold(`\n${i18n.t('wizard.welcome')}`));
35
35
  console.log(chalk.gray(`${i18n.t('wizard.privacy_note')}\n`));
36
36
  // Step 1: Select language
37
37
  await this.configLanguage();
38
- // Step 2: Input API key
38
+ // Step 2: Select plan
39
+ await this.configPlan();
40
+ // Step 3: Input API key
39
41
  await this.configApiKey();
40
- // Step 3: 进入主菜单(模型配置已移到工具配置中)
42
+ // Step 4: Enter main menu
41
43
  await this.showMainMenu();
42
44
  }
43
45
  /**
@@ -46,6 +48,12 @@ export class Wizard {
46
48
  async configLanguage() {
47
49
  await languageFlow.run();
48
50
  }
51
+ /**
52
+ * 配置套餐
53
+ */
54
+ async configPlan() {
55
+ await planSelectionFlow.run();
56
+ }
49
57
  /**
50
58
  * 配置 API Key
51
59
  */
@@ -177,6 +185,7 @@ export class Wizard {
177
185
  async showMainMenu() {
178
186
  await mainMenu.show({
179
187
  onLanguage: () => this.configLanguage(),
188
+ onPlan: () => this.configPlan(),
180
189
  onApiKey: () => this.configApiKey(),
181
190
  onTool: () => this.selectAndConfigureTool(),
182
191
  });
@@ -91,8 +91,15 @@
91
91
  "api_key_set": "Set",
92
92
  "api_key_get_hint": "To obtain API Key, please visit: {{url}}",
93
93
  "menu_config_language": "Interface language",
94
+ "menu_config_plan": "Configure Plan",
94
95
  "menu_config_api_key": "Configure API Key",
95
96
  "menu_config_models": "Configure Models",
97
+ "select_plan": "Select Plan",
98
+ "current_plan": "Current Plan",
99
+ "plan_international": "International",
100
+ "plan_china": "China",
101
+ "plan_changed": "Plan changed to {{plan}}",
102
+ "plan_reload_hint": "Please reload config in each tool menu to apply the new endpoint",
96
103
  "menu_config_tool": "Configure Coding Tool",
97
104
  "menu_exit": "Exit",
98
105
  "select_operation": "Select operation:",
@@ -108,6 +115,7 @@
108
115
  "claude_code_config_title": "Claude Code Current Global Configuration",
109
116
  "opencode_config_title": "OpenCode Current Global Configuration",
110
117
  "current_model": "Model",
118
+ "current_endpoint": "Endpoint",
111
119
  "current_small_model": "Small Model",
112
120
  "config_synced": "Configuration synchronized",
113
121
  "config_out_of_sync": "Configuration out of sync, refresh recommended",
@@ -91,8 +91,15 @@
91
91
  "api_key_set": "已设置",
92
92
  "api_key_get_hint": "如需获取 API Key,请访问: {{url}}",
93
93
  "menu_config_language": "界面语言",
94
+ "menu_config_plan": "配置套餐",
94
95
  "menu_config_api_key": "配置 API Key",
95
96
  "menu_config_models": "配置模型",
97
+ "select_plan": "选择套餐",
98
+ "current_plan": "当前套餐",
99
+ "plan_international": "海外版",
100
+ "plan_china": "内地版",
101
+ "plan_changed": "套餐已切换为 {{plan}}",
102
+ "plan_reload_hint": "请在各工具菜单中重新加载配置以应用新端点",
96
103
  "menu_config_tool": "配置编码工具",
97
104
  "menu_exit": "退出",
98
105
  "select_operation": "请选择操作:",
@@ -108,6 +115,7 @@
108
115
  "claude_code_config_title": "Claude Code 当前全局配置",
109
116
  "opencode_config_title": "OpenCode 当前全局配置",
110
117
  "current_model": "主模型",
118
+ "current_endpoint": "端点",
111
119
  "current_small_model": "小模型",
112
120
  "config_synced": "配置已同步",
113
121
  "config_out_of_sync": "配置不一致,建议刷新",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@x-all-in-one/coding-helper",
3
3
  "type": "module",
4
- "version": "0.3.2",
4
+ "version": "0.4.0",
5
5
  "description": "X All In One Coding Helper",
6
6
  "author": "X.AIO",
7
7
  "homepage": "https://docs.x-aio.com/zh/docs",