This commit is contained in:
锦恢 2025-06-20 13:54:04 +08:00
commit 10befe12b4
6 changed files with 1545 additions and 3023 deletions

View File

@ -26,4 +26,5 @@ software/**
*.vsix *.vsix
.turbo .turbo
.github .github
webpack webpack
.openmcp

4396
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -284,7 +284,7 @@ export class TaskLoop {
/** /**
* @description Start the loop and asynchronously update the DOM * @description Start the loop and asynchronously update the DOM
*/ */
start(tabStorage: any, userMessage: string): Promise<void>; start(tabStorage: ChatStorage, userMessage: string): Promise<void>;
/** /**
* @description Create single conversation context * @description Create single conversation context

View File

@ -1,3 +1,5 @@
import * as fs from 'fs';
import { WebSocket } from 'ws'; import { WebSocket } from 'ws';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { routeMessage } from '../common/router.js'; import { routeMessage } from '../common/router.js';
@ -27,6 +29,10 @@ export interface IConnectionArgs {
cwd?: string; cwd?: string;
url?: string; url?: string;
oauth?: string; oauth?: string;
env?: {
[key: string]: string;
};
[key: string]: any;
} }
// 监听器回调类型 // 监听器回调类型
@ -93,7 +99,7 @@ export class TaskLoopAdapter {
// 默认需要将监听的消息导入到 routeMessage 中 // 默认需要将监听的消息导入到 routeMessage 中
this.onDidReceiveMessage((message) => { this.onDidReceiveMessage((message) => {
const { command, data } = message; const { command, data } = message;
switch (command) { switch (command) {
case 'nodejs/launch-signature': case 'nodejs/launch-signature':
this.postMessage({ this.postMessage({
@ -108,20 +114,20 @@ export class TaskLoopAdapter {
case 'nodejs/update-connection-signature': case 'nodejs/update-connection-signature':
// sdk 模式下不需要自动保存连接参数 // sdk 模式下不需要自动保存连接参数
break; break;
default: default:
routeMessage(command, data, this); routeMessage(command, data, this);
break; break;
} }
}); });
} }
/** /**
* @description * @description
* @param message - command args * @param message - command args
*/ */
public postMessage(message: WebSocketMessage): void { public postMessage(message: WebSocketMessage): void {
this.emitter.emit('message/service', message); this.emitter.emit('message/service', message);
} }
@ -152,25 +158,77 @@ export class TaskLoopAdapter {
} }
} }
import { TaskLoop } from '../../task-loop.js'; interface StdioMCPConfig {
import * as fs from 'fs'; command: string;
import chalk from 'chalk'; args: string[];
env?: {
[key: string]: string;
};
description?: string;
prompt?: string;
}
export class OAgent { interface HttpMCPConfig {
private adapter: TaskLoopAdapter; url: string;
constructor() { type?: string;
this.adapter = new TaskLoopAdapter(); env?: {
[key: string]: string;
};
description?: string;
prompt?: string;
}
export interface OmAgentConfiguration {
version: string;
mcpServers: {
[key: string]: StdioMCPConfig | HttpMCPConfig;
};
defaultLLM: {
baseURL: string;
apiToken: string;
model: string;
} }
}
public addMcp(mcpOption: IConnectionArgs) { import { MessageState, type ChatMessage, type ChatSetting, type TaskLoop, type TextMessage } from '../../task-loop.js';
this.adapter.addMcp(mcpOption);
export function UserMessage(content: string): TextMessage {
return {
role: 'user',
content,
extraInfo: {
created: Date.now(),
state: MessageState.None,
serverName: '',
enableXmlWrapper: false
}
}
}
export function AssistantMessage(content: string): TextMessage {
return {
role: 'assistant',
content,
extraInfo: {
created: Date.now(),
state: MessageState.None,
serverName: '',
enableXmlWrapper: false
}
}
}
export class OmAgent {
public _adapter: TaskLoopAdapter;
public _loop?: TaskLoop;
constructor() {
this._adapter = new TaskLoopAdapter();
} }
/** /**
* @description Load MCP configuration from file. * @description Load MCP configuration from file.
* Supports multiple MCP backends and a default LLM model configuration. * Supports multiple MCP backends and a default LLM model configuration.
*
* Configuration below can be generated from OpenMCP Client directly: https://kirigaya.cn/openmcp
* *
* @example * @example
* Example configuration: * Example configuration:
@ -188,8 +246,8 @@ export class OAgent {
* "prompt": "You are a helpful assistant." * "prompt": "You are a helpful assistant."
* } * }
* }, * },
* "llm": { * "defaultLLM": {
* "baseURL": "https://api.deepseek.com", * "baseURL": "https://api.openmemory.ai",
* "apiToken": "YOUR_API_KEY", * "apiToken": "YOUR_API_KEY",
* "model": "deepseek-chat" * "model": "deepseek-chat"
* } * }
@ -198,14 +256,69 @@ export class OAgent {
* @param configPath - Path to the configuration file * @param configPath - Path to the configuration file
*/ */
public loadMcpConfig(configPath: string) { public loadMcpConfig(configPath: string) {
if (!fs.existsSync(configPath)) { const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as OmAgentConfiguration;
console.log( const { mcpServers, defaultLLM } = config;
chalk.red(`× configPath not exists! ${configPath}`), for (const key in mcpServers) {
); const mcpConfig = mcpServers[key];
throw new Error(`× configPath not exists! ${configPath}`); if ('command' in mcpConfig) {
const commandString = (
mcpConfig.command + ' ' + mcpConfig.args.join(' ')
).trim();
this._adapter.addMcp({
commandString,
connectionType: 'STDIO',
env: mcpConfig.env,
description: mcpConfig.description,
prompt: mcpConfig.prompt,
});
} else {
const connectionType: ConnectionType = mcpConfig.type === 'http' ? 'STREAMABLE_HTTP': 'SSE';
this._adapter.addMcp({
url: mcpConfig.url,
env: mcpConfig.env,
connectionType,
description: mcpConfig.description,
prompt: mcpConfig.prompt,
});
}
}
}
public async getLoop() {
if (this._loop) {
return this._loop;
} }
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); const adapter = this._adapter;
const { TaskLoop } = await import('../../task-loop.js');
this._loop = new TaskLoop({ adapter, verbose: 1 });
return this._loop;
} }
}
public async start(
messages: ChatMessage[] | string,
settings?: ChatSetting
) {
if (messages.length === 0) {
throw new Error('messages is empty');
}
const loop = await this.getLoop();
const storage = await loop.createStorage(settings);
let userMessage: string;
if (typeof messages === 'string') {
userMessage = messages;
} else {
const lastMessageContent = messages.at(-1)?.content;
if (typeof lastMessageContent === 'string') {
userMessage = lastMessageContent;
} else {
throw new Error('last message content is undefined');
}
}
return await loop.start(storage, userMessage);
}
}

View File

@ -1,4 +1,4 @@
export { routeMessage } from './common/router.js'; export { routeMessage } from './common/router.js';
export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter.js'; export { VSCodeWebViewLike, TaskLoopAdapter, OmAgent, OmAgentConfiguration } from './hook/adapter.js';
export { setVscodeWorkspace, setRunningCWD } from './hook/setting.js'; export { setVscodeWorkspace, setRunningCWD } from './hook/setting.js';
export { clientMap } from './mcp/connect.service.js'; export { clientMap } from './mcp/connect.service.js';

View File

@ -284,7 +284,7 @@ export class TaskLoop {
/** /**
* @description Start the loop and asynchronously update the DOM * @description Start the loop and asynchronously update the DOM
*/ */
start(tabStorage: any, userMessage: string): Promise<void>; start(tabStorage: ChatStorage, userMessage: string): Promise<void>;
/** /**
* @description Create single conversation context * @description Create single conversation context