From 8a84d9b468161dcf16bfca8d6068b89fc91d3168 Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Thu, 27 Nov 2025 15:33:30 +0800 Subject: [PATCH] rename: handler -> agent framework; plugin -> tool rename: handler -> agent framework; plugin -> tool lint refactor: more rename further rename --- src/__tests__/__mocks__/window.ts | 4 +- .../TabTypes/CreateNewAgentContent.tsx | 14 +- .../TabTypes/EditAgentDefinitionContent.tsx | 24 +-- .../TabContent/TabTypes/NewTabContent.tsx | 4 +- .../__tests__/CreateNewAgentContent.test.tsx | 58 ++--- .../EditAgentDefinitionContent.test.tsx | 12 +- .../TabTypes/__tests__/NewTabContent.test.tsx | 2 +- .../agentChatStore/actions/agentActions.ts | 22 +- .../agentChatStore/actions/previewActions.ts | 12 +- src/pages/Agent/store/agentChatStore/types.ts | 8 +- .../PromptPreviewDialog/EditView.tsx | 20 +- .../PromptConfigForm/index.tsx | 8 +- .../PromptPreviewDialog.promptConcat.test.tsx | 22 +- .../__tests__/PromptPreviewDialog.ui.test.tsx | 8 +- .../components/PromptPreviewDialog/index.tsx | 14 +- .../agentDefinition/__tests__/index.test.ts | 37 ++-- .../getAgentDefinitionTemplatesFromWikis.ts | 12 +- src/services/agentDefinition/index.ts | 18 +- src/services/agentDefinition/interface.ts | 8 +- .../__tests__/index.failure.test.ts | 4 +- .../__tests__/index.streaming.test.ts | 6 +- .../__tests__/index.wikiOperation.test.ts | 6 +- .../agentInstance/__tests__/utilities.test.ts | 20 +- .../__tests__/taskAgent.failure.test.ts} | 16 +- .../__tests__/taskAgent.test.ts} | 88 ++++---- .../taskAgent.ts} | 46 ++-- .../taskAgents.json} | 20 +- .../utilities}/statusUtilities.error.ts | 6 +- .../utilities}/statusUtilities.ts | 10 +- .../utilities}/type.ts | 10 +- src/services/agentInstance/index.ts | 126 +++++------ src/services/agentInstance/interface.ts | 18 +- src/services/agentInstance/plugins/index.ts | 188 ----------------- .../agentInstance/plugins/schemaRegistry.ts | 165 --------------- .../agentInstance/promptConcat/Readme.md | 49 ++--- .../promptConcat/promptConcat.ts | 71 +++---- .../promptConcat/promptConcatSchema/index.ts | 29 +-- .../promptConcatSchema/jsonSchema.ts | 8 +- .../promptConcat/promptConcatSchema/plugin.ts | 18 +- .../promptConcat/responseConcat.ts | 62 +++--- .../fullReplacementPlugin.duration.test.ts | 72 +++---- .../__tests__/messageManagementPlugin.test.ts | 50 ++--- .../__tests__/wikiOperationPlugin.test.ts | 178 ++++++++-------- .../__tests__/wikiSearchPlugin.test.ts | 198 +++++++++--------- .../__tests__/workspacesListPlugin.test.ts | 82 ++++---- src/services/agentInstance/tools/index.ts | 190 +++++++++++++++++ .../messageManagement.ts} | 60 +++--- .../modelContextProtocol.ts} | 0 .../promptPlugins.ts => tools/prompt.ts} | 48 +++-- .../agentInstance/tools/schemaRegistry.ts | 165 +++++++++++++++ .../agentInstance/{plugins => tools}/types.ts | 48 ++--- .../wikiOperation.ts} | 62 +++--- .../wikiSearch.ts} | 70 +++---- .../workspacesList.ts} | 24 +-- src/services/agentInstance/utilities.ts | 14 +- src/services/database/schema/agent.ts | 6 +- .../__tests__/externalAPI.logging.test.ts | 2 +- src/services/git/registerMenu.ts | 6 +- .../useAgentFrameworkConfigManagement.ts | 102 +++++++++ .../ExternalAPI/useHandlerConfigManagement.ts | 100 --------- 60 files changed, 1382 insertions(+), 1368 deletions(-) rename src/services/agentInstance/{buildInAgentHandlers/__tests__/basicPromptConcatHandler.failure.test.ts => agentFrameworks/__tests__/taskAgent.failure.test.ts} (96%) rename src/services/agentInstance/{buildInAgentHandlers/__tests__/basicPromptConcatHandler.test.ts => agentFrameworks/__tests__/taskAgent.test.ts} (83%) rename src/services/agentInstance/{buildInAgentHandlers/basicPromptConcatHandler.ts => agentFrameworks/taskAgent.ts} (88%) rename src/services/agentInstance/{buildInAgentHandlers/defaultAgents.json => agentFrameworks/taskAgents.json} (94%) rename src/services/agentInstance/{buildInAgentHandlers => agentFrameworks/utilities}/statusUtilities.error.ts (86%) rename src/services/agentInstance/{buildInAgentHandlers => agentFrameworks/utilities}/statusUtilities.ts (90%) rename src/services/agentInstance/{buildInAgentHandlers => agentFrameworks/utilities}/type.ts (83%) delete mode 100644 src/services/agentInstance/plugins/index.ts delete mode 100644 src/services/agentInstance/plugins/schemaRegistry.ts rename src/services/agentInstance/{plugins => tools}/__tests__/fullReplacementPlugin.duration.test.ts (80%) rename src/services/agentInstance/{plugins => tools}/__tests__/messageManagementPlugin.test.ts (91%) rename src/services/agentInstance/{plugins => tools}/__tests__/wikiOperationPlugin.test.ts (71%) rename src/services/agentInstance/{plugins => tools}/__tests__/wikiSearchPlugin.test.ts (84%) rename src/services/agentInstance/{plugins => tools}/__tests__/workspacesListPlugin.test.ts (79%) create mode 100644 src/services/agentInstance/tools/index.ts rename src/services/agentInstance/{plugins/messageManagementPlugin.ts => tools/messageManagement.ts} (80%) rename src/services/agentInstance/{plugins/modelContextProtocolPlugin.ts => tools/modelContextProtocol.ts} (100%) rename src/services/agentInstance/{plugins/promptPlugins.ts => tools/prompt.ts} (83%) create mode 100644 src/services/agentInstance/tools/schemaRegistry.ts rename src/services/agentInstance/{plugins => tools}/types.ts (73%) rename src/services/agentInstance/{plugins/wikiOperationPlugin.ts => tools/wikiOperation.ts} (88%) rename src/services/agentInstance/{plugins/wikiSearchPlugin.ts => tools/wikiSearch.ts} (91%) rename src/services/agentInstance/{plugins/workspacesListPlugin.ts => tools/workspacesList.ts} (85%) create mode 100644 src/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement.ts delete mode 100644 src/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement.ts diff --git a/src/__tests__/__mocks__/window.ts b/src/__tests__/__mocks__/window.ts index 9d8527b3..5ad7aab1 100644 --- a/src/__tests__/__mocks__/window.ts +++ b/src/__tests__/__mocks__/window.ts @@ -46,7 +46,7 @@ Object.defineProperty(window, 'observables', { userInfo$: new BehaviorSubject(undefined).asObservable(), }, agentInstance: { - concatPrompt: vi.fn((promptDescription: Pick, messages: AgentInstanceMessage[]) => { + concatPrompt: vi.fn((promptDescription: Pick, messages: AgentInstanceMessage[]) => { const agentInstanceService = container.get(serviceIdentifier.AgentInstance); // Initialize handlers (plugins and built-in handlers) before calling concatPrompt // We need to wrap this in an Observable since concatPrompt returns an Observable @@ -55,7 +55,7 @@ Object.defineProperty(window, 'observables', { try { // Need to register plugins first. In test environment, this needs to be called manually. While in real // environment, this is handled in `main.ts` when app start. - await agentInstanceService.initializeHandlers(); + await agentInstanceService.initializeFrameworks(); const resultObservable = agentInstanceService.concatPrompt(promptDescription, messages); // Subscribe to the result and forward to our observer resultObservable.subscribe(observer); diff --git a/src/pages/Agent/TabContent/TabTypes/CreateNewAgentContent.tsx b/src/pages/Agent/TabContent/TabTypes/CreateNewAgentContent.tsx index e4482b98..6fb93fd7 100644 --- a/src/pages/Agent/TabContent/TabTypes/CreateNewAgentContent.tsx +++ b/src/pages/Agent/TabContent/TabTypes/CreateNewAgentContent.tsx @@ -4,7 +4,7 @@ import { Box, Button, Container, Step, StepLabel, Stepper, TextField, Typography import { styled } from '@mui/material/styles'; import type { RJSFSchema } from '@rjsf/utils'; import type { AgentDefinition } from '@services/agentDefinition/interface'; -import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import { AgentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback'; import { nanoid } from 'nanoid'; import React, { useEffect, useState } from 'react'; @@ -99,19 +99,19 @@ export const CreateNewAgentContent: React.FC = ({ ta // Load schema when temporaryAgentDefinition is available useEffect(() => { const loadSchema = async () => { - if (temporaryAgentDefinition?.handlerID) { + if (temporaryAgentDefinition?.agentFrameworkID) { try { - const schema = await window.service.agentInstance.getHandlerConfigSchema(temporaryAgentDefinition.handlerID); + const schema = await window.service.agentInstance.getFrameworkConfigSchema(temporaryAgentDefinition.agentFrameworkID); setPromptSchema(schema as RJSFSchema); } catch (error) { - console.error('Failed to load handler config schema:', error); + console.error('Failed to load framework config schema:', error); setPromptSchema(null); } } }; void loadSchema(); - }, [temporaryAgentDefinition?.handlerID]); + }, [temporaryAgentDefinition?.agentFrameworkID]); // Create preview agent when entering step 3 useEffect(() => { @@ -374,11 +374,11 @@ export const CreateNewAgentContent: React.FC = ({ ta { void handleAgentDefinitionChange({ ...temporaryAgentDefinition, - handlerConfig: updatedConfig as Record, + agentFrameworkConfig: updatedConfig as Record, }); }} loading={false} diff --git a/src/pages/Agent/TabContent/TabTypes/EditAgentDefinitionContent.tsx b/src/pages/Agent/TabContent/TabTypes/EditAgentDefinitionContent.tsx index 9edbbec7..52a902b1 100644 --- a/src/pages/Agent/TabContent/TabTypes/EditAgentDefinitionContent.tsx +++ b/src/pages/Agent/TabContent/TabTypes/EditAgentDefinitionContent.tsx @@ -2,7 +2,7 @@ import { Box, Button, CircularProgress, Container, Divider, TextField, Typograph import { styled } from '@mui/material/styles'; import type { RJSFSchema } from '@rjsf/utils'; import type { AgentDefinition } from '@services/agentDefinition/interface'; -import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import { AgentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -94,30 +94,30 @@ export const EditAgentDefinitionContent: React.FC { const loadSchema = async () => { - if (!agentDefinition?.handlerID) { - // No handlerID found + if (!agentDefinition?.agentFrameworkID) { + // No agentFrameworkID found return; } try { - // Loading handler config schema - const schema = await window.service.agentInstance.getHandlerConfigSchema(agentDefinition.handlerID); + // Loading framework config schema + const schema = await window.service.agentInstance.getFrameworkConfigSchema(agentDefinition.agentFrameworkID); // Schema loaded successfully setPromptSchema(schema); } catch (error) { - void window.service.native.log('error', 'EditAgentDefinitionContent: Failed to load handler config schema', { + void window.service.native.log('error', 'EditAgentDefinitionContent: Failed to load framework config schema', { error, - handlerID: agentDefinition.handlerID, + agentFrameworkID: agentDefinition.agentFrameworkID, }); - console.error('Failed to load handler config schema:', error); + console.error('Failed to load framework config schema:', error); } }; void loadSchema(); - }, [agentDefinition?.handlerID]); + }, [agentDefinition?.agentFrameworkID]); // Auto-save to backend whenever agentDefinition changes (debounced) const saveToBackendDebounced = useDebouncedCallback( @@ -235,7 +235,7 @@ export const EditAgentDefinitionContent: React.FC, + agentFrameworkConfig: formData as Record, }; }, ); @@ -356,7 +356,7 @@ export const EditAgentDefinitionContent: React.FC diff --git a/src/pages/Agent/TabContent/TabTypes/NewTabContent.tsx b/src/pages/Agent/TabContent/TabTypes/NewTabContent.tsx index c2b920f9..4e00d312 100644 --- a/src/pages/Agent/TabContent/TabTypes/NewTabContent.tsx +++ b/src/pages/Agent/TabContent/TabTypes/NewTabContent.tsx @@ -86,7 +86,7 @@ export const NewTabContent: React.FC = ({ tab: _tab }) => { const createAgentChatTab = async (agentDefinitionId?: string) => { try { - const agentDefinitionIdToUse = agentDefinitionId || 'example-agent'; + const agentDefinitionIdToUse = agentDefinitionId || 'task-agent'; // Handle current active tab - close temp tabs or NEW_TAB type tabs if (activeTabId) { @@ -162,7 +162,7 @@ export const NewTabContent: React.FC = ({ tab: _tab }) => { const handleEditDefinition = useCallback(() => { // Use the example agent ID for now - in the future this could be configurable - void editAgentDefinitionTab('example-agent'); + void editAgentDefinitionTab('task-agent'); handleCloseContextMenu(); }, []); diff --git a/src/pages/Agent/TabContent/TabTypes/__tests__/CreateNewAgentContent.test.tsx b/src/pages/Agent/TabContent/TabTypes/__tests__/CreateNewAgentContent.test.tsx index f00087c6..27d1349f 100644 --- a/src/pages/Agent/TabContent/TabTypes/__tests__/CreateNewAgentContent.test.tsx +++ b/src/pages/Agent/TabContent/TabTypes/__tests__/CreateNewAgentContent.test.tsx @@ -17,7 +17,7 @@ const mockGetAgentDefs = vi.fn(); const mockUpdateTab = vi.fn(); const mockGetAllTabs = vi.fn(); const mockGetActiveTabId = vi.fn(); -const mockGetHandlerConfigSchema = vi.fn(); +const mockGetFrameworkConfigSchema = vi.fn(); Object.defineProperty(window, 'service', { writable: true, @@ -30,7 +30,7 @@ Object.defineProperty(window, 'service', { getAgentDefs: mockGetAgentDefs, }, agentInstance: { - getHandlerConfigSchema: mockGetHandlerConfigSchema, + getFrameworkConfigSchema: mockGetFrameworkConfigSchema, }, agentBrowser: { updateTab: mockUpdateTab, @@ -117,7 +117,7 @@ describe('CreateNewAgentContent', () => { mockUpdateTab.mockResolvedValue(undefined); mockGetAllTabs.mockResolvedValue([]); mockGetActiveTabId.mockResolvedValue('test-tab-123'); - mockGetHandlerConfigSchema.mockResolvedValue({ + mockGetFrameworkConfigSchema.mockResolvedValue({ type: 'object', properties: { prompts: { @@ -157,7 +157,7 @@ describe('CreateNewAgentContent', () => { id: 'template-1', name: 'Test Template', description: 'Test Description', - handlerConfig: { systemPrompt: 'Test prompt' }, + agentFrameworkConfig: { systemPrompt: 'Test prompt' }, }; mockCreateAgentDef.mockResolvedValue({ @@ -258,8 +258,8 @@ describe('CreateNewAgentContent', () => { const mockAgentDefinition = { id: 'temp-123', name: 'Test Agent', - handlerID: 'test-handler', - handlerConfig: { prompts: [{ text: 'Original prompt', role: 'system' }] }, + agentFrameworkID: 'test-handler', + agentFrameworkConfig: { prompts: [{ text: 'Original prompt', role: 'system' }] }, }; mockGetAgentDef.mockResolvedValue(mockAgentDefinition); @@ -285,13 +285,13 @@ describe('CreateNewAgentContent', () => { expect(mockUpdateAgentDef).not.toHaveBeenCalled(); }); - it('should trigger schema loading when temporaryAgentDefinition has handlerID', async () => { - // Mock agent definition with handlerID that will be restored + it('should trigger schema loading when temporaryAgentDefinition has agentFrameworkID', async () => { + // Mock agent definition with agentFrameworkID that will be restored const mockAgentDefinition = { id: 'temp-123', name: 'Test Agent', - handlerID: 'test-handler', - handlerConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] }, + agentFrameworkID: 'test-handler', + agentFrameworkConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] }, }; mockGetAgentDef.mockResolvedValue(mockAgentDefinition); @@ -313,9 +313,9 @@ describe('CreateNewAgentContent', () => { expect(mockGetAgentDef).toHaveBeenCalledWith('temp-123'); }, { timeout: 1000 }); - // After restoration, the component should have the handlerID and trigger schema loading + // After restoration, the component should have the agentFrameworkID and trigger schema loading await waitFor(() => { - expect(mockGetHandlerConfigSchema).toHaveBeenCalledWith('test-handler'); + expect(mockGetFrameworkConfigSchema).toHaveBeenCalledWith('test-handler'); }, { timeout: 2000 }); }); @@ -341,8 +341,8 @@ describe('CreateNewAgentContent', () => { const mockTemplate = { id: 'template-1', name: 'Test Template', - handlerID: 'test-handler', - handlerConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] }, + agentFrameworkID: 'test-handler', + agentFrameworkConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] }, }; const mockCreatedDefinition = { @@ -378,8 +378,8 @@ describe('CreateNewAgentContent', () => { const mockTemplate = { id: 'template-1', name: 'Test Template', - handlerID: 'test-handler', - handlerConfig: { prompts: [{ text: 'Original prompt' }] }, + agentFrameworkID: 'test-handler', + agentFrameworkConfig: { prompts: [{ text: 'Original prompt' }] }, }; const mockCreatedDefinition = { @@ -443,7 +443,7 @@ describe('CreateNewAgentContent', () => { expect(mockCreateAgentDef).toHaveBeenCalledWith( expect.objectContaining({ name: 'My Agent', - handlerID: 'test-handler', + agentFrameworkID: 'test-handler', }), ); }); @@ -459,19 +459,19 @@ describe('CreateNewAgentContent', () => { expect(mockUpdateAgentDef).toHaveBeenCalledWith( expect.objectContaining({ id: expect.stringContaining('temp-'), - handlerID: 'test-handler', + agentFrameworkID: 'test-handler', }), ); }, { timeout: 500 }); }); - it('should handle nested prompt structure like defaultAgents.json', async () => { - // This is the actual structure from defaultAgents.json + it('should handle nested prompt structure like taskAgents.json', async () => { + // This is the actual structure from taskAgents.json const mockTemplate = { - id: 'example-agent', + id: 'task-agent', name: 'Example Agent', - handlerID: 'basicPromptConcatHandler', - handlerConfig: { + agentFrameworkID: 'basicPromptConcatHandler', + agentFrameworkConfig: { prompts: [ { id: 'system', @@ -503,7 +503,7 @@ describe('CreateNewAgentContent', () => { // Step 1: Create agent definition (simulates template selection) const createdDef = await window.service.agentDefinition.createAgentDef(mockCreatedDefinition); expect(createdDef).toBeDefined(); - const prompts = (createdDef.handlerConfig).prompts as Array<{ + const prompts = (createdDef.agentFrameworkConfig).prompts as Array<{ children?: Array<{ text?: string }>; }>; expect((prompts as Array<{ children?: Array<{ text?: string }> }>)[0]?.children?.[0]?.text).toBe('You are a helpful assistant for Tiddlywiki user.'); @@ -511,14 +511,14 @@ describe('CreateNewAgentContent', () => { // Step 2: Update system prompt in nested structure const updatedDefinition = { ...mockCreatedDefinition, - handlerConfig: { - ...mockCreatedDefinition.handlerConfig, + agentFrameworkConfig: { + ...mockCreatedDefinition.agentFrameworkConfig, prompts: [ { - ...mockCreatedDefinition.handlerConfig.prompts[0], + ...mockCreatedDefinition.agentFrameworkConfig.prompts[0], children: [ { - ...mockCreatedDefinition.handlerConfig.prompts[0].children[0], + ...mockCreatedDefinition.agentFrameworkConfig.prompts[0].children[0], text: '你是一个专业的代码助手,请用中文回答编程问题。', }, ], @@ -532,7 +532,7 @@ describe('CreateNewAgentContent', () => { // Verify the correct nested structure is updated expect(mockUpdateAgentDef).toHaveBeenCalledWith( expect.objectContaining({ - handlerConfig: expect.objectContaining({ + agentFrameworkConfig: expect.objectContaining({ prompts: expect.arrayContaining([ expect.objectContaining({ role: 'system', diff --git a/src/pages/Agent/TabContent/TabTypes/__tests__/EditAgentDefinitionContent.test.tsx b/src/pages/Agent/TabContent/TabTypes/__tests__/EditAgentDefinitionContent.test.tsx index 9053e2e9..765ba00e 100644 --- a/src/pages/Agent/TabContent/TabTypes/__tests__/EditAgentDefinitionContent.test.tsx +++ b/src/pages/Agent/TabContent/TabTypes/__tests__/EditAgentDefinitionContent.test.tsx @@ -17,7 +17,7 @@ const mockCreateAgent = vi.fn(); const mockDeleteAgent = vi.fn(); const mockGetAgentDef = vi.fn(); const mockUpdateAgentDef = vi.fn(); -const mockGetHandlerConfigSchema = vi.fn(); +const mockGetFrameworkConfigSchema = vi.fn(); const mockLog = vi.fn(); Object.defineProperty(window, 'service', { @@ -33,7 +33,7 @@ Object.defineProperty(window, 'service', { agentInstance: { createAgent: mockCreateAgent, deleteAgent: mockDeleteAgent, - getHandlerConfigSchema: mockGetHandlerConfigSchema, + getFrameworkConfigSchema: mockGetFrameworkConfigSchema, }, agentDefinition: { getAgentDef: mockGetAgentDef, @@ -49,7 +49,7 @@ const mockAgentDefinition = { id: 'test-agent-def-id', name: 'Test Agent', description: 'A test agent for editing', - handlerID: 'testHandler', + agentFrameworkID: 'testHandler', config: {}, }; @@ -69,7 +69,7 @@ describe('EditAgentDefinitionContent', () => { mockGetAllTabs.mockResolvedValue([]); mockGetActiveTabId.mockResolvedValue(null); mockGetAgentDef.mockResolvedValue(mockAgentDefinition); - mockGetHandlerConfigSchema.mockResolvedValue(mockSchema); + mockGetFrameworkConfigSchema.mockResolvedValue(mockSchema); mockCreateAgent.mockResolvedValue({ id: 'test-agent-id', name: 'Test Agent', @@ -234,7 +234,7 @@ describe('EditAgentDefinitionContent', () => { await renderComponent(); await waitFor(() => { - expect(mockGetHandlerConfigSchema).toHaveBeenCalledWith('testHandler'); + expect(mockGetFrameworkConfigSchema).toHaveBeenCalledWith('testHandler'); }); await waitFor(() => { @@ -277,7 +277,7 @@ describe('EditAgentDefinitionContent', () => { await renderComponent(); await waitFor(() => { - expect(mockGetHandlerConfigSchema).toHaveBeenCalledWith('testHandler'); + expect(mockGetFrameworkConfigSchema).toHaveBeenCalledWith('testHandler'); }); }); diff --git a/src/pages/Agent/TabContent/TabTypes/__tests__/NewTabContent.test.tsx b/src/pages/Agent/TabContent/TabTypes/__tests__/NewTabContent.test.tsx index c6e1896f..45d0fe57 100644 --- a/src/pages/Agent/TabContent/TabTypes/__tests__/NewTabContent.test.tsx +++ b/src/pages/Agent/TabContent/TabTypes/__tests__/NewTabContent.test.tsx @@ -41,7 +41,7 @@ describe('NewTabContent', () => { mockCreateAgent.mockResolvedValue({ id: 'test-agent-id', name: 'Test Agent', - agentDefId: 'example-agent', + agentDefId: 'task-agent', }); mockAddTab.mockResolvedValue({ id: 'test-tab-id', diff --git a/src/pages/Agent/store/agentChatStore/actions/agentActions.ts b/src/pages/Agent/store/agentChatStore/actions/agentActions.ts index 607785f2..46e1f1ac 100644 --- a/src/pages/Agent/store/agentChatStore/actions/agentActions.ts +++ b/src/pages/Agent/store/agentChatStore/actions/agentActions.ts @@ -302,35 +302,35 @@ export const agentActions = ( } }, - getHandlerId: async () => { + getAgentFrameworkId: async () => { try { const { agent, agentDef } = get(); - if (agentDef?.handlerID) { - return agentDef.handlerID; + if (agentDef?.agentFrameworkID) { + return agentDef.agentFrameworkID; } if (agent?.agentDefId) { const fetchedAgentDefinition = await window.service.agentDefinition.getAgentDef(agent.agentDefId); - if (fetchedAgentDefinition?.handlerID) { - return fetchedAgentDefinition.handlerID; + if (fetchedAgentDefinition?.agentFrameworkID) { + return fetchedAgentDefinition.agentFrameworkID; } } - throw new Error('No active agent in store or handler ID not found'); + throw new Error('No active agent in store or agent framework ID not found'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - const finalError = new Error(`Failed to get handler ID: ${errorMessage}`); + const finalError = new Error(`Failed to get agent framework ID: ${errorMessage}`); set({ error: finalError }); throw finalError; } }, /** - * Get handler configuration schema for current handler + * Get framework configuration schema for current framework */ - getHandlerConfigSchema: async () => { + getFrameworkConfigSchema: async () => { try { - const handlerId = await get().getHandlerId(); - return await window.service.agentInstance.getHandlerConfigSchema(handlerId); + const agentFrameworkId = await get().getAgentFrameworkId(); + return await window.service.agentInstance.getFrameworkConfigSchema(agentFrameworkId); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const finalError = new Error(`Failed to get handler schema: ${errorMessage}`); diff --git a/src/pages/Agent/store/agentChatStore/actions/previewActions.ts b/src/pages/Agent/store/agentChatStore/actions/previewActions.ts index 6f8fcaf6..e5831cb7 100644 --- a/src/pages/Agent/store/agentChatStore/actions/previewActions.ts +++ b/src/pages/Agent/store/agentChatStore/actions/previewActions.ts @@ -70,14 +70,14 @@ export const previewActionsMiddleware: StateCreator { try { set({ previewLoading: true }); const messages = Array.from(get().messages.values()); - // Safety check - if handlerConfig is empty, fail early - if (Object.keys(handlerConfig).length === 0) { + // Safety check - if agentFrameworkConfig is empty, fail early + if (!agentFrameworkConfig || Object.keys(agentFrameworkConfig).length === 0) { set({ previewLoading: false, previewResult: null }); return null; } @@ -93,7 +93,7 @@ export const previewActionsMiddleware: StateCreator { // Update progress and current step const stepDescription = state.step === 'plugin' - ? `Processing plugin: ${state.currentPlugin?.pluginId || 'unknown'}` + ? `Processing tool: ${state.currentPlugin?.toolId || 'unknown'}` : state.step === 'finalize' ? 'Finalizing prompts...' : state.step === 'flatten' @@ -123,7 +123,7 @@ export const previewActionsMiddleware: StateCreator Promise; /** Get the handler ID for the current agent */ - getHandlerId: () => Promise; + getAgentFrameworkId: () => Promise; /** Get the configuration schema for the current handler */ - getHandlerConfigSchema: () => Promise>; + getFrameworkConfigSchema: () => Promise>; /** Process raw agent data into store format */ processAgentData: ( @@ -188,12 +188,12 @@ export interface PreviewActions { /** * Generates a preview of prompts for the current agent state * @param inputText Input text to include in the preview - * @param handlerConfig Prompt configuration to use for preview + * @param agentFrameworkConfig Framework configuration to use for preview * @returns Promise that resolves when preview is generated and state is updated */ getPreviewPromptResult: ( inputText: string, - handlerConfig: AgentPromptDescription['handlerConfig'], + agentFrameworkConfig: AgentPromptDescription['agentFrameworkConfig'], ) => Promise< { flatPrompts: ModelMessage[]; diff --git a/src/pages/ChatTabContent/components/PromptPreviewDialog/EditView.tsx b/src/pages/ChatTabContent/components/PromptPreviewDialog/EditView.tsx index 39a4305d..4658ba2e 100644 --- a/src/pages/ChatTabContent/components/PromptPreviewDialog/EditView.tsx +++ b/src/pages/ChatTabContent/components/PromptPreviewDialog/EditView.tsx @@ -1,4 +1,4 @@ -import { useHandlerConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement'; +import { useAgentFrameworkConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement'; import MonacoEditor from '@monaco-editor/react'; import { Box, styled } from '@mui/material'; import Tab from '@mui/material/Tab'; @@ -9,7 +9,7 @@ import React, { FC, SyntheticEvent, useCallback, useEffect, useState } from 'rea import { useTranslation } from 'react-i18next'; import { useShallow } from 'zustand/react/shallow'; -import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import { AgentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; import { useAgentChatStore } from '../../../Agent/store/agentChatStore/index'; import { PromptConfigForm } from './PromptConfigForm'; @@ -40,11 +40,11 @@ export const EditView: FC = ({ ); const { - loading: handlerConfigLoading, - config: handlerConfig, + loading: agentFrameworkConfigLoading, + config: agentFrameworkConfig, schema: handlerSchema, handleConfigChange, - } = useHandlerConfigManagement({ + } = useAgentFrameworkConfigManagement({ agentDefId: agent?.agentDefId, agentId: agent?.id, }); @@ -98,7 +98,7 @@ export const EditView: FC = ({ ); const handleFormChange = useDebouncedCallback( - async (updatedConfig: HandlerConfig) => { + async (updatedConfig: AgentFrameworkConfig) => { try { // Ensure the config change is fully persisted before proceeding await handleConfigChange(updatedConfig); @@ -121,7 +121,7 @@ export const EditView: FC = ({ const handleEditorChange = useCallback((value: string | undefined) => { if (!value) return; try { - const parsedConfig = JSON.parse(value) as HandlerConfig; + const parsedConfig = JSON.parse(value) as AgentFrameworkConfig; void handleFormChange(parsedConfig); } catch (error) { void window.service.native.log('error', 'EditView: Invalid JSON in code editor:', { error }); @@ -163,16 +163,16 @@ export const EditView: FC = ({ {editorMode === 'form' && ( )} {editorMode === 'code' && ( ; /** Initial form data */ - formData?: HandlerConfig; + formData?: AgentFrameworkConfig; /** Change handler for form data */ - onChange?: (formData: HandlerConfig) => void; + onChange?: (formData: AgentFrameworkConfig) => void; /** Error handler for form validation errors */ onError?: (errors: RJSFValidationError[]) => void; /** Whether the form is disabled */ @@ -87,7 +87,7 @@ export const PromptConfigForm: React.FC = ({ onError?.(errors); }, [onError]); - const handleChange = useCallback((changeEvent: IChangeEvent) => { + const handleChange = useCallback((changeEvent: IChangeEvent) => { const formData = changeEvent.formData; if (formData) { onChange?.(formData); diff --git a/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.promptConcat.test.tsx b/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.promptConcat.test.tsx index 0a0266a0..58e1b88a 100644 --- a/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.promptConcat.test.tsx +++ b/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.promptConcat.test.tsx @@ -9,16 +9,16 @@ import { ThemeProvider } from '@mui/material/styles'; import { lightTheme } from '@services/theme/defaultTheme'; import { useAgentChatStore } from '@/pages/Agent/store/agentChatStore/index'; -import defaultAgents from '@services/agentInstance/buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '@services/agentInstance/agentFrameworks/taskAgents.json'; import { IPrompt } from '@services/agentInstance/promptConcat/promptConcatSchema'; import { ModelMessage } from 'ai'; import { PromptPreviewDialog } from '../index'; // Mock handler config management hook -vi.mock('@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement', () => ({ - useHandlerConfigManagement: vi.fn(() => ({ +vi.mock('@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement', () => ({ + useAgentFrameworkConfigManagement: vi.fn(() => ({ loading: false, - config: defaultAgents[0].handlerConfig, + config: defaultAgents[0].agentFrameworkConfig, handleConfigChange: vi.fn(), })), })); @@ -36,7 +36,7 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => { useAgentChatStore.setState({ agent: { id: 'test-agent', - agentDefId: 'example-agent', + agentDefId: 'task-agent', status: { state: 'working', modified: new Date() }, created: new Date(), }, @@ -63,8 +63,8 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => { // Test if the real concatPrompt is working expect(globalThis.window?.observables?.agentInstance?.concatPrompt).toBeDefined(); - // Create test data matching defaultAgents.json - cast to avoid type issues in test - const handlerConfig = defaultAgents[0].handlerConfig as never; + // Create test data matching taskAgents.json - cast to avoid type issues in test + const agentFrameworkConfig = defaultAgents[0].agentFrameworkConfig as never; const messages = [{ id: 'test-message-1', agentId: 'test-agent', @@ -74,7 +74,7 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => { // Call the real concatPrompt implementation const observable = globalThis.window.observables.agentInstance.concatPrompt( - { handlerConfig }, + { agentFrameworkConfig }, messages, ); @@ -118,11 +118,11 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => { it('should render workspaces and tools info from real concatPrompt execution', async () => { // First execute real concatPrompt to get the structured data - const handlerConfig = defaultAgents[0].handlerConfig; + const agentFrameworkConfig = defaultAgents[0].agentFrameworkConfig; const messages = [{ id: 'test', role: 'user' as const, content: 'Hello world', created: new Date(), modified: new Date(), agentId: 'test' }]; - // Pass handlerConfig wrapped (same shape used elsewhere) - const observable = window.observables.agentInstance.concatPrompt({ handlerConfig } as never, messages); + // Pass agentFrameworkConfig wrapped (same shape used elsewhere) + const observable = window.observables.agentInstance.concatPrompt({ agentFrameworkConfig } as never, messages); const results: unknown[] = []; let finalResult: { flatPrompts: ModelMessage[]; processedPrompts: IPrompt[] } | undefined; diff --git a/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.ui.test.tsx b/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.ui.test.tsx index e6243614..8ceb0be2 100644 --- a/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.ui.test.tsx +++ b/src/pages/ChatTabContent/components/PromptPreviewDialog/__tests__/PromptPreviewDialog.ui.test.tsx @@ -9,14 +9,14 @@ import { ThemeProvider } from '@mui/material/styles'; import { lightTheme } from '@services/theme/defaultTheme'; import { useAgentChatStore } from '@/pages/Agent/store/agentChatStore/index'; -import defaultAgents from '@services/agentInstance/buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '@services/agentInstance/agentFrameworks/taskAgents.json'; import { PromptPreviewDialog } from '../index'; // Mock handler config management hook -vi.mock('@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement', () => ({ - useHandlerConfigManagement: vi.fn(() => ({ +vi.mock('@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement', () => ({ + useAgentFrameworkConfigManagement: vi.fn(() => ({ loading: false, - config: defaultAgents[0].handlerConfig, + config: defaultAgents[0].agentFrameworkConfig, handleConfigChange: vi.fn(), })), })); diff --git a/src/pages/ChatTabContent/components/PromptPreviewDialog/index.tsx b/src/pages/ChatTabContent/components/PromptPreviewDialog/index.tsx index f74eb075..0275da41 100644 --- a/src/pages/ChatTabContent/components/PromptPreviewDialog/index.tsx +++ b/src/pages/ChatTabContent/components/PromptPreviewDialog/index.tsx @@ -1,4 +1,4 @@ -import { useHandlerConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement'; +import { useAgentFrameworkConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement'; import CloseIcon from '@mui/icons-material/Close'; import EditIcon from '@mui/icons-material/Edit'; import FullscreenIcon from '@mui/icons-material/Fullscreen'; @@ -36,9 +36,9 @@ export const PromptPreviewDialog: React.FC = ({ const [isEditMode, setIsEditMode] = useState(false); const { - loading: handlerConfigLoading, - config: handlerConfig, - } = useHandlerConfigManagement({ + loading: agentFrameworkConfigLoading, + config: agentFrameworkConfig, + } = useAgentFrameworkConfigManagement({ agentDefId: agent?.agentDefId, agentId: agent?.id, }); @@ -54,17 +54,17 @@ export const PromptPreviewDialog: React.FC = ({ ); useEffect(() => { const fetchInitialPreview = async () => { - if (!agent?.agentDefId || handlerConfigLoading || !handlerConfig || !open) { + if (!agent?.agentDefId || agentFrameworkConfigLoading || !agentFrameworkConfig || !open) { return; } try { - await getPreviewPromptResult(inputText, handlerConfig); + await getPreviewPromptResult(inputText, agentFrameworkConfig); } catch (error) { console.error('PromptPreviewDialog: Error fetching initial preview:', error); } }; void fetchInitialPreview(); - }, [agent?.agentDefId, handlerConfig, handlerConfigLoading, inputText, open]); // 移除 getPreviewPromptResult + }, [agent?.agentDefId, agentFrameworkConfig, agentFrameworkConfigLoading, inputText, open]); // 移除 getPreviewPromptResult const handleToggleFullScreen = useCallback((): void => { setIsFullScreen(previous => !previous); diff --git a/src/services/agentDefinition/__tests__/index.test.ts b/src/services/agentDefinition/__tests__/index.test.ts index b248574c..d4c76bbe 100644 --- a/src/services/agentDefinition/__tests__/index.test.ts +++ b/src/services/agentDefinition/__tests__/index.test.ts @@ -1,6 +1,6 @@ import { AgentDefinitionService } from '@services/agentDefinition'; import { AgentDefinition } from '@services/agentDefinition/interface'; -import defaultAgents from '@services/agentInstance/buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '@services/agentInstance/agentFrameworks/taskAgents.json'; import type { IAgentInstanceService } from '@services/agentInstance/interface'; import { container } from '@services/container'; import type { IDatabaseService } from '@services/database/interface'; @@ -90,12 +90,13 @@ describe('AgentDefinitionService getAgentDefs integration', () => { const defs = await freshService.getAgentDefs(); expect(defs.length).toBeGreaterThan(0); - const exampleAgent = defs.find(d => d.id === (defaultAgents as AgentDefinition[])[0].id); + // Fixed + const exampleAgent = defs.find(d => d.id === (defaultAgents as unknown as AgentDefinition[])[0].id); expect(exampleAgent).toBeDefined(); expect(exampleAgent!.name).toBeDefined(); - expect(exampleAgent!.handlerID).toBeDefined(); - expect(exampleAgent!.handlerConfig).toBeDefined(); - expect(typeof exampleAgent!.handlerConfig).toBe('object'); + expect(exampleAgent!.agentFrameworkID).toBeDefined(); + expect(exampleAgent!.agentFrameworkConfig).toBeDefined(); + expect(typeof exampleAgent!.agentFrameworkConfig).toBe('object'); }); it('should return only database data without fallback to defaultAgents', async () => { @@ -105,7 +106,7 @@ describe('AgentDefinitionService getAgentDefs integration', () => { const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity); // Save only minimal record (id only) to test new behavior - const example = (defaultAgents as AgentDefinition[])[0]; + const example = (defaultAgents as unknown as AgentDefinition[])[0]; await agentDefRepo.save({ id: example.id, }); @@ -116,11 +117,11 @@ describe('AgentDefinitionService getAgentDefs integration', () => { expect(found).toBeDefined(); // With new behavior, only id should be present, other fields should be undefined or empty expect(found!.id).toBe(example.id); - expect(found!.handlerID).toBeUndefined(); + expect(found!.agentFrameworkID).toBeUndefined(); expect(found!.name).toBeUndefined(); expect(found!.description).toBeUndefined(); expect(found!.avatarUrl).toBeUndefined(); - expect(found!.handlerConfig).toEqual({}); + expect(found!.agentFrameworkConfig).toEqual({}); expect(found!.aiApiConfig).toBeUndefined(); expect(found!.agentTools).toBeUndefined(); }); @@ -132,7 +133,7 @@ describe('AgentDefinitionService getAgentDefs integration', () => { const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity); // Save only minimal record (id only) as per new behavior - const example = (defaultAgents as AgentDefinition[])[0]; + const example = (defaultAgents as unknown as AgentDefinition[])[0]; await agentDefRepo.save({ id: example.id, }); @@ -148,8 +149,8 @@ describe('AgentDefinitionService getAgentDefs integration', () => { expect(entity!.name).toBeNull(); expect(entity!.description).toBeNull(); expect(entity!.avatarUrl).toBeNull(); - expect(entity!.handlerID).toBeNull(); - expect(entity!.handlerConfig).toBeNull(); + expect(entity!.agentFrameworkID).toBeNull(); + expect(entity!.agentFrameworkConfig).toBeNull(); expect(entity!.aiApiConfig).toBeNull(); expect(entity!.agentTools).toBeNull(); }); @@ -158,15 +159,15 @@ describe('AgentDefinitionService getAgentDefs integration', () => { const templates = await agentDefinitionService.getAgentTemplates(); // Should include all default agents - expect(templates.length).toBe((defaultAgents as AgentDefinition[]).length); + expect(templates.length).toBe((defaultAgents as unknown as AgentDefinition[]).length); - // Check that template has complete data from defaultAgents.json - const exampleTemplate = templates.find(t => t.id === (defaultAgents as AgentDefinition[])[0].id); + // Check that template has complete data from taskAgents.json + const exampleTemplate = templates.find(t => t.id === (defaultAgents as unknown as AgentDefinition[])[0].id); expect(exampleTemplate).toBeDefined(); expect(exampleTemplate!.name).toBeDefined(); - expect(exampleTemplate!.handlerID).toBeDefined(); - expect(exampleTemplate!.handlerConfig).toBeDefined(); - expect(typeof exampleTemplate!.handlerConfig).toBe('object'); + expect(exampleTemplate!.agentFrameworkID).toBeDefined(); + expect(exampleTemplate!.agentFrameworkConfig).toBeDefined(); + expect(typeof exampleTemplate!.agentFrameworkConfig).toBe('object'); }); it('should not throw when searchName filtering is requested (client-side filtering expected)', async () => { @@ -185,6 +186,6 @@ describe('AgentDefinitionService getAgentDefs integration', () => { // Should still return default agents and not throw const templates = await agentDefinitionService.getAgentTemplates(); - expect(templates.length).toBe((defaultAgents as AgentDefinition[]).length); + expect(templates.length).toBe((defaultAgents as unknown as AgentDefinition[]).length); }); }); diff --git a/src/services/agentDefinition/getAgentDefinitionTemplatesFromWikis.ts b/src/services/agentDefinition/getAgentDefinitionTemplatesFromWikis.ts index cef54beb..291d7205 100644 --- a/src/services/agentDefinition/getAgentDefinitionTemplatesFromWikis.ts +++ b/src/services/agentDefinition/getAgentDefinitionTemplatesFromWikis.ts @@ -116,21 +116,21 @@ export function validateAndConvertWikiTiddlerToAgentTemplate( }; // Try to parse the tiddler text as JSON for agent configuration - let handlerConfig: Record; + let agentFrameworkConfig: Record; try { const textContent = typeof tiddler.text === 'string' ? tiddler.text : JSON.stringify(tiddler.text || '{}'); const parsed = JSON.parse(textContent) as unknown; - // Ensure handlerConfig is a valid object + // Ensure agentFrameworkConfig is a valid object if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { - logger.warn('Invalid handlerConfig in tiddler', { + logger.warn('Invalid agentFrameworkConfig in tiddler', { function: 'validateAndConvertWikiTiddlerToAgentTemplate', title: getStringField(tiddler.title), reason: 'not an object', }); return null; } - handlerConfig = parsed as Record; + agentFrameworkConfig = parsed as Record; } catch (parseError) { logger.warn('Failed to parse agent template from tiddler', { function: 'validateAndConvertWikiTiddlerToAgentTemplate', @@ -146,8 +146,8 @@ export function validateAndConvertWikiTiddlerToAgentTemplate( name: getStringField(tiddler.caption) || getStringField(tiddler.title), description: getStringField(tiddler.description) || `Agent template from ${workspaceName || 'wiki'}`, avatarUrl: getStringField(tiddler.avatar_url) || undefined, - handlerID: getStringField(tiddler.handler_id) || 'basicPromptConcatHandler', - handlerConfig, + agentFrameworkID: getStringField(tiddler.agentFrameworkID) || 'basicPromptConcatHandler', + agentFrameworkConfig, aiApiConfig: parseAiApiConfig(tiddler.ai_api_config), agentTools: parseAgentTools(tiddler.agent_tools), }; diff --git a/src/services/agentDefinition/index.ts b/src/services/agentDefinition/index.ts index d9c86c53..ba734029 100644 --- a/src/services/agentDefinition/index.ts +++ b/src/services/agentDefinition/index.ts @@ -4,7 +4,7 @@ import { nanoid } from 'nanoid'; import { DataSource, Repository } from 'typeorm'; import type { IAgentBrowserService } from '@services/agentBrowser/interface'; -import defaultAgents from '@services/agentInstance/buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '@services/agentInstance/agentFrameworks/taskAgents.json'; import type { IAgentInstanceService } from '@services/agentInstance/interface'; import { container } from '@services/container'; import type { IDatabaseService } from '@services/database/interface'; @@ -71,15 +71,15 @@ export class AgentDefinitionService implements IAgentDefinitionService { if (existingCount === 0) { logger.info('Agent database is empty, initializing with default agents'); const defaultAgentsList = defaultAgents as AgentDefinition[]; - // Create agent definition entities with complete data from defaultAgents.json + // Create agent definition entities with complete data from taskAgents.json const agentDefinitionEntities = defaultAgentsList.map(defaultAgent => this.agentDefRepository!.create({ id: defaultAgent.id, name: defaultAgent.name, description: defaultAgent.description, avatarUrl: defaultAgent.avatarUrl, - handlerID: defaultAgent.handlerID, - handlerConfig: defaultAgent.handlerConfig, + agentFrameworkID: defaultAgent.agentFrameworkID, + agentFrameworkConfig: defaultAgent.agentFrameworkConfig, aiApiConfig: defaultAgent.aiApiConfig, agentTools: defaultAgent.agentTools, }) @@ -143,7 +143,7 @@ export class AgentDefinitionService implements IAgentDefinitionService { throw new Error(`Agent definition not found: ${agent.id}`); } - const pickedProperties = pick(agent, ['name', 'description', 'avatarUrl', 'handlerID', 'handlerConfig', 'aiApiConfig']); + const pickedProperties = pick(agent, ['name', 'description', 'avatarUrl', 'agentFrameworkID', 'agentFrameworkConfig', 'aiApiConfig']); Object.assign(existingAgent, pickedProperties); await this.agentDefRepository!.save(existingAgent); @@ -171,8 +171,8 @@ export class AgentDefinitionService implements IAgentDefinitionService { name: entity.name || undefined, description: entity.description || undefined, avatarUrl: entity.avatarUrl || undefined, - handlerID: entity.handlerID || undefined, - handlerConfig: entity.handlerConfig || {}, + agentFrameworkID: entity.agentFrameworkID || undefined, + agentFrameworkConfig: entity.agentFrameworkConfig || {}, aiApiConfig: entity.aiApiConfig || undefined, agentTools: entity.agentTools || undefined, })); @@ -212,8 +212,8 @@ export class AgentDefinitionService implements IAgentDefinitionService { name: entity.name || undefined, description: entity.description || undefined, avatarUrl: entity.avatarUrl || undefined, - handlerID: entity.handlerID || undefined, - handlerConfig: entity.handlerConfig || {}, + agentFrameworkID: entity.agentFrameworkID || undefined, + agentFrameworkConfig: entity.agentFrameworkConfig || {}, aiApiConfig: entity.aiApiConfig || undefined, agentTools: entity.agentTools || undefined, }; diff --git a/src/services/agentDefinition/interface.ts b/src/services/agentDefinition/interface.ts index b595fe6d..8f97798e 100644 --- a/src/services/agentDefinition/interface.ts +++ b/src/services/agentDefinition/interface.ts @@ -42,10 +42,10 @@ export interface AgentDefinition { description?: string; /** Agent icon or avatar URL */ avatarUrl?: string; - /** Agent handler function's id, we will find function by this id */ - handlerID?: string; - /** Agent handler's config, specific to the handler. This is required to ensure agent has valid configuration. */ - handlerConfig: Record; + /** Agent framework function's id, we will find function by this id */ + agentFrameworkID?: string; + /** Agent framework's config, specific to the framework. This is required to ensure agent has valid configuration. */ + agentFrameworkConfig: Record; /** * Overwrite the default AI configuration for this agent. * Priority is higher than the global default agent config. diff --git a/src/services/agentInstance/__tests__/index.failure.test.ts b/src/services/agentInstance/__tests__/index.failure.test.ts index 8af7c092..e8f628e9 100644 --- a/src/services/agentInstance/__tests__/index.failure.test.ts +++ b/src/services/agentInstance/__tests__/index.failure.test.ts @@ -6,7 +6,7 @@ import type { IExternalAPIService } from '@services/externalAPI/interface'; import serviceIdentifier from '@services/serviceIdentifier'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AgentInstanceService } from '..'; -import { basicPromptConcatHandler } from '../buildInAgentHandlers/basicPromptConcatHandler'; +import { basicPromptConcatHandler } from '../agentFrameworks/taskAgent'; import type { AgentInstanceMessage, IAgentInstanceService } from '../interface'; import type { AiAPIConfig } from '../promptConcat/promptConcatSchema'; @@ -78,7 +78,7 @@ describe('AgentInstance failure path - external API logs on error', () => { agentDef: { id: 'def-1', name: 'Def 1', - handlerConfig: {}, + agentFrameworkConfig: {}, aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } }, }, isCancelled: () => false, diff --git a/src/services/agentInstance/__tests__/index.streaming.test.ts b/src/services/agentInstance/__tests__/index.streaming.test.ts index 6a55dfdc..da86c696 100644 --- a/src/services/agentInstance/__tests__/index.streaming.test.ts +++ b/src/services/agentInstance/__tests__/index.streaming.test.ts @@ -12,7 +12,7 @@ import { container } from '@services/container'; import type { IDatabaseService } from '@services/database/interface'; import type { IExternalAPIService } from '@services/externalAPI/interface'; import serviceIdentifier from '@services/serviceIdentifier'; -import defaultAgents from '../buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '../agentFrameworks/taskAgents.json'; describe('AgentInstanceService Streaming Behavior', () => { let agentInstanceService: IAgentInstanceService; @@ -55,7 +55,7 @@ describe('AgentInstanceService Streaming Behavior', () => { agentInstanceService = container.get(serviceIdentifier.AgentInstance); await agentInstanceService.initialize(); - // Setup test agent instance using data from defaultAgents.json + // Setup test agent instance using data from taskAgents.json const exampleAgent = defaultAgents[0]; testAgentInstance = { id: nanoid(), @@ -73,7 +73,7 @@ describe('AgentInstanceService Streaming Behavior', () => { // Mock agent definition service to return our test agent definition mockAgentDefinitionService.getAgentDef = vi.fn().mockResolvedValue({ ...exampleAgent, - handlerID: 'basicPromptConcatHandler', + agentFrameworkID: 'basicPromptConcatHandler', }); // Mock the getAgent method to return our test instance vi.spyOn(agentInstanceService, 'getAgent').mockResolvedValue(testAgentInstance); diff --git a/src/services/agentInstance/__tests__/index.wikiOperation.test.ts b/src/services/agentInstance/__tests__/index.wikiOperation.test.ts index ae1bc79a..335f2982 100644 --- a/src/services/agentInstance/__tests__/index.wikiOperation.test.ts +++ b/src/services/agentInstance/__tests__/index.wikiOperation.test.ts @@ -7,7 +7,7 @@ import serviceIdentifier from '@services/serviceIdentifier'; import type { IWikiService } from '@services/wiki/interface'; import { nanoid } from 'nanoid'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import defaultAgents from '../buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '../agentFrameworks/taskAgents.json'; // Follow structure of index.streaming.test.ts describe('AgentInstanceService Wiki Operation', () => { @@ -26,9 +26,9 @@ describe('AgentInstanceService Wiki Operation', () => { agentInstanceService = container.get(serviceIdentifier.AgentInstance); // Initialize both database repositories and handlers - await agentInstanceService.initializeHandlers(); + await agentInstanceService.initializeFrameworks(); - // Setup test agent instance using data from defaultAgents.json + // Setup test agent instance using data from taskAgents.json const exampleAgent = defaultAgents[0]; testAgentInstance = { id: nanoid(), diff --git a/src/services/agentInstance/__tests__/utilities.test.ts b/src/services/agentInstance/__tests__/utilities.test.ts index 7d4168f6..63865577 100644 --- a/src/services/agentInstance/__tests__/utilities.test.ts +++ b/src/services/agentInstance/__tests__/utilities.test.ts @@ -2,11 +2,11 @@ import { describe, expect, it } from 'vitest'; import { createAgentInstanceData } from '../utilities'; describe('createAgentInstanceData', () => { - it('should create agent instance with undefined handlerConfig (fallback to definition)', () => { + it('should create agent instance with undefined agentFrameworkConfig (fallback to definition)', () => { const agentDefinition = { id: 'test-agent-def', name: 'Test Agent', - handlerConfig: { + agentFrameworkConfig: { prompts: [ { text: 'You are a helpful assistant.', @@ -14,29 +14,29 @@ describe('createAgentInstanceData', () => { }, ], }, - handlerID: 'basicPromptConcatHandler', + agentFrameworkID: 'basicPromptConcatHandler', }; const { instanceData } = createAgentInstanceData(agentDefinition); - expect(instanceData.handlerConfig).toBeUndefined(); + expect(instanceData.agentFrameworkConfig).toBeUndefined(); expect(instanceData.agentDefId).toBe('test-agent-def'); - expect(instanceData.handlerID).toBe('basicPromptConcatHandler'); + expect(instanceData.agentFrameworkID).toBe('basicPromptConcatHandler'); expect(instanceData.name).toContain('Test Agent'); }); - it('should create agent instance with undefined handlerConfig even when definition has required handlerConfig', () => { + it('should create agent instance with undefined agentFrameworkConfig even when definition has required agentFrameworkConfig', () => { const agentDefinition = { id: 'test-agent-def-no-config', name: 'Test Agent No Config', - handlerID: 'basicPromptConcatHandler', - handlerConfig: {}, // Required by AgentDefinition interface + agentFrameworkID: 'basicPromptConcatHandler', + agentFrameworkConfig: {}, // Required by AgentDefinition interface }; const { instanceData } = createAgentInstanceData(agentDefinition); - expect(instanceData.handlerConfig).toBeUndefined(); + expect(instanceData.agentFrameworkConfig).toBeUndefined(); expect(instanceData.agentDefId).toBe('test-agent-def-no-config'); - expect(instanceData.handlerID).toBe('basicPromptConcatHandler'); + expect(instanceData.agentFrameworkID).toBe('basicPromptConcatHandler'); }); }); diff --git a/src/services/agentInstance/buildInAgentHandlers/__tests__/basicPromptConcatHandler.failure.test.ts b/src/services/agentInstance/agentFrameworks/__tests__/taskAgent.failure.test.ts similarity index 96% rename from src/services/agentInstance/buildInAgentHandlers/__tests__/basicPromptConcatHandler.failure.test.ts rename to src/services/agentInstance/agentFrameworks/__tests__/taskAgent.failure.test.ts index de815b82..f078def5 100644 --- a/src/services/agentInstance/buildInAgentHandlers/__tests__/basicPromptConcatHandler.failure.test.ts +++ b/src/services/agentInstance/agentFrameworks/__tests__/taskAgent.failure.test.ts @@ -8,8 +8,8 @@ import serviceIdentifier from '@services/serviceIdentifier'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentInstanceMessage, IAgentInstanceService } from '../../interface'; import type { AiAPIConfig } from '../../promptConcat/promptConcatSchema'; -import { basicPromptConcatHandler } from '../basicPromptConcatHandler'; -import type { AgentHandlerContext } from '../type'; +import { basicPromptConcatHandler } from '../taskAgent'; +import type { AgentFrameworkContext } from '../utilities/type'; // Use real normalizeRole implementation — do not mock plugins or persistence in these integration tests @@ -21,7 +21,7 @@ const mockErrorDetail = { message: 'Invalid prompt: message must be a ModelMessage or a UI message', }; -function makeContext(agentId: string, agentDefId: string, messages: AgentInstanceMessage[]): AgentHandlerContext { +function makeContext(agentId: string, agentDefId: string, messages: AgentInstanceMessage[]): AgentFrameworkContext { return { agent: { id: agentId, @@ -33,11 +33,11 @@ function makeContext(agentId: string, agentDefId: string, messages: AgentInstanc agentDef: { id: agentDefId, name: 'Test Agent', - handlerConfig: {}, + agentFrameworkConfig: {}, aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } } as AiAPIConfig, }, isCancelled: () => false, - } as unknown as AgentHandlerContext; + } as unknown as AgentFrameworkContext; } describe('basicPromptConcatHandler - failure path persists error message and logs', () => { @@ -94,10 +94,10 @@ describe('basicPromptConcatHandler - failure path persists error message and log vi.spyOn(agentDefSvc, 'getAgentDef').mockResolvedValue({ id: 'def-1', name: 'Def 1', - handlerID: 'basicPromptConcatHandler', - handlerConfig: { + agentFrameworkID: 'basicPromptConcatHandler', + agentFrameworkConfig: { plugins: [ - { pluginId: 'wikiOperation', wikiOperationParam: {} }, + { toolId: 'wikiOperation', wikiOperationParam: {} }, ], }, }); diff --git a/src/services/agentInstance/buildInAgentHandlers/__tests__/basicPromptConcatHandler.test.ts b/src/services/agentInstance/agentFrameworks/__tests__/taskAgent.test.ts similarity index 83% rename from src/services/agentInstance/buildInAgentHandlers/__tests__/basicPromptConcatHandler.test.ts rename to src/services/agentInstance/agentFrameworks/__tests__/taskAgent.test.ts index 777b87ee..03d7eb11 100644 --- a/src/services/agentInstance/buildInAgentHandlers/__tests__/basicPromptConcatHandler.test.ts +++ b/src/services/agentInstance/agentFrameworks/__tests__/taskAgent.test.ts @@ -18,7 +18,7 @@ import { WikiChannel } from '@/constants/channels'; // types are provided by shared mock; no local type assertions needed // Import defaultAgents configuration -import defaultAgents from '../defaultAgents.json'; +import defaultAgents from '../taskAgents.json'; // Configurable test hooks for mocks let testWikiImplementation: ((channel: WikiChannel, workspaceId?: string, args?: string[]) => Promise) | undefined; @@ -27,12 +27,12 @@ let testStreamResponses: Array<{ status: string; content: string; requestId: str // Use real AgentInstanceService in tests; do not mock // Import plugin components for direct testing -import type { IPromptConcatPlugin } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import type { IPromptConcatTool } from '@services/agentInstance/promptConcat/promptConcatSchema'; import type { IDatabaseService } from '@services/database/interface'; -import { createHandlerHooks, createHooksWithPlugins, initializePluginSystem, PromptConcatHookContext } from '../../plugins/index'; -import { wikiSearchPlugin } from '../../plugins/wikiSearchPlugin'; -import { basicPromptConcatHandler } from '../basicPromptConcatHandler'; -import type { AgentHandlerContext } from '../type'; +import { createAgentFrameworkHooks, createHooksWithTools, initializeToolSystem, PromptConcatHookContext } from '../../tools/index'; +import { wikiSearchTool } from '../../tools/wikiSearch'; +import { basicPromptConcatHandler } from '../taskAgent'; +import type { AgentFrameworkContext } from '../utilities/type'; describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { beforeEach(async () => { @@ -41,8 +41,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { testStreamResponses = []; const { container } = await import('@services/container'); - // Ensure built-in plugin registry includes all built-in plugins - await initializePluginSystem(); + // Ensure built-in tool registry includes all built-in tools + await initializeToolSystem(); // Prepare a mock DataSource/repository so AgentInstanceService.initialize() can run const mockRepo = { @@ -88,32 +88,32 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { describe('Complete Workflow Integration', () => { it('should complete full wiki search workflow: tool list -> tool execution -> response', async () => { - // Use real agent config from defaultAgents.json + // Use real agent config from taskAgents.json const exampleAgent = defaultAgents[0]; - const handlerConfig = exampleAgent.handlerConfig; + const agentFrameworkConfig = exampleAgent.agentFrameworkConfig; - // Get the wiki search plugin configuration - const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch'); + // Get the wiki search tool configuration + const wikiPlugin = agentFrameworkConfig.plugins.find(p => p.toolId === 'wikiSearch'); expect(wikiPlugin).toBeDefined(); if (!wikiPlugin) throw new Error('wikiPlugin not found'); - const prompts = JSON.parse(JSON.stringify(handlerConfig.prompts)); + const prompts = JSON.parse(JSON.stringify(agentFrameworkConfig.prompts)); // Phase 1: Tool List Injection const promptConcatHookContext: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', messages: [], agentDefId: exampleAgent.id, status: { state: 'working' as const, modified: new Date() }, created: new Date(), - handlerConfig: {}, + agentFrameworkConfig: {}, }, - agentDef: { id: exampleAgent.id, name: exampleAgent.name, handlerConfig: exampleAgent.handlerConfig }, + agentDef: { id: exampleAgent.id, name: exampleAgent.name, agentFrameworkConfig: exampleAgent.agentFrameworkConfig }, isCancelled: () => false, }, - pluginConfig: wikiPlugin as IPromptConcatPlugin, + toolConfig: wikiPlugin as IPromptConcatTool, prompts, messages: [ { @@ -127,12 +127,12 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { ], }; - // Create hooks and register plugins as defined in handlerConfig - const { hooks: promptHooks } = await createHooksWithPlugins(handlerConfig); - // First run workspacesList plugin to inject available workspaces (if present) - const workspacesPlugin = handlerConfig.plugins?.find(p => p.pluginId === 'workspacesList'); + // Create hooks and register tools as defined in agentFrameworkConfig + const { hooks: promptHooks } = await createHooksWithTools(agentFrameworkConfig); + // First run workspacesList tool to inject available workspaces (if present) + const workspacesPlugin = agentFrameworkConfig.plugins?.find(p => p.toolId === 'workspacesList'); if (workspacesPlugin) { - const workspacesContext = { ...promptConcatHookContext, pluginConfig: workspacesPlugin } as unknown as PromptConcatHookContext; + const workspacesContext = { ...promptConcatHookContext, toolConfig: workspacesPlugin } as unknown as PromptConcatHookContext; await promptHooks.processPrompts.promise(workspacesContext); } // Then run wikiSearch plugin to inject the tool list @@ -169,7 +169,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { }; const responseContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -179,9 +179,9 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { }, created: new Date(), messages: [], - handlerConfig: {}, + agentFrameworkConfig: {}, }, - agentDef: { id: 'test-agent-def', name: 'test-agent-def', handlerConfig: {} } as AgentDefinition, + agentDef: { id: 'test-agent-def', name: 'test-agent-def', agentFrameworkConfig: {} } as unknown as AgentDefinition, isCancelled: () => false, }, response: { @@ -191,7 +191,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { }, requestId: 'test-request', isFinal: true, - pluginConfig: wikiPlugin as IPromptConcatPlugin, + toolConfig: wikiPlugin as IPromptConcatTool, prompts: [], messages: [], llmResponse: 'I will search for important content using wiki-search tool.', @@ -199,8 +199,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { actions: {} as unknown as Record, }; - // Use hooks registered with all plugins from handlerConfig - const { hooks: responseHooks } = await createHooksWithPlugins(handlerConfig); + // Use hooks registered with all plugins import { AgentFrameworkConfig } + const { hooks: responseHooks } = await createHooksWithTools(agentFrameworkConfig); // Execute the response complete hook await responseHooks.responseComplete.promise(responseContext); // reuse containerForAssert from above assertions @@ -213,8 +213,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { expect(responseContext.actions.yieldNextRoundTo).toBe('self'); // Verify tool result message was added to agent history - expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0); - const toolResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage; + expect(responseContext.agentFrameworkContext.agent.messages.length).toBeGreaterThan(0); + const toolResultMessage = responseContext.agentFrameworkContext.agent.messages[responseContext.agentFrameworkContext.agent.messages.length - 1] as AgentInstanceMessage; expect(toolResultMessage.role).toBe('tool'); // Tool result message expect(toolResultMessage.content).toContain(''); expect(toolResultMessage.content).toContain('Tool: wiki-search'); @@ -222,18 +222,18 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { }); it('should handle errors in wiki search gracefully', async () => { - // Use real agent config from defaultAgents.json + // Use real agent config from taskAgents.json const exampleAgent = defaultAgents[0]; - const handlerConfig = exampleAgent.handlerConfig; + const agentFrameworkConfig = exampleAgent.agentFrameworkConfig; - // Get the wiki search plugin configuration - const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch'); + // Get the wiki search tool configuration + const wikiPlugin = agentFrameworkConfig.plugins.find(p => p.toolId === 'wikiSearch'); expect(wikiPlugin).toBeDefined(); // Mock tool calling with invalid workspace const responseContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -243,9 +243,9 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { }, created: new Date(), messages: [], - handlerConfig: {}, + agentFrameworkConfig: {}, }, - agentDef: { id: 'test-agent-def', name: 'test-agent-def', handlerConfig: {} } as AgentDefinition, + agentDef: { id: 'test-agent-def', name: 'test-agent-def', agentFrameworkConfig: {} } as unknown as AgentDefinition, isCancelled: () => false, }, response: { @@ -255,7 +255,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { }, requestId: 'test-request', isFinal: true, - pluginConfig: wikiPlugin as IPromptConcatPlugin, + toolConfig: wikiPlugin as IPromptConcatTool, prompts: [], messages: [], llmResponse: 'Search in nonexistent wiki', @@ -264,10 +264,10 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { }; // Use real handler hooks - const responseHooks = createHandlerHooks(); + const responseHooks = createAgentFrameworkHooks(); // Register the plugin - wikiSearchPlugin(responseHooks); + wikiSearchTool(responseHooks); // Execute the response complete hook await responseHooks.responseComplete.promise(responseContext); @@ -276,8 +276,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { expect(responseContext.actions.yieldNextRoundTo).toBe('self'); // Verify error message was added to agent history - expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0); - const errorResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage; + expect(responseContext.agentFrameworkContext.agent.messages.length).toBeGreaterThan(0); + const errorResultMessage = responseContext.agentFrameworkContext.agent.messages[responseContext.agentFrameworkContext.agent.messages.length - 1] as AgentInstanceMessage; expect(errorResultMessage.role).toBe('tool'); // Tool error message // The error should be indicated in the message content @@ -295,7 +295,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { const exampleAgent = defaultAgents[0]; const testAgentId = `test-agent-${Date.now()}`; - const context: AgentHandlerContext = { + const context: AgentFrameworkContext = { agent: { id: testAgentId, agentDefId: exampleAgent.id, @@ -315,7 +315,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { agentDef: { id: exampleAgent.id, name: exampleAgent.name, - handlerConfig: exampleAgent.handlerConfig, + agentFrameworkConfig: exampleAgent.agentFrameworkConfig, }, isCancelled: () => false, }; diff --git a/src/services/agentInstance/buildInAgentHandlers/basicPromptConcatHandler.ts b/src/services/agentInstance/agentFrameworks/taskAgent.ts similarity index 88% rename from src/services/agentInstance/buildInAgentHandlers/basicPromptConcatHandler.ts rename to src/services/agentInstance/agentFrameworks/taskAgent.ts index 9946a103..939f3414 100644 --- a/src/services/agentInstance/buildInAgentHandlers/basicPromptConcatHandler.ts +++ b/src/services/agentInstance/agentFrameworks/taskAgent.ts @@ -4,14 +4,14 @@ import { logger } from '@services/libs/log'; import serviceIdentifier from '@services/serviceIdentifier'; import { merge } from 'lodash'; import type { AgentInstanceLatestStatus, AgentInstanceMessage, IAgentInstanceService } from '../interface'; -import { createHooksWithPlugins } from '../plugins'; -import { YieldNextRoundTarget } from '../plugins/types'; -import { AgentPromptDescription, AiAPIConfig, HandlerConfig } from '../promptConcat/promptConcatSchema'; -import type { IPromptConcatPlugin } from '../promptConcat/promptConcatSchema/plugin'; +import { AgentFrameworkConfig, AgentPromptDescription, AiAPIConfig } from '../promptConcat/promptConcatSchema'; +import type { IPromptConcatTool } from '../promptConcat/promptConcatSchema/plugin'; import { responseConcat } from '../promptConcat/responseConcat'; import { getFinalPromptResult } from '../promptConcat/utilities'; -import { canceled, completed, error, working } from './statusUtilities'; -import { AgentHandlerContext } from './type'; +import { createHooksWithTools } from '../tools'; +import { YieldNextRoundTarget } from '../tools/types'; +import { canceled, completed, error, working } from './utilities/statusUtilities'; +import { AgentFrameworkContext } from './utilities/type'; /** * Main conversation orchestrator for AI agents @@ -27,19 +27,19 @@ import { AgentHandlerContext } from './type'; * * @param context - Agent handling context containing configuration and message history */ -export async function* basicPromptConcatHandler(context: AgentHandlerContext) { +export async function* basicPromptConcatHandler(context: AgentFrameworkContext) { // Initialize variables for request tracking let currentRequestId: string | undefined; const lastUserMessage: AgentInstanceMessage | undefined = context.agent.messages[context.agent.messages.length - 1]; - // Create and register handler hooks based on handler config - const { hooks: handlerHooks, pluginConfigs } = await createHooksWithPlugins(context.agentDef.handlerConfig || {}); + // Create and register handler hooks based on framework config + const { hooks: agentFrameworkHooks, toolConfigs } = await createHooksWithTools(context.agentDef.agentFrameworkConfig || {}); // Log the start of handler execution with context information logger.debug('Starting prompt handler execution', { method: 'basicPromptConcatHandler', agentId: context.agent.id, defId: context.agentDef.id, - handlerId: context.agentDef.handlerID, + agentFrameworkId: context.agentDef.agentFrameworkID, messageCount: context.agent.messages.length, }); // Check if there's a new user message to process - trigger user message received hook @@ -48,8 +48,8 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) { if (isNewUserMessage) { // Trigger user message received hook - await handlerHooks.userMessageReceived.promise({ - handlerContext: context, + await agentFrameworkHooks.userMessageReceived.promise({ + agentFrameworkContext: context, content: { text: lastUserMessage.content, file: lastUserMessage.metadata?.file as File | undefined, @@ -62,8 +62,8 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) { lastUserMessage.metadata = { ...lastUserMessage.metadata, processed: true }; // Trigger agent status change to working - await handlerHooks.agentStatusChanged.promise({ - handlerContext: context, + await agentFrameworkHooks.agentStatusChanged.promise({ + agentFrameworkContext: context, status: { state: 'working', modified: new Date(), @@ -94,12 +94,12 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) { // Process prompts using common handler function try { - const handlerConfig: HandlerConfig = context.agentDef.handlerConfig as HandlerConfig; + const agentFrameworkConfig = context.agentDef.agentFrameworkConfig as AgentFrameworkConfig; const agentPromptDescription: AgentPromptDescription = { id: context.agentDef.id, api: aiApiConfig.api, modelParameters: aiApiConfig.modelParameters, - handlerConfig, + agentFrameworkConfig, }; const agentInstanceService = container.get(serviceIdentifier.AgentInstance); @@ -146,12 +146,12 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) { if (response.status === 'update') { // For responseUpdate, we'll skip plugin-specific config for now // since it's called frequently during streaming - await handlerHooks.responseUpdate.promise({ - handlerContext: context, + await agentFrameworkHooks.responseUpdate.promise({ + agentFrameworkContext: context, response, requestId: currentRequestId, isFinal: false, - pluginConfig: {} as IPromptConcatPlugin, // Empty config for streaming updates + toolConfig: {} as IPromptConcatTool, // Empty config for streaming updates }); } @@ -164,16 +164,16 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) { // Delegate final response processing to handler hooks const responseCompleteContext = { - handlerContext: context, + agentFrameworkContext: context, response, requestId: currentRequestId, isFinal: true, - pluginConfig: (pluginConfigs.length > 0 ? pluginConfigs[0] : {}) as IPromptConcatPlugin, // First config for compatibility - handlerConfig: context.agentDef.handlerConfig, // Pass complete config for plugin access + toolConfig: (toolConfigs.length > 0 ? toolConfigs[0] : {}) as IPromptConcatTool, // First config for compatibility + agentFrameworkConfig: context.agentDef.agentFrameworkConfig, // Pass complete config for tool access actions: undefined as { yieldNextRoundTo?: 'self' | 'human'; newUserMessage?: string } | undefined, }; - await handlerHooks.responseComplete.promise(responseCompleteContext); + await agentFrameworkHooks.responseComplete.promise(responseCompleteContext); // Check if responseComplete hooks set yieldNextRoundTo let yieldNextRoundFromHooks: YieldNextRoundTarget | undefined; diff --git a/src/services/agentInstance/buildInAgentHandlers/defaultAgents.json b/src/services/agentInstance/agentFrameworks/taskAgents.json similarity index 94% rename from src/services/agentInstance/buildInAgentHandlers/defaultAgents.json rename to src/services/agentInstance/agentFrameworks/taskAgents.json index b821d35e..90dd74dd 100644 --- a/src/services/agentInstance/buildInAgentHandlers/defaultAgents.json +++ b/src/services/agentInstance/agentFrameworks/taskAgents.json @@ -1,11 +1,11 @@ [ { - "id": "example-agent", - "name": "Example Agent", + "id": "task-agent", + "name": "Task Agent", "description": "Example agent with prompt processing", - "avatarUrl": "https://example.com/example-agent.png", - "handlerID": "basicPromptConcatHandler", - "handlerConfig": { + "avatarUrl": "https://example.com/task-agent.png", + "agentFrameworkID": "basicPromptConcatHandler", + "agentFrameworkConfig": { "prompts": [ { "id": "system", @@ -72,7 +72,7 @@ "plugins": [ { "id": "efe5be74-540d-487d-8a05-7377e486953d", - "pluginId": "fullReplacement", + "toolId": "fullReplacement", "fullReplacementParam": { "targetId": "default-history", "sourceType": "historyOfSession" @@ -84,7 +84,7 @@ "id": "f0e1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5", "caption": "Wiki工作空间列表", "description": "自动在提示词中注入可用的Wiki工作空间列表", - "pluginId": "workspacesList", + "toolId": "workspacesList", "workspacesListParam": { "targetId": "default-before-tool", "position": "after" @@ -94,7 +94,7 @@ "id": "d0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5", "caption": "Wiki搜索和向量索引工具", "description": "提供Wiki搜索(filter和vector)以及向量嵌入索引管理功能", - "pluginId": "wikiSearch", + "toolId": "wikiSearch", "wikiSearchParam": { "sourceType": "wiki", "toolListPosition": { @@ -107,7 +107,7 @@ "id": "e1f2b3c4-5d6e-7f8g-9h0i-k1l2m3n4o5p6", "caption": "Wiki操作工具", "description": "允许AI在Wiki工作空间中创建、更新和删除笔记", - "pluginId": "wikiOperation", + "toolId": "wikiOperation", "wikiOperationParam": { "toolListPosition": { "position": "after", @@ -117,7 +117,7 @@ }, { "id": "a0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5", - "pluginId": "fullReplacement", + "toolId": "fullReplacement", "fullReplacementParam": { "targetId": "default-response", "sourceType": "llmResponse" diff --git a/src/services/agentInstance/buildInAgentHandlers/statusUtilities.error.ts b/src/services/agentInstance/agentFrameworks/utilities/statusUtilities.error.ts similarity index 86% rename from src/services/agentInstance/buildInAgentHandlers/statusUtilities.error.ts rename to src/services/agentInstance/agentFrameworks/utilities/statusUtilities.error.ts index 7bf8166f..f0ec82dd 100644 --- a/src/services/agentInstance/buildInAgentHandlers/statusUtilities.error.ts +++ b/src/services/agentInstance/agentFrameworks/utilities/statusUtilities.error.ts @@ -3,8 +3,8 @@ */ import { nanoid } from 'nanoid'; -import { AgentInstanceLatestStatus } from '../interface'; -import { AgentHandlerContext } from './type'; +import { AgentInstanceLatestStatus } from '../../interface'; +import { AgentFrameworkContext } from './type'; /** * Creates a completed status with error information in message metadata @@ -22,7 +22,7 @@ export function completedWithError( provider: string; message?: string; } | undefined, - context: AgentHandlerContext, + context: AgentFrameworkContext, messageId?: string, ): AgentInstanceLatestStatus { return { diff --git a/src/services/agentInstance/buildInAgentHandlers/statusUtilities.ts b/src/services/agentInstance/agentFrameworks/utilities/statusUtilities.ts similarity index 90% rename from src/services/agentInstance/buildInAgentHandlers/statusUtilities.ts rename to src/services/agentInstance/agentFrameworks/utilities/statusUtilities.ts index 8c374f9f..8a6f334e 100644 --- a/src/services/agentInstance/buildInAgentHandlers/statusUtilities.ts +++ b/src/services/agentInstance/agentFrameworks/utilities/statusUtilities.ts @@ -1,6 +1,6 @@ import { nanoid } from 'nanoid'; -import { AgentInstanceLatestStatus } from '../interface'; -import { AgentHandlerContext } from './type'; +import { AgentInstanceLatestStatus } from '../../interface'; +import { AgentFrameworkContext } from './type'; /** * Creates a working status with a message @@ -11,7 +11,7 @@ import { AgentHandlerContext } from './type'; */ export function working( content: string, - context: AgentHandlerContext, + context: AgentFrameworkContext, messageId?: string, ): AgentInstanceLatestStatus { return { @@ -34,7 +34,7 @@ export function working( */ export function completed( content: string, - context: AgentHandlerContext, + context: AgentFrameworkContext, messageId?: string, ): AgentInstanceLatestStatus { return { @@ -72,7 +72,7 @@ export function error( provider: string; message?: string; } | undefined, - context: AgentHandlerContext, + context: AgentFrameworkContext, messageId?: string, ): AgentInstanceLatestStatus { return { diff --git a/src/services/agentInstance/buildInAgentHandlers/type.ts b/src/services/agentInstance/agentFrameworks/utilities/type.ts similarity index 83% rename from src/services/agentInstance/buildInAgentHandlers/type.ts rename to src/services/agentInstance/agentFrameworks/utilities/type.ts index 0ae429ab..23d0e13f 100644 --- a/src/services/agentInstance/buildInAgentHandlers/type.ts +++ b/src/services/agentInstance/agentFrameworks/utilities/type.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-invalid-void-type */ -import { AgentDefinition } from '../../agentDefinition/interface'; -import { AgentInstance, AgentInstanceLatestStatus } from '../interface'; +import { AgentDefinition } from '../../../agentDefinition/interface'; +import { AgentInstance, AgentInstanceLatestStatus } from '../../interface'; -export interface AgentHandlerContext { +export interface AgentFrameworkContext { agent: AgentInstance; agentDef: AgentDefinition; @@ -28,6 +28,6 @@ export interface AgentHandlerContext { * (needed for non-streaming 'tasks/send'). If void is returned, the server uses the * last known state from the store after processing all yields. */ -export type AgentHandler = ( - context: AgentHandlerContext, +export type AgentFramework = ( + context: AgentFrameworkContext, ) => AsyncGenerator; diff --git a/src/services/agentInstance/index.ts b/src/services/agentInstance/index.ts index 9b6fbe6c..b6314e02 100644 --- a/src/services/agentInstance/index.ts +++ b/src/services/agentInstance/index.ts @@ -5,12 +5,12 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { DataSource, Repository } from 'typeorm'; import type { IAgentDefinitionService } from '@services/agentDefinition/interface'; -import { basicPromptConcatHandler } from '@services/agentInstance/buildInAgentHandlers/basicPromptConcatHandler'; -import type { AgentHandler, AgentHandlerContext } from '@services/agentInstance/buildInAgentHandlers/type'; -import { createHooksWithPlugins, initializePluginSystem } from '@services/agentInstance/plugins'; +import { basicPromptConcatHandler } from '@services/agentInstance/agentFrameworks/taskAgent'; +import type { AgentFramework, AgentFrameworkContext } from '@services/agentInstance/agentFrameworks/utilities/type'; import { promptConcatStream, PromptConcatStreamState } from '@services/agentInstance/promptConcat/promptConcat'; import type { AgentPromptDescription } from '@services/agentInstance/promptConcat/promptConcatSchema'; -import { getPromptConcatHandlerConfigJsonSchema } from '@services/agentInstance/promptConcat/promptConcatSchema/jsonSchema'; +import { getPromptConcatAgentFrameworkConfigJsonSchema } from '@services/agentInstance/promptConcat/promptConcatSchema/jsonSchema'; +import { createHooksWithTools, initializeToolSystem } from '@services/agentInstance/tools'; import type { IDatabaseService } from '@services/database/interface'; import { AgentInstanceEntity, AgentInstanceMessageEntity } from '@services/database/schema/agent'; import { logger } from '@services/libs/log'; @@ -34,15 +34,15 @@ export class AgentInstanceService implements IAgentInstanceService { private agentInstanceSubjects: Map> = new Map(); private statusSubjects: Map> = new Map(); - private agentHandlers: Map = new Map(); - private handlerSchemas: Map> = new Map(); + private agentFrameworks: Map = new Map(); + private frameworkSchemas: Map> = new Map(); private cancelTokenMap: Map = new Map(); private debouncedUpdateFunctions: Map void> = new Map(); public async initialize(): Promise { try { await this.initializeDatabase(); - await this.initializeHandlers(); + await this.initializeFrameworks(); } catch (error) { logger.error('Failed to initialize agent instance service', { error }); throw error; @@ -62,37 +62,37 @@ export class AgentInstanceService implements IAgentInstanceService { } } - public async initializeHandlers(): Promise { + public async initializeFrameworks(): Promise { try { - // Register plugins to global registry once during initialization - await initializePluginSystem(); - logger.debug('AgentInstance Plugin system initialized and plugins registered to global registry'); + // Register tools to global registry once during initialization + await initializeToolSystem(); + logger.debug('AgentInstance Tool system initialized and tools registered to global registry'); - // Register built-in handlers - this.registerBuiltinHandlers(); - logger.debug('AgentInstance handlers registered'); + // Register built-in frameworks + this.registerBuiltinFrameworks(); + logger.debug('AgentInstance frameworks registered'); } catch (error) { - logger.error('Failed to initialize agent instance handlers', { error }); + logger.error('Failed to initialize agent instance frameworks', { error }); throw error; } } - 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, getPromptConcatHandlerConfigJsonSchema()); + public registerBuiltinFrameworks(): void { + // Tools are already registered in initialize(), so we only register frameworks here + // Register basic prompt concatenation framework with its schema + this.registerFramework('basicPromptConcatHandler', basicPromptConcatHandler, getPromptConcatAgentFrameworkConfigJsonSchema()); } /** - * Register a handler with an optional schema - * @param handlerId ID for the handler - * @param handler The handler function - * @param schema Optional JSON schema for the handler configuration + * Register a framework with an optional schema + * @param frameworkId ID for the framework + * @param framework The framework function + * @param schema Optional JSON schema for the framework configuration */ - private registerHandler(handlerId: string, handler: AgentHandler, schema?: Record): void { - this.agentHandlers.set(handlerId, handler); + private registerFramework(frameworkId: string, framework: AgentFramework, schema?: Record): void { + this.agentFrameworks.set(frameworkId, framework); if (schema) { - this.handlerSchemas.set(handlerId, schema); + this.frameworkSchemas.set(frameworkId, schema); } } @@ -216,7 +216,7 @@ export class AgentInstanceService implements IAgentInstanceService { } // Update fields using pick + Object.assign for consistency with updateAgentDef - const pickedProperties = pick(data, ['name', 'status', 'avatarUrl', 'aiApiConfig', 'closed', 'handlerConfig']); + const pickedProperties = pick(data, ['name', 'status', 'avatarUrl', 'aiApiConfig', 'closed', 'agentFrameworkConfig']); Object.assign(instanceEntity, pickedProperties); // Save instance updates @@ -353,20 +353,20 @@ export class AgentInstanceService implements IAgentInstanceService { throw new Error(`Agent definition not found: ${agentInstance.agentDefId}`); } - // Get appropriate handler - const handlerId = agentDefinition.handlerID; - if (!handlerId) { - throw new Error(`Handler ID not found in agent definition: ${agentDefinition.id}`); + // Get appropriate framework + const agentFrameworkId = agentDefinition.agentFrameworkID; + if (!agentFrameworkId) { + throw new Error(`Agent framework ID not found in agent definition: ${agentDefinition.id}`); } - const handler = this.agentHandlers.get(handlerId); - if (!handler) { - throw new Error(`Handler not found: ${handlerId}`); + const framework = this.agentFrameworks.get(agentFrameworkId); + if (!framework) { + throw new Error(`Framework not found: ${agentFrameworkId}`); } - // Create handler context with temporary message added for processing + // Create framework context with temporary message added for processing const cancelToken = { value: false }; this.cancelTokenMap.set(agentId, cancelToken); - const handlerContext: AgentHandlerContext = { + const frameworkContext: AgentFrameworkContext = { agent: { ...agentInstance, messages: [...agentInstance.messages], @@ -379,23 +379,23 @@ export class AgentInstanceService implements IAgentInstanceService { isCancelled: () => cancelToken.value, }; - // Create fresh hooks for this handler execution and register plugins based on handlerConfig - const { hooks: handlerHooks } = await createHooksWithPlugins(agentDefinition.handlerConfig || {}); + // Create fresh hooks for this framework execution and register tools based on frameworkConfig + const { hooks: frameworkHooks } = await createHooksWithTools(agentDefinition.agentFrameworkConfig || {}); - // Trigger userMessageReceived hook with the configured plugins - await handlerHooks.userMessageReceived.promise({ - handlerContext, + // Trigger userMessageReceived hook with the configured tools + await frameworkHooks.userMessageReceived.promise({ + agentFrameworkContext: frameworkContext, content, messageId, timestamp: now, }); // Notify agent update after user message is added - this.notifyAgentUpdate(agentId, handlerContext.agent); + this.notifyAgentUpdate(agentId, frameworkContext.agent); try { // Create async generator - const generator = handler(handlerContext); + const generator = framework(frameworkContext); // Track the last message for completion handling let lastResult: AgentInstanceLatestStatus | undefined; @@ -415,7 +415,7 @@ export class AgentInstanceService implements IAgentInstanceService { } // Notify agent update with latest messages for real-time UI updates - this.notifyAgentUpdate(agentId, handlerContext.agent); + this.notifyAgentUpdate(agentId, frameworkContext.agent); } // Store the last result for completion handling @@ -442,8 +442,8 @@ export class AgentInstanceService implements IAgentInstanceService { } // Trigger agentStatusChanged hook for completion - await handlerHooks.agentStatusChanged.promise({ - handlerContext, + await frameworkHooks.agentStatusChanged.promise({ + agentFrameworkContext: frameworkContext, status: { state: 'completed', modified: new Date(), @@ -458,8 +458,8 @@ export class AgentInstanceService implements IAgentInstanceService { logger.error(`Agent handler execution failed: ${errorMessage}`); // Trigger agentStatusChanged hook for failure - await handlerHooks.agentStatusChanged.promise({ - handlerContext, + await frameworkHooks.agentStatusChanged.promise({ + agentFrameworkContext: frameworkContext, status: { state: 'failed', modified: new Date(), @@ -848,31 +848,31 @@ export class AgentInstanceService implements IAgentInstanceService { } } - public concatPrompt(promptDescription: Pick, messages: AgentInstanceMessage[]): Observable { + public concatPrompt(promptDescription: Pick, messages: AgentInstanceMessage[]): Observable { logger.debug('AgentInstanceService.concatPrompt called', { - hasPromptConfig: !!promptDescription.handlerConfig, - promptConfigKeys: Object.keys(promptDescription.handlerConfig), + hasPromptConfig: !!promptDescription.agentFrameworkConfig, + promptConfigKeys: Object.keys(promptDescription.agentFrameworkConfig || {}), messagesCount: messages.length, }); return new Observable((observer) => { const processStream = async () => { try { - // Create a minimal handler context for prompt concatenation - const handlerContext = { + // Create a minimal framework context for prompt concatenation + const frameworkContext = { agent: { id: 'temp', messages, agentDefId: 'temp', status: { state: 'working' as const, modified: new Date() }, created: new Date(), - handlerConfig: {}, + agentFrameworkConfig: {}, }, - agentDef: { id: 'temp', name: 'temp', handlerConfig: promptDescription.handlerConfig }, + agentDef: { id: 'temp', name: 'temp', agentFrameworkConfig: promptDescription.agentFrameworkConfig || {} }, isCancelled: () => false, }; - const streamGenerator = promptConcatStream(promptDescription as AgentPromptDescription, messages, handlerContext); + const streamGenerator = promptConcatStream(promptDescription as AgentPromptDescription, messages, frameworkContext); for await (const state of streamGenerator) { observer.next(state); if (state.isComplete) { @@ -893,21 +893,21 @@ export class AgentInstanceService implements IAgentInstanceService { }); } - public getHandlerConfigSchema(handlerId: string): Record { + public getFrameworkConfigSchema(frameworkId: string): Record { try { - logger.debug('AgentInstanceService.getHandlerConfigSchema called', { handlerId }); - // Check if we have a schema for this handler - const schema = this.handlerSchemas.get(handlerId); + logger.debug('AgentInstanceService.getFrameworkConfigSchema called', { frameworkId }); + // Check if we have a schema for this framework + const schema = this.frameworkSchemas.get(frameworkId); if (schema) { return schema; } // If no schema found, return an empty schema - logger.warn(`No schema found for handler: ${handlerId}`); + logger.warn(`No schema found for framework: ${frameworkId}`); return { type: 'object', properties: {} }; } catch (error) { - logger.error('Error in AgentInstanceService.getHandlerConfigSchema', { + logger.error('Error in AgentInstanceService.getFrameworkConfigSchema', { error, - handlerId, + frameworkId, }); throw error; } diff --git a/src/services/agentInstance/interface.ts b/src/services/agentInstance/interface.ts index 0de93d0e..3d8d45eb 100644 --- a/src/services/agentInstance/interface.ts +++ b/src/services/agentInstance/interface.ts @@ -8,16 +8,16 @@ import { AgentPromptDescription } from '@services/agentInstance/promptConcat/pro /** * Content of a session instance that user chat with an agent. - * Inherits from AgentDefinition but makes handlerConfig optional to allow fallback. + * Inherits import { AgentFrameworkConfig } optional to allow fallback. * The instance can override the definition's configuration, or fall back to using it. */ -export interface AgentInstance extends Omit { +export interface AgentInstance extends Omit { /** Agent description ID that generates this instance */ agentDefId: string; /** Session name, optional in instance unlike definition */ name?: string; - /** Agent handler's config - optional, falls back to AgentDefinition.handlerConfig if not set */ - handlerConfig?: Record; + /** Agent framework's config - optional, falls back to AgentDefinition.agentFrameworkConfig if not set */ + agentFrameworkConfig?: Record; /** * Message history. * latest on top, so it's easy to get first one as user's latest input, and rest as history. @@ -119,7 +119,7 @@ export interface IAgentInstanceService { /** * For testing purposes, only initialize the built-in handlers without database */ - initializeHandlers(): Promise; + initializeFrameworks(): Promise; /** * Create a new agent instance from a definition @@ -196,15 +196,15 @@ export interface IAgentInstanceService { * @param messages Messages to be included in prompt generation * @returns Observable stream of processing states, with final state containing complete results */ - concatPrompt(promptDescription: Pick, messages: AgentInstanceMessage[]): Observable; + concatPrompt(promptDescription: Pick, messages: AgentInstanceMessage[]): Observable; /** * Get JSON Schema for handler configuration * This allows frontend to generate a form based on the schema for a specific handler - * @param handlerId Handler ID to get schema for + * @param agentFrameworkID Handler ID to get schema for * @returns JSON Schema for handler configuration */ - getHandlerConfigSchema(handlerId: string): Record; + getFrameworkConfigSchema(frameworkId: string): Record; /** * Save user message to database @@ -233,7 +233,7 @@ export const AgentInstanceServiceIPCDescriptor = { deleteAgent: ProxyPropertyType.Function, getAgent: ProxyPropertyType.Function, getAgents: ProxyPropertyType.Function, - getHandlerConfigSchema: ProxyPropertyType.Function, + getFrameworkConfigSchema: ProxyPropertyType.Function, saveUserMessage: ProxyPropertyType.Function, sendMsgToAgent: ProxyPropertyType.Function, subscribeToAgentUpdates: ProxyPropertyType.Function$, diff --git a/src/services/agentInstance/plugins/index.ts b/src/services/agentInstance/plugins/index.ts deleted file mode 100644 index 93b01fd1..00000000 --- a/src/services/agentInstance/plugins/index.ts +++ /dev/null @@ -1,188 +0,0 @@ -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 -export type { AgentResponse, PromptConcatHookContext, PromptConcatHooks, PromptConcatPlugin, ResponseHookContext }; - -/** - * Registry for built-in plugins - */ -export const builtInPlugins = new Map(); - -/** - * Create unified hooks instance for the complete plugin system - */ -export function createHandlerHooks(): PromptConcatHooks { - return { - // Prompt processing hooks - processPrompts: new AsyncSeriesWaterfallHook(['context']), - finalizePrompts: new AsyncSeriesWaterfallHook(['context']), - postProcess: new AsyncSeriesWaterfallHook(['context']), - // Agent lifecycle hooks - userMessageReceived: new AsyncSeriesHook(['context']), - agentStatusChanged: new AsyncSeriesHook(['context']), - toolExecuted: new AsyncSeriesHook(['context']), - responseUpdate: new AsyncSeriesHook(['context']), - responseComplete: new AsyncSeriesHook(['context']), - }; -} - -/** - * Get all available plugins - */ -async function getAllPlugins() { - const [ - promptPluginsModule, - wikiSearchModule, - wikiOperationModule, - workspacesListModule, - messageManagementModule, - ] = await Promise.all([ - import('./promptPlugins'), - import('./wikiSearchPlugin'), - import('./wikiOperationPlugin'), - import('./workspacesListPlugin'), - import('./messageManagementPlugin'), - ]); - - return { - messageManagementPlugin: messageManagementModule.messageManagementPlugin, - fullReplacementPlugin: promptPluginsModule.fullReplacementPlugin, - wikiSearchPlugin: wikiSearchModule.wikiSearchPlugin, - wikiOperationPlugin: wikiOperationModule.wikiOperationPlugin, - workspacesListPlugin: workspacesListModule.workspacesListPlugin, - }; -} - -/** - * Register plugins to hooks based on handler configuration - * @param hooks - The hooks instance to register plugins to - * @param handlerConfig - The handler configuration containing plugin settings - */ -export async function registerPluginsToHooksFromConfig( - hooks: PromptConcatHooks, - handlerConfig: { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, -): Promise { - // Always register core plugins that are needed for basic functionality - const messageManagementModule = await import('./messageManagementPlugin'); - messageManagementModule.messageManagementPlugin(hooks); - logger.debug('Registered messageManagementPlugin to hooks'); - - // Register plugins based on handler configuration - if (handlerConfig.plugins) { - for (const pluginConfig of handlerConfig.plugins) { - const { pluginId } = pluginConfig; - - // Get plugin from global registry (supports both built-in and dynamic plugins) - const plugin = builtInPlugins.get(pluginId); - if (plugin) { - plugin(hooks); - logger.debug(`Registered plugin ${pluginId} to hooks`); - } else { - logger.warn(`Plugin not found in registry: ${pluginId}`); - } - } - } -} - -/** - * Initialize plugin system - register all built-in plugins to global registry - * 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 and manage vector embeddings', - }, - ); - - 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); - builtInPlugins.set('fullReplacement', plugins.fullReplacementPlugin); - builtInPlugins.set('wikiSearch', plugins.wikiSearchPlugin); - builtInPlugins.set('wikiOperation', plugins.wikiOperationPlugin); - builtInPlugins.set('workspacesList', plugins.workspacesListPlugin); - logger.debug('All built-in plugins and schemas registered successfully'); -} - -/** - * Create hooks and register plugins based on handler configuration - * This creates a new hooks instance and registers plugins for that specific context - */ -export async function createHooksWithPlugins( - handlerConfig: { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, -): Promise<{ hooks: PromptConcatHooks; pluginConfigs: Array<{ pluginId: string; [key: string]: unknown }> }> { - const hooks = createHandlerHooks(); - await registerPluginsToHooksFromConfig(hooks, handlerConfig); - return { - hooks, - pluginConfigs: handlerConfig.plugins || [], - }; -} diff --git a/src/services/agentInstance/plugins/schemaRegistry.ts b/src/services/agentInstance/plugins/schemaRegistry.ts deleted file mode 100644 index 892b26ee..00000000 --- a/src/services/agentInstance/plugins/schemaRegistry.ts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * 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/promptConcat/Readme.md b/src/services/agentInstance/promptConcat/Readme.md index 874feacd..8e8178f5 100644 --- a/src/services/agentInstance/promptConcat/Readme.md +++ b/src/services/agentInstance/promptConcat/Readme.md @@ -1,54 +1,55 @@ # Prompt Concat Tools -Prompt engineering and message processing with a plugin-based architecture. +Prompt engineering and message processing with a tool-based architecture. -If final prompt is a food, then `handlerConfig.prompts` is the recipe. Chat history and user input are raw materials. +If final prompt is a food, then `agentFrameworkConfig.prompts` is the recipe. Chat history and user input are raw materials. ## Implementation -The `promptConcat` function uses a tapable hooks-based plugin system. Built-in plugins are registered by `pluginId` and loaded based on configuration in `defaultAgents.json`. +The `promptConcat` function uses a tapable hooks-based tool system. Built-in tools are registered by `toolId` and loaded based on configuration in `taskAgents.json`. -### Plugin System Architecture +### Tool System Architecture -1. **Hooks**: Uses tapable `AsyncSeriesWaterfallHook` for plugin execution +1. **Hooks**: Uses tapable `AsyncSeriesWaterfallHook` for tool execution - `processPrompts`: Modifies prompt tree during processing - `finalizePrompts`: Final processing before LLM call - `postProcess`: Handles response processing -2. **Built-in Plugins**: +2. **Built-in Tools**: - `fullReplacement`: Replaces content from various sources - `dynamicPosition`: Inserts content at specific positions - `retrievalAugmentedGeneration`: Retrieves content from wiki/external sources - `modelContextProtocol`: Integrates with external MCP servers - `toolCalling`: Processes function calls in responses -3. **Plugin Registration**: - - Plugins are registered by `pluginId` field in the `plugins` array - - Each plugin instance has its own configuration parameters - - Built-in plugins are auto-registered on system initialization +3. **Tool Registration**: + - Tools are registered by `toolId` field in the `plugins` array + - Each tool instance has its own configuration parameters + - Built-in tools are auto-registered on system initialization -### Plugin Lifecycle +### Tool Lifecycle -2. **Configuration**: Plugins are loaded based on `handlerConfig.plugins` array -3. **Execution**: Hooks execute plugins in registration order -4. **Error Handling**: Individual plugin failures don't stop the pipeline +1. **Registration**: Tools are registered during initialization +2. **Configuration**: Tools are loaded based on `agentFrameworkConfig.plugins` array +3. **Execution**: Hooks execute tools in registration order +4. **Error Handling**: Individual tool failures don't stop the pipeline -### Adding New Plugins +### Adding New Tools -1. Create plugin function in `plugins/` directory -2. Register in `plugins/index.ts` -3. Add `pluginId` to schema enum +1. Create tool function in `tools/` directory +2. Register in `tools/index.ts` +3. Add `toolId` to schema enum 4. Add parameter schema if needed -Each plugin receives a hooks object and registers handlers for specific hook points. Plugins can modify prompt trees, inject content, process responses, and trigger additional LLM calls. +Each tool receives a hooks object and registers handlers for specific hook points. Tools can modify prompt trees, inject content, process responses, and trigger additional LLM calls. -### Example Plugin Structure +### Example Tool Structure ```typescript -export const myPlugin: PromptConcatPlugin = (hooks) => { - hooks.processPrompts.tapAsync('myPlugin', async (context, callback) => { - const { plugin, prompts, messages } = context; - // Plugin logic here +export const myTool: PromptConcatTool = (hooks) => { + hooks.processPrompts.tapAsync('myTool', async (context, callback) => { + const { tool, prompts, messages } = context; + // Tool logic here callback(null, context); }); }; diff --git a/src/services/agentInstance/promptConcat/promptConcat.ts b/src/services/agentInstance/promptConcat/promptConcat.ts index f6fac07b..83758a21 100644 --- a/src/services/agentInstance/promptConcat/promptConcat.ts +++ b/src/services/agentInstance/promptConcat/promptConcat.ts @@ -10,17 +10,17 @@ * Main Concepts: * - Prompts are tree-structured, can have roles (system/user/assistant) and children. * - Plugins use hooks to modify the prompt tree at runtime. - * - Built-in plugins are registered by pluginId and executed when matching plugins are found. + * - Built-in tools are registered by toolId and executed when matching tools are found. */ import { logger } from '@services/libs/log'; import { ModelMessage } from 'ai'; import { cloneDeep } from 'lodash'; -import { AgentHandlerContext } from '../buildInAgentHandlers/type'; +import { AgentFrameworkContext } from '../agentFrameworks/utilities/type'; import { AgentInstanceMessage } from '../interface'; -import { builtInPlugins, createHandlerHooks, PromptConcatHookContext } from '../plugins'; +import { builtInTools, createAgentFrameworkHooks, PromptConcatHookContext } from '../tools'; import type { AgentPromptDescription, IPrompt } from './promptConcatSchema'; -import type { IPromptConcatPlugin } from './promptConcatSchema/plugin'; +import type { IPromptConcatTool } from './promptConcatSchema/plugin'; /** * Context type specific for prompt concatenation operations @@ -37,7 +37,7 @@ export interface PromptConcatContext { * Generate ID-based path mapping for prompts to enable source tracking * Uses actual node IDs instead of indices to avoid path conflicts with dynamic content */ -function generateSourcePaths(prompts: IPrompt[], plugins: IPromptConcatPlugin[] = []): Map { +function generateSourcePaths(prompts: IPrompt[], plugins: IPromptConcatTool[] = []): Map { const pathMap = new Map(); function traversePrompts(items: IPrompt[], currentPath: string[]): void { items.forEach((item) => { @@ -48,7 +48,7 @@ function generateSourcePaths(prompts: IPrompt[], plugins: IPromptConcatPlugin[] } }); } - function traversePlugins(items: IPromptConcatPlugin[], currentPath: string[]): void { + function traversePlugins(items: IPromptConcatTool[], currentPath: string[]): void { items.forEach((item) => { const itemPath = [...currentPath, item.id]; pathMap.set(item.id, itemPath); @@ -191,7 +191,7 @@ export interface PromptConcatStreamState { /** Current processing step */ step: 'plugin' | 'finalize' | 'flatten' | 'complete'; /** Current plugin being processed (if step is 'plugin') */ - currentPlugin?: IPromptConcatPlugin; + currentPlugin?: IPromptConcatTool; /** Processing progress (0-1) */ progress: number; /** Whether processing is complete */ @@ -203,40 +203,41 @@ export interface PromptConcatStreamState { * Yields intermediate results for real-time UI updates */ export async function* promptConcatStream( - agentConfig: Pick, + agentConfig: Pick, messages: AgentInstanceMessage[], - handlerContext: AgentHandlerContext, + agentFrameworkContext: AgentFrameworkContext, ): AsyncGenerator { - const promptConfigs = Array.isArray(agentConfig.handlerConfig.prompts) ? agentConfig.handlerConfig.prompts : []; - const pluginConfigs = (Array.isArray(agentConfig.handlerConfig.plugins) ? agentConfig.handlerConfig.plugins : []) as IPromptConcatPlugin[]; + const agentFrameworkConfig = agentConfig.agentFrameworkConfig; + const promptConfigs = Array.isArray(agentFrameworkConfig?.prompts) ? agentFrameworkConfig.prompts : []; + const toolConfigs = (Array.isArray(agentFrameworkConfig?.plugins) ? agentFrameworkConfig.plugins : []) as IPromptConcatTool[]; const promptsCopy = cloneDeep(promptConfigs); - const sourcePaths = generateSourcePaths(promptsCopy, pluginConfigs); + const sourcePaths = generateSourcePaths(promptsCopy, toolConfigs); - const hooks = createHandlerHooks(); - // Register plugins that match the configuration - for (const plugin of pluginConfigs) { - const builtInPlugin = builtInPlugins.get(plugin.pluginId); - if (builtInPlugin) { - builtInPlugin(hooks); - logger.debug('Registered plugin', { - pluginId: plugin.pluginId, - pluginInstanceId: plugin.id, + const hooks = createAgentFrameworkHooks(); + // Register tools that match the configuration + for (const tool of toolConfigs) { + const builtInTool = builtInTools.get(tool.toolId); + if (builtInTool) { + builtInTool(hooks); + logger.debug('Registered tool', { + toolId: tool.toolId, + toolInstanceId: tool.id, }); } else { - logger.info(`No built-in plugin found for pluginId: ${plugin.pluginId}`); + logger.info(`No built-in tool found for toolId: ${tool.toolId}`); } } // Process each plugin through hooks with streaming let modifiedPrompts = promptsCopy; - const totalSteps = pluginConfigs.length + 2; // plugins + finalize + flatten + const totalSteps = toolConfigs.length + 2; // plugins + finalize + flatten - for (let index = 0; index < pluginConfigs.length; index++) { + for (let index = 0; index < toolConfigs.length; index++) { const context: PromptConcatHookContext = { - handlerContext, + agentFrameworkContext: agentFrameworkContext, messages, prompts: modifiedPrompts, - pluginConfig: pluginConfigs[index], + toolConfig: toolConfigs[index], metadata: { sourcePaths }, }; try { @@ -255,13 +256,13 @@ export async function* promptConcatStream( processedPrompts: modifiedPrompts, flatPrompts: intermediateFlat, step: 'plugin', - currentPlugin: pluginConfigs[index], + currentPlugin: toolConfigs[index], progress: (index + 1) / totalSteps, isComplete: false, }; } catch (error) { logger.error('Plugin processing error', { - pluginConfig: pluginConfigs[index], + toolConfig: toolConfigs[index], error, }); // Continue processing other plugins even if one fails @@ -273,15 +274,15 @@ export async function* promptConcatStream( processedPrompts: modifiedPrompts, flatPrompts: flattenPrompts(modifiedPrompts), step: 'finalize', - progress: (pluginConfigs.length + 1) / totalSteps, + progress: (toolConfigs.length + 1) / totalSteps, isComplete: false, }; const finalContext: PromptConcatHookContext = { - handlerContext, + agentFrameworkContext: agentFrameworkContext, messages, prompts: modifiedPrompts, - pluginConfig: {} as IPromptConcatPlugin, // Empty plugin for finalization + toolConfig: {} as IPromptConcatTool, // Empty tool for finalization metadata: { sourcePaths }, }; @@ -297,7 +298,7 @@ export async function* promptConcatStream( processedPrompts: modifiedPrompts, flatPrompts: flattenPrompts(modifiedPrompts), step: 'flatten', - progress: (pluginConfigs.length + 2) / totalSteps, + progress: (toolConfigs.length + 2) / totalSteps, isComplete: false, }; @@ -341,15 +342,15 @@ export async function* promptConcatStream( * @returns Processed prompt array and original prompt tree */ export async function promptConcat( - agentConfig: Pick, + agentConfig: Pick, messages: AgentInstanceMessage[], - handlerContext: AgentHandlerContext, + agentFrameworkContext: AgentFrameworkContext, ): Promise<{ flatPrompts: ModelMessage[]; processedPrompts: IPrompt[]; }> { // Use the streaming version and just return the final result - const stream = promptConcatStream(agentConfig, messages, handlerContext); + const stream = promptConcatStream(agentConfig, messages, agentFrameworkContext); let finalResult: PromptConcatStreamState; // Consume all intermediate states to get the final result diff --git a/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts b/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts index 4d2263cb..d2c980d6 100644 --- a/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts +++ b/src/services/agentInstance/promptConcat/promptConcatSchema/index.ts @@ -1,4 +1,4 @@ -import { createDynamicPromptConcatPluginSchema } from '@services/agentInstance/plugins/schemaRegistry'; +import { createDynamicPromptConcatToolSchema } from '@services/agentInstance/tools/schemaRegistry'; import { t } from '@services/libs/i18n/placeholder'; import { z } from 'zod/v4'; import { ModelParametersSchema, ProviderModelSchema } from './modelParameters'; @@ -34,12 +34,12 @@ 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 + * Framework configuration schema + * Contains the framework-related configuration fields for prompts, responses, and tools + * This is dynamically generated to include all registered tools */ -export function getHandlerConfigSchema() { - const dynamicPluginSchema = createDynamicPromptConcatPluginSchema(); +export function getFrameworkConfigSchema() { + const dynamicToolSchema = createDynamicPromptConcatToolSchema(); return z.object({ prompts: z.array(PromptSchema).meta({ @@ -50,7 +50,7 @@ export function getHandlerConfigSchema() { description: t('Schema.AgentConfig.PromptConfig.Response'), title: t('PromptConfig.Tabs.Response'), }), - plugins: z.array(dynamicPluginSchema).meta({ + plugins: z.array(dynamicToolSchema).meta({ description: t('Schema.AgentConfig.PromptConfig.Plugins'), title: t('PromptConfig.Tabs.Plugins'), }), @@ -66,13 +66,13 @@ export function getHandlerConfigSchema() { * @example * ```json * { - * "id": "example-agent", + * "id": "task-agent", * "api": { * "provider": "siliconflow", * "model": "Qwen/Qwen2.5-7B-Instruct" * }, * "modelParameters": { ... }, - * "handlerConfig": { + * "agentFrameworkConfig": { * "prompts": [ ... ], * "response": [ ... ], * "plugins": [ ... ], @@ -81,14 +81,14 @@ export function getHandlerConfigSchema() { * ``` */ export function getAgentConfigSchema() { - const dynamicHandlerConfigSchema = getHandlerConfigSchema(); + const dynamicFrameworkConfigSchema = getFrameworkConfigSchema(); return BaseAPIConfigSchema.extend({ id: z.string().meta({ title: t('Schema.AgentConfig.IdTitle'), description: t('Schema.AgentConfig.Id'), }), - handlerConfig: dynamicHandlerConfigSchema, + agentFrameworkConfig: dynamicFrameworkConfigSchema, }).meta({ title: t('Schema.AgentConfig.Title'), description: t('Schema.AgentConfig.Description'), @@ -110,10 +110,15 @@ export function getDefaultAgentsSchema() { export type DefaultAgents = z.infer>; export type AgentPromptDescription = z.infer>; export type AiAPIConfig = z.infer; -export type HandlerConfig = z.infer>; +export type AgentFrameworkConfig = z.infer>; +// Backward compat aliases - deprecated, use AgentFrameworkConfig directly +export type HandlerConfig = AgentFrameworkConfig; // Re-export all schemas and types export * from './modelParameters'; export * from './plugin'; export * from './prompts'; export * from './response'; + +// Export IPromptConcatTool as IPromptConcatPlugin for backward compatibility +export type { IPromptConcatTool as IPromptConcatPlugin } from './plugin'; diff --git a/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts b/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts index ddf873c6..c6d7cb8a 100644 --- a/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts +++ b/src/services/agentInstance/promptConcat/promptConcatSchema/jsonSchema.ts @@ -1,5 +1,5 @@ import { z } from 'zod/v4'; -import { getHandlerConfigSchema } from './index'; +import { getFrameworkConfigSchema } from './index'; /** * Get the dynamically generated JSON Schema for handler configuration @@ -11,7 +11,7 @@ import { getHandlerConfigSchema } from './index'; * * Description field is i18n key, use i18nAlly extension to see it on VSCode. And use react-i18next to translate it on frontend. */ -export function getPromptConcatHandlerConfigJsonSchema() { - const dynamicHandlerConfigSchema = getHandlerConfigSchema(); - return z.toJSONSchema(dynamicHandlerConfigSchema, { target: 'draft-7' }); +export function getPromptConcatAgentFrameworkConfigJsonSchema() { + const dynamicFrameworkConfigSchema = getFrameworkConfigSchema(); + return z.toJSONSchema(dynamicFrameworkConfigSchema, { target: 'draft-7' }); } diff --git a/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts b/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts index a7e09206..57d6c643 100644 --- a/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts +++ b/src/services/agentInstance/promptConcat/promptConcatSchema/plugin.ts @@ -1,22 +1,22 @@ // 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'; +import type { ModelContextProtocolParameter } from '@services/agentInstance/tools/modelContextProtocol'; +import type { DynamicPositionParameter, FullReplacementParameter } from '@services/agentInstance/tools/prompt'; +import type { WikiOperationParameter } from '@services/agentInstance/tools/wikiOperation'; +import type { WikiSearchParameter } from '@services/agentInstance/tools/wikiSearch'; +import type { WorkspacesListParameter } from '@services/agentInstance/tools/workspacesList'; /** - * Type definition for prompt concat plugin + * Type definition for prompt concat tool * This includes all possible parameter fields for type safety */ -export type IPromptConcatPlugin = { +export type IPromptConcatTool = { id: string; caption?: string; content?: string; forbidOverrides?: boolean; - pluginId: string; + toolId: string; - // Plugin-specific parameters + // Tool-specific parameters fullReplacementParam?: FullReplacementParameter; dynamicPositionParam?: DynamicPositionParameter; wikiOperationParam?: WikiOperationParameter; diff --git a/src/services/agentInstance/promptConcat/responseConcat.ts b/src/services/agentInstance/promptConcat/responseConcat.ts index e4eba3d7..8eaa6afb 100644 --- a/src/services/agentInstance/promptConcat/responseConcat.ts +++ b/src/services/agentInstance/promptConcat/responseConcat.ts @@ -6,12 +6,12 @@ import { ToolCallingMatch } from '@services/agentDefinition/interface'; import { logger } from '@services/libs/log'; import { cloneDeep } from 'lodash'; -import { AgentHandlerContext } from '../buildInAgentHandlers/type'; +import { AgentFrameworkContext } from '../agentFrameworks/utilities/type'; import { AgentInstanceMessage } from '../interface'; -import { builtInPlugins, createHandlerHooks } from '../plugins'; -import { AgentResponse, PostProcessContext, YieldNextRoundTarget } from '../plugins/types'; -import type { IPromptConcatPlugin } from './promptConcatSchema'; -import { AgentPromptDescription, HandlerConfig } from './promptConcatSchema'; +import { builtInTools, createAgentFrameworkHooks } from '../tools'; +import { AgentResponse, PostProcessContext, YieldNextRoundTarget } from '../tools/types'; +import type { IPromptConcatTool } from './promptConcatSchema'; +import { AgentFrameworkConfig, AgentPromptDescription } from './promptConcatSchema'; /** * Process response configuration, apply plugins, and return final response @@ -24,7 +24,7 @@ import { AgentPromptDescription, HandlerConfig } from './promptConcatSchema'; export async function responseConcat( agentConfig: AgentPromptDescription, llmResponse: string, - context: AgentHandlerContext, + context: AgentFrameworkContext, messages: AgentInstanceMessage[] = [], ): Promise<{ processedResponse: string; @@ -38,33 +38,33 @@ export async function responseConcat( responseLength: llmResponse.length, }); - const { handlerConfig } = agentConfig; - const responses: HandlerConfig['response'] = Array.isArray(handlerConfig.response) ? handlerConfig.response : []; - const plugins = (Array.isArray(handlerConfig.plugins) ? handlerConfig.plugins : []) as IPromptConcatPlugin[]; + const { agentFrameworkConfig } = agentConfig; + const responses: AgentFrameworkConfig['response'] = Array.isArray(agentFrameworkConfig?.response) ? (agentFrameworkConfig?.response || []) : []; + const toolConfigs = (Array.isArray(agentFrameworkConfig.plugins) ? agentFrameworkConfig.plugins : []) as IPromptConcatTool[]; let modifiedResponses = cloneDeep(responses) as AgentResponse[]; // Create hooks instance - const hooks = createHandlerHooks(); - // Register all plugins from configuration - for (const plugin of plugins) { - const builtInPlugin = builtInPlugins.get(plugin.pluginId); - if (builtInPlugin) { - builtInPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + // Register all tools from configuration + for (const tool of toolConfigs) { + const builtInTool = builtInTools.get(tool.toolId); + if (builtInTool) { + builtInTool(hooks); } else { - logger.warn(`No built-in plugin found for response pluginId: ${plugin.pluginId}`); + logger.warn(`No built-in tool found for response toolId: ${tool.toolId}`); } } - // Process each plugin through hooks + // Process each tool through hooks let yieldNextRoundTo: YieldNextRoundTarget | undefined; let toolCallInfo: ToolCallingMatch | undefined; - for (const plugin of plugins) { + for (const tool of toolConfigs) { const responseContext: PostProcessContext = { - handlerContext: context, + agentFrameworkContext: context, messages, prompts: [], // Not used in response processing - pluginConfig: plugin, + toolConfig: tool, llmResponse, responses: modifiedResponses, metadata: {}, @@ -78,31 +78,31 @@ export async function responseConcat( modifiedResponses = result.responses; } - // Check if plugin indicated need for new LLM call via actions + // Check if tool indicated need for new LLM call via actions if (result.actions?.yieldNextRoundTo) { yieldNextRoundTo = result.actions.yieldNextRoundTo; if (result.actions.toolCalling) { toolCallInfo = result.actions.toolCalling; } - logger.debug('Plugin requested yield next round', { - pluginId: plugin.pluginId, - pluginInstanceId: plugin.id, + logger.debug('Tool requested yield next round', { + toolId: tool.toolId, + toolInstanceId: tool.id, yieldNextRoundTo, hasToolCall: !!result.actions.toolCalling, }); } - logger.debug('Response plugin processed successfully', { - pluginId: plugin.pluginId, - pluginInstanceId: plugin.id, + logger.debug('Response tool processed successfully', { + toolId: tool.toolId, + toolInstanceId: tool.id, }); } catch (error) { - logger.error('Response plugin processing error', { - pluginId: plugin.pluginId, - pluginInstanceId: plugin.id, + logger.error('Response tool processing error', { + toolId: tool.toolId, + toolInstanceId: tool.id, error, }); - // Continue processing other plugins even if one fails + // Continue processing other tools even if one fails } } diff --git a/src/services/agentInstance/plugins/__tests__/fullReplacementPlugin.duration.test.ts b/src/services/agentInstance/tools/__tests__/fullReplacementPlugin.duration.test.ts similarity index 80% rename from src/services/agentInstance/plugins/__tests__/fullReplacementPlugin.duration.test.ts rename to src/services/agentInstance/tools/__tests__/fullReplacementPlugin.duration.test.ts index 0f81d8d0..f3ef08bf 100644 --- a/src/services/agentInstance/plugins/__tests__/fullReplacementPlugin.duration.test.ts +++ b/src/services/agentInstance/tools/__tests__/fullReplacementPlugin.duration.test.ts @@ -1,21 +1,21 @@ /** * Tests for Full Replacement plugin duration mechanism * Tests that expired messages (with duration) are filtered out from AI context - * Based on real configuration from defaultAgents.json + * Based on real configuration from taskAgents.json */ import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { AgentInstanceMessage } from '../../interface'; -import type { IPromptConcatPlugin } from '../../promptConcat/promptConcatSchema'; +import type { IPromptConcatTool } from '../../promptConcat/promptConcatSchema'; import type { IPrompt } from '../../promptConcat/promptConcatSchema/prompts'; import { cloneDeep } from 'lodash'; -import defaultAgents from '../../buildInAgentHandlers/defaultAgents.json'; -import { createHandlerHooks, PromptConcatHookContext } from '../index'; -import { fullReplacementPlugin } from '../promptPlugins'; +import defaultAgents from '../../agentFrameworks/taskAgents.json'; +import { createAgentFrameworkHooks, PromptConcatHookContext } from '../index'; +import { fullReplacementTool } from '../prompt'; // Use the real agent config const exampleAgent = defaultAgents[0]; -const realHandlerConfig = exampleAgent.handlerConfig; +const realAgentFrameworkConfig = exampleAgent.agentFrameworkConfig; describe('Full Replacement Plugin - Duration Mechanism', () => { beforeEach(() => { @@ -24,15 +24,15 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { describe('History Source Type with Duration Filtering', () => { it('should filter out expired messages (duration=1) from historyOfSession', async () => { - // Find the real fullReplacement plugin for history from defaultAgents.json - const historyPlugin = realHandlerConfig.plugins.find( - p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', + // Find the real fullReplacement plugin for history from taskAgents.json + const historyPlugin = realAgentFrameworkConfig.plugins.find( + p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', ); expect(historyPlugin).toBeDefined(); expect(historyPlugin!.fullReplacementParam!.targetId).toBe('default-history'); // Real target ID - // Use real prompts structure from defaultAgents.json - const testPrompts = cloneDeep(realHandlerConfig.prompts) as IPrompt[]; + // Use real prompts structure from taskAgents.json + const testPrompts = cloneDeep(realAgentFrameworkConfig.prompts) as IPrompt[]; const messages: AgentInstanceMessage[] = [ // Message 0: User message, no duration - should be included @@ -96,7 +96,7 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { ]; const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', messages, @@ -104,16 +104,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { status: { state: 'working' as const, modified: new Date() }, created: new Date(), }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }, - pluginConfig: historyPlugin! as unknown as IPromptConcatPlugin, // Type cast due to JSON import limitations + toolConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast due to JSON import limitations prompts: testPrompts, messages, }; - const hooks = createHandlerHooks(); - fullReplacementPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + fullReplacementTool(hooks); // Execute the processPrompts hook await hooks.processPrompts.promise(context); @@ -126,8 +126,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { const targetPrompt = historyPrompt!.children?.find(child => child.id === targetId); expect(targetPrompt).toBeDefined(); - // The fullReplacementPlugin puts filtered messages in children array - // Note: fullReplacementPlugin removes the last message (current user message) + // The fullReplacementTool puts filtered messages in children array + // Note: fullReplacementTool removes the last message (current user message) const children = (targetPrompt as unknown as { children?: IPrompt[] }).children || []; expect(children.length).toBe(2); // Only non-expired messages (user1, ai-response), excluding last user message @@ -147,8 +147,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { }); it('should include messages with duration=0 (visible in current round)', async () => { - const historyPlugin = realHandlerConfig.plugins.find( - p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', + const historyPlugin = realAgentFrameworkConfig.plugins.find( + p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', ); const messages: AgentInstanceMessage[] = [ @@ -181,10 +181,10 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { }, ]; - const testPrompts = cloneDeep(realHandlerConfig.prompts) as IPrompt[]; + const testPrompts = cloneDeep(realAgentFrameworkConfig.prompts) as IPrompt[]; const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', messages, @@ -192,16 +192,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { status: { state: 'working' as const, modified: new Date() }, created: new Date(), }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }, - pluginConfig: historyPlugin! as unknown as IPromptConcatPlugin, // Type cast for JSON import + toolConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast for JSON import prompts: testPrompts, messages, }; - const hooks = createHandlerHooks(); - fullReplacementPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + fullReplacementTool(hooks); await hooks.processPrompts.promise(context); @@ -220,8 +220,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { }); it('should handle mixed duration values correctly', async () => { - const historyPlugin = realHandlerConfig.plugins.find( - p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', + const historyPlugin = realAgentFrameworkConfig.plugins.find( + p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', ); const messages: AgentInstanceMessage[] = [ @@ -263,10 +263,10 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { }, ]; - const testPrompts = cloneDeep(realHandlerConfig.prompts) as IPrompt[]; + const testPrompts = cloneDeep(realAgentFrameworkConfig.prompts) as IPrompt[]; const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', messages, @@ -274,16 +274,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { status: { state: 'working' as const, modified: new Date() }, created: new Date(), }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }, - pluginConfig: historyPlugin! as unknown as IPromptConcatPlugin, // Type cast for JSON import + toolConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast for JSON import prompts: testPrompts, messages, }; - const hooks = createHandlerHooks(); - fullReplacementPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + fullReplacementTool(hooks); await hooks.processPrompts.promise(context); @@ -308,8 +308,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => { describe('LLM Response Source Type', () => { it('should verify LLM response replacement config exists', () => { // Verify the real config has LLM response replacement - const llmResponsePlugin = realHandlerConfig.plugins.find( - p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'llmResponse', + const llmResponsePlugin = realAgentFrameworkConfig.plugins.find( + p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'llmResponse', ); expect(llmResponsePlugin).toBeDefined(); expect(llmResponsePlugin!.fullReplacementParam!.targetId).toBe('default-response'); diff --git a/src/services/agentInstance/plugins/__tests__/messageManagementPlugin.test.ts b/src/services/agentInstance/tools/__tests__/messageManagementPlugin.test.ts similarity index 91% rename from src/services/agentInstance/plugins/__tests__/messageManagementPlugin.test.ts rename to src/services/agentInstance/tools/__tests__/messageManagementPlugin.test.ts index dc911b1e..a02e50d8 100644 --- a/src/services/agentInstance/plugins/__tests__/messageManagementPlugin.test.ts +++ b/src/services/agentInstance/tools/__tests__/messageManagementPlugin.test.ts @@ -1,6 +1,6 @@ /** - * Deep integration tests for messageManagementPlugin with real SQLite database - * Tests actual message persistence scenarios using defaultAgents.json configuration + * Deep integration tests for messageManagementTool with real SQLite database + * Tests actual message persistence scenarios using taskAgents.json configuration */ import { container } from '@services/container'; import type { IDatabaseService } from '@services/database/interface'; @@ -8,20 +8,20 @@ import { AgentDefinitionEntity, AgentInstanceEntity, AgentInstanceMessageEntity import serviceIdentifier from '@services/serviceIdentifier'; import { DataSource } from 'typeorm'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import defaultAgents from '../../buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '../../agentFrameworks/taskAgents.json'; import type { AgentInstanceMessage, IAgentInstanceService } from '../../interface'; -import { createHandlerHooks } from '../index'; -import { messageManagementPlugin } from '../messageManagementPlugin'; +import { createAgentFrameworkHooks } from '../index'; +import { messageManagementTool } from '../messageManagement'; import type { ToolExecutionContext, UserMessageContext } from '../types'; -// Use the real agent config from defaultAgents.json +// Use the real agent config from taskAgents.json const exampleAgent = defaultAgents[0]; describe('Message Management Plugin - Real Database Integration', () => { let testAgentId: string; // agentInstanceServiceImpl available to test blocks let agentInstanceServiceImpl: IAgentInstanceService; - let hooks: ReturnType; + let hooks: ReturnType; let realDataSource: DataSource; beforeEach(async () => { @@ -69,15 +69,15 @@ describe('Message Management Plugin - Real Database Integration', () => { await agentInstanceServiceImpl.initialize(); // Initialize plugin - hooks = createHandlerHooks(); - messageManagementPlugin(hooks); + hooks = createAgentFrameworkHooks(); + messageManagementTool(hooks); }); afterEach(async () => { // Clean up is handled automatically by beforeEach for each test }); - const createHandlerContext = (messages: AgentInstanceMessage[] = []) => ({ + const createAgentFrameworkContext = (messages: AgentInstanceMessage[] = []) => ({ agent: { id: testAgentId, agentDefId: exampleAgent.id, @@ -90,19 +90,19 @@ describe('Message Management Plugin - Real Database Integration', () => { name: exampleAgent.name, version: '1.0.0', capabilities: [], - handlerConfig: exampleAgent.handlerConfig, + agentFrameworkConfig: exampleAgent.agentFrameworkConfig, }, isCancelled: () => false, }); describe('Real Wiki Search Scenario - The Missing Tool Result Bug', () => { it('should persist all messages in wiki search flow: user query → AI tool call → tool result → AI final response', async () => { - const handlerContext = createHandlerContext(); + const agentFrameworkContext = createAgentFrameworkContext(); // Step 1: User asks to search wiki const userMessageId = `user-msg-${Date.now()}`; const userContext: UserMessageContext = { - handlerContext, + agentFrameworkContext, content: { text: '搜索 wiki 中的 Index 条目并解释' }, messageId: userMessageId, timestamp: new Date(), @@ -133,10 +133,10 @@ describe('Message Management Plugin - Real Database Integration', () => { }; await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage); - handlerContext.agent.messages.push(aiToolCallMessage); + agentFrameworkContext.agent.messages.push(aiToolCallMessage); // Step 3: Tool result message (THIS IS THE MISSING PIECE!) - // This simulates what wikiSearchPlugin does when tool execution completes + // This simulates what wikiSearchTool does when tool execution completes const toolResultMessage: AgentInstanceMessage = { id: `tool-result-${Date.now()}`, agentId: testAgentId, @@ -164,11 +164,11 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内 duration: 10, // Tool results might have expiration }; - // Add tool result to agent messages (simulating what wikiSearchPlugin does) - handlerContext.agent.messages.push(toolResultMessage); + // Add tool result to agent messages (simulating what wikiSearchTool does) + agentFrameworkContext.agent.messages.push(toolResultMessage); const toolContext: ToolExecutionContext = { - handlerContext, + agentFrameworkContext, toolResult: { success: true, data: 'Wiki search completed successfully', @@ -202,7 +202,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内 expect(savedToolResult?.duration).toBe(10); // Verify isPersisted flag was updated - const toolMessageInMemory = handlerContext.agent.messages.find( + const toolMessageInMemory = agentFrameworkContext.agent.messages.find( (m) => m.metadata?.isToolResult, ); expect(toolMessageInMemory?.metadata?.isPersisted).toBe(true); @@ -249,7 +249,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内 }); it('should handle multiple tool results in one execution', async () => { - const handlerContext = createHandlerContext(); + const agentFrameworkContext = createAgentFrameworkContext(); // Add multiple tool result messages const toolResult1: AgentInstanceMessage = { @@ -282,10 +282,10 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内 duration: 3, }; - handlerContext.agent.messages.push(toolResult1, toolResult2); + agentFrameworkContext.agent.messages.push(toolResult1, toolResult2); const toolContext: ToolExecutionContext = { - handlerContext, + agentFrameworkContext, toolResult: { success: true, data: 'Multiple tool search completed', @@ -316,7 +316,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内 it('should maintain message integrity when reloading from database (simulating page refresh)', async () => { // This test simulates the issue where tool results are missing after page refresh - const handlerContext = createHandlerContext(); + const agentFrameworkContext = createAgentFrameworkContext(); // Step 1: Complete chat flow with user message → AI tool call → tool result → AI response const userMessage: AgentInstanceMessage = { @@ -372,9 +372,9 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内 await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage); // Add tool result to context and trigger persistence via toolExecuted hook - handlerContext.agent.messages.push(toolResultMessage); + agentFrameworkContext.agent.messages.push(toolResultMessage); const toolContext: ToolExecutionContext = { - handlerContext, + agentFrameworkContext, toolResult: { success: true, data: 'Search completed' }, toolInfo: { toolId: 'wiki-search', parameters: {} }, }; diff --git a/src/services/agentInstance/plugins/__tests__/wikiOperationPlugin.test.ts b/src/services/agentInstance/tools/__tests__/wikiOperationPlugin.test.ts similarity index 71% rename from src/services/agentInstance/plugins/__tests__/wikiOperationPlugin.test.ts rename to src/services/agentInstance/tools/__tests__/wikiOperationPlugin.test.ts index b20d661c..12b0744b 100644 --- a/src/services/agentInstance/plugins/__tests__/wikiOperationPlugin.test.ts +++ b/src/services/agentInstance/tools/__tests__/wikiOperationPlugin.test.ts @@ -1,5 +1,5 @@ /** - * Tests for wikiOperationPlugin + * Tests for wikiOperationTool */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -11,16 +11,16 @@ import type { IWikiService } from '@services/wiki/interface'; // Removed logger import as it is unused import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility'; -import type { IPromptConcatPlugin } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import type { IPromptConcatTool } from '@services/agentInstance/promptConcat/promptConcatSchema'; import type { IPrompt } from '@services/agentInstance/promptConcat/promptConcatSchema'; import type { AIStreamResponse } from '@services/externalAPI/interface'; import type { IWorkspaceService } from '@services/workspaces/interface'; -import type { AgentHandlerContext } from '../../buildInAgentHandlers/type'; +import type { AgentFrameworkContext } from '../../agentFrameworks/utilities/type'; import type { AgentInstance } from '../../interface'; -import { createHandlerHooks } from '../index'; -import type { AIResponseContext, PluginActions, PromptConcatHookContext } from '../types'; -import { wikiOperationPlugin } from '../wikiOperationPlugin'; -import { workspacesListPlugin } from '../workspacesListPlugin'; +import { createAgentFrameworkHooks } from '../index'; +import type { AIResponseContext, PromptConcatHookContext, ToolActions } from '../types'; +import { wikiOperationTool } from '../wikiOperation'; +import { workspacesListTool } from '../workspacesList'; // Mock i18n vi.mock('@services/libs/i18n', () => ({ @@ -50,8 +50,8 @@ vi.mock('@services/libs/i18n', () => ({ }, })); -// Helper to construct a complete AgentHandlerContext for tests -const makeHandlerContext = (agentId = 'test-agent'): AgentHandlerContext => ({ +// Helper to construct a complete AgentagentFrameworkContext for tests +const makeAgentFrameworkContext = (agentId = 'test-agent'): AgentFrameworkContext => ({ agent: { id: agentId, agentDefId: 'test-agent-def', @@ -59,11 +59,11 @@ const makeHandlerContext = (agentId = 'test-agent'): AgentHandlerContext => ({ status: { state: 'working', modified: new Date() }, created: new Date(), } as unknown as AgentInstance, - agentDef: { id: 'test-agent-def', name: 'test-agent-def', handlerConfig: {} } as unknown as { id: string; name: string; handlerConfig: Record }, + agentDef: { id: 'test-agent-def', name: 'test-agent-def', agentFrameworkConfig: {} } as unknown as { id: string; name: string; agentFrameworkConfig: Record }, isCancelled: () => false, }); -describe('wikiOperationPlugin', () => { +describe('wikiOperationTool', () => { beforeEach(async () => { vi.clearAllMocks(); }); @@ -73,12 +73,12 @@ describe('wikiOperationPlugin', () => { }); it('should inject wiki operation tool content when plugin is configured', async () => { - const hooks = createHandlerHooks(); - // First register workspacesListPlugin to inject available workspaces from the global mock - workspacesListPlugin(hooks); - wikiOperationPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + // First register workspacesListTool to inject available workspaces from the global mock + workspacesListTool(hooks); + wikiOperationTool(hooks); - // Start with prompts and run workspacesList injection first (pluginConfig for workspacesList) + // Start with prompts and run workspacesList injection first (toolConfig for workspacesList) const prompts: IPrompt[] = [ { id: 'target-prompt', @@ -88,48 +88,48 @@ describe('wikiOperationPlugin', () => { ]; const workspacesContext: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', messages: [], agentDefId: 'test', status: { state: 'working' as const, modified: new Date() }, created: new Date() }, - agentDef: { id: 'test', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }, messages: [], prompts, - pluginConfig: { + toolConfig: { id: 'workspaces-plugin', caption: 'Workspaces Plugin', forbidOverrides: false, - pluginId: 'workspacesList', + toolId: 'workspacesList', workspacesListParam: { targetId: 'target-prompt', position: 'after' as const, }, - } as unknown as IPromptConcatPlugin, + } as unknown as IPromptConcatTool, }; await hooks.processPrompts.promise(workspacesContext); // Then run wikiOperation injection which will append its tool content to the same prompt const wikiOpContext: PromptConcatHookContext = { - handlerContext: workspacesContext.handlerContext, + agentFrameworkContext: workspacesContext.agentFrameworkContext, messages: [], prompts, - pluginConfig: { + toolConfig: { id: 'test-plugin', - pluginId: 'wikiOperation', + toolId: 'wikiOperation', wikiOperationParam: { toolListPosition: { targetId: 'target-prompt', position: 'after' as const, }, }, - } as unknown as IPromptConcatPlugin, + } as unknown as IPromptConcatTool, }; await hooks.processPrompts.promise(wikiOpContext); const targetPrompt = prompts[0]; - // workspacesListPlugin and wikiOperationPlugin may both add children; assert the combined children text contains expected snippets + // workspacesListTool and wikiOperationTool may both add children; assert the combined children text contains expected snippets const childrenText = JSON.stringify(targetPrompt.children); expect(childrenText).toContain('wiki-operation'); // Ensure the injected tool content documents the supported operations (enum values) @@ -147,17 +147,17 @@ describe('wikiOperationPlugin', () => { describe('tool execution', () => { it('should execute create operation successfully', async () => { - const hooks = createHandlerHooks(); - wikiOperationPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiOperationTool(hooks); - const handlerContext = makeHandlerContext(); + const agentFrameworkContext = makeAgentFrameworkContext(); const context = { - handlerContext, - handlerConfig: { + agentFrameworkContext, + agentFrameworkConfig: { plugins: [ { - pluginId: 'wikiOperation', + toolId: 'wikiOperation', wikiOperationParam: { toolResultDuration: 1, }, @@ -183,9 +183,9 @@ describe('wikiOperationPlugin', () => { context.response.content = `${JSON.stringify(createParams)}`; // Add an assistant message containing the tool_use so the plugin can find it - handlerContext.agent.messages.push({ + agentFrameworkContext.agent.messages.push({ id: `m-${Date.now()}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'assistant', content: context.response.content, modified: new Date(), @@ -209,13 +209,13 @@ describe('wikiOperationPlugin', () => { expect(typeof wikiSvc.wikiOperationInServer).toBe('function'); const responseCtx: AIResponseContext = { - handlerContext, - pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, - handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, + agentFrameworkContext, + toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool, + agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> }, response: { requestId: 'r-create', content: context.response.content, status: 'done' } as AIStreamResponse, requestId: 'r-create', isFinal: true, - actions: {} as PluginActions, + actions: {} as ToolActions, }; await hooks.responseComplete.promise(responseCtx); @@ -227,7 +227,7 @@ describe('wikiOperationPlugin', () => { ); // Verify a tool result message was added to agent history - const toolResultMessage = handlerContext.agent.messages.find(m => m.metadata?.isToolResult); + const toolResultMessage = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult); expect(toolResultMessage).toBeTruthy(); expect(toolResultMessage?.content).toContain(''); // Check for general success wording and tiddler title @@ -238,15 +238,15 @@ describe('wikiOperationPlugin', () => { }); it('should execute update operation successfully', async () => { - const hooks = createHandlerHooks(); - wikiOperationPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiOperationTool(hooks); - const handlerContext = makeHandlerContext(); + const agentFrameworkContext = makeAgentFrameworkContext(); const context = { - handlerContext, - handlerConfig: { - plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], + agentFrameworkContext, + agentFrameworkConfig: { + plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }], }, response: { status: 'done' as const, @@ -256,9 +256,9 @@ describe('wikiOperationPlugin', () => { }; // Add assistant message so plugin can detect the tool call - handlerContext.agent.messages.push({ + agentFrameworkContext.agent.messages.push({ id: `m-${Date.now()}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'assistant', content: context.response.content, modified: new Date(), @@ -274,11 +274,11 @@ describe('wikiOperationPlugin', () => { context.response.content = `${JSON.stringify(updateParams)}`; const respCtx2: AIResponseContext = { - handlerContext, - pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, - handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, + agentFrameworkContext, + toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool, + agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> }, response: { requestId: 'r-update', content: context.response.content, status: 'done' } as AIStreamResponse, - actions: {} as PluginActions, + actions: {} as ToolActions, requestId: 'r-update', isFinal: true, }; @@ -291,22 +291,22 @@ describe('wikiOperationPlugin', () => { ); // Check general update success wording and tiddler title - const updateResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult); + const updateResult = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult); expect(updateResult).toBeTruthy(); expect(updateResult?.content).toContain('成功在Wiki工作空间'); expect(updateResult?.content).toContain('Existing Note'); }); it('should execute delete operation successfully', async () => { - const hooks = createHandlerHooks(); - wikiOperationPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiOperationTool(hooks); - const handlerContext = makeHandlerContext(); + const agentFrameworkContext = makeAgentFrameworkContext(); const context = { - handlerContext, - handlerConfig: { - plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], + agentFrameworkContext, + agentFrameworkConfig: { + plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }], }, response: { status: 'done' as const, @@ -316,9 +316,9 @@ describe('wikiOperationPlugin', () => { }; // Add assistant message so plugin can detect the tool call - handlerContext.agent.messages.push({ + agentFrameworkContext.agent.messages.push({ id: `m-${Date.now()}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'assistant', content: context.response.content, modified: new Date(), @@ -333,11 +333,11 @@ describe('wikiOperationPlugin', () => { context.response.content = `${JSON.stringify(deleteParams)}`; const respCtx3: AIResponseContext = { - handlerContext, - pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, - handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, + agentFrameworkContext, + toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool, + agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> }, response: { requestId: 'r-delete', content: context.response.content, status: 'done' } as AIStreamResponse, - actions: {} as PluginActions, + actions: {} as ToolActions, requestId: 'r-delete', isFinal: true, }; @@ -349,22 +349,22 @@ describe('wikiOperationPlugin', () => { ['Note to Delete'], ); - const deleteResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult); + const deleteResult = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult); expect(deleteResult).toBeTruthy(); expect(deleteResult?.content).toContain('成功从Wiki工作空间'); }); it('should handle workspace not found error', async () => { - const hooks = createHandlerHooks(); - wikiOperationPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiOperationTool(hooks); // Use an actual tool_use payload with a nonexistent workspace - const handlerContext = makeHandlerContext(); + const agentFrameworkContext = makeAgentFrameworkContext(); const context = { - handlerContext, - handlerConfig: { - plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], + agentFrameworkContext, + agentFrameworkConfig: { + plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }], }, response: { status: 'done', @@ -374,9 +374,9 @@ describe('wikiOperationPlugin', () => { }; // Add assistant message so plugin can detect the tool call - handlerContext.agent.messages.push({ + agentFrameworkContext.agent.messages.push({ id: `m-${Date.now()}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'assistant', content: context.response.content, modified: new Date(), @@ -390,17 +390,17 @@ describe('wikiOperationPlugin', () => { context.response.content = `${JSON.stringify(badParams)}`; const respCtx4: AIResponseContext = { - handlerContext, - pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, - handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, + agentFrameworkContext, + toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool, + agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> }, response: { requestId: 'r-error', content: context.response.content, status: 'done' } as AIStreamResponse, - actions: {} as PluginActions, + actions: {} as ToolActions, requestId: 'r-error', isFinal: true, }; await hooks.responseComplete.promise(respCtx4); - const errResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult); + const errResult = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult); expect(errResult).toBeTruthy(); expect(errResult?.content).toContain('工作空间名称或ID'); // Ensure control is yielded to self on error so AI gets the next round @@ -408,17 +408,17 @@ describe('wikiOperationPlugin', () => { }); it('should not execute when tool call is not found', async () => { - const hooks = createHandlerHooks(); - wikiOperationPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiOperationTool(hooks); // No tool_use in response - const handlerContext = makeHandlerContext(); + const agentFrameworkContext = makeAgentFrameworkContext(); const context = { - handlerContext, - handlerConfig: { - plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], + agentFrameworkContext, + agentFrameworkConfig: { + plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }], }, response: { status: 'done' as const, @@ -428,18 +428,18 @@ describe('wikiOperationPlugin', () => { }; await hooks.responseComplete.promise({ - handlerContext, - pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, - handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, + agentFrameworkContext, + toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool, + agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> }, response: { requestId: 'r-none', content: context.response.content, status: 'done' } as AIStreamResponse, - actions: {} as PluginActions, + actions: {} as ToolActions, requestId: 'r-none', isFinal: true, }); const wikiLocalAssert = container.get>(serviceIdentifier.Wiki); expect(wikiLocalAssert.wikiOperationInServer).not.toHaveBeenCalled(); - expect(handlerContext.agent.messages).toHaveLength(0); + expect(agentFrameworkContext.agent.messages).toHaveLength(0); }); }); }); diff --git a/src/services/agentInstance/plugins/__tests__/wikiSearchPlugin.test.ts b/src/services/agentInstance/tools/__tests__/wikiSearchPlugin.test.ts similarity index 84% rename from src/services/agentInstance/plugins/__tests__/wikiSearchPlugin.test.ts rename to src/services/agentInstance/tools/__tests__/wikiSearchPlugin.test.ts index be670fec..3260bcee 100644 --- a/src/services/agentInstance/plugins/__tests__/wikiSearchPlugin.test.ts +++ b/src/services/agentInstance/tools/__tests__/wikiSearchPlugin.test.ts @@ -13,15 +13,15 @@ import type { AIResponseContext, YieldNextRoundTarget } from '../types'; import { WikiChannel } from '@/constants/channels'; import serviceIdentifier from '@services/serviceIdentifier'; -import type { AgentHandlerContext } from '@services/agentInstance/buildInAgentHandlers/type'; +import type { AgentFrameworkContext } from '@services/agentInstance/agentFrameworks/utilities/type'; import { AgentPromptDescription } from '@services/agentInstance/promptConcat/promptConcatSchema'; import type { IPrompt } from '@services/agentInstance/promptConcat/promptConcatSchema'; -import type { IPromptConcatPlugin } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import type { IPromptConcatTool } from '@services/agentInstance/promptConcat/promptConcatSchema'; import { cloneDeep } from 'lodash'; -import defaultAgents from '../../buildInAgentHandlers/defaultAgents.json'; -import { createHandlerHooks, PromptConcatHookContext } from '../index'; -import { messageManagementPlugin } from '../messageManagementPlugin'; -import { wikiSearchPlugin } from '../wikiSearchPlugin'; +import defaultAgents from '../../agentFrameworks/taskAgents.json'; +import { createAgentFrameworkHooks, PromptConcatHookContext } from '../index'; +import { messageManagementTool } from '../messageManagement'; +import { wikiSearchTool } from '../wikiSearch'; // Mock i18n vi.mock('@services/libs/i18n', () => ({ @@ -53,7 +53,7 @@ vi.mock('@services/libs/i18n', () => ({ // Use the real agent config const exampleAgent = defaultAgents[0]; -const handlerConfig = exampleAgent.handlerConfig as AgentPromptDescription['handlerConfig']; +const agentFrameworkConfig = (exampleAgent.agentFrameworkConfig || {}) as AgentPromptDescription['agentFrameworkConfig']; // Services will be retrieved from container on demand inside each test/describe @@ -77,7 +77,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: unknown): p is IPromptConcatPlugin => (p as IPromptConcatPlugin).pluginId === 'wikiSearch'); + const wikiPlugin = agentFrameworkConfig.plugins.find((p: unknown): p is IPromptConcatTool => (p as IPromptConcatTool).toolId === 'wikiSearch'); expect(wikiPlugin).toBeDefined(); if (!wikiPlugin) { // throw error to keep ts believe the plugin exists @@ -89,7 +89,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { expect(wikiPlugin.wikiSearchParam?.toolListPosition).toBeDefined(); // Create a copy of prompts to test modification - const prompts = cloneDeep(handlerConfig.prompts); + const prompts = cloneDeep(agentFrameworkConfig.prompts); const messages = [ { id: 'user-1', @@ -102,19 +102,19 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { ]; const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test', messages: [], agentDefId: 'test', status: { state: 'working' as const, modified: new Date() }, created: new Date() }, - agentDef: { id: 'test', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }, - pluginConfig: wikiPlugin, + toolConfig: wikiPlugin, prompts: prompts, messages, }; // Use real hooks from the plugin system - const promptHooks = createHandlerHooks(); - wikiSearchPlugin(promptHooks); + const promptHooks = createAgentFrameworkHooks(); + wikiSearchTool(promptHooks); // Execute the processPrompts hook await promptHooks.processPrompts.promise(context); @@ -130,7 +130,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { // Create a plugin config with trigger that won't match const wikiPlugin = { id: 'test-wiki-plugin', - pluginId: 'wikiSearch' as const, + toolId: 'wikiSearch' as const, forbidOverrides: false, retrievalAugmentedGenerationParam: { sourceType: 'wiki' as const, @@ -144,11 +144,11 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, }; - const prompts = cloneDeep(defaultAgents[0].handlerConfig.prompts); + const prompts = cloneDeep(defaultAgents[0].agentFrameworkConfig.prompts); const originalPromptsText = JSON.stringify(prompts); const context = { - pluginConfig: wikiPlugin, + toolConfig: wikiPlugin, prompts, messages: [ { @@ -162,10 +162,10 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { ], }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); // build a minimal PromptConcatHookContext to run the plugin's processPrompts - const handlerCtx: AgentHandlerContext = { + const handlerCtx: AgentFrameworkContext = { agent: { id: 'test', agentDefId: 'test', @@ -173,12 +173,12 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { status: { state: 'working' as const, modified: new Date() }, created: new Date(), } as AgentInstance, - agentDef: { id: 'test', name: 'test', handlerConfig: {} } as AgentDefinition, + agentDef: { id: 'test', name: 'test', agentFrameworkConfig: {} } as AgentDefinition, isCancelled: () => false, }; const hookContext: PromptConcatHookContext = { - handlerContext: handlerCtx, - pluginConfig: wikiPlugin as IPromptConcatPlugin, + agentFrameworkContext: handlerCtx, + toolConfig: wikiPlugin as IPromptConcatTool, prompts: prompts as IPrompt[], messages: context.messages as AgentInstanceMessage[], }; @@ -221,12 +221,12 @@ 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: unknown): p is IPromptConcatPlugin => (p as IPromptConcatPlugin).pluginId === 'wikiSearch'); + // Find the real wikiSearch plugin config from taskAgents.json + const wikiPlugin = agentFrameworkConfig.plugins.find((p: unknown): p is IPromptConcatTool => (p as IPromptConcatTool).toolId === 'wikiSearch'); expect(wikiPlugin).toBeDefined(); expect(wikiPlugin!.wikiSearchParam).toBeDefined(); - const handlerContext = { + const agentFrameworkContext = { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -258,7 +258,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, ], }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }; @@ -270,11 +270,11 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; const context = { - handlerContext, + agentFrameworkContext, response, requestId: 'test-request-123', isFinal: true, - pluginConfig: wikiPlugin!, + toolConfig: wikiPlugin!, prompts: [], messages: [], llmResponse: response.content, @@ -283,10 +283,10 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; // Use real handler hooks - const hooks = createHandlerHooks(); + const hooks = createAgentFrameworkHooks(); // Register the plugin - wikiSearchPlugin(hooks); + wikiSearchTool(hooks); // Execute the response complete hook await hooks.responseComplete.promise(context); @@ -307,15 +307,15 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { ); // Check that AI tool call message now has duration=1 (should gray out immediately) - const aiToolCallMessage = handlerContext.agent.messages[1] as AgentInstanceMessage; + const aiToolCallMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage; expect(aiToolCallMessage.id).toBe('ai-tool-call-msg'); expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true); expect(aiToolCallMessage.metadata?.toolId).toBe('wiki-search'); // Verify tool result message was added to agent history with correct settings - expect(handlerContext.agent.messages.length).toBe(3); // user + ai + tool_result - const toolResultMessage = handlerContext.agent.messages[2] as AgentInstanceMessage; + expect(agentFrameworkContext.agent.messages.length).toBe(3); // user + ai + tool_result + const toolResultMessage = agentFrameworkContext.agent.messages[2] as AgentInstanceMessage; expect(toolResultMessage.role).toBe('tool'); // Tool result message expect(toolResultMessage.content).toContain(''); expect(toolResultMessage.content).toContain('Tool: wiki-search'); @@ -325,13 +325,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { expect(toolResultMessage.duration).toBe(1); // Tool result uses configurable toolResultDuration (default 1) // Check that previous user message is unchanged - const userMessage = handlerContext.agent.messages[0] as AgentInstanceMessage; + const userMessage = agentFrameworkContext.agent.messages[0] as AgentInstanceMessage; expect(userMessage.id).toBe('user-msg-1'); expect(userMessage.duration).toBeUndefined(); // Should stay visible }); it('should handle wiki search errors gracefully and set duration=1 for both messages', async () => { - const handlerContext = { + const agentFrameworkContext = { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -352,7 +352,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, ], }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }; @@ -364,13 +364,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; const context = { - handlerContext, + agentFrameworkContext, response, requestId: 'test-request-error', isFinal: true, - pluginConfig: { + toolConfig: { id: 'test-plugin', - pluginId: 'wikiSearch' as const, + toolId: 'wikiSearch' as const, forbidOverrides: false, }, prompts: [], @@ -383,8 +383,8 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); await hooks.responseComplete.promise(context); @@ -392,14 +392,14 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { expect(context.actions.yieldNextRoundTo).toBe('self'); // Check that AI tool call message has duration=1 even after error (should gray out immediately) - const aiToolCallMessage = handlerContext.agent.messages[0] as AgentInstanceMessage; + const aiToolCallMessage = agentFrameworkContext.agent.messages[0] as AgentInstanceMessage; expect(aiToolCallMessage.id).toBe('ai-error-tool-call'); expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true); // Verify error message was added to agent history - expect(handlerContext.agent.messages.length).toBe(2); // tool_call + error_result - const errorResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage; + expect(agentFrameworkContext.agent.messages.length).toBe(2); // tool_call + error_result + const errorResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage; expect(errorResultMessage.role).toBe('tool'); // Tool error message expect(errorResultMessage.content).toContain(''); expect(errorResultMessage.content).toContain('Error:'); @@ -411,7 +411,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }); it('should not modify duration of unrelated messages', async () => { - const handlerContext = { + const agentFrameworkContext = { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -450,7 +450,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, ], }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }; @@ -461,13 +461,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; const context = { - handlerContext, + agentFrameworkContext, response, requestId: 'test-request-selective', isFinal: true, - pluginConfig: { + toolConfig: { id: 'test-plugin', - pluginId: 'wikiSearch' as const, + toolId: 'wikiSearch' as const, forbidOverrides: false, }, prompts: [], @@ -480,20 +480,20 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); await hooks.responseComplete.promise(context); // Check that unrelated messages were not modified - const unrelatedUserMsg = handlerContext.agent.messages[0] as AgentInstanceMessage; + const unrelatedUserMsg = agentFrameworkContext.agent.messages[0] as AgentInstanceMessage; expect(unrelatedUserMsg.duration).toBe(5); // Should remain unchanged - const unrelatedAiMsg = handlerContext.agent.messages[1] as AgentInstanceMessage; + const unrelatedAiMsg = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage; expect(unrelatedAiMsg.duration).toBeUndefined(); // Should remain unchanged // Check that only the tool call message was modified - const toolCallMsg = handlerContext.agent.messages[2] as AgentInstanceMessage; + const toolCallMsg = agentFrameworkContext.agent.messages[2] as AgentInstanceMessage; expect(toolCallMsg.duration).toBe(1); // Should be set to 1 expect(toolCallMsg.metadata?.containsToolCall).toBe(true); }); @@ -507,20 +507,20 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { status: { state: 'working' as const, modified: new Date() }, created: new Date(), } as AgentInstance, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} } as AgentDefinition, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} } as AgentDefinition, isCancelled: () => false, }; const context: AIResponseContext = { - handlerContext: handlerCtx, - pluginConfig: { id: 'test-plugin', pluginId: 'wikiSearch' } as IPromptConcatPlugin, + agentFrameworkContext: handlerCtx, + toolConfig: { id: 'test-plugin', toolId: 'wikiSearch' } as IPromptConcatTool, response: { requestId: 'test-request-345', content: 'Just a regular response without any tool calls', status: 'done' }, requestId: 'test-request', isFinal: true, }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); await hooks.responseComplete.promise(context); @@ -597,7 +597,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }) as unknown as IWikiService['wikiOperationInServer'], ); - const handlerContext = { + const agentFrameworkContext = { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -627,7 +627,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, ], }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }; @@ -638,13 +638,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; const context = { - handlerContext, + agentFrameworkContext, response, - requestId: 'test-request-vector', + requestId: 'test-request-vector-error', isFinal: true, - pluginConfig: { + toolConfig: { id: 'test-plugin', - pluginId: 'wikiSearch' as const, + toolId: 'wikiSearch' as const, forbidOverrides: false, }, prompts: [], @@ -654,8 +654,8 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { actions: {} as ActionBag, }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); await hooks.responseComplete.promise(context); @@ -676,9 +676,9 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { // Verify results were processed expect(context.actions.yieldNextRoundTo).toBe('self'); - expect(handlerContext.agent.messages.length).toBe(2); + expect(agentFrameworkContext.agent.messages.length).toBe(2); - const toolResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage; + const toolResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage; expect(toolResultMessage.content).toContain(''); expect(toolResultMessage.content).toContain('Vector Result 1'); expect(toolResultMessage.content).toContain('Vector Result 2'); @@ -696,7 +696,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { new Error('Vector database not initialized'), ); - const handlerContext = { + const agentFrameworkContext = { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -724,7 +724,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, ], }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }; @@ -735,13 +735,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; const context = { - handlerContext, + agentFrameworkContext, response, requestId: 'test-request-vector-error', isFinal: true, - pluginConfig: { + toolConfig: { id: 'test-plugin', - pluginId: 'wikiSearch' as const, + toolId: 'wikiSearch' as const, forbidOverrides: false, }, prompts: [], @@ -751,15 +751,15 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { actions: {} as ActionBag, }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); await hooks.responseComplete.promise(context); // Should still set up next round with error message expect(context.actions.yieldNextRoundTo).toBe('self'); - const errorResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage; + const errorResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage; expect(errorResultMessage.content).toContain('Error:'); // Error message contains i18n key or actual error expect(errorResultMessage.content).toMatch(/Vector database not initialized|Tool\.WikiSearch\.Error\.VectorSearchFailed/); @@ -767,7 +767,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }); it('should require query parameter for vector search', async () => { - const handlerContext = { + const agentFrameworkContext = { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -795,7 +795,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, ], }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }; @@ -806,13 +806,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; const context = { - handlerContext, + agentFrameworkContext, response, requestId: 'test-request-no-query', isFinal: true, - pluginConfig: { + toolConfig: { id: 'test-plugin', - pluginId: 'wikiSearch' as const, + toolId: 'wikiSearch' as const, forbidOverrides: false, }, prompts: [], @@ -822,13 +822,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { actions: {} as ActionBag, }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); await hooks.responseComplete.promise(context); // Should return error about missing query - const errorMessage = handlerContext.agent.messages[1] as AgentInstanceMessage; + const errorMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage; expect(errorMessage.content).toContain('Error:'); // Error message contains i18n key or translated text expect(errorMessage.content).toMatch(/query|Tool\.WikiSearch\.Error\.VectorSearchRequiresQuery/); @@ -836,9 +836,9 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }); describe('Message Persistence Integration', () => { - it('should work with messageManagementPlugin for complete persistence flow', async () => { - // This test ensures wikiSearchPlugin works well with messageManagementPlugin - const handlerContext = { + it('should work with messageManagementTool for complete persistence flow', async () => { + // This test ensures wikiSearchTool works well with messageManagementTool + const agentFrameworkContext = { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -859,7 +859,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, ], }, - agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, + agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} }, isCancelled: () => false, }; @@ -870,13 +870,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }; const context = { - handlerContext, + agentFrameworkContext, response, requestId: 'test-request-integration', isFinal: true, - pluginConfig: { + toolConfig: { id: 'test-plugin', - pluginId: 'wikiSearch' as const, + toolId: 'wikiSearch' as const, forbidOverrides: false, }, prompts: [], @@ -889,19 +889,19 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => { }, }; - const hooks = createHandlerHooks(); - wikiSearchPlugin(hooks); - messageManagementPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + wikiSearchTool(hooks); + messageManagementTool(hooks); await hooks.responseComplete.promise(context); // Verify integration works expect(context.actions.yieldNextRoundTo).toBe('self'); - expect(handlerContext.agent.messages.length).toBe(2); // original + tool result + expect(agentFrameworkContext.agent.messages.length).toBe(2); // original + tool result - const toolResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage; + const toolResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage; expect(toolResultMessage.metadata?.isToolResult).toBe(true); - expect(toolResultMessage.metadata?.isPersisted).toBe(true); // Should be true after messageManagementPlugin processing + expect(toolResultMessage.metadata?.isPersisted).toBe(true); // Should be true after messageManagementTool processing }); it('should prevent regression: tool result not filtered in second round', async () => { diff --git a/src/services/agentInstance/plugins/__tests__/workspacesListPlugin.test.ts b/src/services/agentInstance/tools/__tests__/workspacesListPlugin.test.ts similarity index 79% rename from src/services/agentInstance/plugins/__tests__/workspacesListPlugin.test.ts rename to src/services/agentInstance/tools/__tests__/workspacesListPlugin.test.ts index 6b2fcb15..951f597d 100644 --- a/src/services/agentInstance/plugins/__tests__/workspacesListPlugin.test.ts +++ b/src/services/agentInstance/tools/__tests__/workspacesListPlugin.test.ts @@ -1,22 +1,22 @@ /** - * Tests for workspacesListPlugin + * Tests for workspacesListTool */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // Note: global mocks from src/__tests__/setup-vitest.ts provide container and logger -import type { IPromptConcatPlugin } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import type { IPromptConcatTool } from '@services/agentInstance/promptConcat/promptConcatSchema'; import { container } from '@services/container'; import { logger } from '@services/libs/log'; import serviceIdentifier from '@services/serviceIdentifier'; -import type { AgentHandlerContext } from '../../buildInAgentHandlers/type'; +import type { AgentFrameworkContext } from '../../agentFrameworks/utilities/type'; import type { AgentInstance } from '../../interface'; import type { PromptConcatHookContext } from '../types'; import type { IWorkspaceService } from '@services/workspaces/interface'; -import { createHandlerHooks } from '../index'; -import { workspacesListPlugin } from '../workspacesListPlugin'; +import { createAgentFrameworkHooks } from '../index'; +import { workspacesListTool } from '../workspacesList'; -describe('workspacesListPlugin', () => { +describe('workspacesListTool', () => { beforeEach(async () => { vi.clearAllMocks(); }); @@ -27,11 +27,11 @@ describe('workspacesListPlugin', () => { describe('workspaces list injection', () => { it('should inject workspaces list when plugin is configured', async () => { - const hooks = createHandlerHooks(); - workspacesListPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + workspacesListTool(hooks); const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -41,7 +41,7 @@ describe('workspacesListPlugin', () => { } as AgentInstance, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, isCancelled: () => false, - } as AgentHandlerContext, + } as AgentFrameworkContext, messages: [], prompts: [ { @@ -50,16 +50,16 @@ describe('workspacesListPlugin', () => { children: [], }, ], - pluginConfig: { + toolConfig: { id: 'test-plugin', caption: 'Test Plugin', forbidOverrides: false, - pluginId: 'workspacesList', + toolId: 'workspacesList', workspacesListParam: { targetId: 'target-prompt', position: 'after' as const, }, - } as unknown as IPromptConcatPlugin, + } as unknown as IPromptConcatTool, }; await hooks.processPrompts.promise(context); @@ -74,11 +74,11 @@ describe('workspacesListPlugin', () => { }); it('should inject workspaces list when position is before', async () => { - const hooks = createHandlerHooks(); - workspacesListPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + workspacesListTool(hooks); const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -88,7 +88,7 @@ describe('workspacesListPlugin', () => { } as AgentInstance, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, isCancelled: () => false, - } as AgentHandlerContext, + } as AgentFrameworkContext, messages: [], prompts: [ { @@ -97,16 +97,16 @@ describe('workspacesListPlugin', () => { children: [], }, ], - pluginConfig: { + toolConfig: { id: 'test-plugin', caption: 'Test Plugin', forbidOverrides: false, - pluginId: 'workspacesList', + toolId: 'workspacesList', workspacesListParam: { targetId: 'target-prompt', position: 'before' as const, }, - } as unknown as IPromptConcatPlugin, + } as unknown as IPromptConcatTool, }; await hooks.processPrompts.promise(context); @@ -118,11 +118,11 @@ describe('workspacesListPlugin', () => { }); it('should not inject content when plugin is not configured', async () => { - const hooks = createHandlerHooks(); - workspacesListPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + workspacesListTool(hooks); const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -132,7 +132,7 @@ describe('workspacesListPlugin', () => { } as AgentInstance, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, isCancelled: () => false, - } as AgentHandlerContext, + } as AgentFrameworkContext, messages: [], prompts: [ { @@ -141,7 +141,7 @@ describe('workspacesListPlugin', () => { children: [], }, ], - pluginConfig: { id: 'test-plugin', pluginId: 'otherPlugin', forbidOverrides: false } as unknown as IPromptConcatPlugin, + toolConfig: { id: 'test-plugin', toolId: 'otherPlugin', forbidOverrides: false } as unknown as IPromptConcatTool, }; await hooks.processPrompts.promise(context); @@ -155,11 +155,11 @@ describe('workspacesListPlugin', () => { const workspaceService = container.get>(serviceIdentifier.Workspace); workspaceService.getWorkspacesAsList = vi.fn().mockResolvedValue([]) as unknown as IWorkspaceService['getWorkspacesAsList']; - const hooks = createHandlerHooks(); - workspacesListPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + workspacesListTool(hooks); const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -169,7 +169,7 @@ describe('workspacesListPlugin', () => { } as AgentInstance, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, isCancelled: () => false, - } as AgentHandlerContext, + } as AgentFrameworkContext, messages: [], prompts: [ { @@ -178,16 +178,16 @@ describe('workspacesListPlugin', () => { children: [], }, ], - pluginConfig: { + toolConfig: { id: 'test-plugin', caption: 'Test Plugin', forbidOverrides: false, - pluginId: 'workspacesList', + toolId: 'workspacesList', workspacesListParam: { targetId: 'target-prompt', position: 'after' as const, }, - } as unknown as IPromptConcatPlugin, + } as unknown as IPromptConcatTool, }; await hooks.processPrompts.promise(context); @@ -195,16 +195,16 @@ describe('workspacesListPlugin', () => { const targetPrompt = context.prompts[0]; expect(targetPrompt.children).toHaveLength(0); expect(logger.debug).toHaveBeenCalledWith('No wiki workspaces found to inject', { - pluginId: 'test-plugin', + toolId: 'test-plugin', }); }); it('should warn when target prompt is not found', async () => { - const hooks = createHandlerHooks(); - workspacesListPlugin(hooks); + const hooks = createAgentFrameworkHooks(); + workspacesListTool(hooks); const context: PromptConcatHookContext = { - handlerContext: { + agentFrameworkContext: { agent: { id: 'test-agent', agentDefId: 'test-agent-def', @@ -214,7 +214,7 @@ describe('workspacesListPlugin', () => { } as AgentInstance, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, isCancelled: () => false, - } as AgentHandlerContext, + } as AgentFrameworkContext, messages: [], prompts: [ { @@ -223,23 +223,23 @@ describe('workspacesListPlugin', () => { children: [], }, ], - pluginConfig: { + toolConfig: { id: 'test-plugin', caption: 'Test Plugin', forbidOverrides: false, - pluginId: 'workspacesList', + toolId: 'workspacesList', workspacesListParam: { targetId: 'non-existent-prompt', position: 'after' as const, }, - } as unknown as IPromptConcatPlugin, + } as unknown as IPromptConcatTool, }; await hooks.processPrompts.promise(context); expect(logger.warn).toHaveBeenCalledWith('Workspaces list target prompt not found', { targetId: 'non-existent-prompt', - pluginId: 'test-plugin', + toolId: 'test-plugin', }); }); }); diff --git a/src/services/agentInstance/tools/index.ts b/src/services/agentInstance/tools/index.ts new file mode 100644 index 00000000..6c4abbd6 --- /dev/null +++ b/src/services/agentInstance/tools/index.ts @@ -0,0 +1,190 @@ +import { logger } from '@services/libs/log'; +import { AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable'; +import { registerToolParameterSchema } from './schemaRegistry'; +import { AgentResponse, PromptConcatHookContext, PromptConcatHooks, PromptConcatTool, ResponseHookContext } from './types'; + +// Re-export types for convenience +export type { AgentResponse, PromptConcatHookContext, PromptConcatHooks, PromptConcatTool, ResponseHookContext }; +// Backward compatibility aliases +export type { PromptConcatTool as PromptConcatPlugin }; + +/** + * Registry for built-in framework tools + */ +export const builtInTools = new Map(); + +/** + * Create unified hooks instance for the complete agent framework tool system + */ +export function createAgentFrameworkHooks(): PromptConcatHooks { + return { + // Prompt processing hooks + processPrompts: new AsyncSeriesWaterfallHook(['context']), + finalizePrompts: new AsyncSeriesWaterfallHook(['context']), + postProcess: new AsyncSeriesWaterfallHook(['context']), + // Agent lifecycle hooks + userMessageReceived: new AsyncSeriesHook(['context']), + agentStatusChanged: new AsyncSeriesHook(['context']), + toolExecuted: new AsyncSeriesHook(['context']), + responseUpdate: new AsyncSeriesHook(['context']), + responseComplete: new AsyncSeriesHook(['context']), + }; +} + +/** + * Get all available tools + */ +async function getAllTools() { + const [ + promptToolsModule, + wikiSearchModule, + wikiOperationModule, + workspacesListModule, + messageManagementModule, + ] = await Promise.all([ + import('./prompt'), + import('./wikiSearch'), + import('./wikiOperation'), + import('./workspacesList'), + import('./messageManagement'), + ]); + + return { + messageManagement: messageManagementModule.messageManagementTool, + fullReplacement: promptToolsModule.fullReplacementTool, + wikiSearch: wikiSearchModule.wikiSearchTool, + wikiOperation: wikiOperationModule.wikiOperationTool, + workspacesList: workspacesListModule.workspacesListTool, + }; +} + +/** + * Register tools to hooks based on framework configuration + * @param hooks - The hooks instance to register tools to + * @param agentFrameworkConfig - The framework configuration containing tool settings + */ +export async function registerToolsToHooksFromConfig( + hooks: PromptConcatHooks, + agentFrameworkConfig: { plugins?: Array<{ toolId: string; [key: string]: unknown }> }, +): Promise { + // Always register core tools that are needed for basic functionality + const messageManagementModule = await import('./messageManagement'); + messageManagementModule.messageManagementTool(hooks); + logger.debug('Registered messageManagementTool to hooks'); + + // Register tools based on framework configuration + if (agentFrameworkConfig.plugins) { + for (const toolConfig of agentFrameworkConfig.plugins) { + const { toolId } = toolConfig; + + // Get tool from global registry (supports both built-in and dynamic tools) + const tool = builtInTools.get(toolId); + if (tool) { + tool(hooks); + logger.debug(`Registered tool ${toolId} to hooks`); + } else { + logger.warn(`Tool not found in registry: ${toolId}`); + } + } + } +} + +/** + * Initialize tool system - register all built-in tools to global registry + * This should be called once during service initialization + */ +export async function initializeToolSystem(): Promise { + // Import tool schemas and register them + const [ + promptToolsModule, + wikiSearchModule, + wikiOperationModule, + workspacesListModule, + modelContextProtocolModule, + ] = await Promise.all([ + import('./prompt'), + import('./wikiSearch'), + import('./wikiOperation'), + import('./workspacesList'), + import('./modelContextProtocol'), + ]); + + // Register tool parameter schemas + registerToolParameterSchema( + 'fullReplacement', + promptToolsModule.getFullReplacementParameterSchema(), + { + displayName: 'Full Replacement', + description: 'Replace target content with content from specified source', + }, + ); + + registerToolParameterSchema( + 'dynamicPosition', + promptToolsModule.getDynamicPositionParameterSchema(), + { + displayName: 'Dynamic Position', + description: 'Insert content at a specific position relative to a target element', + }, + ); + + registerToolParameterSchema( + 'wikiSearch', + wikiSearchModule.getWikiSearchParameterSchema(), + { + displayName: 'Wiki Search', + description: 'Search content in wiki workspaces and manage vector embeddings', + }, + ); + + registerToolParameterSchema( + 'wikiOperation', + wikiOperationModule.getWikiOperationParameterSchema(), + { + displayName: 'Wiki Operation', + description: 'Perform operations on wiki workspaces (create, update, delete tiddlers)', + }, + ); + + registerToolParameterSchema( + 'workspacesList', + workspacesListModule.getWorkspacesListParameterSchema(), + { + displayName: 'Workspaces List', + description: 'Inject available wiki workspaces list into prompts', + }, + ); + + registerToolParameterSchema( + 'modelContextProtocol', + modelContextProtocolModule.getModelContextProtocolParameterSchema(), + { + displayName: 'Model Context Protocol', + description: 'MCP (Model Context Protocol) integration', + }, + ); + + const tools = await getAllTools(); + // Register all built-in tools to global registry for discovery + builtInTools.set('messageManagement', tools.messageManagement); + builtInTools.set('fullReplacement', tools.fullReplacement); + builtInTools.set('wikiSearch', tools.wikiSearch); + builtInTools.set('wikiOperation', tools.wikiOperation); + builtInTools.set('workspacesList', tools.workspacesList); + logger.debug('All built-in tools and schemas registered successfully'); +} + +/** + * Create hooks and register tools based on framework configuration + * This creates a new hooks instance and registers tools for that specific context + */ +export async function createHooksWithTools( + agentFrameworkConfig: { plugins?: Array<{ toolId: string; [key: string]: unknown }> }, +): Promise<{ hooks: PromptConcatHooks; toolConfigs: Array<{ toolId: string; [key: string]: unknown }> }> { + const hooks = createAgentFrameworkHooks(); + await registerToolsToHooksFromConfig(hooks, agentFrameworkConfig); + return { + hooks, + toolConfigs: agentFrameworkConfig.plugins || [], + }; +} diff --git a/src/services/agentInstance/plugins/messageManagementPlugin.ts b/src/services/agentInstance/tools/messageManagement.ts similarity index 80% rename from src/services/agentInstance/plugins/messageManagementPlugin.ts rename to src/services/agentInstance/tools/messageManagement.ts index 496e667b..305887db 100644 --- a/src/services/agentInstance/plugins/messageManagementPlugin.ts +++ b/src/services/agentInstance/tools/messageManagement.ts @@ -8,20 +8,20 @@ import { logger } from '@services/libs/log'; import serviceIdentifier from '@services/serviceIdentifier'; import type { IAgentInstanceService } from '../interface'; import { createAgentMessage } from '../utilities'; -import type { AgentStatusContext, AIResponseContext, PromptConcatPlugin, ToolExecutionContext, UserMessageContext } from './types'; +import type { AgentStatusContext, AIResponseContext, PromptConcatTool, ToolExecutionContext, UserMessageContext } from './types'; /** * Message management plugin * Handles all message-related operations: persistence, streaming, UI updates, and duration-based filtering */ -export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { +export const messageManagementTool: PromptConcatTool = (hooks) => { // Handle user message persistence - hooks.userMessageReceived.tapAsync('messageManagementPlugin', async (context: UserMessageContext, callback) => { + hooks.userMessageReceived.tapAsync('messageManagementTool', async (context: UserMessageContext, callback) => { try { - const { handlerContext, content, messageId } = context; + const { agentFrameworkContext, content, messageId } = context; // Create user message using the helper function - const userMessage = createAgentMessage(messageId, handlerContext.agent.id, { + const userMessage = createAgentMessage(messageId, agentFrameworkContext.agent.id, { role: 'user', content: content.text, contentType: 'text/plain', @@ -30,7 +30,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { }); // Add message to the agent's message array for immediate use (do this before persistence so plugins see it) - handlerContext.agent.messages.push(userMessage); + agentFrameworkContext.agent.messages.push(userMessage); // Get the agent instance service to access repositories const agentInstanceService = container.get(serviceIdentifier.AgentInstance); @@ -40,7 +40,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { logger.debug('User message persisted to database', { messageId, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, contentLength: content.text.length, }); @@ -49,30 +49,30 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { logger.error('Message management plugin error in userMessageReceived', { error, messageId: context.messageId, - agentId: context.handlerContext.agent.id, + agentId: context.agentFrameworkContext.agent.id, }); callback(); } }); // Handle agent status persistence - hooks.agentStatusChanged.tapAsync('messageManagementPlugin', async (context: AgentStatusContext, callback) => { + hooks.agentStatusChanged.tapAsync('messageManagementTool', async (context: AgentStatusContext, callback) => { try { - const { handlerContext, status } = context; + const { agentFrameworkContext, status } = context; // Get the agent instance service to update status const agentInstanceService = container.get(serviceIdentifier.AgentInstance); // Update agent status in database - await agentInstanceService.updateAgent(handlerContext.agent.id, { + await agentInstanceService.updateAgent(agentFrameworkContext.agent.id, { status, }); // Update the agent object for immediate use - handlerContext.agent.status = status; + agentFrameworkContext.agent.status = status; logger.debug('Agent status updated in database', { - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, state: status.state, }); @@ -80,7 +80,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { } catch (error) { logger.error('Message management plugin error in agentStatusChanged', { error, - agentId: context.handlerContext.agent.id, + agentId: context.agentFrameworkContext.agent.id, status: context.status, }); callback(); @@ -88,13 +88,13 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { }); // Handle AI response updates during streaming - hooks.responseUpdate.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => { + hooks.responseUpdate.tapAsync('messageManagementTool', async (context: AIResponseContext, callback) => { try { - const { handlerContext, response } = context; + const { agentFrameworkContext, response } = context; if (response.status === 'update' && response.content) { // Find or create AI response message in agent's message array - let aiMessage = handlerContext.agent.messages.find( + let aiMessage = agentFrameworkContext.agent.messages.find( (message) => message.role === 'assistant' && !message.metadata?.isComplete, ); @@ -103,7 +103,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { const now = new Date(); aiMessage = { id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'assistant', content: response.content, created: now, @@ -111,7 +111,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { metadata: { isComplete: false }, duration: undefined, // AI responses persist indefinitely by default }; - handlerContext.agent.messages.push(aiMessage); + agentFrameworkContext.agent.messages.push(aiMessage); // Persist immediately so DB timestamp reflects conversation order try { const agentInstanceService = container.get(serviceIdentifier.AgentInstance); @@ -132,7 +132,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { // Update UI using the agent instance service try { const agentInstanceService = container.get(serviceIdentifier.AgentInstance); - agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id); + agentInstanceService.debounceUpdateMessage(aiMessage, agentFrameworkContext.agent.id); } catch (serviceError) { logger.warn('Failed to update UI for streaming message', { error: serviceError, @@ -150,13 +150,13 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { }); // Handle AI response completion - hooks.responseComplete.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => { + hooks.responseComplete.tapAsync('messageManagementTool', async (context: AIResponseContext, callback) => { try { - const { handlerContext, response } = context; + const { agentFrameworkContext, response } = context; if (response.status === 'done' && response.content) { // Find and finalize AI response message - let aiMessage = handlerContext.agent.messages.find( + let aiMessage = agentFrameworkContext.agent.messages.find( (message) => message.role === 'assistant' && !message.metadata?.isComplete && !message.metadata?.isToolResult, ); @@ -170,7 +170,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { const nowFinal = new Date(); aiMessage = { id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'assistant', content: response.content, created: nowFinal, @@ -180,7 +180,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { }, duration: undefined, // Default duration for AI responses }; - handlerContext.agent.messages.push(aiMessage); + agentFrameworkContext.agent.messages.push(aiMessage); } // Get the agent instance service for persistence and UI updates @@ -191,7 +191,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { // Final UI update try { - agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id); + agentInstanceService.debounceUpdateMessage(aiMessage, agentFrameworkContext.agent.id); } catch (serviceError) { logger.warn('Failed to update UI for completed message', { error: serviceError, @@ -215,12 +215,12 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { }); // Handle tool result messages persistence and UI updates - hooks.toolExecuted.tapAsync('messageManagementPlugin', async (context: ToolExecutionContext, callback) => { + hooks.toolExecuted.tapAsync('messageManagementTool', async (context: ToolExecutionContext, callback) => { try { - const { handlerContext } = context; + const { agentFrameworkContext } = context; // Find newly added tool result messages that need to be persisted - const newToolResultMessages = handlerContext.agent.messages.filter( + const newToolResultMessages = agentFrameworkContext.agent.messages.filter( (message) => message.metadata?.isToolResult && !message.metadata.isPersisted, ); @@ -233,7 +233,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => { await agentInstanceService.saveUserMessage(message); // Update UI - agentInstanceService.debounceUpdateMessage(message, handlerContext.agent.id); + agentInstanceService.debounceUpdateMessage(message, agentFrameworkContext.agent.id); // Mark as persisted to avoid duplicate saves message.metadata = { ...message.metadata, isPersisted: true, uiUpdated: true }; diff --git a/src/services/agentInstance/plugins/modelContextProtocolPlugin.ts b/src/services/agentInstance/tools/modelContextProtocol.ts similarity index 100% rename from src/services/agentInstance/plugins/modelContextProtocolPlugin.ts rename to src/services/agentInstance/tools/modelContextProtocol.ts diff --git a/src/services/agentInstance/plugins/promptPlugins.ts b/src/services/agentInstance/tools/prompt.ts similarity index 83% rename from src/services/agentInstance/plugins/promptPlugins.ts rename to src/services/agentInstance/tools/prompt.ts index 9acd86c5..0ec5ecba 100644 --- a/src/services/agentInstance/plugins/promptPlugins.ts +++ b/src/services/agentInstance/tools/prompt.ts @@ -10,7 +10,7 @@ import { findPromptById } from '../promptConcat/promptConcat'; import type { IPrompt } from '../promptConcat/promptConcatSchema'; import { filterMessagesByDuration } from '../utilities/messageDurationFilter'; import { normalizeRole } from '../utilities/normalizeRole'; -import { AgentResponse, PromptConcatPlugin, ResponseHookContext } from './types'; +import { AgentResponse, PromptConcatTool, ResponseHookContext } from './types'; const t = identity; @@ -76,17 +76,17 @@ export function getDynamicPositionParameterSchema() { * Full replacement plugin * Replaces target content with content from specified source */ -export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { +export const fullReplacementTool: PromptConcatTool = (hooks) => { // Normalize an AgentInstanceMessage role to Prompt role - hooks.processPrompts.tapAsync('fullReplacementPlugin', async (context, callback) => { - const { pluginConfig, prompts, messages } = context; + hooks.processPrompts.tapAsync('fullReplacementTool', async (context, callback) => { + const { toolConfig, prompts, messages } = context; - if (pluginConfig.pluginId !== 'fullReplacement' || !pluginConfig.fullReplacementParam) { + if (toolConfig.toolId !== 'fullReplacement' || !toolConfig.fullReplacementParam) { callback(); return; } - const fullReplacementConfig = pluginConfig.fullReplacementParam; + const fullReplacementConfig = toolConfig.fullReplacementParam; if (!fullReplacementConfig) { callback(); return; @@ -98,7 +98,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { if (!found) { logger.warn('Target prompt not found for fullReplacement', { targetId, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); return; @@ -173,16 +173,16 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { }); // Handle response phase for llmResponse source type - hooks.postProcess.tapAsync('fullReplacementPlugin', async (context, callback) => { + hooks.postProcess.tapAsync('fullReplacementTool', async (context, callback) => { const responseContext = context as ResponseHookContext; - const { pluginConfig, llmResponse, responses } = responseContext; + const { toolConfig, llmResponse, responses } = responseContext; - if (pluginConfig.pluginId !== 'fullReplacement' || !pluginConfig.fullReplacementParam) { + if (toolConfig.toolId !== 'fullReplacement' || !toolConfig.fullReplacementParam) { callback(); return; } - const fullReplacementParameter = pluginConfig.fullReplacementParam; + const fullReplacementParameter = toolConfig.fullReplacementParam; if (!fullReplacementParameter) { callback(); return; @@ -202,7 +202,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { if (!found) { logger.warn('Full replacement target not found in responses', { targetId, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); return; @@ -212,7 +212,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Replacing target with LLM response', { targetId, responseLength: llmResponse.length, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); found.text = llmResponse; @@ -220,6 +220,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Full replacement completed in response phase', { targetId, sourceType, + toolId: toolConfig.id, }); callback(); @@ -230,16 +231,16 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => { * Dynamic position plugin * Inserts content at a specific position relative to a target element */ -export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => { - hooks.processPrompts.tapAsync('dynamicPositionPlugin', async (context, callback) => { - const { pluginConfig, prompts } = context; +export const dynamicPositionTool: PromptConcatTool = (hooks) => { + hooks.processPrompts.tapAsync('dynamicPositionTool', async (context, callback) => { + const { toolConfig, prompts } = context; - if (pluginConfig.pluginId !== 'dynamicPosition' || !pluginConfig.dynamicPositionParam || !pluginConfig.content) { + if (toolConfig.toolId !== 'dynamicPosition' || !toolConfig.dynamicPositionParam || !toolConfig.content) { callback(); return; } - const dynamicPositionConfig = pluginConfig.dynamicPositionParam; + const dynamicPositionConfig = toolConfig.dynamicPositionParam; if (!dynamicPositionConfig) { callback(); return; @@ -251,7 +252,7 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => { if (!found) { logger.warn('Target prompt not found for dynamicPosition', { targetId, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); return; @@ -259,9 +260,9 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => { // Create new prompt part const newPart: IPrompt = { - id: `dynamic-${pluginConfig.id}-${Date.now()}`, - caption: pluginConfig.caption || 'Dynamic Content', - text: pluginConfig.content, + id: `dynamic-${toolConfig.id}-${Date.now()}`, + caption: toolConfig.caption || 'Dynamic Content', + text: toolConfig.content, }; // Insert based on position @@ -288,7 +289,8 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Dynamic position insertion completed', { targetId, position, - contentLength: pluginConfig.content.length, + contentLength: toolConfig.content.length, + toolId: toolConfig.id, }); callback(); diff --git a/src/services/agentInstance/tools/schemaRegistry.ts b/src/services/agentInstance/tools/schemaRegistry.ts new file mode 100644 index 00000000..16b7bc4f --- /dev/null +++ b/src/services/agentInstance/tools/schemaRegistry.ts @@ -0,0 +1,165 @@ +/** + * Tool Schema Registry + * + * This system allows tools to register their parameter schemas dynamically, + * enabling dynamic tool loading while maintaining type safety and validation. + */ +import { identity } from 'lodash'; +import { z } from 'zod/v4'; + +const t = identity; + +/** + * Registry for tool parameter schemas + */ +const toolSchemas = new Map(); + +/** + * Registry for tool metadata + */ +const toolMetadata = new Map(); + +/** + * Register a tool parameter schema + * @param toolId The tool ID (should match toolId enum values) + * @param schema The Zod schema for this tool's parameters + * @param metadata Optional metadata for display purposes + */ +export function registerToolParameterSchema( + toolId: string, + schema: z.ZodType, + metadata?: { + displayName: string; + description: string; + }, +): void { + toolSchemas.set(toolId, schema); + if (metadata) { + toolMetadata.set(toolId, metadata); + } +} + +/** + * Get a tool parameter schema by ID + * @param toolId The tool ID + * @returns The schema or undefined if not found + */ +export function getToolParameterSchema(toolId: string): z.ZodType | undefined { + return toolSchemas.get(toolId); +} + +/** + * Get all registered tool IDs + * @returns Array of all registered tool IDs + */ +export function getAllRegisteredToolIds(): string[] { + return Array.from(toolSchemas.keys()); +} + +/** + * Get tool metadata + * @param toolId The tool ID + * @returns Tool metadata or undefined if not found + */ +export function getToolMetadata(toolId: string): { displayName: string; description: string } | undefined { + return toolMetadata.get(toolId); +} + +/** + * Dynamically create the PromptConcatToolSchema based on registered tools + * This is called whenever the schema is needed, ensuring it includes all registered tools + */ +export function createDynamicPromptConcatToolSchema(): z.ZodType { + // Base tool configuration without parameter-specific fields + const baseToolSchema = z.object({ + id: z.string().meta({ + title: t('Schema.Tool.IdTitle'), + description: t('Schema.Tool.Id'), + }), + caption: z.string().optional().meta({ + title: t('Schema.Tool.CaptionTitle'), + description: t('Schema.Tool.Caption'), + }), + content: z.string().optional().meta({ + title: t('Schema.Tool.ContentTitle'), + description: t('Schema.Tool.Content'), + }), + forbidOverrides: z.boolean().optional().default(false).meta({ + title: t('Schema.Tool.ForbidOverridesTitle'), + description: t('Schema.Tool.ForbidOverrides'), + }), + }); + + // Get all registered tool IDs + const registeredToolIds = getAllRegisteredToolIds(); + + if (registeredToolIds.length === 0) { + // Fallback to a basic schema if no tools are registered yet + return baseToolSchema.extend({ + toolId: z.string().meta({ + title: t('Schema.Tool.ToolIdTitle'), + description: t('Schema.Tool.ToolId'), + }), + }); + } + + // Create enum from registered tool IDs + const toolIdEnum = z.enum(registeredToolIds as [string, ...string[]]).meta({ + title: t('Schema.Tool.ToolIdTitle'), + description: t('Schema.Tool.ToolId'), + enumOptions: registeredToolIds.map(toolId => { + const metadata = getToolMetadata(toolId); + return { + value: toolId, + label: metadata?.displayName || toolId, + }; + }), + }); + + // Create parameter schema object with all registered tools + const parameterSchema: Record = {}; + + for (const toolId of registeredToolIds) { + const schema = getToolParameterSchema(toolId); + if (schema) { + const metadata = getToolMetadata(toolId); + parameterSchema[`${toolId}Param`] = schema.optional().meta({ + title: metadata?.displayName || toolId, + description: metadata?.description || `Parameters for ${toolId} tool`, + }); + } + } + + // Combine base schema with tool ID and parameters + return baseToolSchema.extend({ + toolId: toolIdEnum, + ...parameterSchema, + }); +} + +/** + * Get the type of a tool's parameters + * @param toolId The tool ID + * @returns The inferred TypeScript type of the tool's parameters + */ +export type ToolParameterType = T extends keyof ReturnType ? ReturnType[T] : never; + +/** + * Create type definitions for all registered tool parameters + * This is used internally for type inference + */ +export function createToolParameterTypes() { + const types: Record = {}; + + for (const toolId of getAllRegisteredToolIds()) { + const schema = getToolParameterSchema(toolId); + if (schema) { + types[toolId] = schema; + } + } + + return types as Record; +} diff --git a/src/services/agentInstance/plugins/types.ts b/src/services/agentInstance/tools/types.ts similarity index 73% rename from src/services/agentInstance/plugins/types.ts rename to src/services/agentInstance/tools/types.ts index 1a401f7b..e34e0cdb 100644 --- a/src/services/agentInstance/plugins/types.ts +++ b/src/services/agentInstance/tools/types.ts @@ -1,9 +1,9 @@ import { ToolCallingMatch } from '@services/agentDefinition/interface'; -import { AgentHandlerContext } from '@services/agentInstance/buildInAgentHandlers/type'; +import { AgentFrameworkContext } from '@services/agentInstance/agentFrameworks/utilities/type'; import { AgentInstanceMessage } from '@services/agentInstance/interface'; import { AIStreamResponse } from '@services/externalAPI/interface'; import { AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable'; -import type { IPrompt, IPromptConcatPlugin } from '../promptConcat/promptConcatSchema/'; +import type { IPrompt, IPromptConcatTool } from '../promptConcat/promptConcatSchema'; /** * Next round target options @@ -11,9 +11,9 @@ import type { IPrompt, IPromptConcatPlugin } from '../promptConcat/promptConcatS export type YieldNextRoundTarget = 'human' | 'self' | `agent:${string}`; // allows for future agent IDs like "agent:agent-id" /** - * Unified actions interface for all plugin hooks + * Unified actions interface for all tool hooks */ -export interface PluginActions { +export interface ToolActions { /** Whether to yield next round to continue processing */ yieldNextRoundTo?: YieldNextRoundTarget; /** New user message to append */ @@ -23,27 +23,27 @@ export interface PluginActions { } /** - * Base context interface for all plugin hooks + * Base context interface for all tool hooks */ -export interface BasePluginContext { - /** Handler context */ - handlerContext: AgentHandlerContext; +export interface BaseToolContext { + /** Framework context */ + agentFrameworkContext: AgentFrameworkContext; /** Additional context data */ metadata?: Record; - /** Actions set by plugins during processing */ - actions?: PluginActions; + /** Actions set by tools during processing */ + actions?: ToolActions; } /** * Context for prompt processing hooks (processPrompts, finalizePrompts) */ -export interface PromptConcatHookContext extends BasePluginContext { +export interface PromptConcatHookContext extends BaseToolContext { /** Array of agent instance messages for context */ messages: AgentInstanceMessage[]; /** Current prompt tree */ prompts: IPrompt[]; - /** Plugin configuration */ - pluginConfig: IPromptConcatPlugin; + /** Tool configuration */ + toolConfig: IPromptConcatTool; } /** @@ -59,11 +59,11 @@ export interface PostProcessContext extends PromptConcatHookContext { /** * Context for AI response hooks (responseUpdate, responseComplete) */ -export interface AIResponseContext extends BasePluginContext { - /** Plugin configuration - for backward compatibility */ - pluginConfig: IPromptConcatPlugin; - /** Complete handler configuration - allows plugins to access all configs */ - handlerConfig?: { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }; +export interface AIResponseContext extends BaseToolContext { + /** Tool configuration - for backward compatibility */ + toolConfig: IPromptConcatTool; + /** Complete framework configuration - allows tools to access all configs */ + agentFrameworkConfig?: { plugins?: Array<{ toolId: string; [key: string]: unknown }> }; /** AI streaming response */ response: AIStreamResponse; /** Current request ID */ @@ -75,7 +75,7 @@ export interface AIResponseContext extends BasePluginContext { /** * Context for user message hooks */ -export interface UserMessageContext extends BasePluginContext { +export interface UserMessageContext extends BaseToolContext { /** User message content */ content: { text: string; file?: File }; /** Generated message ID */ @@ -87,7 +87,7 @@ export interface UserMessageContext extends BasePluginContext { /** * Context for agent status hooks */ -export interface AgentStatusContext extends BasePluginContext { +export interface AgentStatusContext extends BaseToolContext { /** New status state */ status: { state: 'working' | 'completed' | 'failed' | 'canceled'; @@ -98,7 +98,7 @@ export interface AgentStatusContext extends BasePluginContext { /** * Context for tool execution hooks */ -export interface ToolExecutionContext extends BasePluginContext { +export interface ToolExecutionContext extends BaseToolContext { /** Tool execution result */ toolResult: { success: boolean; @@ -136,7 +136,7 @@ export interface ResponseHookContext extends PromptConcatHookContext { } /** - * Handler hooks for unified plugin system + * Framework hooks for unified tool system * Handles both prompt processing and agent lifecycle events */ export interface PromptConcatHooks { @@ -159,6 +159,6 @@ export interface PromptConcatHooks { } /** - * Universal plugin function interface - can register handlers for any hooks + * Universal tool function interface - can register handlers for any hooks */ -export type PromptConcatPlugin = (hooks: PromptConcatHooks) => void; +export type PromptConcatTool = (hooks: PromptConcatHooks) => void; diff --git a/src/services/agentInstance/plugins/wikiOperationPlugin.ts b/src/services/agentInstance/tools/wikiOperation.ts similarity index 88% rename from src/services/agentInstance/plugins/wikiOperationPlugin.ts rename to src/services/agentInstance/tools/wikiOperation.ts index f33dbe72..21602c3d 100644 --- a/src/services/agentInstance/plugins/wikiOperationPlugin.ts +++ b/src/services/agentInstance/tools/wikiOperation.ts @@ -16,7 +16,7 @@ import { z } from 'zod/v4'; import type { AgentInstanceMessage, IAgentInstanceService } from '../interface'; import { findPromptById } from '../promptConcat/promptConcat'; import { schemaToToolContent } from '../utilities/schemaToToolContent'; -import type { PromptConcatPlugin } from './types'; +import type { PromptConcatTool } from './types'; /** * Wiki Operation Parameter Schema @@ -101,17 +101,17 @@ const WikiOperationToolParameterSchema = z.object({ * Wiki Operation plugin - Prompt processing * Handles tool list injection for wiki operation functionality */ -export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { +export const wikiOperationTool: PromptConcatTool = (hooks) => { // First tapAsync: Tool list injection - hooks.processPrompts.tapAsync('wikiOperationPlugin-toolList', async (context, callback) => { - const { pluginConfig, prompts } = context; + hooks.processPrompts.tapAsync('wikiOperationTool-toolList', async (context, callback) => { + const { toolConfig, prompts } = context; - if (pluginConfig.pluginId !== 'wikiOperation' || !pluginConfig.wikiOperationParam) { + if (toolConfig.toolId !== 'wikiOperation' || !toolConfig.wikiOperationParam) { callback(); return; } - const wikiOperationParameter = pluginConfig.wikiOperationParam; + const wikiOperationParameter = toolConfig.wikiOperationParam; try { // Handle tool list injection if toolListPosition is configured @@ -121,7 +121,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { if (!toolListTarget) { logger.warn('Tool list target prompt not found', { targetId: toolListPosition.targetId, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); return; @@ -140,7 +140,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { } const insertIndex = toolListTarget.prompt.children.length; toolListTarget.prompt.children.splice(insertIndex, 0, { - id: `wiki-operation-tool-${pluginConfig.id}`, + id: `wiki-operation-tool-${toolConfig.id}`, caption: 'Wiki Operation Tool', text: wikiOperationToolContent, }); @@ -149,7 +149,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { toolListTarget.prompt.children = []; } toolListTarget.prompt.children.unshift({ - id: `wiki-operation-tool-${pluginConfig.id}`, + id: `wiki-operation-tool-${toolConfig.id}`, caption: 'Wiki Operation Tool', text: wikiOperationToolContent, }); @@ -161,7 +161,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Wiki operation tool list injected', { targetId: toolListPosition.targetId, position: toolListPosition.position, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); } @@ -169,20 +169,20 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { } catch (error) { logger.error('Error in wiki operation tool list injection', { error, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); } }); // 2. Tool execution when AI response is complete - hooks.responseComplete.tapAsync('wikiOperationPlugin-handler', async (context, callback) => { + hooks.responseComplete.tapAsync('wikiOperationTool-handler', async (context, callback) => { try { - const { handlerContext, response, handlerConfig } = context; + const { agentFrameworkContext, response, agentFrameworkConfig } = context; - // Find this plugin's configuration from handlerConfig - const wikiOperationPluginConfig = handlerConfig?.plugins?.find(p => p.pluginId === 'wikiOperation'); - const wikiOperationParameter = wikiOperationPluginConfig?.wikiOperationParam as { toolResultDuration?: number } | undefined; + // Find this plugin's configuration import { AgentFrameworkConfig } + const wikiOperationToolConfig = agentFrameworkConfig?.plugins?.find((p: { toolId: string; [key: string]: unknown }) => p.toolId === 'wikiOperation'); + const wikiOperationParameter = wikiOperationToolConfig?.wikiOperationParam as { toolResultDuration?: number } | undefined; const toolResultDuration = wikiOperationParameter?.toolResultDuration || 1; // Default to 1 round if (response.status !== 'done' || !response.content) { @@ -200,19 +200,19 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Wiki operation tool call detected', { toolId: toolMatch.toolId, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); // Set duration=1 for the AI message containing the tool call // Find the most recent AI message (should be the one containing the tool call) - const aiMessages = handlerContext.agent.messages.filter(message => message.role === 'assistant'); + const aiMessages = agentFrameworkContext.agent.messages.filter((message: AgentInstanceMessage) => message.role === 'assistant'); if (aiMessages.length > 0) { const latestAiMessage = aiMessages[aiMessages.length - 1]; latestAiMessage.duration = toolResultDuration; logger.debug('Set AI message duration for tool call', { messageId: latestAiMessage.id, duration: toolResultDuration, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); } @@ -220,7 +220,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { try { logger.debug('Parsing wiki operation tool parameters', { toolMatch: toolMatch.parameters, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); // Use parameters returned by matchToolCalling directly. Let zod schema validate. @@ -253,7 +253,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { workspaceName, operation, title, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); let result: string; @@ -293,7 +293,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { workspaceID, operation, title, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); // Format the tool result for display @@ -307,8 +307,8 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Wiki operation setting yieldNextRoundTo=self', { toolId: 'wiki-operation', - agentId: handlerContext.agent.id, - messageCount: handlerContext.agent.messages.length, + agentId: agentFrameworkContext.agent.id, + messageCount: agentFrameworkContext.agent.messages.length, toolResultPreview: toolResultText.slice(0, 200), }); @@ -316,7 +316,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { const toolResultTime = new Date(); const toolResultMessage: AgentInstanceMessage = { id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'tool', // Tool result message content: toolResultText, modified: toolResultTime, @@ -331,7 +331,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { artificialOrder: Date.now() + 10, // Additional ordering hint }, }; - handlerContext.agent.messages.push(toolResultMessage); + agentFrameworkContext.agent.messages.push(toolResultMessage); // Persist tool result immediately so DB ordering matches in-memory order try { @@ -347,7 +347,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { // Signal that tool was executed AFTER adding and persisting the message await hooks.toolExecuted.promise({ - handlerContext, + agentFrameworkContext, toolResult: { success: true, data: result, @@ -370,7 +370,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { } catch (error) { logger.error('Wiki operation tool execution failed', { error, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, toolParameters: toolMatch.parameters, }); @@ -389,7 +389,7 @@ Error: ${error instanceof Error ? error.message : String(error)} const errorResultTime = new Date(); const errorResultMessage: AgentInstanceMessage = { id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'tool', // Tool error message content: errorMessage, modified: errorResultTime, @@ -402,11 +402,11 @@ Error: ${error instanceof Error ? error.message : String(error)} isComplete: true, // Mark as complete to prevent messageManagementPlugin from overwriting content }, }; - handlerContext.agent.messages.push(errorResultMessage); + agentFrameworkContext.agent.messages.push(errorResultMessage); // Signal that tool was executed (with error) AFTER adding the message await hooks.toolExecuted.promise({ - handlerContext, + agentFrameworkContext, toolResult: { success: false, error: error instanceof Error ? error.message : String(error), diff --git a/src/services/agentInstance/plugins/wikiSearchPlugin.ts b/src/services/agentInstance/tools/wikiSearch.ts similarity index 91% rename from src/services/agentInstance/plugins/wikiSearchPlugin.ts rename to src/services/agentInstance/tools/wikiSearch.ts index 0fe32763..a91a4e6e 100644 --- a/src/services/agentInstance/plugins/wikiSearchPlugin.ts +++ b/src/services/agentInstance/tools/wikiSearch.ts @@ -19,7 +19,7 @@ import { findPromptById } from '../promptConcat/promptConcat'; import type { AiAPIConfig } from '../promptConcat/promptConcatSchema'; import type { IPrompt } from '../promptConcat/promptConcatSchema'; import { schemaToToolContent } from '../utilities/schemaToToolContent'; -import type { PromptConcatPlugin } from './types'; +import type { PromptConcatTool } from './types'; /** * Wiki Search Parameter Schema @@ -488,17 +488,17 @@ async function executeWikiUpdateEmbeddingsTool( * Wiki Search plugin - Prompt processing * Handles tool list injection for wiki search and update embeddings functionality */ -export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { +export const wikiSearchTool: PromptConcatTool = (hooks) => { // First tapAsync: Tool list injection - hooks.processPrompts.tapAsync('wikiSearchPlugin-toolList', async (context, callback) => { - const { pluginConfig, prompts } = context; + hooks.processPrompts.tapAsync('wikiSearchTool-toolList', async (context, callback) => { + const { toolConfig, prompts } = context; - if (pluginConfig.pluginId !== 'wikiSearch' || !pluginConfig.wikiSearchParam) { + if (toolConfig.toolId !== 'wikiSearch' || !toolConfig.wikiSearchParam) { callback(); return; } - const wikiSearchParameter = pluginConfig.wikiSearchParam; + const wikiSearchParameter = toolConfig.wikiSearchParam; try { // Handle tool list injection if toolListPosition is configured @@ -508,7 +508,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { if (!toolListTarget) { logger.warn('Tool list target prompt not found', { targetId: toolListPosition.targetId, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); return; @@ -544,7 +544,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { targetId: toolListPosition.targetId, position: toolListPosition.position, toolCount: 2, // wiki-search and wiki-update-embeddings - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); } @@ -552,20 +552,20 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { } catch (error) { logger.error('Error in wiki search tool list injection', { error, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); } }); // 2. Tool execution when AI response is complete - hooks.responseComplete.tapAsync('wikiSearchPlugin-handler', async (context, callback) => { + hooks.responseComplete.tapAsync('wikiSearchTool-handler', async (context, callback) => { try { - const { handlerContext, response, handlerConfig } = context; + const { agentFrameworkContext, response, agentFrameworkConfig } = context; - // Find this plugin's configuration from handlerConfig - const wikiSearchPluginConfig = handlerConfig?.plugins?.find(p => p.pluginId === 'wikiSearch'); - const wikiSearchParameter = wikiSearchPluginConfig?.wikiSearchParam as { toolResultDuration?: number } | undefined; + // Find this plugin's configuration import { AgentFrameworkConfig } + const wikiSearchToolConfig = agentFrameworkConfig?.plugins?.find((p: { toolId: string; [key: string]: unknown }) => p.toolId === 'wikiSearch'); + const wikiSearchParameter = wikiSearchToolConfig?.wikiSearchParam as { toolResultDuration?: number } | undefined; const toolResultDuration = wikiSearchParameter?.toolResultDuration || 1; // Default to 1 round if (response.status !== 'done' || !response.content) { @@ -583,12 +583,12 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Wiki tool call detected', { toolId: toolMatch.toolId, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); // Set duration=1 for the AI message containing the tool call // Find the most recent AI message (should be the one containing the tool call) - const aiMessages = handlerContext.agent.messages.filter(message => message.role === 'assistant'); + const aiMessages = agentFrameworkContext.agent.messages.filter((message: AgentInstanceMessage) => message.role === 'assistant'); if (aiMessages.length > 0) { const latestAiMessage = aiMessages[aiMessages.length - 1]; if (latestAiMessage.content === response.content) { @@ -614,7 +614,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { } // Also update UI immediately - agentInstanceService.debounceUpdateMessage(latestAiMessage, handlerContext.agent.id, 0); // No delay + agentInstanceService.debounceUpdateMessage(latestAiMessage, agentFrameworkContext.agent.id, 0); // No delay logger.debug('Set duration=1 for AI tool call message', { messageId: latestAiMessage.id, @@ -626,10 +626,10 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { // Execute the appropriate tool try { // Check if cancelled before starting tool execution - if (handlerContext.isCancelled()) { + if (agentFrameworkContext.isCancelled()) { logger.debug('Wiki tool cancelled before execution', { toolId: toolMatch.toolId, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); callback(); return; @@ -644,9 +644,9 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { result = await executeWikiSearchTool( validatedParameters, { - agentId: handlerContext.agent.id, - messageId: handlerContext.agent.messages[handlerContext.agent.messages.length - 1]?.id, - config: handlerContext.agent.aiApiConfig as AiAPIConfig | undefined, + agentId: agentFrameworkContext.agent.id, + messageId: agentFrameworkContext.agent.messages[agentFrameworkContext.agent.messages.length - 1]?.id, + config: agentFrameworkContext.agent.aiApiConfig as AiAPIConfig | undefined, }, ); } else { @@ -655,18 +655,18 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { result = await executeWikiUpdateEmbeddingsTool( validatedParameters, { - agentId: handlerContext.agent.id, - messageId: handlerContext.agent.messages[handlerContext.agent.messages.length - 1]?.id, - aiConfig: handlerContext.agent.aiApiConfig, + agentId: agentFrameworkContext.agent.id, + messageId: agentFrameworkContext.agent.messages[agentFrameworkContext.agent.messages.length - 1]?.id, + aiConfig: agentFrameworkContext.agent.aiApiConfig, }, ); } // Check if cancelled after tool execution - if (handlerContext.isCancelled()) { + if (agentFrameworkContext.isCancelled()) { logger.debug('Wiki tool cancelled after execution', { toolId: toolMatch.toolId, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, }); callback(); return; @@ -692,8 +692,8 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Wiki search setting yieldNextRoundTo=self', { toolId: 'wiki-search', - agentId: handlerContext.agent.id, - messageCount: handlerContext.agent.messages.length, + agentId: agentFrameworkContext.agent.id, + messageCount: agentFrameworkContext.agent.messages.length, toolResultPreview: toolResultText.slice(0, 200), }); @@ -701,7 +701,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { const nowTool = new Date(); const toolResultMessage: AgentInstanceMessage = { id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'tool', // Tool result message content: toolResultText, created: nowTool, @@ -717,13 +717,13 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => { artificialOrder: Date.now() + 10, // Additional ordering hint }, }; - handlerContext.agent.messages.push(toolResultMessage); + agentFrameworkContext.agent.messages.push(toolResultMessage); // Do not persist immediately here. Let messageManagementPlugin handle persistence // Signal that tool was executed AFTER adding and persisting the message await hooks.toolExecuted.promise({ - handlerContext, + agentFrameworkContext, toolResult: { success: true, data: result.success ? result.data : result.error, @@ -765,7 +765,7 @@ Error: ${error instanceof Error ? error.message : String(error)} const nowError = new Date(); const errorResultMessage: AgentInstanceMessage = { id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, - agentId: handlerContext.agent.id, + agentId: agentFrameworkContext.agent.id, role: 'tool', // Tool error message content: errorMessage, created: nowError, @@ -779,11 +779,11 @@ Error: ${error instanceof Error ? error.message : String(error)} isComplete: true, // Mark as complete to prevent messageManagementPlugin from overwriting content }, }; - handlerContext.agent.messages.push(errorResultMessage); + agentFrameworkContext.agent.messages.push(errorResultMessage); // Do not persist immediately; let messageManagementPlugin handle it during toolExecuted await hooks.toolExecuted.promise({ - handlerContext, + agentFrameworkContext, toolResult: { success: false, error: error instanceof Error ? error.message : String(error), diff --git a/src/services/agentInstance/plugins/workspacesListPlugin.ts b/src/services/agentInstance/tools/workspacesList.ts similarity index 85% rename from src/services/agentInstance/plugins/workspacesListPlugin.ts rename to src/services/agentInstance/tools/workspacesList.ts index 5d99ff84..ed195968 100644 --- a/src/services/agentInstance/plugins/workspacesListPlugin.ts +++ b/src/services/agentInstance/tools/workspacesList.ts @@ -45,23 +45,23 @@ import type { IWorkspaceService } from '@services/workspaces/interface'; import { isWikiWorkspace } from '@services/workspaces/interface'; import { findPromptById } from '../promptConcat/promptConcat'; -import type { PromptConcatPlugin } from './types'; +import type { PromptConcatTool } from './types'; /** * Workspaces List plugin - Prompt processing * Handles injection of available wiki workspaces list */ -export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { +export const workspacesListTool: PromptConcatTool = (hooks) => { // Tool list injection - hooks.processPrompts.tapAsync('workspacesListPlugin-injection', async (context, callback) => { - const { pluginConfig, prompts } = context; + hooks.processPrompts.tapAsync('workspacesListTool-injection', async (context, callback) => { + const { toolConfig, prompts } = context; - if (pluginConfig.pluginId !== 'workspacesList' || !pluginConfig.workspacesListParam) { + if (toolConfig.toolId !== 'workspacesList' || !toolConfig.workspacesListParam) { callback(); return; } - const workspacesListParameter = pluginConfig.workspacesListParam; + const workspacesListParameter = toolConfig.workspacesListParam; try { // Handle workspaces list injection if targetId is configured @@ -70,7 +70,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { if (!target) { logger.warn('Workspaces list target prompt not found', { targetId: workspacesListParameter.targetId, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); return; @@ -96,7 +96,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { } const insertIndex = target.prompt.children.length; target.prompt.children.splice(insertIndex, 0, { - id: `workspaces-list-${pluginConfig.id}`, + id: `workspaces-list-${toolConfig.id}`, caption: 'Available Workspaces', text: workspacesListContent, }); @@ -105,7 +105,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { target.prompt.children = []; } target.prompt.children.unshift({ - id: `workspaces-list-${pluginConfig.id}`, + id: `workspaces-list-${toolConfig.id}`, caption: 'Available Workspaces', text: workspacesListContent, }); @@ -117,12 +117,12 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { logger.debug('Workspaces list injected successfully', { targetId: workspacesListParameter.targetId, position: workspacesListParameter.position, - pluginId: pluginConfig.id, + toolId: toolConfig.id, workspaceCount: wikiWorkspaces.length, }); } else { logger.debug('No wiki workspaces found to inject', { - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); } } @@ -131,7 +131,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { } catch (error) { logger.error('Error in workspaces list injection', { error, - pluginId: pluginConfig.id, + toolId: toolConfig.id, }); callback(); } diff --git a/src/services/agentInstance/utilities.ts b/src/services/agentInstance/utilities.ts index 64fab91f..d9d526e4 100644 --- a/src/services/agentInstance/utilities.ts +++ b/src/services/agentInstance/utilities.ts @@ -14,8 +14,8 @@ export function createAgentInstanceData(agentDefinition: { name: string; avatarUrl?: string; aiApiConfig?: Record; - handlerConfig?: Record; - handlerID?: string; + agentFrameworkConfig?: Record; + agentFrameworkID?: string; }): { instanceData: Omit; instanceId: string; @@ -31,7 +31,7 @@ export function createAgentInstanceData(agentDefinition: { }; // Extract necessary fields from agent definition - const { avatarUrl, aiApiConfig, handlerID } = agentDefinition; + const { avatarUrl, aiApiConfig, agentFrameworkID } = agentDefinition; const instanceData = { id: instanceId, @@ -40,9 +40,9 @@ export function createAgentInstanceData(agentDefinition: { status: initialStatus, avatarUrl, aiApiConfig, - // Don't copy handlerConfig to instance - it should fallback to definition - handlerConfig: undefined, - handlerID, + // Don't copy agentFrameworkConfig to instance - it should fallback to definition + agentFrameworkConfig: undefined, + agentFrameworkID, messages: [], closed: false, }; @@ -119,6 +119,6 @@ export const AGENT_INSTANCE_FIELDS = [ 'modified', 'avatarUrl', 'aiApiConfig', - 'handlerConfig', + 'agentFrameworkConfig', 'closed', ] as const; diff --git a/src/services/database/schema/agent.ts b/src/services/database/schema/agent.ts index 10c4ff47..5fe7d9b4 100644 --- a/src/services/database/schema/agent.ts +++ b/src/services/database/schema/agent.ts @@ -29,11 +29,11 @@ export class AgentDefinitionEntity implements Partial { /** Agent handler function ID, nullable indicates using default handler */ @Column({ nullable: true }) - handlerID?: string; + agentFrameworkID?: string; /** Agent handler configuration parameters, stored as JSON */ @Column({ type: 'simple-json', nullable: true }) - handlerConfig?: Record; + agentFrameworkConfig?: Record; /** Agent's AI API configuration, can override global default config */ @Column({ type: 'simple-json', nullable: true }) @@ -88,7 +88,7 @@ export class AgentInstanceEntity implements Partial { /** Agent handler configuration parameters, inherited from AgentDefinition */ @Column({ type: 'simple-json', nullable: true }) - handlerConfig?: Record; + agentFrameworkConfig?: Record; @Column({ default: false }) closed: boolean = false; diff --git a/src/services/externalAPI/__tests__/externalAPI.logging.test.ts b/src/services/externalAPI/__tests__/externalAPI.logging.test.ts index 907c5eed..9a09bd81 100644 --- a/src/services/externalAPI/__tests__/externalAPI.logging.test.ts +++ b/src/services/externalAPI/__tests__/externalAPI.logging.test.ts @@ -1,5 +1,5 @@ import type { AgentDefinition } from '@services/agentDefinition/interface'; -import defaultAgents from '@services/agentInstance/buildInAgentHandlers/defaultAgents.json'; +import defaultAgents from '@services/agentInstance/agentFrameworks/taskAgents.json'; import { container } from '@services/container'; import type { IDatabaseService } from '@services/database/interface'; import { AgentDefinitionEntity } from '@services/database/schema/agent'; diff --git a/src/services/git/registerMenu.ts b/src/services/git/registerMenu.ts index a49989ba..4412a36c 100644 --- a/src/services/git/registerMenu.ts +++ b/src/services/git/registerMenu.ts @@ -105,7 +105,7 @@ export async function registerMenu(): Promise { dir: activeWorkspace.wikiFolderLocation, commitOnly: false, commitMessage: i18n.t('LOG.CommitBackupMessage'), - remoteUrl: activeWorkspace.gitUrl, + remoteUrl: activeWorkspace.gitUrl ?? undefined, userInfo, }); } @@ -127,7 +127,7 @@ export async function registerMenu(): Promise { dir: activeWorkspace.wikiFolderLocation, commitOnly: false, // Don't provide commitMessage to trigger AI generation - remoteUrl: activeWorkspace.gitUrl, + remoteUrl: activeWorkspace.gitUrl ?? undefined, userInfo, }); } @@ -151,7 +151,7 @@ export async function registerMenu(): Promise { dir: activeWorkspace.wikiFolderLocation, commitOnly: false, commitMessage: i18n.t('LOG.CommitBackupMessage'), - remoteUrl: activeWorkspace.gitUrl, + remoteUrl: activeWorkspace.gitUrl ?? undefined, userInfo, }); } diff --git a/src/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement.ts b/src/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement.ts new file mode 100644 index 00000000..c978752a --- /dev/null +++ b/src/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement.ts @@ -0,0 +1,102 @@ +import { AgentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; +import { useCallback, useEffect, useState } from 'react'; + +interface useAgentFrameworkConfigManagementProps { + agentDefId?: string; + agentId?: string; +} + +interface UseAgentFrameworkConfigManagementResult { + loading: boolean; + config: AgentFrameworkConfig | undefined; + schema?: Record; + handleConfigChange: (newConfig: AgentFrameworkConfig) => Promise; +} + +export const useAgentFrameworkConfigManagement = ({ agentDefId, agentId }: useAgentFrameworkConfigManagementProps = {}): UseAgentFrameworkConfigManagementResult => { + const [loading, setLoading] = useState(true); + const [config, setConfig] = useState(undefined); + const [schema, setSchema] = useState | undefined>(undefined); + + useEffect(() => { + const fetchConfig = async () => { + try { + setLoading(true); + let finalConfig: AgentFrameworkConfig | undefined; + let agentFrameworkID: string | undefined; + + if (agentId) { + const agentInstance = await window.service.agentInstance.getAgent(agentId); + let agentDefinition: Awaited> | undefined; + if (agentInstance?.agentDefId) { + agentDefinition = await window.service.agentDefinition.getAgentDef(agentInstance.agentDefId); + } + // Use instance config if available, otherwise fallback to definition config + if (agentInstance?.agentFrameworkConfig && Object.keys(agentInstance.agentFrameworkConfig).length > 0) { + finalConfig = agentInstance.agentFrameworkConfig as AgentFrameworkConfig; + } else if (agentDefinition?.agentFrameworkConfig) { + finalConfig = agentDefinition.agentFrameworkConfig as AgentFrameworkConfig; + } + // Use agentFrameworkID from instance, fallback to definition + agentFrameworkID = agentInstance?.agentFrameworkID || agentDefinition?.agentFrameworkID; + } else if (agentDefId) { + const agentDefinition = await window.service.agentDefinition.getAgentDef(agentDefId); + if (agentDefinition?.agentFrameworkConfig) { + finalConfig = agentDefinition.agentFrameworkConfig as AgentFrameworkConfig; + } + agentFrameworkID = agentDefinition?.agentFrameworkID; + } + + if (agentFrameworkID) { + try { + const frameworkSchema = await window.service.agentInstance.getFrameworkConfigSchema(agentFrameworkID); + setSchema(frameworkSchema); + } catch (error) { + void window.service.native.log('error', 'Failed to load framework schema', { function: 'useAgentFrameworkConfigManagement.fetchConfig', error }); + } + } + + setConfig(finalConfig); + setLoading(false); + } catch (error) { + void window.service.native.log('error', 'Failed to load framework configuration', { function: 'useAgentFrameworkConfigManagement.fetchConfig', error }); + setLoading(false); + } + }; + + void fetchConfig(); + }, [agentDefId, agentId]); + + const handleConfigChange = useCallback(async (newConfig: AgentFrameworkConfig) => { + try { + setConfig(newConfig); + + if (agentId) { + await window.service.agentInstance.updateAgent(agentId, { + agentFrameworkConfig: newConfig, + }); + } else if (agentDefId) { + const agentDefinition = await window.service.agentDefinition.getAgentDef(agentDefId); + if (agentDefinition) { + await window.service.agentDefinition.updateAgentDef({ + ...agentDefinition, + agentFrameworkConfig: newConfig, + }); + } + } else { + void window.service.native.log('error', 'No agent ID or definition ID provided for updating handler config', { + function: 'useAgentFrameworkConfigManagement.handleConfigChange', + }); + } + } catch (error) { + void window.service.native.log('error', 'Failed to update framework configuration', { function: 'useAgentFrameworkConfigManagement.handleConfigChange', error }); + } + }, [agentId, agentDefId]); + + return { + loading, + config, + schema, + handleConfigChange, + }; +}; diff --git a/src/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement.ts b/src/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement.ts deleted file mode 100644 index 9a89d6ce..00000000 --- a/src/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; -import { useCallback, useEffect, useState } from 'react'; - -interface UseHandlerConfigManagementProps { - agentDefId?: string; - agentId?: string; -} - -interface UseHandlerConfigManagementResult { - loading: boolean; - config: HandlerConfig | undefined; - schema?: Record; - handleConfigChange: (newConfig: HandlerConfig) => Promise; -} - -export const useHandlerConfigManagement = ({ agentDefId, agentId }: UseHandlerConfigManagementProps = {}): UseHandlerConfigManagementResult => { - const [loading, setLoading] = useState(true); - const [config, setConfig] = useState(undefined); - const [schema, setSchema] = useState | undefined>(undefined); - - useEffect(() => { - const fetchConfig = async () => { - try { - setLoading(true); - let finalConfig: HandlerConfig | undefined; - let handlerID: string | undefined; - - if (agentId) { - const agentInstance = await window.service.agentInstance.getAgent(agentId); - let agentDefinition: Awaited> | undefined; - if (agentInstance?.agentDefId) { - agentDefinition = await window.service.agentDefinition.getAgentDef(agentInstance.agentDefId); - } - // Use instance config if available, otherwise fallback to definition config - if (agentInstance?.handlerConfig && Object.keys(agentInstance.handlerConfig).length > 0) { - finalConfig = agentInstance.handlerConfig as HandlerConfig; - } else if (agentDefinition?.handlerConfig) { - finalConfig = agentDefinition.handlerConfig as HandlerConfig; - } - // Use handlerID from instance, fallback to definition - handlerID = agentInstance?.handlerID || agentDefinition?.handlerID; - } else if (agentDefId) { - const agentDefinition = await window.service.agentDefinition.getAgentDef(agentDefId); - if (agentDefinition?.handlerConfig) { - finalConfig = agentDefinition.handlerConfig as HandlerConfig; - } - handlerID = agentDefinition?.handlerID; - } - - if (handlerID) { - try { - const handlerSchema = await window.service.agentInstance.getHandlerConfigSchema(handlerID); - setSchema(handlerSchema); - } catch (error) { - void window.service.native.log('error', 'Failed to load handler schema', { function: 'useHandlerConfigManagement.fetchConfig', error }); - } - } - - setConfig(finalConfig); - setLoading(false); - } catch (error) { - void window.service.native.log('error', 'Failed to load handler configuration', { function: 'useHandlerConfigManagement.fetchConfig', error }); - setLoading(false); - } - }; - - void fetchConfig(); - }, [agentDefId, agentId]); - - const handleConfigChange = useCallback(async (newConfig: HandlerConfig) => { - try { - setConfig(newConfig); - - if (agentId) { - await window.service.agentInstance.updateAgent(agentId, { - handlerConfig: newConfig, - }); - } else if (agentDefId) { - const agentDefinition = await window.service.agentDefinition.getAgentDef(agentDefId); - if (agentDefinition) { - await window.service.agentDefinition.updateAgentDef({ - ...agentDefinition, - handlerConfig: newConfig, - }); - } - } else { - void window.service.native.log('error', 'No agent ID or definition ID provided for updating handler config', { function: 'useHandlerConfigManagement.handleConfigChange' }); - } - } catch (error) { - void window.service.native.log('error', 'Failed to update handler configuration', { function: 'useHandlerConfigManagement.handleConfigChange', error }); - } - }, [agentId, agentDefId]); - - return { - loading, - config, - schema, - handleConfigChange, - }; -};