import { DynamicStructuredTool } from '@langchain/core/tools'; import { z } from 'zod'; import type { FastifyBaseLogger } from 'fastify'; import type { WebExploreSubagent } from '../../harness/subagents/web-explore/index.js'; import type { SubagentContext } from '../../harness/subagents/base-subagent.js'; import type { HarnessEvent } from '../../harness/harness-events.js'; export interface WebExploreAgentToolConfig { webExploreSubagent: WebExploreSubagent; context: SubagentContext; logger: FastifyBaseLogger; } /** * Creates a LangChain tool that delegates to the web-explore subagent. * The subagent decides whether to use web search or arXiv based on the instruction. */ export function createWebExploreAgentTool(config: WebExploreAgentToolConfig): DynamicStructuredTool & { streamFunc: (args: { instruction: string }, signal?: AbortSignal) => AsyncGenerator } { const { webExploreSubagent, context, logger } = config; async function* streamFunc({ instruction }: { instruction: string }, signal?: AbortSignal): AsyncGenerator { logger.info({ instruction: instruction.substring(0, 100) }, 'Streaming web-explore subagent'); const gen = webExploreSubagent.streamEvents(context, instruction, signal); let step: IteratorResult; while (!(step = await gen.next()).done) { yield step.value; } return step.value; } const tool = new DynamicStructuredTool({ name: 'web_explore', description: `Search the EXTERNAL web or academic databases and return a summarized answer. Use this tool ONLY for external, public information: - Current events, news, or real-time information - External documentation, tutorials, or how-to guides for third-party libraries/tools - Academic papers, research findings, or scientific topics - Any topic requiring external sources NEVER use this tool for: - Questions about the Dexorder platform itself (workspace tools, chartState, indicators, strategies) - Internal API usage (workspace_patch, workspace_read, etc.) — consult the system prompt instead - Anything that can be answered from the context already available The subagent will search the web (or arXiv for academic queries), fetch relevant content, and return a markdown summary with cited sources.`, schema: z.object({ instruction: z.string().describe( 'What to search for and summarize. Be specific — include the topic, what aspects matter, ' + 'and any context that helps narrow the search (e.g. "recent papers on momentum factor in equities" ' + 'or "how to configure rate limiting in Fastify").' ), }), func: async ({ instruction }: { instruction: string }): Promise => { logger.info({ instruction: instruction.substring(0, 100) }, 'Delegating to web-explore subagent'); try { return await webExploreSubagent.execute(context, instruction); } catch (error) { logger.error({ error, errorMessage: (error as Error)?.message }, 'Web explore subagent failed'); throw error; } }, }); return Object.assign(tool, { streamFunc }); }