commit
22a79cfc55
@ -26,4 +26,5 @@ software/**
|
||||
*.vsix
|
||||
.turbo
|
||||
.github
|
||||
webpack
|
||||
webpack
|
||||
.openmcp
|
10
README.md
10
README.md
@ -159,3 +159,13 @@ npm run build:plugin
|
||||
```
|
||||
|
||||
Then just press F5, いただきます (Let's begin)
|
||||
|
||||
---
|
||||
|
||||
## CI Pipeline
|
||||
|
||||
✅ npm run build
|
||||
✅ npm run build:task-loop
|
||||
✅ openmcp-client UT
|
||||
✅ openmcp-sdk UT
|
||||
✅ vscode extension UT
|
4396
package-lock.json
generated
4396
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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;');
|
||||
|
||||
|
@ -39,22 +39,22 @@ export class MessageBridge {
|
||||
switch (platform) {
|
||||
case 'vscode':
|
||||
this.setupVsCodeListener();
|
||||
pinkLog('当前模式: vscode');
|
||||
pinkLog('current platform: vscode');
|
||||
break;
|
||||
|
||||
case 'electron':
|
||||
this.setupElectronListener();
|
||||
pinkLog('当前模式: electron');
|
||||
pinkLog('current platform: electron');
|
||||
break;
|
||||
|
||||
case 'nodejs':
|
||||
this.setupNodejsListener();
|
||||
pinkLog('当前模式: nodejs');
|
||||
pinkLog('current platform: nodejs');
|
||||
break;
|
||||
|
||||
case 'web':
|
||||
this.setupWebSocket();
|
||||
pinkLog('当前模式: web');
|
||||
pinkLog('current platform: web');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { InputSchema, ToolCallContent, ToolItem } from "@/hook/type";
|
||||
import { type Ref, ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
import type { OpenAI } from 'openai';
|
||||
type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
||||
@ -95,7 +95,7 @@ export type RichTextItem = PromptTextItem | ResourceTextItem | TextItem;
|
||||
export interface ICommonRenderMessage {
|
||||
role: 'user' | 'assistant/content';
|
||||
content: string;
|
||||
showJson?: Ref<boolean>;
|
||||
showJson?: any;
|
||||
extraInfo: IExtraInfo;
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ export interface IToolRenderMessage {
|
||||
content: string;
|
||||
toolResults: ToolCallContent[][];
|
||||
tool_calls: ToolCall[];
|
||||
showJson?: Ref<boolean>;
|
||||
showJson?: any;
|
||||
extraInfo: IExtraInfo;
|
||||
}
|
||||
|
||||
|
@ -101,8 +101,6 @@ function handleSend(newMessage?: string) {
|
||||
loop.bindStreaming(streamingContent, streamingToolCalls);
|
||||
|
||||
loop.registerOnError((error) => {
|
||||
console.log('error.msg');
|
||||
console.log(error.msg);
|
||||
|
||||
const errorMessage = clearErrorMessage(error.msg);
|
||||
ElMessage.error(errorMessage);
|
||||
@ -114,7 +112,8 @@ function handleSend(newMessage?: string) {
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: error.state,
|
||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown'
|
||||
serverName: llms[llmManager.currentModelIndex].id || 'unknown',
|
||||
enableXmlWrapper: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ export interface IErrorMssage {
|
||||
msg: string
|
||||
}
|
||||
|
||||
export { MessageState };
|
||||
|
||||
export interface IDoConversationResult {
|
||||
stop: boolean;
|
||||
}
|
||||
@ -63,8 +65,8 @@ export class TaskLoop {
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly taskOptions: TaskLoopOptions = {
|
||||
maxEpochs: 20,
|
||||
private taskOptions: TaskLoopOptions = {
|
||||
maxEpochs: 50,
|
||||
maxJsonParseRetry: 3,
|
||||
adapter: undefined,
|
||||
verbose: 0
|
||||
@ -83,12 +85,37 @@ 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();
|
||||
}
|
||||
|
||||
public async waitConnection() {
|
||||
await this.nodejsStatus.connectionFut;
|
||||
}
|
||||
|
||||
public setTaskLoopOptions(taskOptions: TaskLoopOptions) {
|
||||
const {
|
||||
maxEpochs = 50,
|
||||
maxJsonParseRetry = 3,
|
||||
verbose = 1,
|
||||
} = taskOptions;
|
||||
|
||||
this.taskOptions = {
|
||||
maxEpochs,
|
||||
maxJsonParseRetry,
|
||||
verbose,
|
||||
...this.taskOptions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -381,7 +408,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 +422,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(', '))
|
||||
);
|
||||
}
|
||||
@ -411,7 +438,7 @@ export class TaskLoop {
|
||||
|
||||
private consumeEpochs() {
|
||||
const { verbose = 0 } = this.taskOptions;
|
||||
if (verbose > 0) {
|
||||
if (verbose > 1) {
|
||||
console.log(
|
||||
chalk.gray(`[${new Date().toLocaleString()}]`),
|
||||
chalk.blue('task loop enters a new epoch')
|
||||
@ -422,12 +449,14 @@ export class TaskLoop {
|
||||
|
||||
private consumeDones() {
|
||||
const { verbose = 0 } = this.taskOptions;
|
||||
if (verbose > 0) {
|
||||
|
||||
if (verbose > 1) {
|
||||
console.log(
|
||||
chalk.gray(`[${new Date().toLocaleString()}]`),
|
||||
chalk.green('task loop finish a epoch')
|
||||
);
|
||||
}
|
||||
|
||||
return this.onDone();
|
||||
}
|
||||
|
||||
@ -513,7 +542,7 @@ export class TaskLoop {
|
||||
|
||||
let jsonParseErrorRetryCount = 0;
|
||||
const {
|
||||
maxEpochs = 20,
|
||||
maxEpochs = 50,
|
||||
verbose = 0
|
||||
} = this.taskOptions || {};
|
||||
|
||||
@ -559,7 +588,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(', '))
|
||||
);
|
||||
}
|
||||
@ -773,4 +802,16 @@ export class TaskLoop {
|
||||
settings: _settings
|
||||
}
|
||||
}
|
||||
|
||||
public async getPrompt(promptId: string, args?: Record<string, any>) {
|
||||
const prompt = await mcpClientAdapter.readPromptTemplate(promptId, args);
|
||||
// transform prompt to string
|
||||
const promptString = prompt.messages.map(m => m.content.text).join('\n');
|
||||
return promptString;
|
||||
}
|
||||
|
||||
public async getResource(resourceUri: string) {
|
||||
const resource = await mcpClientAdapter.readResource(resourceUri);
|
||||
return resource;
|
||||
}
|
||||
}
|
@ -288,9 +288,6 @@ export async function handleXmlWrapperToolcall(toolcall: XmlToolCall): Promise<T
|
||||
state: MessageState.InvalidXml
|
||||
}
|
||||
}
|
||||
|
||||
// 进行调用,根据结果返回不同的值
|
||||
console.log(toolcall);
|
||||
|
||||
const toolResponse = await mcpClientAdapter.callTool(toolcall.name, toolcall.parameters);
|
||||
return handleToolResponse(toolResponse);
|
||||
|
@ -171,9 +171,6 @@ watchEffect(async () => {
|
||||
}
|
||||
const renderAssistantMessage = message.content.replace(/```xml[\s\S]*?```/g, '');
|
||||
|
||||
console.log(toolCalls);
|
||||
|
||||
|
||||
renderMessages.value.push({
|
||||
role: 'assistant/tool_calls',
|
||||
content: renderAssistantMessage,
|
||||
|
@ -61,7 +61,7 @@ export async function loadPanels(client: McpClient | Reactive<McpClient>) {
|
||||
panelLoaded.value = true;
|
||||
}
|
||||
|
||||
let debounceHandler: number;
|
||||
let debounceHandler: NodeJS.Timeout;
|
||||
|
||||
export function safeSavePanels() {
|
||||
clearTimeout(debounceHandler);
|
||||
|
@ -67,7 +67,7 @@ async function connect() {
|
||||
}
|
||||
|
||||
const isDraging = ref(false);
|
||||
let dragHandler: number;
|
||||
let dragHandler: NodeJS.Timeout;
|
||||
|
||||
function handleDragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
|
@ -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) {
|
||||
@ -610,7 +610,7 @@ class McpClientAdapter {
|
||||
|
||||
}, { once: false });
|
||||
}
|
||||
|
||||
|
||||
const launchSignature = await this.getLaunchSignature();
|
||||
|
||||
let allOk = true;
|
||||
@ -677,7 +677,7 @@ class McpClientAdapter {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public async readPromptTemplate(promptId: string, args: Record<string, any>) {
|
||||
public async readPromptTemplate(promptId: string, args?: Record<string, any>) {
|
||||
// TODO: 如果遇到不同服务器的同名 tool,请拓展解决方案
|
||||
// 目前只找到第一个匹配 toolName 的工具进行调用
|
||||
let clientId = this.clients[0].clientId;
|
||||
|
@ -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,
|
||||
|
@ -2,123 +2,110 @@
|
||||
|
||||
<img src="./icons/openmcp-sdk.svg" height="200px"/>
|
||||
|
||||
<h3>openmcp-sdk : 适用于 openmcp 的部署框架</h3>
|
||||
<h4>闪电般将您的 agent 从实验室部署到生产环境</h4>
|
||||
<h3>openmcp-sdk: Deployment Framework for OpenMCP</h3>
|
||||
<h4>Lightning-fast deployment of your agent from lab to production</h4>
|
||||
|
||||
<a href="https://kirigaya.cn/openmcp" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #7D3FF8; color: white; border-radius: .5em; text-decoration: none;">📄 OpenMCP 官方文档</a>
|
||||
<a href="https://kirigaya.cn/openmcp" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #7D3FF8; color: white; border-radius: .5em; text-decoration: none;">📄 OpenMCP Official Documentation</a>
|
||||
|
||||
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">QQ 讨论群</a><a href="https://discord.gg/af5cfB9a" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none; margin-left: 5px;">Discord频道</a>
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: #CB81DA; color: white; border-radius: .5em; text-decoration: none;">QQ Discussion Group</a><a href="https://discord.gg/af5cfB9a" target="_blank" style="display: inline-block; padding: 8px 16px; background-color: rgb(84, 176, 84); color: white; border-radius: .5em; text-decoration: none; margin-left: 5px;">Discord Channel</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## 安装
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install openmcp-sdk
|
||||
```
|
||||
|
||||
> 目前 openmcp-sdk 只支持 esm 模式的导入
|
||||
> Currently, openmcp-sdk only supports ESM-style imports.
|
||||
|
||||
## 使用
|
||||
## Usage
|
||||
|
||||
文件名:main.ts
|
||||
Filename: `main.ts`
|
||||
|
||||
```typescript
|
||||
import { TaskLoop } from 'openmcp-sdk/task-loop';
|
||||
import { TaskLoopAdapter } from 'openmcp-sdk/service';
|
||||
async function main() {
|
||||
// 创建适配器,负责通信和 mcp 连接
|
||||
const adapter = new TaskLoopAdapter();
|
||||
import { OmAgent } from 'openmcp-sdk/service/sdk';
|
||||
|
||||
// 添加 mcp 服务器
|
||||
adapter.addMcp({
|
||||
connectionType: 'STDIO',
|
||||
commandString: 'node index.js',
|
||||
cwd: '~/projects/openmcp-tutorial/my-browser/dist'
|
||||
});
|
||||
// create Agent
|
||||
const agent = new OmAgent();
|
||||
|
||||
// Load configuration, which can be automatically generated after debugging with openmcp client
|
||||
agent.loadMcpConfig('./mcpconfig.json');
|
||||
|
||||
// 创建事件循环驱动器, verbose 数值越高,输出的日志越详细
|
||||
const taskLoop = new TaskLoop({ adapter, verbose: 1 });
|
||||
// Read the debugged prompt
|
||||
const prompt = await agent.getPrompt('hacknews', { topn: '5' });
|
||||
|
||||
// 获取所有工具
|
||||
const tools = await taskLoop.getTools();
|
||||
// Execute the task
|
||||
const res = await agent.ainvoke({ messages: prompt });
|
||||
|
||||
// 配置改次事件循环使用的大模型
|
||||
taskLoop.setLlmConfig({
|
||||
id: 'deepseek',
|
||||
baseUrl: 'https://api.deepseek.com/v1',
|
||||
userToken: process.env['DEEPSEEK_API_TOKEN'],
|
||||
userModel: 'deepseek-chat'
|
||||
});
|
||||
console.log('⚙️ Agent Response', res);
|
||||
```
|
||||
|
||||
// 创建当前事件循环对应的上下文,并且配置当前上下文的设置
|
||||
const storage = {
|
||||
messages: [],
|
||||
settings: {
|
||||
temperature: 0.7,
|
||||
// 在本次对话使用所有工具
|
||||
enableTools: tools,
|
||||
// 系统提示词
|
||||
systemPrompt: 'you are a clever bot',
|
||||
// 对话上下文的轮数
|
||||
contextLength: 20
|
||||
`mcpconfig.json` can be generated from [openmcp client](https://github.com/LSTM-Kirigaya/openmcp-client) directly, you don't have to write it by yourself. Here is the example:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"namespace": "openmcp",
|
||||
"mcpServers": {
|
||||
"my-browser": {
|
||||
"command": "mcp",
|
||||
"args": [
|
||||
"run",
|
||||
"~/projects/openmcp-tutorial/crawl4ai-mcp/main.py"
|
||||
],
|
||||
"description": "A MCP for long-term memory support",
|
||||
"prompts": [
|
||||
"hacknews"
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// 本次发出的问题
|
||||
const message = 'hello world';
|
||||
|
||||
// 开启事件循环
|
||||
await taskLoop.start(storage, message);
|
||||
|
||||
// 打印上下文,最终的回答在 messages.at(-1) 中
|
||||
const content = storage.messages.at(-1).content;
|
||||
console.log('最终回答:', content);
|
||||
}
|
||||
|
||||
main();
|
||||
},
|
||||
"defaultLLM": {
|
||||
"baseURL": "https://api.deepseek.com",
|
||||
"apiToken": "sk-xxxxxxxxxxxxxx",
|
||||
"model": "deepseek-chat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面是可能的输出:
|
||||
Run your agent and get example output:
|
||||
|
||||
```
|
||||
[6/5/2025, 8:16:15 PM] 🚀 [my-browser] 0.1.0 connected
|
||||
[6/5/2025, 8:16:15 PM] task loop enters a new epoch
|
||||
[6/5/2025, 8:16:23 PM] task loop finish a epoch
|
||||
[6/5/2025, 8:16:23 PM] 🤖 llm wants to call these tools k_navigate
|
||||
[6/5/2025, 8:16:23 PM] 🔧 calling tool k_navigate
|
||||
[6/5/2025, 8:16:34 PM] × fail to call tools McpError: MCP error -32603: net::ERR_CONNECTION_RESET at https://towardsdatascience.com/tag/editors-pick/
|
||||
[6/5/2025, 8:16:34 PM] task loop enters a new epoch
|
||||
[6/5/2025, 8:16:40 PM] task loop finish a epoch
|
||||
[6/5/2025, 8:16:40 PM] 🤖 llm wants to call these tools k_navigate
|
||||
[6/5/2025, 8:16:40 PM] 🔧 calling tool k_navigate
|
||||
[6/5/2025, 8:16:44 PM] ✓ call tools okey dockey success
|
||||
[6/5/2025, 8:16:44 PM] task loop enters a new epoch
|
||||
[6/5/2025, 8:16:57 PM] task loop finish a epoch
|
||||
[6/5/2025, 8:16:57 PM] 🤖 llm wants to call these tools k_evaluate
|
||||
[6/5/2025, 8:16:57 PM] 🔧 calling tool k_evaluate
|
||||
[6/5/2025, 8:16:57 PM] ✓ call tools okey dockey success
|
||||
[6/5/2025, 8:16:57 PM] task loop enters a new epoch
|
||||
[6/5/2025, 8:17:06 PM] task loop finish a epoch
|
||||
[6/5/2025, 8:17:06 PM] 🤖 llm wants to call these tools k_navigate, k_navigate
|
||||
[6/5/2025, 8:17:06 PM] 🔧 calling tool k_navigate
|
||||
[6/5/2025, 8:17:09 PM] ✓ call tools okey dockey success
|
||||
[6/5/2025, 8:17:09 PM] 🔧 calling tool k_navigate
|
||||
[6/5/2025, 8:17:12 PM] ✓ call tools okey dockey success
|
||||
[6/5/2025, 8:17:12 PM] task loop enters a new epoch
|
||||
[6/5/2025, 8:17:19 PM] task loop finish a epoch
|
||||
[6/5/2025, 8:17:19 PM] 🤖 llm wants to call these tools k_evaluate, k_evaluate
|
||||
[6/5/2025, 8:17:19 PM] 🔧 calling tool k_evaluate
|
||||
[6/5/2025, 8:17:19 PM] ✓ call tools okey dockey success
|
||||
[6/5/2025, 8:17:19 PM] 🔧 calling tool k_evaluate
|
||||
[6/5/2025, 8:17:19 PM] ✓ call tools okey dockey success
|
||||
[6/5/2025, 8:17:19 PM] task loop enters a new epoch
|
||||
[6/5/2025, 8:17:45 PM] task loop finish a epoch
|
||||
"以下是整理好的热门文章信息,并已翻译为简体中文:\n\n---\n\n### K1 标题 \n**《数据漂移并非真正问题:你的监控策略才是》** \n\n**简介** \n在机器学习领域,数据漂移常被视为模型性能下降的罪魁祸首,但本文作者提出了一种颠覆性的观点:数据漂移只是一个信号,真正的核心问题在于监控策略的不足。文章通过实际案例(如电商推荐系统和金融风控模型)揭示了传统统计监控的局限性,并提出了一个三层监控框架: \n1. **统计监控**:快速检测数据分布变化,但仅作为初步信号。 \n2. **上下文监控**:结合业务逻辑,判断漂移是否对关键指标产生影响。 \n3. **行为监控**:追踪模型预测的实际效果,避免“无声漂移”。 \n\n亮点在于作者强调了监控系统需要与业务目标紧密结合,而非单纯依赖技术指标。 \n\n**原文链接** \n[点击阅读原文](https://towardsdatascience.com/data-drift-is-not-the-actual-problem-your-monitoring-strategy-is/) \n\n---\n\n### K2 标题 \n**《从 Jupyter 到程序员的快速入门指南》** \n\n**简介** \n本文为数据科学家和初学者提供了一条从 Jupyter Notebook 过渡到专业编程的清晰路径。作者通过实际代码示例和工具推荐(如 VS Code、Git 和 Docker),帮助读者摆脱 Notebook 的局限性,提升代码的可维护性和可扩展性。 \n\n亮点包括: \n- 如何将 Notebook 代码模块化为可复用的 Python 脚本。 \n- 使用版本控制和容器化技术优化开发流程。 \n- 实战案例展示如何将实验性代码转化为生产级应用。 \n\n**原文链接** \n[点击阅读原文](https://towardsdatascience.com/the-journey-from-jupyter-to-programmer-a-quick-start-guide/) \n\n---\n\n如果需要进一步优化或补充其他内容,请随时告诉我!"
|
||||
[2025/6/20 20:47:31] 🚀 [crawl4ai-mcp] 1.9.1 connected
|
||||
[2025/6/20 20:47:35] 🤖 Agent wants to use these tools get_web_markdown
|
||||
[2025/6/20 20:47:35] 🔧 using tool get_web_markdown
|
||||
[2025/6/20 20:47:39] ✓ use tools success
|
||||
[2025/6/20 20:47:46] 🤖 Agent wants to use these tools get_web_markdown, get_web_markdown, get_web_markdown
|
||||
[2025/6/20 20:47:46] 🔧 using tool get_web_markdown
|
||||
[2025/6/20 20:47:48] ✓ use tools success
|
||||
[2025/6/20 20:47:48] 🔧 using tool get_web_markdown
|
||||
[2025/6/20 20:47:54] ✓ use tools success
|
||||
[2025/6/20 20:47:54] 🔧 using tool get_web_markdown
|
||||
[2025/6/20 20:47:57] ✓ use tools success
|
||||
|
||||
⚙️ Agent Response
|
||||
⌨️ Today's Tech Article Roundup
|
||||
|
||||
📌 How to Detect or Observe Passing Gravitational Waves?
|
||||
Summary: This article explores the physics of gravitational waves, explaining their effects on space-time and how humans might perceive or observe this cosmic phenomenon.
|
||||
Author: ynoxinul
|
||||
Posted: 2 hours ago
|
||||
Link: https://physics.stackexchange.com/questions/338912/how-would-a-passing-gravitational-wave-look-or-feel
|
||||
|
||||
📌 Learn Makefile Tutorial
|
||||
Summary: A comprehensive Makefile tutorial for beginners and advanced users, covering basic syntax, variables, automatic rules, and advanced features to help developers manage project builds efficiently.
|
||||
Author: dsego
|
||||
Posted: 4 hours ago
|
||||
Link: https://makefiletutorial.com/
|
||||
|
||||
📌 Hurl: Run and Test HTTP Requests in Plain Text
|
||||
Summary: Hurl is a command-line tool that allows defining and executing HTTP requests in plain text format, ideal for data fetching and HTTP session testing. It supports chained requests, value capture, and response queries, making it perfect for testing REST, SOAP, and GraphQL APIs.
|
||||
Author: flykespice
|
||||
Posted: 8 hours ago
|
||||
Link: https://github.com/Orange-OpenSource/hurl
|
||||
```
|
||||
|
||||
更多使用请看官方文档:https://kirigaya.cn/openmcp/sdk-tutorial/
|
||||
For more details, see the official documentation: [https://kirigaya.cn/openmcp/sdk-tutorial/](https://kirigaya.cn/openmcp/sdk-tutorial/)
|
||||
|
||||
star 我们的项目:https://github.com/LSTM-Kirigaya/openmcp-client
|
||||
Star our project: [https://github.com/LSTM-Kirigaya/openmcp-client](https://github.com/LSTM-Kirigaya/openmcp-client)
|
||||
|
@ -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"
|
||||
@ -37,6 +38,8 @@
|
||||
"open": "^10.1.2",
|
||||
"ws": "^8.18.1",
|
||||
"cli-table3": "^0.6.5",
|
||||
"https-proxy-agent": "^7.0.6"
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0"
|
||||
}
|
||||
}
|
49
resources/openmcp-sdk-release/task-loop.d.ts
vendored
49
resources/openmcp-sdk-release/task-loop.d.ts
vendored
@ -5,25 +5,25 @@ export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
||||
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
|
||||
|
||||
interface SchemaProperty {
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface InputSchema {
|
||||
type: string;
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
title?: string;
|
||||
$defs?: any;
|
||||
type: string;
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
title?: string;
|
||||
$defs?: any;
|
||||
}
|
||||
|
||||
interface ToolItem {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: InputSchema;
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: InputSchema;
|
||||
enabled: boolean;
|
||||
anyOf?: any;
|
||||
anyOf?: any;
|
||||
}
|
||||
|
||||
interface IExtraInfo {
|
||||
@ -87,7 +87,7 @@ export interface ToolCall {
|
||||
export interface ToolCallContent {
|
||||
type: string;
|
||||
text: string;
|
||||
[key: string]: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ToolCallResult {
|
||||
@ -189,6 +189,17 @@ interface ChatSetting {
|
||||
export class TaskLoop {
|
||||
constructor(taskOptions?: TaskLoopOptions);
|
||||
|
||||
/**
|
||||
* @description wait for connection
|
||||
*/
|
||||
waitConnection(): Promise<void>;
|
||||
|
||||
/**
|
||||
* @description Set the task loop options
|
||||
* @param taskOptions
|
||||
*/
|
||||
setTaskLoopOptions(taskOptions: TaskLoopOptions): void;
|
||||
|
||||
/**
|
||||
* @description make chat data
|
||||
* @param tabStorage
|
||||
@ -284,12 +295,22 @@ export class TaskLoop {
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
createStorage(settings?: ChatSetting): Promise<ChatStorage>;
|
||||
|
||||
/**
|
||||
* @description Get prompt template from mcp server
|
||||
*/
|
||||
getPrompt(promptId: string, args?: Record<string, any>): Promise<string>;
|
||||
|
||||
/**
|
||||
* @description Get resource template from mcp server
|
||||
*/
|
||||
getResource(resourceUri: string): Promise<string>;
|
||||
}
|
||||
|
||||
export declare const getToolSchema: any;
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { WebSocket } from 'ws';
|
||||
import { EventEmitter } from 'events';
|
||||
import { routeMessage } from '../common/router.js';
|
||||
import { ConnectionType, McpOptions } from '../mcp/client.dto.js';
|
||||
import { clientMap, connectService } from '../mcp/connect.service.js';
|
||||
import { ConnectionType } from '../mcp/client.dto.js';
|
||||
|
||||
// WebSocket 消息格式
|
||||
export interface WebSocketMessage {
|
||||
@ -27,6 +24,10 @@ export interface IConnectionArgs {
|
||||
cwd?: string;
|
||||
url?: string;
|
||||
oauth?: string;
|
||||
env?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 监听器回调类型
|
||||
@ -75,80 +76,3 @@ export class VSCodeWebViewLike {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class TaskLoopAdapter {
|
||||
public emitter: EventEmitter;
|
||||
private messageHandlers: Set<MessageHandler>;
|
||||
private connectionOptions: IConnectionArgs[] = [];
|
||||
|
||||
constructor(option?: any) {
|
||||
this.emitter = new EventEmitter(option);
|
||||
this.messageHandlers = new Set();
|
||||
|
||||
this.emitter.on('message/renderer', (message: WebSocketMessage) => {
|
||||
this.messageHandlers.forEach((handler) => handler(message));
|
||||
});
|
||||
|
||||
// 默认需要将监听的消息导入到 routeMessage 中
|
||||
this.onDidReceiveMessage((message) => {
|
||||
const { command, data } = message;
|
||||
|
||||
switch (command) {
|
||||
case 'nodejs/launch-signature':
|
||||
this.postMessage({
|
||||
command: 'nodejs/launch-signature',
|
||||
data: {
|
||||
code: 200,
|
||||
msg: this.connectionOptions
|
||||
}
|
||||
})
|
||||
break;
|
||||
|
||||
case 'nodejs/update-connection-signature':
|
||||
// sdk 模式下不需要自动保存连接参数
|
||||
break;
|
||||
|
||||
default:
|
||||
routeMessage(command, data, this);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 发送消息
|
||||
* @param message - 包含 command 和 args 的消息
|
||||
*/
|
||||
public postMessage(message: WebSocketMessage): void {
|
||||
this.emitter.emit('message/service', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 注册接受消息的句柄
|
||||
* @param callback - 消息回调
|
||||
* @returns {{ dispose: () => void }} - 可销毁的监听器
|
||||
*/
|
||||
public onDidReceiveMessage(callback: MessageHandler): { dispose: () => void } {
|
||||
this.messageHandlers.add(callback);
|
||||
return {
|
||||
dispose: () => this.messageHandlers.delete(callback),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 连接到 mcp 服务端
|
||||
* @param mcpOption
|
||||
*/
|
||||
public addMcp(mcpOption: IConnectionArgs) {
|
||||
|
||||
// 0.1.4 新版本开始,此处修改为懒加载连接
|
||||
// 实际的连接移交给前端 mcpAdapter 中进行统一的调度
|
||||
// 调度步骤如下:
|
||||
// getLaunchSignature 先获取访问签名,访问签名通过当前函数 push 到 class 中
|
||||
|
||||
this.connectionOptions.push(mcpOption);
|
||||
}
|
||||
}
|
||||
|
||||
|
327
service/src/hook/sdk.ts
Normal file
327
service/src/hook/sdk.ts
Normal file
@ -0,0 +1,327 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { routeMessage } from '../common/router.js';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export class TaskLoopAdapter {
|
||||
public emitter: EventEmitter;
|
||||
private messageHandlers: Set<MessageHandler>;
|
||||
private connectionOptions: IConnectionArgs[] = [];
|
||||
|
||||
constructor(option?: any) {
|
||||
this.emitter = new EventEmitter(option);
|
||||
this.messageHandlers = new Set();
|
||||
|
||||
this.emitter.on('message/renderer', (message: WebSocketMessage) => {
|
||||
this.messageHandlers.forEach((handler) => handler(message));
|
||||
});
|
||||
|
||||
// 默认需要将监听的消息导入到 routeMessage 中
|
||||
this.onDidReceiveMessage((message) => {
|
||||
const { command, data } = message;
|
||||
|
||||
switch (command) {
|
||||
case 'nodejs/launch-signature':
|
||||
this.postMessage({
|
||||
command: 'nodejs/launch-signature',
|
||||
data: {
|
||||
code: 200,
|
||||
msg: this.connectionOptions
|
||||
}
|
||||
})
|
||||
break;
|
||||
|
||||
case 'nodejs/update-connection-signature':
|
||||
// sdk 模式下不需要自动保存连接参数
|
||||
break;
|
||||
|
||||
default:
|
||||
routeMessage(command, data, this);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 发送消息
|
||||
* @param message - 包含 command 和 args 的消息
|
||||
*/
|
||||
public postMessage(message: WebSocketMessage): void {
|
||||
this.emitter.emit('message/service', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 注册接受消息的句柄
|
||||
* @param callback - 消息回调
|
||||
* @returns {{ dispose: () => void }} - 可销毁的监听器
|
||||
*/
|
||||
public onDidReceiveMessage(callback: MessageHandler): { dispose: () => void } {
|
||||
this.messageHandlers.add(callback);
|
||||
return {
|
||||
dispose: () => this.messageHandlers.delete(callback),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 连接到 mcp 服务端
|
||||
* @param mcpOption
|
||||
*/
|
||||
public addMcp(mcpOption: IConnectionArgs) {
|
||||
|
||||
// 0.1.4 新版本开始,此处修改为懒加载连接
|
||||
// 实际的连接移交给前端 mcpAdapter 中进行统一的调度
|
||||
// 调度步骤如下:
|
||||
// getLaunchSignature 先获取访问签名,访问签名通过当前函数 push 到 class 中
|
||||
|
||||
this.connectionOptions.push(mcpOption);
|
||||
}
|
||||
}
|
||||
|
||||
interface StdioMCPConfig {
|
||||
command: string;
|
||||
args: string[];
|
||||
env?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
description?: string;
|
||||
prompts?: string[];
|
||||
resources?: string[];
|
||||
}
|
||||
|
||||
interface HttpMCPConfig {
|
||||
url: string;
|
||||
type?: string;
|
||||
env?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
description?: string;
|
||||
prompts?: string[];
|
||||
resources?: string[];
|
||||
}
|
||||
|
||||
export interface OmAgentConfiguration {
|
||||
version: string;
|
||||
mcpServers: {
|
||||
[key: string]: StdioMCPConfig | HttpMCPConfig;
|
||||
};
|
||||
defaultLLM: {
|
||||
baseURL: string;
|
||||
apiToken: string;
|
||||
model: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DefaultLLM {
|
||||
baseURL: string;
|
||||
apiToken?: string;
|
||||
model: string;
|
||||
}
|
||||
|
||||
import {
|
||||
MessageState,
|
||||
type TaskLoopOptions,
|
||||
type ChatMessage,
|
||||
type ChatSetting,
|
||||
type TaskLoop,
|
||||
type TextMessage
|
||||
} from '../../task-loop.js';
|
||||
import { IConnectionArgs, MessageHandler, WebSocketMessage } from './adapter.js';
|
||||
import { ConnectionType } from 'src/mcp/client.dto.js';
|
||||
|
||||
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 {
|
||||
private _adapter: TaskLoopAdapter;
|
||||
private _loop?: TaskLoop;
|
||||
private _defaultLLM?: DefaultLLM;
|
||||
|
||||
constructor() {
|
||||
this._adapter = new TaskLoopAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Load MCP configuration from file.
|
||||
* Supports multiple MCP backends and a default LLM model configuration.
|
||||
*
|
||||
* @example
|
||||
* Example configuration:
|
||||
* {
|
||||
* "version": "1.0.0",
|
||||
* "mcpServers": {
|
||||
* "openmemory": {
|
||||
* "command": "npx",
|
||||
* "args": ["-y", "openmemory"],
|
||||
* "env": {
|
||||
* "OPENMEMORY_API_KEY": "YOUR_API_KEY",
|
||||
* "CLIENT_NAME": "openmemory"
|
||||
* },
|
||||
* "description": "A MCP for long-term memory support"
|
||||
* }
|
||||
* },
|
||||
* "defaultLLM": {
|
||||
* "baseURL": "https://api.openmemory.ai",
|
||||
* "apiToken": "YOUR_API_KEY",
|
||||
* "model": "deepseek-chat"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param configPath - Path to the configuration file
|
||||
*/
|
||||
public loadMcpConfig(configPath: string) {
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as OmAgentConfiguration;
|
||||
const { mcpServers, defaultLLM } = config;
|
||||
|
||||
// set default llm
|
||||
this.setDefaultLLM(defaultLLM);
|
||||
|
||||
for (const key in mcpServers) {
|
||||
const mcpConfig = mcpServers[key];
|
||||
if ('command' in mcpConfig) {
|
||||
const commandString = (
|
||||
mcpConfig.command + ' ' + mcpConfig.args.join(' ')
|
||||
).trim();
|
||||
|
||||
this._adapter.addMcp({
|
||||
commandString,
|
||||
connectionType: 'STDIO',
|
||||
env: mcpConfig.env,
|
||||
description: mcpConfig.description,
|
||||
});
|
||||
} else {
|
||||
const connectionType: ConnectionType = mcpConfig.type === 'http' ? 'STREAMABLE_HTTP' : 'SSE';
|
||||
this._adapter.addMcp({
|
||||
url: mcpConfig.url,
|
||||
env: mcpConfig.env,
|
||||
connectionType,
|
||||
description: mcpConfig.description,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Add MCP server
|
||||
*/
|
||||
public addMcpServer(connectionArgs: IConnectionArgs) {
|
||||
this._adapter.addMcp(connectionArgs);
|
||||
}
|
||||
|
||||
private async getLoop(loopOption?: TaskLoopOptions) {
|
||||
|
||||
if (this._loop) {
|
||||
if (loopOption) {
|
||||
this._loop.setTaskLoopOptions(loopOption);
|
||||
}
|
||||
return this._loop;
|
||||
}
|
||||
|
||||
const {
|
||||
verbose = 1,
|
||||
maxEpochs = 50,
|
||||
maxJsonParseRetry = 3,
|
||||
} = loopOption || {}
|
||||
|
||||
const adapter = this._adapter;
|
||||
const { TaskLoop } = await import('../../task-loop.js');
|
||||
|
||||
this._loop = new TaskLoop({ adapter, verbose, maxEpochs, maxJsonParseRetry });
|
||||
await this._loop.waitConnection();
|
||||
|
||||
return this._loop;
|
||||
}
|
||||
|
||||
public setDefaultLLM(option: DefaultLLM) {
|
||||
this._defaultLLM = option;
|
||||
}
|
||||
|
||||
public async getPrompt(promptId: string, args: Record<string, any>) {
|
||||
const loop = await this.getLoop();
|
||||
|
||||
const prompt = await loop.getPrompt(promptId, JSON.parse(JSON.stringify(args)));
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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');
|
||||
}
|
||||
|
||||
// detach taskloop option from settings and set default value
|
||||
const {
|
||||
maxEpochs = 50,
|
||||
maxJsonParseRetry = 3,
|
||||
verbose = 1
|
||||
} = settings || {};
|
||||
|
||||
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
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
// select correct llm config
|
||||
// user can set llm config via omagent.setDefaultLLM()
|
||||
// or write "defaultLLM" in mcpconfig.json to specify
|
||||
if (this._defaultLLM) {
|
||||
loop.setLlmConfig({
|
||||
baseUrl: this._defaultLLM.baseURL,
|
||||
userToken: this._defaultLLM.apiToken,
|
||||
userModel: this._defaultLLM.model,
|
||||
});
|
||||
} else {
|
||||
// throw error to user and give the suggestion
|
||||
throw new Error('default LLM is not set, please set it via omagent.setDefaultLLM() or write "defaultLLM" in mcpconfig.json');
|
||||
}
|
||||
|
||||
await loop.start(storage, userMessage);
|
||||
|
||||
// get response from last message in message list
|
||||
const lastMessage = storage.messages.at(-1)?.content;
|
||||
return lastMessage;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export { routeMessage } from './common/router.js';
|
||||
export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter.js';
|
||||
export { VSCodeWebViewLike } from './hook/adapter.js';
|
||||
export { setVscodeWorkspace, setRunningCWD } from './hook/setting.js';
|
||||
export { clientMap } from './mcp/connect.service.js';
|
@ -137,7 +137,7 @@ export class McpClient {
|
||||
}
|
||||
|
||||
// 获取提示
|
||||
public async getPrompt(name: string, args: Record<string, any> = {}) {
|
||||
public async getPrompt(name: string, args: Record<string, any> = {}) {
|
||||
return await this.client.getPrompt({
|
||||
name, arguments: args
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -268,24 +268,24 @@ export async function connectService(
|
||||
): Promise<RestfulResponse> {
|
||||
try {
|
||||
// 使用cli-table3创建美观的表格
|
||||
const table = new Table({
|
||||
head: ['Property', 'Value'],
|
||||
colWidths: [20, 60],
|
||||
style: {
|
||||
head: ['green'],
|
||||
border: ['grey']
|
||||
}
|
||||
});
|
||||
// const table = new Table({
|
||||
// head: ['Property', 'Value'],
|
||||
// colWidths: [20, 60],
|
||||
// style: {
|
||||
// head: ['green'],
|
||||
// border: ['grey']
|
||||
// }
|
||||
// });
|
||||
|
||||
table.push(
|
||||
['Connection Type', option.connectionType],
|
||||
['Command', option.command || 'N/A'],
|
||||
['Arguments', option.args?.join(' ') || 'N/A'],
|
||||
['Working Directory', option.cwd || 'N/A'],
|
||||
['URL', option.url || 'N/A']
|
||||
);
|
||||
// table.push(
|
||||
// ['Connection Type', option.connectionType],
|
||||
// ['Command', option.command || 'N/A'],
|
||||
// ['Arguments', option.args?.join(' ') || 'N/A'],
|
||||
// ['Working Directory', option.cwd || 'N/A'],
|
||||
// ['URL', option.url || 'N/A']
|
||||
// );
|
||||
|
||||
console.log(table.toString());
|
||||
// console.log(table.toString());
|
||||
|
||||
// 预处理字符串
|
||||
await preprocessCommand(option, webview);
|
||||
|
@ -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;
|
||||
}
|
||||
|
1
service/src/sdk.ts
Normal file
1
service/src/sdk.ts
Normal file
@ -0,0 +1 @@
|
||||
export { TaskLoopAdapter, OmAgent, OmAgentConfiguration, UserMessage, AssistantMessage } from './hook/sdk.js';
|
324
service/task-loop.d.ts
vendored
Normal file
324
service/task-loop.d.ts
vendored
Normal file
@ -0,0 +1,324 @@
|
||||
/* eslint-disable */
|
||||
import type { OpenAI } from 'openai';
|
||||
|
||||
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
||||
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
|
||||
|
||||
interface SchemaProperty {
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface InputSchema {
|
||||
type: string;
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
title?: string;
|
||||
$defs?: any;
|
||||
}
|
||||
|
||||
interface ToolItem {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: InputSchema;
|
||||
enabled: boolean;
|
||||
anyOf?: any;
|
||||
}
|
||||
|
||||
interface IExtraInfo {
|
||||
created: number,
|
||||
state: MessageState,
|
||||
serverName: string,
|
||||
usage?: ChatCompletionChunk['usage'];
|
||||
enableXmlWrapper: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
interface ToolMessage {
|
||||
role: 'tool';
|
||||
index: number;
|
||||
content: ToolCallContent[];
|
||||
tool_call_id?: string
|
||||
name?: string // 工具名称,当 role 为 tool
|
||||
tool_calls?: ToolCall[],
|
||||
extraInfo: IExtraInfo
|
||||
}
|
||||
|
||||
interface TextMessage {
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
tool_call_id?: string
|
||||
name?: string // 工具名称,当 role 为 tool
|
||||
tool_calls?: ToolCall[],
|
||||
extraInfo: IExtraInfo
|
||||
}
|
||||
|
||||
export type ChatMessage = ToolMessage | TextMessage;
|
||||
|
||||
interface ChatStorage {
|
||||
messages: ChatMessage[]
|
||||
settings: ChatSetting
|
||||
}
|
||||
|
||||
interface EnableToolItem {
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
inputSchema: InputSchema;
|
||||
}
|
||||
|
||||
|
||||
export type Ref<T> = {
|
||||
value: T;
|
||||
};
|
||||
|
||||
export interface ToolCall {
|
||||
id?: string;
|
||||
index?: number;
|
||||
type: string;
|
||||
function: {
|
||||
name: string;
|
||||
arguments: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ToolCallContent {
|
||||
type: string;
|
||||
text: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ToolCallResult {
|
||||
state: MessageState;
|
||||
content: ToolCallContent[];
|
||||
}
|
||||
|
||||
export enum MessageState {
|
||||
ServerError = 'server internal error',
|
||||
ReceiveChunkError = 'receive chunk error',
|
||||
Timeout = 'timeout',
|
||||
MaxEpochs = 'max epochs',
|
||||
Unknown = 'unknown error',
|
||||
Abort = 'abort',
|
||||
ToolCall = 'tool call failed',
|
||||
None = 'none',
|
||||
Success = 'success',
|
||||
ParseJsonError = 'parse json error'
|
||||
}
|
||||
|
||||
export interface IErrorMssage {
|
||||
state: MessageState;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface IDoConversationResult {
|
||||
stop: boolean;
|
||||
}
|
||||
|
||||
export interface TaskLoopOptions {
|
||||
/**
|
||||
* The maximum number of epochs (conversation rounds) to perform.
|
||||
*/
|
||||
maxEpochs?: number;
|
||||
|
||||
/**
|
||||
* The maximum number of retries allowed when parsing JSON responses fails.
|
||||
*/
|
||||
maxJsonParseRetry?: number;
|
||||
|
||||
/**
|
||||
* A custom adapter that can be used to modify behavior or integrate with different environments.
|
||||
*/
|
||||
adapter?: any;
|
||||
|
||||
/**
|
||||
* Verbosity level for logging:
|
||||
* 0 - Silent, 1 - Errors only, 2 - Warnings and errors, 3 - Full debug output.
|
||||
*/
|
||||
verbose?: 0 | 1 | 2 | 3;
|
||||
}
|
||||
|
||||
interface ChatSetting {
|
||||
/**
|
||||
* Index of the selected language model from a list of available models.
|
||||
*/
|
||||
modelIndex: number;
|
||||
|
||||
/**
|
||||
* System-level prompt used to guide the behavior of the assistant.
|
||||
*/
|
||||
systemPrompt: string;
|
||||
|
||||
/**
|
||||
* List of tools that are enabled and available during the chat.
|
||||
*/
|
||||
enableTools: EnableToolItem[];
|
||||
|
||||
/**
|
||||
* Sampling temperature for generating responses.
|
||||
* Higher values (e.g., 0.8) make output more random; lower values (e.g., 0.2) make it more focused and deterministic.
|
||||
*/
|
||||
temperature: number;
|
||||
|
||||
/**
|
||||
* Whether web search is enabled for enhancing responses with real-time information.
|
||||
*/
|
||||
enableWebSearch: boolean;
|
||||
|
||||
/**
|
||||
* Maximum length of the conversation context to keep.
|
||||
*/
|
||||
contextLength: number;
|
||||
|
||||
/**
|
||||
* Whether multiple tools can be called in parallel within a single message.
|
||||
*/
|
||||
parallelToolCalls: boolean;
|
||||
|
||||
/**
|
||||
* Whether to wrap tool call responses in XML format.
|
||||
*/
|
||||
enableXmlWrapper: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 对任务循环进行的抽象封装
|
||||
*/
|
||||
export class TaskLoop {
|
||||
constructor(taskOptions?: TaskLoopOptions);
|
||||
|
||||
/**
|
||||
* @description wait for connection
|
||||
*/
|
||||
waitConnection(): Promise<void>;
|
||||
|
||||
/**
|
||||
* @description Set the task loop options
|
||||
* @param taskOptions
|
||||
*/
|
||||
setTaskLoopOptions(taskOptions: TaskLoopOptions): void;
|
||||
|
||||
/**
|
||||
* @description make chat data
|
||||
* @param tabStorage
|
||||
*/
|
||||
makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined;
|
||||
|
||||
/**
|
||||
* @description stop the task loop
|
||||
*/
|
||||
abort(): void;
|
||||
|
||||
/**
|
||||
* @description Register a callback function triggered on error
|
||||
* @param handler
|
||||
*/
|
||||
registerOnError(handler: (msg: IErrorMssage) => void): void;
|
||||
|
||||
/**
|
||||
* @description Register a callback function triggered on chunk
|
||||
* @param handler
|
||||
*/
|
||||
registerOnChunk(handler: (chunk: ChatCompletionChunk) => void): void;
|
||||
|
||||
/**
|
||||
* @description Register a callback function triggered at the beginning of each epoch
|
||||
* @param handler
|
||||
*/
|
||||
registerOnDone(handler: () => void): void;
|
||||
|
||||
/**
|
||||
* @description Register a callback function triggered at the beginning of each epoch
|
||||
* @param handler
|
||||
*/
|
||||
registerOnEpoch(handler: () => void): void;
|
||||
|
||||
/**
|
||||
* @description Registers a callback function that is triggered when a tool call is completed. This method allows you to intercept and modify the output of the tool call.
|
||||
* @param handler
|
||||
*/
|
||||
registerOnToolCalled(handler: (toolCallResult: ToolCallResult) => ToolCallResult): void;
|
||||
|
||||
/**
|
||||
* @description Register a callback triggered after tool call finishes. You can intercept and modify the output.
|
||||
* @param handler
|
||||
*/
|
||||
registerOnToolCall(handler: (toolCall: ToolCall) => ToolCall): void;
|
||||
|
||||
/**
|
||||
* @description Get current LLM configuration
|
||||
*/
|
||||
getLlmConfig(): any;
|
||||
|
||||
/**
|
||||
* @description Set the current LLM configuration, for Node.js environment
|
||||
* @param config
|
||||
* @example
|
||||
* setLlmConfig({
|
||||
* id: 'openai',
|
||||
* baseUrl: 'https://api.openai.com/v1',
|
||||
* userToken: 'sk-xxx',
|
||||
* userModel: 'gpt-3.5-turbo',
|
||||
* })
|
||||
*/
|
||||
setLlmConfig(config: any): void;
|
||||
|
||||
/**
|
||||
* @description Set proxy server
|
||||
* @param maxEpochs
|
||||
*/
|
||||
setMaxEpochs(maxEpochs: number): void;
|
||||
|
||||
/**
|
||||
* @description bind streaming content and tool calls
|
||||
*/
|
||||
bindStreaming(content: Ref<string>, toolCalls: Ref<ToolCall[]>): void;
|
||||
|
||||
/**
|
||||
* @description not finish
|
||||
*/
|
||||
connectToService(): Promise<void>;
|
||||
|
||||
/**
|
||||
* @description 设置代理服务器
|
||||
* @param proxyServer
|
||||
*/
|
||||
setProxyServer(proxyServer: string): void;
|
||||
|
||||
/**
|
||||
* @description Get all available tool list
|
||||
*/
|
||||
listTools(): Promise<ToolItem[]>;
|
||||
|
||||
/**
|
||||
* @description Start the loop and asynchronously update the DOM
|
||||
*/
|
||||
start(tabStorage: ChatStorage, userMessage: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* @description Create single conversation context
|
||||
*/
|
||||
createStorage(settings?: ChatSetting): Promise<ChatStorage>;
|
||||
|
||||
/**
|
||||
* @description Get prompt template from mcp server
|
||||
*/
|
||||
getPrompt(promptId: string, args?: Record<string, any>): Promise<string>;
|
||||
|
||||
/**
|
||||
* @description Get resource template from mcp server
|
||||
*/
|
||||
getResource(resourceUri: string): Promise<string>;
|
||||
}
|
||||
|
||||
export declare const getToolSchema: any;
|
||||
export declare const useMessageBridge: any;
|
||||
export declare const llmManager: any;
|
||||
export declare const llms: any;
|
||||
export declare const pinkLog: any;
|
||||
export declare const redLog: any;
|
||||
export declare const ElMessage: any;
|
||||
export declare const handleToolCalls: any;
|
||||
export declare const getPlatform: any;
|
@ -10,7 +10,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"declarationMap": false,
|
||||
"experimentalDecorators": true,
|
||||
},
|
||||
"paths": {
|
||||
|
@ -14,6 +14,9 @@
|
||||
"noEmitHelpers": false,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true,
|
||||
"types": [
|
||||
"./service/task-loop.d.ts"
|
||||
],
|
||||
// 允许访问 openmcp-sdk 目录
|
||||
"paths": {
|
||||
"@openmcp-sdk/*": [
|
||||
|
Loading…
x
Reference in New Issue
Block a user