UNPKG

@daitanjs/intelligence

Version:

A modular library for advanced LLM orchestration, stateful RAG, multi-step agentic workflows, and tool use, built on LangChain.js.

4 lines 533 kB
{ "version": 3, "sources": ["../src/index.js", "../src/orchestration/daitanOrchestrator.js", "../src/intelligence/core/llmOrchestrator.js", "../src/intelligence/core/promptBuilder.js", "../src/intelligence/core/expertModels.js", "../src/intelligence/core/llmPricing.js", "../src/intelligence/core/tokenUtils.js", "../src/services/llmService.js", "../src/intelligence/index.js", "../src/intelligence/core/embeddingGenerator.js", "../src/intelligence/core/toolFactory.js", "../src/intelligence/rag/index.js", "../src/intelligence/rag/retrieval.js", "../src/intelligence/rag/vectorStoreFactory.js", "../src/intelligence/rag/chromaVectorStoreAdapter.js", "../src/intelligence/rag/chromaClient.js", "../src/intelligence/rag/memoryVectorStoreAdapter.js", "../src/intelligence/rag/chatMemory.js", "../src/intelligence/rag/chat.js", "../src/intelligence/rag/embed.js", "../src/intelligence/rag/documentLoader.js", "../src/intelligence/metadata/parse.js", "../src/intelligence/metadata/index.js", "../src/intelligence/rag/printStats.js", "../src/intelligence/search/specializedSearch.js", "../src/intelligence/tools/tool-registries.js", "../src/intelligence/tools/calculatorTool.js", "../src/intelligence/tools/wikipediaSearchTool.js", "../src/intelligence/tools/cliTool.js", "../src/intelligence/tools/webSearchTool.js", "../src/intelligence/tools/userManagementTool.js", "../src/intelligence/tools/csvQueryTool.js", "../src/intelligence/tools/createPaymentIntentTool.js", "../src/intelligence/tools/youtubeSearchTool.js", "../src/intelligence/tools/processYoutubeAudioTool.js", "../src/intelligence/tools/imageGenerationTool.js", "../src/intelligence/tools/gmailTools.js", "../src/intelligence/tools/calendarTool.js", "../src/intelligence/tools/googleDriveTools.js", "../src/intelligence/tools/ragTool.js", "../src/intelligence/tools/baseTool.js", "../src/intelligence/agents/agentRunner.js", "../src/intelligence/workflows/graphRunner.js", "../src/intelligence/workflows/planAndExecuteAgentGraph.js", "../src/intelligence/workflows/langGraphManager.js", "../src/intelligence/workflows/reactWithReflectionAgentGraph.js", "../src/intelligence/agents/agentExecutor.js", "../src/intelligence/agents/prompts/generalAgentPrompt.js", "../src/memory/inMemoryChatHistoryStore.js", "../src/intelligence/agents/baseAgent.js", "../src/intelligence/agents/chat/index.js", "../src/intelligence/agents/chat/choreographerAgent.js", "../src/intelligence/agents/chat/coachAgent.js", "../src/intelligence/agents/chat/openingPhraseAgent.js", "../src/intelligence/agents/chat/participantAgent.js", "../src/intelligence/workflows/index.js", "../src/intelligence/workflows/presets/deepResearchAgent.js", "../src/intelligence/workflows/presets/searchAndUnderstand.js", "../src/intelligence/workflows/presets/automatedResearchAgent.js", "../src/intelligence/core/ollamaUtils.js", "../src/caching/cacheManager.js", "../src/language/index.js"], "sourcesContent": ["// src/index.js\n/**\n * @file Main public entry point for the @daitanjs/intelligence package.\n * @module @daitanjs/intelligence\n */\nimport { getLogger } from '@daitanjs/development';\n\nconst mainIndexLogger = getLogger('daitan-intelligence-main-index');\n\nmainIndexLogger.debug(\n 'Initializing DaitanJS Intelligence main package exports...'\n);\n\n// --- Core Abstractions & Services ---\nexport { DaitanOrchestrator } from './orchestration/daitanOrchestrator.js';\nexport { LLMService } from './services/llmService.js';\n\n// --- Configuration Management ---\nexport {\n getConfigManager,\n initializeConfigManager,\n DaitanConfigManagerClass,\n} from '@daitanjs/config';\n\n// --- Core Intelligence & Utilities ---\nexport {\n generateIntelligence,\n generateEmbedding,\n askWithRetrieval,\n createRagChatInstance,\n loadAndEmbedFile,\n printStoreStats,\n getVectorStore,\n vectorStoreCollectionExists,\n checkChromaConnection,\n runToolCallingAgent,\n runGraphAgent,\n runDeepResearchAgent,\n runAutomatedResearchWorkflow,\n searchAndUnderstand,\n // --- New Export for Specialized Search ---\n searchNews,\n searchGeneralWeb,\n searchAcademic,\n BaseAgent,\n ChoreographerAgent,\n CoachAgent,\n OpeningPhraseAgent,\n ParticipantAgent,\n createDaitanTool,\n getDefaultTools,\n getDaitanPlatformTools,\n BaseTool,\n DaitanLangGraph,\n createGraphRunner,\n estimateLlmCost,\n countTokens,\n countTokensForMessages,\n checkOllamaStatus,\n} from './intelligence/index.js';\n\n// --- Caching ---\nexport {\n getCache,\n generateCacheKey,\n clearCache,\n} from './caching/cacheManager.js';\n\n// --- Memory Management ---\nexport { InMemoryChatMessageHistoryStore } from './memory/inMemoryChatHistoryStore.js';\n\n// --- Language Services ---\nexport { translate } from './language/index.js';\n\nmainIndexLogger.info(\n 'DaitanJS Intelligence package main exports configured and ready.'\n);\n", "// intelligence/src/orchestration/daitanOrchestrator.js\n/**\n * @file High-level orchestrator facade for the DaitanJS Intelligence library.\n * @module @daitanjs/orchestration/daitanOrchestrator\n *\n * @description\n * The DaitanOrchestrator provides a simplified, unified interface to the most common\n * and powerful functionalities of the @daitanjs/intelligence package. It is designed\n * to be the primary entry point for developers, abstracting away the underlying\n * complexity of service instantiation, graph compilation, and state management.\n * It now includes direct access to the most powerful research agent.\n */\nimport { getLogger } from '@daitanjs/development';\nimport { getConfigManager } from '@daitanjs/config';\nimport { LLMService } from '../services/llmService.js';\nimport {\n askWithRetrieval,\n loadAndEmbedFile,\n printStoreStats,\n runToolCallingAgent,\n runDeepResearchAgent, // NEW: Direct import of the best research agent\n getDefaultTools,\n createGraphRunner,\n createPlanAndExecuteAgentGraph,\n createReActAgentGraph,\n runAutomatedResearchWorkflow,\n InMemoryChatMessageHistoryStore,\n} from '../intelligence/index.js'; // Use the main barrel file\nimport { HumanMessage } from '@langchain/core/messages';\n\nconst logger = getLogger('daitan-orchestrator');\n\n/**\n * @typedef {import('../services/llmService.js').LLMServiceConfig} LLMServiceConfig\n * @typedef {import('../intelligence/rag/interfaces.js').AskWithRetrievalOptions} AskWithRetrievalOptions\n * @typedef {import('../intelligence/rag/interfaces.js').LoadAndEmbedOptions} LoadAndEmbedOptions\n * @typedef {import('../intelligence/agents/agentExecutor.js').RunDaitanAgentParams} RunToolCallingAgentParams\n * @typedef {import('../intelligence/workflows/planAndExecuteAgentGraph.js').PlanAndExecuteAgentState} PlanAndExecuteAgentState\n * @typedef {import('../intelligence/workflows/reactWithReflectionAgentGraph.js').ReActAgentState} ReActAgentState\n * @typedef {import('../intelligence/workflows/presets/automatedResearchAgent.js').ResearchState} AutomatedResearchState\n * @typedef {import('../intelligence/workflows/graphRunner.js').CreateGraphRunnerOptions} CreateGraphRunnerOptions\n */\n\nexport class DaitanOrchestrator {\n /**\n * @param {Object} [orchestratorConfig={}]\n * @param {LLMServiceConfig} [orchestratorConfig.llmServiceConfig] - Configuration for the internal LLMService.\n * @param {LLMService} [orchestratorConfig.llmServiceInstance] - An existing LLMService instance.\n * @param {boolean} [orchestratorConfig.verboseGlobal] - Global verbosity for orchestrator operations.\n */\n constructor(orchestratorConfig = {}) {\n const configManager = getConfigManager();\n this.verbose =\n orchestratorConfig.verboseGlobal ??\n (configManager.get('DEBUG_ORCHESTRATOR', false) ||\n configManager.get('DEBUG_INTELLIGENCE', false));\n\n this.llmService =\n orchestratorConfig.llmServiceInstance instanceof LLMService\n ? orchestratorConfig.llmServiceInstance\n : new LLMService({\n ...orchestratorConfig.llmServiceConfig,\n verbose: this.verbose,\n });\n\n this.compiledGraphs = {};\n this.defaultHistoryStore = new InMemoryChatMessageHistoryStore();\n\n logger.info('DaitanOrchestrator fully initialized.');\n }\n\n /**\n * Direct call to the underlying LLM service.\n * @param {import('../services/llmService.js').GenerateIntelligenceParams} params\n * @returns {Promise<import('../intelligence/core/llmOrchestrator.js').GenerateIntelligenceResult<any>>}\n */\n async llmCall(params) {\n if (this.verbose) {\n logger.info('Orchestrator: Initiating direct LLM call.', {\n summary: params.metadata?.summary,\n });\n }\n // The LLMService's generate method handles merging defaults correctly.\n return this.llmService.generate(params);\n }\n\n /**\n * Performs a RAG query with advanced options.\n * @param {string} query - The user's query.\n * @param {AskWithRetrievalOptions} [ragOptions] - Options for askWithRetrieval.\n * @returns {Promise<import('../intelligence/rag/retrieval.js').RetrievalResult>}\n */\n async ragQuery(query, ragOptions = {}) {\n if (this.verbose)\n logger.info(\n `Orchestrator: Initiating RAG query for: \"${query.substring(\n 0,\n 50\n )}...\"`,\n { collection: ragOptions.collectionName }\n );\n return askWithRetrieval(query, {\n localVerbose: this.verbose,\n ...ragOptions,\n });\n }\n\n /**\n * Loads and embeds a file into the vector store.\n * @param {string} filePath - Path to the file.\n * @param {Object} [customMetadata={}] - Custom metadata to add.\n * @param {LoadAndEmbedOptions} [embedOptions={}] - Options for loadAndEmbedFile.\n * @returns {Promise<object>} An object indicating success and embedding stats.\n */\n async embedFile(filePath, customMetadata = {}, embedOptions = {}) {\n if (this.verbose)\n logger.info(\n `Orchestrator: Initiating file embedding for: \"${filePath}\"`,\n { collection: embedOptions.collectionName }\n );\n return loadAndEmbedFile({\n filePath,\n customMetadata,\n options: { localVerbose: this.verbose, ...embedOptions },\n });\n }\n\n /**\n * Prints statistics for a RAG collection.\n * @param {string} [collectionName] - Name of the collection.\n * @returns {Promise<void>}\n */\n async getRagStats(collectionName) {\n if (this.verbose)\n logger.info(\n `Orchestrator: Requesting RAG stats for collection: \"${\n collectionName || 'default'\n }\"`\n );\n return printStoreStats({ collectionName, localVerbose: this.verbose });\n }\n\n /**\n * Runs the most advanced, multi-step research agent to answer a complex query.\n * @param {string} query - The complex question or research topic.\n * @param {import('../intelligence/workflows/presets/deepResearchAgent.js').runDeepResearchAgent} options - Options for the deep research agent, including `thinkingLevel`, `collectionName`, `onProgress`, and `chatHistory`.\n * @returns {Promise<{finalAnswer: string, sources: string[], plan: any[]}>} The comprehensive answer and its sources.\n */\n async research(query, options) {\n if (this.verbose) {\n logger.info(\n `Orchestrator: Initiating deep research for topic: \"${query}\"`\n );\n }\n // Directly call the powerful, imported research agent\n return runDeepResearchAgent(query, options);\n }\n\n /**\n * Runs a general-purpose tool-using agent.\n * @param {RunToolCallingAgentParams} params - The parameters for the agent run.\n * @returns {Promise<Object>}\n */\n async runToolAgent(params) {\n if (this.verbose)\n logger.info(`Orchestrator: Running tool-calling Agent.`, {\n input: params.input.substring(0, 50) + '...',\n sessionId: params.sessionId,\n });\n\n return runToolCallingAgent({\n historyStore: this.defaultHistoryStore,\n verbose: this.verbose,\n ...params,\n });\n }\n\n // The more granular agent runners below are kept for advanced use cases or backward compatibility.\n // The top-level `research` method is now the recommended entry point for research tasks.\n\n /**\n * Runs the Plan-and-Execute agent graph.\n * @param {string} query - The user's original query.\n * @param {object} [options={}] - Options for the run.\n * @returns {Promise<PlanAndExecuteAgentState>}\n */\n async runPlanAndExecuteAgent(query, options = {}) {\n const { tools, initialState = {}, sessionId, onStateUpdate } = options;\n const effectiveSessionId = sessionId || `plan-exec-${Date.now()}`;\n if (this.verbose)\n logger.info(\n `Orchestrator: Running Plan-and-Execute Agent for query: \"${query.substring(\n 0,\n 50\n )}...\"`,\n { sessionId: effectiveSessionId }\n );\n\n if (!this.compiledGraphs.planAndExecute) {\n this.compiledGraphs.planAndExecute = await createPlanAndExecuteAgentGraph(\n this.llmService,\n tools || getDefaultTools()\n );\n logger.info('Compiled Plan-and-Execute graph for the first time.');\n }\n\n const runner = createGraphRunner(this.compiledGraphs.planAndExecute, {\n verbose: this.verbose,\n onStateUpdate,\n });\n const effectiveTools = tools || getDefaultTools();\n\n const finalInitialState = {\n originalQuery: query,\n inputMessage: new HumanMessage(query),\n llmServiceInstance: this.llmService,\n toolsMap: effectiveTools.reduce(\n (map, tool) => ({ ...map, [tool.name]: tool }),\n {}\n ),\n verbose: this.verbose,\n ...initialState,\n };\n return runner(finalInitialState, {\n configurable: { thread_id: effectiveSessionId },\n });\n }\n}\n", "// intelligence/src/intelligence/core/llmOrchestrator.js\n/**\n * @file The final, stable, and feature-complete LLM orchestrator.\n * @module @daitanjs/intelligence/core/llmOrchestrator\n *\n * @description\n * This file contains the fully restored logic for the intelligence orchestrator.\n * It is built on a proven stable core and safely integrates multi-provider support,\n * expert profile resolution, configuration, and robust error handling.\n * For architectural guidance, see the README.md in this directory.\n */\nimport { ChatOpenAI } from '@langchain/openai';\nimport { ChatAnthropic } from '@langchain/anthropic';\nimport { ChatGroq } from '@langchain/groq';\nimport {\n StringOutputParser,\n JsonOutputParser,\n} from '@langchain/core/output_parsers';\nimport { getConfigManager } from '@daitanjs/config';\nimport { DaitanApiError, DaitanConfigurationError } from '@daitanjs/error';\nimport { buildLlmMessages } from './promptBuilder.js';\nimport { getExpertModelDefinition } from './expertModels.js';\nimport { estimateLlmCost } from './llmPricing.js';\nimport { countTokensForMessages, countTokens } from './tokenUtils.js';\nimport { getLogger } from '@daitanjs/development';\n\nconst logger = getLogger('daitan-llm-orchestrator');\n\n/**\n * Extracts a JSON object from a string, especially if it's wrapped in markdown code fences.\n * @param {string} text - The input string which might contain a JSON object.\n * @returns {string} - The extracted JSON string or the original text if no JSON is found.\n */\nfunction extractJsonFromString(text) {\n if (typeof text !== 'string') return text;\n\n // This regex finds JSON within ```json ... ``` or just the first valid { ... } or [ ... ]\n const jsonRegex = /```(?:json)?\\s*([\\s\\S]+?)\\s*```|({[\\s\\S]*}|\\[[\\s\\S]*\\])/m;\n const match = text.match(jsonRegex);\n\n // If a match is found, return the captured group (either from the code block or the raw object/array).\n // Otherwise, return the original text to let the parser try.\n return match ? match[1] || match[2] || text : text;\n}\n\nexport const generateIntelligence = async ({\n prompt = {},\n config = {},\n callbacks,\n metadata = {}, // Add metadata to destructuring\n}) => {\n const {\n llm: llmConfig = {},\n response: responseConfig = {},\n ...otherConfigs\n } = config;\n\n if (!prompt.user && (!prompt.shots || prompt.shots.length === 0)) {\n throw new DaitanConfigurationError(\n 'A `prompt.user` message or messages in `prompt.shots` are required.'\n );\n }\n\n let llm;\n let providerName;\n let modelName;\n let temperature;\n\n try {\n const configManager = getConfigManager();\n const rawTarget =\n llmConfig.target ||\n configManager.get('DEFAULT_EXPERT_PROFILE') ||\n configManager.get('LLM_PROVIDER') ||\n 'FAST_TASKER';\n\n const expertDef = getExpertModelDefinition(rawTarget);\n if (expertDef) {\n providerName = expertDef.provider.toLowerCase();\n modelName = expertDef.model;\n temperature = expertDef.temperature;\n } else {\n const [p, m] = rawTarget.split('|');\n providerName = p ? p.toLowerCase() : 'openai';\n modelName = m;\n }\n\n const commonConfig = {\n temperature: temperature ?? llmConfig.temperature ?? 0.7,\n maxRetries: config.retry?.maxAttempts ?? 2,\n timeout: llmConfig.requestTimeout,\n modelName: modelName,\n };\n\n switch (providerName) {\n case 'openai': {\n const apiKey = llmConfig.apiKey || configManager.get('OPENAI_API_KEY');\n if (!apiKey)\n throw new DaitanConfigurationError('OPENAI_API_KEY not found.');\n llm = new ChatOpenAI({ ...commonConfig, apiKey });\n break;\n }\n case 'anthropic': {\n const apiKey =\n llmConfig.apiKey || configManager.get('ANTHROPIC_API_KEY');\n if (!apiKey)\n throw new DaitanConfigurationError('ANTHROPIC_API_KEY not found.');\n llm = new ChatAnthropic({ ...commonConfig, apiKey });\n break;\n }\n case 'groq': {\n const apiKey = llmConfig.apiKey || configManager.get('GROQ_API_KEY');\n if (!apiKey)\n throw new DaitanConfigurationError('GROQ_API_KEY not found.');\n llm = new ChatGroq({ ...commonConfig, apiKey });\n break;\n }\n default:\n throw new DaitanConfigurationError(\n `Unsupported provider in orchestrator: '${providerName}'`\n );\n }\n\n // --- DEFINITIVE FIX: Use the new `buildLlmMessages` which correctly parses the structured prompt object ---\n const messages = buildLlmMessages(prompt);\n // --- END OF FIX ---\n\n const result = await llm.invoke(messages, { callbacks });\n const rawContent = result.content ?? '';\n\n let contentToParse = rawContent;\n if (responseConfig.format === 'json') {\n contentToParse = extractJsonFromString(rawContent);\n }\n\n const parser =\n responseConfig.format === 'json'\n ? new JsonOutputParser()\n : new StringOutputParser();\n\n const finalResponse = await parser.parse(contentToParse);\n\n const usageMetadata = result.usage_metadata ?? {};\n const usage = {\n inputTokens:\n usageMetadata.inputTokens ??\n (await countTokensForMessages(messages, modelName, providerName)),\n outputTokens:\n usageMetadata.outputTokens ??\n (await countTokens(\n typeof finalResponse === 'string'\n ? finalResponse\n : JSON.stringify(finalResponse ?? ''),\n modelName,\n providerName\n )),\n };\n usage.totalTokens = (usage.inputTokens || 0) + (usage.outputTokens || 0);\n const cost = estimateLlmCost(\n providerName,\n modelName,\n usage.inputTokens,\n usage.outputTokens\n );\n\n return {\n response: finalResponse,\n usage: { ...usage, ...cost },\n rawResponse: rawContent,\n };\n } catch (error) {\n logger.error(\n `LLM Interaction Failed. Summary: ${\n metadata.summary || 'N/A'\n }. Provider: ${providerName}, Model: ${modelName}. Error: ${\n error.message\n }`,\n { errorStack: error.stack }\n );\n\n throw new DaitanApiError(\n `An unrecoverable error occurred during the LLM interaction with provider '${\n providerName || 'unknown'\n }'.`,\n providerName || 'unknown',\n error?.status,\n { model: modelName, summary: metadata.summary },\n error\n );\n }\n};\n", "// intelligence/src/intelligence/core/promptBuilder.js\n/**\n * @file Contains helpers for building and constructing prompt message arrays for LLMs.\n * @module @daitanjs/intelligence/core/promptBuilder\n *\n * @description\n * This module centralizes the logic for creating the list of messages that will be sent\n * to a large language model. It handles the assembly of system messages, few-shot examples\n * (`shots`), and the final user prompt into a structured format compatible with\n * LangChain's chat models.\n */\n\nimport {\n HumanMessage,\n SystemMessage,\n AIMessage,\n BaseMessage,\n} from '@langchain/core/messages';\nimport { getLogger } from '@daitanjs/development';\n\nconst promptBuilderLogger = getLogger('daitan-prompt-builder');\n\n/**\n * Converts an array of DaitanJS-style message objects into LangChain `BaseMessage` instances.\n * @private\n * @param {Array<Object>} messages - Array of message objects (e.g., `{role: 'user', content: '...'}`).\n * @returns {BaseMessage[]} An array of LangChain message instances.\n */\nfunction convertToLangChainMessages(messages) {\n if (!Array.isArray(messages)) {\n return [];\n }\n return messages.reduce((acc, msg) => {\n if (msg instanceof BaseMessage) {\n acc.push(msg);\n } else if (\n typeof msg === 'object' &&\n msg !== null &&\n typeof msg.role === 'string' &&\n (typeof msg.content === 'string' ||\n Array.isArray(msg.content) ||\n typeof msg.content === 'object') // Allow object for JSON few-shot\n ) {\n const role = msg.role.toLowerCase();\n try {\n if (role === 'system')\n acc.push(new SystemMessage({ content: msg.content }));\n else if (role === 'user' || role === 'human')\n acc.push(new HumanMessage({ content: msg.content }));\n else if (role === 'assistant' || role === 'ai')\n acc.push(new AIMessage({ content: msg.content }));\n else {\n promptBuilderLogger.warn(\n `Unknown message role \"${msg.role}\". Treating as human.`\n );\n acc.push(new HumanMessage({ content: msg.content }));\n }\n } catch (e) {\n promptBuilderLogger.error(\n 'Failed to create LangChain message from object.',\n { messageObject: msg, error: e.message }\n );\n // ignore malformed message\n }\n }\n return acc;\n }, []);\n}\n\n/**\n * Builds the final array of message objects to be sent to the LLM from a structured prompt object.\n *\n * @param {Object} prompt - The structured prompt object.\n * @param {Object} [prompt.system] - An object containing parts of the system message (e.g., persona, task).\n * @param {string | object} [prompt.user] - The final user query.\n * @param {Array} [prompt.shots=[]] - Few-shot examples in `{role, content}` format.\n * @returns {BaseMessage[]} An array of LangChain `BaseMessage` objects ready for an LLM.\n */\nexport const buildLlmMessages = (prompt = {}) => {\n const systemConfig = prompt.system || {};\n const userContent = prompt.user;\n const fewShotExamples = prompt.shots || [];\n\n // This order defines the structure of the system prompt.\n const systemInstructionParts = [\n systemConfig.persona,\n systemConfig.whoYouAre, // Legacy support\n systemConfig.task,\n systemConfig.whatYouDo, // Legacy support\n systemConfig.guidelines,\n systemConfig.vitals,\n systemConfig.scoring,\n systemConfig.writingStyle,\n systemConfig.outputFormat,\n systemConfig.outputFormatDescription, // Legacy support\n systemConfig.promptingTips,\n systemConfig.reiteration,\n ];\n\n const systemInstructionContent = systemInstructionParts\n .filter(Boolean)\n .join('\\n\\n');\n\n let messages = [];\n if (systemInstructionContent) {\n messages.push({ role: 'system', content: systemInstructionContent });\n }\n\n if (Array.isArray(fewShotExamples) && fewShotExamples.length > 0) {\n messages.push(\n ...fewShotExamples.filter(\n (s) =>\n s &&\n typeof s.role === 'string' &&\n (typeof s.content === 'string' ||\n Array.isArray(s.content) ||\n typeof s.content === 'object')\n )\n );\n }\n\n if (\n userContent &&\n (typeof userContent === 'string' || typeof userContent === 'object')\n ) {\n messages.push({ role: 'user', content: userContent });\n }\n\n return convertToLangChainMessages(messages);\n};\n", "// intelligence/src/intelligence/core/expertModels.js\nimport { getEnvVariable } from '@daitanjs/development';\n\nexport const DEFAULT_EXPERT_PROFILE_NAME = getEnvVariable(\n 'DEFAULT_EXPERT_PROFILE',\n 'FAST_TASKER'\n);\n\nconst getExpertConfig = (envVarKey, defaultValue) => {\n const combinedValue = getEnvVariable(envVarKey, defaultValue);\n const parts = combinedValue.split('|');\n const provider = parts[0]?.trim();\n const model = parts[1]?.trim();\n if (!provider || !model) {\n const [defaultProvider, defaultModel] = defaultValue.split('|');\n return { provider: defaultProvider.trim(), model: defaultModel.trim() };\n }\n return { provider, model };\n};\n\nexport const EXPERT_MODELS = {\n MASTER_COMMUNICATOR: {\n ...getExpertConfig('LLM_EXPERT_MASTER_COMMUNICATOR', 'openai|gpt-4o-mini'),\n description: 'Expert in clear, concise, and engaging communication.',\n temperature: 0.7,\n },\n CREATIVE_WRITER: {\n ...getExpertConfig('LLM_EXPERT_CREATIVE_WRITER', 'openai|gpt-4-turbo'),\n description: 'Expert in creative writing, storytelling, and brainstorming.',\n temperature: 0.9,\n },\n FAST_TASKER: {\n ...getExpertConfig('LLM_EXPERT_FAST_TASKER', 'openai|gpt-4o-mini'),\n description: 'Optimized for speed on less complex tasks.',\n temperature: 0.5,\n },\n LOCAL_DEFAULT: {\n ...getExpertConfig('LLM_EXPERT_LOCAL_DEFAULT', 'ollama|llama3:instruct'),\n description: 'A general-purpose model running locally via Ollama.',\n temperature: 0.7,\n },\n MASTER_CODER: {\n ...getExpertConfig(\n 'LLM_EXPERT_MASTER_CODER',\n 'anthropic|claude-3-opus-20240229'\n ),\n description: 'Expert in code generation, debugging, and explanation.',\n temperature: 0.3,\n },\n CODING_STUDENT: {\n ...getExpertConfig('LLM_EXPERT_CODING_STUDENT', 'openai|gpt-4o-mini'),\n description: 'Capable, cost-effective coding assistant for simpler tasks.',\n temperature: 0.5,\n },\n SENTIMENT_WIZARD: {\n ...getExpertConfig('LLM_EXPERT_SENTIMENT_WIZARD', 'openai|gpt-3.5-turbo'),\n description:\n 'Specialized in sentiment analysis and understanding nuanced text.',\n temperature: 0.2,\n },\n TRANSLATION_MULTILINGUAL: {\n ...getExpertConfig(\n 'LLM_EXPERT_TRANSLATION_MULTILINGUAL',\n 'openai|gpt-4o-mini'\n ),\n description: 'Expert in multilingual translation.',\n temperature: 0.1,\n },\n DATA_ANALYSIS_EXPERT: {\n ...getExpertConfig('LLM_EXPERT_DATA_ANALYSIS', 'openai|gpt-4-turbo'),\n description: 'Expert in interpreting data and generating insights.',\n temperature: 0.4,\n },\n RESEARCH_ASSISTANT: {\n ...getExpertConfig('LLM_EXPERT_RESEARCH_ASSISTANT', 'openai|gpt-4-turbo'),\n description:\n 'Specialized in synthesizing information and research queries.',\n temperature: 0.5,\n },\n};\n\nexport const getExpertModelDefinition = (expertName) => {\n if (typeof expertName !== 'string' || !expertName.trim()) return undefined;\n return EXPERT_MODELS[expertName.toUpperCase()];\n};\n\nexport const getDefaultExpertProfile = () => {\n return getExpertModelDefinition(DEFAULT_EXPERT_PROFILE_NAME);\n};\n", "// intelligence/src/intelligence/core/llmPricing.js\n// This is the full, original code for this file.\n\nimport { getLogger } from '@daitanjs/development';\n\nconst logger = getLogger('llm-pricing');\n\nexport const PROVIDER_MODEL_PRICING = {\n openai: {\n 'gpt-4o': { inputCostPer1MTokens: 5.0, outputCostPer1MTokens: 15.0 },\n 'gpt-4o-mini': { inputCostPer1MTokens: 0.15, outputCostPer1MTokens: 0.6 },\n 'gpt-4-turbo': { inputCostPer1MTokens: 10.0, outputCostPer1MTokens: 30.0 },\n 'gpt-4': { inputCostPer1MTokens: 30.0, outputCostPer1MTokens: 60.0 },\n 'gpt-3.5-turbo': { inputCostPer1MTokens: 0.5, outputCostPer1MTokens: 1.5 },\n 'text-embedding-3-large': {\n inputCostPer1MTokens: 0.13,\n outputCostPer1MTokens: 0.0,\n },\n 'text-embedding-3-small': {\n inputCostPer1MTokens: 0.02,\n outputCostPer1MTokens: 0.0,\n },\n 'text-embedding-ada-002': {\n inputCostPer1MTokens: 0.1,\n outputCostPer1MTokens: 0.0,\n },\n },\n anthropic: {\n 'claude-3-opus-20240229': {\n inputCostPer1MTokens: 15.0,\n outputCostPer1MTokens: 75.0,\n },\n 'claude-3-sonnet-20240229': {\n inputCostPer1MTokens: 3.0,\n outputCostPer1MTokens: 15.0,\n },\n 'claude-3-haiku-20240307': {\n inputCostPer1MTokens: 0.25,\n outputCostPer1MTokens: 1.25,\n },\n },\n groq: {\n 'llama3-8b-8192': {\n inputCostPer1MTokens: 0.05,\n outputCostPer1MTokens: 0.1,\n },\n 'llama3-70b-8192': {\n inputCostPer1MTokens: 0.59,\n outputCostPer1MTokens: 0.79,\n },\n 'mixtral-8x7b-32768': {\n inputCostPer1MTokens: 0.27,\n outputCostPer1MTokens: 0.27,\n },\n },\n ollama: {\n 'llama3:instruct': {\n inputCostPer1MTokens: 0,\n outputCostPer1MTokens: 0,\n details: 'Local model, no cost.',\n },\n 'nomic-embed-text': {\n inputCostPer1MTokens: 0,\n outputCostPer1MTokens: 0,\n details: 'Local model, no cost.',\n },\n },\n};\n\nexport const estimateLlmCost = (\n provider,\n model,\n inputTokens = 0,\n outputTokens = 0\n) => {\n const providerKey = provider?.toLowerCase();\n const modelKey = model?.toLowerCase();\n const result = {\n estimatedCostUSD: null,\n currency: 'USD',\n details: 'No pricing information available.',\n };\n\n if (!providerKey || !modelKey) {\n logger.debug('Provider or model key missing for cost estimation.');\n return result;\n }\n\n const providerPricing = PROVIDER_MODEL_PRICING[providerKey];\n if (!providerPricing) {\n result.details = `No pricing for provider '${providerKey}'.`;\n return result;\n }\n\n // Find a matching model, allowing for variants like 'gpt-4-turbo-2024-04-09' to match 'gpt-4-turbo'\n const baseModelKey = Object.keys(providerPricing).find((key) =>\n modelKey.startsWith(key)\n );\n\n if (!baseModelKey) {\n result.details = `No pricing for model '${modelKey}' under provider '${providerKey}'.`;\n return result;\n }\n\n const modelPricing = providerPricing[baseModelKey];\n\n if (\n modelPricing.inputCostPer1MTokens === 0 &&\n modelPricing.outputCostPer1MTokens === 0\n ) {\n result.estimatedCostUSD = 0;\n result.details = modelPricing.details || 'Model is free or local.';\n return result;\n }\n\n const inputCost =\n (inputTokens / 1_000_000) * (modelPricing.inputCostPer1MTokens || 0);\n const outputCost =\n (outputTokens / 1_000_000) * (modelPricing.outputCostPer1MTokens || 0);\n\n result.estimatedCostUSD = inputCost + outputCost;\n result.details = `Cost calculated for ${providerKey}/${baseModelKey}.`;\n\n return result;\n};\n", "// intelligence/src/intelligence/core/tokenUtils.js\n// This is the full, original code for this file.\n\nimport { get_encoding } from 'tiktoken';\nimport { getLogger } from '@daitanjs/development';\n\nconst logger = getLogger('token-utils');\nconst DEFAULT_CHAR_TO_TOKEN_RATIO = 4;\nconst tiktokenCache = new Map();\n\nconst getTiktokenForModel = (modelNameInput) => {\n const modelName = String(modelNameInput || '');\n if (tiktokenCache.has(modelName)) {\n return tiktokenCache.get(modelName);\n }\n\n let encodingName;\n if (\n modelName.startsWith('gpt-4') ||\n modelName.startsWith('gpt-3.5-turbo') ||\n modelName.startsWith('text-embedding-3') ||\n modelName.startsWith('gpt-4o')\n ) {\n encodingName = 'cl100k_base';\n } else if (modelName.includes('text-embedding-ada-002')) {\n encodingName = 'p50k_base';\n } else {\n logger.debug(\n `No specific tiktoken encoding for model \"${modelName}\". Defaulting to cl100k_base.`\n );\n encodingName = 'cl100k_base';\n }\n\n try {\n const encoding = get_encoding(encodingName);\n tiktokenCache.set(modelName, encoding);\n return encoding;\n } catch (error) {\n logger.warn(\n `Could not initialize tiktoken for model \"${modelName}\" (encoding: ${encodingName}): ${error.message}.`\n );\n return null;\n }\n};\n\nexport const countTokens = (text, modelName, providerName = 'openai') => {\n if (typeof text !== 'string' || text === '') return 0;\n\n const openaiCompatibleProviders = [\n 'openai',\n 'groq',\n 'openrouter',\n 'anthropic',\n ];\n if (openaiCompatibleProviders.includes(providerName?.toLowerCase() || '')) {\n const tiktokenInstance = getTiktokenForModel(modelName);\n if (tiktokenInstance) {\n try {\n return tiktokenInstance.encode(text).length;\n } catch (error) {\n logger.warn(\n `Tiktoken encoding failed for model \"${modelName}\". Falling back to char count.`\n );\n }\n }\n }\n\n const estimatedTokens = Math.ceil(text.length / DEFAULT_CHAR_TO_TOKEN_RATIO);\n logger.debug(\n `Using char-based token approximation for model \"${modelName}\" (provider: ${providerName}). Chars: ${text.length}, Approx Tokens: ${estimatedTokens}`\n );\n return estimatedTokens;\n};\n\nexport const countTokensForMessages = (\n messages,\n modelName,\n providerName = 'openai'\n) => {\n if (!Array.isArray(messages) || messages.length === 0) return 0;\n\n const tiktokenInstance = getTiktokenForModel(modelName);\n if (!tiktokenInstance) {\n let totalChars = 0;\n messages.forEach((msg) => {\n if (typeof msg.content === 'string') {\n totalChars += msg.content.length;\n }\n });\n return (\n Math.ceil(totalChars / DEFAULT_CHAR_TO_TOKEN_RATIO) + messages.length * 2\n );\n }\n\n let tokensPerMessage = 3;\n let tokensPerName = 1;\n\n let numTokens = 0;\n messages.forEach((message) => {\n numTokens += tokensPerMessage;\n for (const key in message) {\n // Note: LangChain message objects have content as a direct property.\n if (key === 'content' && typeof message.content === 'string') {\n numTokens += tiktokenInstance.encode(message.content).length;\n } else if (key === 'name' && message[key]) {\n numTokens += tokensPerName;\n }\n }\n });\n\n numTokens += 3; // every reply is primed with <|start|>assistant<|message|>\n return numTokens;\n};\n", "// intelligence/src/services/llmService.js\n/**\n * @file Provides a service class for simplified and consistent interaction with LLMs.\n * @module @daitanjs/intelligence/services/llmService\n */\nimport { generateIntelligence } from '../intelligence/core/llmOrchestrator.js';\nimport { getLogger } from '@daitanjs/development';\nimport { getConfigManager } from '@daitanjs/config';\nimport { DaitanInvalidInputError } from '@daitanjs/error';\n\nconst logger = getLogger('daitan-llm-service');\n\n/**\n * @typedef {import('../intelligence/core/llmOrchestrator.js').LLMUsageInfo} LLMUsageInfo\n * @typedef {import('../intelligence/core/llmOrchestrator.js').LLMCallbacks} LLMCallbacks\n * @typedef {import('../intelligence/core/llmOrchestrator.js').GenerateIntelligenceParams} GenerateIntelligenceParams\n */\n\n/**\n * @typedef {Object} LLMServiceConfig\n * @property {string} [target] - Default LLM target, as an expert profile name (e.g., 'FAST_TASKER') or a 'provider|model' string.\n * @property {string} [apiKey] - Default API key (overrides configManager).\n * @property {string} [baseURL] - Default base URL (overrides configManager).\n * @property {number} [temperature] - Default temperature.\n * @property {number} [maxTokens] - Default max_tokens.\n * @property {boolean} [verbose] - Default verbosity for LLM calls.\n * @property {boolean} [trace] - Default LangSmith tracing enablement.\n * @property {boolean} [trackUsage] - Default for tracking token usage.\n * @property {number} [requestTimeout] - Default request timeout for LLM calls.\n * @property {number} [maxRetries] - Default max retries for LLM calls.\n * @property {number} [initialDelayMs] - Default initial retry delay.\n */\n\nexport class LLMService {\n /**\n * @param {LLMServiceConfig} [defaultConfig={}] - Default configuration for this service instance.\n */\n\n constructor(defaultConfig = {}) {\n const configManager = getConfigManager();\n this.defaultConfig = {\n target: configManager.get('LLM_PROVIDER', 'openai'), // Default to provider if no specific target\n temperature: 0.7,\n maxTokens: 2000,\n verbose: configManager.get('DEBUG_INTELLIGENCE', false),\n trackUsage: configManager.get('LLM_TRACK_USAGE', true),\n ...defaultConfig,\n };\n this.logger = logger; // Make logger available to instances\n logger.info('LLMService initialized.');\n logger.debug('LLMService default configuration:', this.defaultConfig);\n }\n\n /**\n * Makes a generic call to `generateIntelligence`, merging service defaults with call-specific options.\n * @param {GenerateIntelligenceParams} options - Options for generateIntelligence, including prompt, config, metadata, and callbacks.\n * @returns {Promise<import('../intelligence/core/llmOrchestrator.js').GenerateIntelligenceResult<any>>}\n */\n async generate(options) {\n const {\n prompt = {},\n config: callConfig = {},\n metadata = {},\n callbacks,\n } = options;\n\n if (!prompt?.user && !(prompt?.shots && prompt.shots.length > 0)) {\n throw new DaitanInvalidInputError(\n 'LLMService.generate: A `prompt.user` message or messages in `prompt.shots` are required.'\n );\n }\n\n const {\n llm: callLlm = {},\n response: callResponse = {},\n retry: callRetry = {},\n ...callRootConfig\n } = callConfig;\n\n const finalConfig = {\n verbose: this.defaultConfig.verbose,\n trackUsage: this.defaultConfig.trackUsage,\n ...callRootConfig,\n llm: {\n target: this.defaultConfig.target,\n temperature: this.defaultConfig.temperature,\n maxTokens: this.defaultConfig.maxTokens,\n apiKey: this.defaultConfig.apiKey,\n baseURL: this.defaultConfig.baseURL,\n ...callLlm,\n },\n response: { ...callResponse },\n retry: {\n maxAttempts: this.defaultConfig.maxRetries,\n ...callRetry,\n },\n };\n\n const finalParams = {\n prompt,\n config: finalConfig,\n metadata,\n callbacks,\n };\n\n const summary = metadata?.summary || 'Untitled LLMService Call';\n this.logger.info(`LLMService.generate called for summary: \"${summary}\"`);\n\n try {\n const result = await generateIntelligence(finalParams);\n this.logger.info(`LLMService.generate successful for: \"${summary}\"`);\n if (result.usage) {\n this.logger.debug('LLM Usage:', result.usage);\n }\n return result;\n } catch (error) {\n this.logger.error(\n `LLMService.generate failed for \"${summary}\": ${error.message}`,\n { error }\n );\n throw error;\n }\n }\n\n /**\n * Generates a JSON response from the LLM.\n * @param {Object} params\n * @returns {Promise<{response: Object, usage: LLMUsageInfo | null}>}\n */\n async generateJson({\n userPrompt,\n whoYouAre,\n whatYouDo,\n summary = 'JSON Generation',\n shots,\n ...overrideConfig\n }) {\n const { target, temperature, maxTokens, apiKey, baseURL, ...rootConfig } =\n overrideConfig;\n\n const params = {\n prompt: {\n system: { persona: whoYouAre, task: whatYouDo },\n user: userPrompt,\n shots,\n },\n config: {\n ...rootConfig,\n response: { format: 'json' },\n llm: { target, temperature, maxTokens, apiKey, baseURL },\n },\n metadata: { summary },\n };\n return this.generate(params);\n }\n\n /**\n * Generates a text response from the LLM.\n * @param {Object} params\n * @returns {Promise<{response: string, usage: LLMUsageInfo | null}>}\n */\n async generateText({\n userPrompt,\n whoYouAre,\n whatYouDo,\n summary = 'Text Generation',\n shots,\n ...overrideConfig\n }) {\n const { target, temperature, maxTokens, apiKey, baseURL, ...rootConfig } =\n overrideConfig;\n\n const params = {\n prompt: {\n system: { persona: whoYouAre, task: whatYouDo },\n user: userPrompt,\n shots,\n },\n config: {\n ...rootConfig,\n response: { format: 'text' },\n llm: { target, temperature, maxTokens, apiKey, baseURL },\n },\n metadata: { summary },\n };\n return this.generate(params);\n }\n\n /**\n * Streams a text response from the LLM.\n * @param {Object} params\n * @returns {Promise<{response: string | undefined, usage: LLMUsageInfo | null}>}\n */\n async streamText({\n userPrompt,\n whoYouAre,\n whatYouDo,\n summary = 'Text Streaming',\n shots,\n callbacks,\n returnFullResponseAfterStream = true,\n ...overrideConfig\n }) {\n if (!callbacks || typeof callbacks.onTokenStream !== 'function') {\n throw new DaitanInvalidInputError(\n 'LLMService.streamText: `callbacks.onTokenStream` is required for streaming.'\n );\n }\n const { target, temperature, maxTokens, apiKey, baseURL, ...rootConfig } =\n overrideConfig;\n\n const params = {\n prompt: {\n system: { persona: whoYouAre, task: whatYouDo },\n user: userPrompt,\n shots,\n },\n config: {\n ...rootConfig,\n response: { format: 'text', returnFullResponseAfterStream },\n llm: { target, temperature, maxTokens, apiKey, baseURL },\n },\n metadata: { summary },\n callbacks,\n };\n return this.generate(params);\n }\n}\n", "// intelligence/src/intelligence/index.js\n/**\n * @file Main entry point for core AI/LLM functionalities in the @daitanjs/intelligence package.\n * @module @daitanjs/intelligence\n */\nimport { getLogger } from '@daitanjs/development';\n\nconst intelligenceIndexLogger = getLogger('daitan-intelligence-index');\n\nintelligenceIndexLogger.debug(\n 'Initializing DaitanJS Intelligence module exports...'\n);\n\n// --- Core LLM, Embedding, and Factory Exports ---\nexport { generateIntelligence } from './core/llmOrchestrator.js';\nexport { generateEmbedding } from './core/embeddingGenerator.js';\nexport { createDaitanTool } from './core/toolFactory.js';\n\n// --- Prompt Management ---\nexport * from './prompts/index.js';\n\n// --- RAG (Retrieval Augmented Generation) ---\nexport * from './rag/index.js';\n\n// --- Metadata Extraction ---\nexport * from './metadata/index.js';\n\n// --- Specialized Search (New Export) ---\nexport {\n searchNews,\n searchGeneralWeb,\n searchAcademic,\n} from './search/specializedSearch.js';\n\n// --- Tool Exports ---\nexport {\n getDefaultTools,\n getDaitanPlatformTools,\n} from './tools/tool-registries.js';\nexport { BaseTool } from './tools/baseTool.js';\nexport { calculatorTool } from './tools/calculatorTool.js';\nexport { wikipediaSearchTool } from './tools/wikipediaSearchTool.js';\nexport { cliTool } from './tools/cliTool.js';\nexport { webSearchTool } from './tools/webSearchTool.js';\nexport { ragTool } from './tools/ragTool.js';\nexport { userManagementTool } from './tools/userManagementTool.js';\nexport { csvQueryTool } from './tools/csvQueryTool.js';\nexport { createPaymentIntentTool } from './tools/createPaymentIntentTool.js';\nexport { youtubeSearchTool } from './tools/youtubeSearchTool.js';\nexport { processYoutubeAudioTool } from './tools/processYoutubeAudioTool.js';\nexport { imageGenerationTool } from './tools/imageGenerationTool.js';\nexport {\n searchGmailTool,\n readEmailContentTool,\n createGmailDraftTool,\n} from './tools/gmailTools.js';\nexport { calendarTool } from './tools/calendarTool.js';\nexport {\n createGoogleDocTool,\n createGoogleSheetTool,\n} from './tools/googleDriveTools.js';\n\n// --- Agent Exports ---\nexport { runGraphAgent } from './agents/agentRunner.js';\nexport { runToolCallingAgent } from './agents/agentExecutor.js';\nexport { BaseAgent } from './agents/baseAgent.js';\nexport * from './agents/prompts/index.js';\nexport * from './agents/chat/index.js';\n\n// --- Workflow Exports ---\nexport * from './workflows/index.js';\n\n// --- Memory Management ---\nexport { InMemoryChatMessageHistoryStore } from '../memory/inMemoryChatHistoryStore.js';\n\n// --- Core Utilities (re-exported for convenience) ---\nexport { estimateLlmCost } from './core/llmPricing.js';\nexport { countTokens, countTokensForMessages } from './core/tokenUtils.js';\nexport { checkOllamaStatus } from './core/ollamaUtils.js';\nexport {\n EXPERT_MODELS,\n getExpertModelDefinition,\n getDefaultExpertProfile,\n} from './core/expertModels.js';\n\nintelligenceIndexLogger.info(\n 'DaitanJS Intelligence module main exports configured and ready.'\n);\n", "// intelligence/src/intelligence/core/embeddingGenerator.js\n/**\n * @file Re-exports embedding generation functionalities from the canonical @daitanjs/embeddings package.\n * @module @daitanjs/intelligence/core/embeddingGenerator\n *\n * @description\n * This module ensures that embedding generation capabilities are accessible through the\n * `@daitanjs/intelligence` package's core interface while maintaining a single source of truth.\n * The actual implementation of `generateEmbedding` and related utilities resides in the\n * `@daitanjs/embeddings` package.\n *\n * This re-export approach prevents code duplication and enforces the DaitanJS architectural\n * principle of specialized, reusable packages.\n *\n * For detailed documentation on the exported functions, please refer to the `@daitanjs/embeddings` package.\n */\nimport { getLogger } from '@daitanjs/development';\n\nconst embeddingGeneratorLogger = getLogger('daitan-embedding-generator');\n\nembeddingGeneratorLogger.info(\n 'Embedding generator module is a re-export layer. All embedding functionalities are canonical in @daitanjs/embeddings.'\n);\n\n// Re-exporting the canonical embedding generation functions from the @daitanjs/embeddings package.\n// This assumes that @daitanjs/embeddings is a dependency of @daitanjs/intelligence.\nexport {\n generateEmbedding,\n generateBatchEmbeddings, // This function is deprecated in the source but exported for compatibility\n} from '@daitanjs/embeddings';\n", "// intelligence/src/intelligence/core/toolFactory.js\n/**\n * @file Contains the factory function for creating DaitanJS tools.\n * @module @daitanjs/intelligence/core/toolFactory\n * @private\n */\nimport { DynamicTool } from '@langchain/core/tools';\nimport { getLogger } from '@daitanjs/development';\nimport {\n DaitanInvalidInputError,\n DaitanValidationError,\n DaitanOperationError,\n} from '@daitanjs/error';\nimport { ZodError } from 'zod';\n\nconst toolFactoryLogger = getLogger('daitan-tool-factory');\n\n/**\n * Creates a LangChain-compatible custom tool (`DynamicTool`) from an asynchronous function.\n * This factory wraps the provided function with robust input parsing, validation, logging, and error handling.\n *\n * @public\n * @param {string} name - The unique, snake_case name of the tool.\n * @param {string} description - A detailed description for the LLM.\n * @param {(input: any, callId?: string) => Promise<string | any>} func - The async function that implements the tool's logic.\n * @param {import('zod').ZodSchema} [argsSchema] - Optional Zod schema for input validation.\n * @returns {DynamicTool} A LangChain DynamicTool instance.\n */\nexport const createDaitanTool = (\n name,\n description,\n func,\n argsSchema = undefined\n) => {\n if (!name || typeof name !== 'string' || !name.trim()) {\n throw new DaitanInvalidInputError(\n 'Tool `name` must be a non-empty string.'\n );\n }\n if (!description || typeof description !== 'string' || !description.trim()) {\n throw new DaitanInvalidInputError(\n 'Tool `description` must be a non-empty string.'\n );\n }\n if (typeof func !== 'function') {\n throw new DaitanInvalidInputError(\n 'Tool `func` must be a callable function.'\n );\n }\n\n const daitanToolWrapperFunc = async (rawInput) => {\n const callId = `tool-run-${name}-${Date.now().toString(36)}`;\n const logger = getLogger(`daitan-tool-${name}`);\n logger.info(`Tool \"${name}\" execution: START`, { callId, rawInput });\n\n let inputForRun = rawInput;\n try {\n if (typeof rawInput === 'string') {\n try {\n inputForRun = JSON.parse(rawInput);\n } catch (e) {\n // Ignore if not a valid JSON string.\n }\n }\n\n if (argsSchema) {\n inputForRun = argsSchema.parse(inputForRun);\n }\n\n const result = await func(inputForRun, callId);\n const outputString =\n typeof result === 'string' ? result : JSON.stringify(result, null, 2);\n\n logger.info(`Tool \"${name}\" execution: SUCCESS`, {\n callId,\n outputPreview: outputString.substring(0, 150) + '...',\n });\n return outputString;\n } catch (error) {\n logger.error(`Execution error in tool \"${name}\": ${error.message}`, {\n callId,\n errorName: error.name,\n });\n\n