Commit 38f6f9dd by Archer Committed by GitHub

fix: tool id (#6544)

* fix: tool id

* fix: test

* fix: ts

* add test
parent 960c8898
---
title: 'V4.14.9(进行中)'
description: 'FastGPT V4.14.9 更新说明'
---
## 🚀 新增内容
## ⚙️ 优化
## 🐛 修复
1. 工作流嵌套插件时,未成功保留插件运行详情。同时整理所有 tool 类型前缀。
2. 更新 MCP toolset 后可能无法正常调用。
\ No newline at end of file
...@@ -120,6 +120,7 @@ description: FastGPT 文档目录 ...@@ -120,6 +120,7 @@ description: FastGPT 文档目录
- [/docs/self-host/upgrading/4-14/4147](/docs/self-host/upgrading/4-14/4147) - [/docs/self-host/upgrading/4-14/4147](/docs/self-host/upgrading/4-14/4147)
- [/docs/self-host/upgrading/4-14/4148](/docs/self-host/upgrading/4-14/4148) - [/docs/self-host/upgrading/4-14/4148](/docs/self-host/upgrading/4-14/4148)
- [/docs/self-host/upgrading/4-14/41481](/docs/self-host/upgrading/4-14/41481) - [/docs/self-host/upgrading/4-14/41481](/docs/self-host/upgrading/4-14/41481)
- [/docs/self-host/upgrading/4-14/4149](/docs/self-host/upgrading/4-14/4149)
- [/docs/self-host/upgrading/outdated/40](/docs/self-host/upgrading/outdated/40) - [/docs/self-host/upgrading/outdated/40](/docs/self-host/upgrading/outdated/40)
- [/docs/self-host/upgrading/outdated/41](/docs/self-host/upgrading/outdated/41) - [/docs/self-host/upgrading/outdated/41](/docs/self-host/upgrading/outdated/41)
- [/docs/self-host/upgrading/outdated/4100](/docs/self-host/upgrading/outdated/4100) - [/docs/self-host/upgrading/outdated/4100](/docs/self-host/upgrading/outdated/4100)
......
...@@ -95,8 +95,8 @@ ...@@ -95,8 +95,8 @@
"document/content/docs/introduction/guide/dashboard/workflow/variable_update.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/dashboard/workflow/variable_update.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/knowledge_base/RAG.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/knowledge_base/RAG.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/introduction/guide/knowledge_base/RAG.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/knowledge_base/RAG.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/knowledge_base/api_dataset.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/knowledge_base/api_dataset.en.mdx": "2026-03-09T17:39:53+08:00",
"document/content/docs/introduction/guide/knowledge_base/api_dataset.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/knowledge_base/api_dataset.mdx": "2026-03-09T17:39:53+08:00",
"document/content/docs/introduction/guide/knowledge_base/collection_tags.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/knowledge_base/collection_tags.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/introduction/guide/knowledge_base/collection_tags.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/introduction/guide/knowledge_base/collection_tags.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/introduction/guide/knowledge_base/dataset_engine.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/introduction/guide/knowledge_base/dataset_engine.en.mdx": "2026-02-26T22:14:30+08:00",
...@@ -232,9 +232,10 @@ ...@@ -232,9 +232,10 @@
"document/content/docs/self-host/upgrading/4-14/4147.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4147.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4147.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/4-14/4147.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4148.en.mdx": "2026-03-06T19:32:23+08:00", "document/content/docs/self-host/upgrading/4-14/4148.en.mdx": "2026-03-06T19:32:23+08:00",
"document/content/docs/self-host/upgrading/4-14/4148.mdx": "2026-03-09T12:04:22+08:00", "document/content/docs/self-host/upgrading/4-14/4148.mdx": "2026-03-09T17:39:53+08:00",
"document/content/docs/self-host/upgrading/4-14/41481.en.mdx": "2026-03-09T12:02:02+08:00", "document/content/docs/self-host/upgrading/4-14/41481.en.mdx": "2026-03-09T12:02:02+08:00",
"document/content/docs/self-host/upgrading/4-14/41481.mdx": "2026-03-09T14:24:27+08:00", "document/content/docs/self-host/upgrading/4-14/41481.mdx": "2026-03-09T17:39:53+08:00",
"document/content/docs/self-host/upgrading/4-14/4149.mdx": "2026-03-11T22:47:07+08:00",
"document/content/docs/self-host/upgrading/outdated/40.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/40.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/40.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/40.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/outdated/41.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/outdated/41.en.mdx": "2026-03-03T17:39:47+08:00",
...@@ -376,7 +377,7 @@ ...@@ -376,7 +377,7 @@
"document/content/docs/self-host/upgrading/upgrade-intruction.en.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/upgrade-intruction.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/upgrade-intruction.mdx": "2026-03-03T17:39:47+08:00", "document/content/docs/self-host/upgrading/upgrade-intruction.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/toc.en.mdx": "2026-03-09T12:02:02+08:00", "document/content/docs/toc.en.mdx": "2026-03-09T12:02:02+08:00",
"document/content/docs/toc.mdx": "2026-03-09T12:02:02+08:00", "document/content/docs/toc.mdx": "2026-03-11T22:47:07+08:00",
"document/content/docs/use-cases/app-cases/dalle3.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/use-cases/app-cases/dalle3.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/use-cases/app-cases/dalle3.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/use-cases/app-cases/dalle3.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-02-26T22:14:30+08:00", "document/content/docs/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-02-26T22:14:30+08:00",
......
...@@ -9,7 +9,7 @@ export enum PluginErrEnum { ...@@ -9,7 +9,7 @@ export enum PluginErrEnum {
const errList = [ const errList = [
{ {
statusText: PluginErrEnum.unExist, statusText: PluginErrEnum.unExist,
message: i18nT('common:code_error.plugin_error.not_exist') message: i18nT('common:error.tool_not_exist')
}, },
{ {
statusText: PluginErrEnum.unAuth, statusText: PluginErrEnum.unAuth,
......
...@@ -51,12 +51,14 @@ export const getHTTPToolRuntimeNode = ({ ...@@ -51,12 +51,14 @@ export const getHTTPToolRuntimeNode = ({
tool, tool,
nodeId, nodeId,
avatar = 'core/app/type/httpToolsFill', avatar = 'core/app/type/httpToolsFill',
toolSetId toolSetId,
toolsetName
}: { }: {
tool: Omit<HttpToolConfigType, 'path' | 'method'>; tool: Omit<HttpToolConfigType, 'path' | 'method'>;
nodeId: string; nodeId: string;
avatar?: string; avatar?: string;
toolSetId: string; toolSetId: string;
toolsetName: string;
}): RuntimeNodeItemType => { }): RuntimeNodeItemType => {
return { return {
nodeId, nodeId,
...@@ -81,7 +83,7 @@ export const getHTTPToolRuntimeNode = ({ ...@@ -81,7 +83,7 @@ export const getHTTPToolRuntimeNode = ({
type: FlowNodeOutputTypeEnum.static type: FlowNodeOutputTypeEnum.static
} }
], ],
name: tool.name, name: `${toolsetName}/${tool.name}`,
version: '' version: ''
}; };
}; };
......
...@@ -13,15 +13,13 @@ export const getMCPToolSetRuntimeNode = ({ ...@@ -13,15 +13,13 @@ export const getMCPToolSetRuntimeNode = ({
toolList, toolList,
headerSecret, headerSecret,
name, name,
avatar, avatar
toolId
}: { }: {
url: string; url: string;
toolList: McpToolConfigType[]; toolList: McpToolConfigType[];
headerSecret?: StoreSecretValueType; headerSecret?: StoreSecretValueType;
name?: string; name?: string;
avatar?: string; avatar?: string;
toolId: string;
}): RuntimeNodeItemType => { }): RuntimeNodeItemType => {
return { return {
nodeId: getNanoid(16), nodeId: getNanoid(16),
...@@ -32,8 +30,7 @@ export const getMCPToolSetRuntimeNode = ({ ...@@ -32,8 +30,7 @@ export const getMCPToolSetRuntimeNode = ({
mcpToolSet: { mcpToolSet: {
toolList, toolList,
headerSecret, headerSecret,
url, url
toolId
} }
}, },
inputs: [], inputs: [],
...@@ -47,12 +44,14 @@ export const getMCPToolRuntimeNode = ({ ...@@ -47,12 +44,14 @@ export const getMCPToolRuntimeNode = ({
tool, tool,
avatar = 'core/app/type/mcpToolsFill', avatar = 'core/app/type/mcpToolsFill',
nodeId, nodeId,
toolsetName,
toolSetId toolSetId
}: { }: {
nodeId: string; nodeId: string;
tool: McpToolConfigType; tool: McpToolConfigType;
avatar?: string;
toolSetId: string; toolSetId: string;
toolsetName: string;
avatar?: string;
}): RuntimeNodeItemType => { }): RuntimeNodeItemType => {
return { return {
nodeId, nodeId,
...@@ -76,7 +75,7 @@ export const getMCPToolRuntimeNode = ({ ...@@ -76,7 +75,7 @@ export const getMCPToolRuntimeNode = ({
type: FlowNodeOutputTypeEnum.static type: FlowNodeOutputTypeEnum.static
} }
], ],
name: tool.name, name: `${toolsetName}/${tool.name}`,
version: '' version: ''
}; };
}; };
......
...@@ -2,60 +2,81 @@ import { AppToolSourceEnum } from '../tool/constants'; ...@@ -2,60 +2,81 @@ import { AppToolSourceEnum } from '../tool/constants';
/** /**
Tool id rule: Tool id rule:
- personal: ObjectId - personal: ObjectId(旧版), personal-objectId(新版)
- commercial: commercial-ObjectId - commercial: commercial-ObjectId
- systemtool: systemTool-id - systemtool: systemTool-id
- mcp tool: mcp-parentId/toolName - mcp toolset: appId
- mcp tool pluginId: mcp-appId/toolname
- http toolset: appId
- http tool pluginId: http-appId/toolname
(deprecated) community: community-id (deprecated) community: community-id
*/ */
export function splitCombineToolId(id: string) { export function splitCombineToolId(id: string): {
source: AppToolSourceEnum;
pluginId: string;
authAppId?: string;
} {
const splitRes = id.split('-'); const splitRes = id.split('-');
if (splitRes.length === 1) { if (splitRes.length === 1) {
// app id // app id
return { return {
source: AppToolSourceEnum.personal, source: AppToolSourceEnum.personal,
pluginId: id pluginId: id,
authAppId: id
}; };
} }
const [source, ...rest] = id.split('-') as [AppToolSourceEnum, string | undefined]; const [source, ...rest] = id.split('-') as [AppToolSourceEnum, string | undefined];
const pluginId = rest.join('-'); const toolId = rest.join('-');
if (!source || !pluginId) throw new Error('pluginId not found'); if (!source || !toolId) throw new Error('toolId not found');
// 兼容4.10.0 之前的插件 // 兼容4.10.0 之前的插件
if (source === 'community' || id === 'commercial-dalle3') { if (source === 'community' || id === 'commercial-dalle3') {
return { return {
source: AppToolSourceEnum.systemTool, source: AppToolSourceEnum.systemTool,
pluginId: `${AppToolSourceEnum.systemTool}-${pluginId}` pluginId: toolId
};
}
if (source === AppToolSourceEnum.systemTool) {
return {
source: AppToolSourceEnum.systemTool,
pluginId: toolId
};
}
if (source === AppToolSourceEnum.commercial) {
return {
source: AppToolSourceEnum.commercial,
pluginId: toolId
}; };
} }
// mcp-appId, mcp-appId/toolname // mcp-appId, mcp-appId/toolname
if (source === 'mcp') { if (source === AppToolSourceEnum.mcp) {
const [parentId, toolName] = pluginId.split('/'); const [parentId, toolName] = toolId.split('/');
return { return {
source: AppToolSourceEnum.mcp, source: AppToolSourceEnum.mcp,
pluginId, pluginId: toolId,
authAppId: parentId authAppId: parentId
}; };
} }
if (source === 'http') { if (source === AppToolSourceEnum.http) {
const [parentId, toolName] = pluginId.split('/'); const [parentId, toolName] = toolId.split('/');
return { return {
source: AppToolSourceEnum.http, source: AppToolSourceEnum.http,
pluginId, pluginId: toolId,
parentId authAppId: parentId
}; };
} }
if (source === 'personal') { if (source === AppToolSourceEnum.personal) {
return { return {
source: AppToolSourceEnum.personal, source: AppToolSourceEnum.personal,
pluginId, pluginId: toolId,
parentId: pluginId authAppId: toolId
}; };
} }
return { source, pluginId: id }; throw new Error('Invalid tool id');
} }
export const getToolRawId = (id: string) => { export const getToolRawId = (id: string) => {
......
...@@ -12,7 +12,6 @@ import z from 'zod'; ...@@ -12,7 +12,6 @@ import z from 'zod';
export const NodeToolConfigTypeSchema = z.object({ export const NodeToolConfigTypeSchema = z.object({
mcpToolSet: z mcpToolSet: z
.object({ .object({
toolId: z.string(),
url: z.string(), url: z.string(),
headerSecret: StoreSecretValueTypeSchema.optional(), headerSecret: StoreSecretValueTypeSchema.optional(),
toolList: z.array(McpToolConfigSchema) toolList: z.array(McpToolConfigSchema)
...@@ -20,7 +19,7 @@ export const NodeToolConfigTypeSchema = z.object({ ...@@ -20,7 +19,7 @@ export const NodeToolConfigTypeSchema = z.object({
.optional(), .optional(),
mcpTool: z mcpTool: z
.object({ .object({
toolId: z.string() toolId: z.string() // mcp-appId/oolname
}) })
.optional(), .optional(),
systemTool: z systemTool: z
...@@ -51,7 +50,7 @@ export const NodeToolConfigTypeSchema = z.object({ ...@@ -51,7 +50,7 @@ export const NodeToolConfigTypeSchema = z.object({
.optional(), .optional(),
httpTool: z httpTool: z
.object({ .object({
toolId: z.string() toolId: z.string() // http-appId/oolname
}) })
.optional() .optional()
}); });
......
...@@ -239,10 +239,10 @@ export const getSystemToolsWithInstalled = async ({ ...@@ -239,10 +239,10 @@ export const getSystemToolsWithInstalled = async ({
}; };
export const getSystemToolByIdAndVersionId = async ( export const getSystemToolByIdAndVersionId = async (
pluginId: string, toolId: string,
versionId?: string versionId?: string
): Promise<ChildAppType> => { ): Promise<ChildAppType> => {
const tool = await getSystemToolById(pluginId); const tool = await getSystemToolById(toolId);
// App type system tool // App type system tool
if (tool.associatedPluginId) { if (tool.associatedPluginId) {
...@@ -333,10 +333,6 @@ export const getSystemToolByIdAndVersionId = async ( ...@@ -333,10 +333,6 @@ export const getSystemToolByIdAndVersionId = async (
/* /*
Format plugin to workflow preview node data Format plugin to workflow preview node data
Persion workflow/plugin: objectId
Persion mcptoolset: objectId
Persion mcp tool: mcp-parentId/name
System tool/toolset: system-toolId
*/ */
export async function getChildAppPreviewNode({ export async function getChildAppPreviewNode({
appId, appId,
...@@ -350,8 +346,7 @@ export async function getChildAppPreviewNode({ ...@@ -350,8 +346,7 @@ export async function getChildAppPreviewNode({
const { source, pluginId } = splitCombineToolId(appId); const { source, pluginId } = splitCombineToolId(appId);
const app: ChildAppType = await (async () => { const app: ChildAppType = await (async () => {
// 1. App // App / Mcp toolset / Http toolset
// 2. MCP ToolSets
if (source === AppToolSourceEnum.personal) { if (source === AppToolSourceEnum.personal) {
const item = await MongoApp.findById(pluginId).lean(); const item = await MongoApp.findById(pluginId).lean();
if (!item) return Promise.reject(PluginErrEnum.unExist); if (!item) return Promise.reject(PluginErrEnum.unExist);
...@@ -367,17 +362,18 @@ export async function getChildAppPreviewNode({ ...@@ -367,17 +362,18 @@ export async function getChildAppPreviewNode({
}) })
: true; : true;
if (item.type === AppTypeEnum.mcpToolSet) { // Adapt
if (item.type === AppTypeEnum.mcpToolSet && !version.nodes[0].toolConfig) {
const children = await getMCPChildren(item); const children = await getMCPChildren(item);
version.nodes[0].toolConfig = { version.nodes[0].toolConfig = {
mcpToolSet: { mcpToolSet: {
toolId: pluginId,
toolList: children, toolList: children,
url: '', url: '',
headerSecret: {} headerSecret: {}
} }
}; };
} }
return { return {
id: String(item._id), id: String(item._id),
teamId: String(item.teamId), teamId: String(item.teamId),
...@@ -430,11 +426,12 @@ export async function getChildAppPreviewNode({ ...@@ -430,11 +426,12 @@ export async function getChildAppPreviewNode({
getMCPToolRuntimeNode({ getMCPToolRuntimeNode({
nodeId: getNanoid(6), nodeId: getNanoid(6),
toolSetId: item._id, toolSetId: item._id,
toolsetName: item.name,
avatar: item.avatar, avatar: item.avatar,
tool: { tool: {
description: tool.description, description: tool.description,
inputSchema: tool.inputSchema, inputSchema: tool.inputSchema,
name: `${item.name}/${tool.name}` name: tool.name
} }
}) })
], ],
...@@ -469,11 +466,12 @@ export async function getChildAppPreviewNode({ ...@@ -469,11 +466,12 @@ export async function getChildAppPreviewNode({
getHTTPToolRuntimeNode({ getHTTPToolRuntimeNode({
nodeId: getNanoid(6), nodeId: getNanoid(6),
toolSetId: item._id, toolSetId: item._id,
toolsetName: item.name,
tool: { tool: {
description: tool.description, description: tool.description,
inputSchema: tool.inputSchema, inputSchema: tool.inputSchema,
outputSchema: tool.outputSchema, outputSchema: tool.outputSchema,
name: `${item.name}/${tool.name}` name: tool.name
}, },
avatar: item.avatar avatar: item.avatar
}) })
...@@ -484,10 +482,9 @@ export async function getChildAppPreviewNode({ ...@@ -484,10 +482,9 @@ export async function getChildAppPreviewNode({
isLatestVersion: true isLatestVersion: true
}; };
} }
// 1. System Tools // System Tools/ Commercial system tools
// 2. System Plugins configured in Pro (has associatedPluginId)
else { else {
return getSystemToolByIdAndVersionId(pluginId, versionId); return getSystemToolByIdAndVersionId(appId, versionId);
} }
})(); })();
...@@ -504,7 +501,7 @@ export async function getChildAppPreviewNode({ ...@@ -504,7 +501,7 @@ export async function getChildAppPreviewNode({
if (source === AppToolSourceEnum.systemTool) { if (source === AppToolSourceEnum.systemTool) {
// system Tool or Toolsets // system Tool or Toolsets
const children = app.isFolder const children = app.isFolder
? (await getSystemTools()).filter((item) => item.parentId === pluginId) ? (await getSystemTools()).filter((item) => item.parentId === app.id)
: []; : [];
return { return {
...@@ -619,74 +616,6 @@ export async function getChildAppPreviewNode({ ...@@ -619,74 +616,6 @@ export async function getChildAppPreviewNode({
}; };
} }
/**
Get runtime plugin data
System plugin: plugin id
Personal plugin: Version id
*/
export async function getChildAppRuntimeById({
id,
versionId,
lang = 'en'
}: {
id: string;
versionId?: string;
lang?: localeType;
}): Promise<AppToolRuntimeType> {
const app = await (async () => {
const { source, pluginId } = splitCombineToolId(id);
if (source === AppToolSourceEnum.personal) {
const item = await MongoApp.findById(pluginId).lean();
if (!item) return Promise.reject(PluginErrEnum.unExist);
const version = await getAppVersionById({
appId: pluginId,
versionId,
app: item
});
return {
id: String(item._id),
teamId: String(item.teamId),
tmbId: String(item.tmbId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
originCost: 0,
currentCost: 0,
systemKeyCost: 0,
hasTokenFee: false,
pluginOrder: 0
};
} else {
return getSystemToolByIdAndVersionId(pluginId, versionId);
}
})();
return {
id: app.id,
teamId: app.teamId,
tmbId: app.tmbId,
name: parseI18nString(app.name, lang),
avatar: app.avatar || '',
showStatus: true,
currentCost: app.currentCost,
systemKeyCost: app.systemKeyCost,
nodes: app.workflow.nodes,
edges: app.workflow.edges,
hasTokenFee: app.hasTokenFee
};
}
/* FastsGPT-tool api: */ /* FastsGPT-tool api: */
export const refreshSystemTools = async (): Promise<AppToolTemplateItemType[]> => { export const refreshSystemTools = async (): Promise<AppToolTemplateItemType[]> => {
const workflowToolFormat = (item: SystemPluginToolCollectionType): AppToolTemplateItemType => { const workflowToolFormat = (item: SystemPluginToolCollectionType): AppToolTemplateItemType => {
...@@ -784,10 +713,10 @@ export const refreshSystemTools = async (): Promise<AppToolTemplateItemType[]> = ...@@ -784,10 +713,10 @@ export const refreshSystemTools = async (): Promise<AppToolTemplateItemType[]> =
return concatTools; return concatTools;
}; };
export const getSystemToolById = async (id: string): Promise<AppToolTemplateItemType> => { // toolId: systemTool-id, commercial-id
const { pluginId } = splitCombineToolId(id); export const getSystemToolById = async (toolId: string): Promise<AppToolTemplateItemType> => {
const tools = await getSystemTools(); const tools = await getSystemTools();
const tool = tools.find((item) => item.id === pluginId); const tool = tools.find((item) => item.id === toolId);
if (tool) { if (tool) {
return cloneDeep(tool); return cloneDeep(tool);
} }
......
...@@ -23,7 +23,12 @@ export const computedAppToolUsage = async ({ ...@@ -23,7 +23,12 @@ export const computedAppToolUsage = async ({
const { source } = splitCombineToolId(plugin.id); const { source } = splitCombineToolId(plugin.id);
const childrenUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); const childrenUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
if (source !== AppToolSourceEnum.personal) { const set = new Set([
AppToolSourceEnum.commercial,
AppToolSourceEnum.community,
AppToolSourceEnum.systemTool
]);
if (set.has(source)) {
if (error) return 0; if (error) return 0;
const pluginCurrentCost = plugin.currentCost ?? 0; const pluginCurrentCost = plugin.currentCost ?? 0;
......
...@@ -82,7 +82,7 @@ export async function rewriteAppWorkflowToDetail({ ...@@ -82,7 +82,7 @@ export async function rewriteAppWorkflowToDetail({
/* Add node(App Type) versionlabel and latest sign ==== */ /* Add node(App Type) versionlabel and latest sign ==== */
await Promise.all( await Promise.all(
nodes.map(async (node) => { nodes.map(async (node) => {
// Tool node(简易模式/工作流) // Tool node
if (node.pluginId) { if (node.pluginId) {
const result = await loadToolNode({ id: node.pluginId, versionId: node.version }); const result = await loadToolNode({ id: node.pluginId, versionId: node.version });
if (result.success) { if (result.success) {
......
...@@ -7,6 +7,7 @@ export const collectionName = 'system_plugin_tools'; ...@@ -7,6 +7,7 @@ export const collectionName = 'system_plugin_tools';
const SystemToolSchema = new Schema({ const SystemToolSchema = new Schema({
pluginId: { pluginId: {
// commercial-id
type: String, type: String,
required: true required: true
}, },
......
...@@ -20,6 +20,7 @@ import { getAppVersionById } from '../../../../../../app/version/controller'; ...@@ -20,6 +20,7 @@ import { getAppVersionById } from '../../../../../../app/version/controller';
import { MCPClient } from '../../../../../../app/mcp'; import { MCPClient } from '../../../../../../app/mcp';
import { runHTTPTool } from '../../../../../../app/http'; import { runHTTPTool } from '../../../../../../app/http';
import { getS3ChatSource } from '../../../../../../../common/s3/sources/chat'; import { getS3ChatSource } from '../../../../../../../common/s3/sources/chat';
import { parseToolId } from '../../../../child/runTool';
type SystemInputConfigType = { type SystemInputConfigType = {
type: SystemToolSecretInputTypeEnum; type: SystemToolSecretInputTypeEnum;
...@@ -175,8 +176,7 @@ export const dispatchTool = async ({ ...@@ -175,8 +176,7 @@ export const dispatchTool = async ({
] ]
}; };
} else if (toolConfig?.mcpTool?.toolId) { } else if (toolConfig?.mcpTool?.toolId) {
const { pluginId } = splitCombineToolId(toolConfig.mcpTool.toolId); const { parentId, toolName } = parseToolId(toolConfig.mcpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
const tool = await getAppVersionById({ const tool = await getAppVersionById({
appId: parentId, appId: parentId,
versionId: version versionId: version
...@@ -203,8 +203,7 @@ export const dispatchTool = async ({ ...@@ -203,8 +203,7 @@ export const dispatchTool = async ({
usages: [] usages: []
}; };
} else if (toolConfig?.httpTool?.toolId) { } else if (toolConfig?.httpTool?.toolId) {
const { pluginId } = splitCombineToolId(toolConfig.httpTool.toolId); const { parentId, toolName } = parseToolId(toolConfig.httpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
if (!parentId || !toolName) { if (!parentId || !toolName) {
return Promise.reject(`Invalid HTTP tool id: ${toolConfig.httpTool.toolId}`); return Promise.reject(`Invalid HTTP tool id: ${toolConfig.httpTool.toolId}`);
} }
......
...@@ -25,7 +25,7 @@ import type { SubAppInitType } from '../type'; ...@@ -25,7 +25,7 @@ import type { SubAppInitType } from '../type';
import { getToolConfigStatus } from '@fastgpt/global/core/app/formEdit/utils'; import { getToolConfigStatus } from '@fastgpt/global/core/app/formEdit/utils';
import { getLogger, LogCategories } from '../../../../../../../common/logger'; import { getLogger, LogCategories } from '../../../../../../../common/logger';
export const agentSkillToToolRuntime = async ({ export const getAgentRuntimeTools = async ({
tools, tools,
tmbId, tmbId,
lang lang
...@@ -195,18 +195,15 @@ export const agentSkillToToolRuntime = async ({ ...@@ -195,18 +195,15 @@ export const agentSkillToToolRuntime = async ({
if (!app) return []; if (!app) return [];
const toolList = await getMCPChildren(app); const toolList = await getMCPChildren(app);
const toolSetId = mcpToolsetVal.toolId ?? toolNode.pluginId; const toolSetId = mcpToolsetVal.toolId || toolNode.pluginId;
const children = toolList.map((tool, index) => { const children = toolList.map((tool, index) => {
const newToolNode = getMCPToolRuntimeNode({ const newToolNode = getMCPToolRuntimeNode({
toolSetId, toolSetId,
toolsetName: toolNode.name,
nodeId: `${toolSetId}${index}`, nodeId: `${toolSetId}${index}`,
avatar: toolNode.avatar, avatar: toolNode.avatar,
tool: { tool
...tool,
name: `${toolNode.name}/${tool.name}`
}
}); });
return newToolNode; return newToolNode;
}); });
...@@ -232,15 +229,12 @@ export const agentSkillToToolRuntime = async ({ ...@@ -232,15 +229,12 @@ export const agentSkillToToolRuntime = async ({
} else if (httpToolsetVal) { } else if (httpToolsetVal) {
const children = httpToolsetVal.toolList.map((tool: HttpToolConfigType, index) => { const children = httpToolsetVal.toolList.map((tool: HttpToolConfigType, index) => {
const newToolNode = getHTTPToolRuntimeNode({ const newToolNode = getHTTPToolRuntimeNode({
tool: { tool,
...tool,
name: `${toolNode.name}/${tool.name}`
},
nodeId: `${pluginId}${index}`, nodeId: `${pluginId}${index}`,
avatar: toolNode.avatar, avatar: toolNode.avatar,
toolSetId: pluginId toolSetId: pluginId,
toolsetName: toolNode.name
}); });
return newToolNode; return newToolNode;
}); });
......
...@@ -2,7 +2,7 @@ import type { localeType } from '@fastgpt/global/common/i18n/type'; ...@@ -2,7 +2,7 @@ import type { localeType } from '@fastgpt/global/common/i18n/type';
import type { SkillToolType } from '@fastgpt/global/core/ai/skill/type'; import type { SkillToolType } from '@fastgpt/global/core/ai/skill/type';
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type'; import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import type { SubAppRuntimeType } from './type'; import type { SubAppRuntimeType } from './type';
import { agentSkillToToolRuntime } from './sub/tool/utils'; import { getAgentRuntimeTools } from './sub/tool/utils';
import { readFileTool } from './sub/file/utils'; import { readFileTool } from './sub/file/utils';
import { PlanAgentTool } from './sub/plan/constants'; import { PlanAgentTool } from './sub/plan/constants';
import { datasetSearchTool } from './sub/dataset/utils'; import { datasetSearchTool } from './sub/dataset/utils';
...@@ -43,7 +43,7 @@ export const getSubapps = async ({ ...@@ -43,7 +43,7 @@ export const getSubapps = async ({
} }
/* System tool */ /* System tool */
const formatTools = await agentSkillToToolRuntime({ const formatTools = await getAgentRuntimeTools({
tools, tools,
tmbId, tmbId,
lang lang
......
...@@ -19,7 +19,6 @@ import { getSystemToolById } from '../../../app/tool/controller'; ...@@ -19,7 +19,6 @@ import { getSystemToolById } from '../../../app/tool/controller';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { pushTrack } from '../../../../common/middle/tracks/utils'; import { pushTrack } from '../../../../common/middle/tracks/utils';
import { getNodeErrResponse } from '../utils'; import { getNodeErrResponse } from '../utils';
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
import { getAppVersionById } from '../../../../core/app/version/controller'; import { getAppVersionById } from '../../../../core/app/version/controller';
import { runHTTPTool } from '../../../app/http'; import { runHTTPTool } from '../../../app/http';
import { getS3ChatSource } from '../../../../common/s3/sources/chat'; import { getS3ChatSource } from '../../../../common/s3/sources/chat';
...@@ -207,8 +206,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo ...@@ -207,8 +206,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
}; };
} else if (toolConfig?.mcpTool?.toolId) { } else if (toolConfig?.mcpTool?.toolId) {
// pluginId: toolSetAppId/toolsetName/toolName // pluginId: toolSetAppId/toolsetName/toolName
const { pluginId } = splitCombineToolId(toolConfig.mcpTool.toolId); const { parentId, toolName } = parseToolId(toolConfig.mcpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
const tool = await getAppVersionById({ const tool = await getAppVersionById({
appId: parentId, appId: parentId,
versionId: version versionId: version
...@@ -241,8 +239,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo ...@@ -241,8 +239,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
[DispatchNodeResponseKeyEnum.toolResponses]: result [DispatchNodeResponseKeyEnum.toolResponses]: result
}; };
} else if (toolConfig?.httpTool?.toolId) { } else if (toolConfig?.httpTool?.toolId) {
const { pluginId } = splitCombineToolId(toolConfig.httpTool.toolId); const { parentId, toolName } = parseToolId(toolConfig.httpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
const toolset = await getAppVersionById({ const toolset = await getAppVersionById({
appId: parentId, appId: parentId,
versionId: version versionId: version
...@@ -347,3 +344,16 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo ...@@ -347,3 +344,16 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
}); });
} }
}; };
export const parseToolId = (id: string) => {
const formatId = id.split('-').slice(1).join('-');
const [parentId, toolsetNameOrToolName, legacyToolName] = formatId.split('/');
if (legacyToolName) {
// 旧版格式: source-appId/toolsetName/toolName
return { parentId, toolName: legacyToolName };
}
// 新版格式: source-appId/toolName
return { parentId, toolName: toolsetNameOrToolName };
};
...@@ -14,18 +14,22 @@ import { ...@@ -14,18 +14,22 @@ import {
storeNodes2RuntimeNodes storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils'; } from '@fastgpt/global/core/workflow/runtime/utils';
import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { authPluginByTmbId } from '../../../../support/permission/app/auth'; import { authWorkflowToolByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { computedAppToolUsage } from '../../../app/tool/runtime/utils'; import { computedAppToolUsage } from '../../../app/tool/runtime/utils';
import { filterSystemVariables, getNodeErrResponse } from '../utils'; import { filterSystemVariables, getNodeErrResponse } from '../utils';
import { serverGetWorkflowToolRunUserQuery } from '../../../app/tool/workflowTool/utils'; import { serverGetWorkflowToolRunUserQuery } from '../../../app/tool/workflowTool/utils';
import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import {
import { getChildAppRuntimeById } from '../../../app/tool/controller'; type NodeInputKeyEnum,
type NodeOutputKeyEnum
} from '@fastgpt/global/core/workflow/constants';
import { runWorkflow } from '../index'; import { runWorkflow } from '../index';
import { getUserChatInfo } from '../../../../support/user/team/utils'; import { getUserChatInfo } from '../../../../support/user/team/utils';
import { dispatchRunTool } from '../child/runTool'; import { dispatchRunTool } from '../child/runTool';
import type { AppToolRuntimeType } from '@fastgpt/global/core/app/tool/type'; import type { AppToolRuntimeType } from '@fastgpt/global/core/app/tool/type';
import { anyValueDecrypt } from '../../../../common/secret/utils'; import { anyValueDecrypt } from '../../../../common/secret/utils';
import { getAppVersionById } from '../../../app/version/controller';
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
type RunPluginProps = ModuleDispatchProps<{ type RunPluginProps = ModuleDispatchProps<{
[NodeInputKeyEnum.forbidStream]?: boolean; [NodeInputKeyEnum.forbidStream]?: boolean;
...@@ -51,7 +55,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -51,7 +55,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
return getNodeErrResponse({ error: 'pluginId can not find' }); return getNodeErrResponse({ error: 'pluginId can not find' });
} }
let plugin: AppToolRuntimeType | undefined; let workflowTool: AppToolRuntimeType | undefined;
try { try {
// Adapt <= 4.10 system tool // Adapt <= 4.10 system tool
...@@ -70,33 +74,55 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -70,33 +74,55 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
}); });
} }
if (source !== AppToolSourceEnum.commercial && source !== AppToolSourceEnum.personal) {
return getNodeErrResponse({ error: 'pluginId can not find' });
}
/* /*
1. Team app 1. Team app
2. Admin selected system tool 2. Admin selected system tool
*/ */
const { files } = chatValue2RuntimePrompt(query); const { files } = chatValue2RuntimePrompt(query);
// auth plugin // auth workflowTool
const pluginData = await authPluginByTmbId({ const toolData = await authWorkflowToolByTmbId({
appId: pluginId, appId: formatPluginId,
tmbId: runningAppInfo.tmbId, tmbId: runningAppInfo.tmbId,
per: ReadPermissionVal per: ReadPermissionVal
}); });
plugin = await getChildAppRuntimeById({ id: pluginId, versionId: version }); const toolVersion = await getAppVersionById({
appId: toolData._id,
versionId: version,
app: toolData
});
workflowTool = {
id: String(toolData._id),
teamId: toolData.teamId,
tmbId: toolData.tmbId,
name: parseI18nString(toolData.name, props.lang),
avatar: toolData.avatar || '',
showStatus: true,
currentCost: 0,
systemKeyCost: 0,
nodes: toolVersion.nodes,
edges: toolVersion.edges,
hasTokenFee: false
};
const outputFilterMap = const outputFilterMap =
plugin.nodes workflowTool.nodes
.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginOutput) .find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginOutput)
?.inputs.reduce<Record<string, boolean>>((acc, cur) => { ?.inputs.reduce<Record<string, boolean>>((acc, cur) => {
acc[cur.key] = cur.isToolOutput === false ? false : true; acc[cur.key] = cur.isToolOutput === false ? false : true;
return acc; return acc;
}, {}) ?? {}; }, {}) ?? {};
const runtimeNodes = storeNodes2RuntimeNodes( const runtimeNodes = storeNodes2RuntimeNodes(
plugin.nodes, workflowTool.nodes,
getWorkflowEntryNodeIds(plugin.nodes) getWorkflowEntryNodeIds(workflowTool.nodes)
).map((node) => { ).map((node) => {
// Update plugin input value // Update workflowTool input value
if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) { if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) {
return { return {
...node, ...node,
...@@ -129,7 +155,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -129,7 +155,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
const { externalProvider } = await getUserChatInfo(runningAppInfo.tmbId); const { externalProvider } = await getUserChatInfo(runningAppInfo.tmbId);
const runtimeVariables = { const runtimeVariables = {
...filterSystemVariables(props.variables), ...filterSystemVariables(props.variables),
appId: String(plugin.id), appId: String(workflowTool.id),
...(externalProvider ? externalProvider.externalWorkflowVariables : {}) ...(externalProvider ? externalProvider.externalWorkflowVariables : {})
}; };
const { const {
...@@ -150,27 +176,27 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -150,27 +176,27 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
} }
: {}), : {}),
runningAppInfo: { runningAppInfo: {
id: String(plugin.id), id: String(workflowTool.id),
name: plugin.name, name: workflowTool.name,
// 如果系统插件有 teamId 和 tmbId,则使用系统插件的 teamId 和 tmbId(管理员指定了插件作为系统插件) // 如果系统插件有 teamId 和 tmbId,则使用系统插件的 teamId 和 tmbId(管理员指定了插件作为系统插件)
teamId: plugin.teamId || runningAppInfo.teamId, teamId: workflowTool.teamId || runningAppInfo.teamId,
tmbId: plugin.tmbId || runningAppInfo.tmbId, tmbId: workflowTool.tmbId || runningAppInfo.tmbId,
isChildApp: true isChildApp: true
}, },
variables: runtimeVariables, variables: runtimeVariables,
query: serverGetWorkflowToolRunUserQuery({ query: serverGetWorkflowToolRunUserQuery({
pluginInputs: getWorkflowToolInputsFromStoreNodes(plugin.nodes), pluginInputs: getWorkflowToolInputsFromStoreNodes(workflowTool.nodes),
variables: runtimeVariables, variables: runtimeVariables,
files files
}).value, }).value,
chatConfig: {}, chatConfig: {},
runtimeNodes, runtimeNodes,
runtimeEdges: storeEdges2RuntimeEdges(plugin.edges) runtimeEdges: storeEdges2RuntimeEdges(workflowTool.edges)
}); });
const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput); const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput);
const usagePoints = await computedAppToolUsage({ const usagePoints = await computedAppToolUsage({
plugin, plugin: workflowTool,
childrenUsage: flowUsages, childrenUsage: flowUsages,
error: !!output?.pluginOutput?.error error: !!output?.pluginOutput?.error
}); });
...@@ -182,20 +208,17 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -182,20 +208,17 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
// responseData, // debug // responseData, // debug
[DispatchNodeResponseKeyEnum.runTimes]: runTimes, [DispatchNodeResponseKeyEnum.runTimes]: runTimes,
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: plugin.avatar, moduleLogo: workflowTool.avatar,
totalPoints: usagePoints, totalPoints: usagePoints,
toolInput: data, toolInput: data,
pluginOutput: output?.pluginOutput, pluginOutput: output?.pluginOutput,
pluginDetail: pluginData?.permission?.hasWritePer // Not system plugin pluginDetail: toolData?.permission?.hasWritePer // Not system workflowTool
? flowResponses.filter((item) => { ? flowResponses
const filterArr = [FlowNodeTypeEnum.pluginOutput];
return !filterArr.includes(item.moduleType as any);
})
: undefined : undefined
}, },
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{ {
moduleName: plugin.name, moduleName: workflowTool.name,
totalPoints: usagePoints totalPoints: usagePoints
} }
], ],
...@@ -212,7 +235,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -212,7 +235,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
} catch (error) { } catch (error) {
return getNodeErrResponse({ return getNodeErrResponse({
error, error,
[DispatchNodeResponseKeyEnum.nodeResponse]: { moduleLogo: plugin?.avatar } [DispatchNodeResponseKeyEnum.nodeResponse]: { moduleLogo: workflowTool?.avatar }
}); });
} }
}; };
...@@ -332,34 +332,29 @@ export const rewriteRuntimeWorkFlow = async ({ ...@@ -332,34 +332,29 @@ export const rewriteRuntimeWorkFlow = async ({
if (!app) continue; if (!app) continue;
const toolList = await getMCPChildren(app); const toolList = await getMCPChildren(app);
// mcpToolsetVal.toolId-旧版 MCP // mcpToolsetVal.toolId: 旧版 MCP
const toolSetId = mcpToolsetVal.toolId ?? toolSetNode.pluginId; const toolSetId = mcpToolsetVal.toolId || toolSetNode.pluginId;
toolList.forEach((tool, index) => { toolList.forEach((tool, index) => {
const newToolNode = getMCPToolRuntimeNode({ const newToolNode = getMCPToolRuntimeNode({
nodeId: `${toolSetNode.nodeId}${index}`, nodeId: `${toolSetNode.nodeId}${index}`,
toolSetId, toolSetId,
toolsetName: toolSetNode.name,
avatar: toolSetNode.avatar, avatar: toolSetNode.avatar,
tool: { tool
...tool,
name: `${toolSetNode.name}/${tool.name}`
}
}); });
nodes.push(newToolNode); nodes.push(newToolNode);
pushEdges(newToolNode.nodeId); pushEdges(newToolNode.nodeId);
}); });
} else if (httpToolsetVal) { } else if (httpToolsetVal) {
httpToolsetVal.toolList.forEach((tool: HttpToolConfigType, index: number) => { httpToolsetVal.toolList.forEach((tool: HttpToolConfigType, index: number) => {
const newToolNode = getHTTPToolRuntimeNode({ const newToolNode = getHTTPToolRuntimeNode({
tool: { tool,
...tool,
name: `${toolSetNode.name}/${tool.name}`
},
nodeId: `${toolSetNode.nodeId}${index}`, nodeId: `${toolSetNode.nodeId}${index}`,
avatar: toolSetNode.avatar, avatar: toolSetNode.avatar,
toolSetId: toolSetNode.pluginId! toolSetId: toolSetNode.pluginId!,
toolsetName: toolSetNode.name
}); });
nodes.push(newToolNode); nodes.push(newToolNode);
pushEdges(newToolNode.nodeId); pushEdges(newToolNode.nodeId);
}); });
......
...@@ -15,12 +15,11 @@ import { type PermissionValueType } from '@fastgpt/global/support/permission/typ ...@@ -15,12 +15,11 @@ import { type PermissionValueType } from '@fastgpt/global/support/permission/typ
import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { type AuthModeType, type AuthResponseType } from '../type'; import { type AuthModeType, type AuthResponseType } from '../type';
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant'; import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant';
import { parseHeaderCert } from '../auth/common'; import { parseHeaderCert } from '../auth/common';
import { sumPer } from '@fastgpt/global/support/permission/utils'; import { sumPer } from '@fastgpt/global/support/permission/utils';
export const authPluginByTmbId = async ({ export const authWorkflowToolByTmbId = async ({
tmbId, tmbId,
appId, appId,
per per
...@@ -29,16 +28,12 @@ export const authPluginByTmbId = async ({ ...@@ -29,16 +28,12 @@ export const authPluginByTmbId = async ({
appId: string; appId: string;
per: PermissionValueType; per: PermissionValueType;
}) => { }) => {
const { authAppId } = splitCombineToolId(appId); const { app } = await authAppByTmbId({
if (authAppId) { appId,
const { app } = await authAppByTmbId({ tmbId,
appId: authAppId, per
tmbId, });
per return app;
});
return app;
}
}; };
export const authAppByTmbId = async ({ export const authAppByTmbId = async ({
......
...@@ -177,7 +177,7 @@ ...@@ -177,7 +177,7 @@
"code_error.outlink_error.invalid_link": "Invalid Share Link", "code_error.outlink_error.invalid_link": "Invalid Share Link",
"code_error.outlink_error.link_not_exist": "Share Link Does Not Exist", "code_error.outlink_error.link_not_exist": "Share Link Does Not Exist",
"code_error.outlink_error.un_auth_user": "Identity Verification Failed", "code_error.outlink_error.un_auth_user": "Identity Verification Failed",
"code_error.plugin_error.not_exist": "Tool deleted", "error.tool_not_exist": "Tool deleted",
"code_error.plugin_error.un_auth": "No permission to operate the tool", "code_error.plugin_error.un_auth": "No permission to operate the tool",
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://fastgpt.io", "code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://fastgpt.io",
"code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system", "code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system",
......
...@@ -177,7 +177,7 @@ ...@@ -177,7 +177,7 @@
"code_error.outlink_error.invalid_link": "分享链接无效", "code_error.outlink_error.invalid_link": "分享链接无效",
"code_error.outlink_error.link_not_exist": "分享链接不存在", "code_error.outlink_error.link_not_exist": "分享链接不存在",
"code_error.outlink_error.un_auth_user": "身份校验失败", "code_error.outlink_error.un_auth_user": "身份校验失败",
"code_error.plugin_error.not_exist": "工具已删除", "error.tool_not_exist": "工具已删除",
"code_error.plugin_error.un_auth": "无权操作该工具", "code_error.plugin_error.un_auth": "无权操作该工具",
"code_error.system_error.community_version_num_limit": "超出社区版数量限制,请升级商业版: https://fastgpt.in", "code_error.system_error.community_version_num_limit": "超出社区版数量限制,请升级商业版: https://fastgpt.in",
"code_error.system_error.license_app_amount_limit": "超出系统最大应用数量", "code_error.system_error.license_app_amount_limit": "超出系统最大应用数量",
......
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
"code_error.outlink_error.invalid_link": "分享連結無效", "code_error.outlink_error.invalid_link": "分享連結無效",
"code_error.outlink_error.link_not_exist": "分享連結不存在", "code_error.outlink_error.link_not_exist": "分享連結不存在",
"code_error.outlink_error.un_auth_user": "身份驗證失敗", "code_error.outlink_error.un_auth_user": "身份驗證失敗",
"code_error.plugin_error.not_exist": "工具已刪除", "error.tool_not_exist": "工具已刪除",
"code_error.plugin_error.un_auth": "無權操作該工具", "code_error.plugin_error.un_auth": "無權操作該工具",
"code_error.system_error.community_version_num_limit": "超出開源版數量限制,請升級商業版:https://fastgpt.io", "code_error.system_error.community_version_num_limit": "超出開源版數量限制,請升級商業版:https://fastgpt.io",
"code_error.system_error.license_app_amount_limit": "超出系統最大應用數量", "code_error.system_error.license_app_amount_limit": "超出系統最大應用數量",
......
{ {
"name": "app", "name": "app",
"version": "4.14.8", "version": "4.14.8.1",
"private": false, "private": false,
"scripts": { "scripts": {
"dev": "npm run build:workers && next dev", "dev": "npm run build:workers && next dev",
......
...@@ -55,8 +55,7 @@ async function handler( ...@@ -55,8 +55,7 @@ async function handler(
toolList, toolList,
name, name,
avatar, avatar,
headerSecret: formatedHeaderAuth, headerSecret: formatedHeaderAuth
toolId: ''
}) })
], ],
session session
......
...@@ -28,8 +28,7 @@ async function handler(req: ApiRequestProps<UpdateMcpToolsBodyType>, res: ApiRes ...@@ -28,8 +28,7 @@ async function handler(req: ApiRequestProps<UpdateMcpToolsBodyType>, res: ApiRes
toolList, toolList,
headerSecret: formatedHeaderAuth, headerSecret: formatedHeaderAuth,
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar
toolId: ''
}); });
await mongoSessionRun(async (session) => { await mongoSessionRun(async (session) => {
......
...@@ -39,7 +39,7 @@ async function handler( ...@@ -39,7 +39,7 @@ async function handler(
// System tool plugin // System tool plugin
if (source === AppToolSourceEnum.systemTool) { if (source === AppToolSourceEnum.systemTool) {
const item = await getSystemToolByIdAndVersionId(formatPluginId); const item = await getSystemToolByIdAndVersionId(pluginId);
return { return {
total: 0, total: 0,
......
...@@ -103,14 +103,15 @@ describe('httpTool utils', () => { ...@@ -103,14 +103,15 @@ describe('httpTool utils', () => {
const result = getHTTPToolRuntimeNode({ const result = getHTTPToolRuntimeNode({
tool, tool,
nodeId: 'node-123', nodeId: 'node-123',
toolSetId: 'toolset-456' toolSetId: 'toolset-456',
toolsetName: 'toolsetName'
}); });
expect(result.nodeId).toBe('node-123'); expect(result.nodeId).toBe('node-123');
expect(result.flowNodeType).toBe(FlowNodeTypeEnum.tool); expect(result.flowNodeType).toBe(FlowNodeTypeEnum.tool);
expect(result.avatar).toBe('core/app/type/httpToolsFill'); expect(result.avatar).toBe('core/app/type/httpToolsFill');
expect(result.intro).toBe('Search for items'); expect(result.intro).toBe('Search for items');
expect(result.name).toBe('searchTool'); expect(result.name).toBe('toolsetName/searchTool');
expect(result.toolConfig?.httpTool?.toolId).toBe( expect(result.toolConfig?.httpTool?.toolId).toBe(
`${AppToolSourceEnum.http}-toolset-456/searchTool` `${AppToolSourceEnum.http}-toolset-456/searchTool`
); );
...@@ -128,7 +129,8 @@ describe('httpTool utils', () => { ...@@ -128,7 +129,8 @@ describe('httpTool utils', () => {
tool, tool,
nodeId: 'node-789', nodeId: 'node-789',
avatar: 'custom-icon', avatar: 'custom-icon',
toolSetId: 'toolset-abc' toolSetId: 'toolset-abc',
toolsetName: 'toolsetName'
}); });
expect(result.avatar).toBe('custom-icon'); expect(result.avatar).toBe('custom-icon');
...@@ -145,7 +147,8 @@ describe('httpTool utils', () => { ...@@ -145,7 +147,8 @@ describe('httpTool utils', () => {
const result = getHTTPToolRuntimeNode({ const result = getHTTPToolRuntimeNode({
tool, tool,
nodeId: 'node-001', nodeId: 'node-001',
toolSetId: 'toolset-002' toolSetId: 'toolset-002',
toolsetName: 'toolsetName'
}); });
const rawResponseOutput = result.outputs.find((o) => o.key === NodeOutputKeyEnum.rawResponse); const rawResponseOutput = result.outputs.find((o) => o.key === NodeOutputKeyEnum.rawResponse);
......
...@@ -28,8 +28,7 @@ describe('mcpTool utils', () => { ...@@ -28,8 +28,7 @@ describe('mcpTool utils', () => {
const result = getMCPToolSetRuntimeNode({ const result = getMCPToolSetRuntimeNode({
url: 'https://mcp.example.com/api', url: 'https://mcp.example.com/api',
toolList, toolList
toolId: 'mcp-tool-123'
}); });
expect(result.flowNodeType).toBe(FlowNodeTypeEnum.toolSet); expect(result.flowNodeType).toBe(FlowNodeTypeEnum.toolSet);
...@@ -40,7 +39,6 @@ describe('mcpTool utils', () => { ...@@ -40,7 +39,6 @@ describe('mcpTool utils', () => {
expect(result.nodeId).toHaveLength(16); expect(result.nodeId).toHaveLength(16);
expect(result.toolConfig?.mcpToolSet?.url).toBe('https://mcp.example.com/api'); expect(result.toolConfig?.mcpToolSet?.url).toBe('https://mcp.example.com/api');
expect(result.toolConfig?.mcpToolSet?.toolList).toEqual(toolList); expect(result.toolConfig?.mcpToolSet?.toolList).toEqual(toolList);
expect(result.toolConfig?.mcpToolSet?.toolId).toBe('mcp-tool-123');
}); });
it('should create runtime node with all optional params', () => { it('should create runtime node with all optional params', () => {
...@@ -49,7 +47,6 @@ describe('mcpTool utils', () => { ...@@ -49,7 +47,6 @@ describe('mcpTool utils', () => {
const result = getMCPToolSetRuntimeNode({ const result = getMCPToolSetRuntimeNode({
url: 'https://mcp.example.com/api', url: 'https://mcp.example.com/api',
toolList, toolList,
toolId: 'mcp-tool-456',
name: 'My MCP Tools', name: 'My MCP Tools',
avatar: 'custom-mcp-avatar', avatar: 'custom-mcp-avatar',
headerSecret: { id: 'secret-1', key: 'Authorization' } headerSecret: { id: 'secret-1', key: 'Authorization' }
...@@ -81,14 +78,15 @@ describe('mcpTool utils', () => { ...@@ -81,14 +78,15 @@ describe('mcpTool utils', () => {
const result = getMCPToolRuntimeNode({ const result = getMCPToolRuntimeNode({
tool, tool,
nodeId: 'node-123', nodeId: 'node-123',
toolSetId: 'toolset-456' toolSetId: 'toolset-456',
toolsetName: 'toolsetName'
}); });
expect(result.nodeId).toBe('node-123'); expect(result.nodeId).toBe('node-123');
expect(result.flowNodeType).toBe(FlowNodeTypeEnum.tool); expect(result.flowNodeType).toBe(FlowNodeTypeEnum.tool);
expect(result.avatar).toBe('core/app/type/mcpToolsFill'); expect(result.avatar).toBe('core/app/type/mcpToolsFill');
expect(result.intro).toBe('Search for information'); expect(result.intro).toBe('Search for information');
expect(result.name).toBe('searchTool'); expect(result.name).toBe('toolsetName/searchTool');
expect(result.toolConfig?.mcpTool?.toolId).toBe( expect(result.toolConfig?.mcpTool?.toolId).toBe(
`${AppToolSourceEnum.mcp}-toolset-456/searchTool` `${AppToolSourceEnum.mcp}-toolset-456/searchTool`
); );
...@@ -105,7 +103,8 @@ describe('mcpTool utils', () => { ...@@ -105,7 +103,8 @@ describe('mcpTool utils', () => {
tool, tool,
nodeId: 'node-789', nodeId: 'node-789',
avatar: 'custom-icon', avatar: 'custom-icon',
toolSetId: 'toolset-abc' toolSetId: 'toolset-abc',
toolsetName: 'toolsetName'
}); });
expect(result.avatar).toBe('custom-icon'); expect(result.avatar).toBe('custom-icon');
...@@ -121,7 +120,8 @@ describe('mcpTool utils', () => { ...@@ -121,7 +120,8 @@ describe('mcpTool utils', () => {
const result = getMCPToolRuntimeNode({ const result = getMCPToolRuntimeNode({
tool, tool,
nodeId: 'node-001', nodeId: 'node-001',
toolSetId: 'toolset-002' toolSetId: 'toolset-002',
toolsetName: 'toolsetName'
}); });
expect(result.outputs).toHaveLength(1); expect(result.outputs).toHaveLength(1);
...@@ -142,7 +142,8 @@ describe('mcpTool utils', () => { ...@@ -142,7 +142,8 @@ describe('mcpTool utils', () => {
const result = getMCPToolRuntimeNode({ const result = getMCPToolRuntimeNode({
tool, tool,
nodeId: 'node-test', nodeId: 'node-test',
toolSetId: 'parent-123' toolSetId: 'parent-123',
toolsetName: 'toolsetName'
}); });
expect(result.toolConfig?.mcpTool?.toolId).toBe('mcp-parent-123/myTool'); expect(result.toolConfig?.mcpTool?.toolId).toBe('mcp-parent-123/myTool');
......
...@@ -25,7 +25,6 @@ describe('splitCombineToolId', () => { ...@@ -25,7 +25,6 @@ describe('splitCombineToolId', () => {
expect(result.source).toBe(AppToolSourceEnum.personal); expect(result.source).toBe(AppToolSourceEnum.personal);
expect(result.pluginId).toBe('507f1f77bcf86cd799439011'); expect(result.pluginId).toBe('507f1f77bcf86cd799439011');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
}); });
}); });
...@@ -34,14 +33,14 @@ describe('splitCombineToolId', () => { ...@@ -34,14 +33,14 @@ describe('splitCombineToolId', () => {
const result = splitCombineToolId('commercial-507f1f77bcf86cd799439011'); const result = splitCombineToolId('commercial-507f1f77bcf86cd799439011');
expect(result.source).toBe(AppToolSourceEnum.commercial); expect(result.source).toBe(AppToolSourceEnum.commercial);
expect(result.pluginId).toBe('commercial-507f1f77bcf86cd799439011'); expect(result.pluginId).toBe('507f1f77bcf86cd799439011');
}); });
it('should convert commercial-dalle3 to systemTool', () => { it('should convert commercial-dalle3 to systemTool(Adapt)', () => {
const result = splitCombineToolId('commercial-dalle3'); const result = splitCombineToolId('commercial-dalle3');
expect(result.source).toBe(AppToolSourceEnum.systemTool); expect(result.source).toBe(AppToolSourceEnum.systemTool);
expect(result.pluginId).toBe('systemTool-dalle3'); expect(result.pluginId).toBe('dalle3');
}); });
}); });
...@@ -50,14 +49,14 @@ describe('splitCombineToolId', () => { ...@@ -50,14 +49,14 @@ describe('splitCombineToolId', () => {
const result = splitCombineToolId('systemTool-websearch'); const result = splitCombineToolId('systemTool-websearch');
expect(result.source).toBe(AppToolSourceEnum.systemTool); expect(result.source).toBe(AppToolSourceEnum.systemTool);
expect(result.pluginId).toBe('systemTool-websearch'); expect(result.pluginId).toBe('websearch');
}); });
it('should handle systemTool with complex id', () => { it('should handle systemTool with complex id', () => {
const result = splitCombineToolId('systemTool-code-interpreter'); const result = splitCombineToolId('systemTool-code-interpreter');
expect(result.source).toBe(AppToolSourceEnum.systemTool); expect(result.source).toBe(AppToolSourceEnum.systemTool);
expect(result.pluginId).toBe('systemTool-code-interpreter'); expect(result.pluginId).toBe('code-interpreter');
}); });
}); });
...@@ -85,7 +84,7 @@ describe('splitCombineToolId', () => { ...@@ -85,7 +84,7 @@ describe('splitCombineToolId', () => {
expect(result.source).toBe(AppToolSourceEnum.http); expect(result.source).toBe(AppToolSourceEnum.http);
expect(result.pluginId).toBe('507f1f77bcf86cd799439011'); expect(result.pluginId).toBe('507f1f77bcf86cd799439011');
expect(result.parentId).toBe('507f1f77bcf86cd799439011'); expect(result.authAppId).toBe('507f1f77bcf86cd799439011');
}); });
it('should parse http-parentId/toolName format correctly', () => { it('should parse http-parentId/toolName format correctly', () => {
...@@ -93,7 +92,6 @@ describe('splitCombineToolId', () => { ...@@ -93,7 +92,6 @@ describe('splitCombineToolId', () => {
expect(result.source).toBe(AppToolSourceEnum.http); expect(result.source).toBe(AppToolSourceEnum.http);
expect(result.pluginId).toBe('507f1f77bcf86cd799439011/apiTool'); expect(result.pluginId).toBe('507f1f77bcf86cd799439011/apiTool');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
}); });
}); });
...@@ -102,13 +100,13 @@ describe('splitCombineToolId', () => { ...@@ -102,13 +100,13 @@ describe('splitCombineToolId', () => {
const result = splitCombineToolId('community-oldPlugin'); const result = splitCombineToolId('community-oldPlugin');
expect(result.source).toBe(AppToolSourceEnum.systemTool); expect(result.source).toBe(AppToolSourceEnum.systemTool);
expect(result.pluginId).toBe('systemTool-oldPlugin'); expect(result.pluginId).toBe('oldPlugin');
}); });
}); });
describe('error handling', () => { describe('error handling', () => {
it('should throw error when pluginId is empty after split', () => { it('should throw error when pluginId is empty after split', () => {
expect(() => splitCombineToolId('commercial-')).toThrow('pluginId not found'); expect(() => splitCombineToolId('commercial-')).toThrow('toolId not found');
}); });
}); });
}); });
...@@ -121,7 +119,7 @@ describe('getToolRawId', () => { ...@@ -121,7 +119,7 @@ describe('getToolRawId', () => {
it('should return pluginId for commercial tool', () => { it('should return pluginId for commercial tool', () => {
const result = getToolRawId('commercial-507f1f77bcf86cd799439011'); const result = getToolRawId('commercial-507f1f77bcf86cd799439011');
expect(result).toBe('commercial-507f1f77bcf86cd799439011'); expect(result).toBe('507f1f77bcf86cd799439011');
}); });
it('should return parentId for mcp tool with toolName', () => { it('should return parentId for mcp tool with toolName', () => {
...@@ -136,7 +134,7 @@ describe('getToolRawId', () => { ...@@ -136,7 +134,7 @@ describe('getToolRawId', () => {
it('should handle systemTool correctly', () => { it('should handle systemTool correctly', () => {
const result = getToolRawId('systemTool-websearch'); const result = getToolRawId('systemTool-websearch');
expect(result).toBe('systemTool-websearch'); expect(result).toBe('websearch');
}); });
it('should handle personal tool with prefix', () => { it('should handle personal tool with prefix', () => {
...@@ -146,6 +144,6 @@ describe('getToolRawId', () => { ...@@ -146,6 +144,6 @@ describe('getToolRawId', () => {
it('should handle converted community tool', () => { it('should handle converted community tool', () => {
const result = getToolRawId('community-oldPlugin'); const result = getToolRawId('community-oldPlugin');
expect(result).toBe('systemTool-oldPlugin'); expect(result).toBe('oldPlugin');
}); });
}); });
import { describe, it, expect } from 'vitest';
import { parseToolId } from '@fastgpt/service/core/workflow/dispatch/child/runTool';
describe('parseToolId', () => {
describe('新版格式: source-appId/toolName', () => {
it('should parse mcp tool format correctly', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011/apiTool');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('apiTool');
});
it('should parse http tool format correctly', () => {
const result = parseToolId('http-507f1f77bcf86cd799439011/weatherAPI');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('weatherAPI');
});
it('should handle tool names with special characters', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011/api-tool_v2');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('api-tool_v2');
});
it('should handle tool names with numbers', () => {
const result = parseToolId('http-507f1f77bcf86cd799439011/tool123');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('tool123');
});
});
describe('旧版格式: source-appId/toolsetName/toolName', () => {
it('should parse old mcp format correctly', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011/toolset/apiTool');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('apiTool');
});
it('should parse old http format correctly', () => {
const result = parseToolId('http-507f1f77bcf86cd799439011/MyToolset/weatherAPI');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('weatherAPI');
});
it('should ignore toolset name in old format', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011/ignoredToolset/actualTool');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('actualTool');
// toolsetName 应该被忽略
});
it('should handle toolset names with special characters', () => {
const result = parseToolId('http-507f1f77bcf86cd799439011/tool-set_v1/myTool');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('myTool');
});
});
describe('边界情况', () => {
it('should handle appId with hyphens', () => {
const result = parseToolId('mcp-507f-1f77-bcf8-6cd7-99439011/tool');
expect(result.parentId).toBe('507f-1f77-bcf8-6cd7-99439011');
expect(result.toolName).toBe('tool');
});
it('should handle multiple hyphens in source prefix', () => {
const result = parseToolId('custom-source-507f1f77bcf86cd799439011/tool');
expect(result.parentId).toBe('source-507f1f77bcf86cd799439011');
expect(result.toolName).toBe('tool');
});
it('should handle tool names with slashes in old format', () => {
// 注意: split('/') 只会分割成三个部分,所以第三个部分是 'tool'
const result = parseToolId('mcp-507f1f77bcf86cd799439011/toolset/tool/extra');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
// 实际上 split('/') 会得到 ['507f1f77bcf86cd799439011', 'toolset', 'tool/extra']
// 但由于解构赋值,legacyToolName 会是 'tool/extra'
// 等等,让我重新理解代码逻辑...
// formatId.split('/') 会得到 ['507f1f77bcf86cd799439011', 'toolset', 'tool', 'extra']
// 解构赋值只取前三个: parentId='507f1f77bcf86cd799439011', toolsetNameOrToolName='toolset', legacyToolName='tool'
// 所以 toolName 应该是 'tool',而不是 'tool/extra'
expect(result.toolName).toBe('tool');
});
it('should handle empty tool name', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011/');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('');
});
it('should handle empty toolset and tool name in old format', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011//');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('');
});
});
describe('向后兼容性', () => {
it('should correctly identify new format vs old format', () => {
// 新版格式: 只有两个部分(appId 和 toolName)
const newFormat = parseToolId('mcp-507f1f77bcf86cd799439011/tool');
expect(newFormat.toolName).toBe('tool');
// 旧版格式: 有三个部分(appId, toolsetName, toolName)
const oldFormat = parseToolId('mcp-507f1f77bcf86cd799439011/toolset/tool');
expect(oldFormat.toolName).toBe('tool');
// 两者的 toolName 应该相同
expect(newFormat.toolName).toBe(oldFormat.toolName);
});
it('should handle migration from old to new format', () => {
const oldId = 'mcp-507f1f77bcf86cd799439011/MyToolset/weatherAPI';
const newId = 'mcp-507f1f77bcf86cd799439011/weatherAPI';
const oldResult = parseToolId(oldId);
const newResult = parseToolId(newId);
// 两种格式应该解析出相同的 parentId 和 toolName
expect(oldResult.parentId).toBe(newResult.parentId);
expect(oldResult.toolName).toBe(newResult.toolName);
});
});
describe('真实场景测试', () => {
it('should parse real MCP tool ID', () => {
const result = parseToolId('mcp-65f8a9b2c3d4e5f6a7b8c9d0/filesystem/readFile');
expect(result.parentId).toBe('65f8a9b2c3d4e5f6a7b8c9d0');
expect(result.toolName).toBe('readFile');
});
it('should parse real HTTP tool ID', () => {
const result = parseToolId('http-65f8a9b2c3d4e5f6a7b8c9d0/WeatherAPI');
expect(result.parentId).toBe('65f8a9b2c3d4e5f6a7b8c9d0');
expect(result.toolName).toBe('WeatherAPI');
});
it('should handle Chinese tool names', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011/天气查询');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('天气查询');
});
it('should handle tool names with spaces', () => {
const result = parseToolId('http-507f1f77bcf86cd799439011/Weather API');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBe('Weather API');
});
});
describe('性能测试', () => {
it('should parse tool IDs efficiently', () => {
const iterations = 10000;
const start = Date.now();
for (let i = 0; i < iterations; i++) {
parseToolId('mcp-507f1f77bcf86cd799439011/toolset/tool');
}
const duration = Date.now() - start;
console.log(`Parsed ${iterations} tool IDs in ${duration}ms`);
// 性能检查: 10000 次解析应该在 100ms 内完成
expect(duration).toBeLessThan(100);
});
it('should handle batch parsing', () => {
const toolIds = [
'mcp-507f1f77bcf86cd799439011/tool1',
'http-507f1f77bcf86cd799439011/tool2',
'mcp-507f1f77bcf86cd799439011/toolset/tool3',
'http-507f1f77bcf86cd799439011/toolset/tool4'
];
const results = toolIds.map((id) => parseToolId(id));
expect(results).toHaveLength(4);
expect(results[0].toolName).toBe('tool1');
expect(results[1].toolName).toBe('tool2');
expect(results[2].toolName).toBe('tool3');
expect(results[3].toolName).toBe('tool4');
});
});
describe('错误处理', () => {
it('should handle missing slash', () => {
const result = parseToolId('mcp-507f1f77bcf86cd799439011');
expect(result.parentId).toBe('507f1f77bcf86cd799439011');
expect(result.toolName).toBeUndefined();
});
it('should handle only source prefix', () => {
const result = parseToolId('mcp-');
expect(result.parentId).toBe('');
expect(result.toolName).toBeUndefined();
});
it('should handle malformed ID gracefully', () => {
// 虽然这些是无效的 ID,但函数应该不会崩溃
expect(() => parseToolId('invalid')).not.toThrow();
expect(() => parseToolId('')).not.toThrow();
expect(() => parseToolId('/')).not.toThrow();
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment