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

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,9 +1,6 @@
1
- export interface ModelInfo {
2
- id: string;
3
- name?: string;
4
- context_length?: number;
5
- max_output_tokens?: number;
6
- }
1
+ import type { ModelInfo } from './utils/fetch-models.js';
2
+ export type { ModelInfo } from './utils/fetch-models.js';
3
+ export { CODE_PLAN_MODEL_LIST_API } from './utils/fetch-models.js';
7
4
  /**
8
5
  * 模型服务
9
6
  * 负责模型列表的获取、缓存和同步
@@ -19,9 +16,9 @@ export declare class ModelService {
19
16
  */
20
17
  fetchModels(): Promise<string[]>;
21
18
  /**
22
- * 获取带详细信息的模型列表
19
+ * 获取带详细信息的模型列表(带缓存)
23
20
  */
24
- fetchModelsWithInfo(apiKey: string): Promise<ModelInfo[]>;
21
+ fetchModelsWithInfo(): Promise<ModelInfo[]>;
25
22
  /**
26
23
  * 清除缓存
27
24
  */
@@ -32,6 +29,7 @@ export declare class ModelService {
32
29
  refreshModels(): Promise<{
33
30
  success: boolean;
34
31
  count: number;
32
+ error?: string;
35
33
  }>;
36
34
  /**
37
35
  * 刷新并同步模型列表到 OpenCode 配置
@@ -40,6 +38,7 @@ export declare class ModelService {
40
38
  refreshAndSyncToOpenCode(): Promise<{
41
39
  success: boolean;
42
40
  count: number;
41
+ error?: string;
43
42
  }>;
44
43
  /**
45
44
  * 获取缓存的模型数量
@@ -1,5 +1,7 @@
1
1
  import { configManager } from './config.js';
2
2
  import { openCodeTool } from './tools/opencode-tool.js';
3
+ import { fetchModelsFromApi } from './utils/fetch-models.js';
4
+ export { CODE_PLAN_MODEL_LIST_API } from './utils/fetch-models.js';
3
5
  /**
4
6
  * 模型服务
5
7
  * 负责模型列表的获取、缓存和同步
@@ -19,56 +21,23 @@ export class ModelService {
19
21
  * 获取可用模型列表(带缓存)
20
22
  */
21
23
  async fetchModels() {
22
- const apiKey = configManager.getApiKey();
23
- if (!apiKey) {
24
- return [];
25
- }
26
24
  if (this.cachedModels.length > 0) {
27
25
  return this.cachedModels;
28
26
  }
29
- const modelsWithInfo = await this.fetchModelsWithInfo(apiKey);
27
+ const modelsWithInfo = await this.fetchModelsWithInfo();
30
28
  this.cachedModels = modelsWithInfo.map(m => m.id);
31
29
  return this.cachedModels;
32
30
  }
33
31
  /**
34
- * 获取带详细信息的模型列表
32
+ * 获取带详细信息的模型列表(带缓存)
35
33
  */
36
- async fetchModelsWithInfo(apiKey) {
34
+ async fetchModelsWithInfo() {
37
35
  if (this.cachedModelsWithInfo.length > 0) {
38
36
  return this.cachedModelsWithInfo;
39
37
  }
40
- const controller = new AbortController();
41
- const timeoutId = setTimeout(() => controller.abort(), 30000);
42
- try {
43
- const response = await fetch('https://code-api.x-aio.com/v1/models', {
44
- method: 'GET',
45
- headers: {
46
- 'Authorization': `Bearer ${apiKey}`,
47
- 'Content-Type': 'application/json',
48
- },
49
- signal: controller.signal,
50
- });
51
- clearTimeout(timeoutId);
52
- if (!response.ok) {
53
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
54
- }
55
- const data = await response.json();
56
- if (data && data.data && Array.isArray(data.data)) {
57
- this.cachedModelsWithInfo = data.data.map((m) => ({
58
- id: m.id,
59
- name: m.name || m.id,
60
- context_length: m.context_length,
61
- max_output_tokens: m.max_output_tokens,
62
- }));
63
- this.cachedModels = this.cachedModelsWithInfo.map(m => m.id);
64
- return this.cachedModelsWithInfo;
65
- }
66
- return [];
67
- }
68
- catch (error) {
69
- clearTimeout(timeoutId);
70
- throw error;
71
- }
38
+ this.cachedModelsWithInfo = await fetchModelsFromApi();
39
+ this.cachedModels = this.cachedModelsWithInfo.map(m => m.id);
40
+ return this.cachedModelsWithInfo;
72
41
  }
73
42
  /**
74
43
  * 清除缓存
@@ -83,15 +52,19 @@ export class ModelService {
83
52
  async refreshModels() {
84
53
  const apiKey = configManager.getApiKey();
85
54
  if (!apiKey) {
86
- return { success: false, count: 0 };
55
+ return { success: false, count: 0, error: 'No API key configured' };
87
56
  }
88
57
  this.clearCache();
89
58
  try {
90
- const models = await this.fetchModelsWithInfo(apiKey);
91
- return { success: models.length > 0, count: models.length };
59
+ const models = await this.fetchModelsWithInfo();
60
+ if (models.length === 0) {
61
+ return { success: false, count: 0, error: 'API returned empty model list' };
62
+ }
63
+ return { success: true, count: models.length };
92
64
  }
93
- catch {
94
- return { success: false, count: 0 };
65
+ catch (err) {
66
+ const errorMessage = err instanceof Error ? err.message : String(err);
67
+ return { success: false, count: 0, error: errorMessage };
95
68
  }
96
69
  }
97
70
  /**
@@ -101,21 +74,20 @@ export class ModelService {
101
74
  async refreshAndSyncToOpenCode() {
102
75
  const apiKey = configManager.getApiKey();
103
76
  if (!apiKey) {
104
- return { success: false, count: 0 };
77
+ return { success: false, count: 0, error: 'No API key configured' };
105
78
  }
106
79
  this.clearCache();
107
80
  try {
108
- // 获取最新模型
109
- const models = await this.fetchModelsWithInfo(apiKey);
81
+ const models = await this.fetchModelsWithInfo();
110
82
  if (models.length === 0) {
111
- return { success: false, count: 0 };
83
+ return { success: false, count: 0, error: 'API returned empty model list' };
112
84
  }
113
- // 同步到 OpenCode 配置
114
85
  openCodeTool.syncModelsToConfig(models);
115
86
  return { success: true, count: models.length };
116
87
  }
117
- catch {
118
- return { success: false, count: 0 };
88
+ catch (err) {
89
+ const errorMessage = err instanceof Error ? err.message : String(err);
90
+ return { success: false, count: 0, error: errorMessage };
119
91
  }
120
92
  }
121
93
  /**
@@ -69,5 +69,5 @@ export declare abstract class BaseTool implements ITool {
69
69
  abstract getModelConfig(): unknown;
70
70
  abstract saveModelConfig(config: unknown): Promise<void>;
71
71
  abstract clearModelConfig(): void;
72
- abstract fetchAvailableModels(apiKey: string): Promise<string[]>;
72
+ abstract fetchAvailableModels(): Promise<string[]>;
73
73
  }
@@ -83,7 +83,7 @@ export declare class ClaudeCodeTool extends BaseTool {
83
83
  /**
84
84
  * 从 API 获取可用的模型列表
85
85
  */
86
- fetchAvailableModels(apiKey: string): Promise<string[]>;
86
+ fetchAvailableModels(): Promise<string[]>;
87
87
  /**
88
88
  * 保存模型配置到 Claude Code
89
89
  */
@@ -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 { modelService } from '../model-service.js';
4
5
  import { BaseTool } from './base-tool.js';
5
6
  // 默认配置
6
7
  export const DEFAULT_CONFIG = {
@@ -131,32 +132,8 @@ export class ClaudeCodeTool extends BaseTool {
131
132
  /**
132
133
  * 从 API 获取可用的模型列表
133
134
  */
134
- async fetchAvailableModels(apiKey) {
135
- try {
136
- const controller = new AbortController();
137
- const timeoutId = setTimeout(() => controller.abort(), 30000);
138
- const response = await fetch('https://code-api.x-aio.com/v1/models', {
139
- method: 'GET',
140
- headers: {
141
- 'Authorization': `Bearer ${apiKey}`,
142
- 'Content-Type': 'application/json',
143
- },
144
- signal: controller.signal,
145
- });
146
- clearTimeout(timeoutId);
147
- if (!response.ok) {
148
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
149
- }
150
- const data = await response.json();
151
- if (data && data.data && Array.isArray(data.data)) {
152
- return data.data.map((model) => model.id);
153
- }
154
- return [];
155
- }
156
- catch (error) {
157
- console.warn('Failed to fetch available models:', error);
158
- return [];
159
- }
135
+ async fetchAvailableModels() {
136
+ return modelService.fetchModels();
160
137
  }
161
138
  /**
162
139
  * 保存模型配置到 Claude Code
@@ -28,7 +28,7 @@ export declare class CodexTool extends BaseTool {
28
28
  private ensureDir;
29
29
  getConfig(): CodexConfig;
30
30
  saveConfig(config: CodexConfig): void;
31
- fetchAvailableModels(apiKey: string): Promise<string[]>;
31
+ fetchAvailableModels(): Promise<string[]>;
32
32
  saveModelConfig(config: unknown): Promise<void>;
33
33
  private setApiKeyEnv;
34
34
  private getShellRcPath;
@@ -3,6 +3,7 @@ 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 { modelService } from '../model-service.js';
6
7
  import { BaseTool } from './base-tool.js';
7
8
  const DEFAULT_BASE_URL = 'https://code-api.x-aio.com/v1';
8
9
  const X_AIO_CODE_API_KEY_ENV = 'X_AIO_CODE_API_KEY';
@@ -50,32 +51,8 @@ export class CodexTool extends BaseTool {
50
51
  throw new Error(`Failed to save Codex config: ${error}`);
51
52
  }
52
53
  }
53
- async fetchAvailableModels(apiKey) {
54
- try {
55
- const controller = new AbortController();
56
- const timeoutId = setTimeout(() => controller.abort(), 30000);
57
- const response = await fetch(`${DEFAULT_BASE_URL}/models`, {
58
- method: 'GET',
59
- headers: {
60
- 'Authorization': `Bearer ${apiKey}`,
61
- 'Content-Type': 'application/json',
62
- },
63
- signal: controller.signal,
64
- });
65
- clearTimeout(timeoutId);
66
- if (!response.ok) {
67
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
68
- }
69
- const data = await response.json();
70
- if (data && data.data && Array.isArray(data.data)) {
71
- return data.data.map((model) => model.id);
72
- }
73
- return [];
74
- }
75
- catch (error) {
76
- console.warn('Failed to fetch available models:', error);
77
- return [];
78
- }
54
+ async fetchAvailableModels() {
55
+ return modelService.fetchModels();
79
56
  }
80
57
  async saveModelConfig(config) {
81
58
  const modelConfig = config;
@@ -1,3 +1,4 @@
1
+ import type { ModelInfo } from '../utils/fetch-models.js';
1
2
  import type { IPlugin } from './base-tool.js';
2
3
  import { BaseTool } from './base-tool.js';
3
4
  export interface OhMyOpenCodeConfig {
@@ -18,12 +19,7 @@ export declare const OPENCODE_DEFAULT_CONFIG: {
18
19
  VISION_CONTEXT: number;
19
20
  VISION_OUTPUT: number;
20
21
  };
21
- export interface ModelInfo {
22
- id: string;
23
- name?: string;
24
- context_length?: number;
25
- max_output_tokens?: number;
26
- }
22
+ export type { ModelInfo } from '../utils/fetch-models.js';
27
23
  export interface OpenCodeModelEntry {
28
24
  name?: string;
29
25
  limit?: {
@@ -70,7 +66,6 @@ export declare class OpenCodeTool extends BaseTool {
70
66
  readonly installCommand = "npm install -g opencode-ai";
71
67
  readonly configPath: string;
72
68
  private ohMyOpenCodeConfigPath;
73
- private cachedModels;
74
69
  private constructor();
75
70
  static getInstance(): OpenCodeTool;
76
71
  /**
@@ -88,11 +83,7 @@ export declare class OpenCodeTool extends BaseTool {
88
83
  /**
89
84
  * Fetch available models from API
90
85
  */
91
- fetchAvailableModels(apiKey: string): Promise<string[]>;
92
- /**
93
- * Fetch available models with full info from API
94
- */
95
- fetchAvailableModelsWithInfo(apiKey: string): Promise<ModelInfo[]>;
86
+ fetchAvailableModels(): Promise<string[]>;
96
87
  /**
97
88
  * Check if model is a vision model (contains "VL" or ends with "V")
98
89
  */
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { configManager } from '../config.js';
5
+ import { modelService } from '../model-service.js';
5
6
  import { BaseTool } from './base-tool.js';
6
7
  // X-AIO Provider ID
7
8
  const XAIO_PROVIDER_ID = 'xaio';
@@ -40,7 +41,6 @@ export class OpenCodeTool extends BaseTool {
40
41
  installCommand = 'npm install -g opencode-ai';
41
42
  configPath;
42
43
  ohMyOpenCodeConfigPath;
43
- cachedModels = [];
44
44
  constructor() {
45
45
  super();
46
46
  // OpenCode config file paths (cross-platform support)
@@ -96,45 +96,8 @@ export class OpenCodeTool extends BaseTool {
96
96
  /**
97
97
  * Fetch available models from API
98
98
  */
99
- async fetchAvailableModels(apiKey) {
100
- const models = await this.fetchAvailableModelsWithInfo(apiKey);
101
- return models.map(m => m.id);
102
- }
103
- /**
104
- * Fetch available models with full info from API
105
- */
106
- async fetchAvailableModelsWithInfo(apiKey) {
107
- try {
108
- const controller = new AbortController();
109
- const timeoutId = setTimeout(() => controller.abort(), 30000);
110
- const response = await fetch(`${OPENCODE_DEFAULT_CONFIG.BASE_URL}/models`, {
111
- method: 'GET',
112
- headers: {
113
- 'Authorization': `Bearer ${apiKey}`,
114
- 'Content-Type': 'application/json',
115
- },
116
- signal: controller.signal,
117
- });
118
- clearTimeout(timeoutId);
119
- if (!response.ok) {
120
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
121
- }
122
- const data = await response.json();
123
- if (data && data.data && Array.isArray(data.data)) {
124
- this.cachedModels = data.data.map((model) => ({
125
- id: model.id,
126
- name: model.name || model.id,
127
- context_length: model.context_length,
128
- max_output_tokens: model.max_output_tokens,
129
- }));
130
- return this.cachedModels;
131
- }
132
- return [];
133
- }
134
- catch (error) {
135
- console.warn('Failed to fetch available models:', error);
136
- return [];
137
- }
99
+ async fetchAvailableModels() {
100
+ return modelService.fetchModels();
138
101
  }
139
102
  /**
140
103
  * Check if model is a vision model (contains "VL" or ends with "V")
@@ -183,10 +146,7 @@ export class OpenCodeTool extends BaseTool {
183
146
  async saveModelConfig(config) {
184
147
  const modelConfig = config;
185
148
  // Fetch all available models from API
186
- let modelInfos = this.cachedModels;
187
- if (modelInfos.length === 0) {
188
- modelInfos = await this.fetchAvailableModelsWithInfo(modelConfig.apiKey);
189
- }
149
+ const modelInfos = await modelService.fetchModelsWithInfo();
190
150
  // Build models config from API response
191
151
  const modelsConfig = this.buildModelsConfig(modelInfos);
192
152
  // If no models fetched, at least include the selected ones
@@ -0,0 +1,8 @@
1
+ export declare const CODE_PLAN_MODEL_LIST_API = "https://dashboard.x-aio.com/api/index_view/code_plan_model_list";
2
+ export interface ModelInfo {
3
+ id: string;
4
+ name?: string;
5
+ context_length?: number;
6
+ max_output_tokens?: number;
7
+ }
8
+ export declare function fetchModelsFromApi(): Promise<ModelInfo[]>;
@@ -0,0 +1,30 @@
1
+ export const CODE_PLAN_MODEL_LIST_API = 'https://dashboard.x-aio.com/api/index_view/code_plan_model_list';
2
+ export async function fetchModelsFromApi() {
3
+ const controller = new AbortController();
4
+ const timeoutId = setTimeout(() => controller.abort(), 30000);
5
+ try {
6
+ const response = await fetch(CODE_PLAN_MODEL_LIST_API, {
7
+ method: 'GET',
8
+ signal: controller.signal,
9
+ });
10
+ clearTimeout(timeoutId);
11
+ if (!response.ok) {
12
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
13
+ }
14
+ const responseData = await response.json();
15
+ const models = responseData?.data;
16
+ if (models && Array.isArray(models)) {
17
+ return models.map((m) => ({
18
+ id: m.real_model_name,
19
+ name: m.real_model_name,
20
+ context_length: m.context ? m.context * 1000 : undefined,
21
+ max_output_tokens: m.context ? m.context * 500 : undefined,
22
+ }));
23
+ }
24
+ return [];
25
+ }
26
+ catch (error) {
27
+ clearTimeout(timeoutId);
28
+ throw error;
29
+ }
30
+ }
@@ -119,6 +119,9 @@ export class PluginMenu {
119
119
  }
120
120
  else {
121
121
  spinner.fail(i18n.t('wizard.refresh_models_failed'));
122
+ if (result.error) {
123
+ console.log(chalk.red(` Error: ${result.error}`));
124
+ }
122
125
  }
123
126
  }
124
127
  }
@@ -228,7 +228,7 @@ export class ToolMenu {
228
228
  actionText = i18n.t('wizard.action_refresh_config', { tool: toolRegistry.getTool(toolName)?.displayName });
229
229
  }
230
230
  else {
231
- console.log(chalk.blue(`${i18n.t('wizard.config_not_loaded')}`));
231
+ console.log(chalk.blue(`${i18n.t('wizard.config_not_loaded', { tool: toolRegistry.getTool(toolName)?.displayName })}`));
232
232
  actionText = i18n.t('wizard.action_load_config', { tool: toolRegistry.getTool(toolName)?.displayName });
233
233
  }
234
234
  console.log('');
@@ -500,11 +500,9 @@ export class ToolMenu {
500
500
  }).start();
501
501
  let result;
502
502
  if (toolName === 'opencode') {
503
- // OpenCode: 刷新并同步到配置文件
504
503
  result = await modelService.refreshAndSyncToOpenCode();
505
504
  }
506
505
  else {
507
- // 其他工具: 仅刷新缓存
508
506
  result = await modelService.refreshModels();
509
507
  }
510
508
  if (result.success) {
@@ -512,6 +510,9 @@ export class ToolMenu {
512
510
  }
513
511
  else {
514
512
  spinner.fail(i18n.t('wizard.refresh_models_failed'));
513
+ if (result.error) {
514
+ console.log(chalk.red(` Error: ${result.error}`));
515
+ }
515
516
  }
516
517
  await new Promise(resolve => setTimeout(resolve, 1500));
517
518
  }
@@ -111,7 +111,7 @@
111
111
  "current_small_model": "Small Model",
112
112
  "config_synced": "Configuration synchronized",
113
113
  "config_out_of_sync": "Configuration out of sync, refresh recommended",
114
- "config_not_loaded": "Claude Code configuration not loaded yet",
114
+ "config_not_loaded": "{{tool}} configuration not loaded yet",
115
115
  "action_refresh_config": "Configuration Refresh - (Update {{tool}}'s configuration)",
116
116
  "global_config_warning": "⚠️ Warning: You are modifying the {{tool}} global configuration. Changes will affect all workspaces",
117
117
  "global_label": "GLOBAL",
@@ -111,7 +111,7 @@
111
111
  "current_small_model": "小模型",
112
112
  "config_synced": "配置已同步",
113
113
  "config_out_of_sync": "配置不一致,建议刷新",
114
- "config_not_loaded": "Claude Code 尚未装载配置",
114
+ "config_not_loaded": "{{tool}} 尚未装载配置",
115
115
  "action_refresh_config": "配置刷新 - (更新 {{tool}} 的配置)",
116
116
  "global_config_warning": "⚠️ 注意:您正在修改 {{tool}} 全局配置,更改将影响所有工作区",
117
117
  "global_label": "全局",
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.0",
4
+ "version": "0.3.2",
5
5
  "description": "X All In One Coding Helper",
6
6
  "author": "X.AIO",
7
7
  "homepage": "https://docs.x-aio.com/zh/docs",