pulse-coder-engine 0.0.1-alpha.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.
- package/examples/new-engine-usage.ts +52 -0
- package/package.json +54 -0
- package/src/Engine.ts +150 -0
- package/src/ai/index.ts +116 -0
- package/src/built-in/index.ts +27 -0
- package/src/built-in/mcp-plugin/index.ts +104 -0
- package/src/built-in/skills-plugin/index.ts +223 -0
- package/src/built-in/sub-agent-plugin/index.ts +203 -0
- package/src/config/index.ts +35 -0
- package/src/context/index.ts +134 -0
- package/src/core/loop.ts +147 -0
- package/src/index.ts +17 -0
- package/src/plugin/EnginePlugin.ts +60 -0
- package/src/plugin/PluginManager.ts +426 -0
- package/src/plugin/UserConfigPlugin.ts +183 -0
- package/src/prompt/index.ts +1 -0
- package/src/prompt/system.ts +126 -0
- package/src/shared/types.ts +50 -0
- package/src/tools/bash.ts +59 -0
- package/src/tools/clarify.ts +74 -0
- package/src/tools/edit.ts +79 -0
- package/src/tools/grep.ts +148 -0
- package/src/tools/index.ts +44 -0
- package/src/tools/ls.ts +20 -0
- package/src/tools/read.ts +69 -0
- package/src/tools/tavily.ts +55 -0
- package/src/tools/utils.ts +16 -0
- package/src/tools/write.ts +42 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Engine } from '../src/Engine.js';
|
|
2
|
+
import { skillRegistryPlugin } from '../src/plugins/skill-registry.plugin.js';
|
|
3
|
+
|
|
4
|
+
// 示例1:基础使用
|
|
5
|
+
async function basicUsage() {
|
|
6
|
+
const engine = new Engine();
|
|
7
|
+
|
|
8
|
+
// 加载引擎插件(包括技能系统)
|
|
9
|
+
await engine.initialize();
|
|
10
|
+
|
|
11
|
+
console.log('✅ Engine initialized with plugin system');
|
|
12
|
+
console.log('📊 Plugin status:', engine.getPluginStatus());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 示例2:自定义配置
|
|
16
|
+
async function customConfigUsage() {
|
|
17
|
+
const engine = new Engine({
|
|
18
|
+
enginePlugins: {
|
|
19
|
+
plugins: [skillRegistryPlugin],
|
|
20
|
+
dirs: ['./custom-engine-plugins']
|
|
21
|
+
},
|
|
22
|
+
userConfigPlugins: {
|
|
23
|
+
dirs: ['./config', '~/.coder/config'],
|
|
24
|
+
scan: true
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await engine.initialize();
|
|
29
|
+
|
|
30
|
+
const context = { messages: [] };
|
|
31
|
+
const result = await engine.run(context);
|
|
32
|
+
console.log('🎯 Execution result:', result);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 示例3:动态添加用户配置
|
|
36
|
+
async function dynamicConfigUsage() {
|
|
37
|
+
const engine = new Engine();
|
|
38
|
+
await engine.initialize();
|
|
39
|
+
|
|
40
|
+
// 运行时添加用户配置
|
|
41
|
+
// 这将在第二阶段实现
|
|
42
|
+
console.log('🔄 Dynamic config will be available in phase 2');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 运行示例
|
|
46
|
+
async function main() {
|
|
47
|
+
await basicUsage();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
51
|
+
main().catch(console.error);
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pulse-coder-engine",
|
|
3
|
+
"version": "0.0.1-alpha.1",
|
|
4
|
+
"description": "Plugin-based AI engine for Pulse Coder",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./src": {
|
|
14
|
+
"types": "./src/index.ts",
|
|
15
|
+
"import": "./src/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./dist/types.d.ts",
|
|
19
|
+
"import": "./dist/types.js"
|
|
20
|
+
},
|
|
21
|
+
"./built-in": {
|
|
22
|
+
"types": "./dist/built-in/index.d.ts",
|
|
23
|
+
"import": "./dist/built-in/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup",
|
|
31
|
+
"dev": "tsup --watch",
|
|
32
|
+
"test": "vitest",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@ai-sdk/anthropic": "^3.0.42",
|
|
37
|
+
"@ai-sdk/mcp": "^1.0.19",
|
|
38
|
+
"@ai-sdk/openai": "^3.0.21",
|
|
39
|
+
"@requesty/ai-sdk": "^3.1.0",
|
|
40
|
+
"ai": "^6.0.57",
|
|
41
|
+
"dotenv": "^17.2.3",
|
|
42
|
+
"glob": "^11.0.0",
|
|
43
|
+
"gray-matter": "^4.0.3",
|
|
44
|
+
"yaml": "^2.8.2",
|
|
45
|
+
"zod": "^4.3.6"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/glob": "^8.1.0",
|
|
49
|
+
"@types/node": "^25.0.10",
|
|
50
|
+
"tsup": "^8.0.0",
|
|
51
|
+
"typescript": "^5.0.0",
|
|
52
|
+
"vitest": "^1.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/Engine.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { Context } from './shared/types';
|
|
2
|
+
import type { LoopOptions } from './core/loop';
|
|
3
|
+
import type { EnginePluginLoadOptions } from './plugin/EnginePlugin.js';
|
|
4
|
+
import type { UserConfigPluginLoadOptions } from './plugin/UserConfigPlugin.js';
|
|
5
|
+
|
|
6
|
+
import { loop } from './core/loop.js';
|
|
7
|
+
import { BuiltinToolsMap } from './tools/index.js';
|
|
8
|
+
import { PluginManager } from './plugin/PluginManager.js';
|
|
9
|
+
import { builtInPlugins } from './built-in/index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 引擎配置选项
|
|
13
|
+
*/
|
|
14
|
+
export interface EngineOptions {
|
|
15
|
+
// 引擎插件配置
|
|
16
|
+
enginePlugins?: EnginePluginLoadOptions;
|
|
17
|
+
|
|
18
|
+
// 用户配置插件配置
|
|
19
|
+
userConfigPlugins?: UserConfigPluginLoadOptions;
|
|
20
|
+
|
|
21
|
+
// 是否禁用内置插件(默认启用)
|
|
22
|
+
disableBuiltInPlugins?: boolean;
|
|
23
|
+
|
|
24
|
+
// 全局配置
|
|
25
|
+
config?: Record<string, any>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 重构后的引擎类
|
|
30
|
+
* 自动包含内置插件,支持可选禁用
|
|
31
|
+
*/
|
|
32
|
+
export class Engine {
|
|
33
|
+
private pluginManager: PluginManager;
|
|
34
|
+
private tools: Record<string, any> = { ...BuiltinToolsMap };
|
|
35
|
+
private options: EngineOptions = {};
|
|
36
|
+
private config: Record<string, any> = {};
|
|
37
|
+
|
|
38
|
+
constructor(options?: EngineOptions) {
|
|
39
|
+
this.pluginManager = new PluginManager();
|
|
40
|
+
|
|
41
|
+
// 初始化全局配置
|
|
42
|
+
this.config = options?.config || {};
|
|
43
|
+
this.options = options || {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 初始化引擎和插件系统
|
|
48
|
+
* 自动包含内置插件
|
|
49
|
+
*/
|
|
50
|
+
async initialize(): Promise<void> {
|
|
51
|
+
console.log('Initializing engine...', this.config);
|
|
52
|
+
|
|
53
|
+
// 准备插件列表:内置插件 + 用户配置插件
|
|
54
|
+
const allEnginePlugins = this.prepareEnginePlugins();
|
|
55
|
+
|
|
56
|
+
// 插件管理器会自动处理加载顺序
|
|
57
|
+
await this.pluginManager.initialize({
|
|
58
|
+
enginePlugins: {
|
|
59
|
+
...allEnginePlugins
|
|
60
|
+
},
|
|
61
|
+
userConfigPlugins: {
|
|
62
|
+
...(this.options.userConfigPlugins || {})
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 合并插件工具到引擎工具库
|
|
67
|
+
const pluginTools = this.pluginManager.getTools();
|
|
68
|
+
this.tools = { ...this.tools, ...pluginTools };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 准备引擎插件列表(包含内置插件)
|
|
73
|
+
*/
|
|
74
|
+
private prepareEnginePlugins(): EnginePluginLoadOptions {
|
|
75
|
+
const userPlugins = this.options.enginePlugins || {};
|
|
76
|
+
|
|
77
|
+
// 如果用户禁用了内置插件,只返回用户插件
|
|
78
|
+
if (this.options.disableBuiltInPlugins) {
|
|
79
|
+
return userPlugins;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 合并内置插件和用户插件
|
|
83
|
+
const builtInPluginList = [...builtInPlugins];
|
|
84
|
+
const userPluginList = userPlugins.plugins || [];
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
plugins: [...builtInPluginList, ...userPluginList],
|
|
88
|
+
dirs: userPlugins.dirs || ['.pulse-coder/engine-plugins', '.coder/engine-plugins', '~/.pulse-coder/engine-plugins', '~/.coder/engine-plugins'],
|
|
89
|
+
scan: userPlugins.scan !== false // 默认启用扫描
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 运行AI循环
|
|
95
|
+
*/
|
|
96
|
+
async run(context: Context, options?: LoopOptions): Promise<string> {
|
|
97
|
+
return loop(context, {
|
|
98
|
+
...options,
|
|
99
|
+
tools: this.tools,
|
|
100
|
+
onToolCall: (toolCall) => {
|
|
101
|
+
options?.onToolCall?.(toolCall);
|
|
102
|
+
},
|
|
103
|
+
onClarificationRequest: options?.onClarificationRequest,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 获取插件状态
|
|
109
|
+
*/
|
|
110
|
+
getPluginStatus() {
|
|
111
|
+
return this.pluginManager.getStatus();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 获取工具
|
|
116
|
+
*/
|
|
117
|
+
getTools(): Record<string, any> {
|
|
118
|
+
return { ...this.tools };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 获取服务
|
|
123
|
+
*/
|
|
124
|
+
getService<T>(name: string): T | undefined {
|
|
125
|
+
return this.pluginManager.getService<T>(name);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 获取配置
|
|
130
|
+
*/
|
|
131
|
+
getConfig<T>(key: string): T | undefined {
|
|
132
|
+
return this.config[key];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 设置配置
|
|
137
|
+
*/
|
|
138
|
+
setConfig<T>(key: string, value: T): void {
|
|
139
|
+
this.config[key] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 重新导出类型
|
|
144
|
+
export * from './shared/types.js';
|
|
145
|
+
export * from './plugin/EnginePlugin.js';
|
|
146
|
+
export * from './plugin/UserConfigPlugin.js';
|
|
147
|
+
export { loop } from './core/loop.js';
|
|
148
|
+
export { streamTextAI } from './ai/index.js';
|
|
149
|
+
export { maybeCompactContext } from './context/index.js';
|
|
150
|
+
export * from './tools/index.js';
|
package/src/ai/index.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { generateText, streamText, tool, type ModelMessage, type StepResult, type Tool } from 'ai';
|
|
2
|
+
import { CoderAI, DEFAULT_MODEL, COMPACT_SUMMARY_MAX_TOKENS, OPENAI_REASONING_EFFORT } from '../config';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
import { generateSystemPrompt } from '../prompt';
|
|
5
|
+
import type { Tool as CoderTool, ToolExecutionContext } from '../shared/types';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const providerOptions = OPENAI_REASONING_EFFORT
|
|
9
|
+
? { openai: { reasoningEffort: OPENAI_REASONING_EFFORT } }
|
|
10
|
+
: undefined;
|
|
11
|
+
|
|
12
|
+
export const generateTextAI = (messages: ModelMessage[], tools: Record<string, Tool>) => {
|
|
13
|
+
const finalMessages = [
|
|
14
|
+
{
|
|
15
|
+
role: 'system',
|
|
16
|
+
content: generateSystemPrompt(),
|
|
17
|
+
},
|
|
18
|
+
...messages,
|
|
19
|
+
] as ModelMessage[];
|
|
20
|
+
|
|
21
|
+
return generateText({
|
|
22
|
+
model: CoderAI(DEFAULT_MODEL),
|
|
23
|
+
messages: finalMessages,
|
|
24
|
+
tools,
|
|
25
|
+
providerOptions,
|
|
26
|
+
}) as unknown as ReturnType<typeof generateText> & { steps: StepResult<any>[]; finishReason: string };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StreamOptions {
|
|
30
|
+
abortSignal?: AbortSignal;
|
|
31
|
+
onStepFinish?: (event: StepResult<any>) => void;
|
|
32
|
+
onChunk?: (event: { chunk: any }) => void;
|
|
33
|
+
toolExecutionContext?: ToolExecutionContext;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Wraps tools to inject ToolExecutionContext before execution
|
|
38
|
+
*/
|
|
39
|
+
export const wrapToolsWithContext = (
|
|
40
|
+
tools: Record<string, CoderTool>,
|
|
41
|
+
context?: ToolExecutionContext
|
|
42
|
+
): Record<string, Tool> => {
|
|
43
|
+
const wrappedTools: Record<string, Tool> = {};
|
|
44
|
+
|
|
45
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
46
|
+
wrappedTools[name] = {
|
|
47
|
+
...tool,
|
|
48
|
+
execute: async (input: any) => {
|
|
49
|
+
// Call the original execute with context
|
|
50
|
+
return await tool.execute(input, context);
|
|
51
|
+
}
|
|
52
|
+
} as Tool;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return wrappedTools;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const streamTextAI = (messages: ModelMessage[], tools: Record<string, CoderTool>, options?: StreamOptions) => {
|
|
59
|
+
|
|
60
|
+
const finalMessages = [
|
|
61
|
+
{
|
|
62
|
+
role: 'system',
|
|
63
|
+
content: generateSystemPrompt(),
|
|
64
|
+
},
|
|
65
|
+
...messages,
|
|
66
|
+
] as ModelMessage[];
|
|
67
|
+
|
|
68
|
+
// Wrap tools with execution context if provided
|
|
69
|
+
const wrappedTools = options?.toolExecutionContext
|
|
70
|
+
? wrapToolsWithContext(tools, options.toolExecutionContext)
|
|
71
|
+
: tools;
|
|
72
|
+
|
|
73
|
+
return streamText({
|
|
74
|
+
model: CoderAI(DEFAULT_MODEL),
|
|
75
|
+
messages: finalMessages,
|
|
76
|
+
tools: wrappedTools as Record<string, Tool>,
|
|
77
|
+
providerOptions,
|
|
78
|
+
abortSignal: options?.abortSignal,
|
|
79
|
+
onStepFinish: options?.onStepFinish,
|
|
80
|
+
onChunk: options?.onChunk,
|
|
81
|
+
}) as unknown as ReturnType<typeof streamText> & { steps: StepResult<any>[]; finishReason: string };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const summarizeMessages = async (
|
|
85
|
+
messages: ModelMessage[],
|
|
86
|
+
options?: { maxOutputTokens?: number }
|
|
87
|
+
): Promise<string> => {
|
|
88
|
+
const SUMMARY_SYSTEM_PROMPT =
|
|
89
|
+
'你是负责压缩对话上下文的助手。请基于给定历史消息,提炼关键事实与决策,避免臆测或扩展。';
|
|
90
|
+
|
|
91
|
+
const SUMMARY_USER_PROMPT = [
|
|
92
|
+
'请将以上对话压缩为以下格式,使用中文:',
|
|
93
|
+
'[COMPACTED_CONTEXT]',
|
|
94
|
+
'- 目标: ...',
|
|
95
|
+
'- 进展: ...',
|
|
96
|
+
'- 关键结果: ...',
|
|
97
|
+
'- 文件与变更: ...',
|
|
98
|
+
'- 关键片段: "..." / `...` / "..."',
|
|
99
|
+
'- 待确认: ...',
|
|
100
|
+
'',
|
|
101
|
+
'要求:内容简洁准确,不要编造。',
|
|
102
|
+
].join('\n');
|
|
103
|
+
|
|
104
|
+
const result = await generateText({
|
|
105
|
+
model: CoderAI(DEFAULT_MODEL),
|
|
106
|
+
messages: [
|
|
107
|
+
{ role: 'system', content: SUMMARY_SYSTEM_PROMPT },
|
|
108
|
+
...messages,
|
|
109
|
+
{ role: 'user', content: SUMMARY_USER_PROMPT },
|
|
110
|
+
],
|
|
111
|
+
maxOutputTokens: options?.maxOutputTokens ?? COMPACT_SUMMARY_MAX_TOKENS,
|
|
112
|
+
providerOptions,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return result.text ?? '';
|
|
116
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in plugins for Pulse Coder Engine
|
|
3
|
+
* 引擎内置插件集合
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { builtInMCPPlugin } from './mcp-plugin';
|
|
7
|
+
import { builtInSkillsPlugin } from './skills-plugin';
|
|
8
|
+
import { SubAgentPlugin } from './sub-agent-plugin';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 默认内置插件列表
|
|
12
|
+
* 这些插件会在引擎启动时自动加载
|
|
13
|
+
*/
|
|
14
|
+
export const builtInPlugins = [
|
|
15
|
+
builtInMCPPlugin,
|
|
16
|
+
builtInSkillsPlugin,
|
|
17
|
+
new SubAgentPlugin()
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 单独导出各个内置插件,便于外部使用
|
|
22
|
+
*/
|
|
23
|
+
export { builtInMCPPlugin } from './mcp-plugin';
|
|
24
|
+
export { builtInSkillsPlugin, BuiltInSkillRegistry } from './skills-plugin';
|
|
25
|
+
export { SubAgentPlugin } from './sub-agent-plugin';
|
|
26
|
+
|
|
27
|
+
export default builtInPlugins;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in MCP Plugin for Pulse Coder Engine
|
|
3
|
+
* 将 MCP 功能作为引擎内置插件
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { EnginePlugin, EnginePluginContext } from '../../plugin/EnginePlugin';
|
|
7
|
+
import { createMCPClient } from '@ai-sdk/mcp';
|
|
8
|
+
import { existsSync, readFileSync } from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
|
|
11
|
+
export interface MCPPluginConfig {
|
|
12
|
+
servers: Record<string, { url: string }>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function loadMCPConfig(cwd: string): Promise<MCPPluginConfig> {
|
|
16
|
+
// 优先读取 .pulse-coder/mcp.json,兼容旧版 .coder/mcp.json
|
|
17
|
+
const newConfigPath = path.join(cwd, '.pulse-coder', 'mcp.json');
|
|
18
|
+
const legacyConfigPath = path.join(cwd, '.coder', 'mcp.json');
|
|
19
|
+
const configPath = existsSync(newConfigPath) ? newConfigPath : legacyConfigPath;
|
|
20
|
+
|
|
21
|
+
if (!existsSync(configPath)) {
|
|
22
|
+
return { servers: {} };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
27
|
+
const parsed = JSON.parse(content);
|
|
28
|
+
|
|
29
|
+
if (!parsed.servers || typeof parsed.servers !== 'object') {
|
|
30
|
+
console.warn('[MCP] Invalid config: missing "servers" object');
|
|
31
|
+
return { servers: {} };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { servers: parsed.servers };
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.warn(`[MCP] Failed to load config: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
37
|
+
return { servers: {} };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createHTTPTransport(config: { url: string }) {
|
|
42
|
+
return {
|
|
43
|
+
type: 'http' as const,
|
|
44
|
+
url: config.url
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const builtInMCPPlugin: EnginePlugin = {
|
|
49
|
+
name: '@pulse-coder/engine/built-in-mcp',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
|
|
52
|
+
async initialize(context: EnginePluginContext) {
|
|
53
|
+
const config = await loadMCPConfig(process.cwd());
|
|
54
|
+
|
|
55
|
+
const serverCount = Object.keys(config.servers).length;
|
|
56
|
+
if (serverCount === 0) {
|
|
57
|
+
console.log('[MCP] No MCP servers configured');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let loadedCount = 0;
|
|
62
|
+
|
|
63
|
+
for (const [serverName, serverConfig] of Object.entries(config.servers)) {
|
|
64
|
+
try {
|
|
65
|
+
if (!serverConfig.url) {
|
|
66
|
+
console.warn(`[MCP] Server "${serverName}" missing URL, skipping`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const transport = createHTTPTransport(serverConfig);
|
|
71
|
+
const client = await createMCPClient({ transport });
|
|
72
|
+
|
|
73
|
+
const tools = await client.tools();
|
|
74
|
+
|
|
75
|
+
// 注册工具到引擎,使用命名空间前缀
|
|
76
|
+
const namespacedTools = Object.fromEntries(
|
|
77
|
+
Object.entries(tools).map(([toolName, tool]) => [
|
|
78
|
+
`mcp_${serverName}_${toolName}`,
|
|
79
|
+
tool as any
|
|
80
|
+
])
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
context.registerTools(namespacedTools);
|
|
84
|
+
|
|
85
|
+
loadedCount++;
|
|
86
|
+
console.log(`[MCP] Server "${serverName}" loaded (${Object.keys(tools).length} tools)`);
|
|
87
|
+
|
|
88
|
+
// 注册服务供其他插件使用
|
|
89
|
+
context.registerService(`mcp:${serverName}`, client);
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.warn(`[MCP] Failed to load server "${serverName}": ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (loadedCount > 0) {
|
|
97
|
+
console.log(`[MCP] Successfully loaded ${loadedCount}/${serverCount} MCP servers`);
|
|
98
|
+
} else {
|
|
99
|
+
console.warn('[MCP] No MCP servers were loaded successfully');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default builtInMCPPlugin;
|