import { WikiChannel } from '@/constants/channels'; import { container } from '@services/container'; import { logger } from '@services/libs/log'; import serviceIdentifier from '@services/serviceIdentifier'; import type { IWikiService } from '@services/wiki/interface'; import type { IWorkspaceService } from '@services/workspaces/interface'; import { isWikiWorkspace } from '@services/workspaces/interface'; import type { ITiddlerFields } from 'tiddlywiki'; import { AgentDefinition, AgentToolConfig } from './interface'; /** * Get agent templates from wiki workspaces with tag [$:/tags/AI/Template] */ export async function getWikiAgentTemplates(): Promise { try { // Get services from container const workspaceService = container.get(serviceIdentifier.Workspace); const wikiService = container.get(serviceIdentifier.Wiki); // Get all active main wiki workspaces const workspaces = await workspaceService.getWorkspacesAsList(); const activeMainWikiWorkspaces = workspaces.filter( (workspace) => isWikiWorkspace(workspace) && workspace.active && !workspace.isSubWiki, ); const wikiTemplates: AgentDefinition[] = []; // Process each active main workspace for (const workspace of activeMainWikiWorkspaces) { try { // Query for tiddlers with the AI Template tag const templateTiddlers = await wikiService.wikiOperationInServer( WikiChannel.getTiddlersAsJson, workspace.id, ['[tag[$:/tags/AI/Template]]'], ); if (Array.isArray(templateTiddlers)) { for (const tiddler of templateTiddlers) { const agentTemplate = validateAndConvertWikiTiddlerToAgentTemplate(tiddler, workspace.name); if (agentTemplate) { wikiTemplates.push(agentTemplate); } } } } catch (workspaceError) { logger.warn(`Failed to get templates from workspace ${workspace.name}:`, workspaceError); // Continue with other workspaces } } return wikiTemplates; } catch (error) { logger.error(`Failed to get wiki agent templates: ${error as Error}`); return []; // Return empty array on error to not break the main functionality } } /** * Validate and convert a wiki tiddler to an AgentDefinition template */ export function validateAndConvertWikiTiddlerToAgentTemplate( tiddler: ITiddlerFields, workspaceName?: string, ): AgentDefinition | null { try { // Basic validation if (!tiddler || !tiddler.title || typeof tiddler.text !== 'string') { return null; } // Helper function to safely get string value from tiddler field const getStringField = (field: unknown, fallback = ''): string => { if (typeof field === 'string') return field; if (field && typeof field === 'object') return JSON.stringify(field); return fallback; }; // Helper function to safely parse JSON field const parseJsonField = (field: unknown): Record | unknown[] | undefined => { if (typeof field === 'string') { try { const parsed = JSON.parse(field) as unknown; if (typeof parsed === 'object' && parsed !== null) { return parsed as Record | unknown[]; } return undefined; } catch { return undefined; } } return undefined; }; // Helper function to safely parse AI API config const parseAiApiConfig = (field: unknown) => { const parsed = parseJsonField(field); if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { return parsed; } return undefined; }; // Helper function to safely parse agent tools const parseAgentTools = (field: unknown): AgentToolConfig[] | undefined => { const parsed = parseJsonField(field); if (Array.isArray(parsed)) { // Validate that all items are valid AgentToolConfig objects const validConfigs = parsed.filter((item): item is AgentToolConfig => { return typeof item === 'object' && item !== null && typeof (item as Record).toolId === 'string'; }); return validConfigs.length > 0 ? validConfigs : undefined; } return undefined; }; // Try to parse the tiddler text as JSON for agent configuration let handlerConfig: Record; try { const textContent = typeof tiddler.text === 'string' ? tiddler.text : JSON.stringify(tiddler.text || '{}'); const parsed = JSON.parse(textContent) as unknown; // Ensure handlerConfig is a valid object if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { logger.warn('Invalid handlerConfig in tiddler', { function: 'validateAndConvertWikiTiddlerToAgentTemplate', title: getStringField(tiddler.title), reason: 'not an object', }); return null; } handlerConfig = parsed as Record; } catch (parseError) { logger.warn('Failed to parse agent template from tiddler', { function: 'validateAndConvertWikiTiddlerToAgentTemplate', title: getStringField(tiddler.title), error: parseError, }); return null; } // Create AgentDefinition from tiddler const agentTemplate: AgentDefinition = { id: `wiki-template-${getStringField(tiddler.title).replace(/[^a-zA-Z0-9-_]/g, '-')}`, name: getStringField(tiddler.caption) || getStringField(tiddler.title), description: getStringField(tiddler.description) || `Agent template from ${workspaceName || 'wiki'}`, avatarUrl: getStringField(tiddler.avatar_url) || undefined, handlerID: getStringField(tiddler.handler_id) || 'basicPromptConcatHandler', handlerConfig, aiApiConfig: parseAiApiConfig(tiddler.ai_api_config), agentTools: parseAgentTools(tiddler.agent_tools), }; logger.debug('Successfully converted wiki tiddler to agent template', { function: 'validateAndConvertWikiTiddlerToAgentTemplate', name: agentTemplate.name, }); return agentTemplate; } catch (error) { logger.warn('Failed to validate and convert wiki tiddler to agent template', { function: 'validateAndConvertWikiTiddlerToAgentTemplate', error }); return null; } }