import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import type { FastifyBaseLogger } from 'fastify'; import type { StrategySubagent } from '../../harness/subagents/strategy/index.js'; import type { SubagentContext } from '../../harness/subagents/base-subagent.js'; import type { HarnessEvent } from '../../harness/harness-events.js'; export interface StrategyAgentToolConfig { strategySubagent: StrategySubagent; context: SubagentContext; logger: FastifyBaseLogger; } /** * Creates a LangChain tool that delegates to the strategy subagent. * Mirrors the pattern of indicator-agent.tool.ts. */ export function createStrategyAgentTool(config: StrategyAgentToolConfig): DynamicStructuredTool & { streamFunc: (args: { instruction: string }, signal?: AbortSignal) => AsyncGenerator } { const { strategySubagent, context, logger } = config; async function* streamFunc({ instruction }: { instruction: string }, signal?: AbortSignal): AsyncGenerator { logger.info({ instruction: instruction.substring(0, 100) }, 'Streaming strategy subagent'); const gen = strategySubagent.streamEvents(context, instruction, signal); let step: IteratorResult; while (!(step = await gen.next()).done) { yield step.value; } return step.value; } const tool = new DynamicStructuredTool({ name: 'strategy', description: `Delegate to the strategy subagent for all trading strategy tasks. Use this tool for: - Writing new PandasStrategy classes ("create a strategy that...") - Editing or improving existing strategies - Running backtests on a strategy - Interpreting backtest results (Sharpe ratio, drawdown, trade list) - Activating or deactivating strategies for paper trading - Monitoring running strategy PnL and trade logs - Checking which strategies already exist ALWAYS use this tool for any request about trading strategies, backtesting, or strategy activation. NEVER write strategy Python code or call backtest_strategy directly — delegate here instead.`, schema: z.object({ instruction: z.string().describe( 'The strategy task to perform. Be specific: include the strategy name, ' + 'desired signals (e.g. RSI < 30 = buy), timeframe, and symbol if known. ' + 'For backtest requests include the date range and starting capital.' ), }), func: async ({ instruction }: { instruction: string }): Promise => { logger.info({ instruction: instruction.substring(0, 100) }, 'Delegating to strategy subagent'); try { return await strategySubagent.execute(context, instruction); } catch (error) { logger.error({ error, errorMessage: (error as Error)?.message }, 'Strategy subagent failed'); throw error; } }, }); return Object.assign(tool, { streamFunc }); }