diff --git a/src/services/agentInstance/index.ts b/src/services/agentInstance/index.ts index 14894452..0bf050e9 100644 --- a/src/services/agentInstance/index.ts +++ b/src/services/agentInstance/index.ts @@ -11,7 +11,7 @@ import { AgentHandler, AgentHandlerContext } from '@services/agentInstance/build import { createHandlerHooks, createHooksWithPlugins, initializePluginSystem } from '@services/agentInstance/plugins'; import { promptConcatStream, PromptConcatStreamState } from '@services/agentInstance/promptConcat/promptConcat'; import { AgentPromptDescription } from '@services/agentInstance/promptConcat/promptConcatSchema'; -import { promptConcatHandlerConfigJsonSchema } from '@services/agentInstance/promptConcat/promptConcatSchema/jsonSchema'; +import { getPromptConcatHandlerConfigJsonSchema } from '@services/agentInstance/promptConcat/promptConcatSchema/jsonSchema'; import { IDatabaseService } from '@services/database/interface'; import { AgentInstanceEntity, AgentInstanceMessageEntity } from '@services/database/schema/agent'; import { logger } from '@services/libs/log'; @@ -72,7 +72,7 @@ export class AgentInstanceService implements IAgentInstanceService { public registerBuiltinHandlers(): void { // Plugins are already registered in initialize(), so we only register handlers here // Register basic prompt concatenation handler with its schema - this.registerHandler('basicPromptConcatHandler', basicPromptConcatHandler, promptConcatHandlerConfigJsonSchema); + this.registerHandler('basicPromptConcatHandler', basicPromptConcatHandler, getPromptConcatHandlerConfigJsonSchema()); } /** diff --git a/src/services/agentInstance/plugins/__tests__/wikiSearchPlugin.test.ts b/src/services/agentInstance/plugins/__tests__/wikiSearchPlugin.test.ts index a28299ad..17135cba 100644 --- a/src/services/agentInstance/plugins/__tests__/wikiSearchPlugin.test.ts +++ b/src/services/agentInstance/plugins/__tests__/wikiSearchPlugin.test.ts @@ -49,7 +49,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { it('should inject wiki tools into prompts when configured', async () => { // Find the wiki search plugin config, make sure our default config - const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch'); + const wikiPlugin = handlerConfig.plugins.find((p: unknown): p is IPromptConcatPlugin => (p as IPromptConcatPlugin).pluginId === 'wikiSearch'); expect(wikiPlugin).toBeDefined(); if (!wikiPlugin) { // throw error to keep ts believe the plugin exists @@ -194,7 +194,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { it('should execute wiki search with correct duration=1 and trigger next round', async () => { // Find the real wikiSearch plugin config from defaultAgents.json - const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch'); + const wikiPlugin = handlerConfig.plugins.find((p: unknown): p is IPromptConcatPlugin => (p as IPromptConcatPlugin).pluginId === 'wikiSearch'); expect(wikiPlugin).toBeDefined(); expect(wikiPlugin!.wikiSearchParam).toBeDefined(); diff --git a/src/services/agentInstance/plugins/__tests__/workspacesListPlugin.test.ts b/src/services/agentInstance/plugins/__tests__/workspacesListPlugin.test.ts index 9d03caff..42d421dd 100644 --- a/src/services/agentInstance/plugins/__tests__/workspacesListPlugin.test.ts +++ b/src/services/agentInstance/plugins/__tests__/workspacesListPlugin.test.ts @@ -152,8 +152,8 @@ describe('workspacesListPlugin', () => { it('should handle empty workspaces list', async () => { // Override the workspace service implementation returned by the global container mock - const workspaceService = container.get>(serviceIdentifier.Workspace); - workspaceService.getWorkspacesAsList = vi.fn().mockResolvedValue([]) as unknown as IWorkspaceService['getWorkspacesAsList']; + const workspaceService = container.get>(serviceIdentifier.Workspace); + workspaceService.getWorkspacesAsList = vi.fn().mockResolvedValue([]) as unknown as IWorkspaceService['getWorkspacesAsList']; const hooks = createHandlerHooks(); workspacesListPlugin(hooks); diff --git a/src/services/agentInstance/plugins/index.ts b/src/services/agentInstance/plugins/index.ts index 990b886b..1b2d4712 100644 --- a/src/services/agentInstance/plugins/index.ts +++ b/src/services/agentInstance/plugins/index.ts @@ -1,5 +1,6 @@ import { logger } from '@services/libs/log'; import { AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable'; +import { registerPluginParameterSchema } from './schemaRegistry'; import { AgentResponse, PromptConcatHookContext, PromptConcatHooks, PromptConcatPlugin, ResponseHookContext } from './types'; // Re-export types for convenience @@ -85,6 +86,76 @@ export async function registerPluginsToHooksFromConfig( * This should be called once during service initialization */ export async function initializePluginSystem(): Promise { + // Import plugin schemas and register them + const [ + promptPluginsModule, + wikiSearchModule, + wikiOperationModule, + workspacesListModule, + modelContextProtocolModule, + ] = await Promise.all([ + import('./promptPlugins'), + import('./wikiSearchPlugin'), + import('./wikiOperationPlugin'), + import('./workspacesListPlugin'), + import('./modelContextProtocolPlugin'), + ]); + + // Register plugin parameter schemas + registerPluginParameterSchema( + 'fullReplacement', + promptPluginsModule.getFullReplacementParameterSchema(), + { + displayName: 'Full Replacement', + description: 'Replace target content with content from specified source', + }, + ); + + registerPluginParameterSchema( + 'dynamicPosition', + promptPluginsModule.getDynamicPositionParameterSchema(), + { + displayName: 'Dynamic Position', + description: 'Insert content at a specific position relative to a target element', + }, + ); + + registerPluginParameterSchema( + 'wikiSearch', + wikiSearchModule.getWikiSearchParameterSchema(), + { + displayName: 'Wiki Search', + description: 'Search content in wiki workspaces', + }, + ); + + registerPluginParameterSchema( + 'wikiOperation', + wikiOperationModule.getWikiOperationParameterSchema(), + { + displayName: 'Wiki Operation', + description: 'Perform operations on wiki workspaces (create, update, delete tiddlers)', + }, + ); + + registerPluginParameterSchema( + 'workspacesList', + workspacesListModule.getWorkspacesListParameterSchema(), + { + displayName: 'Workspaces List', + description: 'Inject available wiki workspaces list into prompts', + }, + ); + + registerPluginParameterSchema( + 'modelContextProtocol', + modelContextProtocolModule.getModelContextProtocolParameterSchema(), + { + displayName: 'Model Context Protocol', + description: 'MCP (Model Context Protocol) integration', + }, + ); + const plugins = await getAllPlugins(); // Register all built-in plugins to global registry for discovery builtInPlugins.set('messageManagement', plugins.messageManagementPlugin); @@ -92,7 +163,7 @@ export async function initializePluginSystem(): Promise { builtInPlugins.set('wikiSearch', plugins.wikiSearchPlugin); builtInPlugins.set('wikiOperation', plugins.wikiOperationPlugin); builtInPlugins.set('workspacesList', plugins.workspacesListPlugin); - logger.debug('All built-in plugins registered to global registry successfully'); + logger.debug('All built-in plugins and schemas registered successfully'); } /** diff --git a/src/services/agentInstance/plugins/modelContextProtocolPlugin.ts b/src/services/agentInstance/plugins/modelContextProtocolPlugin.ts new file mode 100644 index 00000000..9efe27a8 --- /dev/null +++ b/src/services/agentInstance/plugins/modelContextProtocolPlugin.ts @@ -0,0 +1,54 @@ +/** + * Model Context Protocol Plugin + * Handles MCP (Model Context Protocol) integration + */ +import { identity } from 'lodash'; +import { z } from 'zod/v4'; + +const t = identity; + +/** + * Model Context Protocol Parameter Schema + * Configuration parameters for the MCP plugin + */ +export const ModelContextProtocolParameterSchema = z.object({ + id: z.string().meta({ + title: t('Schema.MCP.IdTitle'), + description: t('Schema.MCP.Id'), + }), + timeoutSecond: z.number().optional().meta({ + title: t('Schema.MCP.TimeoutSecondTitle'), + description: t('Schema.MCP.TimeoutSecond'), + }), + timeoutMessage: z.string().optional().meta({ + title: t('Schema.MCP.TimeoutMessageTitle'), + description: t('Schema.MCP.TimeoutMessage'), + }), + position: z.enum(['before', 'after']).meta({ + title: t('Schema.Position.TypeTitle'), + description: t('Schema.Position.Type'), + }), + targetId: z.string().meta({ + title: t('Schema.Position.TargetIdTitle'), + description: t('Schema.Position.TargetId'), + }), +}).meta({ + title: t('Schema.MCP.Title'), + description: t('Schema.MCP.Description'), +}); + +/** + * Type definition for MCP parameters + */ +export type ModelContextProtocolParameter = z.infer; + +/** + * Get the model context protocol parameter schema + * @returns The schema for MCP parameters + */ +export function getModelContextProtocolParameterSchema() { + return ModelContextProtocolParameterSchema; +} + +// TODO: Implement the actual MCP plugin functionality +// This is a placeholder for future MCP integration diff --git a/src/services/agentInstance/plugins/promptPlugins.ts b/src/services/agentInstance/plugins/promptPlugins.ts index 035dfafe..0873c88d 100644 --- a/src/services/agentInstance/plugins/promptPlugins.ts +++ b/src/services/agentInstance/plugins/promptPlugins.ts @@ -1,6 +1,9 @@ /** * Built-in plugins for prompt concatenation */ +import { identity } from 'lodash'; +import { z } from 'zod/v4'; + import { logger } from '@services/libs/log'; import { cloneDeep } from 'lodash'; import { findPromptById } from '../promptConcat/promptConcat'; @@ -8,6 +11,66 @@ import { IPrompt } from '../promptConcat/promptConcatSchema'; import { filterMessagesByDuration } from '../utilities/messageDurationFilter'; import { AgentResponse, PromptConcatPlugin, ResponseHookContext } from './types'; +const t = identity; + +/** + * Full Replacement Parameter Schema + * Configuration parameters for the full replacement plugin + */ +export const FullReplacementParameterSchema = z.object({ + targetId: z.string().meta({ + title: t('Schema.FullReplacement.TargetIdTitle'), + description: t('Schema.FullReplacement.TargetId'), + }), + sourceType: z.enum(['historyOfSession', 'llmResponse']).meta({ + title: t('Schema.FullReplacement.SourceTypeTitle'), + description: t('Schema.FullReplacement.SourceType'), + }), +}).meta({ + title: t('Schema.FullReplacement.Title'), + description: t('Schema.FullReplacement.Description'), +}); + +/** + * Dynamic Position Parameter Schema + * Configuration parameters for the dynamic position plugin + */ +export const DynamicPositionParameterSchema = z.object({ + targetId: z.string().meta({ + title: t('Schema.Position.TargetIdTitle'), + description: t('Schema.Position.TargetId'), + }), + position: z.enum(['before', 'after', 'relative']).meta({ + title: t('Schema.Position.TypeTitle'), + description: t('Schema.Position.Type'), + }), +}).meta({ + title: t('Schema.Position.Title'), + description: t('Schema.Position.Description'), +}); + +/** + * Type definitions + */ +export type FullReplacementParameter = z.infer; +export type DynamicPositionParameter = z.infer; + +/** + * Get the full replacement parameter schema + * @returns The schema for full replacement parameters + */ +export function getFullReplacementParameterSchema() { + return FullReplacementParameterSchema; +} + +/** + * Get the dynamic position parameter schema + * @returns The schema for dynamic position parameters + */ +export function getDynamicPositionParameterSchema() { + return DynamicPositionParameterSchema; +} + /** * Full replacement plugin * Replaces target content with content from specified source @@ -22,6 +85,11 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { } const fullReplacementConfig = pluginConfig.fullReplacementParam; + if (!fullReplacementConfig) { + callback(); + return; + } + const { targetId, sourceType } = fullReplacementConfig; const found = findPromptById(prompts, targetId); @@ -116,7 +184,13 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { return; } - const { targetId, sourceType } = pluginConfig.fullReplacementParam; + const fullReplacementParameter = pluginConfig.fullReplacementParam; + if (!fullReplacementParameter) { + callback(); + return; + } + + const { targetId, sourceType } = fullReplacementParameter; // Only handle llmResponse in response phase if (sourceType !== 'llmResponse') { @@ -168,6 +242,11 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => { } const dynamicPositionConfig = pluginConfig.dynamicPositionParam; + if (!dynamicPositionConfig) { + callback(); + return; + } + const { targetId, position } = dynamicPositionConfig; const found = findPromptById(prompts, targetId); @@ -217,80 +296,3 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => { callback(); }); }; - -/** - * Model Context Protocol plugin - * Integrates with external MCP servers - */ -export const modelContextProtocolPlugin: PromptConcatPlugin = (hooks) => { - hooks.processPrompts.tapAsync('modelContextProtocolPlugin', async (context, callback) => { - const { pluginConfig, prompts } = context; - - if (pluginConfig.pluginId !== 'modelContextProtocol' || !pluginConfig.modelContextProtocolParam) { - callback(); - return; - } - - const parameter = pluginConfig.modelContextProtocolParam; - const { targetId, position, id, timeoutSecond = 10, timeoutMessage = 'MCP server call timed out' } = parameter; - const found = findPromptById(prompts, targetId); - - if (!found) { - logger.warn('Target prompt not found for modelContextProtocol', { - targetId, - pluginId: pluginConfig.id, - }); - callback(); - return; - } - - try { - // TODO: Implement actual MCP server call with timeout - // For now, create a placeholder that indicates MCP integration - const content = `MCP Server Call: ${id} -Timeout: ${timeoutSecond} seconds -Timeout Message: ${timeoutMessage} - -This is where the actual Model Context Protocol server would be called. -The MCP server would provide additional context or capabilities to the AI model.`; - - // Simulate timeout handling in future implementation - logger.debug('MCP plugin - simulating server call', { - mcpId: id, - timeout: timeoutSecond, - pluginId: pluginConfig.id, - }); - - const newPart: IPrompt = { - id: `mcp-${pluginConfig.id}-${Date.now()}`, - caption: pluginConfig.caption || 'MCP Content', - text: content, - }; - - // Insert based on position - switch (position) { - case 'before': - found.parent.splice(found.index, 0, newPart); - break; - case 'after': - found.parent.splice(found.index + 1, 0, newPart); - break; - default: - logger.warn(`Unknown position: ${position as string}`); - callback(); - return; - } - - logger.debug('MCP plugin completed', { - targetId, - position, - mcpId: id, - }); - - callback(); - } catch (error) { - logger.error('MCP plugin error', error); - callback(); - } - }); -}; diff --git a/src/services/agentInstance/plugins/schemaRegistry.ts b/src/services/agentInstance/plugins/schemaRegistry.ts new file mode 100644 index 00000000..892b26ee --- /dev/null +++ b/src/services/agentInstance/plugins/schemaRegistry.ts @@ -0,0 +1,165 @@ +/** + * Plugin Schema Registry + * + * This system allows plugins to register their parameter schemas dynamically, + * enabling dynamic plugin loading while maintaining type safety and validation. + */ +import { identity } from 'lodash'; +import { z } from 'zod/v4'; + +const t = identity; + +/** + * Registry for plugin parameter schemas + */ +const pluginSchemas = new Map(); + +/** + * Registry for plugin metadata + */ +const pluginMetadata = new Map(); + +/** + * Register a plugin parameter schema + * @param pluginId The plugin ID (should match pluginId enum values) + * @param schema The Zod schema for this plugin's parameters + * @param metadata Optional metadata for display purposes + */ +export function registerPluginParameterSchema( + pluginId: string, + schema: z.ZodType, + metadata?: { + displayName: string; + description: string; + }, +): void { + pluginSchemas.set(pluginId, schema); + if (metadata) { + pluginMetadata.set(pluginId, metadata); + } +} + +/** + * Get a plugin parameter schema by ID + * @param pluginId The plugin ID + * @returns The schema or undefined if not found + */ +export function getPluginParameterSchema(pluginId: string): z.ZodType | undefined { + return pluginSchemas.get(pluginId); +} + +/** + * Get all registered plugin IDs + * @returns Array of all registered plugin IDs + */ +export function getAllRegisteredPluginIds(): string[] { + return Array.from(pluginSchemas.keys()); +} + +/** + * Get plugin metadata + * @param pluginId The plugin ID + * @returns Plugin metadata or undefined if not found + */ +export function getPluginMetadata(pluginId: string): { displayName: string; description: string } | undefined { + return pluginMetadata.get(pluginId); +} + +/** + * Dynamically create the PromptConcatPluginSchema based on registered plugins + * This is called whenever the schema is needed, ensuring it includes all registered plugins + */ +export function createDynamicPromptConcatPluginSchema(): z.ZodType { + // Base plugin configuration without parameter-specific fields + const basePluginSchema = z.object({ + id: z.string().meta({ + title: t('Schema.Plugin.IdTitle'), + description: t('Schema.Plugin.Id'), + }), + caption: z.string().optional().meta({ + title: t('Schema.Plugin.CaptionTitle'), + description: t('Schema.Plugin.Caption'), + }), + content: z.string().optional().meta({ + title: t('Schema.Plugin.ContentTitle'), + description: t('Schema.Plugin.Content'), + }), + forbidOverrides: z.boolean().optional().default(false).meta({ + title: t('Schema.Plugin.ForbidOverridesTitle'), + description: t('Schema.Plugin.ForbidOverrides'), + }), + }); + + // Get all registered plugin IDs + const registeredPluginIds = getAllRegisteredPluginIds(); + + if (registeredPluginIds.length === 0) { + // Fallback to a basic schema if no plugins are registered yet + return basePluginSchema.extend({ + pluginId: z.string().meta({ + title: t('Schema.Plugin.PluginIdTitle'), + description: t('Schema.Plugin.PluginId'), + }), + }); + } + + // Create enum from registered plugin IDs + const pluginIdEnum = z.enum(registeredPluginIds as [string, ...string[]]).meta({ + title: t('Schema.Plugin.PluginIdTitle'), + description: t('Schema.Plugin.PluginId'), + enumOptions: registeredPluginIds.map(pluginId => { + const metadata = getPluginMetadata(pluginId); + return { + value: pluginId, + label: metadata?.displayName || pluginId, + }; + }), + }); + + // Create parameter schema object with all registered plugins + const parameterSchema: Record = {}; + + for (const pluginId of registeredPluginIds) { + const schema = getPluginParameterSchema(pluginId); + if (schema) { + const metadata = getPluginMetadata(pluginId); + parameterSchema[`${pluginId}Param`] = schema.optional().meta({ + title: metadata?.displayName || pluginId, + description: metadata?.description || `Parameters for ${pluginId} plugin`, + }); + } + } + + // Combine base schema with plugin ID and parameters + return basePluginSchema.extend({ + pluginId: pluginIdEnum, + ...parameterSchema, + }); +} + +/** + * Get the type of a plugin's parameters + * @param pluginId The plugin ID + * @returns The inferred TypeScript type of the plugin's parameters + */ +export type PluginParameterType = T extends keyof ReturnType ? ReturnType[T] : never; + +/** + * Create type definitions for all registered plugin parameters + * This is used internally for type inference + */ +export function createPluginParameterTypes() { + const types: Record = {}; + + for (const pluginId of getAllRegisteredPluginIds()) { + const schema = getPluginParameterSchema(pluginId); + if (schema) { + types[pluginId] = schema; + } + } + + return types as Record; +} diff --git a/src/services/agentInstance/plugins/wikiOperationPlugin.ts b/src/services/agentInstance/plugins/wikiOperationPlugin.ts index 9ff7b6d0..148e4068 100644 --- a/src/services/agentInstance/plugins/wikiOperationPlugin.ts +++ b/src/services/agentInstance/plugins/wikiOperationPlugin.ts @@ -3,8 +3,51 @@ * Handles wiki operation tool list injection, tool calling detection and response processing * Supports creating, updating, and deleting tiddlers in wiki workspaces */ +import { identity } from 'lodash'; import { z } from 'zod/v4'; +const t = identity; + +/** + * Wiki Operation Parameter Schema + * Configuration parameters for the wiki operation plugin + */ +export const WikiOperationParameterSchema = z.object({ + toolListPosition: z.object({ + targetId: z.string().meta({ + title: t('Schema.WikiOperation.ToolListPosition.TargetIdTitle'), + description: t('Schema.WikiOperation.ToolListPosition.TargetId'), + }), + position: z.enum(['before', 'after']).meta({ + title: t('Schema.WikiOperation.ToolListPosition.PositionTitle'), + description: t('Schema.WikiOperation.ToolListPosition.Position'), + }), + }).optional().meta({ + title: t('Schema.WikiOperation.ToolListPositionTitle'), + description: t('Schema.WikiOperation.ToolListPosition'), + }), + toolResultDuration: z.number().optional().default(1).meta({ + title: t('Schema.WikiOperation.ToolResultDurationTitle'), + description: t('Schema.WikiOperation.ToolResultDuration'), + }), +}).meta({ + title: t('Schema.WikiOperation.Title'), + description: t('Schema.WikiOperation.Description'), +}); + +/** + * Type definition for wiki operation parameters + */ +export type WikiOperationParameter = z.infer; + +/** + * Get the wiki operation parameter schema + * @returns The schema for wiki operation parameters + */ +export function getWikiOperationParameterSchema() { + return WikiOperationParameterSchema; +} + import { WikiChannel } from '@/constants/channels'; import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility'; import { container } from '@services/container'; @@ -62,7 +105,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { // Get available wikis - now handled by workspacesListPlugin // The workspaces list will be injected separately by workspacesListPlugin - const wikiOperationToolContent = ` + const wikiOperationToolContent = ` ## wiki-operation **描述**: 在Wiki工作空间中执行操作(添加、删除或设置Tiddler文本) **参数**: diff --git a/src/services/agentInstance/plugins/wikiSearchPlugin.ts b/src/services/agentInstance/plugins/wikiSearchPlugin.ts index b47ec749..dae1abd6 100644 --- a/src/services/agentInstance/plugins/wikiSearchPlugin.ts +++ b/src/services/agentInstance/plugins/wikiSearchPlugin.ts @@ -2,8 +2,67 @@ * Wiki Search plugin * Handles wiki search tool list injection, tool calling detection and response processing */ +import { identity } from 'lodash'; import { z } from 'zod/v4'; +const t = identity; + +/** + * Wiki Search Parameter Schema + * Configuration parameters for the wiki search plugin + */ +export const WikiSearchParameterSchema = z.object({ + position: z.enum(['relative', 'absolute', 'before', 'after']).meta({ + title: t('Schema.Position.TypeTitle'), + description: t('Schema.Position.Type'), + }), + targetId: z.string().meta({ + title: t('Schema.Position.TargetIdTitle'), + description: t('Schema.Position.TargetId'), + }), + bottom: z.number().optional().meta({ + title: t('Schema.Position.BottomTitle'), + description: t('Schema.Position.Bottom'), + }), + sourceType: z.enum(['wiki']).meta({ + title: t('Schema.WikiSearch.SourceTypeTitle'), + description: t('Schema.WikiSearch.SourceType'), + }), + toolListPosition: z.object({ + targetId: z.string().meta({ + title: t('Schema.WikiSearch.ToolListPosition.TargetIdTitle'), + description: t('Schema.WikiSearch.ToolListPosition.TargetId'), + }), + position: z.enum(['before', 'after']).meta({ + title: t('Schema.WikiSearch.ToolListPosition.PositionTitle'), + description: t('Schema.WikiSearch.ToolListPosition.Position'), + }), + }).optional().meta({ + title: t('Schema.WikiSearch.ToolListPositionTitle'), + description: t('Schema.WikiSearch.ToolListPosition'), + }), + toolResultDuration: z.number().optional().default(1).meta({ + title: t('Schema.WikiSearch.ToolResultDurationTitle'), + description: t('Schema.WikiSearch.ToolResultDuration'), + }), +}).meta({ + title: t('Schema.WikiSearch.Title'), + description: t('Schema.WikiSearch.Description'), +}); + +/** + * Type definition for wiki search parameters + */ +export type WikiSearchParameter = z.infer; + +/** + * Get the wiki search parameter schema + * @returns The schema for wiki search parameters + */ +export function getWikiSearchParameterSchema() { + return WikiSearchParameterSchema; +} + import { WikiChannel } from '@/constants/channels'; import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility'; import { container } from '@services/container'; diff --git a/src/services/agentInstance/plugins/workspacesListPlugin.ts b/src/services/agentInstance/plugins/workspacesListPlugin.ts index c95ca4f5..8e12a2bd 100644 --- a/src/services/agentInstance/plugins/workspacesListPlugin.ts +++ b/src/services/agentInstance/plugins/workspacesListPlugin.ts @@ -2,6 +2,42 @@ * Workspaces List plugin * Handles injection of available wiki workspaces list into prompts */ +import { identity } from 'lodash'; +import { z } from 'zod/v4'; + +const t = identity; + +/** + * Workspaces List Parameter Schema + * Configuration parameters for the workspaces list plugin + */ +export const WorkspacesListParameterSchema = z.object({ + targetId: z.string().meta({ + title: t('Schema.WorkspacesList.TargetIdTitle'), + description: t('Schema.WorkspacesList.TargetId'), + }), + position: z.enum(['before', 'after']).meta({ + title: t('Schema.WorkspacesList.PositionTitle'), + description: t('Schema.WorkspacesList.Position'), + }), +}).meta({ + title: t('Schema.WorkspacesList.Title'), + description: t('Schema.WorkspacesList.Description'), +}); + +/** + * Type definition for workspaces list parameters + */ +export type WorkspacesListParameter = z.infer; + +/** + * Get the workspaces list parameter schema + * @returns The schema for workspaces list parameters + */ +export function getWorkspacesListParameterSchema() { + return WorkspacesListParameterSchema; +} + import { container } from '@services/container'; import { logger } from '@services/libs/log'; import serviceIdentifier from '@services/serviceIdentifier'; @@ -29,7 +65,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { try { // Handle workspaces list injection if targetId is configured - if (workspacesListParameter.targetId) { + if (workspacesListParameter?.targetId) { const target = findPromptById(prompts, workspacesListParameter.targetId); if (!target) { logger.warn('Workspaces list target prompt not found', { diff --git a/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts b/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts index 768917bf..9ed01072 100644 --- a/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts +++ b/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts @@ -1,7 +1,7 @@ +import { createDynamicPromptConcatPluginSchema } from '@services/agentInstance/plugins/schemaRegistry'; import { identity } from 'lodash'; import { z } from 'zod/v4'; import { ModelParametersSchema, ProviderModelSchema } from './modelParameters'; -import { PromptConcatPluginSchema } from './plugin'; import { PromptSchema } from './prompts'; import { ResponseSchema } from './response'; import { HANDLER_CONFIG_UI_SCHEMA } from './uiSchema'; @@ -39,27 +39,33 @@ export const AIConfigSchema = BaseAPIConfigSchema /** * Handler configuration schema * Contains the handler-related configuration fields for prompts, responses, and plugins + * This is dynamically generated to include all registered plugins */ -export const HandlerConfigSchema = z.object({ - prompts: z.array(PromptSchema).meta({ - description: t('Schema.AgentConfig.PromptConfig.Prompts'), - title: t('PromptConfig.Tabs.Prompts'), - }), - response: z.array(ResponseSchema).meta({ - description: t('Schema.AgentConfig.PromptConfig.Response'), - title: t('PromptConfig.Tabs.Response'), - }), - plugins: z.array(PromptConcatPluginSchema).meta({ - description: t('Schema.AgentConfig.PromptConfig.Plugins'), - title: t('PromptConfig.Tabs.Plugins'), - }), -}).meta({ - title: t('Schema.AgentConfig.PromptConfig.Title'), - description: t('Schema.AgentConfig.PromptConfig.Description'), - uiSchema: HANDLER_CONFIG_UI_SCHEMA, -}); +export function getHandlerConfigSchema() { + const dynamicPluginSchema = createDynamicPromptConcatPluginSchema(); + + return z.object({ + prompts: z.array(PromptSchema).meta({ + description: t('Schema.AgentConfig.PromptConfig.Prompts'), + title: t('PromptConfig.Tabs.Prompts'), + }), + response: z.array(ResponseSchema).meta({ + description: t('Schema.AgentConfig.PromptConfig.Response'), + title: t('PromptConfig.Tabs.Response'), + }), + plugins: z.array(dynamicPluginSchema).meta({ + description: t('Schema.AgentConfig.PromptConfig.Plugins'), + title: t('PromptConfig.Tabs.Plugins'), + }), + }).meta({ + title: t('Schema.AgentConfig.PromptConfig.Title'), + description: t('Schema.AgentConfig.PromptConfig.Description'), + uiSchema: HANDLER_CONFIG_UI_SCHEMA, + }); +} + /** - * Agent configuration schema + * Agent configuration schema (dynamic) * @example * ```json * { @@ -77,30 +83,37 @@ export const HandlerConfigSchema = z.object({ * } * ``` */ -export const AgentConfigSchema = BaseAPIConfigSchema.extend({ - id: z.string().meta({ - title: t('Schema.AgentConfig.IdTitle'), - description: t('Schema.AgentConfig.Id'), - }), - handlerConfig: HandlerConfigSchema, -}).meta({ - title: t('Schema.AgentConfig.Title'), - description: t('Schema.AgentConfig.Description'), -}); +export function getAgentConfigSchema() { + const dynamicHandlerConfigSchema = getHandlerConfigSchema(); + + return BaseAPIConfigSchema.extend({ + id: z.string().meta({ + title: t('Schema.AgentConfig.IdTitle'), + description: t('Schema.AgentConfig.Id'), + }), + handlerConfig: dynamicHandlerConfigSchema, + }).meta({ + title: t('Schema.AgentConfig.Title'), + description: t('Schema.AgentConfig.Description'), + }); +} /** - * Default agents list schema + * Default agents list schema (dynamic) * Contains an array of agent configurations */ -export const DefaultAgentsSchema = z.array(AgentConfigSchema).meta({ - title: t('Schema.DefaultAgents.Title'), - description: t('Schema.DefaultAgents.Description'), -}); +export function getDefaultAgentsSchema() { + const dynamicAgentConfigSchema = getAgentConfigSchema(); + return z.array(dynamicAgentConfigSchema).meta({ + title: t('Schema.DefaultAgents.Title'), + description: t('Schema.DefaultAgents.Description'), + }); +} -export type DefaultAgents = z.infer; -export type AgentPromptDescription = z.infer; +export type DefaultAgents = z.infer>; +export type AgentPromptDescription = z.infer>; export type AiAPIConfig = z.infer; -export type HandlerConfig = z.infer; +export type HandlerConfig = z.infer>; // Re-export all schemas and types export * from './modelParameters'; diff --git a/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts b/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts index a9290894..ddf873c6 100644 --- a/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts +++ b/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts @@ -1,11 +1,17 @@ import { z } from 'zod/v4'; -import { HandlerConfigSchema } from './index'; +import { getHandlerConfigSchema } from './index'; /** + * Get the dynamically generated JSON Schema for handler configuration + * This allows the frontend to generate forms based on currently registered plugins + * * Pre-generated JSON Schema for just the handler configuration part * This can be used when only the handler configuration is needed * It contains the prompt configuration without the parent agent structure * * Description field is i18n key, use i18nAlly extension to see it on VSCode. And use react-i18next to translate it on frontend. */ -export const promptConcatHandlerConfigJsonSchema = z.toJSONSchema(HandlerConfigSchema, { target: 'draft-7' }); +export function getPromptConcatHandlerConfigJsonSchema() { + const dynamicHandlerConfigSchema = getHandlerConfigSchema(); + return z.toJSONSchema(dynamicHandlerConfigSchema, { target: 'draft-7' }); +} diff --git a/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts b/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts index 966692c8..a7e09206 100644 --- a/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts +++ b/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts @@ -1,222 +1,26 @@ -import { identity } from 'lodash'; -import { z } from 'zod/v4'; +// Import parameter types from plugin files +import type { ModelContextProtocolParameter } from '@services/agentInstance/plugins/modelContextProtocolPlugin'; +import type { DynamicPositionParameter, FullReplacementParameter } from '@services/agentInstance/plugins/promptPlugins'; +import type { WikiOperationParameter } from '@services/agentInstance/plugins/wikiOperationPlugin'; +import type { WikiSearchParameter } from '@services/agentInstance/plugins/wikiSearchPlugin'; +import type { WorkspacesListParameter } from '@services/agentInstance/plugins/workspacesListPlugin'; -const t = identity; +/** + * Type definition for prompt concat plugin + * This includes all possible parameter fields for type safety + */ +export type IPromptConcatPlugin = { + id: string; + caption?: string; + content?: string; + forbidOverrides?: boolean; + pluginId: string; -export const FullReplacementParameterSchema = z.object({ - targetId: z.string().meta({ - title: t('Schema.FullReplacement.TargetIdTitle'), - description: t('Schema.FullReplacement.TargetId'), - }), - sourceType: z.enum(['historyOfSession', 'llmResponse']).meta({ - title: t('Schema.FullReplacement.SourceTypeTitle'), - description: t('Schema.FullReplacement.SourceType'), - }), -}).meta({ - title: t('Schema.FullReplacement.Title'), - description: t('Schema.FullReplacement.Description'), -}); - -export const DynamicPositionParameterSchema = z.object({ - targetId: z.string().meta({ - title: t('Schema.Position.TargetIdTitle'), - description: t('Schema.Position.TargetId'), - }), - position: z.enum(['before', 'after', 'relative']).meta({ - title: t('Schema.Position.TypeTitle'), - description: t('Schema.Position.Type'), - }), -}).meta({ - title: t('Schema.Position.Title'), - description: t('Schema.Position.Description'), -}); - -export const WikiSearchParameterSchema = z.object({ - position: z.enum(['relative', 'absolute', 'before', 'after']).meta({ - title: t('Schema.Position.TypeTitle'), - description: t('Schema.Position.Type'), - }), - targetId: z.string().meta({ - title: t('Schema.Position.TargetIdTitle'), - description: t('Schema.Position.TargetId'), - }), - bottom: z.number().optional().meta({ - title: t('Schema.Position.BottomTitle'), - description: t('Schema.Position.Bottom'), - }), - sourceType: z.enum(['wiki']).meta({ - title: t('Schema.WikiSearch.SourceTypeTitle'), - description: t('Schema.WikiSearch.SourceType'), - }), - toolListPosition: z.object({ - targetId: z.string().meta({ - title: t('Schema.WikiSearch.ToolListPosition.TargetIdTitle'), - description: t('Schema.WikiSearch.ToolListPosition.TargetId'), - }), - position: z.enum(['before', 'after']).meta({ - title: t('Schema.WikiSearch.ToolListPosition.PositionTitle'), - description: t('Schema.WikiSearch.ToolListPosition.Position'), - }), - }).optional().meta({ - title: t('Schema.WikiSearch.ToolListPositionTitle'), - description: t('Schema.WikiSearch.ToolListPosition'), - }), - toolResultDuration: z.number().optional().default(1).meta({ - title: t('Schema.WikiSearch.ToolResultDurationTitle'), - description: t('Schema.WikiSearch.ToolResultDuration'), - }), -}).meta({ - title: t('Schema.WikiSearch.Title'), - description: t('Schema.WikiSearch.Description'), -}); - -export const WikiOperationParameterSchema = z.object({ - toolListPosition: z.object({ - targetId: z.string().meta({ - title: t('Schema.WikiOperation.ToolListPosition.TargetIdTitle'), - description: t('Schema.WikiOperation.ToolListPosition.TargetId'), - }), - position: z.enum(['before', 'after']).meta({ - title: t('Schema.WikiOperation.ToolListPosition.PositionTitle'), - description: t('Schema.WikiOperation.ToolListPosition.Position'), - }), - }).optional().meta({ - title: t('Schema.WikiOperation.ToolListPositionTitle'), - description: t('Schema.WikiOperation.ToolListPosition'), - }), - toolResultDuration: z.number().optional().default(1).meta({ - title: t('Schema.WikiOperation.ToolResultDurationTitle'), - description: t('Schema.WikiOperation.ToolResultDuration'), - }), -}).meta({ - title: t('Schema.WikiOperation.Title'), - description: t('Schema.WikiOperation.Description'), -}); - -export const WorkspacesListParameterSchema = z.object({ - targetId: z.string().meta({ - title: t('Schema.WorkspacesList.TargetIdTitle'), - description: t('Schema.WorkspacesList.TargetId'), - }), - position: z.enum(['before', 'after']).meta({ - title: t('Schema.WorkspacesList.PositionTitle'), - description: t('Schema.WorkspacesList.Position'), - }), -}).meta({ - title: t('Schema.WorkspacesList.Title'), - description: t('Schema.WorkspacesList.Description'), -}); - -export const ModelContextProtocolParameterSchema = z.object({ - id: z.string().meta({ - title: t('Schema.MCP.IdTitle'), - description: t('Schema.MCP.Id'), - }), - timeoutSecond: z.number().optional().meta({ - title: t('Schema.MCP.TimeoutSecondTitle'), - description: t('Schema.MCP.TimeoutSecond'), - }), - timeoutMessage: z.string().optional().meta({ - title: t('Schema.MCP.TimeoutMessageTitle'), - description: t('Schema.MCP.TimeoutMessage'), - }), - position: z.enum(['before', 'after']).meta({ - title: t('Schema.Position.TypeTitle'), - description: t('Schema.Position.Type'), - }), - targetId: z.string().meta({ - title: t('Schema.Position.TargetIdTitle'), - description: t('Schema.Position.TargetId'), - }), -}).meta({ - title: t('Schema.MCP.Title'), - description: t('Schema.MCP.Description'), -}); - -export const ToolCallingParameterSchema = z.object({ - targetId: z.string().meta({ - title: t('Schema.ToolCalling.TargetIdTitle'), - description: t('Schema.ToolCalling.TargetId'), - }), - match: z.string().meta({ - title: t('Schema.ToolCalling.MatchTitle'), - description: t('Schema.ToolCalling.Match'), - }), -}).meta({ - title: t('Schema.ToolCalling.Title'), - description: t('Schema.ToolCalling.Description'), -}); - -export const PromptConcatPluginSchema = z.object({ - id: z.string().meta({ - title: t('Schema.Plugin.IdTitle'), - description: t('Schema.Plugin.Id'), - }), - caption: z.string().optional().meta({ - title: t('Schema.Plugin.CaptionTitle'), - description: t('Schema.Plugin.Caption'), - }), - content: z.string().optional().meta({ - title: t('Schema.Plugin.ContentTitle'), - description: t('Schema.Plugin.Content'), - }), - forbidOverrides: z.boolean().optional().default(false).meta({ - title: t('Schema.Plugin.ForbidOverridesTitle'), - description: t('Schema.Plugin.ForbidOverrides'), - }), - pluginId: z.enum([ - 'fullReplacement', - 'dynamicPosition', - 'retrievalAugmentedGeneration', - 'wikiSearch', - 'wikiOperation', - 'workspacesList', - 'modelContextProtocol', - 'toolCalling', - ]).meta({ - title: t('Schema.Plugin.PluginIdTitle'), - description: t('Schema.Plugin.PluginId'), - enumOptions: [ - { value: 'fullReplacement', label: t('Schema.Plugin.FullReplacementParamTitle') }, - { value: 'dynamicPosition', label: t('Schema.Plugin.DynamicPositionParamTitle') }, - { value: 'retrievalAugmentedGeneration', label: t('Schema.Plugin.RAGParamTitle') }, - { value: 'wikiSearch', label: t('Schema.Plugin.WikiSearchParamTitle') }, - { value: 'wikiOperation', label: t('Schema.Plugin.WikiOperationParamTitle') }, - { value: 'workspacesList', label: t('Schema.Plugin.WorkspacesListParamTitle') }, - { value: 'modelContextProtocol', label: t('Schema.Plugin.MCPParamTitle') }, - { value: 'toolCalling', label: t('Schema.Plugin.ToolCallingParamTitle') }, - ], - }), - // 参数配置 - fullReplacementParam: FullReplacementParameterSchema.optional().meta({ - title: t('Schema.Plugin.FullReplacementParamTitle'), - description: t('Schema.Plugin.FullReplacementParam'), - }), - dynamicPositionParam: DynamicPositionParameterSchema.optional().meta({ - title: t('Schema.Plugin.DynamicPositionParamTitle'), - description: t('Schema.Plugin.DynamicPositionParam'), - }), - wikiSearchParam: WikiSearchParameterSchema.optional().meta({ - title: t('Schema.Plugin.WikiSearchParamTitle'), - description: t('Schema.Plugin.WikiSearchParam'), - }), - wikiOperationParam: WikiOperationParameterSchema.optional().meta({ - title: t('Schema.Plugin.WikiOperationParamTitle'), - description: t('Schema.Plugin.WikiOperationParam'), - }), - workspacesListParam: WorkspacesListParameterSchema.optional().meta({ - title: t('Schema.Plugin.WorkspacesListParamTitle'), - description: t('Schema.Plugin.WorkspacesListParam'), - }), - modelContextProtocolParam: ModelContextProtocolParameterSchema.optional().meta({ - title: t('Schema.Plugin.MCPParamTitle'), - description: t('Schema.Plugin.MCPParam'), - }), - toolCallingParam: ToolCallingParameterSchema.optional().meta({ - title: t('Schema.Plugin.ToolCallingParamTitle'), - description: t('Schema.Plugin.ToolCallingParam'), - }), -}); - -// Allow pluginId to be a string so less error on test. -export type IPromptConcatPlugin = Omit, 'pluginId'> & { pluginId: string }; + // Plugin-specific parameters + fullReplacementParam?: FullReplacementParameter; + dynamicPositionParam?: DynamicPositionParameter; + wikiOperationParam?: WikiOperationParameter; + wikiSearchParam?: WikiSearchParameter; + workspacesListParam?: WorkspacesListParameter; + modelContextProtocolParam?: ModelContextProtocolParameter; +}; diff --git a/src/services/agentInstance/promptConcat/promptConcatSchema/uiSchema.ts b/src/services/agentInstance/promptConcat/promptConcatSchema/uiSchema.ts index 50392de5..28093926 100644 --- a/src/services/agentInstance/promptConcat/promptConcatSchema/uiSchema.ts +++ b/src/services/agentInstance/promptConcat/promptConcatSchema/uiSchema.ts @@ -79,13 +79,6 @@ export const HANDLER_CONFIG_UI_SCHEMA: UiSchema = { showWhen: 'modelContextProtocol', }, }, - toolCallingParam: { - 'ui:field': 'ConditionalField', - 'ui:condition': { - dependsOn: 'pluginId', - showWhen: 'toolCalling', - }, - }, }, }, response: {