rename: handler -> agent framework; plugin -> tool

rename: handler -> agent framework; plugin -> tool

lint

refactor: more rename

further rename
This commit is contained in:
lin onetwo 2025-11-27 15:33:30 +08:00 committed by GitHub
parent 8963527b41
commit 8a84d9b468
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 1382 additions and 1368 deletions

View file

@ -46,7 +46,7 @@ Object.defineProperty(window, 'observables', {
userInfo$: new BehaviorSubject(undefined).asObservable(), userInfo$: new BehaviorSubject(undefined).asObservable(),
}, },
agentInstance: { agentInstance: {
concatPrompt: vi.fn((promptDescription: Pick<AgentPromptDescription, 'handlerConfig'>, messages: AgentInstanceMessage[]) => { concatPrompt: vi.fn((promptDescription: Pick<AgentPromptDescription, 'agentFrameworkConfig'>, messages: AgentInstanceMessage[]) => {
const agentInstanceService = container.get<AgentInstanceService>(serviceIdentifier.AgentInstance); const agentInstanceService = container.get<AgentInstanceService>(serviceIdentifier.AgentInstance);
// Initialize handlers (plugins and built-in handlers) before calling concatPrompt // Initialize handlers (plugins and built-in handlers) before calling concatPrompt
// We need to wrap this in an Observable since concatPrompt returns an Observable // We need to wrap this in an Observable since concatPrompt returns an Observable
@ -55,7 +55,7 @@ Object.defineProperty(window, 'observables', {
try { try {
// Need to register plugins first. In test environment, this needs to be called manually. While in real // 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. // environment, this is handled in `main.ts` when app start.
await agentInstanceService.initializeHandlers(); await agentInstanceService.initializeFrameworks();
const resultObservable = agentInstanceService.concatPrompt(promptDescription, messages); const resultObservable = agentInstanceService.concatPrompt(promptDescription, messages);
// Subscribe to the result and forward to our observer // Subscribe to the result and forward to our observer
resultObservable.subscribe(observer); resultObservable.subscribe(observer);

View file

@ -4,7 +4,7 @@ import { Box, Button, Container, Step, StepLabel, Stepper, TextField, Typography
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import type { RJSFSchema } from '@rjsf/utils'; import type { RJSFSchema } from '@rjsf/utils';
import type { AgentDefinition } from '@services/agentDefinition/interface'; 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 useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@ -99,19 +99,19 @@ export const CreateNewAgentContent: React.FC<CreateNewAgentContentProps> = ({ ta
// Load schema when temporaryAgentDefinition is available // Load schema when temporaryAgentDefinition is available
useEffect(() => { useEffect(() => {
const loadSchema = async () => { const loadSchema = async () => {
if (temporaryAgentDefinition?.handlerID) { if (temporaryAgentDefinition?.agentFrameworkID) {
try { try {
const schema = await window.service.agentInstance.getHandlerConfigSchema(temporaryAgentDefinition.handlerID); const schema = await window.service.agentInstance.getFrameworkConfigSchema(temporaryAgentDefinition.agentFrameworkID);
setPromptSchema(schema as RJSFSchema); setPromptSchema(schema as RJSFSchema);
} catch (error) { } catch (error) {
console.error('Failed to load handler config schema:', error); console.error('Failed to load framework config schema:', error);
setPromptSchema(null); setPromptSchema(null);
} }
} }
}; };
void loadSchema(); void loadSchema();
}, [temporaryAgentDefinition?.handlerID]); }, [temporaryAgentDefinition?.agentFrameworkID]);
// Create preview agent when entering step 3 // Create preview agent when entering step 3
useEffect(() => { useEffect(() => {
@ -374,11 +374,11 @@ export const CreateNewAgentContent: React.FC<CreateNewAgentContentProps> = ({ ta
<Box sx={{ mt: 2, height: 400, overflow: 'auto' }}> <Box sx={{ mt: 2, height: 400, overflow: 'auto' }}>
<PromptConfigForm <PromptConfigForm
schema={promptSchema} schema={promptSchema}
formData={(temporaryAgentDefinition.handlerConfig || {}) as HandlerConfig} formData={(temporaryAgentDefinition.agentFrameworkConfig || {}) as AgentFrameworkConfig}
onChange={(updatedConfig) => { onChange={(updatedConfig) => {
void handleAgentDefinitionChange({ void handleAgentDefinitionChange({
...temporaryAgentDefinition, ...temporaryAgentDefinition,
handlerConfig: updatedConfig as Record<string, unknown>, agentFrameworkConfig: updatedConfig as Record<string, unknown>,
}); });
}} }}
loading={false} loading={false}

View file

@ -2,7 +2,7 @@ import { Box, Button, CircularProgress, Container, Divider, TextField, Typograph
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import type { RJSFSchema } from '@rjsf/utils'; import type { RJSFSchema } from '@rjsf/utils';
import type { AgentDefinition } from '@services/agentDefinition/interface'; 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 useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -94,30 +94,30 @@ export const EditAgentDefinitionContent: React.FC<EditAgentDefinitionContentProp
void loadAgentDefinition(); void loadAgentDefinition();
}, [tab.agentDefId]); }, [tab.agentDefId]);
// Load handler config schema // Load framework config schema
useEffect(() => { useEffect(() => {
const loadSchema = async () => { const loadSchema = async () => {
if (!agentDefinition?.handlerID) { if (!agentDefinition?.agentFrameworkID) {
// No handlerID found // No agentFrameworkID found
return; return;
} }
try { try {
// Loading handler config schema // Loading framework config schema
const schema = await window.service.agentInstance.getHandlerConfigSchema(agentDefinition.handlerID); const schema = await window.service.agentInstance.getFrameworkConfigSchema(agentDefinition.agentFrameworkID);
// Schema loaded successfully // Schema loaded successfully
setPromptSchema(schema); setPromptSchema(schema);
} catch (error) { } 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, 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(); void loadSchema();
}, [agentDefinition?.handlerID]); }, [agentDefinition?.agentFrameworkID]);
// Auto-save to backend whenever agentDefinition changes (debounced) // Auto-save to backend whenever agentDefinition changes (debounced)
const saveToBackendDebounced = useDebouncedCallback( const saveToBackendDebounced = useDebouncedCallback(
@ -235,7 +235,7 @@ export const EditAgentDefinitionContent: React.FC<EditAgentDefinitionContentProp
return { return {
...previous, ...previous,
handlerConfig: formData as Record<string, unknown>, agentFrameworkConfig: formData as Record<string, unknown>,
}; };
}, },
); );
@ -356,7 +356,7 @@ export const EditAgentDefinitionContent: React.FC<EditAgentDefinitionContentProp
<Box sx={{ mt: 2 }} data-testid='edit-agent-prompt-form'> <Box sx={{ mt: 2 }} data-testid='edit-agent-prompt-form'>
<PromptConfigForm <PromptConfigForm
schema={promptSchema} schema={promptSchema}
formData={agentDefinition.handlerConfig as HandlerConfig} formData={agentDefinition.agentFrameworkConfig as AgentFrameworkConfig}
onChange={handlePromptConfigChange} onChange={handlePromptConfigChange}
/> />
</Box> </Box>

View file

@ -86,7 +86,7 @@ export const NewTabContent: React.FC<NewTabContentProps> = ({ tab: _tab }) => {
const createAgentChatTab = async (agentDefinitionId?: string) => { const createAgentChatTab = async (agentDefinitionId?: string) => {
try { try {
const agentDefinitionIdToUse = agentDefinitionId || 'example-agent'; const agentDefinitionIdToUse = agentDefinitionId || 'task-agent';
// Handle current active tab - close temp tabs or NEW_TAB type tabs // Handle current active tab - close temp tabs or NEW_TAB type tabs
if (activeTabId) { if (activeTabId) {
@ -162,7 +162,7 @@ export const NewTabContent: React.FC<NewTabContentProps> = ({ tab: _tab }) => {
const handleEditDefinition = useCallback(() => { const handleEditDefinition = useCallback(() => {
// Use the example agent ID for now - in the future this could be configurable // Use the example agent ID for now - in the future this could be configurable
void editAgentDefinitionTab('example-agent'); void editAgentDefinitionTab('task-agent');
handleCloseContextMenu(); handleCloseContextMenu();
}, []); }, []);

View file

@ -17,7 +17,7 @@ const mockGetAgentDefs = vi.fn();
const mockUpdateTab = vi.fn(); const mockUpdateTab = vi.fn();
const mockGetAllTabs = vi.fn(); const mockGetAllTabs = vi.fn();
const mockGetActiveTabId = vi.fn(); const mockGetActiveTabId = vi.fn();
const mockGetHandlerConfigSchema = vi.fn(); const mockGetFrameworkConfigSchema = vi.fn();
Object.defineProperty(window, 'service', { Object.defineProperty(window, 'service', {
writable: true, writable: true,
@ -30,7 +30,7 @@ Object.defineProperty(window, 'service', {
getAgentDefs: mockGetAgentDefs, getAgentDefs: mockGetAgentDefs,
}, },
agentInstance: { agentInstance: {
getHandlerConfigSchema: mockGetHandlerConfigSchema, getFrameworkConfigSchema: mockGetFrameworkConfigSchema,
}, },
agentBrowser: { agentBrowser: {
updateTab: mockUpdateTab, updateTab: mockUpdateTab,
@ -117,7 +117,7 @@ describe('CreateNewAgentContent', () => {
mockUpdateTab.mockResolvedValue(undefined); mockUpdateTab.mockResolvedValue(undefined);
mockGetAllTabs.mockResolvedValue([]); mockGetAllTabs.mockResolvedValue([]);
mockGetActiveTabId.mockResolvedValue('test-tab-123'); mockGetActiveTabId.mockResolvedValue('test-tab-123');
mockGetHandlerConfigSchema.mockResolvedValue({ mockGetFrameworkConfigSchema.mockResolvedValue({
type: 'object', type: 'object',
properties: { properties: {
prompts: { prompts: {
@ -157,7 +157,7 @@ describe('CreateNewAgentContent', () => {
id: 'template-1', id: 'template-1',
name: 'Test Template', name: 'Test Template',
description: 'Test Description', description: 'Test Description',
handlerConfig: { systemPrompt: 'Test prompt' }, agentFrameworkConfig: { systemPrompt: 'Test prompt' },
}; };
mockCreateAgentDef.mockResolvedValue({ mockCreateAgentDef.mockResolvedValue({
@ -258,8 +258,8 @@ describe('CreateNewAgentContent', () => {
const mockAgentDefinition = { const mockAgentDefinition = {
id: 'temp-123', id: 'temp-123',
name: 'Test Agent', name: 'Test Agent',
handlerID: 'test-handler', agentFrameworkID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Original prompt', role: 'system' }] }, agentFrameworkConfig: { prompts: [{ text: 'Original prompt', role: 'system' }] },
}; };
mockGetAgentDef.mockResolvedValue(mockAgentDefinition); mockGetAgentDef.mockResolvedValue(mockAgentDefinition);
@ -285,13 +285,13 @@ describe('CreateNewAgentContent', () => {
expect(mockUpdateAgentDef).not.toHaveBeenCalled(); expect(mockUpdateAgentDef).not.toHaveBeenCalled();
}); });
it('should trigger schema loading when temporaryAgentDefinition has handlerID', async () => { it('should trigger schema loading when temporaryAgentDefinition has agentFrameworkID', async () => {
// Mock agent definition with handlerID that will be restored // Mock agent definition with agentFrameworkID that will be restored
const mockAgentDefinition = { const mockAgentDefinition = {
id: 'temp-123', id: 'temp-123',
name: 'Test Agent', name: 'Test Agent',
handlerID: 'test-handler', agentFrameworkID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] }, agentFrameworkConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] },
}; };
mockGetAgentDef.mockResolvedValue(mockAgentDefinition); mockGetAgentDef.mockResolvedValue(mockAgentDefinition);
@ -313,9 +313,9 @@ describe('CreateNewAgentContent', () => {
expect(mockGetAgentDef).toHaveBeenCalledWith('temp-123'); expect(mockGetAgentDef).toHaveBeenCalledWith('temp-123');
}, { timeout: 1000 }); }, { 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(() => { await waitFor(() => {
expect(mockGetHandlerConfigSchema).toHaveBeenCalledWith('test-handler'); expect(mockGetFrameworkConfigSchema).toHaveBeenCalledWith('test-handler');
}, { timeout: 2000 }); }, { timeout: 2000 });
}); });
@ -341,8 +341,8 @@ describe('CreateNewAgentContent', () => {
const mockTemplate = { const mockTemplate = {
id: 'template-1', id: 'template-1',
name: 'Test Template', name: 'Test Template',
handlerID: 'test-handler', agentFrameworkID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] }, agentFrameworkConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] },
}; };
const mockCreatedDefinition = { const mockCreatedDefinition = {
@ -378,8 +378,8 @@ describe('CreateNewAgentContent', () => {
const mockTemplate = { const mockTemplate = {
id: 'template-1', id: 'template-1',
name: 'Test Template', name: 'Test Template',
handlerID: 'test-handler', agentFrameworkID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Original prompt' }] }, agentFrameworkConfig: { prompts: [{ text: 'Original prompt' }] },
}; };
const mockCreatedDefinition = { const mockCreatedDefinition = {
@ -443,7 +443,7 @@ describe('CreateNewAgentContent', () => {
expect(mockCreateAgentDef).toHaveBeenCalledWith( expect(mockCreateAgentDef).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
name: 'My Agent', name: 'My Agent',
handlerID: 'test-handler', agentFrameworkID: 'test-handler',
}), }),
); );
}); });
@ -459,19 +459,19 @@ describe('CreateNewAgentContent', () => {
expect(mockUpdateAgentDef).toHaveBeenCalledWith( expect(mockUpdateAgentDef).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
id: expect.stringContaining('temp-'), id: expect.stringContaining('temp-'),
handlerID: 'test-handler', agentFrameworkID: 'test-handler',
}), }),
); );
}, { timeout: 500 }); }, { timeout: 500 });
}); });
it('should handle nested prompt structure like defaultAgents.json', async () => { it('should handle nested prompt structure like taskAgents.json', async () => {
// This is the actual structure from defaultAgents.json // This is the actual structure from taskAgents.json
const mockTemplate = { const mockTemplate = {
id: 'example-agent', id: 'task-agent',
name: 'Example Agent', name: 'Example Agent',
handlerID: 'basicPromptConcatHandler', agentFrameworkID: 'basicPromptConcatHandler',
handlerConfig: { agentFrameworkConfig: {
prompts: [ prompts: [
{ {
id: 'system', id: 'system',
@ -503,7 +503,7 @@ describe('CreateNewAgentContent', () => {
// Step 1: Create agent definition (simulates template selection) // Step 1: Create agent definition (simulates template selection)
const createdDef = await window.service.agentDefinition.createAgentDef(mockCreatedDefinition); const createdDef = await window.service.agentDefinition.createAgentDef(mockCreatedDefinition);
expect(createdDef).toBeDefined(); expect(createdDef).toBeDefined();
const prompts = (createdDef.handlerConfig).prompts as Array<{ const prompts = (createdDef.agentFrameworkConfig).prompts as Array<{
children?: Array<{ text?: string }>; 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.'); 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 // Step 2: Update system prompt in nested structure
const updatedDefinition = { const updatedDefinition = {
...mockCreatedDefinition, ...mockCreatedDefinition,
handlerConfig: { agentFrameworkConfig: {
...mockCreatedDefinition.handlerConfig, ...mockCreatedDefinition.agentFrameworkConfig,
prompts: [ prompts: [
{ {
...mockCreatedDefinition.handlerConfig.prompts[0], ...mockCreatedDefinition.agentFrameworkConfig.prompts[0],
children: [ children: [
{ {
...mockCreatedDefinition.handlerConfig.prompts[0].children[0], ...mockCreatedDefinition.agentFrameworkConfig.prompts[0].children[0],
text: '你是一个专业的代码助手,请用中文回答编程问题。', text: '你是一个专业的代码助手,请用中文回答编程问题。',
}, },
], ],
@ -532,7 +532,7 @@ describe('CreateNewAgentContent', () => {
// Verify the correct nested structure is updated // Verify the correct nested structure is updated
expect(mockUpdateAgentDef).toHaveBeenCalledWith( expect(mockUpdateAgentDef).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
handlerConfig: expect.objectContaining({ agentFrameworkConfig: expect.objectContaining({
prompts: expect.arrayContaining([ prompts: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
role: 'system', role: 'system',

View file

@ -17,7 +17,7 @@ const mockCreateAgent = vi.fn();
const mockDeleteAgent = vi.fn(); const mockDeleteAgent = vi.fn();
const mockGetAgentDef = vi.fn(); const mockGetAgentDef = vi.fn();
const mockUpdateAgentDef = vi.fn(); const mockUpdateAgentDef = vi.fn();
const mockGetHandlerConfigSchema = vi.fn(); const mockGetFrameworkConfigSchema = vi.fn();
const mockLog = vi.fn(); const mockLog = vi.fn();
Object.defineProperty(window, 'service', { Object.defineProperty(window, 'service', {
@ -33,7 +33,7 @@ Object.defineProperty(window, 'service', {
agentInstance: { agentInstance: {
createAgent: mockCreateAgent, createAgent: mockCreateAgent,
deleteAgent: mockDeleteAgent, deleteAgent: mockDeleteAgent,
getHandlerConfigSchema: mockGetHandlerConfigSchema, getFrameworkConfigSchema: mockGetFrameworkConfigSchema,
}, },
agentDefinition: { agentDefinition: {
getAgentDef: mockGetAgentDef, getAgentDef: mockGetAgentDef,
@ -49,7 +49,7 @@ const mockAgentDefinition = {
id: 'test-agent-def-id', id: 'test-agent-def-id',
name: 'Test Agent', name: 'Test Agent',
description: 'A test agent for editing', description: 'A test agent for editing',
handlerID: 'testHandler', agentFrameworkID: 'testHandler',
config: {}, config: {},
}; };
@ -69,7 +69,7 @@ describe('EditAgentDefinitionContent', () => {
mockGetAllTabs.mockResolvedValue([]); mockGetAllTabs.mockResolvedValue([]);
mockGetActiveTabId.mockResolvedValue(null); mockGetActiveTabId.mockResolvedValue(null);
mockGetAgentDef.mockResolvedValue(mockAgentDefinition); mockGetAgentDef.mockResolvedValue(mockAgentDefinition);
mockGetHandlerConfigSchema.mockResolvedValue(mockSchema); mockGetFrameworkConfigSchema.mockResolvedValue(mockSchema);
mockCreateAgent.mockResolvedValue({ mockCreateAgent.mockResolvedValue({
id: 'test-agent-id', id: 'test-agent-id',
name: 'Test Agent', name: 'Test Agent',
@ -234,7 +234,7 @@ describe('EditAgentDefinitionContent', () => {
await renderComponent(); await renderComponent();
await waitFor(() => { await waitFor(() => {
expect(mockGetHandlerConfigSchema).toHaveBeenCalledWith('testHandler'); expect(mockGetFrameworkConfigSchema).toHaveBeenCalledWith('testHandler');
}); });
await waitFor(() => { await waitFor(() => {
@ -277,7 +277,7 @@ describe('EditAgentDefinitionContent', () => {
await renderComponent(); await renderComponent();
await waitFor(() => { await waitFor(() => {
expect(mockGetHandlerConfigSchema).toHaveBeenCalledWith('testHandler'); expect(mockGetFrameworkConfigSchema).toHaveBeenCalledWith('testHandler');
}); });
}); });

View file

@ -41,7 +41,7 @@ describe('NewTabContent', () => {
mockCreateAgent.mockResolvedValue({ mockCreateAgent.mockResolvedValue({
id: 'test-agent-id', id: 'test-agent-id',
name: 'Test Agent', name: 'Test Agent',
agentDefId: 'example-agent', agentDefId: 'task-agent',
}); });
mockAddTab.mockResolvedValue({ mockAddTab.mockResolvedValue({
id: 'test-tab-id', id: 'test-tab-id',

View file

@ -302,35 +302,35 @@ export const agentActions = (
} }
}, },
getHandlerId: async () => { getAgentFrameworkId: async () => {
try { try {
const { agent, agentDef } = get(); const { agent, agentDef } = get();
if (agentDef?.handlerID) { if (agentDef?.agentFrameworkID) {
return agentDef.handlerID; return agentDef.agentFrameworkID;
} }
if (agent?.agentDefId) { if (agent?.agentDefId) {
const fetchedAgentDefinition = await window.service.agentDefinition.getAgentDef(agent.agentDefId); const fetchedAgentDefinition = await window.service.agentDefinition.getAgentDef(agent.agentDefId);
if (fetchedAgentDefinition?.handlerID) { if (fetchedAgentDefinition?.agentFrameworkID) {
return fetchedAgentDefinition.handlerID; 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) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(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 }); set({ error: finalError });
throw finalError; throw finalError;
} }
}, },
/** /**
* Get handler configuration schema for current handler * Get framework configuration schema for current framework
*/ */
getHandlerConfigSchema: async () => { getFrameworkConfigSchema: async () => {
try { try {
const handlerId = await get().getHandlerId(); const agentFrameworkId = await get().getAgentFrameworkId();
return await window.service.agentInstance.getHandlerConfigSchema(handlerId); return await window.service.agentInstance.getFrameworkConfigSchema(agentFrameworkId);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
const finalError = new Error(`Failed to get handler schema: ${errorMessage}`); const finalError = new Error(`Failed to get handler schema: ${errorMessage}`);

View file

@ -70,14 +70,14 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
getPreviewPromptResult: async ( getPreviewPromptResult: async (
inputText: string, inputText: string,
handlerConfig: AgentPromptDescription['handlerConfig'], agentFrameworkConfig: AgentPromptDescription['agentFrameworkConfig'],
) => { ) => {
try { try {
set({ previewLoading: true }); set({ previewLoading: true });
const messages = Array.from(get().messages.values()); const messages = Array.from(get().messages.values());
// Safety check - if handlerConfig is empty, fail early // Safety check - if agentFrameworkConfig is empty, fail early
if (Object.keys(handlerConfig).length === 0) { if (!agentFrameworkConfig || Object.keys(agentFrameworkConfig).length === 0) {
set({ previewLoading: false, previewResult: null }); set({ previewLoading: false, previewResult: null });
return null; return null;
} }
@ -93,7 +93,7 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
} }
// Use the streaming API with progress updates // Use the streaming API with progress updates
const concatStream = window.observables.agentInstance.concatPrompt({ handlerConfig }, messages); const concatStream = window.observables.agentInstance.concatPrompt({ agentFrameworkConfig }, messages);
// Initialize progress // Initialize progress
set({ set({
@ -113,7 +113,7 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
next: (state) => { next: (state) => {
// Update progress and current step // Update progress and current step
const stepDescription = state.step === 'plugin' const stepDescription = state.step === 'plugin'
? `Processing plugin: ${state.currentPlugin?.pluginId || 'unknown'}` ? `Processing tool: ${state.currentPlugin?.toolId || 'unknown'}`
: state.step === 'finalize' : state.step === 'finalize'
? 'Finalizing prompts...' ? 'Finalizing prompts...'
: state.step === 'flatten' : state.step === 'flatten'
@ -123,7 +123,7 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
set({ set({
previewProgress: state.progress, previewProgress: state.progress,
previewCurrentStep: stepDescription, previewCurrentStep: stepDescription,
previewCurrentPlugin: state.currentPlugin?.pluginId || null, previewCurrentPlugin: state.currentPlugin?.toolId || null,
// Update intermediate results // Update intermediate results
previewResult: { previewResult: {
flatPrompts: state.flatPrompts, flatPrompts: state.flatPrompts,

View file

@ -90,10 +90,10 @@ export interface BasicActions {
cancelAgent: () => Promise<void>; cancelAgent: () => Promise<void>;
/** Get the handler ID for the current agent */ /** Get the handler ID for the current agent */
getHandlerId: () => Promise<string>; getAgentFrameworkId: () => Promise<string>;
/** Get the configuration schema for the current handler */ /** Get the configuration schema for the current handler */
getHandlerConfigSchema: () => Promise<Record<string, unknown>>; getFrameworkConfigSchema: () => Promise<Record<string, unknown>>;
/** Process raw agent data into store format */ /** Process raw agent data into store format */
processAgentData: ( processAgentData: (
@ -188,12 +188,12 @@ export interface PreviewActions {
/** /**
* Generates a preview of prompts for the current agent state * Generates a preview of prompts for the current agent state
* @param inputText Input text to include in the preview * @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 * @returns Promise that resolves when preview is generated and state is updated
*/ */
getPreviewPromptResult: ( getPreviewPromptResult: (
inputText: string, inputText: string,
handlerConfig: AgentPromptDescription['handlerConfig'], agentFrameworkConfig: AgentPromptDescription['agentFrameworkConfig'],
) => Promise< ) => Promise<
{ {
flatPrompts: ModelMessage[]; flatPrompts: ModelMessage[];

View file

@ -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 MonacoEditor from '@monaco-editor/react';
import { Box, styled } from '@mui/material'; import { Box, styled } from '@mui/material';
import Tab from '@mui/material/Tab'; 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 { useTranslation } from 'react-i18next';
import { useShallow } from 'zustand/react/shallow'; 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 { useAgentChatStore } from '../../../Agent/store/agentChatStore/index';
import { PromptConfigForm } from './PromptConfigForm'; import { PromptConfigForm } from './PromptConfigForm';
@ -40,11 +40,11 @@ export const EditView: FC<EditViewProps> = ({
); );
const { const {
loading: handlerConfigLoading, loading: agentFrameworkConfigLoading,
config: handlerConfig, config: agentFrameworkConfig,
schema: handlerSchema, schema: handlerSchema,
handleConfigChange, handleConfigChange,
} = useHandlerConfigManagement({ } = useAgentFrameworkConfigManagement({
agentDefId: agent?.agentDefId, agentDefId: agent?.agentDefId,
agentId: agent?.id, agentId: agent?.id,
}); });
@ -98,7 +98,7 @@ export const EditView: FC<EditViewProps> = ({
); );
const handleFormChange = useDebouncedCallback( const handleFormChange = useDebouncedCallback(
async (updatedConfig: HandlerConfig) => { async (updatedConfig: AgentFrameworkConfig) => {
try { try {
// Ensure the config change is fully persisted before proceeding // Ensure the config change is fully persisted before proceeding
await handleConfigChange(updatedConfig); await handleConfigChange(updatedConfig);
@ -121,7 +121,7 @@ export const EditView: FC<EditViewProps> = ({
const handleEditorChange = useCallback((value: string | undefined) => { const handleEditorChange = useCallback((value: string | undefined) => {
if (!value) return; if (!value) return;
try { try {
const parsedConfig = JSON.parse(value) as HandlerConfig; const parsedConfig = JSON.parse(value) as AgentFrameworkConfig;
void handleFormChange(parsedConfig); void handleFormChange(parsedConfig);
} catch (error) { } catch (error) {
void window.service.native.log('error', 'EditView: Invalid JSON in code editor:', { error }); void window.service.native.log('error', 'EditView: Invalid JSON in code editor:', { error });
@ -163,16 +163,16 @@ export const EditView: FC<EditViewProps> = ({
{editorMode === 'form' && ( {editorMode === 'form' && (
<PromptConfigForm <PromptConfigForm
schema={handlerSchema ?? {}} schema={handlerSchema ?? {}}
formData={handlerConfig} formData={agentFrameworkConfig}
onChange={handleFormChange} onChange={handleFormChange}
loading={handlerConfigLoading} loading={agentFrameworkConfigLoading}
/> />
)} )}
{editorMode === 'code' && ( {editorMode === 'code' && (
<MonacoEditor <MonacoEditor
height='100%' height='100%'
defaultLanguage='json' defaultLanguage='json'
value={handlerConfig ? JSON.stringify(handlerConfig, null, 2) : '{}'} value={agentFrameworkConfig ? JSON.stringify(agentFrameworkConfig, null, 2) : '{}'}
onChange={handleEditorChange} onChange={handleEditorChange}
options={{ options={{
minimap: { enabled: true }, minimap: { enabled: true },

View file

@ -3,7 +3,7 @@ import { IChangeEvent } from '@rjsf/core';
import Form from '@rjsf/mui'; import Form from '@rjsf/mui';
import { ObjectFieldTemplateProps, RJSFSchema, RJSFValidationError } from '@rjsf/utils'; import { ObjectFieldTemplateProps, RJSFSchema, RJSFValidationError } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8'; import validator from '@rjsf/validator-ajv8';
import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema'; import { AgentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ErrorDisplay } from './components/ErrorDisplay'; import { ErrorDisplay } from './components/ErrorDisplay';
@ -37,9 +37,9 @@ interface PromptConfigFormProps {
/** UI schema for layout customization */ /** UI schema for layout customization */
uiSchema?: Record<string, unknown>; uiSchema?: Record<string, unknown>;
/** Initial form data */ /** Initial form data */
formData?: HandlerConfig; formData?: AgentFrameworkConfig;
/** Change handler for form data */ /** Change handler for form data */
onChange?: (formData: HandlerConfig) => void; onChange?: (formData: AgentFrameworkConfig) => void;
/** Error handler for form validation errors */ /** Error handler for form validation errors */
onError?: (errors: RJSFValidationError[]) => void; onError?: (errors: RJSFValidationError[]) => void;
/** Whether the form is disabled */ /** Whether the form is disabled */
@ -87,7 +87,7 @@ export const PromptConfigForm: React.FC<PromptConfigFormProps> = ({
onError?.(errors); onError?.(errors);
}, [onError]); }, [onError]);
const handleChange = useCallback((changeEvent: IChangeEvent<HandlerConfig>) => { const handleChange = useCallback((changeEvent: IChangeEvent<AgentFrameworkConfig>) => {
const formData = changeEvent.formData; const formData = changeEvent.formData;
if (formData) { if (formData) {
onChange?.(formData); onChange?.(formData);

View file

@ -9,16 +9,16 @@ import { ThemeProvider } from '@mui/material/styles';
import { lightTheme } from '@services/theme/defaultTheme'; import { lightTheme } from '@services/theme/defaultTheme';
import { useAgentChatStore } from '@/pages/Agent/store/agentChatStore/index'; 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 { IPrompt } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { ModelMessage } from 'ai'; import { ModelMessage } from 'ai';
import { PromptPreviewDialog } from '../index'; import { PromptPreviewDialog } from '../index';
// Mock handler config management hook // Mock handler config management hook
vi.mock('@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement', () => ({ vi.mock('@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement', () => ({
useHandlerConfigManagement: vi.fn(() => ({ useAgentFrameworkConfigManagement: vi.fn(() => ({
loading: false, loading: false,
config: defaultAgents[0].handlerConfig, config: defaultAgents[0].agentFrameworkConfig,
handleConfigChange: vi.fn(), handleConfigChange: vi.fn(),
})), })),
})); }));
@ -36,7 +36,7 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
useAgentChatStore.setState({ useAgentChatStore.setState({
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'example-agent', agentDefId: 'task-agent',
status: { state: 'working', modified: new Date() }, status: { state: 'working', modified: new Date() },
created: new Date(), created: new Date(),
}, },
@ -63,8 +63,8 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
// Test if the real concatPrompt is working // Test if the real concatPrompt is working
expect(globalThis.window?.observables?.agentInstance?.concatPrompt).toBeDefined(); expect(globalThis.window?.observables?.agentInstance?.concatPrompt).toBeDefined();
// Create test data matching defaultAgents.json - cast to avoid type issues in test // Create test data matching taskAgents.json - cast to avoid type issues in test
const handlerConfig = defaultAgents[0].handlerConfig as never; const agentFrameworkConfig = defaultAgents[0].agentFrameworkConfig as never;
const messages = [{ const messages = [{
id: 'test-message-1', id: 'test-message-1',
agentId: 'test-agent', agentId: 'test-agent',
@ -74,7 +74,7 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
// Call the real concatPrompt implementation // Call the real concatPrompt implementation
const observable = globalThis.window.observables.agentInstance.concatPrompt( const observable = globalThis.window.observables.agentInstance.concatPrompt(
{ handlerConfig }, { agentFrameworkConfig },
messages, messages,
); );
@ -118,11 +118,11 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
it('should render workspaces and tools info from real concatPrompt execution', async () => { it('should render workspaces and tools info from real concatPrompt execution', async () => {
// First execute real concatPrompt to get the structured data // 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' }]; 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) // Pass agentFrameworkConfig wrapped (same shape used elsewhere)
const observable = window.observables.agentInstance.concatPrompt({ handlerConfig } as never, messages); const observable = window.observables.agentInstance.concatPrompt({ agentFrameworkConfig } as never, messages);
const results: unknown[] = []; const results: unknown[] = [];
let finalResult: { flatPrompts: ModelMessage[]; processedPrompts: IPrompt[] } | undefined; let finalResult: { flatPrompts: ModelMessage[]; processedPrompts: IPrompt[] } | undefined;

View file

@ -9,14 +9,14 @@ import { ThemeProvider } from '@mui/material/styles';
import { lightTheme } from '@services/theme/defaultTheme'; import { lightTheme } from '@services/theme/defaultTheme';
import { useAgentChatStore } from '@/pages/Agent/store/agentChatStore/index'; 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'; import { PromptPreviewDialog } from '../index';
// Mock handler config management hook // Mock handler config management hook
vi.mock('@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement', () => ({ vi.mock('@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement', () => ({
useHandlerConfigManagement: vi.fn(() => ({ useAgentFrameworkConfigManagement: vi.fn(() => ({
loading: false, loading: false,
config: defaultAgents[0].handlerConfig, config: defaultAgents[0].agentFrameworkConfig,
handleConfigChange: vi.fn(), handleConfigChange: vi.fn(),
})), })),
})); }));

View file

@ -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 CloseIcon from '@mui/icons-material/Close';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import FullscreenIcon from '@mui/icons-material/Fullscreen'; import FullscreenIcon from '@mui/icons-material/Fullscreen';
@ -36,9 +36,9 @@ export const PromptPreviewDialog: React.FC<PromptPreviewDialogProps> = ({
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const { const {
loading: handlerConfigLoading, loading: agentFrameworkConfigLoading,
config: handlerConfig, config: agentFrameworkConfig,
} = useHandlerConfigManagement({ } = useAgentFrameworkConfigManagement({
agentDefId: agent?.agentDefId, agentDefId: agent?.agentDefId,
agentId: agent?.id, agentId: agent?.id,
}); });
@ -54,17 +54,17 @@ export const PromptPreviewDialog: React.FC<PromptPreviewDialogProps> = ({
); );
useEffect(() => { useEffect(() => {
const fetchInitialPreview = async () => { const fetchInitialPreview = async () => {
if (!agent?.agentDefId || handlerConfigLoading || !handlerConfig || !open) { if (!agent?.agentDefId || agentFrameworkConfigLoading || !agentFrameworkConfig || !open) {
return; return;
} }
try { try {
await getPreviewPromptResult(inputText, handlerConfig); await getPreviewPromptResult(inputText, agentFrameworkConfig);
} catch (error) { } catch (error) {
console.error('PromptPreviewDialog: Error fetching initial preview:', error); console.error('PromptPreviewDialog: Error fetching initial preview:', error);
} }
}; };
void fetchInitialPreview(); void fetchInitialPreview();
}, [agent?.agentDefId, handlerConfig, handlerConfigLoading, inputText, open]); // 移除 getPreviewPromptResult }, [agent?.agentDefId, agentFrameworkConfig, agentFrameworkConfigLoading, inputText, open]); // 移除 getPreviewPromptResult
const handleToggleFullScreen = useCallback((): void => { const handleToggleFullScreen = useCallback((): void => {
setIsFullScreen(previous => !previous); setIsFullScreen(previous => !previous);

View file

@ -1,6 +1,6 @@
import { AgentDefinitionService } from '@services/agentDefinition'; import { AgentDefinitionService } from '@services/agentDefinition';
import { AgentDefinition } from '@services/agentDefinition/interface'; 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 type { IAgentInstanceService } from '@services/agentInstance/interface';
import { container } from '@services/container'; import { container } from '@services/container';
import type { IDatabaseService } from '@services/database/interface'; import type { IDatabaseService } from '@services/database/interface';
@ -90,12 +90,13 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const defs = await freshService.getAgentDefs(); const defs = await freshService.getAgentDefs();
expect(defs.length).toBeGreaterThan(0); 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).toBeDefined();
expect(exampleAgent!.name).toBeDefined(); expect(exampleAgent!.name).toBeDefined();
expect(exampleAgent!.handlerID).toBeDefined(); expect(exampleAgent!.agentFrameworkID).toBeDefined();
expect(exampleAgent!.handlerConfig).toBeDefined(); expect(exampleAgent!.agentFrameworkConfig).toBeDefined();
expect(typeof exampleAgent!.handlerConfig).toBe('object'); expect(typeof exampleAgent!.agentFrameworkConfig).toBe('object');
}); });
it('should return only database data without fallback to defaultAgents', async () => { it('should return only database data without fallback to defaultAgents', async () => {
@ -105,7 +106,7 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity); const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity);
// Save only minimal record (id only) to test new behavior // 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({ await agentDefRepo.save({
id: example.id, id: example.id,
}); });
@ -116,11 +117,11 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
expect(found).toBeDefined(); expect(found).toBeDefined();
// With new behavior, only id should be present, other fields should be undefined or empty // With new behavior, only id should be present, other fields should be undefined or empty
expect(found!.id).toBe(example.id); expect(found!.id).toBe(example.id);
expect(found!.handlerID).toBeUndefined(); expect(found!.agentFrameworkID).toBeUndefined();
expect(found!.name).toBeUndefined(); expect(found!.name).toBeUndefined();
expect(found!.description).toBeUndefined(); expect(found!.description).toBeUndefined();
expect(found!.avatarUrl).toBeUndefined(); expect(found!.avatarUrl).toBeUndefined();
expect(found!.handlerConfig).toEqual({}); expect(found!.agentFrameworkConfig).toEqual({});
expect(found!.aiApiConfig).toBeUndefined(); expect(found!.aiApiConfig).toBeUndefined();
expect(found!.agentTools).toBeUndefined(); expect(found!.agentTools).toBeUndefined();
}); });
@ -132,7 +133,7 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity); const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity);
// Save only minimal record (id only) as per new behavior // 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({ await agentDefRepo.save({
id: example.id, id: example.id,
}); });
@ -148,8 +149,8 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
expect(entity!.name).toBeNull(); expect(entity!.name).toBeNull();
expect(entity!.description).toBeNull(); expect(entity!.description).toBeNull();
expect(entity!.avatarUrl).toBeNull(); expect(entity!.avatarUrl).toBeNull();
expect(entity!.handlerID).toBeNull(); expect(entity!.agentFrameworkID).toBeNull();
expect(entity!.handlerConfig).toBeNull(); expect(entity!.agentFrameworkConfig).toBeNull();
expect(entity!.aiApiConfig).toBeNull(); expect(entity!.aiApiConfig).toBeNull();
expect(entity!.agentTools).toBeNull(); expect(entity!.agentTools).toBeNull();
}); });
@ -158,15 +159,15 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const templates = await agentDefinitionService.getAgentTemplates(); const templates = await agentDefinitionService.getAgentTemplates();
// Should include all default agents // 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 // Check that template has complete data from taskAgents.json
const exampleTemplate = templates.find(t => t.id === (defaultAgents as AgentDefinition[])[0].id); const exampleTemplate = templates.find(t => t.id === (defaultAgents as unknown as AgentDefinition[])[0].id);
expect(exampleTemplate).toBeDefined(); expect(exampleTemplate).toBeDefined();
expect(exampleTemplate!.name).toBeDefined(); expect(exampleTemplate!.name).toBeDefined();
expect(exampleTemplate!.handlerID).toBeDefined(); expect(exampleTemplate!.agentFrameworkID).toBeDefined();
expect(exampleTemplate!.handlerConfig).toBeDefined(); expect(exampleTemplate!.agentFrameworkConfig).toBeDefined();
expect(typeof exampleTemplate!.handlerConfig).toBe('object'); expect(typeof exampleTemplate!.agentFrameworkConfig).toBe('object');
}); });
it('should not throw when searchName filtering is requested (client-side filtering expected)', async () => { 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 // Should still return default agents and not throw
const templates = await agentDefinitionService.getAgentTemplates(); const templates = await agentDefinitionService.getAgentTemplates();
expect(templates.length).toBe((defaultAgents as AgentDefinition[]).length); expect(templates.length).toBe((defaultAgents as unknown as AgentDefinition[]).length);
}); });
}); });

View file

@ -116,21 +116,21 @@ export function validateAndConvertWikiTiddlerToAgentTemplate(
}; };
// Try to parse the tiddler text as JSON for agent configuration // Try to parse the tiddler text as JSON for agent configuration
let handlerConfig: Record<string, unknown>; let agentFrameworkConfig: Record<string, unknown>;
try { try {
const textContent = typeof tiddler.text === 'string' ? tiddler.text : JSON.stringify(tiddler.text || '{}'); const textContent = typeof tiddler.text === 'string' ? tiddler.text : JSON.stringify(tiddler.text || '{}');
const parsed = JSON.parse(textContent) as unknown; 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)) { if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
logger.warn('Invalid handlerConfig in tiddler', { logger.warn('Invalid agentFrameworkConfig in tiddler', {
function: 'validateAndConvertWikiTiddlerToAgentTemplate', function: 'validateAndConvertWikiTiddlerToAgentTemplate',
title: getStringField(tiddler.title), title: getStringField(tiddler.title),
reason: 'not an object', reason: 'not an object',
}); });
return null; return null;
} }
handlerConfig = parsed as Record<string, unknown>; agentFrameworkConfig = parsed as Record<string, unknown>;
} catch (parseError) { } catch (parseError) {
logger.warn('Failed to parse agent template from tiddler', { logger.warn('Failed to parse agent template from tiddler', {
function: 'validateAndConvertWikiTiddlerToAgentTemplate', function: 'validateAndConvertWikiTiddlerToAgentTemplate',
@ -146,8 +146,8 @@ export function validateAndConvertWikiTiddlerToAgentTemplate(
name: getStringField(tiddler.caption) || getStringField(tiddler.title), name: getStringField(tiddler.caption) || getStringField(tiddler.title),
description: getStringField(tiddler.description) || `Agent template from ${workspaceName || 'wiki'}`, description: getStringField(tiddler.description) || `Agent template from ${workspaceName || 'wiki'}`,
avatarUrl: getStringField(tiddler.avatar_url) || undefined, avatarUrl: getStringField(tiddler.avatar_url) || undefined,
handlerID: getStringField(tiddler.handler_id) || 'basicPromptConcatHandler', agentFrameworkID: getStringField(tiddler.agentFrameworkID) || 'basicPromptConcatHandler',
handlerConfig, agentFrameworkConfig,
aiApiConfig: parseAiApiConfig(tiddler.ai_api_config), aiApiConfig: parseAiApiConfig(tiddler.ai_api_config),
agentTools: parseAgentTools(tiddler.agent_tools), agentTools: parseAgentTools(tiddler.agent_tools),
}; };

View file

@ -4,7 +4,7 @@ import { nanoid } from 'nanoid';
import { DataSource, Repository } from 'typeorm'; import { DataSource, Repository } from 'typeorm';
import type { IAgentBrowserService } from '@services/agentBrowser/interface'; 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 type { IAgentInstanceService } from '@services/agentInstance/interface';
import { container } from '@services/container'; import { container } from '@services/container';
import type { IDatabaseService } from '@services/database/interface'; import type { IDatabaseService } from '@services/database/interface';
@ -71,15 +71,15 @@ export class AgentDefinitionService implements IAgentDefinitionService {
if (existingCount === 0) { if (existingCount === 0) {
logger.info('Agent database is empty, initializing with default agents'); logger.info('Agent database is empty, initializing with default agents');
const defaultAgentsList = defaultAgents as AgentDefinition[]; 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 => const agentDefinitionEntities = defaultAgentsList.map(defaultAgent =>
this.agentDefRepository!.create({ this.agentDefRepository!.create({
id: defaultAgent.id, id: defaultAgent.id,
name: defaultAgent.name, name: defaultAgent.name,
description: defaultAgent.description, description: defaultAgent.description,
avatarUrl: defaultAgent.avatarUrl, avatarUrl: defaultAgent.avatarUrl,
handlerID: defaultAgent.handlerID, agentFrameworkID: defaultAgent.agentFrameworkID,
handlerConfig: defaultAgent.handlerConfig, agentFrameworkConfig: defaultAgent.agentFrameworkConfig,
aiApiConfig: defaultAgent.aiApiConfig, aiApiConfig: defaultAgent.aiApiConfig,
agentTools: defaultAgent.agentTools, agentTools: defaultAgent.agentTools,
}) })
@ -143,7 +143,7 @@ export class AgentDefinitionService implements IAgentDefinitionService {
throw new Error(`Agent definition not found: ${agent.id}`); 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); Object.assign(existingAgent, pickedProperties);
await this.agentDefRepository!.save(existingAgent); await this.agentDefRepository!.save(existingAgent);
@ -171,8 +171,8 @@ export class AgentDefinitionService implements IAgentDefinitionService {
name: entity.name || undefined, name: entity.name || undefined,
description: entity.description || undefined, description: entity.description || undefined,
avatarUrl: entity.avatarUrl || undefined, avatarUrl: entity.avatarUrl || undefined,
handlerID: entity.handlerID || undefined, agentFrameworkID: entity.agentFrameworkID || undefined,
handlerConfig: entity.handlerConfig || {}, agentFrameworkConfig: entity.agentFrameworkConfig || {},
aiApiConfig: entity.aiApiConfig || undefined, aiApiConfig: entity.aiApiConfig || undefined,
agentTools: entity.agentTools || undefined, agentTools: entity.agentTools || undefined,
})); }));
@ -212,8 +212,8 @@ export class AgentDefinitionService implements IAgentDefinitionService {
name: entity.name || undefined, name: entity.name || undefined,
description: entity.description || undefined, description: entity.description || undefined,
avatarUrl: entity.avatarUrl || undefined, avatarUrl: entity.avatarUrl || undefined,
handlerID: entity.handlerID || undefined, agentFrameworkID: entity.agentFrameworkID || undefined,
handlerConfig: entity.handlerConfig || {}, agentFrameworkConfig: entity.agentFrameworkConfig || {},
aiApiConfig: entity.aiApiConfig || undefined, aiApiConfig: entity.aiApiConfig || undefined,
agentTools: entity.agentTools || undefined, agentTools: entity.agentTools || undefined,
}; };

View file

@ -42,10 +42,10 @@ export interface AgentDefinition {
description?: string; description?: string;
/** Agent icon or avatar URL */ /** Agent icon or avatar URL */
avatarUrl?: string; avatarUrl?: string;
/** Agent handler function's id, we will find function by this id */ /** Agent framework function's id, we will find function by this id */
handlerID?: string; agentFrameworkID?: string;
/** Agent handler's config, specific to the handler. This is required to ensure agent has valid configuration. */ /** Agent framework's config, specific to the framework. This is required to ensure agent has valid configuration. */
handlerConfig: Record<string, unknown>; agentFrameworkConfig: Record<string, unknown>;
/** /**
* Overwrite the default AI configuration for this agent. * Overwrite the default AI configuration for this agent.
* Priority is higher than the global default agent config. * Priority is higher than the global default agent config.

View file

@ -6,7 +6,7 @@ import type { IExternalAPIService } from '@services/externalAPI/interface';
import serviceIdentifier from '@services/serviceIdentifier'; import serviceIdentifier from '@services/serviceIdentifier';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AgentInstanceService } from '..'; import { AgentInstanceService } from '..';
import { basicPromptConcatHandler } from '../buildInAgentHandlers/basicPromptConcatHandler'; import { basicPromptConcatHandler } from '../agentFrameworks/taskAgent';
import type { AgentInstanceMessage, IAgentInstanceService } from '../interface'; import type { AgentInstanceMessage, IAgentInstanceService } from '../interface';
import type { AiAPIConfig } from '../promptConcat/promptConcatSchema'; import type { AiAPIConfig } from '../promptConcat/promptConcatSchema';
@ -78,7 +78,7 @@ describe('AgentInstance failure path - external API logs on error', () => {
agentDef: { agentDef: {
id: 'def-1', id: 'def-1',
name: 'Def 1', name: 'Def 1',
handlerConfig: {}, agentFrameworkConfig: {},
aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } }, aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } },
}, },
isCancelled: () => false, isCancelled: () => false,

View file

@ -12,7 +12,7 @@ import { container } from '@services/container';
import type { IDatabaseService } from '@services/database/interface'; import type { IDatabaseService } from '@services/database/interface';
import type { IExternalAPIService } from '@services/externalAPI/interface'; import type { IExternalAPIService } from '@services/externalAPI/interface';
import serviceIdentifier from '@services/serviceIdentifier'; import serviceIdentifier from '@services/serviceIdentifier';
import defaultAgents from '../buildInAgentHandlers/defaultAgents.json'; import defaultAgents from '../agentFrameworks/taskAgents.json';
describe('AgentInstanceService Streaming Behavior', () => { describe('AgentInstanceService Streaming Behavior', () => {
let agentInstanceService: IAgentInstanceService; let agentInstanceService: IAgentInstanceService;
@ -55,7 +55,7 @@ describe('AgentInstanceService Streaming Behavior', () => {
agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance); agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
await agentInstanceService.initialize(); 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]; const exampleAgent = defaultAgents[0];
testAgentInstance = { testAgentInstance = {
id: nanoid(), id: nanoid(),
@ -73,7 +73,7 @@ describe('AgentInstanceService Streaming Behavior', () => {
// Mock agent definition service to return our test agent definition // Mock agent definition service to return our test agent definition
mockAgentDefinitionService.getAgentDef = vi.fn().mockResolvedValue({ mockAgentDefinitionService.getAgentDef = vi.fn().mockResolvedValue({
...exampleAgent, ...exampleAgent,
handlerID: 'basicPromptConcatHandler', agentFrameworkID: 'basicPromptConcatHandler',
}); });
// Mock the getAgent method to return our test instance // Mock the getAgent method to return our test instance
vi.spyOn(agentInstanceService, 'getAgent').mockResolvedValue(testAgentInstance); vi.spyOn(agentInstanceService, 'getAgent').mockResolvedValue(testAgentInstance);

View file

@ -7,7 +7,7 @@ import serviceIdentifier from '@services/serviceIdentifier';
import type { IWikiService } from '@services/wiki/interface'; import type { IWikiService } from '@services/wiki/interface';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 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 // Follow structure of index.streaming.test.ts
describe('AgentInstanceService Wiki Operation', () => { describe('AgentInstanceService Wiki Operation', () => {
@ -26,9 +26,9 @@ describe('AgentInstanceService Wiki Operation', () => {
agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance); agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
// Initialize both database repositories and handlers // 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]; const exampleAgent = defaultAgents[0];
testAgentInstance = { testAgentInstance = {
id: nanoid(), id: nanoid(),

View file

@ -2,11 +2,11 @@ import { describe, expect, it } from 'vitest';
import { createAgentInstanceData } from '../utilities'; import { createAgentInstanceData } from '../utilities';
describe('createAgentInstanceData', () => { 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 = { const agentDefinition = {
id: 'test-agent-def', id: 'test-agent-def',
name: 'Test Agent', name: 'Test Agent',
handlerConfig: { agentFrameworkConfig: {
prompts: [ prompts: [
{ {
text: 'You are a helpful assistant.', text: 'You are a helpful assistant.',
@ -14,29 +14,29 @@ describe('createAgentInstanceData', () => {
}, },
], ],
}, },
handlerID: 'basicPromptConcatHandler', agentFrameworkID: 'basicPromptConcatHandler',
}; };
const { instanceData } = createAgentInstanceData(agentDefinition); const { instanceData } = createAgentInstanceData(agentDefinition);
expect(instanceData.handlerConfig).toBeUndefined(); expect(instanceData.agentFrameworkConfig).toBeUndefined();
expect(instanceData.agentDefId).toBe('test-agent-def'); expect(instanceData.agentDefId).toBe('test-agent-def');
expect(instanceData.handlerID).toBe('basicPromptConcatHandler'); expect(instanceData.agentFrameworkID).toBe('basicPromptConcatHandler');
expect(instanceData.name).toContain('Test Agent'); 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 = { const agentDefinition = {
id: 'test-agent-def-no-config', id: 'test-agent-def-no-config',
name: 'Test Agent No Config', name: 'Test Agent No Config',
handlerID: 'basicPromptConcatHandler', agentFrameworkID: 'basicPromptConcatHandler',
handlerConfig: {}, // Required by AgentDefinition interface agentFrameworkConfig: {}, // Required by AgentDefinition interface
}; };
const { instanceData } = createAgentInstanceData(agentDefinition); const { instanceData } = createAgentInstanceData(agentDefinition);
expect(instanceData.handlerConfig).toBeUndefined(); expect(instanceData.agentFrameworkConfig).toBeUndefined();
expect(instanceData.agentDefId).toBe('test-agent-def-no-config'); expect(instanceData.agentDefId).toBe('test-agent-def-no-config');
expect(instanceData.handlerID).toBe('basicPromptConcatHandler'); expect(instanceData.agentFrameworkID).toBe('basicPromptConcatHandler');
}); });
}); });

View file

@ -8,8 +8,8 @@ import serviceIdentifier from '@services/serviceIdentifier';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { AgentInstanceMessage, IAgentInstanceService } from '../../interface'; import type { AgentInstanceMessage, IAgentInstanceService } from '../../interface';
import type { AiAPIConfig } from '../../promptConcat/promptConcatSchema'; import type { AiAPIConfig } from '../../promptConcat/promptConcatSchema';
import { basicPromptConcatHandler } from '../basicPromptConcatHandler'; import { basicPromptConcatHandler } from '../taskAgent';
import type { AgentHandlerContext } from '../type'; import type { AgentFrameworkContext } from '../utilities/type';
// Use real normalizeRole implementation — do not mock plugins or persistence in these integration tests // 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', 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 { return {
agent: { agent: {
id: agentId, id: agentId,
@ -33,11 +33,11 @@ function makeContext(agentId: string, agentDefId: string, messages: AgentInstanc
agentDef: { agentDef: {
id: agentDefId, id: agentDefId,
name: 'Test Agent', name: 'Test Agent',
handlerConfig: {}, agentFrameworkConfig: {},
aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } } as AiAPIConfig, aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } } as AiAPIConfig,
}, },
isCancelled: () => false, isCancelled: () => false,
} as unknown as AgentHandlerContext; } as unknown as AgentFrameworkContext;
} }
describe('basicPromptConcatHandler - failure path persists error message and logs', () => { 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({ vi.spyOn(agentDefSvc, 'getAgentDef').mockResolvedValue({
id: 'def-1', id: 'def-1',
name: 'Def 1', name: 'Def 1',
handlerID: 'basicPromptConcatHandler', agentFrameworkID: 'basicPromptConcatHandler',
handlerConfig: { agentFrameworkConfig: {
plugins: [ plugins: [
{ pluginId: 'wikiOperation', wikiOperationParam: {} }, { toolId: 'wikiOperation', wikiOperationParam: {} },
], ],
}, },
}); });

View file

@ -18,7 +18,7 @@ import { WikiChannel } from '@/constants/channels';
// types are provided by shared mock; no local type assertions needed // types are provided by shared mock; no local type assertions needed
// Import defaultAgents configuration // Import defaultAgents configuration
import defaultAgents from '../defaultAgents.json'; import defaultAgents from '../taskAgents.json';
// Configurable test hooks for mocks // Configurable test hooks for mocks
let testWikiImplementation: ((channel: WikiChannel, workspaceId?: string, args?: string[]) => Promise<unknown>) | undefined; let testWikiImplementation: ((channel: WikiChannel, workspaceId?: string, args?: string[]) => Promise<unknown>) | undefined;
@ -27,12 +27,12 @@ let testStreamResponses: Array<{ status: string; content: string; requestId: str
// Use real AgentInstanceService in tests; do not mock // Use real AgentInstanceService in tests; do not mock
// Import plugin components for direct testing // 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 type { IDatabaseService } from '@services/database/interface';
import { createHandlerHooks, createHooksWithPlugins, initializePluginSystem, PromptConcatHookContext } from '../../plugins/index'; import { createAgentFrameworkHooks, createHooksWithTools, initializeToolSystem, PromptConcatHookContext } from '../../tools/index';
import { wikiSearchPlugin } from '../../plugins/wikiSearchPlugin'; import { wikiSearchTool } from '../../tools/wikiSearch';
import { basicPromptConcatHandler } from '../basicPromptConcatHandler'; import { basicPromptConcatHandler } from '../taskAgent';
import type { AgentHandlerContext } from '../type'; import type { AgentFrameworkContext } from '../utilities/type';
describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => { describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
beforeEach(async () => { beforeEach(async () => {
@ -41,8 +41,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
testStreamResponses = []; testStreamResponses = [];
const { container } = await import('@services/container'); const { container } = await import('@services/container');
// Ensure built-in plugin registry includes all built-in plugins // Ensure built-in tool registry includes all built-in tools
await initializePluginSystem(); await initializeToolSystem();
// Prepare a mock DataSource/repository so AgentInstanceService.initialize() can run // Prepare a mock DataSource/repository so AgentInstanceService.initialize() can run
const mockRepo = { const mockRepo = {
@ -88,32 +88,32 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
describe('Complete Workflow Integration', () => { describe('Complete Workflow Integration', () => {
it('should complete full wiki search workflow: tool list -> tool execution -> response', async () => { 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 exampleAgent = defaultAgents[0];
const handlerConfig = exampleAgent.handlerConfig; const agentFrameworkConfig = exampleAgent.agentFrameworkConfig;
// Get the wiki search plugin configuration // Get the wiki search tool configuration
const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch'); const wikiPlugin = agentFrameworkConfig.plugins.find(p => p.toolId === 'wikiSearch');
expect(wikiPlugin).toBeDefined(); expect(wikiPlugin).toBeDefined();
if (!wikiPlugin) throw new Error('wikiPlugin not found'); 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 // Phase 1: Tool List Injection
const promptConcatHookContext: PromptConcatHookContext = { const promptConcatHookContext: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
messages: [], messages: [],
agentDefId: exampleAgent.id, agentDefId: exampleAgent.id,
status: { state: 'working' as const, modified: new Date() }, status: { state: 'working' as const, modified: new Date() },
created: 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, isCancelled: () => false,
}, },
pluginConfig: wikiPlugin as IPromptConcatPlugin, toolConfig: wikiPlugin as IPromptConcatTool,
prompts, prompts,
messages: [ messages: [
{ {
@ -127,12 +127,12 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
], ],
}; };
// Create hooks and register plugins as defined in handlerConfig // Create hooks and register tools as defined in agentFrameworkConfig
const { hooks: promptHooks } = await createHooksWithPlugins(handlerConfig); const { hooks: promptHooks } = await createHooksWithTools(agentFrameworkConfig);
// First run workspacesList plugin to inject available workspaces (if present) // First run workspacesList tool to inject available workspaces (if present)
const workspacesPlugin = handlerConfig.plugins?.find(p => p.pluginId === 'workspacesList'); const workspacesPlugin = agentFrameworkConfig.plugins?.find(p => p.toolId === 'workspacesList');
if (workspacesPlugin) { 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); await promptHooks.processPrompts.promise(workspacesContext);
} }
// Then run wikiSearch plugin to inject the tool list // Then run wikiSearch plugin to inject the tool list
@ -169,7 +169,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
}; };
const responseContext = { const responseContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -179,9 +179,9 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
}, },
created: new Date(), created: new Date(),
messages: [], 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, isCancelled: () => false,
}, },
response: { response: {
@ -191,7 +191,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
}, },
requestId: 'test-request', requestId: 'test-request',
isFinal: true, isFinal: true,
pluginConfig: wikiPlugin as IPromptConcatPlugin, toolConfig: wikiPlugin as IPromptConcatTool,
prompts: [], prompts: [],
messages: [], messages: [],
llmResponse: 'I will search for important content using wiki-search tool.', 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<string, unknown>, actions: {} as unknown as Record<string, unknown>,
}; };
// Use hooks registered with all plugins from handlerConfig // Use hooks registered with all plugins import { AgentFrameworkConfig }
const { hooks: responseHooks } = await createHooksWithPlugins(handlerConfig); const { hooks: responseHooks } = await createHooksWithTools(agentFrameworkConfig);
// Execute the response complete hook // Execute the response complete hook
await responseHooks.responseComplete.promise(responseContext); await responseHooks.responseComplete.promise(responseContext);
// reuse containerForAssert from above assertions // reuse containerForAssert from above assertions
@ -213,8 +213,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
expect(responseContext.actions.yieldNextRoundTo).toBe('self'); expect(responseContext.actions.yieldNextRoundTo).toBe('self');
// Verify tool result message was added to agent history // Verify tool result message was added to agent history
expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0); expect(responseContext.agentFrameworkContext.agent.messages.length).toBeGreaterThan(0);
const toolResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage; const toolResultMessage = responseContext.agentFrameworkContext.agent.messages[responseContext.agentFrameworkContext.agent.messages.length - 1] as AgentInstanceMessage;
expect(toolResultMessage.role).toBe('tool'); // Tool result message expect(toolResultMessage.role).toBe('tool'); // Tool result message
expect(toolResultMessage.content).toContain('<functions_result>'); expect(toolResultMessage.content).toContain('<functions_result>');
expect(toolResultMessage.content).toContain('Tool: wiki-search'); 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 () => { 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 exampleAgent = defaultAgents[0];
const handlerConfig = exampleAgent.handlerConfig; const agentFrameworkConfig = exampleAgent.agentFrameworkConfig;
// Get the wiki search plugin configuration // Get the wiki search tool configuration
const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch'); const wikiPlugin = agentFrameworkConfig.plugins.find(p => p.toolId === 'wikiSearch');
expect(wikiPlugin).toBeDefined(); expect(wikiPlugin).toBeDefined();
// Mock tool calling with invalid workspace // Mock tool calling with invalid workspace
const responseContext = { const responseContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -243,9 +243,9 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
}, },
created: new Date(), created: new Date(),
messages: [], 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, isCancelled: () => false,
}, },
response: { response: {
@ -255,7 +255,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
}, },
requestId: 'test-request', requestId: 'test-request',
isFinal: true, isFinal: true,
pluginConfig: wikiPlugin as IPromptConcatPlugin, toolConfig: wikiPlugin as IPromptConcatTool,
prompts: [], prompts: [],
messages: [], messages: [],
llmResponse: 'Search in nonexistent wiki', llmResponse: 'Search in nonexistent wiki',
@ -264,10 +264,10 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
}; };
// Use real handler hooks // Use real handler hooks
const responseHooks = createHandlerHooks(); const responseHooks = createAgentFrameworkHooks();
// Register the plugin // Register the plugin
wikiSearchPlugin(responseHooks); wikiSearchTool(responseHooks);
// Execute the response complete hook // Execute the response complete hook
await responseHooks.responseComplete.promise(responseContext); await responseHooks.responseComplete.promise(responseContext);
@ -276,8 +276,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
expect(responseContext.actions.yieldNextRoundTo).toBe('self'); expect(responseContext.actions.yieldNextRoundTo).toBe('self');
// Verify error message was added to agent history // Verify error message was added to agent history
expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0); expect(responseContext.agentFrameworkContext.agent.messages.length).toBeGreaterThan(0);
const errorResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage; const errorResultMessage = responseContext.agentFrameworkContext.agent.messages[responseContext.agentFrameworkContext.agent.messages.length - 1] as AgentInstanceMessage;
expect(errorResultMessage.role).toBe('tool'); // Tool error message expect(errorResultMessage.role).toBe('tool'); // Tool error message
// The error should be indicated in the message content // The error should be indicated in the message content
@ -295,7 +295,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
const exampleAgent = defaultAgents[0]; const exampleAgent = defaultAgents[0];
const testAgentId = `test-agent-${Date.now()}`; const testAgentId = `test-agent-${Date.now()}`;
const context: AgentHandlerContext = { const context: AgentFrameworkContext = {
agent: { agent: {
id: testAgentId, id: testAgentId,
agentDefId: exampleAgent.id, agentDefId: exampleAgent.id,
@ -315,7 +315,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
agentDef: { agentDef: {
id: exampleAgent.id, id: exampleAgent.id,
name: exampleAgent.name, name: exampleAgent.name,
handlerConfig: exampleAgent.handlerConfig, agentFrameworkConfig: exampleAgent.agentFrameworkConfig,
}, },
isCancelled: () => false, isCancelled: () => false,
}; };

View file

@ -4,14 +4,14 @@ import { logger } from '@services/libs/log';
import serviceIdentifier from '@services/serviceIdentifier'; import serviceIdentifier from '@services/serviceIdentifier';
import { merge } from 'lodash'; import { merge } from 'lodash';
import type { AgentInstanceLatestStatus, AgentInstanceMessage, IAgentInstanceService } from '../interface'; import type { AgentInstanceLatestStatus, AgentInstanceMessage, IAgentInstanceService } from '../interface';
import { createHooksWithPlugins } from '../plugins'; import { AgentFrameworkConfig, AgentPromptDescription, AiAPIConfig } from '../promptConcat/promptConcatSchema';
import { YieldNextRoundTarget } from '../plugins/types'; import type { IPromptConcatTool } from '../promptConcat/promptConcatSchema/plugin';
import { AgentPromptDescription, AiAPIConfig, HandlerConfig } from '../promptConcat/promptConcatSchema';
import type { IPromptConcatPlugin } from '../promptConcat/promptConcatSchema/plugin';
import { responseConcat } from '../promptConcat/responseConcat'; import { responseConcat } from '../promptConcat/responseConcat';
import { getFinalPromptResult } from '../promptConcat/utilities'; import { getFinalPromptResult } from '../promptConcat/utilities';
import { canceled, completed, error, working } from './statusUtilities'; import { createHooksWithTools } from '../tools';
import { AgentHandlerContext } from './type'; import { YieldNextRoundTarget } from '../tools/types';
import { canceled, completed, error, working } from './utilities/statusUtilities';
import { AgentFrameworkContext } from './utilities/type';
/** /**
* Main conversation orchestrator for AI agents * Main conversation orchestrator for AI agents
@ -27,19 +27,19 @@ import { AgentHandlerContext } from './type';
* *
* @param context - Agent handling context containing configuration and message history * @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 // Initialize variables for request tracking
let currentRequestId: string | undefined; let currentRequestId: string | undefined;
const lastUserMessage: AgentInstanceMessage | undefined = context.agent.messages[context.agent.messages.length - 1]; const lastUserMessage: AgentInstanceMessage | undefined = context.agent.messages[context.agent.messages.length - 1];
// Create and register handler hooks based on handler config // Create and register handler hooks based on framework config
const { hooks: handlerHooks, pluginConfigs } = await createHooksWithPlugins(context.agentDef.handlerConfig || {}); const { hooks: agentFrameworkHooks, toolConfigs } = await createHooksWithTools(context.agentDef.agentFrameworkConfig || {});
// Log the start of handler execution with context information // Log the start of handler execution with context information
logger.debug('Starting prompt handler execution', { logger.debug('Starting prompt handler execution', {
method: 'basicPromptConcatHandler', method: 'basicPromptConcatHandler',
agentId: context.agent.id, agentId: context.agent.id,
defId: context.agentDef.id, defId: context.agentDef.id,
handlerId: context.agentDef.handlerID, agentFrameworkId: context.agentDef.agentFrameworkID,
messageCount: context.agent.messages.length, messageCount: context.agent.messages.length,
}); });
// Check if there's a new user message to process - trigger user message received hook // 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) { if (isNewUserMessage) {
// Trigger user message received hook // Trigger user message received hook
await handlerHooks.userMessageReceived.promise({ await agentFrameworkHooks.userMessageReceived.promise({
handlerContext: context, agentFrameworkContext: context,
content: { content: {
text: lastUserMessage.content, text: lastUserMessage.content,
file: lastUserMessage.metadata?.file as File | undefined, file: lastUserMessage.metadata?.file as File | undefined,
@ -62,8 +62,8 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) {
lastUserMessage.metadata = { ...lastUserMessage.metadata, processed: true }; lastUserMessage.metadata = { ...lastUserMessage.metadata, processed: true };
// Trigger agent status change to working // Trigger agent status change to working
await handlerHooks.agentStatusChanged.promise({ await agentFrameworkHooks.agentStatusChanged.promise({
handlerContext: context, agentFrameworkContext: context,
status: { status: {
state: 'working', state: 'working',
modified: new Date(), modified: new Date(),
@ -94,12 +94,12 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) {
// Process prompts using common handler function // Process prompts using common handler function
try { try {
const handlerConfig: HandlerConfig = context.agentDef.handlerConfig as HandlerConfig; const agentFrameworkConfig = context.agentDef.agentFrameworkConfig as AgentFrameworkConfig;
const agentPromptDescription: AgentPromptDescription = { const agentPromptDescription: AgentPromptDescription = {
id: context.agentDef.id, id: context.agentDef.id,
api: aiApiConfig.api, api: aiApiConfig.api,
modelParameters: aiApiConfig.modelParameters, modelParameters: aiApiConfig.modelParameters,
handlerConfig, agentFrameworkConfig,
}; };
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance); const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
@ -146,12 +146,12 @@ export async function* basicPromptConcatHandler(context: AgentHandlerContext) {
if (response.status === 'update') { if (response.status === 'update') {
// For responseUpdate, we'll skip plugin-specific config for now // For responseUpdate, we'll skip plugin-specific config for now
// since it's called frequently during streaming // since it's called frequently during streaming
await handlerHooks.responseUpdate.promise({ await agentFrameworkHooks.responseUpdate.promise({
handlerContext: context, agentFrameworkContext: context,
response, response,
requestId: currentRequestId, requestId: currentRequestId,
isFinal: false, 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 // Delegate final response processing to handler hooks
const responseCompleteContext = { const responseCompleteContext = {
handlerContext: context, agentFrameworkContext: context,
response, response,
requestId: currentRequestId, requestId: currentRequestId,
isFinal: true, isFinal: true,
pluginConfig: (pluginConfigs.length > 0 ? pluginConfigs[0] : {}) as IPromptConcatPlugin, // First config for compatibility toolConfig: (toolConfigs.length > 0 ? toolConfigs[0] : {}) as IPromptConcatTool, // First config for compatibility
handlerConfig: context.agentDef.handlerConfig, // Pass complete config for plugin access agentFrameworkConfig: context.agentDef.agentFrameworkConfig, // Pass complete config for tool access
actions: undefined as { yieldNextRoundTo?: 'self' | 'human'; newUserMessage?: string } | undefined, 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 // Check if responseComplete hooks set yieldNextRoundTo
let yieldNextRoundFromHooks: YieldNextRoundTarget | undefined; let yieldNextRoundFromHooks: YieldNextRoundTarget | undefined;

View file

@ -1,11 +1,11 @@
[ [
{ {
"id": "example-agent", "id": "task-agent",
"name": "Example Agent", "name": "Task Agent",
"description": "Example agent with prompt processing", "description": "Example agent with prompt processing",
"avatarUrl": "https://example.com/example-agent.png", "avatarUrl": "https://example.com/task-agent.png",
"handlerID": "basicPromptConcatHandler", "agentFrameworkID": "basicPromptConcatHandler",
"handlerConfig": { "agentFrameworkConfig": {
"prompts": [ "prompts": [
{ {
"id": "system", "id": "system",
@ -72,7 +72,7 @@
"plugins": [ "plugins": [
{ {
"id": "efe5be74-540d-487d-8a05-7377e486953d", "id": "efe5be74-540d-487d-8a05-7377e486953d",
"pluginId": "fullReplacement", "toolId": "fullReplacement",
"fullReplacementParam": { "fullReplacementParam": {
"targetId": "default-history", "targetId": "default-history",
"sourceType": "historyOfSession" "sourceType": "historyOfSession"
@ -84,7 +84,7 @@
"id": "f0e1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5", "id": "f0e1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5",
"caption": "Wiki工作空间列表", "caption": "Wiki工作空间列表",
"description": "自动在提示词中注入可用的Wiki工作空间列表", "description": "自动在提示词中注入可用的Wiki工作空间列表",
"pluginId": "workspacesList", "toolId": "workspacesList",
"workspacesListParam": { "workspacesListParam": {
"targetId": "default-before-tool", "targetId": "default-before-tool",
"position": "after" "position": "after"
@ -94,7 +94,7 @@
"id": "d0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5", "id": "d0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5",
"caption": "Wiki搜索和向量索引工具", "caption": "Wiki搜索和向量索引工具",
"description": "提供Wiki搜索filter和vector以及向量嵌入索引管理功能", "description": "提供Wiki搜索filter和vector以及向量嵌入索引管理功能",
"pluginId": "wikiSearch", "toolId": "wikiSearch",
"wikiSearchParam": { "wikiSearchParam": {
"sourceType": "wiki", "sourceType": "wiki",
"toolListPosition": { "toolListPosition": {
@ -107,7 +107,7 @@
"id": "e1f2b3c4-5d6e-7f8g-9h0i-k1l2m3n4o5p6", "id": "e1f2b3c4-5d6e-7f8g-9h0i-k1l2m3n4o5p6",
"caption": "Wiki操作工具", "caption": "Wiki操作工具",
"description": "允许AI在Wiki工作空间中创建、更新和删除笔记", "description": "允许AI在Wiki工作空间中创建、更新和删除笔记",
"pluginId": "wikiOperation", "toolId": "wikiOperation",
"wikiOperationParam": { "wikiOperationParam": {
"toolListPosition": { "toolListPosition": {
"position": "after", "position": "after",
@ -117,7 +117,7 @@
}, },
{ {
"id": "a0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5", "id": "a0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5",
"pluginId": "fullReplacement", "toolId": "fullReplacement",
"fullReplacementParam": { "fullReplacementParam": {
"targetId": "default-response", "targetId": "default-response",
"sourceType": "llmResponse" "sourceType": "llmResponse"

View file

@ -3,8 +3,8 @@
*/ */
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { AgentInstanceLatestStatus } from '../interface'; import { AgentInstanceLatestStatus } from '../../interface';
import { AgentHandlerContext } from './type'; import { AgentFrameworkContext } from './type';
/** /**
* Creates a completed status with error information in message metadata * Creates a completed status with error information in message metadata
@ -22,7 +22,7 @@ export function completedWithError(
provider: string; provider: string;
message?: string; message?: string;
} | undefined, } | undefined,
context: AgentHandlerContext, context: AgentFrameworkContext,
messageId?: string, messageId?: string,
): AgentInstanceLatestStatus { ): AgentInstanceLatestStatus {
return { return {

View file

@ -1,6 +1,6 @@
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { AgentInstanceLatestStatus } from '../interface'; import { AgentInstanceLatestStatus } from '../../interface';
import { AgentHandlerContext } from './type'; import { AgentFrameworkContext } from './type';
/** /**
* Creates a working status with a message * Creates a working status with a message
@ -11,7 +11,7 @@ import { AgentHandlerContext } from './type';
*/ */
export function working( export function working(
content: string, content: string,
context: AgentHandlerContext, context: AgentFrameworkContext,
messageId?: string, messageId?: string,
): AgentInstanceLatestStatus { ): AgentInstanceLatestStatus {
return { return {
@ -34,7 +34,7 @@ export function working(
*/ */
export function completed( export function completed(
content: string, content: string,
context: AgentHandlerContext, context: AgentFrameworkContext,
messageId?: string, messageId?: string,
): AgentInstanceLatestStatus { ): AgentInstanceLatestStatus {
return { return {
@ -72,7 +72,7 @@ export function error(
provider: string; provider: string;
message?: string; message?: string;
} | undefined, } | undefined,
context: AgentHandlerContext, context: AgentFrameworkContext,
messageId?: string, messageId?: string,
): AgentInstanceLatestStatus { ): AgentInstanceLatestStatus {
return { return {

View file

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-invalid-void-type */ /* eslint-disable @typescript-eslint/no-invalid-void-type */
import { AgentDefinition } from '../../agentDefinition/interface'; import { AgentDefinition } from '../../../agentDefinition/interface';
import { AgentInstance, AgentInstanceLatestStatus } from '../interface'; import { AgentInstance, AgentInstanceLatestStatus } from '../../interface';
export interface AgentHandlerContext { export interface AgentFrameworkContext {
agent: AgentInstance; agent: AgentInstance;
agentDef: AgentDefinition; agentDef: AgentDefinition;
@ -28,6 +28,6 @@ export interface AgentHandlerContext {
* (needed for non-streaming 'tasks/send'). If void is returned, the server uses the * (needed for non-streaming 'tasks/send'). If void is returned, the server uses the
* last known state from the store after processing all yields. * last known state from the store after processing all yields.
*/ */
export type AgentHandler = ( export type AgentFramework = (
context: AgentHandlerContext, context: AgentFrameworkContext,
) => AsyncGenerator<AgentInstanceLatestStatus, AgentInstance | undefined | void, unknown>; ) => AsyncGenerator<AgentInstanceLatestStatus, AgentInstance | undefined | void, unknown>;

View file

@ -5,12 +5,12 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { DataSource, Repository } from 'typeorm'; import { DataSource, Repository } from 'typeorm';
import type { IAgentDefinitionService } from '@services/agentDefinition/interface'; import type { IAgentDefinitionService } from '@services/agentDefinition/interface';
import { basicPromptConcatHandler } from '@services/agentInstance/buildInAgentHandlers/basicPromptConcatHandler'; import { basicPromptConcatHandler } from '@services/agentInstance/agentFrameworks/taskAgent';
import type { AgentHandler, AgentHandlerContext } from '@services/agentInstance/buildInAgentHandlers/type'; import type { AgentFramework, AgentFrameworkContext } from '@services/agentInstance/agentFrameworks/utilities/type';
import { createHooksWithPlugins, initializePluginSystem } from '@services/agentInstance/plugins';
import { promptConcatStream, PromptConcatStreamState } from '@services/agentInstance/promptConcat/promptConcat'; import { promptConcatStream, PromptConcatStreamState } from '@services/agentInstance/promptConcat/promptConcat';
import type { AgentPromptDescription } from '@services/agentInstance/promptConcat/promptConcatSchema'; 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 type { IDatabaseService } from '@services/database/interface';
import { AgentInstanceEntity, AgentInstanceMessageEntity } from '@services/database/schema/agent'; import { AgentInstanceEntity, AgentInstanceMessageEntity } from '@services/database/schema/agent';
import { logger } from '@services/libs/log'; import { logger } from '@services/libs/log';
@ -34,15 +34,15 @@ export class AgentInstanceService implements IAgentInstanceService {
private agentInstanceSubjects: Map<string, BehaviorSubject<AgentInstance | undefined>> = new Map(); private agentInstanceSubjects: Map<string, BehaviorSubject<AgentInstance | undefined>> = new Map();
private statusSubjects: Map<string, BehaviorSubject<AgentInstanceLatestStatus | undefined>> = new Map(); private statusSubjects: Map<string, BehaviorSubject<AgentInstanceLatestStatus | undefined>> = new Map();
private agentHandlers: Map<string, AgentHandler> = new Map(); private agentFrameworks: Map<string, AgentFramework> = new Map();
private handlerSchemas: Map<string, Record<string, unknown>> = new Map(); private frameworkSchemas: Map<string, Record<string, unknown>> = new Map();
private cancelTokenMap: Map<string, { value: boolean }> = new Map(); private cancelTokenMap: Map<string, { value: boolean }> = new Map();
private debouncedUpdateFunctions: Map<string, (message: AgentInstanceLatestStatus['message'] & { id: string }, agentId?: string) => void> = new Map(); private debouncedUpdateFunctions: Map<string, (message: AgentInstanceLatestStatus['message'] & { id: string }, agentId?: string) => void> = new Map();
public async initialize(): Promise<void> { public async initialize(): Promise<void> {
try { try {
await this.initializeDatabase(); await this.initializeDatabase();
await this.initializeHandlers(); await this.initializeFrameworks();
} catch (error) { } catch (error) {
logger.error('Failed to initialize agent instance service', { error }); logger.error('Failed to initialize agent instance service', { error });
throw error; throw error;
@ -62,37 +62,37 @@ export class AgentInstanceService implements IAgentInstanceService {
} }
} }
public async initializeHandlers(): Promise<void> { public async initializeFrameworks(): Promise<void> {
try { try {
// Register plugins to global registry once during initialization // Register tools to global registry once during initialization
await initializePluginSystem(); await initializeToolSystem();
logger.debug('AgentInstance Plugin system initialized and plugins registered to global registry'); logger.debug('AgentInstance Tool system initialized and tools registered to global registry');
// Register built-in handlers // Register built-in frameworks
this.registerBuiltinHandlers(); this.registerBuiltinFrameworks();
logger.debug('AgentInstance handlers registered'); logger.debug('AgentInstance frameworks registered');
} catch (error) { } catch (error) {
logger.error('Failed to initialize agent instance handlers', { error }); logger.error('Failed to initialize agent instance frameworks', { error });
throw error; throw error;
} }
} }
public registerBuiltinHandlers(): void { public registerBuiltinFrameworks(): void {
// Plugins are already registered in initialize(), so we only register handlers here // Tools are already registered in initialize(), so we only register frameworks here
// Register basic prompt concatenation handler with its schema // Register basic prompt concatenation framework with its schema
this.registerHandler('basicPromptConcatHandler', basicPromptConcatHandler, getPromptConcatHandlerConfigJsonSchema()); this.registerFramework('basicPromptConcatHandler', basicPromptConcatHandler, getPromptConcatAgentFrameworkConfigJsonSchema());
} }
/** /**
* Register a handler with an optional schema * Register a framework with an optional schema
* @param handlerId ID for the handler * @param frameworkId ID for the framework
* @param handler The handler function * @param framework The framework function
* @param schema Optional JSON schema for the handler configuration * @param schema Optional JSON schema for the framework configuration
*/ */
private registerHandler(handlerId: string, handler: AgentHandler, schema?: Record<string, unknown>): void { private registerFramework(frameworkId: string, framework: AgentFramework, schema?: Record<string, unknown>): void {
this.agentHandlers.set(handlerId, handler); this.agentFrameworks.set(frameworkId, framework);
if (schema) { 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 // 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); Object.assign(instanceEntity, pickedProperties);
// Save instance updates // Save instance updates
@ -353,20 +353,20 @@ export class AgentInstanceService implements IAgentInstanceService {
throw new Error(`Agent definition not found: ${agentInstance.agentDefId}`); throw new Error(`Agent definition not found: ${agentInstance.agentDefId}`);
} }
// Get appropriate handler // Get appropriate framework
const handlerId = agentDefinition.handlerID; const agentFrameworkId = agentDefinition.agentFrameworkID;
if (!handlerId) { if (!agentFrameworkId) {
throw new Error(`Handler ID not found in agent definition: ${agentDefinition.id}`); throw new Error(`Agent framework ID not found in agent definition: ${agentDefinition.id}`);
} }
const handler = this.agentHandlers.get(handlerId); const framework = this.agentFrameworks.get(agentFrameworkId);
if (!handler) { if (!framework) {
throw new Error(`Handler not found: ${handlerId}`); 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 }; const cancelToken = { value: false };
this.cancelTokenMap.set(agentId, cancelToken); this.cancelTokenMap.set(agentId, cancelToken);
const handlerContext: AgentHandlerContext = { const frameworkContext: AgentFrameworkContext = {
agent: { agent: {
...agentInstance, ...agentInstance,
messages: [...agentInstance.messages], messages: [...agentInstance.messages],
@ -379,23 +379,23 @@ export class AgentInstanceService implements IAgentInstanceService {
isCancelled: () => cancelToken.value, isCancelled: () => cancelToken.value,
}; };
// Create fresh hooks for this handler execution and register plugins based on handlerConfig // Create fresh hooks for this framework execution and register tools based on frameworkConfig
const { hooks: handlerHooks } = await createHooksWithPlugins(agentDefinition.handlerConfig || {}); const { hooks: frameworkHooks } = await createHooksWithTools(agentDefinition.agentFrameworkConfig || {});
// Trigger userMessageReceived hook with the configured plugins // Trigger userMessageReceived hook with the configured tools
await handlerHooks.userMessageReceived.promise({ await frameworkHooks.userMessageReceived.promise({
handlerContext, agentFrameworkContext: frameworkContext,
content, content,
messageId, messageId,
timestamp: now, timestamp: now,
}); });
// Notify agent update after user message is added // Notify agent update after user message is added
this.notifyAgentUpdate(agentId, handlerContext.agent); this.notifyAgentUpdate(agentId, frameworkContext.agent);
try { try {
// Create async generator // Create async generator
const generator = handler(handlerContext); const generator = framework(frameworkContext);
// Track the last message for completion handling // Track the last message for completion handling
let lastResult: AgentInstanceLatestStatus | undefined; let lastResult: AgentInstanceLatestStatus | undefined;
@ -415,7 +415,7 @@ export class AgentInstanceService implements IAgentInstanceService {
} }
// Notify agent update with latest messages for real-time UI updates // 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 // Store the last result for completion handling
@ -442,8 +442,8 @@ export class AgentInstanceService implements IAgentInstanceService {
} }
// Trigger agentStatusChanged hook for completion // Trigger agentStatusChanged hook for completion
await handlerHooks.agentStatusChanged.promise({ await frameworkHooks.agentStatusChanged.promise({
handlerContext, agentFrameworkContext: frameworkContext,
status: { status: {
state: 'completed', state: 'completed',
modified: new Date(), modified: new Date(),
@ -458,8 +458,8 @@ export class AgentInstanceService implements IAgentInstanceService {
logger.error(`Agent handler execution failed: ${errorMessage}`); logger.error(`Agent handler execution failed: ${errorMessage}`);
// Trigger agentStatusChanged hook for failure // Trigger agentStatusChanged hook for failure
await handlerHooks.agentStatusChanged.promise({ await frameworkHooks.agentStatusChanged.promise({
handlerContext, agentFrameworkContext: frameworkContext,
status: { status: {
state: 'failed', state: 'failed',
modified: new Date(), modified: new Date(),
@ -848,31 +848,31 @@ export class AgentInstanceService implements IAgentInstanceService {
} }
} }
public concatPrompt(promptDescription: Pick<AgentPromptDescription, 'handlerConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState> { public concatPrompt(promptDescription: Pick<AgentPromptDescription, 'agentFrameworkConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState> {
logger.debug('AgentInstanceService.concatPrompt called', { logger.debug('AgentInstanceService.concatPrompt called', {
hasPromptConfig: !!promptDescription.handlerConfig, hasPromptConfig: !!promptDescription.agentFrameworkConfig,
promptConfigKeys: Object.keys(promptDescription.handlerConfig), promptConfigKeys: Object.keys(promptDescription.agentFrameworkConfig || {}),
messagesCount: messages.length, messagesCount: messages.length,
}); });
return new Observable<PromptConcatStreamState>((observer) => { return new Observable<PromptConcatStreamState>((observer) => {
const processStream = async () => { const processStream = async () => {
try { try {
// Create a minimal handler context for prompt concatenation // Create a minimal framework context for prompt concatenation
const handlerContext = { const frameworkContext = {
agent: { agent: {
id: 'temp', id: 'temp',
messages, messages,
agentDefId: 'temp', agentDefId: 'temp',
status: { state: 'working' as const, modified: new Date() }, status: { state: 'working' as const, modified: new Date() },
created: 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, isCancelled: () => false,
}; };
const streamGenerator = promptConcatStream(promptDescription as AgentPromptDescription, messages, handlerContext); const streamGenerator = promptConcatStream(promptDescription as AgentPromptDescription, messages, frameworkContext);
for await (const state of streamGenerator) { for await (const state of streamGenerator) {
observer.next(state); observer.next(state);
if (state.isComplete) { if (state.isComplete) {
@ -893,21 +893,21 @@ export class AgentInstanceService implements IAgentInstanceService {
}); });
} }
public getHandlerConfigSchema(handlerId: string): Record<string, unknown> { public getFrameworkConfigSchema(frameworkId: string): Record<string, unknown> {
try { try {
logger.debug('AgentInstanceService.getHandlerConfigSchema called', { handlerId }); logger.debug('AgentInstanceService.getFrameworkConfigSchema called', { frameworkId });
// Check if we have a schema for this handler // Check if we have a schema for this framework
const schema = this.handlerSchemas.get(handlerId); const schema = this.frameworkSchemas.get(frameworkId);
if (schema) { if (schema) {
return schema; return schema;
} }
// If no schema found, return an empty 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: {} }; return { type: 'object', properties: {} };
} catch (error) { } catch (error) {
logger.error('Error in AgentInstanceService.getHandlerConfigSchema', { logger.error('Error in AgentInstanceService.getFrameworkConfigSchema', {
error, error,
handlerId, frameworkId,
}); });
throw error; throw error;
} }

View file

@ -8,16 +8,16 @@ import { AgentPromptDescription } from '@services/agentInstance/promptConcat/pro
/** /**
* Content of a session instance that user chat with an agent. * 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. * The instance can override the definition's configuration, or fall back to using it.
*/ */
export interface AgentInstance extends Omit<AgentDefinition, 'name' | 'handlerConfig'> { export interface AgentInstance extends Omit<AgentDefinition, 'name' | 'agentFrameworkConfig'> {
/** Agent description ID that generates this instance */ /** Agent description ID that generates this instance */
agentDefId: string; agentDefId: string;
/** Session name, optional in instance unlike definition */ /** Session name, optional in instance unlike definition */
name?: string; name?: string;
/** Agent handler's config - optional, falls back to AgentDefinition.handlerConfig if not set */ /** Agent framework's config - optional, falls back to AgentDefinition.agentFrameworkConfig if not set */
handlerConfig?: Record<string, unknown>; agentFrameworkConfig?: Record<string, unknown>;
/** /**
* Message history. * Message history.
* latest on top, so it's easy to get first one as user's latest input, and rest as 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 * For testing purposes, only initialize the built-in handlers without database
*/ */
initializeHandlers(): Promise<void>; initializeFrameworks(): Promise<void>;
/** /**
* Create a new agent instance from a definition * Create a new agent instance from a definition
@ -196,15 +196,15 @@ export interface IAgentInstanceService {
* @param messages Messages to be included in prompt generation * @param messages Messages to be included in prompt generation
* @returns Observable stream of processing states, with final state containing complete results * @returns Observable stream of processing states, with final state containing complete results
*/ */
concatPrompt(promptDescription: Pick<AgentPromptDescription, 'handlerConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState>; concatPrompt(promptDescription: Pick<AgentPromptDescription, 'agentFrameworkConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState>;
/** /**
* Get JSON Schema for handler configuration * Get JSON Schema for handler configuration
* This allows frontend to generate a form based on the schema for a specific handler * 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 * @returns JSON Schema for handler configuration
*/ */
getHandlerConfigSchema(handlerId: string): Record<string, unknown>; getFrameworkConfigSchema(frameworkId: string): Record<string, unknown>;
/** /**
* Save user message to database * Save user message to database
@ -233,7 +233,7 @@ export const AgentInstanceServiceIPCDescriptor = {
deleteAgent: ProxyPropertyType.Function, deleteAgent: ProxyPropertyType.Function,
getAgent: ProxyPropertyType.Function, getAgent: ProxyPropertyType.Function,
getAgents: ProxyPropertyType.Function, getAgents: ProxyPropertyType.Function,
getHandlerConfigSchema: ProxyPropertyType.Function, getFrameworkConfigSchema: ProxyPropertyType.Function,
saveUserMessage: ProxyPropertyType.Function, saveUserMessage: ProxyPropertyType.Function,
sendMsgToAgent: ProxyPropertyType.Function, sendMsgToAgent: ProxyPropertyType.Function,
subscribeToAgentUpdates: ProxyPropertyType.Function$, subscribeToAgentUpdates: ProxyPropertyType.Function$,

View file

@ -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<string, PromptConcatPlugin>();
/**
* 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<void> {
// 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<void> {
// 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 || [],
};
}

View file

@ -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<string, z.ZodType>();
/**
* Registry for plugin metadata
*/
const pluginMetadata = new Map<string, {
displayName: string;
description: string;
}>();
/**
* 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<string, z.ZodType> = {};
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 string> = T extends keyof ReturnType<typeof createPluginParameterTypes> ? ReturnType<typeof createPluginParameterTypes>[T] : never;
/**
* Create type definitions for all registered plugin parameters
* This is used internally for type inference
*/
export function createPluginParameterTypes() {
const types: Record<string, unknown> = {};
for (const pluginId of getAllRegisteredPluginIds()) {
const schema = getPluginParameterSchema(pluginId);
if (schema) {
types[pluginId] = schema;
}
}
return types as Record<string, z.ZodType>;
}

View file

@ -1,54 +1,55 @@
# Prompt Concat Tools # 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 ## 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 - `processPrompts`: Modifies prompt tree during processing
- `finalizePrompts`: Final processing before LLM call - `finalizePrompts`: Final processing before LLM call
- `postProcess`: Handles response processing - `postProcess`: Handles response processing
2. **Built-in Plugins**: 2. **Built-in Tools**:
- `fullReplacement`: Replaces content from various sources - `fullReplacement`: Replaces content from various sources
- `dynamicPosition`: Inserts content at specific positions - `dynamicPosition`: Inserts content at specific positions
- `retrievalAugmentedGeneration`: Retrieves content from wiki/external sources - `retrievalAugmentedGeneration`: Retrieves content from wiki/external sources
- `modelContextProtocol`: Integrates with external MCP servers - `modelContextProtocol`: Integrates with external MCP servers
- `toolCalling`: Processes function calls in responses - `toolCalling`: Processes function calls in responses
3. **Plugin Registration**: 3. **Tool Registration**:
- Plugins are registered by `pluginId` field in the `plugins` array - Tools are registered by `toolId` field in the `plugins` array
- Each plugin instance has its own configuration parameters - Each tool instance has its own configuration parameters
- Built-in plugins are auto-registered on system initialization - Built-in tools are auto-registered on system initialization
### Plugin Lifecycle ### Tool Lifecycle
2. **Configuration**: Plugins are loaded based on `handlerConfig.plugins` array 1. **Registration**: Tools are registered during initialization
3. **Execution**: Hooks execute plugins in registration order 2. **Configuration**: Tools are loaded based on `agentFrameworkConfig.plugins` array
4. **Error Handling**: Individual plugin failures don't stop the pipeline 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 1. Create tool function in `tools/` directory
2. Register in `plugins/index.ts` 2. Register in `tools/index.ts`
3. Add `pluginId` to schema enum 3. Add `toolId` to schema enum
4. Add parameter schema if needed 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 ```typescript
export const myPlugin: PromptConcatPlugin = (hooks) => { export const myTool: PromptConcatTool = (hooks) => {
hooks.processPrompts.tapAsync('myPlugin', async (context, callback) => { hooks.processPrompts.tapAsync('myTool', async (context, callback) => {
const { plugin, prompts, messages } = context; const { tool, prompts, messages } = context;
// Plugin logic here // Tool logic here
callback(null, context); callback(null, context);
}); });
}; };

View file

@ -10,17 +10,17 @@
* Main Concepts: * Main Concepts:
* - Prompts are tree-structured, can have roles (system/user/assistant) and children. * - Prompts are tree-structured, can have roles (system/user/assistant) and children.
* - Plugins use hooks to modify the prompt tree at runtime. * - 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 { logger } from '@services/libs/log';
import { ModelMessage } from 'ai'; import { ModelMessage } from 'ai';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { AgentHandlerContext } from '../buildInAgentHandlers/type'; import { AgentFrameworkContext } from '../agentFrameworks/utilities/type';
import { AgentInstanceMessage } from '../interface'; import { AgentInstanceMessage } from '../interface';
import { builtInPlugins, createHandlerHooks, PromptConcatHookContext } from '../plugins'; import { builtInTools, createAgentFrameworkHooks, PromptConcatHookContext } from '../tools';
import type { AgentPromptDescription, IPrompt } from './promptConcatSchema'; 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 * 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 * 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 * Uses actual node IDs instead of indices to avoid path conflicts with dynamic content
*/ */
function generateSourcePaths(prompts: IPrompt[], plugins: IPromptConcatPlugin[] = []): Map<string, string[]> { function generateSourcePaths(prompts: IPrompt[], plugins: IPromptConcatTool[] = []): Map<string, string[]> {
const pathMap = new Map<string, string[]>(); const pathMap = new Map<string, string[]>();
function traversePrompts(items: IPrompt[], currentPath: string[]): void { function traversePrompts(items: IPrompt[], currentPath: string[]): void {
items.forEach((item) => { 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) => { items.forEach((item) => {
const itemPath = [...currentPath, item.id]; const itemPath = [...currentPath, item.id];
pathMap.set(item.id, itemPath); pathMap.set(item.id, itemPath);
@ -191,7 +191,7 @@ export interface PromptConcatStreamState {
/** Current processing step */ /** Current processing step */
step: 'plugin' | 'finalize' | 'flatten' | 'complete'; step: 'plugin' | 'finalize' | 'flatten' | 'complete';
/** Current plugin being processed (if step is 'plugin') */ /** Current plugin being processed (if step is 'plugin') */
currentPlugin?: IPromptConcatPlugin; currentPlugin?: IPromptConcatTool;
/** Processing progress (0-1) */ /** Processing progress (0-1) */
progress: number; progress: number;
/** Whether processing is complete */ /** Whether processing is complete */
@ -203,40 +203,41 @@ export interface PromptConcatStreamState {
* Yields intermediate results for real-time UI updates * Yields intermediate results for real-time UI updates
*/ */
export async function* promptConcatStream( export async function* promptConcatStream(
agentConfig: Pick<AgentPromptDescription, 'handlerConfig'>, agentConfig: Pick<AgentPromptDescription, 'agentFrameworkConfig'>,
messages: AgentInstanceMessage[], messages: AgentInstanceMessage[],
handlerContext: AgentHandlerContext, agentFrameworkContext: AgentFrameworkContext,
): AsyncGenerator<PromptConcatStreamState, PromptConcatStreamState, unknown> { ): AsyncGenerator<PromptConcatStreamState, PromptConcatStreamState, unknown> {
const promptConfigs = Array.isArray(agentConfig.handlerConfig.prompts) ? agentConfig.handlerConfig.prompts : []; const agentFrameworkConfig = agentConfig.agentFrameworkConfig;
const pluginConfigs = (Array.isArray(agentConfig.handlerConfig.plugins) ? agentConfig.handlerConfig.plugins : []) as IPromptConcatPlugin[]; const promptConfigs = Array.isArray(agentFrameworkConfig?.prompts) ? agentFrameworkConfig.prompts : [];
const toolConfigs = (Array.isArray(agentFrameworkConfig?.plugins) ? agentFrameworkConfig.plugins : []) as IPromptConcatTool[];
const promptsCopy = cloneDeep(promptConfigs); const promptsCopy = cloneDeep(promptConfigs);
const sourcePaths = generateSourcePaths(promptsCopy, pluginConfigs); const sourcePaths = generateSourcePaths(promptsCopy, toolConfigs);
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
// Register plugins that match the configuration // Register tools that match the configuration
for (const plugin of pluginConfigs) { for (const tool of toolConfigs) {
const builtInPlugin = builtInPlugins.get(plugin.pluginId); const builtInTool = builtInTools.get(tool.toolId);
if (builtInPlugin) { if (builtInTool) {
builtInPlugin(hooks); builtInTool(hooks);
logger.debug('Registered plugin', { logger.debug('Registered tool', {
pluginId: plugin.pluginId, toolId: tool.toolId,
pluginInstanceId: plugin.id, toolInstanceId: tool.id,
}); });
} else { } 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 // Process each plugin through hooks with streaming
let modifiedPrompts = promptsCopy; 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 = { const context: PromptConcatHookContext = {
handlerContext, agentFrameworkContext: agentFrameworkContext,
messages, messages,
prompts: modifiedPrompts, prompts: modifiedPrompts,
pluginConfig: pluginConfigs[index], toolConfig: toolConfigs[index],
metadata: { sourcePaths }, metadata: { sourcePaths },
}; };
try { try {
@ -255,13 +256,13 @@ export async function* promptConcatStream(
processedPrompts: modifiedPrompts, processedPrompts: modifiedPrompts,
flatPrompts: intermediateFlat, flatPrompts: intermediateFlat,
step: 'plugin', step: 'plugin',
currentPlugin: pluginConfigs[index], currentPlugin: toolConfigs[index],
progress: (index + 1) / totalSteps, progress: (index + 1) / totalSteps,
isComplete: false, isComplete: false,
}; };
} catch (error) { } catch (error) {
logger.error('Plugin processing error', { logger.error('Plugin processing error', {
pluginConfig: pluginConfigs[index], toolConfig: toolConfigs[index],
error, error,
}); });
// Continue processing other plugins even if one fails // Continue processing other plugins even if one fails
@ -273,15 +274,15 @@ export async function* promptConcatStream(
processedPrompts: modifiedPrompts, processedPrompts: modifiedPrompts,
flatPrompts: flattenPrompts(modifiedPrompts), flatPrompts: flattenPrompts(modifiedPrompts),
step: 'finalize', step: 'finalize',
progress: (pluginConfigs.length + 1) / totalSteps, progress: (toolConfigs.length + 1) / totalSteps,
isComplete: false, isComplete: false,
}; };
const finalContext: PromptConcatHookContext = { const finalContext: PromptConcatHookContext = {
handlerContext, agentFrameworkContext: agentFrameworkContext,
messages, messages,
prompts: modifiedPrompts, prompts: modifiedPrompts,
pluginConfig: {} as IPromptConcatPlugin, // Empty plugin for finalization toolConfig: {} as IPromptConcatTool, // Empty tool for finalization
metadata: { sourcePaths }, metadata: { sourcePaths },
}; };
@ -297,7 +298,7 @@ export async function* promptConcatStream(
processedPrompts: modifiedPrompts, processedPrompts: modifiedPrompts,
flatPrompts: flattenPrompts(modifiedPrompts), flatPrompts: flattenPrompts(modifiedPrompts),
step: 'flatten', step: 'flatten',
progress: (pluginConfigs.length + 2) / totalSteps, progress: (toolConfigs.length + 2) / totalSteps,
isComplete: false, isComplete: false,
}; };
@ -341,15 +342,15 @@ export async function* promptConcatStream(
* @returns Processed prompt array and original prompt tree * @returns Processed prompt array and original prompt tree
*/ */
export async function promptConcat( export async function promptConcat(
agentConfig: Pick<AgentPromptDescription, 'handlerConfig'>, agentConfig: Pick<AgentPromptDescription, 'agentFrameworkConfig'>,
messages: AgentInstanceMessage[], messages: AgentInstanceMessage[],
handlerContext: AgentHandlerContext, agentFrameworkContext: AgentFrameworkContext,
): Promise<{ ): Promise<{
flatPrompts: ModelMessage[]; flatPrompts: ModelMessage[];
processedPrompts: IPrompt[]; processedPrompts: IPrompt[];
}> { }> {
// Use the streaming version and just return the final result // 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; let finalResult: PromptConcatStreamState;
// Consume all intermediate states to get the final result // Consume all intermediate states to get the final result

View file

@ -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 { t } from '@services/libs/i18n/placeholder';
import { z } from 'zod/v4'; import { z } from 'zod/v4';
import { ModelParametersSchema, ProviderModelSchema } from './modelParameters'; import { ModelParametersSchema, ProviderModelSchema } from './modelParameters';
@ -34,12 +34,12 @@ export const AIConfigSchema = BaseAPIConfigSchema
}); });
/** /**
* Handler configuration schema * Framework configuration schema
* Contains the handler-related configuration fields for prompts, responses, and plugins * Contains the framework-related configuration fields for prompts, responses, and tools
* This is dynamically generated to include all registered plugins * This is dynamically generated to include all registered tools
*/ */
export function getHandlerConfigSchema() { export function getFrameworkConfigSchema() {
const dynamicPluginSchema = createDynamicPromptConcatPluginSchema(); const dynamicToolSchema = createDynamicPromptConcatToolSchema();
return z.object({ return z.object({
prompts: z.array(PromptSchema).meta({ prompts: z.array(PromptSchema).meta({
@ -50,7 +50,7 @@ export function getHandlerConfigSchema() {
description: t('Schema.AgentConfig.PromptConfig.Response'), description: t('Schema.AgentConfig.PromptConfig.Response'),
title: t('PromptConfig.Tabs.Response'), title: t('PromptConfig.Tabs.Response'),
}), }),
plugins: z.array(dynamicPluginSchema).meta({ plugins: z.array(dynamicToolSchema).meta({
description: t('Schema.AgentConfig.PromptConfig.Plugins'), description: t('Schema.AgentConfig.PromptConfig.Plugins'),
title: t('PromptConfig.Tabs.Plugins'), title: t('PromptConfig.Tabs.Plugins'),
}), }),
@ -66,13 +66,13 @@ export function getHandlerConfigSchema() {
* @example * @example
* ```json * ```json
* { * {
* "id": "example-agent", * "id": "task-agent",
* "api": { * "api": {
* "provider": "siliconflow", * "provider": "siliconflow",
* "model": "Qwen/Qwen2.5-7B-Instruct" * "model": "Qwen/Qwen2.5-7B-Instruct"
* }, * },
* "modelParameters": { ... }, * "modelParameters": { ... },
* "handlerConfig": { * "agentFrameworkConfig": {
* "prompts": [ ... ], * "prompts": [ ... ],
* "response": [ ... ], * "response": [ ... ],
* "plugins": [ ... ], * "plugins": [ ... ],
@ -81,14 +81,14 @@ export function getHandlerConfigSchema() {
* ``` * ```
*/ */
export function getAgentConfigSchema() { export function getAgentConfigSchema() {
const dynamicHandlerConfigSchema = getHandlerConfigSchema(); const dynamicFrameworkConfigSchema = getFrameworkConfigSchema();
return BaseAPIConfigSchema.extend({ return BaseAPIConfigSchema.extend({
id: z.string().meta({ id: z.string().meta({
title: t('Schema.AgentConfig.IdTitle'), title: t('Schema.AgentConfig.IdTitle'),
description: t('Schema.AgentConfig.Id'), description: t('Schema.AgentConfig.Id'),
}), }),
handlerConfig: dynamicHandlerConfigSchema, agentFrameworkConfig: dynamicFrameworkConfigSchema,
}).meta({ }).meta({
title: t('Schema.AgentConfig.Title'), title: t('Schema.AgentConfig.Title'),
description: t('Schema.AgentConfig.Description'), description: t('Schema.AgentConfig.Description'),
@ -110,10 +110,15 @@ export function getDefaultAgentsSchema() {
export type DefaultAgents = z.infer<ReturnType<typeof getDefaultAgentsSchema>>; export type DefaultAgents = z.infer<ReturnType<typeof getDefaultAgentsSchema>>;
export type AgentPromptDescription = z.infer<ReturnType<typeof getAgentConfigSchema>>; export type AgentPromptDescription = z.infer<ReturnType<typeof getAgentConfigSchema>>;
export type AiAPIConfig = z.infer<typeof AIConfigSchema>; export type AiAPIConfig = z.infer<typeof AIConfigSchema>;
export type HandlerConfig = z.infer<ReturnType<typeof getHandlerConfigSchema>>; export type AgentFrameworkConfig = z.infer<ReturnType<typeof getFrameworkConfigSchema>>;
// Backward compat aliases - deprecated, use AgentFrameworkConfig directly
export type HandlerConfig = AgentFrameworkConfig;
// Re-export all schemas and types // Re-export all schemas and types
export * from './modelParameters'; export * from './modelParameters';
export * from './plugin'; export * from './plugin';
export * from './prompts'; export * from './prompts';
export * from './response'; export * from './response';
// Export IPromptConcatTool as IPromptConcatPlugin for backward compatibility
export type { IPromptConcatTool as IPromptConcatPlugin } from './plugin';

View file

@ -1,5 +1,5 @@
import { z } from 'zod/v4'; import { z } from 'zod/v4';
import { getHandlerConfigSchema } from './index'; import { getFrameworkConfigSchema } from './index';
/** /**
* Get the dynamically generated JSON Schema for handler configuration * 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. * 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() { export function getPromptConcatAgentFrameworkConfigJsonSchema() {
const dynamicHandlerConfigSchema = getHandlerConfigSchema(); const dynamicFrameworkConfigSchema = getFrameworkConfigSchema();
return z.toJSONSchema(dynamicHandlerConfigSchema, { target: 'draft-7' }); return z.toJSONSchema(dynamicFrameworkConfigSchema, { target: 'draft-7' });
} }

View file

@ -1,22 +1,22 @@
// Import parameter types from plugin files // Import parameter types from plugin files
import type { ModelContextProtocolParameter } from '@services/agentInstance/plugins/modelContextProtocolPlugin'; import type { ModelContextProtocolParameter } from '@services/agentInstance/tools/modelContextProtocol';
import type { DynamicPositionParameter, FullReplacementParameter } from '@services/agentInstance/plugins/promptPlugins'; import type { DynamicPositionParameter, FullReplacementParameter } from '@services/agentInstance/tools/prompt';
import type { WikiOperationParameter } from '@services/agentInstance/plugins/wikiOperationPlugin'; import type { WikiOperationParameter } from '@services/agentInstance/tools/wikiOperation';
import type { WikiSearchParameter } from '@services/agentInstance/plugins/wikiSearchPlugin'; import type { WikiSearchParameter } from '@services/agentInstance/tools/wikiSearch';
import type { WorkspacesListParameter } from '@services/agentInstance/plugins/workspacesListPlugin'; 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 * This includes all possible parameter fields for type safety
*/ */
export type IPromptConcatPlugin = { export type IPromptConcatTool = {
id: string; id: string;
caption?: string; caption?: string;
content?: string; content?: string;
forbidOverrides?: boolean; forbidOverrides?: boolean;
pluginId: string; toolId: string;
// Plugin-specific parameters // Tool-specific parameters
fullReplacementParam?: FullReplacementParameter; fullReplacementParam?: FullReplacementParameter;
dynamicPositionParam?: DynamicPositionParameter; dynamicPositionParam?: DynamicPositionParameter;
wikiOperationParam?: WikiOperationParameter; wikiOperationParam?: WikiOperationParameter;

View file

@ -6,12 +6,12 @@
import { ToolCallingMatch } from '@services/agentDefinition/interface'; import { ToolCallingMatch } from '@services/agentDefinition/interface';
import { logger } from '@services/libs/log'; import { logger } from '@services/libs/log';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { AgentHandlerContext } from '../buildInAgentHandlers/type'; import { AgentFrameworkContext } from '../agentFrameworks/utilities/type';
import { AgentInstanceMessage } from '../interface'; import { AgentInstanceMessage } from '../interface';
import { builtInPlugins, createHandlerHooks } from '../plugins'; import { builtInTools, createAgentFrameworkHooks } from '../tools';
import { AgentResponse, PostProcessContext, YieldNextRoundTarget } from '../plugins/types'; import { AgentResponse, PostProcessContext, YieldNextRoundTarget } from '../tools/types';
import type { IPromptConcatPlugin } from './promptConcatSchema'; import type { IPromptConcatTool } from './promptConcatSchema';
import { AgentPromptDescription, HandlerConfig } from './promptConcatSchema'; import { AgentFrameworkConfig, AgentPromptDescription } from './promptConcatSchema';
/** /**
* Process response configuration, apply plugins, and return final response * Process response configuration, apply plugins, and return final response
@ -24,7 +24,7 @@ import { AgentPromptDescription, HandlerConfig } from './promptConcatSchema';
export async function responseConcat( export async function responseConcat(
agentConfig: AgentPromptDescription, agentConfig: AgentPromptDescription,
llmResponse: string, llmResponse: string,
context: AgentHandlerContext, context: AgentFrameworkContext,
messages: AgentInstanceMessage[] = [], messages: AgentInstanceMessage[] = [],
): Promise<{ ): Promise<{
processedResponse: string; processedResponse: string;
@ -38,33 +38,33 @@ export async function responseConcat(
responseLength: llmResponse.length, responseLength: llmResponse.length,
}); });
const { handlerConfig } = agentConfig; const { agentFrameworkConfig } = agentConfig;
const responses: HandlerConfig['response'] = Array.isArray(handlerConfig.response) ? handlerConfig.response : []; const responses: AgentFrameworkConfig['response'] = Array.isArray(agentFrameworkConfig?.response) ? (agentFrameworkConfig?.response || []) : [];
const plugins = (Array.isArray(handlerConfig.plugins) ? handlerConfig.plugins : []) as IPromptConcatPlugin[]; const toolConfigs = (Array.isArray(agentFrameworkConfig.plugins) ? agentFrameworkConfig.plugins : []) as IPromptConcatTool[];
let modifiedResponses = cloneDeep(responses) as AgentResponse[]; let modifiedResponses = cloneDeep(responses) as AgentResponse[];
// Create hooks instance // Create hooks instance
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
// Register all plugins from configuration // Register all tools from configuration
for (const plugin of plugins) { for (const tool of toolConfigs) {
const builtInPlugin = builtInPlugins.get(plugin.pluginId); const builtInTool = builtInTools.get(tool.toolId);
if (builtInPlugin) { if (builtInTool) {
builtInPlugin(hooks); builtInTool(hooks);
} else { } 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 yieldNextRoundTo: YieldNextRoundTarget | undefined;
let toolCallInfo: ToolCallingMatch | undefined; let toolCallInfo: ToolCallingMatch | undefined;
for (const plugin of plugins) { for (const tool of toolConfigs) {
const responseContext: PostProcessContext = { const responseContext: PostProcessContext = {
handlerContext: context, agentFrameworkContext: context,
messages, messages,
prompts: [], // Not used in response processing prompts: [], // Not used in response processing
pluginConfig: plugin, toolConfig: tool,
llmResponse, llmResponse,
responses: modifiedResponses, responses: modifiedResponses,
metadata: {}, metadata: {},
@ -78,31 +78,31 @@ export async function responseConcat(
modifiedResponses = result.responses; 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) { if (result.actions?.yieldNextRoundTo) {
yieldNextRoundTo = result.actions.yieldNextRoundTo; yieldNextRoundTo = result.actions.yieldNextRoundTo;
if (result.actions.toolCalling) { if (result.actions.toolCalling) {
toolCallInfo = result.actions.toolCalling; toolCallInfo = result.actions.toolCalling;
} }
logger.debug('Plugin requested yield next round', { logger.debug('Tool requested yield next round', {
pluginId: plugin.pluginId, toolId: tool.toolId,
pluginInstanceId: plugin.id, toolInstanceId: tool.id,
yieldNextRoundTo, yieldNextRoundTo,
hasToolCall: !!result.actions.toolCalling, hasToolCall: !!result.actions.toolCalling,
}); });
} }
logger.debug('Response plugin processed successfully', { logger.debug('Response tool processed successfully', {
pluginId: plugin.pluginId, toolId: tool.toolId,
pluginInstanceId: plugin.id, toolInstanceId: tool.id,
}); });
} catch (error) { } catch (error) {
logger.error('Response plugin processing error', { logger.error('Response tool processing error', {
pluginId: plugin.pluginId, toolId: tool.toolId,
pluginInstanceId: plugin.id, toolInstanceId: tool.id,
error, error,
}); });
// Continue processing other plugins even if one fails // Continue processing other tools even if one fails
} }
} }

View file

@ -1,21 +1,21 @@
/** /**
* Tests for Full Replacement plugin duration mechanism * Tests for Full Replacement plugin duration mechanism
* Tests that expired messages (with duration) are filtered out from AI context * 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 { beforeEach, describe, expect, it, vi } from 'vitest';
import type { AgentInstanceMessage } from '../../interface'; 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 type { IPrompt } from '../../promptConcat/promptConcatSchema/prompts';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import defaultAgents from '../../buildInAgentHandlers/defaultAgents.json'; import defaultAgents from '../../agentFrameworks/taskAgents.json';
import { createHandlerHooks, PromptConcatHookContext } from '../index'; import { createAgentFrameworkHooks, PromptConcatHookContext } from '../index';
import { fullReplacementPlugin } from '../promptPlugins'; import { fullReplacementTool } from '../prompt';
// Use the real agent config // Use the real agent config
const exampleAgent = defaultAgents[0]; const exampleAgent = defaultAgents[0];
const realHandlerConfig = exampleAgent.handlerConfig; const realAgentFrameworkConfig = exampleAgent.agentFrameworkConfig;
describe('Full Replacement Plugin - Duration Mechanism', () => { describe('Full Replacement Plugin - Duration Mechanism', () => {
beforeEach(() => { beforeEach(() => {
@ -24,15 +24,15 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
describe('History Source Type with Duration Filtering', () => { describe('History Source Type with Duration Filtering', () => {
it('should filter out expired messages (duration=1) from historyOfSession', async () => { it('should filter out expired messages (duration=1) from historyOfSession', async () => {
// Find the real fullReplacement plugin for history from defaultAgents.json // Find the real fullReplacement plugin for history from taskAgents.json
const historyPlugin = realHandlerConfig.plugins.find( const historyPlugin = realAgentFrameworkConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
); );
expect(historyPlugin).toBeDefined(); expect(historyPlugin).toBeDefined();
expect(historyPlugin!.fullReplacementParam!.targetId).toBe('default-history'); // Real target ID expect(historyPlugin!.fullReplacementParam!.targetId).toBe('default-history'); // Real target ID
// Use real prompts structure from defaultAgents.json // Use real prompts structure from taskAgents.json
const testPrompts = cloneDeep(realHandlerConfig.prompts) as IPrompt[]; const testPrompts = cloneDeep(realAgentFrameworkConfig.prompts) as IPrompt[];
const messages: AgentInstanceMessage[] = [ const messages: AgentInstanceMessage[] = [
// Message 0: User message, no duration - should be included // Message 0: User message, no duration - should be included
@ -96,7 +96,7 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
]; ];
const context: PromptConcatHookContext = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
messages, messages,
@ -104,16 +104,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
status: { state: 'working' as const, modified: new Date() }, status: { state: 'working' as const, modified: new Date() },
created: new Date(), created: new Date(),
}, },
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false, 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, prompts: testPrompts,
messages, messages,
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
fullReplacementPlugin(hooks); fullReplacementTool(hooks);
// Execute the processPrompts hook // Execute the processPrompts hook
await hooks.processPrompts.promise(context); await hooks.processPrompts.promise(context);
@ -126,8 +126,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
const targetPrompt = historyPrompt!.children?.find(child => child.id === targetId); const targetPrompt = historyPrompt!.children?.find(child => child.id === targetId);
expect(targetPrompt).toBeDefined(); expect(targetPrompt).toBeDefined();
// The fullReplacementPlugin puts filtered messages in children array // The fullReplacementTool puts filtered messages in children array
// Note: fullReplacementPlugin removes the last message (current user message) // Note: fullReplacementTool removes the last message (current user message)
const children = (targetPrompt as unknown as { children?: IPrompt[] }).children || []; 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 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 () => { it('should include messages with duration=0 (visible in current round)', async () => {
const historyPlugin = realHandlerConfig.plugins.find( const historyPlugin = realAgentFrameworkConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
); );
const messages: AgentInstanceMessage[] = [ 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 = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
messages, messages,
@ -192,16 +192,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
status: { state: 'working' as const, modified: new Date() }, status: { state: 'working' as const, modified: new Date() },
created: new Date(), created: new Date(),
}, },
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false, 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, prompts: testPrompts,
messages, messages,
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
fullReplacementPlugin(hooks); fullReplacementTool(hooks);
await hooks.processPrompts.promise(context); await hooks.processPrompts.promise(context);
@ -220,8 +220,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
}); });
it('should handle mixed duration values correctly', async () => { it('should handle mixed duration values correctly', async () => {
const historyPlugin = realHandlerConfig.plugins.find( const historyPlugin = realAgentFrameworkConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession', p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
); );
const messages: AgentInstanceMessage[] = [ 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 = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
messages, messages,
@ -274,16 +274,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
status: { state: 'working' as const, modified: new Date() }, status: { state: 'working' as const, modified: new Date() },
created: new Date(), created: new Date(),
}, },
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} }, agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false, 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, prompts: testPrompts,
messages, messages,
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
fullReplacementPlugin(hooks); fullReplacementTool(hooks);
await hooks.processPrompts.promise(context); await hooks.processPrompts.promise(context);
@ -308,8 +308,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
describe('LLM Response Source Type', () => { describe('LLM Response Source Type', () => {
it('should verify LLM response replacement config exists', () => { it('should verify LLM response replacement config exists', () => {
// Verify the real config has LLM response replacement // Verify the real config has LLM response replacement
const llmResponsePlugin = realHandlerConfig.plugins.find( const llmResponsePlugin = realAgentFrameworkConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'llmResponse', p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'llmResponse',
); );
expect(llmResponsePlugin).toBeDefined(); expect(llmResponsePlugin).toBeDefined();
expect(llmResponsePlugin!.fullReplacementParam!.targetId).toBe('default-response'); expect(llmResponsePlugin!.fullReplacementParam!.targetId).toBe('default-response');

View file

@ -1,6 +1,6 @@
/** /**
* Deep integration tests for messageManagementPlugin with real SQLite database * Deep integration tests for messageManagementTool with real SQLite database
* Tests actual message persistence scenarios using defaultAgents.json configuration * Tests actual message persistence scenarios using taskAgents.json configuration
*/ */
import { container } from '@services/container'; import { container } from '@services/container';
import type { IDatabaseService } from '@services/database/interface'; import type { IDatabaseService } from '@services/database/interface';
@ -8,20 +8,20 @@ import { AgentDefinitionEntity, AgentInstanceEntity, AgentInstanceMessageEntity
import serviceIdentifier from '@services/serviceIdentifier'; import serviceIdentifier from '@services/serviceIdentifier';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 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 type { AgentInstanceMessage, IAgentInstanceService } from '../../interface';
import { createHandlerHooks } from '../index'; import { createAgentFrameworkHooks } from '../index';
import { messageManagementPlugin } from '../messageManagementPlugin'; import { messageManagementTool } from '../messageManagement';
import type { ToolExecutionContext, UserMessageContext } from '../types'; 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]; const exampleAgent = defaultAgents[0];
describe('Message Management Plugin - Real Database Integration', () => { describe('Message Management Plugin - Real Database Integration', () => {
let testAgentId: string; let testAgentId: string;
// agentInstanceServiceImpl available to test blocks // agentInstanceServiceImpl available to test blocks
let agentInstanceServiceImpl: IAgentInstanceService; let agentInstanceServiceImpl: IAgentInstanceService;
let hooks: ReturnType<typeof createHandlerHooks>; let hooks: ReturnType<typeof createAgentFrameworkHooks>;
let realDataSource: DataSource; let realDataSource: DataSource;
beforeEach(async () => { beforeEach(async () => {
@ -69,15 +69,15 @@ describe('Message Management Plugin - Real Database Integration', () => {
await agentInstanceServiceImpl.initialize(); await agentInstanceServiceImpl.initialize();
// Initialize plugin // Initialize plugin
hooks = createHandlerHooks(); hooks = createAgentFrameworkHooks();
messageManagementPlugin(hooks); messageManagementTool(hooks);
}); });
afterEach(async () => { afterEach(async () => {
// Clean up is handled automatically by beforeEach for each test // Clean up is handled automatically by beforeEach for each test
}); });
const createHandlerContext = (messages: AgentInstanceMessage[] = []) => ({ const createAgentFrameworkContext = (messages: AgentInstanceMessage[] = []) => ({
agent: { agent: {
id: testAgentId, id: testAgentId,
agentDefId: exampleAgent.id, agentDefId: exampleAgent.id,
@ -90,19 +90,19 @@ describe('Message Management Plugin - Real Database Integration', () => {
name: exampleAgent.name, name: exampleAgent.name,
version: '1.0.0', version: '1.0.0',
capabilities: [], capabilities: [],
handlerConfig: exampleAgent.handlerConfig, agentFrameworkConfig: exampleAgent.agentFrameworkConfig,
}, },
isCancelled: () => false, isCancelled: () => false,
}); });
describe('Real Wiki Search Scenario - The Missing Tool Result Bug', () => { 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 () => { 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 // Step 1: User asks to search wiki
const userMessageId = `user-msg-${Date.now()}`; const userMessageId = `user-msg-${Date.now()}`;
const userContext: UserMessageContext = { const userContext: UserMessageContext = {
handlerContext, agentFrameworkContext,
content: { text: '搜索 wiki 中的 Index 条目并解释' }, content: { text: '搜索 wiki 中的 Index 条目并解释' },
messageId: userMessageId, messageId: userMessageId,
timestamp: new Date(), timestamp: new Date(),
@ -133,10 +133,10 @@ describe('Message Management Plugin - Real Database Integration', () => {
}; };
await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage); await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage);
handlerContext.agent.messages.push(aiToolCallMessage); agentFrameworkContext.agent.messages.push(aiToolCallMessage);
// Step 3: Tool result message (THIS IS THE MISSING PIECE!) // 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 = { const toolResultMessage: AgentInstanceMessage = {
id: `tool-result-${Date.now()}`, id: `tool-result-${Date.now()}`,
agentId: testAgentId, agentId: testAgentId,
@ -164,11 +164,11 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
duration: 10, // Tool results might have expiration duration: 10, // Tool results might have expiration
}; };
// Add tool result to agent messages (simulating what wikiSearchPlugin does) // Add tool result to agent messages (simulating what wikiSearchTool does)
handlerContext.agent.messages.push(toolResultMessage); agentFrameworkContext.agent.messages.push(toolResultMessage);
const toolContext: ToolExecutionContext = { const toolContext: ToolExecutionContext = {
handlerContext, agentFrameworkContext,
toolResult: { toolResult: {
success: true, success: true,
data: 'Wiki search completed successfully', data: 'Wiki search completed successfully',
@ -202,7 +202,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
expect(savedToolResult?.duration).toBe(10); expect(savedToolResult?.duration).toBe(10);
// Verify isPersisted flag was updated // Verify isPersisted flag was updated
const toolMessageInMemory = handlerContext.agent.messages.find( const toolMessageInMemory = agentFrameworkContext.agent.messages.find(
(m) => m.metadata?.isToolResult, (m) => m.metadata?.isToolResult,
); );
expect(toolMessageInMemory?.metadata?.isPersisted).toBe(true); expect(toolMessageInMemory?.metadata?.isPersisted).toBe(true);
@ -249,7 +249,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
}); });
it('should handle multiple tool results in one execution', async () => { it('should handle multiple tool results in one execution', async () => {
const handlerContext = createHandlerContext(); const agentFrameworkContext = createAgentFrameworkContext();
// Add multiple tool result messages // Add multiple tool result messages
const toolResult1: AgentInstanceMessage = { const toolResult1: AgentInstanceMessage = {
@ -282,10 +282,10 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
duration: 3, duration: 3,
}; };
handlerContext.agent.messages.push(toolResult1, toolResult2); agentFrameworkContext.agent.messages.push(toolResult1, toolResult2);
const toolContext: ToolExecutionContext = { const toolContext: ToolExecutionContext = {
handlerContext, agentFrameworkContext,
toolResult: { toolResult: {
success: true, success: true,
data: 'Multiple tool search completed', 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 () => { 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 // 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 // Step 1: Complete chat flow with user message → AI tool call → tool result → AI response
const userMessage: AgentInstanceMessage = { const userMessage: AgentInstanceMessage = {
@ -372,9 +372,9 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage); await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage);
// Add tool result to context and trigger persistence via toolExecuted hook // Add tool result to context and trigger persistence via toolExecuted hook
handlerContext.agent.messages.push(toolResultMessage); agentFrameworkContext.agent.messages.push(toolResultMessage);
const toolContext: ToolExecutionContext = { const toolContext: ToolExecutionContext = {
handlerContext, agentFrameworkContext,
toolResult: { success: true, data: 'Search completed' }, toolResult: { success: true, data: 'Search completed' },
toolInfo: { toolId: 'wiki-search', parameters: {} }, toolInfo: { toolId: 'wiki-search', parameters: {} },
}; };

View file

@ -1,5 +1,5 @@
/** /**
* Tests for wikiOperationPlugin * Tests for wikiOperationTool
*/ */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 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 // Removed logger import as it is unused
import { matchToolCalling } from '@services/agentDefinition/responsePatternUtility'; 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 { IPrompt } from '@services/agentInstance/promptConcat/promptConcatSchema';
import type { AIStreamResponse } from '@services/externalAPI/interface'; import type { AIStreamResponse } from '@services/externalAPI/interface';
import type { IWorkspaceService } from '@services/workspaces/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 type { AgentInstance } from '../../interface';
import { createHandlerHooks } from '../index'; import { createAgentFrameworkHooks } from '../index';
import type { AIResponseContext, PluginActions, PromptConcatHookContext } from '../types'; import type { AIResponseContext, PromptConcatHookContext, ToolActions } from '../types';
import { wikiOperationPlugin } from '../wikiOperationPlugin'; import { wikiOperationTool } from '../wikiOperation';
import { workspacesListPlugin } from '../workspacesListPlugin'; import { workspacesListTool } from '../workspacesList';
// Mock i18n // Mock i18n
vi.mock('@services/libs/i18n', () => ({ vi.mock('@services/libs/i18n', () => ({
@ -50,8 +50,8 @@ vi.mock('@services/libs/i18n', () => ({
}, },
})); }));
// Helper to construct a complete AgentHandlerContext for tests // Helper to construct a complete AgentagentFrameworkContext for tests
const makeHandlerContext = (agentId = 'test-agent'): AgentHandlerContext => ({ const makeAgentFrameworkContext = (agentId = 'test-agent'): AgentFrameworkContext => ({
agent: { agent: {
id: agentId, id: agentId,
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -59,11 +59,11 @@ const makeHandlerContext = (agentId = 'test-agent'): AgentHandlerContext => ({
status: { state: 'working', modified: new Date() }, status: { state: 'working', modified: new Date() },
created: new Date(), created: new Date(),
} as unknown as AgentInstance, } as unknown as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test-agent-def', handlerConfig: {} } as unknown as { id: string; name: string; handlerConfig: Record<string, unknown> }, agentDef: { id: 'test-agent-def', name: 'test-agent-def', agentFrameworkConfig: {} } as unknown as { id: string; name: string; agentFrameworkConfig: Record<string, unknown> },
isCancelled: () => false, isCancelled: () => false,
}); });
describe('wikiOperationPlugin', () => { describe('wikiOperationTool', () => {
beforeEach(async () => { beforeEach(async () => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@ -73,12 +73,12 @@ describe('wikiOperationPlugin', () => {
}); });
it('should inject wiki operation tool content when plugin is configured', async () => { it('should inject wiki operation tool content when plugin is configured', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
// First register workspacesListPlugin to inject available workspaces from the global mock // First register workspacesListTool to inject available workspaces from the global mock
workspacesListPlugin(hooks); workspacesListTool(hooks);
wikiOperationPlugin(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[] = [ const prompts: IPrompt[] = [
{ {
id: 'target-prompt', id: 'target-prompt',
@ -88,48 +88,48 @@ describe('wikiOperationPlugin', () => {
]; ];
const workspacesContext: PromptConcatHookContext = { const workspacesContext: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { id: 'test-agent', messages: [], agentDefId: 'test', status: { state: 'working' as const, modified: new Date() }, created: new Date() }, 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, isCancelled: () => false,
}, },
messages: [], messages: [],
prompts, prompts,
pluginConfig: { toolConfig: {
id: 'workspaces-plugin', id: 'workspaces-plugin',
caption: 'Workspaces Plugin', caption: 'Workspaces Plugin',
forbidOverrides: false, forbidOverrides: false,
pluginId: 'workspacesList', toolId: 'workspacesList',
workspacesListParam: { workspacesListParam: {
targetId: 'target-prompt', targetId: 'target-prompt',
position: 'after' as const, position: 'after' as const,
}, },
} as unknown as IPromptConcatPlugin, } as unknown as IPromptConcatTool,
}; };
await hooks.processPrompts.promise(workspacesContext); await hooks.processPrompts.promise(workspacesContext);
// Then run wikiOperation injection which will append its tool content to the same prompt // Then run wikiOperation injection which will append its tool content to the same prompt
const wikiOpContext: PromptConcatHookContext = { const wikiOpContext: PromptConcatHookContext = {
handlerContext: workspacesContext.handlerContext, agentFrameworkContext: workspacesContext.agentFrameworkContext,
messages: [], messages: [],
prompts, prompts,
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
pluginId: 'wikiOperation', toolId: 'wikiOperation',
wikiOperationParam: { wikiOperationParam: {
toolListPosition: { toolListPosition: {
targetId: 'target-prompt', targetId: 'target-prompt',
position: 'after' as const, position: 'after' as const,
}, },
}, },
} as unknown as IPromptConcatPlugin, } as unknown as IPromptConcatTool,
}; };
await hooks.processPrompts.promise(wikiOpContext); await hooks.processPrompts.promise(wikiOpContext);
const targetPrompt = prompts[0]; 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); const childrenText = JSON.stringify(targetPrompt.children);
expect(childrenText).toContain('wiki-operation'); expect(childrenText).toContain('wiki-operation');
// Ensure the injected tool content documents the supported operations (enum values) // Ensure the injected tool content documents the supported operations (enum values)
@ -147,17 +147,17 @@ describe('wikiOperationPlugin', () => {
describe('tool execution', () => { describe('tool execution', () => {
it('should execute create operation successfully', async () => { it('should execute create operation successfully', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks); wikiOperationTool(hooks);
const handlerContext = makeHandlerContext(); const agentFrameworkContext = makeAgentFrameworkContext();
const context = { const context = {
handlerContext, agentFrameworkContext,
handlerConfig: { agentFrameworkConfig: {
plugins: [ plugins: [
{ {
pluginId: 'wikiOperation', toolId: 'wikiOperation',
wikiOperationParam: { wikiOperationParam: {
toolResultDuration: 1, toolResultDuration: 1,
}, },
@ -183,9 +183,9 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(createParams)}</tool_use>`; context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(createParams)}</tool_use>`;
// Add an assistant message containing the tool_use so the plugin can find it // 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()}`, id: `m-${Date.now()}`,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
role: 'assistant', role: 'assistant',
content: context.response.content, content: context.response.content,
modified: new Date(), modified: new Date(),
@ -209,13 +209,13 @@ describe('wikiOperationPlugin', () => {
expect(typeof wikiSvc.wikiOperationInServer).toBe('function'); expect(typeof wikiSvc.wikiOperationInServer).toBe('function');
const responseCtx: AIResponseContext = { const responseCtx: AIResponseContext = {
handlerContext, agentFrameworkContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-create', content: context.response.content, status: 'done' } as AIStreamResponse, response: { requestId: 'r-create', content: context.response.content, status: 'done' } as AIStreamResponse,
requestId: 'r-create', requestId: 'r-create',
isFinal: true, isFinal: true,
actions: {} as PluginActions, actions: {} as ToolActions,
}; };
await hooks.responseComplete.promise(responseCtx); await hooks.responseComplete.promise(responseCtx);
@ -227,7 +227,7 @@ describe('wikiOperationPlugin', () => {
); );
// Verify a tool result message was added to agent history // 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).toBeTruthy();
expect(toolResultMessage?.content).toContain('<functions_result>'); expect(toolResultMessage?.content).toContain('<functions_result>');
// Check for general success wording and tiddler title // Check for general success wording and tiddler title
@ -238,15 +238,15 @@ describe('wikiOperationPlugin', () => {
}); });
it('should execute update operation successfully', async () => { it('should execute update operation successfully', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks); wikiOperationTool(hooks);
const handlerContext = makeHandlerContext(); const agentFrameworkContext = makeAgentFrameworkContext();
const context = { const context = {
handlerContext, agentFrameworkContext,
handlerConfig: { agentFrameworkConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
}, },
response: { response: {
status: 'done' as const, status: 'done' as const,
@ -256,9 +256,9 @@ describe('wikiOperationPlugin', () => {
}; };
// Add assistant message so plugin can detect the tool call // Add assistant message so plugin can detect the tool call
handlerContext.agent.messages.push({ agentFrameworkContext.agent.messages.push({
id: `m-${Date.now()}`, id: `m-${Date.now()}`,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
role: 'assistant', role: 'assistant',
content: context.response.content, content: context.response.content,
modified: new Date(), modified: new Date(),
@ -274,11 +274,11 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(updateParams)}</tool_use>`; context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(updateParams)}</tool_use>`;
const respCtx2: AIResponseContext = { const respCtx2: AIResponseContext = {
handlerContext, agentFrameworkContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-update', content: context.response.content, status: 'done' } as AIStreamResponse, response: { requestId: 'r-update', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as PluginActions, actions: {} as ToolActions,
requestId: 'r-update', requestId: 'r-update',
isFinal: true, isFinal: true,
}; };
@ -291,22 +291,22 @@ describe('wikiOperationPlugin', () => {
); );
// Check general update success wording and tiddler title // 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).toBeTruthy();
expect(updateResult?.content).toContain('成功在Wiki工作空间'); expect(updateResult?.content).toContain('成功在Wiki工作空间');
expect(updateResult?.content).toContain('Existing Note'); expect(updateResult?.content).toContain('Existing Note');
}); });
it('should execute delete operation successfully', async () => { it('should execute delete operation successfully', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks); wikiOperationTool(hooks);
const handlerContext = makeHandlerContext(); const agentFrameworkContext = makeAgentFrameworkContext();
const context = { const context = {
handlerContext, agentFrameworkContext,
handlerConfig: { agentFrameworkConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
}, },
response: { response: {
status: 'done' as const, status: 'done' as const,
@ -316,9 +316,9 @@ describe('wikiOperationPlugin', () => {
}; };
// Add assistant message so plugin can detect the tool call // Add assistant message so plugin can detect the tool call
handlerContext.agent.messages.push({ agentFrameworkContext.agent.messages.push({
id: `m-${Date.now()}`, id: `m-${Date.now()}`,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
role: 'assistant', role: 'assistant',
content: context.response.content, content: context.response.content,
modified: new Date(), modified: new Date(),
@ -333,11 +333,11 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(deleteParams)}</tool_use>`; context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(deleteParams)}</tool_use>`;
const respCtx3: AIResponseContext = { const respCtx3: AIResponseContext = {
handlerContext, agentFrameworkContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-delete', content: context.response.content, status: 'done' } as AIStreamResponse, response: { requestId: 'r-delete', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as PluginActions, actions: {} as ToolActions,
requestId: 'r-delete', requestId: 'r-delete',
isFinal: true, isFinal: true,
}; };
@ -349,22 +349,22 @@ describe('wikiOperationPlugin', () => {
['Note to Delete'], ['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).toBeTruthy();
expect(deleteResult?.content).toContain('成功从Wiki工作空间'); expect(deleteResult?.content).toContain('成功从Wiki工作空间');
}); });
it('should handle workspace not found error', async () => { it('should handle workspace not found error', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks); wikiOperationTool(hooks);
// Use an actual tool_use payload with a nonexistent workspace // Use an actual tool_use payload with a nonexistent workspace
const handlerContext = makeHandlerContext(); const agentFrameworkContext = makeAgentFrameworkContext();
const context = { const context = {
handlerContext, agentFrameworkContext,
handlerConfig: { agentFrameworkConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
}, },
response: { response: {
status: 'done', status: 'done',
@ -374,9 +374,9 @@ describe('wikiOperationPlugin', () => {
}; };
// Add assistant message so plugin can detect the tool call // Add assistant message so plugin can detect the tool call
handlerContext.agent.messages.push({ agentFrameworkContext.agent.messages.push({
id: `m-${Date.now()}`, id: `m-${Date.now()}`,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
role: 'assistant', role: 'assistant',
content: context.response.content, content: context.response.content,
modified: new Date(), modified: new Date(),
@ -390,17 +390,17 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(badParams)}</tool_use>`; context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(badParams)}</tool_use>`;
const respCtx4: AIResponseContext = { const respCtx4: AIResponseContext = {
handlerContext, agentFrameworkContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-error', content: context.response.content, status: 'done' } as AIStreamResponse, response: { requestId: 'r-error', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as PluginActions, actions: {} as ToolActions,
requestId: 'r-error', requestId: 'r-error',
isFinal: true, isFinal: true,
}; };
await hooks.responseComplete.promise(respCtx4); 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).toBeTruthy();
expect(errResult?.content).toContain('工作空间名称或ID'); expect(errResult?.content).toContain('工作空间名称或ID');
// Ensure control is yielded to self on error so AI gets the next round // 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 () => { it('should not execute when tool call is not found', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks); wikiOperationTool(hooks);
// No tool_use in response // No tool_use in response
const handlerContext = makeHandlerContext(); const agentFrameworkContext = makeAgentFrameworkContext();
const context = { const context = {
handlerContext, agentFrameworkContext,
handlerConfig: { agentFrameworkConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }], plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
}, },
response: { response: {
status: 'done' as const, status: 'done' as const,
@ -428,18 +428,18 @@ describe('wikiOperationPlugin', () => {
}; };
await hooks.responseComplete.promise({ await hooks.responseComplete.promise({
handlerContext, agentFrameworkContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatPlugin, toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }, agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-none', content: context.response.content, status: 'done' } as AIStreamResponse, response: { requestId: 'r-none', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as PluginActions, actions: {} as ToolActions,
requestId: 'r-none', requestId: 'r-none',
isFinal: true, isFinal: true,
}); });
const wikiLocalAssert = container.get<Partial<IWikiService>>(serviceIdentifier.Wiki); const wikiLocalAssert = container.get<Partial<IWikiService>>(serviceIdentifier.Wiki);
expect(wikiLocalAssert.wikiOperationInServer).not.toHaveBeenCalled(); expect(wikiLocalAssert.wikiOperationInServer).not.toHaveBeenCalled();
expect(handlerContext.agent.messages).toHaveLength(0); expect(agentFrameworkContext.agent.messages).toHaveLength(0);
}); });
}); });
}); });

View file

@ -13,15 +13,15 @@ import type { AIResponseContext, YieldNextRoundTarget } from '../types';
import { WikiChannel } from '@/constants/channels'; import { WikiChannel } from '@/constants/channels';
import serviceIdentifier from '@services/serviceIdentifier'; 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 { AgentPromptDescription } from '@services/agentInstance/promptConcat/promptConcatSchema';
import type { IPrompt } 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 { cloneDeep } from 'lodash';
import defaultAgents from '../../buildInAgentHandlers/defaultAgents.json'; import defaultAgents from '../../agentFrameworks/taskAgents.json';
import { createHandlerHooks, PromptConcatHookContext } from '../index'; import { createAgentFrameworkHooks, PromptConcatHookContext } from '../index';
import { messageManagementPlugin } from '../messageManagementPlugin'; import { messageManagementTool } from '../messageManagement';
import { wikiSearchPlugin } from '../wikiSearchPlugin'; import { wikiSearchTool } from '../wikiSearch';
// Mock i18n // Mock i18n
vi.mock('@services/libs/i18n', () => ({ vi.mock('@services/libs/i18n', () => ({
@ -53,7 +53,7 @@ vi.mock('@services/libs/i18n', () => ({
// Use the real agent config // Use the real agent config
const exampleAgent = defaultAgents[0]; 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 // 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 () => { it('should inject wiki tools into prompts when configured', async () => {
// Find the wiki search plugin config, make sure our default config // 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(); expect(wikiPlugin).toBeDefined();
if (!wikiPlugin) { if (!wikiPlugin) {
// throw error to keep ts believe the plugin exists // throw error to keep ts believe the plugin exists
@ -89,7 +89,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
expect(wikiPlugin.wikiSearchParam?.toolListPosition).toBeDefined(); expect(wikiPlugin.wikiSearchParam?.toolListPosition).toBeDefined();
// Create a copy of prompts to test modification // Create a copy of prompts to test modification
const prompts = cloneDeep(handlerConfig.prompts); const prompts = cloneDeep(agentFrameworkConfig.prompts);
const messages = [ const messages = [
{ {
id: 'user-1', id: 'user-1',
@ -102,19 +102,19 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
]; ];
const context: PromptConcatHookContext = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { id: 'test', messages: [], agentDefId: 'test', status: { state: 'working' as const, modified: new Date() }, created: new Date() }, 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, isCancelled: () => false,
}, },
pluginConfig: wikiPlugin, toolConfig: wikiPlugin,
prompts: prompts, prompts: prompts,
messages, messages,
}; };
// Use real hooks from the plugin system // Use real hooks from the plugin system
const promptHooks = createHandlerHooks(); const promptHooks = createAgentFrameworkHooks();
wikiSearchPlugin(promptHooks); wikiSearchTool(promptHooks);
// Execute the processPrompts hook // Execute the processPrompts hook
await promptHooks.processPrompts.promise(context); 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 // Create a plugin config with trigger that won't match
const wikiPlugin = { const wikiPlugin = {
id: 'test-wiki-plugin', id: 'test-wiki-plugin',
pluginId: 'wikiSearch' as const, toolId: 'wikiSearch' as const,
forbidOverrides: false, forbidOverrides: false,
retrievalAugmentedGenerationParam: { retrievalAugmentedGenerationParam: {
sourceType: 'wiki' as const, 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 originalPromptsText = JSON.stringify(prompts);
const context = { const context = {
pluginConfig: wikiPlugin, toolConfig: wikiPlugin,
prompts, prompts,
messages: [ messages: [
{ {
@ -162,10 +162,10 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
], ],
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
// build a minimal PromptConcatHookContext to run the plugin's processPrompts // build a minimal PromptConcatHookContext to run the plugin's processPrompts
const handlerCtx: AgentHandlerContext = { const handlerCtx: AgentFrameworkContext = {
agent: { agent: {
id: 'test', id: 'test',
agentDefId: 'test', agentDefId: 'test',
@ -173,12 +173,12 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
status: { state: 'working' as const, modified: new Date() }, status: { state: 'working' as const, modified: new Date() },
created: new Date(), created: new Date(),
} as AgentInstance, } as AgentInstance,
agentDef: { id: 'test', name: 'test', handlerConfig: {} } as AgentDefinition, agentDef: { id: 'test', name: 'test', agentFrameworkConfig: {} } as AgentDefinition,
isCancelled: () => false, isCancelled: () => false,
}; };
const hookContext: PromptConcatHookContext = { const hookContext: PromptConcatHookContext = {
handlerContext: handlerCtx, agentFrameworkContext: handlerCtx,
pluginConfig: wikiPlugin as IPromptConcatPlugin, toolConfig: wikiPlugin as IPromptConcatTool,
prompts: prompts as IPrompt[], prompts: prompts as IPrompt[],
messages: context.messages as AgentInstanceMessage[], 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 () => { it('should execute wiki search with correct duration=1 and trigger next round', async () => {
// Find the real wikiSearch plugin config from defaultAgents.json // Find the real wikiSearch plugin config from taskAgents.json
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(); expect(wikiPlugin).toBeDefined();
expect(wikiPlugin!.wikiSearchParam).toBeDefined(); expect(wikiPlugin!.wikiSearchParam).toBeDefined();
const handlerContext = { const agentFrameworkContext = {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', 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, isCancelled: () => false,
}; };
@ -270,11 +270,11 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
const context = { const context = {
handlerContext, agentFrameworkContext,
response, response,
requestId: 'test-request-123', requestId: 'test-request-123',
isFinal: true, isFinal: true,
pluginConfig: wikiPlugin!, toolConfig: wikiPlugin!,
prompts: [], prompts: [],
messages: [], messages: [],
llmResponse: response.content, llmResponse: response.content,
@ -283,10 +283,10 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
// Use real handler hooks // Use real handler hooks
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
// Register the plugin // Register the plugin
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
// Execute the response complete hook // Execute the response complete hook
await hooks.responseComplete.promise(context); 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) // 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.id).toBe('ai-tool-call-msg');
expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately
expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true); expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true);
expect(aiToolCallMessage.metadata?.toolId).toBe('wiki-search'); expect(aiToolCallMessage.metadata?.toolId).toBe('wiki-search');
// Verify tool result message was added to agent history with correct settings // Verify tool result message was added to agent history with correct settings
expect(handlerContext.agent.messages.length).toBe(3); // user + ai + tool_result expect(agentFrameworkContext.agent.messages.length).toBe(3); // user + ai + tool_result
const toolResultMessage = handlerContext.agent.messages[2] as AgentInstanceMessage; const toolResultMessage = agentFrameworkContext.agent.messages[2] as AgentInstanceMessage;
expect(toolResultMessage.role).toBe('tool'); // Tool result message expect(toolResultMessage.role).toBe('tool'); // Tool result message
expect(toolResultMessage.content).toContain('<functions_result>'); expect(toolResultMessage.content).toContain('<functions_result>');
expect(toolResultMessage.content).toContain('Tool: wiki-search'); 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) expect(toolResultMessage.duration).toBe(1); // Tool result uses configurable toolResultDuration (default 1)
// Check that previous user message is unchanged // 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.id).toBe('user-msg-1');
expect(userMessage.duration).toBeUndefined(); // Should stay visible expect(userMessage.duration).toBeUndefined(); // Should stay visible
}); });
it('should handle wiki search errors gracefully and set duration=1 for both messages', async () => { it('should handle wiki search errors gracefully and set duration=1 for both messages', async () => {
const handlerContext = { const agentFrameworkContext = {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', 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, isCancelled: () => false,
}; };
@ -364,13 +364,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
const context = { const context = {
handlerContext, agentFrameworkContext,
response, response,
requestId: 'test-request-error', requestId: 'test-request-error',
isFinal: true, isFinal: true,
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
pluginId: 'wikiSearch' as const, toolId: 'wikiSearch' as const,
forbidOverrides: false, forbidOverrides: false,
}, },
prompts: [], prompts: [],
@ -383,8 +383,8 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}, },
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
await hooks.responseComplete.promise(context); await hooks.responseComplete.promise(context);
@ -392,14 +392,14 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
expect(context.actions.yieldNextRoundTo).toBe('self'); expect(context.actions.yieldNextRoundTo).toBe('self');
// Check that AI tool call message has duration=1 even after error (should gray out immediately) // 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.id).toBe('ai-error-tool-call');
expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately
expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true); expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true);
// Verify error message was added to agent history // Verify error message was added to agent history
expect(handlerContext.agent.messages.length).toBe(2); // tool_call + error_result expect(agentFrameworkContext.agent.messages.length).toBe(2); // tool_call + error_result
const errorResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage; const errorResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(errorResultMessage.role).toBe('tool'); // Tool error message expect(errorResultMessage.role).toBe('tool'); // Tool error message
expect(errorResultMessage.content).toContain('<functions_result>'); expect(errorResultMessage.content).toContain('<functions_result>');
expect(errorResultMessage.content).toContain('Error:'); expect(errorResultMessage.content).toContain('Error:');
@ -411,7 +411,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}); });
it('should not modify duration of unrelated messages', async () => { it('should not modify duration of unrelated messages', async () => {
const handlerContext = { const agentFrameworkContext = {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', 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, isCancelled: () => false,
}; };
@ -461,13 +461,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
const context = { const context = {
handlerContext, agentFrameworkContext,
response, response,
requestId: 'test-request-selective', requestId: 'test-request-selective',
isFinal: true, isFinal: true,
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
pluginId: 'wikiSearch' as const, toolId: 'wikiSearch' as const,
forbidOverrides: false, forbidOverrides: false,
}, },
prompts: [], prompts: [],
@ -480,20 +480,20 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}, },
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
await hooks.responseComplete.promise(context); await hooks.responseComplete.promise(context);
// Check that unrelated messages were not modified // 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 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 expect(unrelatedAiMsg.duration).toBeUndefined(); // Should remain unchanged
// Check that only the tool call message was modified // 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.duration).toBe(1); // Should be set to 1
expect(toolCallMsg.metadata?.containsToolCall).toBe(true); expect(toolCallMsg.metadata?.containsToolCall).toBe(true);
}); });
@ -507,20 +507,20 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
status: { state: 'working' as const, modified: new Date() }, status: { state: 'working' as const, modified: new Date() },
created: new Date(), created: new Date(),
} as AgentInstance, } as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} } as AgentDefinition, agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} } as AgentDefinition,
isCancelled: () => false, isCancelled: () => false,
}; };
const context: AIResponseContext = { const context: AIResponseContext = {
handlerContext: handlerCtx, agentFrameworkContext: handlerCtx,
pluginConfig: { id: 'test-plugin', pluginId: 'wikiSearch' } as IPromptConcatPlugin, toolConfig: { id: 'test-plugin', toolId: 'wikiSearch' } as IPromptConcatTool,
response: { requestId: 'test-request-345', content: 'Just a regular response without any tool calls', status: 'done' }, response: { requestId: 'test-request-345', content: 'Just a regular response without any tool calls', status: 'done' },
requestId: 'test-request', requestId: 'test-request',
isFinal: true, isFinal: true,
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
await hooks.responseComplete.promise(context); await hooks.responseComplete.promise(context);
@ -597,7 +597,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}) as unknown as IWikiService['wikiOperationInServer'], }) as unknown as IWikiService['wikiOperationInServer'],
); );
const handlerContext = { const agentFrameworkContext = {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', 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, isCancelled: () => false,
}; };
@ -638,13 +638,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
const context = { const context = {
handlerContext, agentFrameworkContext,
response, response,
requestId: 'test-request-vector', requestId: 'test-request-vector-error',
isFinal: true, isFinal: true,
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
pluginId: 'wikiSearch' as const, toolId: 'wikiSearch' as const,
forbidOverrides: false, forbidOverrides: false,
}, },
prompts: [], prompts: [],
@ -654,8 +654,8 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
actions: {} as ActionBag, actions: {} as ActionBag,
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
await hooks.responseComplete.promise(context); await hooks.responseComplete.promise(context);
@ -676,9 +676,9 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
// Verify results were processed // Verify results were processed
expect(context.actions.yieldNextRoundTo).toBe('self'); 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('<functions_result>'); expect(toolResultMessage.content).toContain('<functions_result>');
expect(toolResultMessage.content).toContain('Vector Result 1'); expect(toolResultMessage.content).toContain('Vector Result 1');
expect(toolResultMessage.content).toContain('Vector Result 2'); expect(toolResultMessage.content).toContain('Vector Result 2');
@ -696,7 +696,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
new Error('Vector database not initialized'), new Error('Vector database not initialized'),
); );
const handlerContext = { const agentFrameworkContext = {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', 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, isCancelled: () => false,
}; };
@ -735,13 +735,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
const context = { const context = {
handlerContext, agentFrameworkContext,
response, response,
requestId: 'test-request-vector-error', requestId: 'test-request-vector-error',
isFinal: true, isFinal: true,
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
pluginId: 'wikiSearch' as const, toolId: 'wikiSearch' as const,
forbidOverrides: false, forbidOverrides: false,
}, },
prompts: [], prompts: [],
@ -751,15 +751,15 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
actions: {} as ActionBag, actions: {} as ActionBag,
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
await hooks.responseComplete.promise(context); await hooks.responseComplete.promise(context);
// Should still set up next round with error message // Should still set up next round with error message
expect(context.actions.yieldNextRoundTo).toBe('self'); 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:'); expect(errorResultMessage.content).toContain('Error:');
// Error message contains i18n key or actual error // Error message contains i18n key or actual error
expect(errorResultMessage.content).toMatch(/Vector database not initialized|Tool\.WikiSearch\.Error\.VectorSearchFailed/); 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 () => { it('should require query parameter for vector search', async () => {
const handlerContext = { const agentFrameworkContext = {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', 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, isCancelled: () => false,
}; };
@ -806,13 +806,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
const context = { const context = {
handlerContext, agentFrameworkContext,
response, response,
requestId: 'test-request-no-query', requestId: 'test-request-no-query',
isFinal: true, isFinal: true,
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
pluginId: 'wikiSearch' as const, toolId: 'wikiSearch' as const,
forbidOverrides: false, forbidOverrides: false,
}, },
prompts: [], prompts: [],
@ -822,13 +822,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
actions: {} as ActionBag, actions: {} as ActionBag,
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
await hooks.responseComplete.promise(context); await hooks.responseComplete.promise(context);
// Should return error about missing query // 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:'); expect(errorMessage.content).toContain('Error:');
// Error message contains i18n key or translated text // Error message contains i18n key or translated text
expect(errorMessage.content).toMatch(/query|Tool\.WikiSearch\.Error\.VectorSearchRequiresQuery/); expect(errorMessage.content).toMatch(/query|Tool\.WikiSearch\.Error\.VectorSearchRequiresQuery/);
@ -836,9 +836,9 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}); });
describe('Message Persistence Integration', () => { describe('Message Persistence Integration', () => {
it('should work with messageManagementPlugin for complete persistence flow', async () => { it('should work with messageManagementTool for complete persistence flow', async () => {
// This test ensures wikiSearchPlugin works well with messageManagementPlugin // This test ensures wikiSearchTool works well with messageManagementTool
const handlerContext = { const agentFrameworkContext = {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', 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, isCancelled: () => false,
}; };
@ -870,13 +870,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}; };
const context = { const context = {
handlerContext, agentFrameworkContext,
response, response,
requestId: 'test-request-integration', requestId: 'test-request-integration',
isFinal: true, isFinal: true,
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
pluginId: 'wikiSearch' as const, toolId: 'wikiSearch' as const,
forbidOverrides: false, forbidOverrides: false,
}, },
prompts: [], prompts: [],
@ -889,19 +889,19 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}, },
}; };
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks); wikiSearchTool(hooks);
messageManagementPlugin(hooks); messageManagementTool(hooks);
await hooks.responseComplete.promise(context); await hooks.responseComplete.promise(context);
// Verify integration works // Verify integration works
expect(context.actions.yieldNextRoundTo).toBe('self'); 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?.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 () => { it('should prevent regression: tool result not filtered in second round', async () => {

View file

@ -1,22 +1,22 @@
/** /**
* Tests for workspacesListPlugin * Tests for workspacesListTool
*/ */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// Note: global mocks from src/__tests__/setup-vitest.ts provide container and logger // 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 { container } from '@services/container';
import { logger } from '@services/libs/log'; import { logger } from '@services/libs/log';
import serviceIdentifier from '@services/serviceIdentifier'; 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 { AgentInstance } from '../../interface';
import type { PromptConcatHookContext } from '../types'; import type { PromptConcatHookContext } from '../types';
import type { IWorkspaceService } from '@services/workspaces/interface'; import type { IWorkspaceService } from '@services/workspaces/interface';
import { createHandlerHooks } from '../index'; import { createAgentFrameworkHooks } from '../index';
import { workspacesListPlugin } from '../workspacesListPlugin'; import { workspacesListTool } from '../workspacesList';
describe('workspacesListPlugin', () => { describe('workspacesListTool', () => {
beforeEach(async () => { beforeEach(async () => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
@ -27,11 +27,11 @@ describe('workspacesListPlugin', () => {
describe('workspaces list injection', () => { describe('workspaces list injection', () => {
it('should inject workspaces list when plugin is configured', async () => { it('should inject workspaces list when plugin is configured', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks); workspacesListTool(hooks);
const context: PromptConcatHookContext = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -41,7 +41,7 @@ describe('workspacesListPlugin', () => {
} as AgentInstance, } as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown,
isCancelled: () => false, isCancelled: () => false,
} as AgentHandlerContext, } as AgentFrameworkContext,
messages: [], messages: [],
prompts: [ prompts: [
{ {
@ -50,16 +50,16 @@ describe('workspacesListPlugin', () => {
children: [], children: [],
}, },
], ],
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
caption: 'Test Plugin', caption: 'Test Plugin',
forbidOverrides: false, forbidOverrides: false,
pluginId: 'workspacesList', toolId: 'workspacesList',
workspacesListParam: { workspacesListParam: {
targetId: 'target-prompt', targetId: 'target-prompt',
position: 'after' as const, position: 'after' as const,
}, },
} as unknown as IPromptConcatPlugin, } as unknown as IPromptConcatTool,
}; };
await hooks.processPrompts.promise(context); await hooks.processPrompts.promise(context);
@ -74,11 +74,11 @@ describe('workspacesListPlugin', () => {
}); });
it('should inject workspaces list when position is before', async () => { it('should inject workspaces list when position is before', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks); workspacesListTool(hooks);
const context: PromptConcatHookContext = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -88,7 +88,7 @@ describe('workspacesListPlugin', () => {
} as AgentInstance, } as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown,
isCancelled: () => false, isCancelled: () => false,
} as AgentHandlerContext, } as AgentFrameworkContext,
messages: [], messages: [],
prompts: [ prompts: [
{ {
@ -97,16 +97,16 @@ describe('workspacesListPlugin', () => {
children: [], children: [],
}, },
], ],
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
caption: 'Test Plugin', caption: 'Test Plugin',
forbidOverrides: false, forbidOverrides: false,
pluginId: 'workspacesList', toolId: 'workspacesList',
workspacesListParam: { workspacesListParam: {
targetId: 'target-prompt', targetId: 'target-prompt',
position: 'before' as const, position: 'before' as const,
}, },
} as unknown as IPromptConcatPlugin, } as unknown as IPromptConcatTool,
}; };
await hooks.processPrompts.promise(context); await hooks.processPrompts.promise(context);
@ -118,11 +118,11 @@ describe('workspacesListPlugin', () => {
}); });
it('should not inject content when plugin is not configured', async () => { it('should not inject content when plugin is not configured', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks); workspacesListTool(hooks);
const context: PromptConcatHookContext = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -132,7 +132,7 @@ describe('workspacesListPlugin', () => {
} as AgentInstance, } as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown,
isCancelled: () => false, isCancelled: () => false,
} as AgentHandlerContext, } as AgentFrameworkContext,
messages: [], messages: [],
prompts: [ prompts: [
{ {
@ -141,7 +141,7 @@ describe('workspacesListPlugin', () => {
children: [], 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); await hooks.processPrompts.promise(context);
@ -155,11 +155,11 @@ describe('workspacesListPlugin', () => {
const workspaceService = container.get<Partial<IWorkspaceService>>(serviceIdentifier.Workspace); const workspaceService = container.get<Partial<IWorkspaceService>>(serviceIdentifier.Workspace);
workspaceService.getWorkspacesAsList = vi.fn().mockResolvedValue([]) as unknown as IWorkspaceService['getWorkspacesAsList']; workspaceService.getWorkspacesAsList = vi.fn().mockResolvedValue([]) as unknown as IWorkspaceService['getWorkspacesAsList'];
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks); workspacesListTool(hooks);
const context: PromptConcatHookContext = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -169,7 +169,7 @@ describe('workspacesListPlugin', () => {
} as AgentInstance, } as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown,
isCancelled: () => false, isCancelled: () => false,
} as AgentHandlerContext, } as AgentFrameworkContext,
messages: [], messages: [],
prompts: [ prompts: [
{ {
@ -178,16 +178,16 @@ describe('workspacesListPlugin', () => {
children: [], children: [],
}, },
], ],
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
caption: 'Test Plugin', caption: 'Test Plugin',
forbidOverrides: false, forbidOverrides: false,
pluginId: 'workspacesList', toolId: 'workspacesList',
workspacesListParam: { workspacesListParam: {
targetId: 'target-prompt', targetId: 'target-prompt',
position: 'after' as const, position: 'after' as const,
}, },
} as unknown as IPromptConcatPlugin, } as unknown as IPromptConcatTool,
}; };
await hooks.processPrompts.promise(context); await hooks.processPrompts.promise(context);
@ -195,16 +195,16 @@ describe('workspacesListPlugin', () => {
const targetPrompt = context.prompts[0]; const targetPrompt = context.prompts[0];
expect(targetPrompt.children).toHaveLength(0); expect(targetPrompt.children).toHaveLength(0);
expect(logger.debug).toHaveBeenCalledWith('No wiki workspaces found to inject', { 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 () => { it('should warn when target prompt is not found', async () => {
const hooks = createHandlerHooks(); const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks); workspacesListTool(hooks);
const context: PromptConcatHookContext = { const context: PromptConcatHookContext = {
handlerContext: { agentFrameworkContext: {
agent: { agent: {
id: 'test-agent', id: 'test-agent',
agentDefId: 'test-agent-def', agentDefId: 'test-agent-def',
@ -214,7 +214,7 @@ describe('workspacesListPlugin', () => {
} as AgentInstance, } as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown, agentDef: { id: 'test-agent-def', name: 'test-agent-def' } as unknown,
isCancelled: () => false, isCancelled: () => false,
} as AgentHandlerContext, } as AgentFrameworkContext,
messages: [], messages: [],
prompts: [ prompts: [
{ {
@ -223,23 +223,23 @@ describe('workspacesListPlugin', () => {
children: [], children: [],
}, },
], ],
pluginConfig: { toolConfig: {
id: 'test-plugin', id: 'test-plugin',
caption: 'Test Plugin', caption: 'Test Plugin',
forbidOverrides: false, forbidOverrides: false,
pluginId: 'workspacesList', toolId: 'workspacesList',
workspacesListParam: { workspacesListParam: {
targetId: 'non-existent-prompt', targetId: 'non-existent-prompt',
position: 'after' as const, position: 'after' as const,
}, },
} as unknown as IPromptConcatPlugin, } as unknown as IPromptConcatTool,
}; };
await hooks.processPrompts.promise(context); await hooks.processPrompts.promise(context);
expect(logger.warn).toHaveBeenCalledWith('Workspaces list target prompt not found', { expect(logger.warn).toHaveBeenCalledWith('Workspaces list target prompt not found', {
targetId: 'non-existent-prompt', targetId: 'non-existent-prompt',
pluginId: 'test-plugin', toolId: 'test-plugin',
}); });
}); });
}); });

View file

@ -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<string, PromptConcatTool>();
/**
* 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<void> {
// 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<void> {
// 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 || [],
};
}

View file

@ -8,20 +8,20 @@ import { logger } from '@services/libs/log';
import serviceIdentifier from '@services/serviceIdentifier'; import serviceIdentifier from '@services/serviceIdentifier';
import type { IAgentInstanceService } from '../interface'; import type { IAgentInstanceService } from '../interface';
import { createAgentMessage } from '../utilities'; 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 * Message management plugin
* Handles all message-related operations: persistence, streaming, UI updates, and duration-based filtering * 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 // Handle user message persistence
hooks.userMessageReceived.tapAsync('messageManagementPlugin', async (context: UserMessageContext, callback) => { hooks.userMessageReceived.tapAsync('messageManagementTool', async (context: UserMessageContext, callback) => {
try { try {
const { handlerContext, content, messageId } = context; const { agentFrameworkContext, content, messageId } = context;
// Create user message using the helper function // Create user message using the helper function
const userMessage = createAgentMessage(messageId, handlerContext.agent.id, { const userMessage = createAgentMessage(messageId, agentFrameworkContext.agent.id, {
role: 'user', role: 'user',
content: content.text, content: content.text,
contentType: 'text/plain', 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) // 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 // Get the agent instance service to access repositories
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance); const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
@ -40,7 +40,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('User message persisted to database', { logger.debug('User message persisted to database', {
messageId, messageId,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
contentLength: content.text.length, contentLength: content.text.length,
}); });
@ -49,30 +49,30 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
logger.error('Message management plugin error in userMessageReceived', { logger.error('Message management plugin error in userMessageReceived', {
error, error,
messageId: context.messageId, messageId: context.messageId,
agentId: context.handlerContext.agent.id, agentId: context.agentFrameworkContext.agent.id,
}); });
callback(); callback();
} }
}); });
// Handle agent status persistence // Handle agent status persistence
hooks.agentStatusChanged.tapAsync('messageManagementPlugin', async (context: AgentStatusContext, callback) => { hooks.agentStatusChanged.tapAsync('messageManagementTool', async (context: AgentStatusContext, callback) => {
try { try {
const { handlerContext, status } = context; const { agentFrameworkContext, status } = context;
// Get the agent instance service to update status // Get the agent instance service to update status
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance); const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
// Update agent status in database // Update agent status in database
await agentInstanceService.updateAgent(handlerContext.agent.id, { await agentInstanceService.updateAgent(agentFrameworkContext.agent.id, {
status, status,
}); });
// Update the agent object for immediate use // Update the agent object for immediate use
handlerContext.agent.status = status; agentFrameworkContext.agent.status = status;
logger.debug('Agent status updated in database', { logger.debug('Agent status updated in database', {
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
state: status.state, state: status.state,
}); });
@ -80,7 +80,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
} catch (error) { } catch (error) {
logger.error('Message management plugin error in agentStatusChanged', { logger.error('Message management plugin error in agentStatusChanged', {
error, error,
agentId: context.handlerContext.agent.id, agentId: context.agentFrameworkContext.agent.id,
status: context.status, status: context.status,
}); });
callback(); callback();
@ -88,13 +88,13 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
}); });
// Handle AI response updates during streaming // Handle AI response updates during streaming
hooks.responseUpdate.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => { hooks.responseUpdate.tapAsync('messageManagementTool', async (context: AIResponseContext, callback) => {
try { try {
const { handlerContext, response } = context; const { agentFrameworkContext, response } = context;
if (response.status === 'update' && response.content) { if (response.status === 'update' && response.content) {
// Find or create AI response message in agent's message array // 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, (message) => message.role === 'assistant' && !message.metadata?.isComplete,
); );
@ -103,7 +103,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
const now = new Date(); const now = new Date();
aiMessage = { aiMessage = {
id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
role: 'assistant', role: 'assistant',
content: response.content, content: response.content,
created: now, created: now,
@ -111,7 +111,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
metadata: { isComplete: false }, metadata: { isComplete: false },
duration: undefined, // AI responses persist indefinitely by default 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 // Persist immediately so DB timestamp reflects conversation order
try { try {
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance); const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
@ -132,7 +132,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
// Update UI using the agent instance service // Update UI using the agent instance service
try { try {
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance); const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id); agentInstanceService.debounceUpdateMessage(aiMessage, agentFrameworkContext.agent.id);
} catch (serviceError) { } catch (serviceError) {
logger.warn('Failed to update UI for streaming message', { logger.warn('Failed to update UI for streaming message', {
error: serviceError, error: serviceError,
@ -150,13 +150,13 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
}); });
// Handle AI response completion // Handle AI response completion
hooks.responseComplete.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => { hooks.responseComplete.tapAsync('messageManagementTool', async (context: AIResponseContext, callback) => {
try { try {
const { handlerContext, response } = context; const { agentFrameworkContext, response } = context;
if (response.status === 'done' && response.content) { if (response.status === 'done' && response.content) {
// Find and finalize AI response message // 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, (message) => message.role === 'assistant' && !message.metadata?.isComplete && !message.metadata?.isToolResult,
); );
@ -170,7 +170,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
const nowFinal = new Date(); const nowFinal = new Date();
aiMessage = { aiMessage = {
id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
role: 'assistant', role: 'assistant',
content: response.content, content: response.content,
created: nowFinal, created: nowFinal,
@ -180,7 +180,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
}, },
duration: undefined, // Default duration for AI responses 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 // Get the agent instance service for persistence and UI updates
@ -191,7 +191,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
// Final UI update // Final UI update
try { try {
agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id); agentInstanceService.debounceUpdateMessage(aiMessage, agentFrameworkContext.agent.id);
} catch (serviceError) { } catch (serviceError) {
logger.warn('Failed to update UI for completed message', { logger.warn('Failed to update UI for completed message', {
error: serviceError, error: serviceError,
@ -215,12 +215,12 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
}); });
// Handle tool result messages persistence and UI updates // 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 { try {
const { handlerContext } = context; const { agentFrameworkContext } = context;
// Find newly added tool result messages that need to be persisted // 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, (message) => message.metadata?.isToolResult && !message.metadata.isPersisted,
); );
@ -233,7 +233,7 @@ export const messageManagementPlugin: PromptConcatPlugin = (hooks) => {
await agentInstanceService.saveUserMessage(message); await agentInstanceService.saveUserMessage(message);
// Update UI // Update UI
agentInstanceService.debounceUpdateMessage(message, handlerContext.agent.id); agentInstanceService.debounceUpdateMessage(message, agentFrameworkContext.agent.id);
// Mark as persisted to avoid duplicate saves // Mark as persisted to avoid duplicate saves
message.metadata = { ...message.metadata, isPersisted: true, uiUpdated: true }; message.metadata = { ...message.metadata, isPersisted: true, uiUpdated: true };

View file

@ -10,7 +10,7 @@ import { findPromptById } from '../promptConcat/promptConcat';
import type { IPrompt } from '../promptConcat/promptConcatSchema'; import type { IPrompt } from '../promptConcat/promptConcatSchema';
import { filterMessagesByDuration } from '../utilities/messageDurationFilter'; import { filterMessagesByDuration } from '../utilities/messageDurationFilter';
import { normalizeRole } from '../utilities/normalizeRole'; import { normalizeRole } from '../utilities/normalizeRole';
import { AgentResponse, PromptConcatPlugin, ResponseHookContext } from './types'; import { AgentResponse, PromptConcatTool, ResponseHookContext } from './types';
const t = identity; const t = identity;
@ -76,17 +76,17 @@ export function getDynamicPositionParameterSchema() {
* Full replacement plugin * Full replacement plugin
* Replaces target content with content from specified source * 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 // Normalize an AgentInstanceMessage role to Prompt role
hooks.processPrompts.tapAsync('fullReplacementPlugin', async (context, callback) => { hooks.processPrompts.tapAsync('fullReplacementTool', async (context, callback) => {
const { pluginConfig, prompts, messages } = context; const { toolConfig, prompts, messages } = context;
if (pluginConfig.pluginId !== 'fullReplacement' || !pluginConfig.fullReplacementParam) { if (toolConfig.toolId !== 'fullReplacement' || !toolConfig.fullReplacementParam) {
callback(); callback();
return; return;
} }
const fullReplacementConfig = pluginConfig.fullReplacementParam; const fullReplacementConfig = toolConfig.fullReplacementParam;
if (!fullReplacementConfig) { if (!fullReplacementConfig) {
callback(); callback();
return; return;
@ -98,7 +98,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => {
if (!found) { if (!found) {
logger.warn('Target prompt not found for fullReplacement', { logger.warn('Target prompt not found for fullReplacement', {
targetId, targetId,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
return; return;
@ -173,16 +173,16 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => {
}); });
// Handle response phase for llmResponse source type // 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 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(); callback();
return; return;
} }
const fullReplacementParameter = pluginConfig.fullReplacementParam; const fullReplacementParameter = toolConfig.fullReplacementParam;
if (!fullReplacementParameter) { if (!fullReplacementParameter) {
callback(); callback();
return; return;
@ -202,7 +202,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => {
if (!found) { if (!found) {
logger.warn('Full replacement target not found in responses', { logger.warn('Full replacement target not found in responses', {
targetId, targetId,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
return; return;
@ -212,7 +212,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Replacing target with LLM response', { logger.debug('Replacing target with LLM response', {
targetId, targetId,
responseLength: llmResponse.length, responseLength: llmResponse.length,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
found.text = llmResponse; found.text = llmResponse;
@ -220,6 +220,7 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Full replacement completed in response phase', { logger.debug('Full replacement completed in response phase', {
targetId, targetId,
sourceType, sourceType,
toolId: toolConfig.id,
}); });
callback(); callback();
@ -230,16 +231,16 @@ export const fullReplacementPlugin: PromptConcatPlugin = (hooks) => {
* Dynamic position plugin * Dynamic position plugin
* Inserts content at a specific position relative to a target element * Inserts content at a specific position relative to a target element
*/ */
export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => { export const dynamicPositionTool: PromptConcatTool = (hooks) => {
hooks.processPrompts.tapAsync('dynamicPositionPlugin', async (context, callback) => { hooks.processPrompts.tapAsync('dynamicPositionTool', async (context, callback) => {
const { pluginConfig, prompts } = context; const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'dynamicPosition' || !pluginConfig.dynamicPositionParam || !pluginConfig.content) { if (toolConfig.toolId !== 'dynamicPosition' || !toolConfig.dynamicPositionParam || !toolConfig.content) {
callback(); callback();
return; return;
} }
const dynamicPositionConfig = pluginConfig.dynamicPositionParam; const dynamicPositionConfig = toolConfig.dynamicPositionParam;
if (!dynamicPositionConfig) { if (!dynamicPositionConfig) {
callback(); callback();
return; return;
@ -251,7 +252,7 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => {
if (!found) { if (!found) {
logger.warn('Target prompt not found for dynamicPosition', { logger.warn('Target prompt not found for dynamicPosition', {
targetId, targetId,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
return; return;
@ -259,9 +260,9 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => {
// Create new prompt part // Create new prompt part
const newPart: IPrompt = { const newPart: IPrompt = {
id: `dynamic-${pluginConfig.id}-${Date.now()}`, id: `dynamic-${toolConfig.id}-${Date.now()}`,
caption: pluginConfig.caption || 'Dynamic Content', caption: toolConfig.caption || 'Dynamic Content',
text: pluginConfig.content, text: toolConfig.content,
}; };
// Insert based on position // Insert based on position
@ -288,7 +289,8 @@ export const dynamicPositionPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Dynamic position insertion completed', { logger.debug('Dynamic position insertion completed', {
targetId, targetId,
position, position,
contentLength: pluginConfig.content.length, contentLength: toolConfig.content.length,
toolId: toolConfig.id,
}); });
callback(); callback();

View file

@ -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<string, z.ZodType>();
/**
* Registry for tool metadata
*/
const toolMetadata = new Map<string, {
displayName: string;
description: string;
}>();
/**
* 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<string, z.ZodType> = {};
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 string> = T extends keyof ReturnType<typeof createToolParameterTypes> ? ReturnType<typeof createToolParameterTypes>[T] : never;
/**
* Create type definitions for all registered tool parameters
* This is used internally for type inference
*/
export function createToolParameterTypes() {
const types: Record<string, unknown> = {};
for (const toolId of getAllRegisteredToolIds()) {
const schema = getToolParameterSchema(toolId);
if (schema) {
types[toolId] = schema;
}
}
return types as Record<string, z.ZodType>;
}

View file

@ -1,9 +1,9 @@
import { ToolCallingMatch } from '@services/agentDefinition/interface'; 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 { AgentInstanceMessage } from '@services/agentInstance/interface';
import { AIStreamResponse } from '@services/externalAPI/interface'; import { AIStreamResponse } from '@services/externalAPI/interface';
import { AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable'; import { AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable';
import type { IPrompt, IPromptConcatPlugin } from '../promptConcat/promptConcatSchema/'; import type { IPrompt, IPromptConcatTool } from '../promptConcat/promptConcatSchema';
/** /**
* Next round target options * 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" 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 */ /** Whether to yield next round to continue processing */
yieldNextRoundTo?: YieldNextRoundTarget; yieldNextRoundTo?: YieldNextRoundTarget;
/** New user message to append */ /** 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 { export interface BaseToolContext {
/** Handler context */ /** Framework context */
handlerContext: AgentHandlerContext; agentFrameworkContext: AgentFrameworkContext;
/** Additional context data */ /** Additional context data */
metadata?: Record<string, unknown>; metadata?: Record<string, unknown>;
/** Actions set by plugins during processing */ /** Actions set by tools during processing */
actions?: PluginActions; actions?: ToolActions;
} }
/** /**
* Context for prompt processing hooks (processPrompts, finalizePrompts) * Context for prompt processing hooks (processPrompts, finalizePrompts)
*/ */
export interface PromptConcatHookContext extends BasePluginContext { export interface PromptConcatHookContext extends BaseToolContext {
/** Array of agent instance messages for context */ /** Array of agent instance messages for context */
messages: AgentInstanceMessage[]; messages: AgentInstanceMessage[];
/** Current prompt tree */ /** Current prompt tree */
prompts: IPrompt[]; prompts: IPrompt[];
/** Plugin configuration */ /** Tool configuration */
pluginConfig: IPromptConcatPlugin; toolConfig: IPromptConcatTool;
} }
/** /**
@ -59,11 +59,11 @@ export interface PostProcessContext extends PromptConcatHookContext {
/** /**
* Context for AI response hooks (responseUpdate, responseComplete) * Context for AI response hooks (responseUpdate, responseComplete)
*/ */
export interface AIResponseContext extends BasePluginContext { export interface AIResponseContext extends BaseToolContext {
/** Plugin configuration - for backward compatibility */ /** Tool configuration - for backward compatibility */
pluginConfig: IPromptConcatPlugin; toolConfig: IPromptConcatTool;
/** Complete handler configuration - allows plugins to access all configs */ /** Complete framework configuration - allows tools to access all configs */
handlerConfig?: { plugins?: Array<{ pluginId: string; [key: string]: unknown }> }; agentFrameworkConfig?: { plugins?: Array<{ toolId: string; [key: string]: unknown }> };
/** AI streaming response */ /** AI streaming response */
response: AIStreamResponse; response: AIStreamResponse;
/** Current request ID */ /** Current request ID */
@ -75,7 +75,7 @@ export interface AIResponseContext extends BasePluginContext {
/** /**
* Context for user message hooks * Context for user message hooks
*/ */
export interface UserMessageContext extends BasePluginContext { export interface UserMessageContext extends BaseToolContext {
/** User message content */ /** User message content */
content: { text: string; file?: File }; content: { text: string; file?: File };
/** Generated message ID */ /** Generated message ID */
@ -87,7 +87,7 @@ export interface UserMessageContext extends BasePluginContext {
/** /**
* Context for agent status hooks * Context for agent status hooks
*/ */
export interface AgentStatusContext extends BasePluginContext { export interface AgentStatusContext extends BaseToolContext {
/** New status state */ /** New status state */
status: { status: {
state: 'working' | 'completed' | 'failed' | 'canceled'; state: 'working' | 'completed' | 'failed' | 'canceled';
@ -98,7 +98,7 @@ export interface AgentStatusContext extends BasePluginContext {
/** /**
* Context for tool execution hooks * Context for tool execution hooks
*/ */
export interface ToolExecutionContext extends BasePluginContext { export interface ToolExecutionContext extends BaseToolContext {
/** Tool execution result */ /** Tool execution result */
toolResult: { toolResult: {
success: boolean; 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 * Handles both prompt processing and agent lifecycle events
*/ */
export interface PromptConcatHooks { 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;

View file

@ -16,7 +16,7 @@ import { z } from 'zod/v4';
import type { AgentInstanceMessage, IAgentInstanceService } from '../interface'; import type { AgentInstanceMessage, IAgentInstanceService } from '../interface';
import { findPromptById } from '../promptConcat/promptConcat'; import { findPromptById } from '../promptConcat/promptConcat';
import { schemaToToolContent } from '../utilities/schemaToToolContent'; import { schemaToToolContent } from '../utilities/schemaToToolContent';
import type { PromptConcatPlugin } from './types'; import type { PromptConcatTool } from './types';
/** /**
* Wiki Operation Parameter Schema * Wiki Operation Parameter Schema
@ -101,17 +101,17 @@ const WikiOperationToolParameterSchema = z.object({
* Wiki Operation plugin - Prompt processing * Wiki Operation plugin - Prompt processing
* Handles tool list injection for wiki operation functionality * Handles tool list injection for wiki operation functionality
*/ */
export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => { export const wikiOperationTool: PromptConcatTool = (hooks) => {
// First tapAsync: Tool list injection // First tapAsync: Tool list injection
hooks.processPrompts.tapAsync('wikiOperationPlugin-toolList', async (context, callback) => { hooks.processPrompts.tapAsync('wikiOperationTool-toolList', async (context, callback) => {
const { pluginConfig, prompts } = context; const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'wikiOperation' || !pluginConfig.wikiOperationParam) { if (toolConfig.toolId !== 'wikiOperation' || !toolConfig.wikiOperationParam) {
callback(); callback();
return; return;
} }
const wikiOperationParameter = pluginConfig.wikiOperationParam; const wikiOperationParameter = toolConfig.wikiOperationParam;
try { try {
// Handle tool list injection if toolListPosition is configured // Handle tool list injection if toolListPosition is configured
@ -121,7 +121,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
if (!toolListTarget) { if (!toolListTarget) {
logger.warn('Tool list target prompt not found', { logger.warn('Tool list target prompt not found', {
targetId: toolListPosition.targetId, targetId: toolListPosition.targetId,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
return; return;
@ -140,7 +140,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
} }
const insertIndex = toolListTarget.prompt.children.length; const insertIndex = toolListTarget.prompt.children.length;
toolListTarget.prompt.children.splice(insertIndex, 0, { toolListTarget.prompt.children.splice(insertIndex, 0, {
id: `wiki-operation-tool-${pluginConfig.id}`, id: `wiki-operation-tool-${toolConfig.id}`,
caption: 'Wiki Operation Tool', caption: 'Wiki Operation Tool',
text: wikiOperationToolContent, text: wikiOperationToolContent,
}); });
@ -149,7 +149,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
toolListTarget.prompt.children = []; toolListTarget.prompt.children = [];
} }
toolListTarget.prompt.children.unshift({ toolListTarget.prompt.children.unshift({
id: `wiki-operation-tool-${pluginConfig.id}`, id: `wiki-operation-tool-${toolConfig.id}`,
caption: 'Wiki Operation Tool', caption: 'Wiki Operation Tool',
text: wikiOperationToolContent, text: wikiOperationToolContent,
}); });
@ -161,7 +161,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Wiki operation tool list injected', { logger.debug('Wiki operation tool list injected', {
targetId: toolListPosition.targetId, targetId: toolListPosition.targetId,
position: toolListPosition.position, position: toolListPosition.position,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
} }
@ -169,20 +169,20 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
} catch (error) { } catch (error) {
logger.error('Error in wiki operation tool list injection', { logger.error('Error in wiki operation tool list injection', {
error, error,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
} }
}); });
// 2. Tool execution when AI response is complete // 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 { try {
const { handlerContext, response, handlerConfig } = context; const { agentFrameworkContext, response, agentFrameworkConfig } = context;
// Find this plugin's configuration from handlerConfig // Find this plugin's configuration import { AgentFrameworkConfig }
const wikiOperationPluginConfig = handlerConfig?.plugins?.find(p => p.pluginId === 'wikiOperation'); const wikiOperationToolConfig = agentFrameworkConfig?.plugins?.find((p: { toolId: string; [key: string]: unknown }) => p.toolId === 'wikiOperation');
const wikiOperationParameter = wikiOperationPluginConfig?.wikiOperationParam as { toolResultDuration?: number } | undefined; const wikiOperationParameter = wikiOperationToolConfig?.wikiOperationParam as { toolResultDuration?: number } | undefined;
const toolResultDuration = wikiOperationParameter?.toolResultDuration || 1; // Default to 1 round const toolResultDuration = wikiOperationParameter?.toolResultDuration || 1; // Default to 1 round
if (response.status !== 'done' || !response.content) { if (response.status !== 'done' || !response.content) {
@ -200,19 +200,19 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Wiki operation tool call detected', { logger.debug('Wiki operation tool call detected', {
toolId: toolMatch.toolId, toolId: toolMatch.toolId,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
// Set duration=1 for the AI message containing the tool call // 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) // 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) { if (aiMessages.length > 0) {
const latestAiMessage = aiMessages[aiMessages.length - 1]; const latestAiMessage = aiMessages[aiMessages.length - 1];
latestAiMessage.duration = toolResultDuration; latestAiMessage.duration = toolResultDuration;
logger.debug('Set AI message duration for tool call', { logger.debug('Set AI message duration for tool call', {
messageId: latestAiMessage.id, messageId: latestAiMessage.id,
duration: toolResultDuration, duration: toolResultDuration,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
} }
@ -220,7 +220,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
try { try {
logger.debug('Parsing wiki operation tool parameters', { logger.debug('Parsing wiki operation tool parameters', {
toolMatch: toolMatch.parameters, toolMatch: toolMatch.parameters,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
// Use parameters returned by matchToolCalling directly. Let zod schema validate. // Use parameters returned by matchToolCalling directly. Let zod schema validate.
@ -253,7 +253,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
workspaceName, workspaceName,
operation, operation,
title, title,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
let result: string; let result: string;
@ -293,7 +293,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
workspaceID, workspaceID,
operation, operation,
title, title,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
// Format the tool result for display // Format the tool result for display
@ -307,8 +307,8 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Wiki operation setting yieldNextRoundTo=self', { logger.debug('Wiki operation setting yieldNextRoundTo=self', {
toolId: 'wiki-operation', toolId: 'wiki-operation',
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
messageCount: handlerContext.agent.messages.length, messageCount: agentFrameworkContext.agent.messages.length,
toolResultPreview: toolResultText.slice(0, 200), toolResultPreview: toolResultText.slice(0, 200),
}); });
@ -316,7 +316,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
const toolResultTime = new Date(); const toolResultTime = new Date();
const toolResultMessage: AgentInstanceMessage = { const toolResultMessage: AgentInstanceMessage = {
id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, 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 role: 'tool', // Tool result message
content: toolResultText, content: toolResultText,
modified: toolResultTime, modified: toolResultTime,
@ -331,7 +331,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
artificialOrder: Date.now() + 10, // Additional ordering hint 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 // Persist tool result immediately so DB ordering matches in-memory order
try { try {
@ -347,7 +347,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
// Signal that tool was executed AFTER adding and persisting the message // Signal that tool was executed AFTER adding and persisting the message
await hooks.toolExecuted.promise({ await hooks.toolExecuted.promise({
handlerContext, agentFrameworkContext,
toolResult: { toolResult: {
success: true, success: true,
data: result, data: result,
@ -370,7 +370,7 @@ export const wikiOperationPlugin: PromptConcatPlugin = (hooks) => {
} catch (error) { } catch (error) {
logger.error('Wiki operation tool execution failed', { logger.error('Wiki operation tool execution failed', {
error, error,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
toolParameters: toolMatch.parameters, toolParameters: toolMatch.parameters,
}); });
@ -389,7 +389,7 @@ Error: ${error instanceof Error ? error.message : String(error)}
const errorResultTime = new Date(); const errorResultTime = new Date();
const errorResultMessage: AgentInstanceMessage = { const errorResultMessage: AgentInstanceMessage = {
id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, 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 role: 'tool', // Tool error message
content: errorMessage, content: errorMessage,
modified: errorResultTime, 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 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 // Signal that tool was executed (with error) AFTER adding the message
await hooks.toolExecuted.promise({ await hooks.toolExecuted.promise({
handlerContext, agentFrameworkContext,
toolResult: { toolResult: {
success: false, success: false,
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),

View file

@ -19,7 +19,7 @@ import { findPromptById } from '../promptConcat/promptConcat';
import type { AiAPIConfig } from '../promptConcat/promptConcatSchema'; import type { AiAPIConfig } from '../promptConcat/promptConcatSchema';
import type { IPrompt } from '../promptConcat/promptConcatSchema'; import type { IPrompt } from '../promptConcat/promptConcatSchema';
import { schemaToToolContent } from '../utilities/schemaToToolContent'; import { schemaToToolContent } from '../utilities/schemaToToolContent';
import type { PromptConcatPlugin } from './types'; import type { PromptConcatTool } from './types';
/** /**
* Wiki Search Parameter Schema * Wiki Search Parameter Schema
@ -488,17 +488,17 @@ async function executeWikiUpdateEmbeddingsTool(
* Wiki Search plugin - Prompt processing * Wiki Search plugin - Prompt processing
* Handles tool list injection for wiki search and update embeddings functionality * 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 // First tapAsync: Tool list injection
hooks.processPrompts.tapAsync('wikiSearchPlugin-toolList', async (context, callback) => { hooks.processPrompts.tapAsync('wikiSearchTool-toolList', async (context, callback) => {
const { pluginConfig, prompts } = context; const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'wikiSearch' || !pluginConfig.wikiSearchParam) { if (toolConfig.toolId !== 'wikiSearch' || !toolConfig.wikiSearchParam) {
callback(); callback();
return; return;
} }
const wikiSearchParameter = pluginConfig.wikiSearchParam; const wikiSearchParameter = toolConfig.wikiSearchParam;
try { try {
// Handle tool list injection if toolListPosition is configured // Handle tool list injection if toolListPosition is configured
@ -508,7 +508,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
if (!toolListTarget) { if (!toolListTarget) {
logger.warn('Tool list target prompt not found', { logger.warn('Tool list target prompt not found', {
targetId: toolListPosition.targetId, targetId: toolListPosition.targetId,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
return; return;
@ -544,7 +544,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
targetId: toolListPosition.targetId, targetId: toolListPosition.targetId,
position: toolListPosition.position, position: toolListPosition.position,
toolCount: 2, // wiki-search and wiki-update-embeddings 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) { } catch (error) {
logger.error('Error in wiki search tool list injection', { logger.error('Error in wiki search tool list injection', {
error, error,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
} }
}); });
// 2. Tool execution when AI response is complete // 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 { try {
const { handlerContext, response, handlerConfig } = context; const { agentFrameworkContext, response, agentFrameworkConfig } = context;
// Find this plugin's configuration from handlerConfig // Find this plugin's configuration import { AgentFrameworkConfig }
const wikiSearchPluginConfig = handlerConfig?.plugins?.find(p => p.pluginId === 'wikiSearch'); const wikiSearchToolConfig = agentFrameworkConfig?.plugins?.find((p: { toolId: string; [key: string]: unknown }) => p.toolId === 'wikiSearch');
const wikiSearchParameter = wikiSearchPluginConfig?.wikiSearchParam as { toolResultDuration?: number } | undefined; const wikiSearchParameter = wikiSearchToolConfig?.wikiSearchParam as { toolResultDuration?: number } | undefined;
const toolResultDuration = wikiSearchParameter?.toolResultDuration || 1; // Default to 1 round const toolResultDuration = wikiSearchParameter?.toolResultDuration || 1; // Default to 1 round
if (response.status !== 'done' || !response.content) { if (response.status !== 'done' || !response.content) {
@ -583,12 +583,12 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Wiki tool call detected', { logger.debug('Wiki tool call detected', {
toolId: toolMatch.toolId, toolId: toolMatch.toolId,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
// Set duration=1 for the AI message containing the tool call // 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) // 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) { if (aiMessages.length > 0) {
const latestAiMessage = aiMessages[aiMessages.length - 1]; const latestAiMessage = aiMessages[aiMessages.length - 1];
if (latestAiMessage.content === response.content) { if (latestAiMessage.content === response.content) {
@ -614,7 +614,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
} }
// Also update UI immediately // 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', { logger.debug('Set duration=1 for AI tool call message', {
messageId: latestAiMessage.id, messageId: latestAiMessage.id,
@ -626,10 +626,10 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
// Execute the appropriate tool // Execute the appropriate tool
try { try {
// Check if cancelled before starting tool execution // Check if cancelled before starting tool execution
if (handlerContext.isCancelled()) { if (agentFrameworkContext.isCancelled()) {
logger.debug('Wiki tool cancelled before execution', { logger.debug('Wiki tool cancelled before execution', {
toolId: toolMatch.toolId, toolId: toolMatch.toolId,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
callback(); callback();
return; return;
@ -644,9 +644,9 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
result = await executeWikiSearchTool( result = await executeWikiSearchTool(
validatedParameters, validatedParameters,
{ {
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
messageId: handlerContext.agent.messages[handlerContext.agent.messages.length - 1]?.id, messageId: agentFrameworkContext.agent.messages[agentFrameworkContext.agent.messages.length - 1]?.id,
config: handlerContext.agent.aiApiConfig as AiAPIConfig | undefined, config: agentFrameworkContext.agent.aiApiConfig as AiAPIConfig | undefined,
}, },
); );
} else { } else {
@ -655,18 +655,18 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
result = await executeWikiUpdateEmbeddingsTool( result = await executeWikiUpdateEmbeddingsTool(
validatedParameters, validatedParameters,
{ {
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
messageId: handlerContext.agent.messages[handlerContext.agent.messages.length - 1]?.id, messageId: agentFrameworkContext.agent.messages[agentFrameworkContext.agent.messages.length - 1]?.id,
aiConfig: handlerContext.agent.aiApiConfig, aiConfig: agentFrameworkContext.agent.aiApiConfig,
}, },
); );
} }
// Check if cancelled after tool execution // Check if cancelled after tool execution
if (handlerContext.isCancelled()) { if (agentFrameworkContext.isCancelled()) {
logger.debug('Wiki tool cancelled after execution', { logger.debug('Wiki tool cancelled after execution', {
toolId: toolMatch.toolId, toolId: toolMatch.toolId,
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
}); });
callback(); callback();
return; return;
@ -692,8 +692,8 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Wiki search setting yieldNextRoundTo=self', { logger.debug('Wiki search setting yieldNextRoundTo=self', {
toolId: 'wiki-search', toolId: 'wiki-search',
agentId: handlerContext.agent.id, agentId: agentFrameworkContext.agent.id,
messageCount: handlerContext.agent.messages.length, messageCount: agentFrameworkContext.agent.messages.length,
toolResultPreview: toolResultText.slice(0, 200), toolResultPreview: toolResultText.slice(0, 200),
}); });
@ -701,7 +701,7 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
const nowTool = new Date(); const nowTool = new Date();
const toolResultMessage: AgentInstanceMessage = { const toolResultMessage: AgentInstanceMessage = {
id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, 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 role: 'tool', // Tool result message
content: toolResultText, content: toolResultText,
created: nowTool, created: nowTool,
@ -717,13 +717,13 @@ export const wikiSearchPlugin: PromptConcatPlugin = (hooks) => {
artificialOrder: Date.now() + 10, // Additional ordering hint 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 // Do not persist immediately here. Let messageManagementPlugin handle persistence
// Signal that tool was executed AFTER adding and persisting the message // Signal that tool was executed AFTER adding and persisting the message
await hooks.toolExecuted.promise({ await hooks.toolExecuted.promise({
handlerContext, agentFrameworkContext,
toolResult: { toolResult: {
success: true, success: true,
data: result.success ? result.data : result.error, data: result.success ? result.data : result.error,
@ -765,7 +765,7 @@ Error: ${error instanceof Error ? error.message : String(error)}
const nowError = new Date(); const nowError = new Date();
const errorResultMessage: AgentInstanceMessage = { const errorResultMessage: AgentInstanceMessage = {
id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, 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 role: 'tool', // Tool error message
content: errorMessage, content: errorMessage,
created: nowError, 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 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 // Do not persist immediately; let messageManagementPlugin handle it during toolExecuted
await hooks.toolExecuted.promise({ await hooks.toolExecuted.promise({
handlerContext, agentFrameworkContext,
toolResult: { toolResult: {
success: false, success: false,
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),

View file

@ -45,23 +45,23 @@ import type { IWorkspaceService } from '@services/workspaces/interface';
import { isWikiWorkspace } from '@services/workspaces/interface'; import { isWikiWorkspace } from '@services/workspaces/interface';
import { findPromptById } from '../promptConcat/promptConcat'; import { findPromptById } from '../promptConcat/promptConcat';
import type { PromptConcatPlugin } from './types'; import type { PromptConcatTool } from './types';
/** /**
* Workspaces List plugin - Prompt processing * Workspaces List plugin - Prompt processing
* Handles injection of available wiki workspaces list * Handles injection of available wiki workspaces list
*/ */
export const workspacesListPlugin: PromptConcatPlugin = (hooks) => { export const workspacesListTool: PromptConcatTool = (hooks) => {
// Tool list injection // Tool list injection
hooks.processPrompts.tapAsync('workspacesListPlugin-injection', async (context, callback) => { hooks.processPrompts.tapAsync('workspacesListTool-injection', async (context, callback) => {
const { pluginConfig, prompts } = context; const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'workspacesList' || !pluginConfig.workspacesListParam) { if (toolConfig.toolId !== 'workspacesList' || !toolConfig.workspacesListParam) {
callback(); callback();
return; return;
} }
const workspacesListParameter = pluginConfig.workspacesListParam; const workspacesListParameter = toolConfig.workspacesListParam;
try { try {
// Handle workspaces list injection if targetId is configured // Handle workspaces list injection if targetId is configured
@ -70,7 +70,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => {
if (!target) { if (!target) {
logger.warn('Workspaces list target prompt not found', { logger.warn('Workspaces list target prompt not found', {
targetId: workspacesListParameter.targetId, targetId: workspacesListParameter.targetId,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
return; return;
@ -96,7 +96,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => {
} }
const insertIndex = target.prompt.children.length; const insertIndex = target.prompt.children.length;
target.prompt.children.splice(insertIndex, 0, { target.prompt.children.splice(insertIndex, 0, {
id: `workspaces-list-${pluginConfig.id}`, id: `workspaces-list-${toolConfig.id}`,
caption: 'Available Workspaces', caption: 'Available Workspaces',
text: workspacesListContent, text: workspacesListContent,
}); });
@ -105,7 +105,7 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => {
target.prompt.children = []; target.prompt.children = [];
} }
target.prompt.children.unshift({ target.prompt.children.unshift({
id: `workspaces-list-${pluginConfig.id}`, id: `workspaces-list-${toolConfig.id}`,
caption: 'Available Workspaces', caption: 'Available Workspaces',
text: workspacesListContent, text: workspacesListContent,
}); });
@ -117,12 +117,12 @@ export const workspacesListPlugin: PromptConcatPlugin = (hooks) => {
logger.debug('Workspaces list injected successfully', { logger.debug('Workspaces list injected successfully', {
targetId: workspacesListParameter.targetId, targetId: workspacesListParameter.targetId,
position: workspacesListParameter.position, position: workspacesListParameter.position,
pluginId: pluginConfig.id, toolId: toolConfig.id,
workspaceCount: wikiWorkspaces.length, workspaceCount: wikiWorkspaces.length,
}); });
} else { } else {
logger.debug('No wiki workspaces found to inject', { 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) { } catch (error) {
logger.error('Error in workspaces list injection', { logger.error('Error in workspaces list injection', {
error, error,
pluginId: pluginConfig.id, toolId: toolConfig.id,
}); });
callback(); callback();
} }

View file

@ -14,8 +14,8 @@ export function createAgentInstanceData(agentDefinition: {
name: string; name: string;
avatarUrl?: string; avatarUrl?: string;
aiApiConfig?: Record<string, unknown>; aiApiConfig?: Record<string, unknown>;
handlerConfig?: Record<string, unknown>; agentFrameworkConfig?: Record<string, unknown>;
handlerID?: string; agentFrameworkID?: string;
}): { }): {
instanceData: Omit<AgentInstance, 'created' | 'modified'>; instanceData: Omit<AgentInstance, 'created' | 'modified'>;
instanceId: string; instanceId: string;
@ -31,7 +31,7 @@ export function createAgentInstanceData(agentDefinition: {
}; };
// Extract necessary fields from agent definition // Extract necessary fields from agent definition
const { avatarUrl, aiApiConfig, handlerID } = agentDefinition; const { avatarUrl, aiApiConfig, agentFrameworkID } = agentDefinition;
const instanceData = { const instanceData = {
id: instanceId, id: instanceId,
@ -40,9 +40,9 @@ export function createAgentInstanceData(agentDefinition: {
status: initialStatus, status: initialStatus,
avatarUrl, avatarUrl,
aiApiConfig, aiApiConfig,
// Don't copy handlerConfig to instance - it should fallback to definition // Don't copy agentFrameworkConfig to instance - it should fallback to definition
handlerConfig: undefined, agentFrameworkConfig: undefined,
handlerID, agentFrameworkID,
messages: [], messages: [],
closed: false, closed: false,
}; };
@ -119,6 +119,6 @@ export const AGENT_INSTANCE_FIELDS = [
'modified', 'modified',
'avatarUrl', 'avatarUrl',
'aiApiConfig', 'aiApiConfig',
'handlerConfig', 'agentFrameworkConfig',
'closed', 'closed',
] as const; ] as const;

View file

@ -29,11 +29,11 @@ export class AgentDefinitionEntity implements Partial<AgentDefinition> {
/** Agent handler function ID, nullable indicates using default handler */ /** Agent handler function ID, nullable indicates using default handler */
@Column({ nullable: true }) @Column({ nullable: true })
handlerID?: string; agentFrameworkID?: string;
/** Agent handler configuration parameters, stored as JSON */ /** Agent handler configuration parameters, stored as JSON */
@Column({ type: 'simple-json', nullable: true }) @Column({ type: 'simple-json', nullable: true })
handlerConfig?: Record<string, unknown>; agentFrameworkConfig?: Record<string, unknown>;
/** Agent's AI API configuration, can override global default config */ /** Agent's AI API configuration, can override global default config */
@Column({ type: 'simple-json', nullable: true }) @Column({ type: 'simple-json', nullable: true })
@ -88,7 +88,7 @@ export class AgentInstanceEntity implements Partial<AgentInstance> {
/** Agent handler configuration parameters, inherited from AgentDefinition */ /** Agent handler configuration parameters, inherited from AgentDefinition */
@Column({ type: 'simple-json', nullable: true }) @Column({ type: 'simple-json', nullable: true })
handlerConfig?: Record<string, unknown>; agentFrameworkConfig?: Record<string, unknown>;
@Column({ default: false }) @Column({ default: false })
closed: boolean = false; closed: boolean = false;

View file

@ -1,5 +1,5 @@
import type { AgentDefinition } from '@services/agentDefinition/interface'; 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 { container } from '@services/container';
import type { IDatabaseService } from '@services/database/interface'; import type { IDatabaseService } from '@services/database/interface';
import { AgentDefinitionEntity } from '@services/database/schema/agent'; import { AgentDefinitionEntity } from '@services/database/schema/agent';

View file

@ -105,7 +105,7 @@ export async function registerMenu(): Promise<void> {
dir: activeWorkspace.wikiFolderLocation, dir: activeWorkspace.wikiFolderLocation,
commitOnly: false, commitOnly: false,
commitMessage: i18n.t('LOG.CommitBackupMessage'), commitMessage: i18n.t('LOG.CommitBackupMessage'),
remoteUrl: activeWorkspace.gitUrl, remoteUrl: activeWorkspace.gitUrl ?? undefined,
userInfo, userInfo,
}); });
} }
@ -127,7 +127,7 @@ export async function registerMenu(): Promise<void> {
dir: activeWorkspace.wikiFolderLocation, dir: activeWorkspace.wikiFolderLocation,
commitOnly: false, commitOnly: false,
// Don't provide commitMessage to trigger AI generation // Don't provide commitMessage to trigger AI generation
remoteUrl: activeWorkspace.gitUrl, remoteUrl: activeWorkspace.gitUrl ?? undefined,
userInfo, userInfo,
}); });
} }
@ -151,7 +151,7 @@ export async function registerMenu(): Promise<void> {
dir: activeWorkspace.wikiFolderLocation, dir: activeWorkspace.wikiFolderLocation,
commitOnly: false, commitOnly: false,
commitMessage: i18n.t('LOG.CommitBackupMessage'), commitMessage: i18n.t('LOG.CommitBackupMessage'),
remoteUrl: activeWorkspace.gitUrl, remoteUrl: activeWorkspace.gitUrl ?? undefined,
userInfo, userInfo,
}); });
} }

View file

@ -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<string, unknown>;
handleConfigChange: (newConfig: AgentFrameworkConfig) => Promise<void>;
}
export const useAgentFrameworkConfigManagement = ({ agentDefId, agentId }: useAgentFrameworkConfigManagementProps = {}): UseAgentFrameworkConfigManagementResult => {
const [loading, setLoading] = useState(true);
const [config, setConfig] = useState<AgentFrameworkConfig | undefined>(undefined);
const [schema, setSchema] = useState<Record<string, unknown> | 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<ReturnType<typeof window.service.agentDefinition.getAgentDef>> | 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,
};
};

View file

@ -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<string, unknown>;
handleConfigChange: (newConfig: HandlerConfig) => Promise<void>;
}
export const useHandlerConfigManagement = ({ agentDefId, agentId }: UseHandlerConfigManagementProps = {}): UseHandlerConfigManagementResult => {
const [loading, setLoading] = useState(true);
const [config, setConfig] = useState<HandlerConfig | undefined>(undefined);
const [schema, setSchema] = useState<Record<string, unknown> | 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<ReturnType<typeof window.service.agentDefinition.getAgentDef>> | 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,
};
};