UNPKG

langchain

Version:
1 lines 12 kB
{"version":3,"file":"tool-call.cjs","names":["ToolMessage","StreamChannel"],"sources":["../../../src/agents/transformers/tool-call.ts"],"sourcesContent":["import {\n StreamChannel,\n type NativeStreamTransformer,\n type ProtocolEvent,\n type ToolCallStream,\n type ToolCallStatus,\n type ToolsEventData,\n type Namespace,\n} from \"@langchain/langgraph\";\nimport { ToolMessage } from \"@langchain/core/messages\";\n\ninterface ToolCallProjection {\n toolCalls: AsyncIterable<ToolCallStream>;\n}\n\n/**\n * Returns true when `ns` belongs to the agent's own graph — i.e. it\n * starts with `path` and is at most one level deeper (the agent's\n * internal nodes like `tools`, `model_request`, etc.).\n *\n * Events from subagent subgraphs (two or more levels deeper) are\n * excluded, so `run.toolCalls` / `run.middleware` only show events\n * from the agent itself, not from its subagents.\n */\nfunction isOwnEvent(ns: Namespace, path: Namespace): boolean {\n if (ns.length < path.length || ns.length > path.length + 1) return false;\n for (let i = 0; i < path.length; i += 1) {\n if (ns[i] !== path[i]) return false;\n }\n return true;\n}\n\n/**\n * Detects when a `tool-error` payload is actually a graph interrupt rather\n * than a genuine tool failure.\n *\n * A tool that calls `interrupt()` throws a `GraphInterrupt`, whose message is\n * the JSON-serialized `Interrupt[]` array. Each entry has the LangGraph\n * `Interrupt` shape `{ id, value }`: a stable `id` (a hash of the checkpoint\n * namespace, generated by `interrupt()` and always present during graph\n * execution) plus the `value` passed to `interrupt(...)`. We require BOTH a\n * string `id` and a `value` on every entry — a bare `value` is not a reliable\n * discriminator, since a genuine tool error message can also be a JSON array\n * of `{ value }` records (e.g. a validator emitting\n * `[{ \"value\": \"bad input\", \"message\": \"invalid\" }]`). Keying off the\n * interrupt `id` keeps real tool failures on the error path.\n *\n * An interrupt is control flow that *suspends* the run (the tool re-runs on\n * resume); it is not an error, so the tool call must stay pending rather than\n * have its `output` promise rejected. Any interrupt qualifies regardless of\n * its `value` shape: HITL middleware interrupts (`value.type === \"tool\"`) and\n * raw `interrupt(...)` calls from inside a tool are treated identically —\n * raising an interrupt in a tool must work whether or not\n * `humanInTheLoopMiddleware` is involved.\n */\nfunction isToolInterrupt(message: string): boolean {\n let parsed: unknown;\n try {\n parsed = JSON.parse(message);\n } catch {\n return false;\n }\n if (!Array.isArray(parsed) || parsed.length === 0) return false;\n return parsed.every((entry) => {\n if (entry == null || typeof entry !== \"object\") return false;\n const record = entry as Record<string, unknown>;\n return typeof record.id === \"string\" && \"value\" in record;\n });\n}\n\n/**\n * Detects serialized LangChain `ToolMessage` values that can appear on\n * `tool-finished.output` after crossing a protocol or serialization boundary.\n *\n * @example\n * ```ts\n * {\n * lc: 1,\n * type: \"constructor\",\n * id: [\"langchain_core\", \"messages\", \"ToolMessage\"],\n * kwargs: { content: \"raw tool result\", tool_call_id: \"call_1\" }\n * }\n * ```\n */\nfunction isSerializedToolMessage(\n value: unknown\n): value is { kwargs?: { content?: unknown } } {\n if (value == null || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n if (record.type !== \"constructor\" || !Array.isArray(record.id)) return false;\n return record.id[record.id.length - 1] === \"ToolMessage\";\n}\n\nfunction normalizeToolOutput(output: unknown): unknown {\n if (ToolMessage.isInstance(output)) {\n return output.content;\n }\n if (isSerializedToolMessage(output)) {\n return output.kwargs?.content;\n }\n return output;\n}\n\n/**\n * Creates a native transformer that correlates `tools` channel events\n * into per-call {@link ToolCallStream} objects.\n *\n * Marked `__native: true` — projection keys land directly on the\n * `GraphRunStream` instance as `run.toolCalls`.\n */\nexport function createToolCallTransformer(\n path: Namespace\n): () => NativeStreamTransformer<ToolCallProjection> {\n return () => {\n const toolCallsLog = StreamChannel.local<ToolCallStream>();\n\n const pendingCalls = new Map<\n string,\n {\n resolveOutput: (v: unknown) => void;\n rejectOutput: (e: unknown) => void;\n resolveStatus: (v: ToolCallStatus) => void;\n resolveError: (v: string | undefined) => void;\n }\n >();\n\n function createToolCallEntry(\n callId: string,\n name: string,\n rawInput: unknown\n ): void {\n if (pendingCalls.has(callId)) return;\n const input =\n typeof rawInput === \"string\" ? JSON.parse(rawInput) : rawInput;\n\n let resolveOutput!: (v: unknown) => void;\n let rejectOutput!: (e: unknown) => void;\n let resolveStatus!: (v: ToolCallStatus) => void;\n let resolveError!: (v: string | undefined) => void;\n\n const output = new Promise<unknown>((res, rej) => {\n resolveOutput = res;\n rejectOutput = rej;\n });\n const status = new Promise<ToolCallStatus>((res) => {\n resolveStatus = res;\n });\n const error = new Promise<string | undefined>((res) => {\n resolveError = res;\n });\n\n pendingCalls.set(callId, {\n resolveOutput,\n rejectOutput,\n resolveStatus,\n resolveError,\n });\n\n toolCallsLog.push({\n name,\n callId,\n input,\n output,\n status,\n error,\n } as ToolCallStream);\n }\n\n return {\n __native: true as const,\n\n init: () => ({\n toolCalls: toolCallsLog,\n }),\n\n process(event: ProtocolEvent): boolean {\n /**\n * Only process events that are at the same depth as the agent's graph.\n */\n if (!isOwnEvent(event.params.namespace, path)) return true;\n\n if (event.method === \"messages\") {\n const data = event.params.data as Record<string, unknown>;\n if (data.event === \"content-block-finish\") {\n const cb = (data.contentBlock ?? data.content_block) as\n | Record<string, unknown>\n | undefined;\n if (cb?.type === \"tool_call\") {\n createToolCallEntry(\n String(cb.id ?? \"\"),\n String(cb.name ?? \"\"),\n cb.args ?? cb.input\n );\n }\n }\n }\n\n if (event.method === \"tools\") {\n const data = event.params.data as ToolsEventData;\n const toolCallId = (data as Record<string, unknown>)\n .tool_call_id as string;\n\n if (data.event === \"tool-started\") {\n createToolCallEntry(\n toolCallId,\n ((data as Record<string, unknown>).tool_name as string) ??\n \"unknown\",\n (data as Record<string, unknown>).input\n );\n }\n\n const pending = toolCallId ? pendingCalls.get(toolCallId) : undefined;\n\n if (pending) {\n if (data.event === \"tool-finished\") {\n pending.resolveOutput(\n normalizeToolOutput((data as Record<string, unknown>).output)\n );\n pending.resolveStatus(\"finished\");\n pending.resolveError(undefined);\n pendingCalls.delete(toolCallId);\n } else if (data.event === \"tool-error\") {\n const message =\n ((data as Record<string, unknown>).message as string) ??\n \"unknown error\";\n // An interrupt raised inside a tool (HITL middleware *or* a\n // raw `interrupt()`) surfaces here as a `tool-error` whose\n // message is the serialized interrupt. It is control flow,\n // not a failure: keep the call pending (it re-runs on resume)\n // and never reject `output`, which would otherwise become an\n // unhandled rejection and crash the run.\n if (isToolInterrupt(message)) {\n return true;\n }\n pending.rejectOutput(new Error(message));\n pending.resolveStatus(\"error\");\n pending.resolveError(message);\n pendingCalls.delete(toolCallId);\n }\n }\n }\n\n return true;\n },\n\n finalize(): void {\n for (const pending of pendingCalls.values()) {\n pending.resolveStatus(\"finished\");\n pending.resolveError(undefined);\n pending.resolveOutput(undefined);\n }\n pendingCalls.clear();\n toolCallsLog.close();\n },\n\n fail(err: unknown): void {\n for (const pending of pendingCalls.values()) {\n pending.resolveStatus(\"error\");\n pending.resolveError(\n err instanceof Error ? err.message : String(err)\n );\n pending.rejectOutput(err);\n }\n pendingCalls.clear();\n toolCallsLog.fail(err);\n },\n };\n };\n}\n"],"mappings":"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"}