commit
d04c04f574
@ -3,6 +3,7 @@
|
||||
## [main] 0.1.6
|
||||
- 针对 0.1.5 无法在 Windows 启动的紧急修复。
|
||||
- 修复环境变量中添加 token 失效的问题。
|
||||
- 优化工具展示的页面布局。
|
||||
|
||||
## [main] 0.1.5
|
||||
- 修复 gemini 获取模型列表时存在 models 前缀的问题
|
||||
|
42
package-lock.json
generated
42
package-lock.json
generated
@ -2698,6 +2698,16 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/xml2js": {
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||
@ -9401,6 +9411,12 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
@ -11392,6 +11408,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
@ -11579,7 +11617,8 @@
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^11.1.0",
|
||||
"vue-router": "^4.5.0"
|
||||
"vue-router": "^4.5.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
@ -11593,6 +11632,7 @@
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/babel-plugin-jsx": "^1.4.0",
|
||||
"@vue/devtools-core": "^7.7.6",
|
||||
|
@ -33,7 +33,8 @@
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^11.1.0",
|
||||
"vue-router": "^4.5.0"
|
||||
"vue-router": "^4.5.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
@ -47,6 +48,7 @@
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/babel-plugin-jsx": "^1.4.0",
|
||||
"@vue/devtools-core": "^7.7.6",
|
||||
|
@ -228,3 +228,4 @@ a {
|
||||
.ͼo .cm-gutters {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { ToolCallContent, ToolItem } from "@/hook/type";
|
||||
import type { InputSchema, ToolCallContent, ToolItem } from "@/hook/type";
|
||||
import { type Ref, ref } from "vue";
|
||||
|
||||
import type { OpenAI } from 'openai';
|
||||
@ -16,6 +16,7 @@ export enum MessageState {
|
||||
Success = 'success',
|
||||
ParseJsonError = 'parse json error',
|
||||
NoToolFunction = 'no tool function',
|
||||
InvalidXml = 'invalid xml',
|
||||
}
|
||||
|
||||
export interface IExtraInfo {
|
||||
@ -23,6 +24,7 @@ export interface IExtraInfo {
|
||||
state: MessageState,
|
||||
serverName: string,
|
||||
usage?: ChatCompletionChunk['usage'];
|
||||
enableXmlWrapper: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@ -52,7 +54,7 @@ export interface EnableToolItem {
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
inputSchema: any;
|
||||
inputSchema: InputSchema;
|
||||
}
|
||||
|
||||
export interface ChatSetting {
|
||||
@ -108,7 +110,7 @@ export interface IToolRenderMessage {
|
||||
|
||||
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
|
||||
|
||||
export function getToolSchema(enableTools: EnableToolItem[]) {
|
||||
export function getToolSchema(enableTools: EnableToolItem[]): any[] {
|
||||
const toolsSchema = [];
|
||||
for (let i = 0; i < enableTools.length; i++) {
|
||||
const enableTool = enableTools[i];
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('context-length')" placement="top">
|
||||
<el-tooltip :content="t('context-length')" placement="top" effect="light">
|
||||
<div class="setting-button width-30" @click="showContextLengthDialog = true">
|
||||
<span class="iconfont icon-length"></span>
|
||||
<span class="value-badge">{{ tabStorage.settings.contextLength }}</span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('choose-model')" placement="top">
|
||||
<el-tooltip :content="t('choose-model')" placement="top" effect="light">
|
||||
<div class="setting-button" @click="showModelDialog = true">
|
||||
<span class="iconfont icon-model">
|
||||
{{ currentServerName }}/{{ currentModelName }}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('parallel-tool-calls')" placement="top">
|
||||
<el-tooltip :content="t('parallel-tool-calls')" placement="top" effect="light">
|
||||
<div class="setting-button" :class="{ 'active': tabStorage.settings.parallelToolCalls }" size="small"
|
||||
@click="toggle">
|
||||
<span class="iconfont icon-parallel"></span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('prompts')" placement="top">
|
||||
<el-tooltip :content="t('prompts')" placement="top" effect="light">
|
||||
<div class="setting-button" @click="showChoosePrompt = true; saveCursorPosition();">
|
||||
<span class="iconfont icon-chat"></span>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('resources')" placement="top">
|
||||
<el-tooltip :content="t('resources')" placement="top" effect="light">
|
||||
<div class="setting-button" @click="showChooseResource = true; saveCursorPosition();">
|
||||
<span class="iconfont icon-file"></span>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('system-prompt')" placement="top">
|
||||
<el-tooltip :content="t('system-prompt')" placement="top" effect="light">
|
||||
<div class="setting-button" :class="{ 'active': hasSystemPrompt }" size="small"
|
||||
@click="showSystemPromptDialog = true">
|
||||
<span class="iconfont icon-prompt"></span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('temperature-parameter')" placement="top">
|
||||
<el-tooltip :content="t('temperature-parameter')" placement="top" effect="light">
|
||||
<div class="setting-button width-30" @click="showTemperatureSlider = true">
|
||||
<span class="iconfont icon-temperature"></span>
|
||||
<span class="value-badge">{{ tabStorage.settings.temperature.toFixed(1) }}</span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('tool-use')" placement="top">
|
||||
<el-tooltip :content="t('tool-use')" placement="top" effect="light">
|
||||
<div class="setting-button" :class="{ 'active': availableToolsNum > 0 }" size="small" @click="toggleTools">
|
||||
<span class="iconfont icon-tool badge-outer">
|
||||
<span class="badge-inner">
|
||||
@ -10,7 +10,19 @@
|
||||
|
||||
</el-tooltip>
|
||||
|
||||
<el-dialog v-model="showToolsDialog" title="工具管理" width="800px">
|
||||
<el-dialog v-model="showToolsDialog" width="800px">
|
||||
|
||||
<template #header>
|
||||
<div>
|
||||
<span>{{ t('tool-manage') }}</span>
|
||||
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
|
||||
<span class="xml-tag" :class="{
|
||||
'active': tabStorage.settings.enableXmlWrapper
|
||||
}" @click="tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper">xml</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="tools-dialog-container">
|
||||
<el-scrollbar height="400px" class="tools-list">
|
||||
<div v-for="(tool, index) in tabStorage.settings.enableTools" :key="index" class="tool-item">
|
||||
@ -23,12 +35,15 @@
|
||||
</el-scrollbar>
|
||||
|
||||
<el-scrollbar height="400px" class="schema-viewer">
|
||||
<div v-html="activeToolsSchemaHTML"></div>
|
||||
<!-- 如果激活 xml 指令包裹,则展示对应的 prompt -->
|
||||
<div v-if="tabStorage.settings.enableXmlWrapper" v-html="activeToolsXmlPrompt" />
|
||||
<!-- 如果是普通模式,则展示普通的工具列表 -->
|
||||
<div v-else v-html="activeToolsSchemaHTML" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="enableAllTools">激活所有工具</el-button>
|
||||
<el-button type="danger" @click="disableAllTools">禁用所有工具</el-button>
|
||||
<el-button type="primary" @click="enableAllTools">{{ t('enable-all-tools') }}</el-button>
|
||||
<el-button type="danger" @click="disableAllTools">{{ t('disable-all-tools') }}</el-button>
|
||||
<el-button type="primary" @click="showToolsDialog = false">{{ t("cancel") }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -40,6 +55,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat';
|
||||
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
import { toolSchemaToPromptDescription } from '../../core/xml-wrapper';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -48,26 +64,34 @@ const tabStorage = inject('tabStorage') as ChatStorage;
|
||||
const showToolsDialog = ref(false);
|
||||
|
||||
const availableToolsNum = computed(() => {
|
||||
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
|
||||
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
|
||||
});
|
||||
|
||||
// 修改 toggleTools 方法
|
||||
const toggleTools = () => {
|
||||
showToolsDialog.value = true;
|
||||
showToolsDialog.value = true;
|
||||
};
|
||||
|
||||
const activeToolsSchemaHTML = computed(() => {
|
||||
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
|
||||
const jsonString = JSON.stringify(toolsSchema, null, 2);
|
||||
|
||||
return markdownToHtml(
|
||||
"```json\n" + jsonString + "\n```"
|
||||
);
|
||||
const activeToolsSchemaHTML = computed(() => {
|
||||
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
|
||||
const jsonString = JSON.stringify(toolsSchema, null, 2);
|
||||
|
||||
return markdownToHtml(
|
||||
"```json\n" + jsonString + "\n```"
|
||||
);
|
||||
});
|
||||
|
||||
const activeToolsXmlPrompt = computed(() => {
|
||||
const prompt = toolSchemaToPromptDescription(tabStorage.settings.enableTools);
|
||||
return markdownToHtml(
|
||||
"```markdown\n" + prompt + "\n```"
|
||||
);
|
||||
});
|
||||
|
||||
// 新增方法 - 激活所有工具
|
||||
const enableAllTools = () => {
|
||||
tabStorage.settings.enableTools.forEach(tool => {
|
||||
tabStorage.settings.enableTools.forEach(tool => {
|
||||
tool.enabled = true;
|
||||
});
|
||||
};
|
||||
@ -121,4 +145,22 @@ onMounted(async () => {
|
||||
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style scoped>
|
||||
.xml-tag {
|
||||
margin-left: 10px;
|
||||
border-radius: .5em;
|
||||
padding: 2px 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
color: black;
|
||||
background-color: var(--main-color);
|
||||
opacity: 0.3;
|
||||
transition: var(--animation-3s);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xml-tag.active {
|
||||
opacity: 1;
|
||||
transition: var(--animation-3s);
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('websearch')" placement="top">
|
||||
<el-tooltip :content="t('websearch')" placement="top" effect="light">
|
||||
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableWebSearch }" size="small"
|
||||
@click="toggleWebSearch">
|
||||
<span class="iconfont icon-web"></span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="t('enable-xml-wrapper')" placement="top">
|
||||
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
|
||||
<div class="setting-button" :class="{ 'active': tabStorage.settings.enableXmlWrapper }" size="small"
|
||||
@click="toggle">
|
||||
<span class="iconfont icon-suffix-xml"></span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip :content="props.messages[0].content.text" placement="top">
|
||||
<el-tooltip :content="props.messages[0].content.text" placement="top" effect="light">
|
||||
<span class="chat-prompt-item" contenteditable="false">
|
||||
<span class="iconfont icon-chat"></span>
|
||||
<span class="real-text">{{ props.messages[0].content.text }}</span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-tooltip placement="top">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>
|
||||
<div class="resource-chat-item-tooltip">
|
||||
<div v-for="(item, index) of toolRenderItems" :key="index">
|
||||
|
@ -2,7 +2,14 @@ import type { ToolCallContent, ToolCallResponse } from "@/hook/type";
|
||||
import { MessageState, type ToolCall } from "../chat-box/chat";
|
||||
import { mcpClientAdapter } from "@/views/connect/core";
|
||||
import type { BasicLlmDescription } from "@/views/setting/llm";
|
||||
import { redLog } from "@/views/setting/util";
|
||||
import type OpenAI from "openai";
|
||||
|
||||
export interface TaskLoopChatOption {
|
||||
id?: string
|
||||
proxyServer?: string
|
||||
enableXmlWrapper?: boolean
|
||||
}
|
||||
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
|
||||
|
||||
export interface ToolCallResult {
|
||||
state: MessageState;
|
||||
@ -60,7 +67,7 @@ function deserializeToolCallResponse(toolArgs: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleToolResponse(toolResponse: ToolCallResponse) {
|
||||
export function handleToolResponse(toolResponse: ToolCallResponse) {
|
||||
if (typeof toolResponse === 'string') {
|
||||
|
||||
return {
|
||||
@ -98,36 +105,67 @@ function parseErrorObject(error: any): string {
|
||||
}
|
||||
}
|
||||
|
||||
function grokIndexAdapter(toolCall: ToolCall, callId2Index: Map<string, number>): IToolCallIndex {
|
||||
|
||||
/**
|
||||
* @description 将工具调用的ID映射为索引
|
||||
* @param toolCall 工具调用对象
|
||||
* @param callId2Index ID到索引的映射表
|
||||
* @returns 映射后的索引值
|
||||
*/
|
||||
export function idAsIndexAdapter(toolCall: ToolCall | string, callId2Index: Map<string, number>): IToolCallIndex {
|
||||
// grok 采用 id 作为 index,需要将 id 映射到 zero-based 的 index
|
||||
if (!toolCall.id) {
|
||||
const id = typeof toolCall === 'string' ? toolCall : toolCall.id;
|
||||
if (!id) {
|
||||
return 0;
|
||||
}
|
||||
if (!callId2Index.has(toolCall.id)) {
|
||||
callId2Index.set(toolCall.id, callId2Index.size);
|
||||
if (!callId2Index.has(id)) {
|
||||
callId2Index.set(id, callId2Index.size);
|
||||
}
|
||||
return callId2Index.get(toolCall.id)!;
|
||||
return callId2Index.get(id)!;
|
||||
}
|
||||
|
||||
function geminiIndexAdapter(toolCall: ToolCall): IToolCallIndex {
|
||||
|
||||
/**
|
||||
* @description 单次调用的索引适配器(暂未实现)
|
||||
* @param toolCall 工具调用对象
|
||||
* @returns 固定返回0
|
||||
*/
|
||||
export function singleCallIndexAdapter(toolCall: ToolCall): IToolCallIndex {
|
||||
// TODO: 等待后续支持
|
||||
return 0;
|
||||
}
|
||||
|
||||
function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
|
||||
/**
|
||||
* @description
|
||||
* @param toolCall
|
||||
* @returns
|
||||
*/
|
||||
export function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
|
||||
return toolCall.index || 0;
|
||||
}
|
||||
|
||||
export function getToolCallIndexAdapter(llm: BasicLlmDescription) {
|
||||
export function getToolCallIndexAdapter(llm: BasicLlmDescription, chatData: ChatCompletionCreateParamsBase) {
|
||||
|
||||
// 如果是 xml 模式,那么 index adapter 必须是 idAsIndexAdapter
|
||||
|
||||
if (chatData.enableXmlWrapper) {
|
||||
const callId2Index = new Map<string, number>();
|
||||
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
|
||||
}
|
||||
|
||||
if (llm.userModel.startsWith('gemini')) {
|
||||
return geminiIndexAdapter;
|
||||
return singleCallIndexAdapter;
|
||||
}
|
||||
|
||||
if (llm.userModel.startsWith('grok')) {
|
||||
const callId2Index = new Map<string, number>();
|
||||
return (toolCall: ToolCall) => grokIndexAdapter(toolCall, callId2Index);
|
||||
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
|
||||
}
|
||||
|
||||
return defaultIndexAdapter;
|
||||
}
|
||||
|
||||
export function getIdAsIndexAdapter() {
|
||||
const callId2Index = new Map<string, number>();
|
||||
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable */
|
||||
import { ref, type Ref } from "vue";
|
||||
import { type ToolCall, type ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
|
||||
import { type ToolCall, type ChatStorage, getToolSchema, MessageState, type ChatMessage, type ChatSetting, type EnableToolItem } from "../chat-box/chat";
|
||||
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge";
|
||||
import type { OpenAI } from 'openai';
|
||||
import { llmManager, llms, type BasicLlmDescription } from "@/views/setting/llm";
|
||||
@ -13,6 +13,7 @@ import { mcpSetting } from "@/hook/mcp";
|
||||
import { mcpClientAdapter } from "@/views/connect/core";
|
||||
import type { ToolItem } from "@/hook/type";
|
||||
import chalk from 'chalk';
|
||||
import { getXmlWrapperPrompt, getToolCallFromXmlString, getXmlsFromString, handleXmlWrapperToolcall, toNormaliseToolcall, getXmlResultPrompt } from "./xml-wrapper";
|
||||
|
||||
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
||||
export interface TaskLoopChatOption {
|
||||
@ -90,14 +91,25 @@ export class TaskLoop {
|
||||
this.bridge = useMessageBridge();
|
||||
}
|
||||
|
||||
private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
|
||||
/**
|
||||
* @description 处理 streaming 输出的每一个分块的 content 部分
|
||||
* @param chunk
|
||||
* @param chatData
|
||||
*/
|
||||
private handleChunkDeltaContent(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase) {
|
||||
const content = chunk.choices[0]?.delta?.content || '';
|
||||
if (content) {
|
||||
this.streamingContent.value += content;
|
||||
}
|
||||
}
|
||||
|
||||
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
|
||||
/**
|
||||
* @description 处理 streaming 输出的每一个 chunk 的 tool_calls 部分
|
||||
* @param chunk
|
||||
* @param chatData
|
||||
* @param toolcallIndexAdapter
|
||||
*/
|
||||
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
|
||||
const toolCall = chunk.choices[0]?.delta?.tool_calls?.[0];
|
||||
|
||||
if (toolCall) {
|
||||
@ -154,12 +166,12 @@ export class TaskLoop {
|
||||
|
||||
// 处理增量的 content 和 tool_calls
|
||||
if (chatData.enableXmlWrapper) {
|
||||
this.handleChunkDeltaContent(chunk);
|
||||
this.handleChunkDeltaContent(chunk, chatData);
|
||||
// no tool call in enableXmlWrapper
|
||||
this.handleChunkUsage(chunk);
|
||||
} else {
|
||||
this.handleChunkDeltaContent(chunk);
|
||||
this.handleChunkDeltaToolCalls(chunk, toolcallIndexAdapter);
|
||||
this.handleChunkDeltaContent(chunk, chatData);
|
||||
this.handleChunkDeltaToolCalls(chunk, chatData, toolcallIndexAdapter);
|
||||
this.handleChunkUsage(chunk);
|
||||
}
|
||||
|
||||
@ -218,24 +230,35 @@ export class TaskLoop {
|
||||
|
||||
const model = this.getLlmConfig().userModel;
|
||||
const temperature = tabStorage.settings.temperature;
|
||||
const tools = getToolSchema(tabStorage.settings.enableTools);
|
||||
const parallelToolCalls = tabStorage.settings.parallelToolCalls;
|
||||
const proxyServer = mcpSetting.proxyServer || '';
|
||||
|
||||
// 如果是 xml 模式,则 tools 为空
|
||||
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
|
||||
const tools = enableXmlWrapper ? []: getToolSchema(tabStorage.settings.enableTools);
|
||||
|
||||
const userMessages = [];
|
||||
|
||||
// 尝试获取 system prompt,在 api 模式下,systemPrompt 就是目标提词
|
||||
// 但是在 UI 模式下,systemPrompt 只是一个 index,需要从后端数据库中获取真实 prompt
|
||||
if (tabStorage.settings.systemPrompt) {
|
||||
const prompt = getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
|
||||
|
||||
userMessages.push({
|
||||
role: 'system',
|
||||
content: prompt
|
||||
});
|
||||
let prompt = '';
|
||||
|
||||
// 如果存在系统提示词,则从数据库中获取对应的数据
|
||||
if (tabStorage.settings.systemPrompt) {
|
||||
prompt += getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
|
||||
}
|
||||
|
||||
// 如果是 xml 模式,则在开头注入 xml
|
||||
if (enableXmlWrapper) {
|
||||
prompt += getXmlWrapperPrompt(tabStorage.settings.enableTools, tabStorage);
|
||||
}
|
||||
|
||||
userMessages.push({
|
||||
role: 'system',
|
||||
content: prompt
|
||||
});
|
||||
|
||||
// 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息
|
||||
const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength);
|
||||
userMessages.push(...loadMessages);
|
||||
@ -253,7 +276,7 @@ export class TaskLoop {
|
||||
parallelToolCalls,
|
||||
messages: userMessages,
|
||||
proxyServer,
|
||||
enableXmlWrapper
|
||||
enableXmlWrapper,
|
||||
} as ChatCompletionCreateParamsBase;
|
||||
|
||||
return chatData;
|
||||
@ -474,6 +497,7 @@ export class TaskLoop {
|
||||
// 等待连接完成
|
||||
await this.nodejsStatus.connectionFut;
|
||||
}
|
||||
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
|
||||
|
||||
// 添加目前的消息
|
||||
tabStorage.messages.push({
|
||||
@ -482,7 +506,8 @@ export class TaskLoop {
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: MessageState.Success,
|
||||
serverName: this.getLlmConfig().id || 'unknown'
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
enableXmlWrapper
|
||||
}
|
||||
});
|
||||
|
||||
@ -511,7 +536,7 @@ export class TaskLoop {
|
||||
|
||||
this.currentChatId = chatData.id!;
|
||||
const llm = this.getLlmConfig();
|
||||
const toolcallIndexAdapter = getToolCallIndexAdapter(llm);
|
||||
const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData);
|
||||
|
||||
// 发送请求
|
||||
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
|
||||
@ -526,7 +551,8 @@ export class TaskLoop {
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: MessageState.Success,
|
||||
serverName: this.getLlmConfig().id || 'unknown'
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
enableXmlWrapper
|
||||
}
|
||||
});
|
||||
|
||||
@ -563,7 +589,8 @@ export class TaskLoop {
|
||||
created: Date.now(),
|
||||
state: toolCallResult.state,
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
usage: undefined
|
||||
usage: undefined,
|
||||
enableXmlWrapper
|
||||
}
|
||||
});
|
||||
break;
|
||||
@ -578,7 +605,8 @@ export class TaskLoop {
|
||||
created: Date.now(),
|
||||
state: toolCallResult.state,
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
usage: this.completionUsage
|
||||
usage: this.completionUsage,
|
||||
enableXmlWrapper
|
||||
}
|
||||
});
|
||||
} else if (toolCallResult.state === MessageState.ToolCall) {
|
||||
@ -592,7 +620,8 @@ export class TaskLoop {
|
||||
created: Date.now(),
|
||||
state: toolCallResult.state,
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
usage: this.completionUsage
|
||||
usage: this.completionUsage,
|
||||
enableXmlWrapper
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -606,10 +635,96 @@ export class TaskLoop {
|
||||
created: Date.now(),
|
||||
state: MessageState.Success,
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
usage: this.completionUsage
|
||||
usage: this.completionUsage,
|
||||
enableXmlWrapper
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
// 如果 xml 模型,需要检查内部是否含有有效的 xml 进行调用
|
||||
if (tabStorage.settings.enableXmlWrapper) {
|
||||
const xmls = getXmlsFromString(this.streamingContent.value);
|
||||
if (xmls.length === 0) {
|
||||
// 没有 xml 了,说明对话结束
|
||||
break;
|
||||
}
|
||||
|
||||
// 使用 user 作为身份来承载 xml 调用的结果
|
||||
// 并且在 extra 内存储结构化信息
|
||||
const fakeUserMessage = {
|
||||
role: 'user',
|
||||
content: '',
|
||||
extraInfo: {
|
||||
created: Date.now(),
|
||||
state: MessageState.Success,
|
||||
serverName: this.getLlmConfig().id || 'unknown',
|
||||
usage: this.completionUsage,
|
||||
enableXmlWrapper,
|
||||
}
|
||||
} as ChatMessage;
|
||||
|
||||
// 有 xml 了,需要检查 xml 内部是否有有效的 xml 进行调用
|
||||
for (const xml of xmls) {
|
||||
const toolcall = await getToolCallFromXmlString(xml);
|
||||
|
||||
if (!toolcall) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// toolcall 事件
|
||||
// 此处使用的是 xml 使用的 toolcall,为了保持一致性,需要转换成 openai 标准下的 toolcall
|
||||
const normaliseToolcall = toNormaliseToolcall(toolcall, toolcallIndexAdapter);
|
||||
this.consumeToolCalls(normaliseToolcall);
|
||||
|
||||
// 调用 XML 调用,其实可以考虑后续把这个循环改成 Promise.race
|
||||
const toolCallResult = await handleXmlWrapperToolcall(toolcall);
|
||||
|
||||
// toolcalled 事件
|
||||
// 因为是交付给后续进行统一消费的,所以此处的输出满足 openai 接口规范
|
||||
this.consumeToolCalleds(toolCallResult);
|
||||
|
||||
// XML 模式下只存在 assistant 和 user 这两个角色,因此,以 user 为身份来存储
|
||||
if (toolCallResult.state === MessageState.InvalidXml) {
|
||||
// 如果是因为解析 XML 错误,则重新开始
|
||||
tabStorage.messages.pop();
|
||||
jsonParseErrorRetryCount ++;
|
||||
|
||||
redLog('解析 XML 错误 ' + normaliseToolcall?.function?.arguments);
|
||||
|
||||
// 如果因为 XML 错误而失败太多,就只能中断了
|
||||
if (jsonParseErrorRetryCount >= (this.taskOptions.maxJsonParseRetry || 3)) {
|
||||
|
||||
const prompt = getXmlResultPrompt(toolcall.callId, `解析 XML 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`);
|
||||
|
||||
fakeUserMessage.content += prompt;
|
||||
|
||||
break;
|
||||
}
|
||||
} else if (toolCallResult.state === MessageState.Success) {
|
||||
// TODO: xml 目前只支持 text 类型的回复
|
||||
const toolCallResultString = toolCallResult.content
|
||||
.filter(c => c.type === 'text')
|
||||
.map(c => c.text)
|
||||
.join('\n');
|
||||
|
||||
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
|
||||
|
||||
} else if (toolCallResult.state === MessageState.ToolCall) {
|
||||
// TODO: xml 目前只支持 text 类型的回复
|
||||
const toolCallResultString = toolCallResult.content
|
||||
.filter(c => c.type === 'text')
|
||||
.map(c => c.text)
|
||||
.join('\n');
|
||||
|
||||
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
|
||||
}
|
||||
}
|
||||
|
||||
tabStorage.messages.push(fakeUserMessage);
|
||||
|
||||
} else {
|
||||
// 普通对话直接结束
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
// 一些提示
|
||||
@ -622,4 +737,40 @@ export class TaskLoop {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async createStorage(settings?: ChatSetting): Promise<ChatStorage> {
|
||||
let {
|
||||
enableXmlWrapper = false,
|
||||
systemPrompt = '',
|
||||
temperature = 0.6,
|
||||
contextLength = 100,
|
||||
parallelToolCalls = true,
|
||||
enableWebSearch = false,
|
||||
enableTools = undefined,
|
||||
} = settings || {};
|
||||
|
||||
if (enableTools === undefined) {
|
||||
// 默认缺省的情况下使用全部工具
|
||||
const tools = await this.listTools();
|
||||
enableTools = tools.map(tool => ({
|
||||
...tool,
|
||||
enabled: true
|
||||
})) as EnableToolItem[];
|
||||
}
|
||||
|
||||
const _settings = {
|
||||
enableXmlWrapper,
|
||||
systemPrompt,
|
||||
temperature,
|
||||
contextLength,
|
||||
parallelToolCalls,
|
||||
enableTools,
|
||||
enableWebSearch
|
||||
} as ChatSetting;
|
||||
|
||||
return {
|
||||
messages: [],
|
||||
settings: _settings
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,23 @@
|
||||
import type { ToolItem } from "@/hook/type";
|
||||
import { parseString } from 'xml2js';
|
||||
import { MessageState, type ToolCall } from '../chat-box/chat';
|
||||
import { mcpClientAdapter } from '@/views/connect/core';
|
||||
import { handleToolResponse, type IToolCallIndex, type ToolCallResult } from './handle-tool-calls';
|
||||
import type { ChatStorage, EnableToolItem } from "../chat-box/chat";
|
||||
import type { ToolCallContent } from '@/hook/type';
|
||||
|
||||
export function toolSchemaToPromptDescription(tools: ToolItem[]) {
|
||||
export interface XmlToolCall {
|
||||
server: string;
|
||||
name: string;
|
||||
callId: string;
|
||||
parameters: Record<string, string>;
|
||||
}
|
||||
|
||||
|
||||
export function toolSchemaToPromptDescription(enableTools: EnableToolItem[]) {
|
||||
let prompt = '';
|
||||
|
||||
const tools = enableTools.filter(tool => tool.enabled);
|
||||
|
||||
// 无参数的工具
|
||||
const noParamTools = tools.filter(tool =>
|
||||
!tool.inputSchema.required || tool.inputSchema.required.length === 0
|
||||
@ -28,7 +43,7 @@ export function toolSchemaToPromptDescription(tools: ToolItem[]) {
|
||||
|
||||
Object.entries(tool.inputSchema.properties).forEach(([name, prop]) => {
|
||||
const required = tool.inputSchema.required?.includes(name) || false;
|
||||
prompt += `- \`${name}\`: ${prop.description || '无描述'} (${prop.type}) ${required ? '(required)' : ''}\n`;
|
||||
prompt += `- \`${name}\`: ${prop.description || 'No Description'} (${prop.type}) ${required ? '(required)' : ''}\n`;
|
||||
});
|
||||
|
||||
prompt += '\n';
|
||||
@ -38,15 +53,32 @@ export function toolSchemaToPromptDescription(tools: ToolItem[]) {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
export function getXmlWrapperPrompt(tools: ToolItem[]) {
|
||||
export function getXmlWrapperPrompt(tools: EnableToolItem[], tabStorage: ChatStorage) {
|
||||
|
||||
const toolPrompt = toolSchemaToPromptDescription(tools);
|
||||
const requests = [
|
||||
`ALWAYS analyze what function calls would be appropriate for the task`,
|
||||
`ALWAYS format your function call usage EXACTLY as specified in the schema`,
|
||||
`NEVER skip required parameters in function calls`,
|
||||
`NEVER invent functions that arent available to you`,
|
||||
`ALWAYS wait for function call execution results before continuing`,
|
||||
`After invoking a function, wait for the output in <function_results> tag and then continue with your response`,
|
||||
`NEVER mock or form <function_results> on your own, it will be provided to you after the execution`,
|
||||
];
|
||||
|
||||
if (!tabStorage.settings.parallelToolCalls) {
|
||||
requests.push(`NEVER invoke multiple functions in a single response`);
|
||||
}
|
||||
|
||||
const requestString = requests.map((text, index) => {
|
||||
return `${index + 1}. ${text}`;
|
||||
}).join('\n');
|
||||
|
||||
return `
|
||||
[Start Fresh Session from here]
|
||||
|
||||
<SYSTEM>
|
||||
You are SuperAssistant with the capabilities of invoke functions and make the best use of it during your assistance, a knowledgeable assistant focused on answering questions and providing information on any topics.
|
||||
You are OpenMCP Assistant with the capabilities of invoke functions and make the best use of it during your assistance, a knowledgeable assistant focused on answering questions and providing information on any topics.
|
||||
In this environment you have access to a set of tools you can use to answer the user's question.
|
||||
You have access to a set of functions you can use to answer the user's question. You do NOT currently have the ability to inspect files or interact with external resources, except by invoking the below functions.
|
||||
|
||||
@ -94,15 +126,7 @@ You can invoke one or more functions by writing a "<function_calls>" block like
|
||||
String and scalar parameters should be specified as is, while lists and objects should use JSON format. Note that spaces for string values are not stripped. The output is not expected to be valid XML and is parsed with regular expressions.
|
||||
|
||||
When a user makes a request:
|
||||
1. ALWAYS analyze what function calls would be appropriate for the task
|
||||
2. ALWAYS format your function call usage EXACTLY as specified in the schema
|
||||
3. NEVER skip required parameters in function calls
|
||||
4. NEVER invent functions that arent available to you
|
||||
5. ALWAYS wait for function call execution results before continuing
|
||||
6. After invoking a function, wait for the output in <function_results> tag and then continue with your response
|
||||
7. NEVER invoke multiple functions in a single response
|
||||
8. NEVER mock or form <function_results> on your own, it will be provided to you after the execution
|
||||
|
||||
${requestString}
|
||||
|
||||
Answer the user's request using the relevant tool(s), if they are available. Check that all the required parameters for each tool call are provided or can reasonably be inferred from context. IF there are no relevant tools or there are missing values for required parameters, ask the user to supply these values; otherwise proceed with the tool calls. If the user provides a specific value for a parameter (for example provided in quotes), make sure to use that value EXACTLY. DO NOT make up values for or ask about optional parameters. Carefully analyze descriptive terms in the request as they may indicate required parameter values that should be included even if not explicitly quoted.
|
||||
|
||||
@ -148,6 +172,126 @@ User Interaction Starts here:
|
||||
}
|
||||
|
||||
|
||||
export function getXmlWrapperPromptCn() {
|
||||
|
||||
export function getXmlResultPrompt(callId: string, result: string) {
|
||||
return `
|
||||
\`\`\`xml
|
||||
<function_results>
|
||||
<result call_id="${callId}">
|
||||
${result}
|
||||
</result>
|
||||
</function_results>
|
||||
\`\`\`
|
||||
`.trim() + '\n\n';
|
||||
}
|
||||
|
||||
export function getXmlsFromString(content: string) {
|
||||
const matches = content.matchAll(/```xml\n([\s\S]*?)\n```/g);
|
||||
return Array.from(matches).map(match => match[1].trim());
|
||||
}
|
||||
|
||||
|
||||
export async function getToolCallFromXmlString(xmlString: string): Promise<XmlToolCall | null> {
|
||||
try {
|
||||
const result = await new Promise<any>((resolve, reject) => {
|
||||
parseString(xmlString, (err, result) => {
|
||||
if (err) reject(err);
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
|
||||
if (!result?.function_calls?.invoke) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const invoke = result.function_calls.invoke[0].$;
|
||||
const parameters: Record<string, any> = {};
|
||||
|
||||
if (result.function_calls.invoke[0].parameter) {
|
||||
result.function_calls.invoke[0].parameter.forEach((param: any) => {
|
||||
const name = param.$.name as string;
|
||||
parameters[name] = param._;
|
||||
});
|
||||
}
|
||||
|
||||
// name 可能是 neo4j-mcp.executeReadOnlyCypherQuery
|
||||
return {
|
||||
server: '',
|
||||
name: invoke.name,
|
||||
callId: invoke.call_id,
|
||||
parameters
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to parse function calls:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function getToolResultFromXmlString(xmlString: string) {
|
||||
try {
|
||||
const result = await new Promise<any>((resolve, reject) => {
|
||||
parseString(xmlString, (err, result) => {
|
||||
if (err) reject(err);
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
|
||||
if (!result?.function_results?.result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resultData = result.function_results.result[0];
|
||||
const callId = resultData.$.call_id;
|
||||
|
||||
// 提取所有评论文本
|
||||
const toolcallContent = [] as ToolCallContent[];
|
||||
const content = resultData._;
|
||||
|
||||
toolcallContent.push({
|
||||
type: 'text',
|
||||
text: content
|
||||
});
|
||||
|
||||
return {
|
||||
callId,
|
||||
toolcallContent
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to parse function results:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function toNormaliseToolcall(xmlToolcall: XmlToolCall, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex): ToolCall {
|
||||
const toolcall = {
|
||||
id: xmlToolcall.callId,
|
||||
index: -1,
|
||||
type: 'function',
|
||||
function: {
|
||||
name: xmlToolcall.name,
|
||||
arguments: JSON.stringify(xmlToolcall.parameters)
|
||||
}
|
||||
} as ToolCall;
|
||||
|
||||
toolcall.index = toolcallIndexAdapter(toolcall);
|
||||
|
||||
return toolcall;
|
||||
}
|
||||
|
||||
export async function handleXmlWrapperToolcall(toolcall: XmlToolCall): Promise<ToolCallResult> {
|
||||
if (!toolcall) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'error',
|
||||
text: 'invalid xml'
|
||||
}],
|
||||
state: MessageState.InvalidXml
|
||||
}
|
||||
}
|
||||
|
||||
// 进行调用,根据结果返回不同的值
|
||||
console.log(toolcall);
|
||||
|
||||
const toolResponse = await mcpClientAdapter.callTool(toolcall.name, toolcall.parameters);
|
||||
return handleToolResponse(toolResponse);
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="chat-container" :ref="el => chatContainerRef = el">
|
||||
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll" v-if="renderMessages.length > 0 || isLoading">
|
||||
<el-scrollbar ref="scrollbarRef" :height="'90%'" @scroll="handleScroll"
|
||||
v-if="renderMessages.length > 0 || isLoading">
|
||||
<div class="message-list" :ref="el => messageListRef = el">
|
||||
<div v-for="(message, index) in renderMessages" :key="index"
|
||||
:class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]"
|
||||
>
|
||||
:class="['message-item', message.role.split('/')[0], message.role.split('/')[1]]">
|
||||
<div class="message-avatar" v-if="message.role === 'assistant/content'">
|
||||
<span class="iconfont icon-robot"></span>
|
||||
</div>
|
||||
@ -23,10 +23,8 @@
|
||||
|
||||
<!-- 助手调用的工具部分 -->
|
||||
<div class="message-content" v-else-if="message.role === 'assistant/tool_calls'">
|
||||
<Message.Toolcall
|
||||
:message="message" :tab-id="props.tabId"
|
||||
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value"
|
||||
/>
|
||||
<Message.Toolcall :message="message" :tab-id="props.tabId"
|
||||
@update:tool-result="(value, toolIndex, index) => message.toolResults[toolIndex][index] = value" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -47,23 +45,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChatBox
|
||||
:ref="el => footerRef = el"
|
||||
:tab-id="props.tabId"
|
||||
/>
|
||||
<ChatBox :ref="el => footerRef = el" :tab-id="props.tabId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide } from 'vue';
|
||||
import { ref, defineComponent, defineProps, onUnmounted, computed, nextTick, watch, provide, watchEffect } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ElMessage, type ScrollbarInstance } from 'element-plus';
|
||||
import { type ScrollbarInstance } from 'element-plus';
|
||||
import { tabs } from '../panel';
|
||||
import type { ChatMessage, ChatStorage, IRenderMessage, ToolCall } from './chat-box/chat';
|
||||
import { MessageState } from './chat-box/chat';
|
||||
|
||||
import * as Message from './message';
|
||||
import ChatBox from './chat-box/index.vue';
|
||||
import { getToolCallFromXmlString, getToolResultFromXmlString, getXmlsFromString, toNormaliseToolcall } from './core/xml-wrapper';
|
||||
import { getIdAsIndexAdapter } from './core/handle-tool-calls';
|
||||
|
||||
|
||||
defineComponent({ name: 'chat' });
|
||||
@ -85,18 +82,71 @@ if (!tabStorage.messages) {
|
||||
tabStorage.messages = [] as ChatMessage[];
|
||||
}
|
||||
|
||||
const renderMessages = computed(() => {
|
||||
const messages: IRenderMessage[] = [];
|
||||
function getXmlToolCalls(message: ChatMessage) {
|
||||
if (message.role !== 'assistant' && message.role !== 'user') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const enableXmlTools = message.extraInfo?.enableXmlWrapper ?? false;
|
||||
|
||||
if (!enableXmlTools) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const xmls = getXmlsFromString(message.content);
|
||||
|
||||
return xmls || [];
|
||||
}
|
||||
|
||||
const renderMessages = ref<IRenderMessage[]>([]);
|
||||
|
||||
watchEffect(async () => {
|
||||
renderMessages.value = [];
|
||||
|
||||
for (const message of tabStorage.messages) {
|
||||
const indexAdapter = getIdAsIndexAdapter();
|
||||
const xmls = getXmlToolCalls(message);
|
||||
|
||||
if (message.role === 'user') {
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: message.content,
|
||||
extraInfo: message.extraInfo
|
||||
});
|
||||
if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
|
||||
// 判断是否是 xml 模式,如果是 xml 模式且存在有效的 xml,则按照工具来判定
|
||||
// 往前寻找 assistant/tool_calls 并自动加入其中
|
||||
const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
|
||||
if (lastAssistantMessage.role === 'assistant/tool_calls') {
|
||||
|
||||
const toolCallResultXmls = getXmlsFromString(message.content);
|
||||
|
||||
for (const xml of toolCallResultXmls) {
|
||||
const toolResult = await getToolResultFromXmlString(xml);
|
||||
if (toolResult) {
|
||||
const index = indexAdapter(toolResult.callId);
|
||||
|
||||
lastAssistantMessage.toolResults[index] = toolResult.toolcallContent;
|
||||
|
||||
if (lastAssistantMessage.extraInfo.state === MessageState.Unknown) {
|
||||
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
|
||||
} else if (lastAssistantMessage.extraInfo.state === MessageState.Success
|
||||
|| message.extraInfo.state !== MessageState.Success
|
||||
) {
|
||||
lastAssistantMessage.extraInfo.state = message.extraInfo.state;
|
||||
}
|
||||
|
||||
lastAssistantMessage.extraInfo.usage = lastAssistantMessage.extraInfo.usage || message.extraInfo.usage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
renderMessages.value.push({
|
||||
role: 'user',
|
||||
content: message.content,
|
||||
extraInfo: message.extraInfo
|
||||
});
|
||||
}
|
||||
|
||||
} else if (message.role === 'assistant') {
|
||||
if (message.tool_calls) {
|
||||
messages.push({
|
||||
renderMessages.value.push({
|
||||
role: 'assistant/tool_calls',
|
||||
content: message.content,
|
||||
toolResults: Array(message.tool_calls.length).fill([]),
|
||||
@ -108,16 +158,46 @@ const renderMessages = computed(() => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
messages.push({
|
||||
role: 'assistant/content',
|
||||
content: message.content,
|
||||
extraInfo: message.extraInfo
|
||||
});
|
||||
if (xmls.length > 0 && message.extraInfo.enableXmlWrapper) {
|
||||
// 判断是否是 xml 模式,如果是 xml 模式且存在有效的 xml,则按照工具来判定
|
||||
const toolCalls = [];
|
||||
for (const xml of xmls) {
|
||||
const xmlToolCall = await getToolCallFromXmlString(xml);
|
||||
if (xmlToolCall) {
|
||||
toolCalls.push(
|
||||
toNormaliseToolcall(xmlToolCall, indexAdapter)
|
||||
);
|
||||
}
|
||||
}
|
||||
const renderAssistantMessage = message.content.replace(/```xml[\s\S]*?```/g, '');
|
||||
|
||||
console.log(toolCalls);
|
||||
|
||||
|
||||
renderMessages.value.push({
|
||||
role: 'assistant/tool_calls',
|
||||
content: renderAssistantMessage,
|
||||
toolResults: Array(toolCalls.length).fill([]),
|
||||
tool_calls: toolCalls,
|
||||
showJson: ref(false),
|
||||
extraInfo: {
|
||||
...message.extraInfo,
|
||||
state: MessageState.Unknown
|
||||
}
|
||||
});
|
||||
} else {
|
||||
renderMessages.value.push({
|
||||
role: 'assistant/content',
|
||||
content: message.content,
|
||||
extraInfo: message.extraInfo
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if (message.role === 'tool') {
|
||||
// 如果是工具,则合并进入 之前 assistant 一起渲染
|
||||
const lastAssistantMessage = messages[messages.length - 1];
|
||||
const lastAssistantMessage = renderMessages.value[renderMessages.value.length - 1];
|
||||
if (lastAssistantMessage.role === 'assistant/tool_calls') {
|
||||
lastAssistantMessage.toolResults[message.index] = message.content;
|
||||
|
||||
@ -133,10 +213,9 @@ const renderMessages = computed(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
});
|
||||
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const streamingContent = ref('');
|
||||
@ -232,14 +311,14 @@ watch(streamingToolCalls, () => {
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
.chat-openmcp-icon > div {
|
||||
.chat-openmcp-icon>div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: left;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.chat-openmcp-icon > div > span {
|
||||
.chat-openmcp-icon>div>span {
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
|
||||
@ -285,7 +364,7 @@ watch(streamingToolCalls, () => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user .message-text > span {
|
||||
.user .message-text>span {
|
||||
border-radius: .9em;
|
||||
background-color: var(--main-light-color);
|
||||
padding: 10px 15px;
|
||||
@ -340,9 +419,12 @@ watch(streamingToolCalls, () => {
|
||||
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="message-role">
|
||||
<span class="message-reminder" v-if="callingTools">
|
||||
Agent 正在使用工具
|
||||
Agent {{ t('using-tool') }}
|
||||
<span class="tool-loading iconfont icon-double-loading">
|
||||
</span>
|
||||
</span>
|
||||
|
@ -158,9 +158,5 @@ onMounted(async () => {
|
||||
.tool-description {
|
||||
opacity: 0.6;
|
||||
font-size: 12.5px;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
@ -2,7 +2,7 @@
|
||||
<div v-if="!isConnecting" class="connected-status-container" id="connected-status-container"
|
||||
@click.stop="toggleConnectionPanel()" :class="{ 'connected': client.connectionResult.success }">
|
||||
<span class="mcp-server-info">
|
||||
<el-tooltip class="extra-connect-container" effect="dark" placement="right"
|
||||
<el-tooltip class="extra-connect-container" effect="light" placement="right"
|
||||
:content="fullDisplayServerName">
|
||||
<span class="name">{{ displayServerName }}</span>
|
||||
</el-tooltip>
|
||||
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<div v-else class="connected-status-container">
|
||||
<span class="mcp-server-info">
|
||||
<el-tooltip class="extra-connect-container" effect="dark" placement="right"
|
||||
<el-tooltip class="extra-connect-container" effect="light" placement="right"
|
||||
:content="fullDisplayServerName">
|
||||
<span class="name">
|
||||
加载中
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div v-for="(item, index) of sidebarItems" :key="index"
|
||||
:id="`sidebar-${item.ident}`"
|
||||
>
|
||||
<el-tooltip :content="t(item.ident)" placement="right">
|
||||
<el-tooltip :content="t(item.ident)" placement="right" effect="light">
|
||||
<div class="sidebar-option-item" :class="{ 'active': isActive(item.ident) }"
|
||||
@click="gotoOption(item.ident)">
|
||||
<span :class="`iconfont ${item.icon}`"></span>
|
||||
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "تمكين تغليف تعليمات XML"
|
||||
"enable-xml-wrapper": "تمكين تغليف تعليمات XML",
|
||||
"tool-manage": "إدارة الأدوات",
|
||||
"enable-all-tools": "تفعيل جميع الأدوات",
|
||||
"disable-all-tools": "تعطيل جميع الأدوات",
|
||||
"using-tool": "جاري استخدام الأداة"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "XML-Befehlsverpackung aktivieren"
|
||||
"enable-xml-wrapper": "XML-Befehlsverpackung aktivieren",
|
||||
"tool-manage": "Werkzeugverwaltung",
|
||||
"enable-all-tools": "Alle Tools aktivieren",
|
||||
"disable-all-tools": "Alle Tools deaktivieren",
|
||||
"using-tool": "Werkzeug wird verwendet"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "Enable XML command wrapping"
|
||||
"enable-xml-wrapper": "Enable XML command wrapping",
|
||||
"tool-manage": "Tool Management",
|
||||
"enable-all-tools": "Activate all tools",
|
||||
"disable-all-tools": "Disable all tools",
|
||||
"using-tool": "Using tool"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "Activer l'encapsulation de commande XML"
|
||||
"enable-xml-wrapper": "Activer l'encapsulation de commande XML",
|
||||
"tool-manage": "Gestion des outils",
|
||||
"enable-all-tools": "Activer tous les outils",
|
||||
"disable-all-tools": "Désactiver tous les outils",
|
||||
"using-tool": "Utilisation de l'outil"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "XMLコマンドラッピングを有効にする"
|
||||
"enable-xml-wrapper": "XMLコマンドラッピングを有効にする",
|
||||
"tool-manage": "ツール管理",
|
||||
"enable-all-tools": "すべてのツールを有効にする",
|
||||
"disable-all-tools": "すべてのツールを無効にする",
|
||||
"using-tool": "ツール使用中"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "XML 명령 래핑 활성화"
|
||||
"enable-xml-wrapper": "XML 명령 래핑 활성화",
|
||||
"tool-manage": "도구 관리",
|
||||
"enable-all-tools": "모든 도구 활성화",
|
||||
"disable-all-tools": "모든 도구 비활성화",
|
||||
"using-tool": "도구 사용 중"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "Включить обёртку XML-команд"
|
||||
"enable-xml-wrapper": "Включить обёртку XML-команд",
|
||||
"tool-manage": "Управление инструментами",
|
||||
"enable-all-tools": "Активировать все инструменты",
|
||||
"disable-all-tools": "Отключить все инструменты",
|
||||
"using-tool": "Использование инструмента"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "为我们撰写评价!",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} 由 {author} 开发",
|
||||
"error-parse-json": "JSON 解析错误:",
|
||||
"enable-xml-wrapper": "开启 XML 指令包裹"
|
||||
"enable-xml-wrapper": "开启 XML 指令包裹",
|
||||
"tool-manage": "工具管理",
|
||||
"enable-all-tools": "激活所有工具",
|
||||
"disable-all-tools": "禁用所有工具",
|
||||
"using-tool": "正在使用工具"
|
||||
}
|
@ -172,5 +172,9 @@
|
||||
"comment-for-us": "Écrivez un avis pour nous !",
|
||||
"openmcp-developed-by": "OpenMCP Client {version} développé par {author}",
|
||||
"error-parse-json": "Erreur d'analyse JSON :",
|
||||
"enable-xml-wrapper": "開啟 XML 指令包裹"
|
||||
"enable-xml-wrapper": "開啟 XML 指令包裹",
|
||||
"tool-manage": "工具管理",
|
||||
"enable-all-tools": "啟用所有工具",
|
||||
"disable-all-tools": "禁用所有工具",
|
||||
"using-tool": "正在使用工具"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmcp-sdk",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"description": "openmcp-sdk",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
186
resources/openmcp-sdk-release/task-loop.d.ts
vendored
186
resources/openmcp-sdk-release/task-loop.d.ts
vendored
@ -4,20 +4,13 @@ import type { OpenAI } from 'openai';
|
||||
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
||||
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & { id?: string };
|
||||
|
||||
export interface TaskLoopOptions {
|
||||
maxEpochs?: number;
|
||||
maxJsonParseRetry?: number;
|
||||
adapter?: any;
|
||||
verbose?: 0 | 1 | 2 | 3;
|
||||
}
|
||||
|
||||
export interface SchemaProperty {
|
||||
interface SchemaProperty {
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface InputSchema {
|
||||
interface InputSchema {
|
||||
type: string;
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
@ -25,7 +18,7 @@ export interface InputSchema {
|
||||
$defs?: any;
|
||||
}
|
||||
|
||||
export interface ToolItem {
|
||||
interface ToolItem {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: InputSchema;
|
||||
@ -33,6 +26,50 @@ export interface ToolItem {
|
||||
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;
|
||||
};
|
||||
@ -80,68 +117,132 @@ 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 {
|
||||
private streamingContent;
|
||||
private streamingToolCalls;
|
||||
private readonly taskOptions;
|
||||
private bridge;
|
||||
private currentChatId;
|
||||
private onError;
|
||||
private onChunk;
|
||||
private onDone;
|
||||
private onToolCalled;
|
||||
private onEpoch;
|
||||
private completionUsage;
|
||||
private llmConfig;
|
||||
constructor(taskOptions?: TaskLoopOptions);
|
||||
private handleChunkDeltaContent;
|
||||
private handleChunkDeltaToolCalls;
|
||||
private handleChunkUsage;
|
||||
private doConversation;
|
||||
|
||||
/**
|
||||
* @description make chat data
|
||||
* @param tabStorage
|
||||
*/
|
||||
makeChatData(tabStorage: any): ChatCompletionCreateParamsBase | undefined;
|
||||
|
||||
/**
|
||||
* @description stop the task loop
|
||||
*/
|
||||
abort(): void;
|
||||
|
||||
/**
|
||||
* @description 注册 error 发生时触发的回调函数
|
||||
* @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 注册 chat.completion 完成时触发的回调函数
|
||||
* @description Register a callback function triggered at the beginning of each epoch
|
||||
* @param handler
|
||||
*/
|
||||
registerOnDone(handler: () => void): void;
|
||||
|
||||
/**
|
||||
* @description 注册每一个 epoch 开始时触发的回调函数
|
||||
* @description Register a callback function triggered at the beginning of each epoch
|
||||
* @param handler
|
||||
*/
|
||||
registerOnEpoch(handler: () => void): void;
|
||||
|
||||
/**
|
||||
* @description 注册当工具调用完成时的回调函数,会调用这个方法,可以拦截并修改 toolcall 的输出
|
||||
* @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 注册当工具调用前的回调函数,可以拦截并修改 toolcall 的输入
|
||||
* @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 获取当前的 LLM 配置
|
||||
* @description Get current LLM configuration
|
||||
*/
|
||||
getLlmConfig(): any;
|
||||
|
||||
/**
|
||||
* @description 设置当前的 LLM 配置,用于 nodejs 环境运行
|
||||
* @description Set the current LLM configuration, for Node.js environment
|
||||
* @param config
|
||||
* @example
|
||||
* setLlmConfig({
|
||||
@ -154,11 +255,19 @@ export class TaskLoop {
|
||||
setLlmConfig(config: any): void;
|
||||
|
||||
/**
|
||||
* @description 设置最大 epoch 次数
|
||||
* @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>;
|
||||
|
||||
/**
|
||||
@ -168,14 +277,19 @@ export class TaskLoop {
|
||||
setProxyServer(proxyServer: string): void;
|
||||
|
||||
/**
|
||||
* @description 获取所有可用的工具列表
|
||||
* @description Get all available tool list
|
||||
*/
|
||||
listTools(): Promise<ToolItem[]>;
|
||||
|
||||
/**
|
||||
* @description 开启循环,异步更新 DOM
|
||||
* @description Start the loop and asynchronously update the DOM
|
||||
*/
|
||||
start(tabStorage: any, userMessage: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* @description Create single conversation context
|
||||
*/
|
||||
createStorage(settings?: ChatSetting): Promise<ChatStorage>;
|
||||
}
|
||||
|
||||
export declare const getToolSchema: any;
|
||||
|
@ -95,6 +95,9 @@ export function getConnectionConfig() {
|
||||
*/
|
||||
export function getWorkspaceConnectionConfigPath() {
|
||||
const workspace = getWorkspacePath();
|
||||
if (!workspace) {
|
||||
throw new Error('No workspace found. Please open a folder in VSCode first.');
|
||||
}
|
||||
const configDir = fspath.join(workspace, '.openmcp');
|
||||
if (!fs.existsSync(configDir)) {
|
||||
fs.mkdirSync(configDir, { recursive: true }); // 递归创建目录
|
||||
@ -113,6 +116,9 @@ export function getWorkspaceConnectionConfig() {
|
||||
}
|
||||
|
||||
const workspace = getWorkspacePath();
|
||||
if (!workspace) {
|
||||
throw new Error('No workspace found. Please open a folder in VSCode first.');
|
||||
}
|
||||
const configDir = fspath.join(workspace, '.openmcp');
|
||||
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user