重构 OpenMCPService,采用新的架构
This commit is contained in:
parent
8ef6ddd1ed
commit
31fa5ead4f
@ -55,7 +55,7 @@ export function makeEnv() {
|
|||||||
type ConnectionType = 'STDIO' | 'SSE';
|
type ConnectionType = 'STDIO' | 'SSE';
|
||||||
|
|
||||||
// 定义命令行参数接口
|
// 定义命令行参数接口
|
||||||
export interface MCPOptions {
|
export interface McpOptions {
|
||||||
connectionType: ConnectionType;
|
connectionType: ConnectionType;
|
||||||
// STDIO 特定选项
|
// STDIO 特定选项
|
||||||
command?: string;
|
command?: string;
|
||||||
@ -70,7 +70,7 @@ export interface MCPOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function doConnect() {
|
export function doConnect() {
|
||||||
let connectOption: MCPOptions;
|
let connectOption: McpOptions;
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
const env = makeEnv();
|
const env = makeEnv();
|
||||||
|
|
||||||
@ -313,7 +313,7 @@ async function launchSSE() {
|
|||||||
resolve(void 0);
|
resolve(void 0);
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
const connectOption: MCPOptions = {
|
const connectOption: McpOptions = {
|
||||||
connectionType: 'SSE',
|
connectionType: 'SSE',
|
||||||
url: connectionArgs.urlString,
|
url: connectionArgs.urlString,
|
||||||
clientName: 'openmcp.connect.sse',
|
clientName: 'openmcp.connect.sse',
|
||||||
|
33
service/src/common/index.dto.ts
Normal file
33
service/src/common/index.dto.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { McpClient } from "../mcp/client.service";
|
||||||
|
|
||||||
|
export type RequestClientType = McpClient | undefined;
|
||||||
|
|
||||||
|
export type RequestHandler<T, R> = (
|
||||||
|
client: RequestClientType,
|
||||||
|
data: T,
|
||||||
|
webview: PostMessageble
|
||||||
|
) => Promise<R>;
|
||||||
|
|
||||||
|
export interface RequestHandlerStore<T, R> {
|
||||||
|
handler: RequestHandler<T, R>
|
||||||
|
option?: ControllerOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MapperDescriptor<T> {
|
||||||
|
configurable?: boolean;
|
||||||
|
enumerable?: boolean;
|
||||||
|
value?: RequestHandler<T, RestfulResponse>;
|
||||||
|
writable?: boolean;
|
||||||
|
get?(): any;
|
||||||
|
set?(v: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RestfulResponse {
|
||||||
|
code: number;
|
||||||
|
msg: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ControllerOption {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
17
service/src/common/index.ts
Normal file
17
service/src/common/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { MapperDescriptor, RequestHandler, RequestClientType, RestfulResponse, ControllerOption, RequestHandlerStore } from "./index.dto";
|
||||||
|
export { MapperDescriptor, RequestHandler, RequestClientType };
|
||||||
|
|
||||||
|
export const requestHandlerStorage = new Map<string, RequestHandlerStore<any, RestfulResponse>>();
|
||||||
|
|
||||||
|
export function Controller(command: string, option: ControllerOption = {}) {
|
||||||
|
return function<T>(target: any, propertykey: string, descriptor: MapperDescriptor<T>) {
|
||||||
|
const handler = descriptor.value;
|
||||||
|
if (requestHandlerStorage.has(command)) {
|
||||||
|
throw new Error(`Duplicate request handler for ${command}`);
|
||||||
|
}
|
||||||
|
if (handler) {
|
||||||
|
requestHandlerStorage.set(command, { handler, option });
|
||||||
|
}
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
41
service/src/common/router.ts
Normal file
41
service/src/common/router.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { requestHandlerStorage } from ".";
|
||||||
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { LlmController } from "../llm/llm.controller";
|
||||||
|
import { ClientController } from "../mcp/client.controller";
|
||||||
|
import { ConnectController } from "../mcp/connect.controller";
|
||||||
|
import { client } from "../mcp/connect.service";
|
||||||
|
import { PanelController } from "../panel/panel.controller";
|
||||||
|
import { SettingController } from "../setting/setting.controller";
|
||||||
|
|
||||||
|
export const ModuleControllers = [
|
||||||
|
ConnectController,
|
||||||
|
ClientController,
|
||||||
|
LlmController,
|
||||||
|
PanelController,
|
||||||
|
SettingController
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function routeMessage(command: string, data: any, webview: PostMessageble) {
|
||||||
|
const handlerStore = requestHandlerStorage.get(command);
|
||||||
|
if (handlerStore) {
|
||||||
|
const { handler, option = {} } = handlerStore;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: select client based on something
|
||||||
|
const res = await handler(client, data, webview);
|
||||||
|
|
||||||
|
// res.code = -1 代表当前请求不需要返回发送
|
||||||
|
if (res.code >= 0) {
|
||||||
|
webview.postMessage({ command, data: res });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
webview.postMessage({
|
||||||
|
command, data: {
|
||||||
|
code: 500,
|
||||||
|
msg: (error as any).toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -1,97 +0,0 @@
|
|||||||
|
|
||||||
import { PostMessageble } from '../hook/adapter';
|
|
||||||
import { lookupEnvVarService } from '../service/env-var';
|
|
||||||
|
|
||||||
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';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function messageController(command: string, data: any, webview: PostMessageble) {
|
|
||||||
switch (command) {
|
|
||||||
case 'connect':
|
|
||||||
connectService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'server/version':
|
|
||||||
getServerVersionService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'prompts/list':
|
|
||||||
listPromptsService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'prompts/get':
|
|
||||||
getPromptService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'resources/list':
|
|
||||||
listResourcesService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'resources/templates/list':
|
|
||||||
listResourceTemplatesService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'resources/read':
|
|
||||||
readResourceService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tools/list':
|
|
||||||
listToolsService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tools/call':
|
|
||||||
callToolService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ping':
|
|
||||||
pingService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'setting/save':
|
|
||||||
settingSaveService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'setting/load':
|
|
||||||
settingLoadService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'panel/save':
|
|
||||||
panelSaveService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'panel/load':
|
|
||||||
panelLoadService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'llm/chat/completions':
|
|
||||||
chatCompletionService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'llm/chat/completions/abort':
|
|
||||||
abortMessageService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'lookup-env-var':
|
|
||||||
lookupEnvVarService(client, data, webview);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
import { OpenAI } from 'openai';
|
|
||||||
|
|
||||||
export const llms = [
|
export const llms = [
|
||||||
{
|
{
|
||||||
id: 'deepseek',
|
id: 'deepseek',
|
||||||
@ -86,57 +84,3 @@ export const llms = [
|
|||||||
userModel: 'moonshot-v1-8k'
|
userModel: 'moonshot-v1-8k'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
type MyMessageType = OpenAI.Chat.ChatCompletionMessageParam & {
|
|
||||||
extraInfo?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
type MyToolMessageType = OpenAI.Chat.ChatCompletionToolMessageParam & {
|
|
||||||
extraInfo?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function postProcessToolMessages(message: MyToolMessageType) {
|
|
||||||
if (typeof message.content === 'string') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const content of message.content) {
|
|
||||||
const contentType = content.type as string;
|
|
||||||
const rawContent = content as any;
|
|
||||||
|
|
||||||
if (contentType === 'image') {
|
|
||||||
delete rawContent._meta;
|
|
||||||
|
|
||||||
rawContent.type = 'text';
|
|
||||||
|
|
||||||
// 从缓存中提取图像数据
|
|
||||||
rawContent.text = '图片已被删除';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message.content = JSON.stringify(message.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postProcessMessages(messages: MyMessageType[]) {
|
|
||||||
for (const message of messages) {
|
|
||||||
// 去除 extraInfo 属性
|
|
||||||
delete message.extraInfo;
|
|
||||||
|
|
||||||
switch (message.role) {
|
|
||||||
case 'user':
|
|
||||||
break;
|
|
||||||
case 'assistant':
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'system':
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tool':
|
|
||||||
postProcessToolMessages(message);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,161 +1,5 @@
|
|||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as os from 'os';
|
|
||||||
import { llms } from './llm';
|
|
||||||
import { IServerVersion } from './client';
|
|
||||||
|
|
||||||
export let VSCODE_WORKSPACE = '';
|
export let VSCODE_WORKSPACE = '';
|
||||||
|
|
||||||
export function setVscodeWorkspace(workspace: string) {
|
export function setVscodeWorkspace(workspace: string) {
|
||||||
VSCODE_WORKSPACE = workspace;
|
VSCODE_WORKSPACE = workspace;
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigurationPath() {
|
|
||||||
// 如果是 vscode 插件下,则修改为 ~/.openmcp/config.json
|
|
||||||
if (VSCODE_WORKSPACE) {
|
|
||||||
// 在 VSCode 插件环境下
|
|
||||||
const homeDir = os.homedir();
|
|
||||||
const configDir = path.join(homeDir, '.openmcp');
|
|
||||||
if (!fs.existsSync(configDir)) {
|
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
|
||||||
}
|
|
||||||
return path.join(configDir, 'setting.json');
|
|
||||||
}
|
|
||||||
return 'setting.json';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTabSavePath(serverInfo: IServerVersion) {
|
|
||||||
const { name = 'untitle', version = '0.0.0' } = serverInfo || {};
|
|
||||||
const tabSaveName = `tabs.${name}.json`;
|
|
||||||
|
|
||||||
// 如果是 vscode 插件下,则修改为 ~/.vscode/openmcp.json
|
|
||||||
if (VSCODE_WORKSPACE) {
|
|
||||||
// 在 VSCode 插件环境下
|
|
||||||
const configDir = path.join(VSCODE_WORKSPACE, '.vscode');
|
|
||||||
if (!fs.existsSync(configDir)) {
|
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
|
||||||
}
|
|
||||||
return path.join(configDir, tabSaveName);
|
|
||||||
}
|
|
||||||
return tabSaveName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultLanguage() {
|
|
||||||
if (process.env.VSCODE_PID) {
|
|
||||||
// TODO: 获取 vscode 内部的语言
|
|
||||||
|
|
||||||
}
|
|
||||||
return 'zh';
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IConfig {
|
|
||||||
MODEL_INDEX: number;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_CONFIG: IConfig = {
|
|
||||||
MODEL_INDEX: 0,
|
|
||||||
LLM_INFO: llms,
|
|
||||||
LANG: getDefaultLanguage()
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SaveTabItem {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
type: string;
|
|
||||||
componentIndex: number;
|
|
||||||
storage: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SaveTab {
|
|
||||||
tabs: SaveTabItem[]
|
|
||||||
currentIndex: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_TABS: SaveTab = {
|
|
||||||
tabs: [],
|
|
||||||
currentIndex: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
function createConfig(): IConfig {
|
|
||||||
const configPath = getConfigurationPath();
|
|
||||||
const configDir = path.dirname(configPath);
|
|
||||||
|
|
||||||
// 确保配置目录存在
|
|
||||||
if (configDir && !fs.existsSync(configDir)) {
|
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入默认配置
|
|
||||||
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2), 'utf-8');
|
|
||||||
return DEFAULT_CONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSaveTabConfig(serverInfo: IServerVersion): SaveTab {
|
|
||||||
const configPath = getTabSavePath(serverInfo);
|
|
||||||
const configDir = path.dirname(configPath);
|
|
||||||
|
|
||||||
// 确保配置目录存在
|
|
||||||
if (configDir && !fs.existsSync(configDir)) {
|
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入默认配置
|
|
||||||
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_TABS, null, 2), 'utf-8');
|
|
||||||
return DEFAULT_TABS;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadConfig(): IConfig {
|
|
||||||
const configPath = getConfigurationPath();
|
|
||||||
|
|
||||||
if (!fs.existsSync(configPath)) {
|
|
||||||
return createConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const configData = fs.readFileSync(configPath, 'utf-8');
|
|
||||||
return JSON.parse(configData) as IConfig;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading config file, creating new one:', error);
|
|
||||||
return createConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveConfig(config: Partial<IConfig>): void {
|
|
||||||
const configPath = getConfigurationPath();
|
|
||||||
console.log('save to ' + configPath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving config file:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadTabSaveConfig(serverInfo: IServerVersion): SaveTab {
|
|
||||||
const tabSavePath = getTabSavePath(serverInfo);
|
|
||||||
|
|
||||||
if (!fs.existsSync(tabSavePath)) {
|
|
||||||
return createSaveTabConfig(serverInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const configData = fs.readFileSync(tabSavePath, 'utf-8');
|
|
||||||
return JSON.parse(configData) as SaveTab;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading config file, creating new one:', error);
|
|
||||||
return createSaveTabConfig(serverInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveTabSaveConfig(serverInfo: IServerVersion, config: Partial<IConfig>): void {
|
|
||||||
const tabSavePath = getTabSavePath(serverInfo);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(tabSavePath, JSON.stringify(config, null, 2), 'utf-8');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving config file:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
export { messageController } from './controller';
|
export { routeMessage } from './common/router';
|
||||||
export { VSCodeWebViewLike } from './hook/adapter';
|
export { VSCodeWebViewLike } from './hook/adapter';
|
||||||
export { setVscodeWorkspace } from './hook/setting';
|
export { setVscodeWorkspace } from './hook/setting';
|
||||||
// TODO: 更加规范
|
// TODO: 更加规范
|
||||||
export { client } from './service/connect';
|
export { client } from './mcp/connect.service';
|
29
service/src/llm/llm.controller.ts
Normal file
29
service/src/llm/llm.controller.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Controller, RequestClientType } from "../common";
|
||||||
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { abortMessageService, streamingChatCompletion } from "./llm.service";
|
||||||
|
|
||||||
|
export class LlmController {
|
||||||
|
|
||||||
|
@Controller('llm/chat/completions')
|
||||||
|
async chatCompletion(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg:'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await streamingChatCompletion(data, webview);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: -1,
|
||||||
|
msg: 'terminate'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('llm/chat/completions/abort')
|
||||||
|
async abortChatCompletion(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
return abortMessageService(data, webview);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
service/src/llm/llm.dto.ts
Normal file
9
service/src/llm/llm.dto.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { OpenAI } from "openai";
|
||||||
|
|
||||||
|
export type MyMessageType = OpenAI.Chat.ChatCompletionMessageParam & {
|
||||||
|
extraInfo?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MyToolMessageType = OpenAI.Chat.ChatCompletionToolMessageParam & {
|
||||||
|
extraInfo?: any;
|
||||||
|
}
|
146
service/src/llm/llm.service.ts
Normal file
146
service/src/llm/llm.service.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { OpenAI } from "openai";
|
||||||
|
import { MyMessageType, MyToolMessageType } from "./llm.dto";
|
||||||
|
import { RestfulResponse } from "../common/index.dto";
|
||||||
|
|
||||||
|
export let currentStream: AsyncIterable<any> | null = null;
|
||||||
|
|
||||||
|
export async function streamingChatCompletion(
|
||||||
|
data: any,
|
||||||
|
webview: PostMessageble
|
||||||
|
) {
|
||||||
|
let { baseURL, apiKey, model, messages, temperature, tools = [] } = data;
|
||||||
|
|
||||||
|
const client = new OpenAI({
|
||||||
|
baseURL,
|
||||||
|
apiKey
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tools.length === 0) {
|
||||||
|
tools = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
postProcessMessages(messages);
|
||||||
|
|
||||||
|
const stream = await client.chat.completions.create({
|
||||||
|
model,
|
||||||
|
messages,
|
||||||
|
temperature,
|
||||||
|
tools,
|
||||||
|
tool_choice: 'auto',
|
||||||
|
web_search_options: {},
|
||||||
|
stream: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 存储当前的流式传输对象
|
||||||
|
currentStream = stream;
|
||||||
|
|
||||||
|
// 流式传输结果
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
if (!currentStream) {
|
||||||
|
// 如果流被中止,则停止循环
|
||||||
|
// TODO: 为每一个标签页设置不同的 currentStream 管理器
|
||||||
|
stream.controller.abort();
|
||||||
|
// 传输结束
|
||||||
|
webview.postMessage({
|
||||||
|
command: 'llm/chat/completions/done',
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
msg: {
|
||||||
|
success: true,
|
||||||
|
stage: 'abort'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.choices) {
|
||||||
|
const chunkResult = {
|
||||||
|
code: 200,
|
||||||
|
msg: {
|
||||||
|
chunk
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
webview.postMessage({
|
||||||
|
command: 'llm/chat/completions/chunk',
|
||||||
|
data: chunkResult
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传输结束
|
||||||
|
webview.postMessage({
|
||||||
|
command: 'llm/chat/completions/done',
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
msg: {
|
||||||
|
success: true,
|
||||||
|
stage: 'done'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 处理中止消息的函数
|
||||||
|
export function abortMessageService(data: any, webview: PostMessageble): RestfulResponse {
|
||||||
|
if (currentStream) {
|
||||||
|
// 标记流已中止
|
||||||
|
currentStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function postProcessToolMessages(message: MyToolMessageType) {
|
||||||
|
if (typeof message.content === 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const content of message.content) {
|
||||||
|
const contentType = content.type as string;
|
||||||
|
const rawContent = content as any;
|
||||||
|
|
||||||
|
if (contentType === 'image') {
|
||||||
|
delete rawContent._meta;
|
||||||
|
|
||||||
|
rawContent.type = 'text';
|
||||||
|
|
||||||
|
// 从缓存中提取图像数据
|
||||||
|
rawContent.text = '图片已被删除';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message.content = JSON.stringify(message.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postProcessMessages(messages: MyMessageType[]) {
|
||||||
|
for (const message of messages) {
|
||||||
|
// 去除 extraInfo 属性
|
||||||
|
delete message.extraInfo;
|
||||||
|
|
||||||
|
switch (message.role) {
|
||||||
|
case 'user':
|
||||||
|
break;
|
||||||
|
case 'assistant':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'system':
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tool':
|
||||||
|
postProcessToolMessages(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import pino from 'pino';
|
import pino from 'pino';
|
||||||
|
|
||||||
import { messageController } from './controller';
|
import { routeMessage } from './common/router';
|
||||||
import { VSCodeWebViewLike } from './hook/adapter';
|
import { VSCodeWebViewLike } from './hook/adapter';
|
||||||
|
|
||||||
export interface VSCodeMessage {
|
export interface VSCodeMessage {
|
||||||
@ -23,7 +23,7 @@ const logger = pino({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export type MessageHandler = (message: VSCodeMessage) => void;
|
export type MessageHandler = (message: VSCodeMessage) => void;
|
||||||
const wss = new WebSocket.Server({ port: 8080 });
|
const wss = new (WebSocket as any).Server({ port: 8080 });
|
||||||
|
|
||||||
wss.on('connection', ws => {
|
wss.on('connection', ws => {
|
||||||
|
|
||||||
@ -44,6 +44,6 @@ wss.on('connection', ws => {
|
|||||||
logger.info(`command: [${message.command || 'No Command'}]`);
|
logger.info(`command: [${message.command || 'No Command'}]`);
|
||||||
|
|
||||||
const { command, data } = message;
|
const { command, data } = message;
|
||||||
messageController(command, data, webview);
|
routeMessage(command, data, webview);
|
||||||
});
|
});
|
||||||
});
|
});
|
138
service/src/mcp/client.controller.ts
Normal file
138
service/src/mcp/client.controller.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { Controller, RequestClientType } from "../common";
|
||||||
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
|
||||||
|
export class ClientController {
|
||||||
|
|
||||||
|
@Controller('server/version')
|
||||||
|
async getServerVersion(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg:'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = client.getServerVersion();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: version
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('prompts/list')
|
||||||
|
async listPrompts(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
const connectResult = {
|
||||||
|
code: 501,
|
||||||
|
msg: 'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
return connectResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompts = await client.listPrompts();
|
||||||
|
const result = {
|
||||||
|
code: 200,
|
||||||
|
msg: prompts
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('prompts/get')
|
||||||
|
async getPrompt(client: RequestClientType, option: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg: 'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = await client.getPrompt(option.promptId, option.args || {});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: prompt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('resources/list')
|
||||||
|
async listResources(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg: 'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const resources = await client.listResources();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: resources
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('resources/templates/list')
|
||||||
|
async listResourceTemplates(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg: 'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const resources = await client.listResourceTemplates();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: resources
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('resources/read')
|
||||||
|
async readResource(client: RequestClientType, option: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg: 'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = await client.readResource(option.resourceUri);
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: resource
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('tools/list')
|
||||||
|
async listTools(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg: 'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const tools = await client.listTools();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: tools
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('tools/call')
|
||||||
|
async callTool(client: RequestClientType, option: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
return {
|
||||||
|
code: 501,
|
||||||
|
msg: 'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolResult = await client.callTool({
|
||||||
|
name: option.toolName,
|
||||||
|
arguments: option.toolArgs
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: toolResult
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
37
service/src/mcp/client.dto.ts
Normal file
37
service/src/mcp/client.dto.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||||
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
|
import { Implementation } from "@modelcontextprotocol/sdk/types";
|
||||||
|
|
||||||
|
export interface GetPromptOption {
|
||||||
|
promptId: string;
|
||||||
|
args?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReadResourceOption {
|
||||||
|
resourceUri: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CallToolOption {
|
||||||
|
toolName: string;
|
||||||
|
toolArgs: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义连接类型
|
||||||
|
export type ConnectionType = 'STDIO' | 'SSE';
|
||||||
|
|
||||||
|
export type McpTransport = StdioClientTransport | SSEClientTransport;
|
||||||
|
export type IServerVersion = Implementation | undefined;
|
||||||
|
|
||||||
|
// 定义命令行参数接口
|
||||||
|
export interface McpOptions {
|
||||||
|
connectionType: ConnectionType;
|
||||||
|
// STDIO 特定选项
|
||||||
|
command?: string;
|
||||||
|
args?: string[];
|
||||||
|
// SSE 特定选项
|
||||||
|
url?: string;
|
||||||
|
cwd?: string;
|
||||||
|
// 通用客户端选项
|
||||||
|
clientName?: string;
|
||||||
|
clientVersion?: string;
|
||||||
|
}
|
@ -2,38 +2,18 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|||||||
|
|
||||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
import { Implementation } from "@modelcontextprotocol/sdk/types";
|
import { McpOptions, McpTransport, IServerVersion } from './client.dto';
|
||||||
|
|
||||||
// 定义连接类型
|
|
||||||
type ConnectionType = 'STDIO' | 'SSE';
|
|
||||||
|
|
||||||
type McpTransport = StdioClientTransport | SSEClientTransport;
|
|
||||||
export type IServerVersion = Implementation | undefined;
|
|
||||||
|
|
||||||
// 定义命令行参数接口
|
|
||||||
export interface MCPOptions {
|
|
||||||
connectionType: ConnectionType;
|
|
||||||
// STDIO 特定选项
|
|
||||||
command?: string;
|
|
||||||
args?: string[];
|
|
||||||
// SSE 特定选项
|
|
||||||
url?: string;
|
|
||||||
cwd?: string;
|
|
||||||
// 通用客户端选项
|
|
||||||
clientName?: string;
|
|
||||||
clientVersion?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 增强的客户端类
|
// 增强的客户端类
|
||||||
export class MCPClient {
|
export class McpClient {
|
||||||
private client: Client;
|
private client: Client;
|
||||||
private transport?: McpTransport;
|
private transport?: McpTransport;
|
||||||
private options: MCPOptions;
|
private options: McpOptions;
|
||||||
private serverVersion: IServerVersion;
|
private serverVersion: IServerVersion;
|
||||||
|
|
||||||
private transportStdErr: string = '';
|
private transportStdErr: string = '';
|
||||||
|
|
||||||
constructor(options: MCPOptions) {
|
constructor(options: McpOptions) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.serverVersion = undefined;
|
this.serverVersion = undefined;
|
||||||
|
|
||||||
@ -144,8 +124,8 @@ export class MCPClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect 函数实现
|
// Connect 函数实现
|
||||||
export async function connect(options: MCPOptions): Promise<MCPClient> {
|
export async function connect(options: McpOptions): Promise<McpClient> {
|
||||||
const client = new MCPClient(options);
|
const client = new McpClient(options);
|
||||||
await client.connect();
|
await client.connect();
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
39
service/src/mcp/connect.controller.ts
Normal file
39
service/src/mcp/connect.controller.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Controller, RequestClientType } from '../common';
|
||||||
|
import { PostMessageble } from '../hook/adapter';
|
||||||
|
import { connectService } from './connect.service';
|
||||||
|
|
||||||
|
export class ConnectController {
|
||||||
|
|
||||||
|
@Controller('connect')
|
||||||
|
async connect(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
const res = await connectService(client, data);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('lookup-env-var')
|
||||||
|
async lookupEnvVar(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
const { keys } = data;
|
||||||
|
const values = keys.map((key: string) => process.env[key] || '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('ping')
|
||||||
|
async ping(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
if (!client) {
|
||||||
|
const connectResult = {
|
||||||
|
code: 501,
|
||||||
|
msg:'mcp client 尚未连接'
|
||||||
|
};
|
||||||
|
return connectResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
|
|
||||||
import { PostMessageble } from '../hook/adapter';
|
|
||||||
import { connect, MCPClient, type MCPOptions } from '../hook/client';
|
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { RequestClientType } from '../common';
|
||||||
|
import { connect } from './client.service';
|
||||||
|
import { RestfulResponse } from '../common/index.dto';
|
||||||
|
import { McpOptions } from './client.dto';
|
||||||
|
|
||||||
// TODO: 支持更多的 client
|
|
||||||
export let client: MCPClient | undefined = undefined;
|
|
||||||
|
|
||||||
function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
|
// TODO: 更多的 client
|
||||||
|
export let client: RequestClientType = undefined;
|
||||||
|
|
||||||
|
export function tryGetRunCommandError(command: string, args: string[] = [], cwd?: string): string | null {
|
||||||
try {
|
try {
|
||||||
console.log('current command', command);
|
console.log('current command', command);
|
||||||
console.log('current args', args);
|
console.log('current args', args);
|
||||||
@ -30,10 +32,9 @@ function tryGetRunCommandError(command: string, args: string[] = [], cwd?: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function connectService(
|
export async function connectService(
|
||||||
_client: MCPClient | undefined,
|
_client: RequestClientType,
|
||||||
option: MCPOptions,
|
option: McpOptions
|
||||||
webview: PostMessageble
|
): Promise<RestfulResponse> {
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
console.log('ready to connect', option);
|
console.log('ready to connect', option);
|
||||||
|
|
||||||
@ -42,7 +43,8 @@ export async function connectService(
|
|||||||
code: 200,
|
code: 200,
|
||||||
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
|
msg: 'Connect to OpenMCP successfully\nWelcome back, Kirigaya'
|
||||||
};
|
};
|
||||||
webview.postMessage({ command: 'connect', data: connectResult });
|
|
||||||
|
return connectResult;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
|
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
|
||||||
@ -61,6 +63,7 @@ export async function connectService(
|
|||||||
code: 500,
|
code: 500,
|
||||||
msg: errorMsg
|
msg: errorMsg
|
||||||
};
|
};
|
||||||
webview.postMessage({ command: 'connect', data: connectResult });
|
|
||||||
|
return connectResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
28
service/src/panel/panel.controller.ts
Normal file
28
service/src/panel/panel.controller.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Controller, RequestClientType } from "../common";
|
||||||
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { loadTabSaveConfig, saveTabSaveConfig } from "./panel.service";
|
||||||
|
|
||||||
|
export class PanelController {
|
||||||
|
@Controller('panel/save')
|
||||||
|
async savePanel(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
const serverInfo = client?.getServerVersion();
|
||||||
|
saveTabSaveConfig(serverInfo, data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: 'Settings saved successfully'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Controller('panel/load')
|
||||||
|
async loadPanel(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
const serverInfo = client?.getServerVersion();
|
||||||
|
const config = loadTabSaveConfig(serverInfo);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
12
service/src/panel/panel.dto.ts
Normal file
12
service/src/panel/panel.dto.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface SaveTabItem {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
type: string;
|
||||||
|
componentIndex: number;
|
||||||
|
storage: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveTab {
|
||||||
|
tabs: SaveTabItem[]
|
||||||
|
currentIndex: number
|
||||||
|
}
|
68
service/src/panel/panel.service.ts
Normal file
68
service/src/panel/panel.service.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { VSCODE_WORKSPACE } from '../hook/setting';
|
||||||
|
import { IServerVersion } from '../mcp/client.dto';
|
||||||
|
import { SaveTab } from './panel.dto';
|
||||||
|
import { IConfig } from '../setting/setting.dto';
|
||||||
|
|
||||||
|
const DEFAULT_TABS: SaveTab = {
|
||||||
|
tabs: [],
|
||||||
|
currentIndex: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTabSavePath(serverInfo: IServerVersion) {
|
||||||
|
const { name = 'untitle', version = '0.0.0' } = serverInfo || {};
|
||||||
|
const tabSaveName = `tabs.${name}.json`;
|
||||||
|
|
||||||
|
// 如果是 vscode 插件下,则修改为 ~/.vscode/openmcp.json
|
||||||
|
if (VSCODE_WORKSPACE) {
|
||||||
|
// 在 VSCode 插件环境下
|
||||||
|
const configDir = path.join(VSCODE_WORKSPACE, '.vscode');
|
||||||
|
if (!fs.existsSync(configDir)) {
|
||||||
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
|
}
|
||||||
|
return path.join(configDir, tabSaveName);
|
||||||
|
}
|
||||||
|
return tabSaveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSaveTabConfig(serverInfo: IServerVersion): SaveTab {
|
||||||
|
const configPath = getTabSavePath(serverInfo);
|
||||||
|
const configDir = path.dirname(configPath);
|
||||||
|
|
||||||
|
// 确保配置目录存在
|
||||||
|
if (configDir && !fs.existsSync(configDir)) {
|
||||||
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入默认配置
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_TABS, null, 2), 'utf-8');
|
||||||
|
return DEFAULT_TABS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadTabSaveConfig(serverInfo: IServerVersion): SaveTab {
|
||||||
|
const tabSavePath = getTabSavePath(serverInfo);
|
||||||
|
|
||||||
|
if (!fs.existsSync(tabSavePath)) {
|
||||||
|
return createSaveTabConfig(serverInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const configData = fs.readFileSync(tabSavePath, 'utf-8');
|
||||||
|
return JSON.parse(configData) as SaveTab;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading config file, creating new one:', error);
|
||||||
|
return createSaveTabConfig(serverInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveTabSaveConfig(serverInfo: IServerVersion, config: Partial<IConfig>): void {
|
||||||
|
const tabSavePath = getTabSavePath(serverInfo);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(tabSavePath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving config file:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
import { PostMessageble } from "../hook/adapter";
|
|
||||||
import { MCPClient } from "../hook/client";
|
|
||||||
|
|
||||||
|
|
||||||
export async function lookupEnvVarService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
|
|
||||||
try {
|
|
||||||
const { keys } = data;
|
|
||||||
|
|
||||||
const values = keys.map((key: string) => process.env[key] || '');
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'lookup-env-var',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: values
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'lookup-env-var',
|
|
||||||
data: {
|
|
||||||
code: 500,
|
|
||||||
msg: `Failed to lookup env vars: ${(error as Error).message}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
import { OpenAI } from 'openai';
|
|
||||||
import { MCPClient } from '../hook/client';
|
|
||||||
import { PostMessageble } from '../hook/adapter';
|
|
||||||
import { postProcessMessages } from '../hook/llm';
|
|
||||||
|
|
||||||
let currentStream: AsyncIterable<any> | null = null;
|
|
||||||
|
|
||||||
export async function chatCompletionService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'ping', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let { baseURL, apiKey, model, messages, temperature, tools = [] } = data;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const client = new OpenAI({
|
|
||||||
baseURL,
|
|
||||||
apiKey
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tools.length === 0) {
|
|
||||||
tools = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
postProcessMessages(messages);
|
|
||||||
|
|
||||||
const stream = await client.chat.completions.create({
|
|
||||||
model,
|
|
||||||
messages,
|
|
||||||
temperature,
|
|
||||||
tools,
|
|
||||||
tool_choice: 'auto',
|
|
||||||
web_search_options: {},
|
|
||||||
stream: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 存储当前的流式传输对象
|
|
||||||
currentStream = stream;
|
|
||||||
|
|
||||||
// 流式传输结果
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
if (!currentStream) {
|
|
||||||
// 如果流被中止,则停止循环
|
|
||||||
// TODO: 为每一个标签页设置不同的 currentStream 管理器
|
|
||||||
stream.controller.abort();
|
|
||||||
// 传输结束
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'llm/chat/completions/done',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: {
|
|
||||||
success: true,
|
|
||||||
stage: 'abort'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunk.choices) {
|
|
||||||
const chunkResult = {
|
|
||||||
code: 200,
|
|
||||||
msg: {
|
|
||||||
chunk
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'llm/chat/completions/chunk',
|
|
||||||
data: chunkResult
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 传输结束
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'llm/chat/completions/done',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: {
|
|
||||||
success: true,
|
|
||||||
stage: 'done'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'llm/chat/completions/chunk',
|
|
||||||
data: {
|
|
||||||
code: 500,
|
|
||||||
msg: `OpenAI API error: ${(error as Error).message}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理中止消息的函数
|
|
||||||
export function abortMessageService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
|
|
||||||
if (currentStream) {
|
|
||||||
// 标记流已中止
|
|
||||||
currentStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'llm/chat/completions/abort',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: {
|
|
||||||
success: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
import { PostMessageble } from "../hook/adapter";
|
|
||||||
import { MCPClient } from "../hook/client";
|
|
||||||
|
|
||||||
// ==================== 接口定义 ====================
|
|
||||||
export interface GetPromptOption {
|
|
||||||
promptId: string;
|
|
||||||
args?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReadResourceOption {
|
|
||||||
resourceUri: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CallToolOption {
|
|
||||||
toolName: string;
|
|
||||||
toolArgs: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 函数实现 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 列出所有 prompts
|
|
||||||
*/
|
|
||||||
export async function listPromptsService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'prompts/list', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const prompts = await client.listPrompts();
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: prompts
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'prompts/list', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'prompts/list', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取特定 prompt
|
|
||||||
*/
|
|
||||||
export async function getPromptService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
option: GetPromptOption,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'prompts/get', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const prompt = await client.getPrompt(option.promptId, option.args || {});
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: prompt
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'prompts/get', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'prompts/get', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 列出所有resources
|
|
||||||
*/
|
|
||||||
export async function listResourcesService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/list', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resources = await client.listResources();
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: resources
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/list', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/list', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 列出所有resources
|
|
||||||
*/
|
|
||||||
export async function listResourceTemplatesService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/templates/list', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resources = await client.listResourceTemplates();
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: resources
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/templates/list', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/templates/list', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 读取特定resource
|
|
||||||
*/
|
|
||||||
export async function readResourceService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
option: ReadResourceOption,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/read', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resource = await client.readResource(option.resourceUri);
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: resource
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/read', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'resources/read', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取工具列表
|
|
||||||
*/
|
|
||||||
export async function listToolsService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'tools/list', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tools = await client.listTools();
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: tools
|
|
||||||
};
|
|
||||||
|
|
||||||
webview.postMessage({ command: 'tools/list', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'tools/list', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 调用工具
|
|
||||||
*/
|
|
||||||
export async function callToolService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
option: CallToolOption,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'tools/call', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const toolResult = await client.callTool({
|
|
||||||
name: option.toolName,
|
|
||||||
arguments: option.toolArgs
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: toolResult
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'tools/call', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'tools/call', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getServerVersionService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg:'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'server/version', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const version = client.getServerVersion();
|
|
||||||
const result = {
|
|
||||||
code: 200,
|
|
||||||
msg: version
|
|
||||||
};
|
|
||||||
webview.postMessage({ command:'server/version', data: result });
|
|
||||||
} catch (error) {
|
|
||||||
const result = {
|
|
||||||
code: 500,
|
|
||||||
msg: (error as any).toString()
|
|
||||||
};
|
|
||||||
webview.postMessage({ command:'server/version', data: result });
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
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: {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
import { PostMessageble } from '../hook/adapter';
|
|
||||||
import { loadConfig, loadTabSaveConfig, saveConfig, saveTabSaveConfig } from '../hook/setting';
|
|
||||||
import { MCPClient } from '../hook/client';
|
|
||||||
|
|
||||||
export async function panelSaveService(client: MCPClient | undefined, data: any, webview: PostMessageble) {
|
|
||||||
try {
|
|
||||||
// 保存配置
|
|
||||||
const serverInfo = client?.getServerVersion();
|
|
||||||
saveTabSaveConfig(serverInfo, data);
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'panel/save',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: 'Settings saved successfully'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'panel/save',
|
|
||||||
data: {
|
|
||||||
code: 500,
|
|
||||||
msg: `Failed to save settings: ${(error as Error).message}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function panelLoadService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
// 加载配置
|
|
||||||
const serverInfo = client?.getServerVersion();
|
|
||||||
const config = loadTabSaveConfig(serverInfo);
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'panel/load',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: config // 直接返回配置对象
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'panel/load',
|
|
||||||
data: {
|
|
||||||
code: 500,
|
|
||||||
msg: `Failed to load settings: ${(error as Error).message}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
import { PostMessageble } from '../hook/adapter';
|
|
||||||
import { loadConfig, saveConfig } from '../hook/setting';
|
|
||||||
import { MCPClient } from '../hook/client';
|
|
||||||
|
|
||||||
export async function settingSaveService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
// 保存配置
|
|
||||||
saveConfig(data);
|
|
||||||
console.log('Settings saved successfully');
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'setting/save',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: 'Settings saved successfully'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Setting save failed:', error);
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'setting/save',
|
|
||||||
data: {
|
|
||||||
code: 500,
|
|
||||||
msg: `Failed to save settings: ${(error as Error).message}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function settingLoadService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
// 加载配置
|
|
||||||
const config = loadConfig();
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'setting/load',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: config // 直接返回配置对象
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'setting/load',
|
|
||||||
data: {
|
|
||||||
code: 500,
|
|
||||||
msg: `Failed to load settings: ${(error as Error).message}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { PostMessageble } from "../hook/adapter";
|
|
||||||
import { MCPClient } from "../hook/client";
|
|
||||||
|
|
||||||
export function pingService(
|
|
||||||
client: MCPClient | undefined,
|
|
||||||
data: any,
|
|
||||||
webview: PostMessageble
|
|
||||||
) {
|
|
||||||
if (!client) {
|
|
||||||
const connectResult = {
|
|
||||||
code: 501,
|
|
||||||
msg: 'mcp client 尚未连接'
|
|
||||||
};
|
|
||||||
webview.postMessage({ command: 'ping', data: connectResult });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
webview.postMessage({
|
|
||||||
command: 'ping',
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
msg: {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
27
service/src/setting/setting.controller.ts
Normal file
27
service/src/setting/setting.controller.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Controller, RequestClientType } from "../common";
|
||||||
|
import { PostMessageble } from "../hook/adapter";
|
||||||
|
import { loadSetting, saveSetting } from "./setting.service";
|
||||||
|
|
||||||
|
export class SettingController {
|
||||||
|
|
||||||
|
@Controller('setting/save')
|
||||||
|
async saveSetting(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
saveSetting(data);
|
||||||
|
console.log('Settings saved successfully');
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: 'Settings saved successfully'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('setting/load')
|
||||||
|
async loadSetting(client: RequestClientType, data: any, webview: PostMessageble) {
|
||||||
|
const config = loadSetting();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
msg: config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
service/src/setting/setting.dto.ts
Normal file
4
service/src/setting/setting.dto.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface IConfig {
|
||||||
|
MODEL_INDEX: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
77
service/src/setting/setting.service.ts
Normal file
77
service/src/setting/setting.service.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { VSCODE_WORKSPACE } from '../hook/setting';
|
||||||
|
import { IConfig } from './setting.dto';
|
||||||
|
import { llms } from '../hook/llm';
|
||||||
|
|
||||||
|
function getConfigurationPath() {
|
||||||
|
// 如果是 vscode 插件下,则修改为 ~/.openmcp/config.json
|
||||||
|
if (VSCODE_WORKSPACE) {
|
||||||
|
// 在 VSCode 插件环境下
|
||||||
|
const homeDir = os.homedir();
|
||||||
|
const configDir = path.join(homeDir, '.openmcp');
|
||||||
|
if (!fs.existsSync(configDir)) {
|
||||||
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
|
}
|
||||||
|
return path.join(configDir, 'setting.json');
|
||||||
|
}
|
||||||
|
return 'setting.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultLanguage() {
|
||||||
|
if (process.env.VSCODE_PID) {
|
||||||
|
// TODO: 获取 vscode 内部的语言
|
||||||
|
|
||||||
|
}
|
||||||
|
return 'zh';
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: IConfig = {
|
||||||
|
MODEL_INDEX: 0,
|
||||||
|
LLM_INFO: llms,
|
||||||
|
LANG: getDefaultLanguage()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function createConfig(): IConfig {
|
||||||
|
const configPath = getConfigurationPath();
|
||||||
|
const configDir = path.dirname(configPath);
|
||||||
|
|
||||||
|
// 确保配置目录存在
|
||||||
|
if (configDir && !fs.existsSync(configDir)) {
|
||||||
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入默认配置
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2), 'utf-8');
|
||||||
|
return DEFAULT_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadSetting(): IConfig {
|
||||||
|
const configPath = getConfigurationPath();
|
||||||
|
|
||||||
|
if (!fs.existsSync(configPath)) {
|
||||||
|
return createConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const configData = fs.readFileSync(configPath, 'utf-8');
|
||||||
|
return JSON.parse(configData) as IConfig;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading config file, creating new one:', error);
|
||||||
|
return createConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveSetting(config: Partial<IConfig>): void {
|
||||||
|
const configPath = getConfigurationPath();
|
||||||
|
console.log('save to ' + configPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving config file:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user