import { ToolCall } from '@langchain/core/messages/tool'; import { END, Command, MessagesAnnotation } from '@langchain/langgraph'; import type { RunnableConfig } from '@langchain/core/runnables'; import type { BaseMessage } from '@langchain/core/messages'; import type * as t from '@/types'; import { RunnableCallable } from '@/utils'; import { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences'; import type { ResolvedArgsByCallId } from '@/tools/toolOutputReferences'; /** * Per-call batch context for `runTool`. Bundles every optional batch-scoped * value the method needs so the signature stays at three positional params * even as new context fields are added. Upstream-aligned (PR #117). */ type RunToolBatchContext = { /** Position of this call within the parent ToolNode batch. */ batchIndex?: number; /** Batch turn shared across every call in the batch. */ turn?: number; /** Registry partition scope (run id or anonymous batch id). */ batchScopeId?: string; /** Batch-local sink for post-substitution args. */ resolvedArgsByCallId?: ResolvedArgsByCallId; }; export declare class ToolNode extends RunnableCallable { private toolMap; private loadRuntimeTools?; handleToolErrors: boolean; trace: boolean; toolCallStepIds?: Map; errorHandler?: t.ToolNodeConstructorParams['errorHandler']; private toolUsageCount; /** Maps toolCallId → turn captured in runTool, used by handleRunToolCompletions */ private toolCallTurns; /** Tool registry for filtering (lazy computation of programmatic maps) */ private toolRegistry?; /** Cached programmatic tools (computed once on first PTC call) */ private programmaticCache?; /** Reference to Graph's sessions map for automatic session injection */ private sessions?; /** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */ private eventDrivenMode; /** Agent ID for event-driven mode */ private agentId?; /** Tool names that bypass event dispatch and execute directly (e.g., graph-managed handoff tools) */ private directToolNames?; /** HITL tool approval configuration */ private toolApprovalConfig?; /** Buffer for recovering truncated tool call arguments from streaming data */ private streamingToolCallBuffer?; /** Hook registry threaded down from Run for tool-lifecycle events. */ private hookRegistry?; /** * Tool output reference registry threaded down from Run / Graph * (upstream PR #114). When set, dispatchToolEvents resolves * `{{toolturn}}` placeholders in args before invoking each tool * and stores successful outputs under their stable reference keys for * subsequent calls in the same run to pipe through. */ private toolOutputRegistry?; /** * Per-batch turn counter for the registry. Increments once per * `dispatchToolEvents` invocation so `toolturn` keys remain * unique across overlapping batches in a single run. */ private toolOutputTurn; /** * Counter for synthesising scope ids when `run()` is called without a * `run_id` in config. Each anonymous batch gets a unique * `\0anon-` scope so concurrent invocations don't collide on the * shared `toolOutputRegistry`. */ private anonBatchCounter; constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, agentId, directToolNames, streamingToolCallBuffer, toolApprovalConfig, hookRegistry, toolOutputRegistry, toolOutputReferences, maxToolResultChars: _maxToolResultChars, }: t.ToolNodeConstructorParams); /** * Test-observation accessor for the tool-output reference registry. * @internal Exposed for test observation only. Host code should rely on * `{{toolturn}}` substitution at tool-invocation time and not * mutate the registry directly. */ _unsafeGetToolOutputRegistry(): ToolOutputReferenceRegistry | undefined; /** * Returns cached programmatic tools, computing once on first access. * Single iteration builds both toolMap and toolDefs simultaneously. */ private getProgrammaticTools; /** * Returns a snapshot of the current tool usage counts. * @returns A ReadonlyMap where keys are tool names and values are their usage counts. */ getToolUsageCounts(): ReadonlyMap; /** * Evaluates whether a tool call requires human approval. * Returns true if approval is needed, false otherwise. * Does NOT perform the actual approval flow - that's handled by requestApproval(). */ private requiresApproval; /** * Requests human approval for a tool call using LangGraph's native interrupt(). * * Flow: * 1. Dispatches ON_TOOL_APPROVAL_REQUIRED notification (no resolve/reject — data only) * so the host can persist the request and send UI events. * 2. Calls interrupt() which checkpoints graph state and pauses execution. * 3. When the host resumes via Command({ resume: ToolApprovalResponse }), * interrupt() returns the response synchronously. * * @param call - The tool call requiring approval * @param config - The runnable config for event dispatch * @returns The approval response from the human */ private requestApproval; /** * Runs a single tool call with error handling. * * @param batchContext Optional per-batch context (upstream PR #117). * Threaded from `run()` for tool output reference annotation. The * `batchScopeId` field carries an anonymous synthetic scope when the * caller has no `run_id`, so concurrent batches don't collide on the * shared registry. */ protected runTool(call: ToolCall, config: RunnableConfig, batchContext?: RunToolBatchContext): Promise; /** * Recover truncated tool call arguments using the raw streaming buffer. * * When parsePartialJson drops content (e.g., a large "content" field truncated * at max_tokens), this method extracts the field value from the raw accumulated * arg string and merges it back into the parsed args object. * * This is generic — it checks ALL string fields in the raw buffer, not just * content_tool fields. Any tool with a truncated string value benefits. */ /** * Recover truncated tool call arguments using the raw streaming buffer. * * Strategy: * 1. If args are completely empty → try parsePartialJson on the raw buffer * 2. Otherwise → field-level recovery: extract missing fields from raw buffer * * @param toolName - Tool name (for logging) * @param toolCallId - The tool call ID * @param args - The parsed args (potentially incomplete from parsePartialJson) * @returns The args with recovered fields merged in */ private recoverTruncatedArgs; /** * Stores the raw, untruncated tool output in the registry under `refKey` * (when registry + refKey are both present) and returns the metadata * envelope to stamp on `ToolMessage.additional_kwargs`. The lazy * annotation transform reads `_refKey` / `_refScope` / `_unresolvedRefs` * at LLM request time to produce the transient annotated copy. */ private recordOutputReference; /** * Builds code session context for injection into event-driven tool calls. * Mirrors the session injection logic in runTool() for direct execution. */ private getCodeSessionContext; /** * Extracts code execution session context from tool results and stores in Graph.sessions. * Mirrors the session storage logic in handleRunToolCompletions for direct execution. */ private storeCodeSessionFromResults; /** * Post-processes standard runTool outputs: dispatches ON_RUN_STEP_COMPLETED * and stores code session context. Mirrors the completion handling in * dispatchToolEvents for the event-driven path. * * By handling completions here in graph context (rather than in the * stream consumer via ToolEndHandler), the race between the stream * consumer and graph execution is eliminated. */ private handleRunToolCompletions; /** * Converts InjectedMessage entries into LangChain HumanMessages. * 'user' and 'system' both become HumanMessage to avoid provider rejections * (Anthropic/Google reject non-leading SystemMessages); the original role * and metadata are preserved in additional_kwargs for downstream consumers. */ private convertInjectedMessages; /** * Dispatches tool calls to the host via ON_TOOL_EXECUTE event and returns * the resulting ToolMessages along with any injected messages emitted by * the host (e.g. SkillTool body injection). Injected messages must be * placed AFTER the ToolMessages so the AIMessage tool_calls -> ToolMessage * adjacency required by some providers is preserved. */ private dispatchToolEvents; /** * Execute all tool calls via ON_TOOL_EXECUTE event dispatch. * Used in event-driven mode where the host handles actual tool execution. */ private executeViaEvent; protected run(input: any, config: RunnableConfig): Promise; private isSendInput; private isMessagesState; } export declare function toolsCondition(state: BaseMessage[] | typeof MessagesAnnotation.State, toolNode: T, invokedToolIds?: Set): T | typeof END; export {};