@x-all-in-one/coding-helper 0.4.5 → 0.4.7

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.
@@ -13,9 +13,6 @@ export declare class OhMyOpenCodePlugin implements IPlugin {
13
13
  * 通过检查配置文件或 plugin 数组来判断
14
14
  */
15
15
  isInstalled(): boolean;
16
- /**
17
- * 安装 Oh My OpenCode
18
- */
19
16
  install(): Promise<void>;
20
17
  /**
21
18
  * 装载 Oh My OpenCode(添加到 OpenCode 配置)
@@ -27,34 +27,26 @@ export class OhMyOpenCodePlugin {
27
27
  // 或者检查是否已在 opencode.json 的 plugin 数组中
28
28
  return this.isLoaded();
29
29
  }
30
- /**
31
- * 安装 Oh My OpenCode
32
- */
33
30
  async install() {
34
- // 使用 bunInstaller 确保 Bun 已安装
35
31
  const hasBun = await bunInstaller.ensureBun();
36
32
  if (!hasBun) {
37
33
  throw new Error('Bun is required for Oh My OpenCode. Please install bun first.');
38
34
  }
39
35
  const isWindows = process.platform === 'win32';
40
- const command = isWindows ? 'bunx.cmd' : 'bunx';
41
- const args = ['oh-my-opencode', 'install', '--no-tui', '--claude=no', '--chatgpt=no', '--gemini=no'];
36
+ const bunxCommand = isWindows ? 'bunx.cmd' : 'bunx';
42
37
  return new Promise((resolve, reject) => {
43
- const child = spawn(command, args, {
38
+ const args = ['oh-my-opencode@latest', 'install', '--no-tui', '--claude=no', '--openai=no', '--gemini=no', '--copilot=no'];
39
+ const child = spawn(bunxCommand, args, {
44
40
  stdio: 'inherit',
45
41
  shell: true,
46
42
  });
47
43
  child.on('close', (code) => {
48
- if (code === 0) {
44
+ if (code === 0)
49
45
  resolve();
50
- }
51
- else {
46
+ else
52
47
  reject(new Error(`Oh My OpenCode install failed with code ${code}`));
53
- }
54
- });
55
- child.on('error', (error) => {
56
- reject(error);
57
48
  });
49
+ child.on('error', reject);
58
50
  });
59
51
  }
60
52
  /**
@@ -7,6 +7,10 @@ export interface OhMyOpenCodeConfig {
7
7
  model?: string;
8
8
  [key: string]: any;
9
9
  }>;
10
+ categories?: Record<string, {
11
+ model?: string;
12
+ [key: string]: any;
13
+ }>;
10
14
  [key: string]: any;
11
15
  }
12
16
  export declare const OPENCODE_DEFAULT_CONFIG: {
@@ -34,6 +38,9 @@ export interface OpenCodeModelEntry {
34
38
  interleaved?: {
35
39
  field: string;
36
40
  };
41
+ provider?: {
42
+ npm: string;
43
+ };
37
44
  }
38
45
  export interface OpenCodeProviderConfig {
39
46
  npm: string;
@@ -58,6 +65,7 @@ export interface OpenCodeModelConfig {
58
65
  smallModel: string;
59
66
  baseUrl?: string;
60
67
  }
68
+ export declare function buildOpenCodeModelEntry(info: ModelInfo): OpenCodeModelEntry;
61
69
  /**
62
70
  * OpenCode 工具实现
63
71
  * 实现 ITool 接口,管理 OpenCode 的配置和生命周期
@@ -88,10 +96,6 @@ export declare class OpenCodeTool extends BaseTool {
88
96
  * Fetch available models from API
89
97
  */
90
98
  fetchAvailableModels(): Promise<string[]>;
91
- private hasTag;
92
- private isVisionModelByTag;
93
- private isReasoningModel;
94
- private isInterleavedReasoningModel;
95
99
  private buildModelsConfig;
96
100
  /**
97
101
  * Save model config to OpenCode (merge, not overwrite)
@@ -123,11 +127,13 @@ export declare class OpenCodeTool extends BaseTool {
123
127
  * Save oh-my-opencode config
124
128
  */
125
129
  saveOhMyOpenCodeConfig(config: OhMyOpenCodeConfig): void;
130
+ private updateOhMyOpenCodeAgents;
131
+ private updateOhMyOpenCodeCategories;
126
132
  /**
127
- * Update oh-my-opencode agent models
128
- * Only updates the model field, preserves other agent configurations
133
+ * Update oh-my-opencode agent and category models
134
+ * Only updates the model field, preserves other configurations
129
135
  */
130
- updateOhMyOpenCodeModels(model: string): void;
136
+ updateOhMyOpenCodeModels(model: string, smallModel: string): void;
131
137
  /**
132
138
  * 获取所有已注册的插件
133
139
  */
@@ -7,16 +7,41 @@ import { modelService } from '../model-service.js';
7
7
  import { BaseTool } from './base-tool.js';
8
8
  // X-AIO Provider ID
9
9
  const XAIO_PROVIDER_ID = 'xaio';
10
- // oh-my-opencode agent names
10
+ // oh-my-opencode agent names (latest version)
11
11
  const OH_MY_OPENCODE_AGENTS = [
12
- 'Sisyphus',
12
+ 'sisyphus',
13
+ 'hephaestus',
14
+ 'oracle',
13
15
  'librarian',
14
16
  'explore',
15
- 'oracle',
16
- 'frontend-ui-ux-engineer',
17
- 'document-writer',
18
17
  'multimodal-looker',
18
+ 'prometheus',
19
+ 'metis',
20
+ 'momus',
19
21
  'atlas',
22
+ 'frontend-ui-ux-engineer',
23
+ 'document-writer',
24
+ ];
25
+ const OH_MY_OPENCODE_SMALL_AGENTS = [
26
+ 'librarian',
27
+ 'explore',
28
+ 'document-writer',
29
+ ];
30
+ // oh-my-opencode category names
31
+ const OH_MY_OPENCODE_CATEGORIES = [
32
+ 'visual-engineering',
33
+ 'ultrabrain',
34
+ 'deep',
35
+ 'artistry',
36
+ 'quick',
37
+ 'unspecified-low',
38
+ 'unspecified-high',
39
+ 'writing',
40
+ ];
41
+ const OH_MY_OPENCODE_SMALL_CATEGORIES = [
42
+ 'quick',
43
+ 'unspecified-low',
44
+ 'writing',
20
45
  ];
21
46
  // Unified output token limit for all models
22
47
  const OUTPUT_TOKEN_LIMIT = 32000;
@@ -25,6 +50,18 @@ const TAG_VISION = '视觉';
25
50
  const TAG_REASONING = '推理';
26
51
  const TAG_INTERLEAVED_REASONING = '交替推理';
27
52
  const deepmerge = deepmergeCustom({ mergeArrays: false });
53
+ function hasTag(tags, tag) {
54
+ return Array.isArray(tags) && tags.includes(tag);
55
+ }
56
+ function isVisionModelByTag(tags) {
57
+ return hasTag(tags, TAG_VISION);
58
+ }
59
+ function isReasoningModel(tags) {
60
+ return hasTag(tags, TAG_REASONING) && !hasTag(tags, TAG_INTERLEAVED_REASONING);
61
+ }
62
+ function isInterleavedReasoningModel(tags) {
63
+ return hasTag(tags, TAG_INTERLEAVED_REASONING);
64
+ }
28
65
  // Default configuration
29
66
  export const OPENCODE_DEFAULT_CONFIG = {
30
67
  BASE_URL: 'https://code-api.x-aio.com/v1',
@@ -38,6 +75,45 @@ export const OPENCODE_DEFAULT_CONFIG = {
38
75
  VISION_CONTEXT: 64000,
39
76
  VISION_OUTPUT: OUTPUT_TOKEN_LIMIT,
40
77
  };
78
+ export function buildOpenCodeModelEntry(info) {
79
+ const entry = {};
80
+ const isVision = isVisionModelByTag(info.tags);
81
+ const isReasoning = isReasoningModel(info.tags);
82
+ const isInterleavedReasoning = isInterleavedReasoningModel(info.tags);
83
+ if (info.name && info.name !== info.id) {
84
+ entry.name = info.name;
85
+ }
86
+ if (isVision) {
87
+ entry.limit = {
88
+ context: info.context_length || OPENCODE_DEFAULT_CONFIG.VISION_CONTEXT,
89
+ output: OUTPUT_TOKEN_LIMIT,
90
+ };
91
+ entry.modalities = {
92
+ input: ['text', 'image', 'video'],
93
+ output: ['text'],
94
+ };
95
+ }
96
+ else {
97
+ entry.limit = {
98
+ context: info.context_length || OPENCODE_DEFAULT_CONFIG.DEFAULT_CONTEXT,
99
+ output: OUTPUT_TOKEN_LIMIT,
100
+ };
101
+ }
102
+ const isResponsesModel = info.upstream_type === 'responses';
103
+ if (isInterleavedReasoning) {
104
+ entry.reasoning = true;
105
+ if (!isResponsesModel) {
106
+ entry.interleaved = { field: 'reasoning_content' };
107
+ }
108
+ }
109
+ else if (isReasoning) {
110
+ entry.reasoning = true;
111
+ }
112
+ if (isResponsesModel) {
113
+ entry.provider = { npm: '@ai-sdk/openai' };
114
+ }
115
+ return entry;
116
+ }
41
117
  /**
42
118
  * OpenCode 工具实现
43
119
  * 实现 ITool 接口,管理 OpenCode 的配置和生命周期
@@ -108,52 +184,10 @@ export class OpenCodeTool extends BaseTool {
108
184
  async fetchAvailableModels() {
109
185
  return modelService.fetchModels();
110
186
  }
111
- hasTag(tags, tag) {
112
- return Array.isArray(tags) && tags.includes(tag);
113
- }
114
- isVisionModelByTag(tags) {
115
- return this.hasTag(tags, TAG_VISION);
116
- }
117
- isReasoningModel(tags) {
118
- return this.hasTag(tags, TAG_REASONING) && !this.hasTag(tags, TAG_INTERLEAVED_REASONING);
119
- }
120
- isInterleavedReasoningModel(tags) {
121
- return this.hasTag(tags, TAG_INTERLEAVED_REASONING);
122
- }
123
187
  buildModelsConfig(modelInfos) {
124
188
  const models = {};
125
189
  for (const info of modelInfos) {
126
- const entry = {};
127
- const isVision = this.isVisionModelByTag(info.tags);
128
- const isReasoning = this.isReasoningModel(info.tags);
129
- const isInterleavedReasoning = this.isInterleavedReasoningModel(info.tags);
130
- if (info.name && info.name !== info.id) {
131
- entry.name = info.name;
132
- }
133
- if (isVision) {
134
- entry.limit = {
135
- context: info.context_length || OPENCODE_DEFAULT_CONFIG.VISION_CONTEXT,
136
- output: OUTPUT_TOKEN_LIMIT,
137
- };
138
- entry.modalities = {
139
- input: ['text', 'image', 'video'],
140
- output: ['text'],
141
- };
142
- }
143
- else {
144
- entry.limit = {
145
- context: info.context_length || OPENCODE_DEFAULT_CONFIG.DEFAULT_CONTEXT,
146
- output: OUTPUT_TOKEN_LIMIT,
147
- };
148
- }
149
- if (isInterleavedReasoning) {
150
- entry.reasoning = true;
151
- entry.interleaved = { field: 'reasoning_content' };
152
- }
153
- else if (isReasoning) {
154
- entry.reasoning = true;
155
- }
156
- models[info.id] = entry;
190
+ models[info.id] = buildOpenCodeModelEntry(info);
157
191
  }
158
192
  return models;
159
193
  }
@@ -322,30 +356,48 @@ export class OpenCodeTool extends BaseTool {
322
356
  throw new Error(`Failed to save oh-my-opencode config: ${error}`);
323
357
  }
324
358
  }
325
- /**
326
- * Update oh-my-opencode agent models
327
- * Only updates the model field, preserves other agent configurations
328
- */
329
- updateOhMyOpenCodeModels(model) {
330
- const prefixedModel = `${XAIO_PROVIDER_ID}/${model}`;
331
- // Read existing config or create new one
332
- const currentConfig = this.getOhMyOpenCodeConfig();
333
- // Initialize agents if not exists
334
- if (!currentConfig.agents) {
335
- currentConfig.agents = {};
359
+ updateOhMyOpenCodeAgents(config, model, smallModel) {
360
+ if (!config.agents) {
361
+ config.agents = {};
336
362
  }
337
- // Update each agent's model, preserving other settings
338
363
  for (const agentName of OH_MY_OPENCODE_AGENTS) {
339
- if (currentConfig.agents[agentName]) {
340
- // Agent exists, only update model
341
- currentConfig.agents[agentName].model = prefixedModel;
364
+ const targetModel = OH_MY_OPENCODE_SMALL_AGENTS.includes(agentName)
365
+ ? smallModel
366
+ : model;
367
+ if (config.agents[agentName]) {
368
+ config.agents[agentName].model = targetModel;
342
369
  }
343
370
  else {
344
- // Agent doesn't exist, create with only model
345
- currentConfig.agents[agentName] = { model: prefixedModel };
371
+ config.agents[agentName] = { model: targetModel };
346
372
  }
347
373
  }
348
- // Ensure schema is set
374
+ }
375
+ updateOhMyOpenCodeCategories(config, model, smallModel) {
376
+ if (!config.categories) {
377
+ config.categories = {};
378
+ }
379
+ for (const categoryName of OH_MY_OPENCODE_CATEGORIES) {
380
+ const targetModel = OH_MY_OPENCODE_SMALL_CATEGORIES.includes(categoryName)
381
+ ? smallModel
382
+ : model;
383
+ if (config.categories[categoryName]) {
384
+ config.categories[categoryName].model = targetModel;
385
+ }
386
+ else {
387
+ config.categories[categoryName] = { model: targetModel };
388
+ }
389
+ }
390
+ }
391
+ /**
392
+ * Update oh-my-opencode agent and category models
393
+ * Only updates the model field, preserves other configurations
394
+ */
395
+ updateOhMyOpenCodeModels(model, smallModel) {
396
+ const prefixedModel = `${XAIO_PROVIDER_ID}/${model}`;
397
+ const prefixedSmallModel = `${XAIO_PROVIDER_ID}/${smallModel}`;
398
+ const currentConfig = this.getOhMyOpenCodeConfig();
399
+ this.updateOhMyOpenCodeAgents(currentConfig, prefixedModel, prefixedSmallModel);
400
+ this.updateOhMyOpenCodeCategories(currentConfig, prefixedModel, prefixedSmallModel);
349
401
  if (!currentConfig.$schema) {
350
402
  currentConfig.$schema = 'https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json';
351
403
  }
@@ -5,5 +5,6 @@ export interface ModelInfo {
5
5
  context_length?: number;
6
6
  max_output_tokens?: number;
7
7
  tags?: string[] | null;
8
+ upstream_type?: string;
8
9
  }
9
10
  export declare function fetchModelsFromApi(): Promise<ModelInfo[]>;
@@ -20,6 +20,7 @@ export async function fetchModelsFromApi() {
20
20
  context_length: m.context ? m.context * 1000 : undefined,
21
21
  max_output_tokens: m.context ? m.context * 500 : undefined,
22
22
  tags: Array.isArray(m.tags) ? m.tags : null,
23
+ upstream_type: typeof m.upstream_type === 'string' ? m.upstream_type : undefined,
23
24
  }));
24
25
  }
25
26
  return [];
@@ -3,6 +3,7 @@ import inquirer from 'inquirer';
3
3
  import ora from 'ora';
4
4
  import { i18n } from '../../i18n.js';
5
5
  import { modelService } from '../../model-service.js';
6
+ import { openCodeTool } from '../../tools/opencode-tool.js';
6
7
  import { promptHelper } from '../ui/prompt-helper.js';
7
8
  import { uiRenderer } from '../ui/ui-renderer.js';
8
9
  /**
@@ -115,6 +116,10 @@ export class PluginMenu {
115
116
  }).start();
116
117
  const result = await modelService.refreshAndSyncToOpenCode();
117
118
  if (result.success) {
119
+ const modelConfig = openCodeTool.getModelConfig();
120
+ if (modelConfig?.model && modelConfig.smallModel) {
121
+ openCodeTool.updateOhMyOpenCodeModels(modelConfig.model, modelConfig.smallModel);
122
+ }
118
123
  spinner.succeed(chalk.green(i18n.t('wizard.models_refreshed', { count: String(result.count) })));
119
124
  }
120
125
  else {
@@ -364,7 +364,7 @@ export class ToolMenu {
364
364
  // Update oh-my-opencode agent models if plugin is installed
365
365
  if (toolName === 'opencode' && openCodeTool.hasOhMyOpenCodePlugin()) {
366
366
  spinner.text = i18n.t('wizard.oh_my_opencode_detected');
367
- openCodeTool.updateOhMyOpenCodeModels(openCodeModel);
367
+ openCodeTool.updateOhMyOpenCodeModels(openCodeModel, openCodeSmallModel);
368
368
  await new Promise(resolve => setTimeout(resolve, 500));
369
369
  console.log(chalk.gray(` ${i18n.t('wizard.oh_my_opencode_models_updated')}`));
370
370
  }
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.4.5",
4
+ "version": "0.4.7",
5
5
  "description": "X All In One Coding Helper",
6
6
  "author": "X.AIO",
7
7
  "homepage": "https://docs.x-aio.com/zh/docs",
@@ -69,6 +69,7 @@
69
69
  "lint": "eslint .",
70
70
  "lint:fix": "eslint . --fix",
71
71
  "type-check": "tsc --noEmit",
72
+ "verify:opencode-provider": "tsx scripts/verify-opencode-responses-provider.ts",
72
73
  "changeset": "changeset",
73
74
  "version": "changeset version",
74
75
  "release": "pnpm run build && changeset publish"