upgrade taskloop

This commit is contained in:
锦恢 2025-06-20 17:04:26 +08:00
parent 50510ae647
commit 51ceacbc10
9 changed files with 66 additions and 26 deletions

View File

@ -3,11 +3,11 @@ import * as fs from 'node:fs';
const targetFile = './openmcp-sdk/task-loop.js';
if (fs.existsSync(targetFile)) {
let content = fs.readFileSync(targetFile, 'utf8');
let content = fs.readFileSync(targetFile, 'utf-8');
// Replace element-plus with ./tools.js
content = content.replace(/'element-plus'/g, "'./tools.js'");
content = content.replace(/"element-plus"/g, "\"./tools.js\"");
content = content.replace(/'element-plus'/g, "'./tools.mjs'");
content = content.replace(/"element-plus"/g, "\"./tools.mjs\"");
// content = content.replace(/const chalk = require\("chalk"\);/g, 'const chalk = require("chalk").default;');

View File

@ -34,6 +34,8 @@ export interface IErrorMssage {
msg: string
}
export { MessageState };
export interface IDoConversationResult {
stop: boolean;
}
@ -83,12 +85,18 @@ export class TaskLoop {
throw new Error('adapter is required');
}
// 根据 adapter 创建 nodejs 下特殊的、基于 event 的 message bridge (不占用任何端口)
createMessageBridge(adapter.emitter);
// 用于进行连接同步
this.nodejsStatus.connectionFut = mcpClientAdapter.launch();
}
// web 环境下 bridge 会自动加载完成
this.bridge = useMessageBridge();
// 注册 HMR
mcpClientAdapter.addConnectRefreshListener();
}
/**
@ -381,7 +389,7 @@ export class TaskLoop {
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.blueBright('🔧 calling tool'),
chalk.blueBright('🔧 using tool'),
chalk.blueBright(toolCall.function!.name)
);
}
@ -395,13 +403,13 @@ export class TaskLoop {
if (result.state === 'success') {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.green('✓ call tools okey dockey'),
chalk.green('✓ use tools'),
chalk.green(result.state)
);
} else {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.red('× fail to call tools'),
chalk.red('× use tools'),
chalk.red(result.content.map(item => item.text).join(', '))
);
}
@ -413,7 +421,12 @@ export class TaskLoop {
const { verbose = 0 } = this.taskOptions;
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.gray(`[${new Date().toLocaleString()}]`)
);
}
if (verbose > 1) {
console.log(
chalk.blue('task loop enters a new epoch')
);
}
@ -425,9 +438,15 @@ export class TaskLoop {
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
);
}
if (verbose > 1) {
console.log(
chalk.green('task loop finish a epoch')
);
}
return this.onDone();
}
@ -559,7 +578,7 @@ export class TaskLoop {
if (verbose > 0) {
console.log(
chalk.gray(`[${new Date().toLocaleString()}]`),
chalk.yellow('🤖 llm wants to call these tools'),
chalk.yellow('🤖 Agent wants to use these tools'),
chalk.yellow(this.streamingToolCalls.value.map(tool => tool.function!.name || '').join(', '))
);
}

View File

@ -506,9 +506,7 @@ class McpClientAdapter {
constructor(
public platform: string
) {
this.addConnectRefreshListener();
}
) {}
/**
* @description
@ -562,7 +560,9 @@ class McpClientAdapter {
return index;
}
/**
* @description register HMR
*/
public addConnectRefreshListener() {
// 创建对于 connect/refresh 的监听
if (!this.connectrefreshListener) {

View File

@ -32,8 +32,8 @@ export default defineConfig({
lib: {
entry: resolve(__dirname, '..', 'renderer/src/components/main-panel/chat/core/task-loop.ts'),
name: 'TaskLoop',
fileName: 'task-loop',
formats: ['cjs']
fileName: (format) => `task-loop.js`,
formats: ['es']
},
outDir: resolve(__dirname, '..', 'openmcp-sdk'),
emptyOutDir: false,

View File

@ -1,6 +1,7 @@
{
"name": "openmcp-sdk",
"version": "0.0.8",
"type": "module",
"description": "openmcp-sdk",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"

View File

@ -195,7 +195,14 @@ export interface DefaultLLM {
model: string;
}
import { MessageState, TaskLoopOptions, type ChatMessage, type ChatSetting, type TaskLoop, type TextMessage } from '../../task-loop.js';
import {
MessageState,
type TaskLoopOptions,
type ChatMessage,
type ChatSetting,
type TaskLoop,
type TextMessage
} from '../../task-loop.js';
export function UserMessage(content: string): TextMessage {
return {
@ -274,7 +281,7 @@ export class OmAgent {
const commandString = (
mcpConfig.command + ' ' + mcpConfig.args.join(' ')
).trim();
this._adapter.addMcp({
commandString,
connectionType: 'STDIO',
@ -283,7 +290,7 @@ export class OmAgent {
prompt: mcpConfig.prompt,
});
} else {
const connectionType: ConnectionType = mcpConfig.type === 'http' ? 'STREAMABLE_HTTP': 'SSE';
const connectionType: ConnectionType = mcpConfig.type === 'http' ? 'STREAMABLE_HTTP' : 'SSE';
this._adapter.addMcp({
url: mcpConfig.url,
env: mcpConfig.env,
@ -309,6 +316,7 @@ export class OmAgent {
const adapter = this._adapter;
const { TaskLoop } = await import('../../task-loop.js');
this._loop = new TaskLoop({ adapter, verbose, maxEpochs, maxJsonParseRetry });
return this._loop;
}
@ -316,9 +324,14 @@ export class OmAgent {
this._defaultLLM = option;
}
public async start(
messages: ChatMessage[] | string,
settings?: ChatSetting & Partial<TaskLoopOptions>
/**
* @description Asynchronous invoking agent by string or messages
* @param messages Chat message or string
* @param settings Chat setting and task loop options
* @returns
*/
public async ainvoke(
{ messages, settings }: { messages: ChatMessage[] | string; settings?: ChatSetting & Partial<TaskLoopOptions>; }
) {
if (messages.length === 0) {
throw new Error('messages is empty');
@ -333,7 +346,7 @@ export class OmAgent {
const loop = await this.getLoop({ maxEpochs, maxJsonParseRetry, verbose });
const storage = await loop.createStorage(settings);
// set input message
// user can invoke [UserMessage("CONTENT")] to make messages quickly
// or use string directly
@ -363,6 +376,10 @@ export class OmAgent {
throw new Error('default LLM is not set, please set it via omagent.setDefaultLLM() or write "defaultLLM" in mcpconfig.json');
}
return await loop.start(storage, userMessage);
await loop.start(storage, userMessage);
// get response from last message in message list
const lastMessage = storage.messages.at(-1)?.content;
return lastMessage;
}
}

View File

@ -38,7 +38,7 @@ export class McpServerConnectMonitor {
this.filePath = getFilePath(options);
// 记录实例创建
logger.info({ uuid, connectionType: options.connectionType }, 'Created new connection monitor instance');
// logger.info({ uuid, connectionType: options.connectionType }, 'Created new connection monitor instance');
switch (options.connectionType) {
case 'STDIO':
@ -94,7 +94,7 @@ export class McpServerConnectMonitor {
},
onStart: () => {
// 使用 info 级别记录监控开始
logger.info({ uuid: this.uuid, filePath: path.resolve(fileConfig.filePath) }, 'Started monitoring file');
// logger.info({ uuid: this.uuid, filePath: path.resolve(fileConfig.filePath) }, 'Started monitoring file');
try {
const stats = fs.statSync(fileConfig.filePath);

View File

@ -71,7 +71,7 @@ class SingleFileMonitor {
this.handleFileChange(true);
}
});
console.log(`正在监控文件: ${this.filePath}`);
// console.log(`正在监控文件: ${this.filePath}`);
} catch (error) {
this.onError(error as Error);
}
@ -171,7 +171,7 @@ class SingleFileMonitor {
if (this.watcher) {
// 明确指定close方法的类型解决TS2554错误
(this.watcher.close as (callback?: () => void) => void)(() => {
console.log(`已停止监控文件: ${this.filePath}`);
// console.log(`已停止监控文件: ${this.filePath}`);
});
this.watcher = null;
}

View File

@ -14,6 +14,9 @@
"noEmitHelpers": false,
"experimentalDecorators": true,
"sourceMap": true,
"types": [
"./service/task-loop.d.ts"
],
// 访 openmcp-sdk
"paths": {
"@openmcp-sdk/*": [