UNPKG

langchain

Version:
1 lines • 15.6 kB
{"version":3,"file":"subagent.cjs","names":["StreamChannel","createMessagesTransformer","createToolCallTransformer"],"sources":["../../../src/agents/transformers/subagent.ts"],"sourcesContent":["import {\n StreamChannel,\n createMessagesTransformer,\n type NativeStreamTransformer,\n type ProtocolEvent,\n type Namespace,\n type LifecycleCause,\n} from \"@langchain/langgraph\";\n\nimport { createToolCallTransformer } from \"./tool-call.js\";\nimport type { SubagentRunStream } from \"./types.js\";\n\ninterface SubagentProjection {\n subagents: AsyncIterable<SubagentRunStream>;\n}\n\n/** Per-subagent transformer instances, driven manually by the parent. */\ntype MessagesTransformer = ReturnType<typeof createMessagesTransformer>;\ntype ToolCallTransformer = ReturnType<\n ReturnType<typeof createToolCallTransformer>\n>;\ntype NestedSubagentTransformer = ReturnType<\n ReturnType<typeof createSubagentTransformer>\n>;\n\ninterface SubagentHandle {\n readonly key: string;\n readonly path: Namespace;\n readonly name: string;\n readonly messages: MessagesTransformer;\n readonly toolCall: ToolCallTransformer;\n readonly nested: NestedSubagentTransformer;\n readonly resolveOutput: (value: unknown) => void;\n readonly rejectOutput: (error: unknown) => void;\n latestValues: Record<string, unknown> | undefined;\n done: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Stable string key for a namespace. */\nfunction nsKey(ns: Namespace): string {\n return ns.join(\"\\u0000\");\n}\n\n/** Tests whether `ns` starts with every segment in `prefix`. */\nfunction hasPrefix(ns: Namespace, prefix: Namespace): boolean {\n if (prefix.length > ns.length) return false;\n for (let i = 0; i < prefix.length; i += 1) {\n if (ns[i] !== prefix[i]) return false;\n }\n return true;\n}\n\n/**\n * Creates a native transformer that surfaces nested named agents on\n * `run.subagents`.\n *\n * It watches `tasks` events to record each namespace's `lc_agent_name` (set by\n * `createAgent({ name })`) and the triggering tool call, then — for any nested\n * run one level below {@link scope} that carries an `lc_agent_name` — emits a\n * typed {@link SubagentRunStream} handle.\n *\n * Each handle is backed by its own per-subagent transformer instances\n * ({@link createMessagesTransformer}, {@link createToolCallTransformer}, and a\n * nested {@link createSubagentTransformer}) scoped to the subagent's namespace.\n * Every event in the subtree is fed straight into those transformers, which\n * self-filter by namespace; the subagent's final `output` is resolved from its\n * last `values` snapshot when its `lifecycle` completes.\n *\n * Marked `__native: true` — the `subagents` projection lands directly on the\n * `GraphRunStream` instance as `run.subagents`.\n *\n * @param scope - Namespace prefix this transformer is scoped to. The root agent\n * uses `[]`; nested handles use their subagent's namespace, so grandchild\n * subagents are discovered recursively.\n */\nexport function createSubagentTransformer(\n scope: Namespace = []\n): () => NativeStreamTransformer<SubagentProjection> {\n return () => {\n const subagentsLog = StreamChannel.local<SubagentRunStream>();\n /** `lc_agent_name` observed per namespace (first task event wins). */\n const lcByNs = new Map<string, string | undefined>();\n /** Triggering task id -> originating LLM `tool_call_id`. */\n const pendingToolCalls = new Map<string, string>();\n /**\n * Namespace key -> the `tool_call_id` of the most recent tool to start\n * executing there. A tool that invokes a subagent emits its `tool-started`\n * at the tools-node namespace (`tools:<task_id>`) where the subagent then\n * roots, so this is the tool call that caused the subagent.\n */\n const activeToolCallByNs = new Map<string, string>();\n const handles = new Map<string, SubagentHandle>();\n const depth = scope.length;\n\n function recordIdentity(ns: Namespace, data: unknown): void {\n const key = nsKey(ns);\n if (lcByNs.has(key)) return;\n const metadata =\n isRecord(data) && isRecord(data.metadata) ? data.metadata : undefined;\n const lc = metadata?.lc_agent_name;\n lcByNs.set(key, typeof lc === \"string\" ? lc : undefined);\n }\n\n function recordPendingToolCalls(data: unknown): void {\n if (!isRecord(data)) return;\n const taskId = data.id;\n if (typeof taskId !== \"string\") return;\n const input = data.input;\n let toolCallId: string | undefined;\n if (isRecord(input) && isRecord(input.tool_call)) {\n const candidate = input.tool_call.id;\n if (typeof candidate === \"string\") toolCallId = candidate;\n } else if (Array.isArray(input)) {\n for (const toolCall of input) {\n if (isRecord(toolCall) && typeof toolCall.id === \"string\") {\n toolCallId = toolCall.id;\n break;\n }\n }\n }\n if (toolCallId != null) pendingToolCalls.set(taskId, toolCallId);\n }\n\n /**\n * Derive the `toolCall` cause for a named-subagent namespace.\n *\n * Primary signal: the tool whose `tool-started` event fired at the\n * subagent's own namespace (the tools node it roots under). Fallback: the\n * namespace segment's task id (`node:<task_id>`) joined to a tool call\n * harvested from a `tool_call_with_context`-shaped task input, so the\n * derivation stays correct if that shape reaches the stream in the future.\n */\n function deriveCause(ns: Namespace): LifecycleCause | undefined {\n const active = activeToolCallByNs.get(nsKey(ns));\n if (typeof active === \"string\" && active.length > 0) {\n return { type: \"toolCall\", tool_call_id: active } as LifecycleCause;\n }\n const segment = ns[ns.length - 1];\n const colon = segment.indexOf(\":\");\n if (colon === -1) return undefined;\n const triggerCallId = segment.slice(colon + 1);\n if (triggerCallId.length === 0) return undefined;\n const toolCallId = pendingToolCalls.get(triggerCallId);\n if (typeof toolCallId !== \"string\" || toolCallId.length === 0) {\n return undefined;\n }\n return { type: \"toolCall\", tool_call_id: toolCallId } as LifecycleCause;\n }\n\n function maybeStartSubagent(ns: Namespace): void {\n if (ns.length !== depth + 1 || !hasPrefix(ns, scope)) return;\n const key = nsKey(ns);\n if (handles.has(key)) return;\n const lc = lcByNs.get(key);\n // Only surface nested runs carrying an `lc_agent_name`; plain subgraphs\n // (no name) are excluded so `run.subagents` stays agent-only.\n if (typeof lc !== \"string\" || lc.length === 0) return;\n\n // Per-subagent transformers, each scoped to the subagent's namespace so\n // they pick out only the subagent's own model node / tools / nested\n // agents when fed the full event stream.\n const messages = createMessagesTransformer(ns);\n const messagesProjection = messages.init();\n const toolCall = createToolCallTransformer(ns)();\n const toolCallProjection = toolCall.init();\n const nested = createSubagentTransformer(ns)();\n const nestedProjection = nested.init();\n\n let resolveOutput!: (value: unknown) => void;\n let rejectOutput!: (error: unknown) => void;\n const output = new Promise<Record<string, unknown>>((resolve, reject) => {\n resolveOutput = resolve as (value: unknown) => void;\n rejectOutput = reject;\n });\n // Same as tool-call.ts: an unconsumed failed `output` must not crash the process.\n output.catch(() => {});\n\n handles.set(key, {\n key,\n path: ns,\n name: lc,\n messages,\n toolCall,\n nested,\n resolveOutput,\n rejectOutput,\n latestValues: undefined,\n done: false,\n });\n\n subagentsLog.push({\n name: lc,\n cause: deriveCause(ns),\n output,\n messages: messagesProjection.messages,\n toolCalls: toolCallProjection.toolCalls,\n subagents: nestedProjection.subagents,\n });\n }\n\n function finishHandle(\n handle: SubagentHandle,\n outcome: { type: \"resolve\" } | { type: \"reject\"; error: unknown }\n ): void {\n if (handle.done) return;\n handle.done = true;\n if (outcome.type === \"resolve\") {\n handle.resolveOutput(handle.latestValues);\n } else {\n handle.rejectOutput(outcome.error);\n }\n handle.messages.finalize?.();\n handle.toolCall.finalize?.();\n handle.nested.finalize?.();\n }\n\n return {\n __native: true as const,\n\n init: () => ({ subagents: subagentsLog }),\n\n process(event: ProtocolEvent): boolean {\n const ns = event.params.namespace;\n const data = event.params.data;\n const isTaskResult =\n event.method === \"tasks\" && isRecord(data) && \"result\" in data;\n\n // Track the tool currently executing at each namespace. A subagent's\n // dispatching tool starts at the same namespace the subagent roots\n // under, so this records the cause before the subagent is discovered.\n if (\n event.method === \"tools\" &&\n isRecord(data) &&\n data.event === \"tool-started\" &&\n typeof data.tool_call_id === \"string\" &&\n data.tool_call_id.length > 0\n ) {\n activeToolCallByNs.set(nsKey(ns), data.tool_call_id);\n }\n\n // A task start: record identity / tool call, then discover a subagent\n // boundary *before* fanning out so the new handle receives its own\n // subtree events (which Pregel emits after the parent-namespace task).\n if (event.method === \"tasks\" && !isTaskResult) {\n recordIdentity(ns, data);\n recordPendingToolCalls(data);\n maybeStartSubagent(ns);\n }\n\n // Fan the event out to every active subagent whose subtree contains it.\n // The per-subagent transformers self-filter by namespace depth.\n for (const handle of handles.values()) {\n if (handle.done) continue;\n if (!hasPrefix(ns, handle.path)) continue;\n\n handle.messages.process(event);\n handle.toolCall.process(event);\n handle.nested.process(event);\n\n // Track the subagent's own (root-level) state and resolve its\n // `output` from the last snapshot when its lifecycle completes.\n if (nsKey(ns) === handle.key) {\n if (event.method === \"values\" && isRecord(data)) {\n handle.latestValues = data;\n } else if (event.method === \"lifecycle\" && isRecord(data)) {\n const status = data.event;\n if (status === \"completed\" || status === \"interrupted\") {\n finishHandle(handle, { type: \"resolve\" });\n } else if (status === \"failed\") {\n finishHandle(handle, {\n type: \"reject\",\n error: new Error(`Subagent ${handle.name} failed`),\n });\n }\n }\n }\n }\n\n return true;\n },\n\n finalize(): void {\n for (const handle of handles.values()) {\n finishHandle(handle, { type: \"resolve\" });\n }\n subagentsLog.close();\n },\n\n fail(err: unknown): void {\n for (const handle of handles.values()) {\n finishHandle(handle, { type: \"reject\", error: err });\n }\n subagentsLog.fail(err);\n },\n };\n };\n}\n"],"mappings":"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"}