@langchain/langgraph-sdk
Version:
Client library for interacting with the LangGraph API
1 lines • 16.6 kB
Source Map (JSON)
{"version":3,"file":"tools.cjs","names":[],"sources":["../../../../src/client/stream/handles/tools.ts"],"sourcesContent":["import type {\n ToolsEvent,\n ToolStartedData,\n ToolFinishedData,\n ToolErrorData,\n} from \"@langchain/protocol\";\n\n/**\n * High-level outcome of a single tool call.\n */\nexport type ToolCallStatus = \"running\" | \"finished\" | \"error\";\n\n/** Shared metadata for assembled tool-call handles. */\ninterface ToolCallBase<TName extends string = string, TInput = unknown> {\n readonly name: TName;\n readonly callId: string;\n /**\n * Pre-v1 alias for {@link callId}. Matches `ToolCallWithResult.id` and\n * `ToolCall.id` on message-level tool calls.\n */\n readonly id: string;\n readonly namespace: string[];\n readonly input: TInput;\n /**\n * Pre-v1 alias for {@link input}. Matches `ToolCallFromTool` `args`.\n */\n readonly args: TInput;\n}\n\n/**\n * Script-oriented tool handle from the client SDK (`ThreadStream.toolCalls`,\n * subagent/subgraph projections). Completion and errors are surfaced only\n * through {@link output}.\n */\nexport interface ClientAssembledToolCall<\n TName extends string = string,\n TInput = unknown,\n TOutput = unknown,\n> extends ToolCallBase<TName, TInput> {\n readonly output: Promise<TOutput>;\n}\n\n/**\n * Reactive tool handle for framework bindings (`stream.toolCalls`,\n * `useToolCalls`, `injectToolCalls`).\n *\n * {@link status}, {@link error}, and {@link output} are plain values that\n * the assembler updates in place as tool events arrive. That lets React,\n * Vue, Svelte, and Angular re-render from a snapshot on each store tick\n * without `await`, effects, or Suspense boundaries around a promise.\n * {@link ClientAssembledToolCall} keeps a promise-based {@link output}\n * instead for script consumers that read tool results sequentially.\n *\n * {@link output} is `null` while the call is running or after it fails;\n * successful completion sets it to the parsed tool return value (objects\n * and strings are unwrapped from ToolMessage wire envelopes when needed).\n */\nexport interface AssembledToolCall<\n TName extends string = string,\n TInput = unknown,\n TOutput = unknown,\n> extends ToolCallBase<TName, TInput> {\n readonly output: TOutput | null;\n readonly status: ToolCallStatus;\n readonly error: string | undefined;\n}\n\n/** @internal Mutable runtime handle shared by client and framework views. */\ntype MutableToolCallHandle = {\n name: string;\n callId: string;\n id: string;\n namespace: string[];\n input: unknown;\n args: unknown;\n output: unknown | null;\n status: ToolCallStatus;\n error: string | undefined;\n outputPromise: Promise<unknown>;\n};\n\n/**\n * Project a runtime handle to the client SDK surface (promise-only\n * {@link output}, no {@link status} / {@link error} fields).\n */\nexport function toClientAssembledToolCall(\n handle: MutableToolCallHandle\n): ClientAssembledToolCall {\n return {\n name: handle.name,\n callId: handle.callId,\n id: handle.id,\n namespace: handle.namespace,\n input: handle.input,\n args: handle.args,\n output: handle.outputPromise as Promise<unknown>,\n };\n}\n\n/**\n * Parse wire-format tool payloads into structured values.\n *\n * Tool events may carry JSON-encoded object strings on the wire; this\n * helper normalises them to plain objects for consumers. Non-JSON strings\n * are returned unchanged.\n */\nexport function parseToolPayload(value: unknown): unknown {\n if (typeof value !== \"string\") return value;\n const trimmed = value.trim();\n if (!trimmed.startsWith(\"{\") && !trimmed.startsWith(\"[\")) {\n return value;\n }\n\n try {\n return JSON.parse(trimmed) as unknown;\n } catch {\n return value;\n }\n}\n\n/**\n * Skip wrapper `task` tool events scoped to a subagent namespace.\n *\n * Deep-agent subagents are discovered from root-level `task` tool calls;\n * replaying the same dispatch tool inside the worker namespace would\n * otherwise surface as a spurious entry in `sub.toolCalls`.\n */\nexport function shouldIgnoreScopedTaskToolEvent(\n scopeNamespace: readonly string[],\n event: ToolsEvent\n): boolean {\n const data = event.params.data;\n return (\n scopeNamespace.length > 0 &&\n event.params.namespace.length === scopeNamespace.length &&\n event.params.namespace.every(\n (segment, index) => segment === scopeNamespace[index]\n ) &&\n \"tool_name\" in data &&\n data.tool_name === \"task\"\n );\n}\n\nfunction getWireMessageField(message: unknown, field: string): unknown {\n if (!message || typeof message !== \"object\" || Array.isArray(message)) {\n return undefined;\n }\n const record = message as Record<string, unknown>;\n if (field in record) return record[field];\n const kwargs = record.kwargs as Record<string, unknown> | undefined;\n if (kwargs != null && field in kwargs) return kwargs[field];\n const lcKwargs = record.lc_kwargs as Record<string, unknown> | undefined;\n if (lcKwargs != null && field in lcKwargs) return lcKwargs[field];\n return undefined;\n}\n\nfunction isToolMessageLike(\n value: unknown\n): value is Record<string, unknown> & { content: unknown } {\n if (!value || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n if (record.type === \"tool\") return true;\n const type = getWireMessageField(value, \"type\");\n if (type === \"tool\") return true;\n return (\n typeof getWireMessageField(value, \"tool_call_id\") === \"string\" &&\n getWireMessageField(value, \"content\") !== undefined\n );\n}\n\nfunction isCommandLike(\n value: unknown\n): value is Record<string, unknown> & { update?: unknown } {\n return (\n !!value &&\n typeof value === \"object\" &&\n (value as Record<string, unknown>).lg_name === \"Command\"\n );\n}\n\nfunction textFromContentBlocks(content: unknown[]): string {\n let out = \"\";\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const record = block as Record<string, unknown>;\n if (record.type === \"text\" && typeof record.text === \"string\") {\n out += record.text;\n }\n }\n return out;\n}\n\n/**\n * Normalise tool-result `content` from a wire ToolMessage into the value\n * a tool implementation returned (object, string, etc.).\n */\nfunction parseToolResultContent(content: unknown): unknown | null {\n if (content == null) return null;\n\n if (typeof content === \"string\") {\n const trimmed = content.trim();\n if (trimmed.length === 0) return null;\n return parseToolPayload(content);\n }\n\n if (Array.isArray(content)) {\n const text = textFromContentBlocks(content);\n if (text.length === 0) return null;\n return parseToolPayload(text);\n }\n\n if (typeof content === \"object\") {\n return content;\n }\n\n return null;\n}\n\nfunction parseToolMessageRecord(message: unknown): unknown | null {\n const content = getWireMessageField(message, \"content\");\n return parseToolResultContent(content);\n}\n\ntype ParsedCommandToolOutput =\n | { found: true; value: unknown | null }\n | { found: false };\n\nfunction parseCommandToolOutput(\n command: Record<string, unknown> & { update?: unknown },\n toolCallId?: string\n): ParsedCommandToolOutput {\n const update = command.update;\n if (update == null || typeof update !== \"object\" || Array.isArray(update)) {\n return { found: false };\n }\n\n const messages = (update as Record<string, unknown>).messages;\n if (!Array.isArray(messages)) return { found: false };\n\n const toolMessages = messages.filter((message) => isToolMessageLike(message));\n if (toolMessages.length === 0) return { found: false };\n\n if (toolCallId != null) {\n for (const message of toolMessages) {\n const callId = getWireMessageField(message, \"tool_call_id\");\n if (callId !== toolCallId) continue;\n return { found: true, value: parseToolMessageRecord(message) };\n }\n return { found: false };\n }\n\n if (toolMessages.length === 1) {\n return { found: true, value: parseToolMessageRecord(toolMessages[0]) };\n }\n\n for (let i = toolMessages.length - 1; i >= 0; i -= 1) {\n const parsed = parseToolMessageRecord(toolMessages[i]);\n if (parsed != null) return { found: true, value: parsed };\n }\n\n return { found: true, value: null };\n}\n\n/**\n * Parse a `tool-finished` output payload into the tool's return value.\n *\n * Wire events often wrap structured tool results in a ToolMessage-shaped\n * object (`{ type: \"tool\", content: \"...\" }`) or a LangGraph\n * {@link Command} whose `update.messages` carries the ToolMessage.\n * This unwraps those envelopes, JSON-decodes string content when possible,\n * and leaves plain strings as-is. Returns `null` when a ToolMessage envelope\n * is present but its content cannot be normalised.\n */\nexport function parseToolOutput(\n value: unknown,\n toolCallId?: string\n): unknown | null {\n const parsed = parseToolPayload(value);\n if (isCommandLike(parsed)) {\n const commandOutput = parseCommandToolOutput(parsed, toolCallId);\n return commandOutput.found ? commandOutput.value : parsed;\n }\n if (isToolMessageLike(parsed)) {\n return parseToolResultContent(getWireMessageField(parsed, \"content\"));\n }\n return parsed ?? null;\n}\n\ntype ActiveToolCall = {\n handle: MutableToolCallHandle;\n resolveOutput: (value: unknown) => void;\n rejectOutput: (err: Error) => void;\n};\n\n/**\n * Incrementally assembles `tools` events into mutable tool-call handles.\n *\n * Framework consumers store the handle directly; client SDK consumers\n * should map with {@link toClientAssembledToolCall} before yielding.\n */\nexport class ToolCallAssembler {\n private readonly active = new Map<string, ActiveToolCall>();\n\n consume(event: ToolsEvent): MutableToolCallHandle | undefined {\n const data = event.params.data;\n\n if (data.event === \"tool-started\") {\n return this.handleStarted(event, data);\n }\n\n if (data.event === \"tool-finished\") {\n return this.handleFinished(data);\n }\n\n if (data.event === \"tool-error\") {\n return this.handleError(data);\n }\n\n // tool-output-delta: no action needed at assembly level\n return undefined;\n }\n\n /**\n * Reject any in-flight tool calls (e.g. on session close).\n */\n failAll(reason: Error): void {\n for (const entry of this.active.values()) {\n entry.rejectOutput(reason);\n entry.handle.status = \"error\";\n entry.handle.error = reason.message;\n }\n this.active.clear();\n }\n\n private handleStarted(\n event: ToolsEvent,\n data: ToolStartedData\n ): MutableToolCallHandle {\n let resolveOutput!: (value: unknown) => void;\n let rejectOutput!: (err: Error) => void;\n\n const outputPromise = new Promise<unknown>((resolve, reject) => {\n resolveOutput = resolve;\n rejectOutput = reject;\n });\n // Attach a default no-op catch so if no consumer awaits\n // `output` the eventual rejection on `tool-error` / `failAll`\n // doesn't surface as an unhandled Promise rejection.\n outputPromise.catch(() => undefined);\n\n const input = parseToolPayload(data.input);\n const name = data.tool_name;\n const callId = data.tool_call_id;\n const namespace = [...event.params.namespace];\n\n const handle: MutableToolCallHandle = {\n name,\n callId,\n id: callId,\n namespace,\n input,\n args: input,\n output: null,\n status: \"running\",\n error: undefined,\n outputPromise,\n };\n\n this.active.set(callId, {\n handle,\n resolveOutput,\n rejectOutput,\n });\n\n return handle;\n }\n\n private handleFinished(\n data: ToolFinishedData\n ): MutableToolCallHandle | undefined {\n const entry = this.active.get(data.tool_call_id);\n if (!entry) return undefined;\n this.active.delete(data.tool_call_id);\n const value = parseToolOutput(data.output, data.tool_call_id);\n entry.resolveOutput(value);\n entry.handle.output = value;\n entry.handle.status = \"finished\";\n entry.handle.error = undefined;\n return entry.handle;\n }\n\n private handleError(data: ToolErrorData): MutableToolCallHandle | undefined {\n const entry = this.active.get(data.tool_call_id);\n if (!entry) return undefined;\n this.active.delete(data.tool_call_id);\n entry.rejectOutput(new Error(data.message));\n entry.handle.output = null;\n entry.handle.status = \"error\";\n entry.handle.error = data.message;\n return entry.handle;\n }\n}\n"],"mappings":"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"}