diff --git a/lib/worker/loadTool.ts b/lib/worker/loadTool.ts index 57c1c66d..b15aebff 100644 --- a/lib/worker/loadTool.ts +++ b/lib/worker/loadTool.ts @@ -8,6 +8,7 @@ import { ToolTagEnum } from '@tool/type/tags'; import { existsSync } from 'fs'; import { readdir } from 'fs/promises'; import { join } from 'path'; +import { generateToolVersion, generateToolSetVersion } from '@tool/utils/tool'; const LoadToolsDev = async (filename: string): Promise => { if (isProd) { @@ -28,16 +29,6 @@ const LoadToolsDev = async (filename: string): Promise => { const parentIcon = rootMod.icon ?? getIconPath(`${toolsetId}/logo`); if (isToolSet) { - tools.push({ - ...rootMod, - tags: rootMod.tags || [ToolTagEnum.enum.other], - toolId: toolsetId, - icon: parentIcon, - toolFilename: filename, - cb: () => Promise.resolve({}), - versionList: [] - }); - const children: ToolType[] = []; { @@ -49,27 +40,54 @@ const LoadToolsDev = async (filename: string): Promise => { const toolId = childMod.toolId || `${toolsetId}/${file}`; const childIcon = childMod.icon ?? rootMod.icon ?? getIconPath(`${toolsetId}/${file}/logo`); + + // Generate version for child tool + const childVersion = childMod.versionList + ? generateToolVersion(childMod.versionList) + : generateToolVersion([]); + children.push({ ...childMod, toolId, toolFilename: filename, icon: childIcon, - parentId: toolsetId + parentId: toolsetId, + version: childVersion }); } } + // Generate version for tool set based on children + const toolSetVersion = generateToolSetVersion(children) ?? ''; + + tools.push({ + ...rootMod, + tags: rootMod.tags || [ToolTagEnum.enum.other], + toolId: toolsetId, + icon: parentIcon, + toolFilename: filename, + cb: () => Promise.resolve({}), + versionList: [], + version: toolSetVersion + }); + tools.push(...children); } else { // is not toolset const icon = rootMod.icon ?? getIconPath(`${toolsetId}/logo`); + // Generate version for single tool + const toolVersion = (rootMod as any).versionList + ? generateToolVersion((rootMod as any).versionList) + : generateToolVersion([]); + tools.push({ ...(rootMod as ToolType), tags: rootMod.tags || [ToolTagEnum.enum.other], toolId: toolsetId, icon, - toolFilename: filename + toolFilename: filename, + version: toolVersion }); } diff --git a/modules/tool/build/build-json.ts b/modules/tool/build/build-json.ts index 9a1f930e..0f3ba288 100644 --- a/modules/tool/build/build-json.ts +++ b/modules/tool/build/build-json.ts @@ -5,6 +5,7 @@ import { existsSync, writeFileSync } from 'fs'; import { readdir } from 'fs/promises'; import { join } from 'path'; import { ToolDetailSchema } from 'sdk/client'; +import { generateToolVersion, generateToolSetVersion } from '../utils/tool'; const filterToolList = ['.DS_Store', '.git', '.github', 'node_modules', 'dist', 'scripts']; @@ -26,16 +27,6 @@ const LoadToolsDev = async (filename: string): Promise => { const parentIcon = rootMod.icon ?? `${S3BasePath}${UploadToolsS3Path}/${toolsetId}/logo`; if (isToolSet) { - tools.push({ - ...rootMod, - tags: rootMod.tags || [ToolTagEnum.enum.other], - toolId: toolsetId, - icon: parentIcon, - toolFilename: filename, - cb: () => Promise.resolve({}), - versionList: [] - }); - const children: ToolType[] = []; { @@ -50,27 +41,54 @@ const LoadToolsDev = async (filename: string): Promise => { childMod.icon ?? rootMod.icon ?? `${S3BasePath}${UploadToolsS3Path}/${toolsetId}/${file}/logo`; + + // Generate version for child tool + const childVersion = childMod.versionList + ? generateToolVersion(childMod.versionList) + : generateToolVersion([]); + children.push({ ...childMod, toolId, toolFilename: filename, icon: childIcon, - parentId: toolsetId + parentId: toolsetId, + version: childVersion }); } } + // Generate version for tool set based on children + const toolSetVersion = generateToolSetVersion(children) ?? ''; + + tools.push({ + ...rootMod, + tags: rootMod.tags || [ToolTagEnum.enum.other], + toolId: toolsetId, + icon: parentIcon, + toolFilename: filename, + cb: () => Promise.resolve({}), + versionList: [], + version: toolSetVersion + }); + tools.push(...children); } else { // is not toolset const icon = rootMod.icon ?? `${S3BasePath}${UploadToolsS3Path}/${toolsetId}/logo`; + // Generate version for single tool + const toolVersion = (rootMod as any).versionList + ? generateToolVersion((rootMod as any).versionList) + : generateToolVersion([]); + tools.push({ ...(rootMod as ToolType), tags: rootMod.tags || [ToolTagEnum.enum.other], toolId: toolsetId, icon, - toolFilename: filename + toolFilename: filename, + version: toolVersion }); } diff --git a/modules/tool/init.ts b/modules/tool/init.ts index 556bc7ad..5fe6aded 100644 --- a/modules/tool/init.ts +++ b/modules/tool/init.ts @@ -82,11 +82,11 @@ export async function initTools() { } addLog.info(`Load Tools: ${toolMap.size}`); - isIniting = false; + global.isIniting = false; return toolMap; } catch (e) { addLog.error(`Init Tools Error:`, e); - isIniting = false; + global.isIniting = false; return getCachedData(SystemCacheKeyEnum.systemTool); } } diff --git a/modules/tool/loadToolDev.ts b/modules/tool/loadToolDev.ts index 83b660df..62c115ee 100644 --- a/modules/tool/loadToolDev.ts +++ b/modules/tool/loadToolDev.ts @@ -9,6 +9,7 @@ import type { ToolType, ToolSetType } from './type'; import { ToolTagEnum } from './type/tags'; import { publicS3Server } from '@/s3'; import { mimeMap } from '@/s3/const'; +import { generateToolVersion, generateToolSetVersion } from './utils/tool'; /** * Load Tools in dev mode. Only avaliable in dev mode @@ -82,16 +83,6 @@ export const LoadToolsDev = async (filename: string): Promise => { (await publicS3Server.generateExternalUrl(`${UploadToolsS3Path}/${toolsetId}/logo`)); if (isToolSet) { - tools.push({ - ...rootMod, - tags: rootMod.tags || [ToolTagEnum.enum.other], - toolId: toolsetId, - icon: parentIcon, - toolFilename: filename, - cb: () => Promise.resolve({}), - versionList: [] - }); - const children: ToolType[] = []; { @@ -161,16 +152,37 @@ export const LoadToolsDev = async (filename: string): Promise => { (await publicS3Server.generateExternalUrl( `${UploadToolsS3Path}/${toolsetId}/${file}/logo` )); + + // Generate version for child tool + const childVersion = childMod.versionList + ? generateToolVersion(childMod.versionList) + : generateToolVersion([]); + children.push({ ...childMod, toolId, toolFilename: filename, icon: childIcon, - parentId: toolsetId + parentId: toolsetId, + version: childVersion }); } } + // Generate version for tool set based on children + const toolSetVersion = generateToolSetVersion(children); + + tools.push({ + ...rootMod, + tags: rootMod.tags || [ToolTagEnum.enum.other], + toolId: toolsetId, + icon: parentIcon, + toolFilename: filename, + cb: () => Promise.resolve({}), + versionList: [], + version: toolSetVersion ?? '' + }); + tools.push(...children); } else { // is not toolset @@ -178,12 +190,18 @@ export const LoadToolsDev = async (filename: string): Promise => { rootMod.icon ?? (await publicS3Server.generateExternalUrl(`${UploadToolsS3Path}/${toolsetId}/logo`)); + // Generate version for single tool + const toolVersion = (rootMod as any).versionList + ? generateToolVersion((rootMod as any).versionList) + : generateToolVersion([]); + tools.push({ ...(rootMod as ToolType), tags: rootMod.tags || [ToolTagEnum.enum.other], toolId: toolsetId, icon, - toolFilename: filename + toolFilename: filename, + version: toolVersion }); } diff --git a/modules/tool/parseMod.ts b/modules/tool/parseMod.ts index e47a8115..d622c0bd 100644 --- a/modules/tool/parseMod.ts +++ b/modules/tool/parseMod.ts @@ -2,6 +2,7 @@ import { ToolTagEnum } from 'sdk/client'; import { UploadToolsS3Path } from './constants'; import type { ToolSetType, ToolType } from './type'; import { PublicBucketBaseURL } from '@/s3/const'; +import { generateToolVersion, generateToolSetVersion } from './utils/tool'; export const getIconPath = (name: string) => `${PublicBucketBaseURL}${UploadToolsS3Path}/${name}`; @@ -21,17 +22,6 @@ export const parseMod = async ({ const parentIcon = rootMod.icon || getIconPath(`${toolsetId}/logo`); - // push parent - tools.push({ - ...rootMod, - tags: rootMod.tags || [ToolTagEnum.enum.other], - toolId: toolsetId, - icon: parentIcon, - toolFilename: `${filename}`, - cb: () => Promise.resolve({}), - versionList: [] - }); - const children = rootMod.children; for (const child of children) { @@ -39,6 +29,8 @@ export const parseMod = async ({ const childIcon = child.icon || rootMod.icon || getIconPath(`${childToolId}/logo`); + // Generate version for child tool + const childVersion = generateToolVersion(child.versionList); tools.push({ ...child, toolId: childToolId, @@ -47,9 +39,22 @@ export const parseMod = async ({ courseUrl: rootMod.courseUrl, author: rootMod.author, icon: childIcon, - toolFilename: filename + toolFilename: filename, + version: childVersion }); } + + // push parent + tools.push({ + ...rootMod, + tags: rootMod.tags || [ToolTagEnum.enum.other], + toolId: toolsetId, + icon: parentIcon, + toolFilename: `${filename}`, + cb: () => Promise.resolve({}), + versionList: [], + version: generateToolSetVersion(children) || '' + }); } else { // is not toolset const toolId = rootMod.toolId; @@ -61,7 +66,8 @@ export const parseMod = async ({ tags: rootMod.tags || [ToolTagEnum.enum.tools], icon, toolId, - toolFilename: filename + toolFilename: filename, + version: generateToolVersion(rootMod.versionList) }); } return tools; diff --git a/modules/tool/type/tool.ts b/modules/tool/type/tool.ts index afc3c729..5cda6395 100644 --- a/modules/tool/type/tool.ts +++ b/modules/tool/type/tool.ts @@ -51,6 +51,7 @@ export const ToolSchema = ToolConfigWithCbSchema.extend({ parentId: z.string().optional().describe('The parent id of the tool'), toolFilename: z.string(), + version: z.string().describe('The version hash of the tool'), // ToolSet Parent secretInputConfig: z .array(InputConfigSchema) diff --git a/modules/tool/utils/tool.ts b/modules/tool/utils/tool.ts index 1534d84e..6b4c892c 100644 --- a/modules/tool/utils/tool.ts +++ b/modules/tool/utils/tool.ts @@ -1,7 +1,8 @@ import type { z } from 'zod'; -import type { ToolSetConfigType } from '@tool/type'; +import type { ToolSetConfigType, ToolType, ToolSetType } from '@tool/type'; import { ToolConfigSchema } from '@tool/type/tool'; import type { RunToolSecondParamsType } from '@tool/type/req'; +import { createHash } from 'node:crypto'; export const exportTool = ({ toolCb, @@ -46,20 +47,25 @@ export const exportToolSet = ({ config }: { config: ToolSetConfigType }) => { }; }; -// export function formatToolList( -// list: (z.infer | z.infer)[] -// ): ToolListItemType[] { -// return list.map((item) => ({ -// author: item.author, -// name: item.name, -// parentId: 'parentId' in item ? item.parentId : undefined, -// courseUrl: item.courseUrl, -// id: item.toolId, -// avatar: item.icon, -// versionList: item.versionList, -// description: item.description, -// toolDescription: item.toolDescription, -// templateType: item.tags?.[0], -// secretInputConfig: item.secretInputConfig -// })); -// } +export function generateToolVersion(versionList: Array<{ value: string }>): string { + const versionString = versionList.map((v) => v.value).join(''); + return createHash('sha256').update(versionString).digest('hex').substring(0, 8); +} + +/** + * Generate version hash for a tool set based on all child tools' versions + * @param children - Array of child tools + * @returns First 8 characters of SHA256 hash of all child version hashes concatenated + */ +export function generateToolSetVersion(children: ToolType[]) { + if (!children || children.length === 0) { + return undefined; + } + + const childVersions = children + .map((child) => generateToolVersion(child.versionList) || '') + .sort(); + const versionString = childVersions.join(''); + + return createHash('sha256').update(versionString).digest('hex').substring(0, 8); +} diff --git a/runtime/index.ts b/runtime/index.ts index a51cbff6..3445cbd2 100644 --- a/runtime/index.ts +++ b/runtime/index.ts @@ -1,4 +1,4 @@ -import { getCachedData } from '@/cache'; +import { refreshVersionKey } from '@/cache'; import { SystemCacheKeyEnum } from '@/cache/type'; import { isProd } from '@/constants'; import { initOpenAPI } from '@/contract/openapi'; @@ -51,7 +51,7 @@ async function main(reboot: boolean = false) { await ensureDir(tempToolsDir); // ensure the unpkged tools temp dir await Promise.all([ - getCachedData(SystemCacheKeyEnum.systemTool), // init system tool + refreshVersionKey(SystemCacheKeyEnum.systemTool), // init system tool initModels(reboot), initWorkflowTemplates() ]); diff --git a/sdk/package.json b/sdk/package.json index 077988fe..bd9d4158 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@fastgpt-sdk/plugin", - "version": "0.2.15", + "version": "0.2.16", "description": "fastgpt-plugin sdk", "main": "dist/client.js", "types": "dist/client.d.ts",