增加小型对象数据库

This commit is contained in:
锦恢 2025-04-25 20:14:59 +08:00
parent f925da7d7d
commit ca64ce040f
18 changed files with 1501 additions and 122 deletions

View File

@ -2,7 +2,10 @@
## [main] 0.0.5
- 支持对已经打开过的文件项目进行管理
- 支持对用户对应服务器的调试工作内容进行保存MVP 100%
- 支持对用户对应服务器的调试工作内容进行保存
- 支持连续工具调用和错误警告的显示
- 实现小型本地对象数据库,用于对对话产生的多媒体进行数据持久化
- 支持对于调用结果进行一键复现
## [main] 0.0.4
- 修复选择模型后点击确认跳转回 deepseek 的 bug

View File

@ -51,6 +51,7 @@
| `service` | 系统配置信息云同步 | `MVP` | 0% | `P1` |
| `all` | 系统提示词管理模块 | `MVP` | 0% | `P1` |
| `service` | 工具 wise 的日志系统 | `MVP` | 0% | `P0` |
| `service` | 自带 OCR 进行字符识别 | `MVP` | 0% | `P1` |
## Dev

1257
service/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,8 @@
"pako": "^2.1.0",
"pino": "^9.6.0",
"pino-pretty": "^13.0.0",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
"tesseract.js": "^6.0.1",
"ws": "^8.18.1"
}

View File

@ -1,140 +1,94 @@
import { PostMessageble } from '../hook/adapter';
import { connect, MCPClient, type MCPOptions } from '../hook/client';
import { lookupEnvVarHandler } from './env-var';
import { callTool, getPrompt, getServerVersion, listPrompts, listResources, listResourceTemplates, listTools, readResource } from './mcp-server';
import { chatCompletionHandler } from './llm';
import { panelLoadHandler, panelSaveHandler } from './panel';
import { settingLoadHandler, settingSaveHandler } from './setting';
import { ping } from './util';
import { lookupEnvVarService } from '../service/env-var';
import { spawnSync } from 'node:child_process';
import {
callToolService,
getPromptService,
getServerVersionService,
listPromptsService,
listResourcesService,
listResourceTemplatesService,
listToolsService,
readResourceService
} from '../service/mcp-server';
import { abortMessageService, chatCompletionService } from '../service/llm';
import { panelLoadService, panelSaveService } from '../service/panel';
import { settingLoadService, settingSaveService } from '../service/setting';
import { pingService } from '../service/util';
import { client, connectService } from '../service/connect';
// TODO: 支持更多的 client
export let client: MCPClient | undefined = undefined;
function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
try {
console.log('current command', command);
console.log('current args', args);
const result = spawnSync(command, args, {
cwd: cwd || process.cwd(),
stdio: 'pipe',
encoding: 'utf-8'
});
if (result.error) {
return result.error.message;
}
if (result.status !== 0) {
return result.stderr || `Command failed with code ${result.status}`;
}
return null;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
async function connectHandler(option: MCPOptions, webview: PostMessageble) {
try {
console.log('ready to connect', option);
client = await connect(option);
const connectResult = {
code: 200,
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
};
webview.postMessage({ command: 'connect', data: connectResult });
} catch (error) {
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
// 比如 error: Failed to spawn: `server.py`
// Caused by: No such file or directory (os error 2)
let errorMsg = '';
if (option.command) {
errorMsg += tryGetRunCommandError(option.command, option.args, option.cwd);
}
errorMsg += (error as any).toString();
const connectResult = {
code: 500,
msg: errorMsg
};
webview.postMessage({ command: 'connect', data: connectResult });
}
}
export function messageController(command: string, data: any, webview: PostMessageble) {
switch (command) {
case 'connect':
connectHandler(data, webview);
connectService(client, data, webview);
break;
case 'server/version':
getServerVersion(client, webview);
getServerVersionService(client, data, webview);
break;
case 'prompts/list':
listPrompts(client, webview);
listPromptsService(client, data, webview);
break;
case 'prompts/get':
getPrompt(client, data, webview);
getPromptService(client, data, webview);
break;
case 'resources/list':
listResources(client, webview);
listResourcesService(client, data, webview);
break;
case 'resources/templates/list':
listResourceTemplates(client, webview);
listResourceTemplatesService(client, data, webview);
break;
case 'resources/read':
readResource(client, data, webview);
readResourceService(client, data, webview);
break;
case 'tools/list':
listTools(client, webview);
listToolsService(client, data, webview);
break;
case 'tools/call':
callTool(client, data, webview);
callToolService(client, data, webview);
break;
case 'ping':
ping(client, webview);
pingService(client, data, webview);
break;
case 'setting/save':
settingSaveHandler(client, data, webview);
settingSaveService(client, data, webview);
break;
case 'setting/load':
settingLoadHandler(client, webview);
settingLoadService(client, data, webview);
break;
case 'panel/save':
panelSaveHandler(client, data, webview);
panelSaveService(client, data, webview);
break;
case 'panel/load':
panelLoadHandler(client, webview);
panelLoadService(client, data, webview);
break;
case 'llm/chat/completions':
chatCompletionHandler(client, data, webview);
chatCompletionService(client, data, webview);
break;
case 'llm/chat/completions/cancel':
abortMessageService(client, data, webview);
break;
case 'lookup-env-var':
lookupEnvVarHandler(client, data, webview);
lookupEnvVarService(client, data, webview);
break;
default:

69
service/src/hook/db.ts Normal file
View File

@ -0,0 +1,69 @@
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import * as os from 'os';
import * as path from 'path';
interface Entity {
id: string | number;
[key: string]: any;
}
export class LocalDB<T extends Entity> {
private db: any;
constructor(private tableName: string) {
this.init();
}
private async init() {
// 默认存储在用户目录的 .openmcp 目录下
const homedir = os.homedir();
const filename = path.join(homedir, '.openmcp', 'db.sqlite');
this.db = await open({
filename,
driver: sqlite3.Database
});
await this.db.exec(`
CREATE TABLE IF NOT EXISTS ${this.tableName} (
id TEXT PRIMARY KEY,
data TEXT NOT NULL
)
`);
}
async insert(entity: T): Promise<void> {
await this.db.run(
`INSERT OR REPLACE INTO ${this.tableName} (id, data) VALUES (?, ?)`,
entity.id,
JSON.stringify(entity)
);
}
async findById(id: string | number): Promise<T | undefined> {
const row = await this.db.get(
`SELECT data FROM ${this.tableName} WHERE id = ?`,
id
);
return row ? JSON.parse(row.data) : undefined;
}
async findAll(): Promise<T[]> {
const rows = await this.db.all(
`SELECT data FROM ${this.tableName}`
);
return rows.map((row: any) => JSON.parse(row.data));
}
async delete(id: string | number): Promise<void> {
await this.db.run(
`DELETE FROM ${this.tableName} WHERE id = ?`,
id
);
}
async close(): Promise<void> {
await this.db.close();
}
}

View File

@ -2,4 +2,4 @@ export { messageController } from './controller';
export { VSCodeWebViewLike } from './hook/adapter';
export { setVscodeWorkspace } from './hook/setting';
// TODO: 更加规范
export { client } from './controller';
export { client } from './service/connect';

View File

@ -2,7 +2,7 @@ import WebSocket from 'ws';
import pino from 'pino';
import { messageController } from './controller';
import { VSCodeWebViewLike } from './adapter';
import { VSCodeWebViewLike } from './hook/adapter';
export interface VSCodeMessage {
command: string;

View File

@ -0,0 +1,66 @@
import { PostMessageble } from '../hook/adapter';
import { connect, MCPClient, type MCPOptions } from '../hook/client';
import { spawnSync } from 'node:child_process';
// TODO: 支持更多的 client
export let client: MCPClient | undefined = undefined;
function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
try {
console.log('current command', command);
console.log('current args', args);
const result = spawnSync(command, args, {
cwd: cwd || process.cwd(),
stdio: 'pipe',
encoding: 'utf-8'
});
if (result.error) {
return result.error.message;
}
if (result.status !== 0) {
return result.stderr || `Command failed with code ${result.status}`;
}
return null;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
export async function connectService(
client: MCPClient | undefined,
option: MCPOptions,
webview: PostMessageble
) {
try {
console.log('ready to connect', option);
client = await connect(option);
const connectResult = {
code: 200,
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
};
webview.postMessage({ command: 'connect', data: connectResult });
} catch (error) {
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
// 比如 error: Failed to spawn: `server.py`
// Caused by: No such file or directory (os error 2)
let errorMsg = '';
if (option.command) {
errorMsg += tryGetRunCommandError(option.command, option.args, option.cwd);
}
errorMsg += (error as any).toString();
const connectResult = {
code: 500,
msg: errorMsg
};
webview.postMessage({ command: 'connect', data: connectResult });
}
}

View File

@ -2,7 +2,7 @@ import { PostMessageble } from "../hook/adapter";
import { MCPClient } from "../hook/client";
export async function lookupEnvVarHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) {
export async function lookupEnvVarService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
try {
const { keys } = data;

View File

@ -4,7 +4,7 @@ import { PostMessageble } from '../hook/adapter';
let currentStream: AsyncIterable<any> | null = null;
export async function chatCompletionHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) {
export async function chatCompletionService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
if (!client) {
const connectResult = {
code: 501,
@ -100,7 +100,7 @@ export async function chatCompletionHandler(client: MCPClient | undefined, data:
}
// 处理中止消息的函数
export function handleAbortMessage(webview: PostMessageble) {
export function abortMessageService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
if (currentStream) {
// 标记流已中止
currentStream = null;

View File

@ -21,8 +21,9 @@ export interface CallToolOption {
/**
* @description prompts
*/
export async function listPrompts(
export async function listPromptsService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
if (!client) {
@ -53,7 +54,7 @@ export async function listPrompts(
/**
* @description prompt
*/
export async function getPrompt(
export async function getPromptService(
client: MCPClient | undefined,
option: GetPromptOption,
webview: PostMessageble
@ -86,8 +87,9 @@ export async function getPrompt(
/**
* @description resources
*/
export async function listResources(
export async function listResourcesService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
if (!client) {
@ -119,8 +121,9 @@ export async function listResources(
/**
* @description resources
*/
export async function listResourceTemplates(
export async function listResourceTemplatesService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
if (!client) {
@ -152,7 +155,7 @@ export async function listResourceTemplates(
/**
* @description resource
*/
export async function readResource(
export async function readResourceService(
client: MCPClient | undefined,
option: ReadResourceOption,
webview: PostMessageble
@ -185,8 +188,9 @@ export async function readResource(
/**
* @description
*/
export async function listTools(
export async function listToolsService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
if (!client) {
@ -220,7 +224,7 @@ export async function listTools(
/**
* @description
*/
export async function callTool(
export async function callToolService(
client: MCPClient | undefined,
option: CallToolOption,
webview: PostMessageble
@ -254,8 +258,9 @@ export async function callTool(
}
}
export async function getServerVersion(
export async function getServerVersionService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
if (!client) {

View File

@ -0,0 +1,18 @@
import { PostMessageble } from "../hook/adapter";
import { MCPClient } from "../hook/client";
export function ocrService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
webview.postMessage({
command: 'ping',
data: {
code: 200,
msg: {}
}
});
}

View File

@ -2,7 +2,7 @@ import { PostMessageble } from '../hook/adapter';
import { loadConfig, loadTabSaveConfig, saveConfig, saveTabSaveConfig } from '../hook/setting';
import { MCPClient } from '../hook/client';
export async function panelSaveHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) {
export async function panelSaveService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
try {
// 保存配置
const serverInfo = client?.getServerVersion();
@ -26,7 +26,11 @@ export async function panelSaveHandler(client: MCPClient | undefined, data: any,
}
}
export async function panelLoadHandler(client: MCPClient | undefined, webview: PostMessageble) {
export async function panelLoadService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
try {
// 加载配置
const serverInfo = client?.getServerVersion();

View File

@ -2,7 +2,11 @@ import { PostMessageble } from '../hook/adapter';
import { loadConfig, saveConfig } from '../hook/setting';
import { MCPClient } from '../hook/client';
export async function settingSaveHandler(client: MCPClient | undefined, data: any, webview: PostMessageble) {
export async function settingSaveService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
try {
// 保存配置
saveConfig(data);
@ -28,7 +32,11 @@ export async function settingSaveHandler(client: MCPClient | undefined, data: an
}
}
export async function settingLoadHandler(client: MCPClient | undefined, webview: PostMessageble) {
export async function settingLoadService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
try {
// 加载配置
const config = loadConfig();

View File

@ -1,7 +1,11 @@
import { PostMessageble } from "../hook/adapter";
import { MCPClient } from "../hook/client";
export function ping(client: MCPClient | undefined, webview: PostMessageble) {
export function pingService(
client: MCPClient | undefined,
data: any,
webview: PostMessageble
) {
if (!client) {
const connectResult = {
code: 501,
@ -12,7 +16,8 @@ export function ping(client: MCPClient | undefined, webview: PostMessageble) {
}
webview.postMessage({
command: 'ping', data: {
command: 'ping',
data: {
code: 200,
msg: {}
}

View File

@ -10,6 +10,11 @@
"declaration": true, //
"declarationMap": true //
},
"paths": {
"@/*": [
"src/*"
]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "src/main.ts"] // main.ts
}