@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.
- package/dist/lib/plugins/oh-my-opencode-plugin.d.ts +0 -3
- package/dist/lib/plugins/oh-my-opencode-plugin.js +6 -14
- package/dist/lib/tools/opencode-tool.d.ts +13 -7
- package/dist/lib/tools/opencode-tool.js +118 -66
- package/dist/lib/utils/fetch-models.d.ts +1 -0
- package/dist/lib/utils/fetch-models.js +1 -0
- package/dist/lib/wizard/menus/plugin-menu.js +5 -0
- package/dist/lib/wizard/menus/tool-menu.js +1 -1
- package/package.json +2 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
327
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
345
|
-
currentConfig.agents[agentName] = { model: prefixedModel };
|
|
371
|
+
config.agents[agentName] = { model: targetModel };
|
|
346
372
|
}
|
|
347
373
|
}
|
|
348
|
-
|
|
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
|
}
|
|
@@ -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.
|
|
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"
|