/** * Surface 4 (V0.2) — `DarwinCallbackHandler`. * * Drop-in replacement for {@link withDarwinEvolution} that uses * LangChain's canonical `BaseCallbackHandler` mechanism instead of * monkey-patching `graph.invoke` / `graph.stream`. The same trajectory * hook fires, the same `nodeMap` routing applies, but the integration * is now LangGraph-native — no method overwrites, no concurrent-invoke * Set dance, no streamMode gymnastics. * * Usage: * ```ts * import { DarwinCallbackHandler } from "darwin-langgraph"; * * const handler = new DarwinCallbackHandler({ * nodeMap: { research: "researcher" }, * onTrajectory: (event) => { * console.log(event.nodeName, event.trajectory.toolCalls.length); * }, * }); * * const result = await graph.invoke( * { task: "What is GEPA?" }, * { callbacks: [handler] }, * ); * ``` * * Design notes (S1185 V0.2): * - **runId → runName mapping.** LangChain invokes `handleChainStart` * with the `runName` arg (the node name in LangGraph's case) and * a unique `runId`. We cache that mapping. On `handleChainEnd` we * look up the name, find the matching `nodeMap` entry, and dispatch * `onTrajectory`. * - **Fire-and-forget.** Like the v0.1 monkey-patch wrapper, hook * errors are swallowed with one warn-once per handler instance. * - **No mutation of LangGraph internals.** This handler never touches * `graph.invoke` or `graph.stream` — registering it via the standard * `{ callbacks: [...] }` option is the only side effect, which is * LangChain's documented integration point (matches Langfuse, * Braintrust, LangSmith handler patterns). * - **Stream-mode-agnostic.** Works identically with `invoke`, * `stream` (any streamMode), and `streamEvents` because the chain * callbacks fire regardless of how the consumer iterates. * - **Concurrent runs work natively.** LangChain's runId is unique * per call — no shared-counter race condition is possible. * - **Backwards compat.** `withDarwinEvolution` from v0.1 still works * (marked `@deprecated`) and produces the same `DarwinTrajectoryEvent` * payload shape. Migration is one line: replace * `withDarwinEvolution(graph, opts)` with * `graph.invoke(input, { callbacks: [new DarwinCallbackHandler(opts)] })`. */ import { BaseCallbackHandler } from "@langchain/core/callbacks/base"; import type { ChainValues } from "@langchain/core/utils/types"; import type { DarwinEvolutionOptions } from "./with-darwin-evolution.js"; /** * Highest `ExecutionTrace.version` this handler natively understands. * The forward-compat guard in {@link isExecutionTrace} warns once for * higher versions but still emits them (structural shape covers the * required fields) so a Darwin upgrade ahead of the adapter doesn't * silently drop trajectories. * * NEW V0.4 (S1235). */ export declare const MAX_KNOWN_TRACE_VERSION = 1; /** * Surface 8 (V0.4) — `DarwinToolEvent`. * * Emitted by {@link DarwinCallbackHandler.onToolEvent} whenever LangGraph * surfaces a tool-chain start, end, or error inside a tracked node-chain. * Lets consumers correlate per-tool spans with the parent agent trajectory * BEFORE the chain completes — useful for live OTEL span emission, * progress UIs, and abort-on-tool-failure logic. * * Phase semantics: * - `"start"` — populated `input`, no `output`/`error`. Fired from * `handleToolStart`. * - `"end"` — populated `output`, no `error`. Fired from `handleToolEnd`. * - `"error"` — populated `error`, no `output`. Fired from `handleToolError`. * * NEW V0.4 (S1235). */ export interface DarwinToolEvent { /** Node name the tool ran under (resolved from `runIdToName` via the * tool-chain's parentRunId — the chain runId from `handleChainStart`). */ nodeName: string; /** Darwin agent name (from `nodeMap`). */ agentName: string; /** Tool name LangChain passed in `handleToolStart` (often the function name). */ toolName: string; /** Phase of the tool-chain event. */ phase: "start" | "end" | "error"; /** LangChain runId of this tool-chain (NOT the parent chain). */ runId: string; /** LangChain parentRunId — chain runId of the node the tool ran under. */ parentRunId: string; /** Raw input arg passed to `handleToolStart`. Only present on phase=start. */ input?: string; /** Raw output from `handleToolEnd`. Only present on phase=end. */ output?: unknown; /** Error from `handleToolError`. Only present on phase=error. */ error?: Error; } /** * V0.3 — extra options on top of `DarwinEvolutionOptions`. Pass to * `new DarwinCallbackHandler({ ...opts, maxInFlightRuns })`. * * NEW V0.3 (S1187), extended V0.4 (S1235). */ export interface DarwinCallbackHandlerOptions extends DarwinEvolutionOptions { /** * Maximum number of in-flight `runId → nodeName` mappings the handler * holds at once. If `handleChainEnd` / `handleChainError` never fires * (LangGraph internal bug, OS-level kill of the worker mid-invoke, * etc.) the map would otherwise grow without bound and leak memory. * * When the limit is exceeded, the OLDEST entry is evicted and a * one-shot warning is logged. Default: 1024 (enough for typical * fan-out patterns with safety margin, small enough to surface real * leaks within minutes of an incident). * * Set to `Infinity` to opt out — discouraged in production. */ maxInFlightRuns?: number; /** * V0.4 — soft cap on the serialised trajectory size before it is * surfaced through `onTrajectory`. When a trajectory's `JSON.stringify` * length exceeds `maxTrajectoryBytes`, the handler emits a one-shot * warning and replaces the `trajectory` field on the event with a * structurally-compatible BUT minimal stub (same `version`, empty * `toolCalls` and `errors`, original counts and capturedAt preserved). * Downstream consumers can detect the swap by inspecting * `event.trajectoryTruncated === true`. * * Default: `262_144` (256 KiB). Set to `Infinity` to opt out. * * NEW V0.4 (S1235). */ maxTrajectoryBytes?: number; /** * V0.4 — Fired for every tool-chain start / end / error observed inside * a tracked node-chain. Use this to emit per-tool OTEL spans, drive a * progress UI, or abort downstream work on a tool failure. * * Errors thrown inside `onToolEvent` are swallowed with a single warn * per handler instance — never breaks the graph. * * NEW V0.4 (S1235). */ onToolEvent?: (event: DarwinToolEvent) => void | Promise; } /** * LangChain `BaseCallbackHandler` that listens for LangGraph node-chain * events and dispatches Darwin trajectory hooks. Pass it via the * standard `{ callbacks: [...] }` option to any `invoke`/`stream`/`streamEvents` * call on a compiled `StateGraph`. */ export declare class DarwinCallbackHandler extends BaseCallbackHandler { readonly name = "DarwinCallbackHandler"; readonly awaitHandlers = false; private readonly resolved; private readonly onTrajectory; private readonly onToolEvent; /** * runId → in-flight run state. Map preserves insertion order so the * oldest entry is always at `.keys().next().value` for LRU eviction * when the cap is exceeded (V0.3 hung-invoke guard). */ private readonly runIdToName; /** * V0.4 (R1 P1-1 fix, S1235) — `toolRunId → toolName` cache populated * from `handleToolStart` and consumed by `handleToolEnd` / * `handleToolError`. LangChain v0.3 `handleToolEnd` does NOT reliably * carry the tool name in its signature (the slot was repurposed * across releases); reading `output.name` only works for some shapes * and yields "" for the common ToolNode case. */ private readonly toolRunIdToName; /** * V0.4 (R1 P1-1 fix, S1235) — pending callbacks keyed by parent chain * runId so we can also restore the chain's tool name at end-time even * after the `toolRunIdToName` entry is cleaned up. */ private readonly maxInFlightRuns; /** V0.4 — byte cap before trajectory replacement. */ private readonly maxTrajectoryBytes; /** * V0.4 (R1 P1-1 fix) — also cap the tool-name cache. Default matches * `maxInFlightRuns` since tool runs are children of chain runs. */ private readonly maxInFlightToolRuns; /** * Pending trajectory dispatches keyed by chain runId. When a chain * ends with tools still in flight we hold the dispatch closure here * and fire it from the LAST `handleToolEnd` / `handleToolError`. * * V0.4 (R1 P1-4 fix, S1235). */ private readonly pendingTrajectoryDispatch; private warned; private evictionWarned; private toolWarned; /** V0.4 — once-per-handler warn for trajectory size cap. */ private trajectoryTruncatedWarned; constructor(opts: DarwinCallbackHandlerOptions); /** * Capture the run-id → node-name mapping. * * Implementation note (S1185 V0.2 — live-debug against @langchain/langgraph@1.3.x): * `metadata.langgraph_node` is the stable, reliable source for the * StateGraph node name. LangGraph populates it on every node-chain * invocation. The `runName` parameter slot in `@langchain/core`'s * `BaseCallbackHandler` d.ts is undefined for LangGraph chains at * runtime — we keep it as a fallback for non-LangGraph chains. */ handleChainStart(_chain: unknown, _inputs: ChainValues, runId: string, _runType?: string, _tags?: string[], metadata?: Record, runName?: string, parentRunId?: string, _extra?: Record): void; /** * When a node-chain finishes, look up its name, locate the matching * trajectory in `outputs`, and dispatch onTrajectory. * * `outputs` carries the state update the node returned — which may be * a partial state (just the new keys). When the node was wrapped via * {@link createDarwinNode}, the trajectoryKey lives directly on that * partial state under its configured name. */ handleChainEnd(outputs: ChainValues, runId: string, _parentRunId?: string, _tags?: string[], _kwargs?: { inputs?: ChainValues; }): void; /** * Forget the in-flight runId when a chain errors out. We don't fire * the hook on error — the trajectory is by definition incomplete. */ handleChainError(_err: Error, runId: string, _parentRunId?: string): void; /** * V0.4 (S1235) — tool-chain start event. LangGraph fires `handleToolStart` * for every tool-invocation a node performs (whether via `ToolNode`, * `createReactAgent`, or a custom tool-call dispatcher). We use the * `parentRunId` slot to associate the tool with the tracking node and * emit a `DarwinToolEvent` so consumers can build per-tool OTEL spans * or progress UIs. * * If `parentRunId` is missing OR the parent is not a tracked node, the * tool event is silently dropped — the handler never surfaces unmapped * activity (consistent with the chain-side `resolved.has(nodeName)` gate). */ handleToolStart(_tool: unknown, input: string, runId: string, parentRunId?: string, _tags?: string[], _metadata?: Record, _runType?: string, name?: string): void; /** * V0.4 (S1235) — tool-chain end event. */ handleToolEnd(output: unknown, runId: string, parentRunId?: string): void; /** * V0.4 (S1235) — tool-chain error event. */ handleToolError(err: Error, runId: string, parentRunId?: string): void; private dispatchToolEvent; private swallow; private swallowTool; /** * Helper for tests + debug introspection — returns how many in-flight * chain runs we are currently tracking. Should be 0 between * top-level invocations. */ getInFlightCount(): number; } //# sourceMappingURL=darwin-callback-handler.d.ts.map