langchain
Version:
Typescript bindings for langchain
205 lines (204 loc) • 7.81 kB
JavaScript
require("../../_virtual/_rolldown/runtime.cjs");
const require_tool_call = require("./tool-call.cjs");
let _langchain_langgraph = require("@langchain/langgraph");
//#region src/agents/transformers/subagent.ts
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Stable string key for a namespace. */
function nsKey(ns) {
return ns.join("\0");
}
/** Tests whether `ns` starts with every segment in `prefix`. */
function hasPrefix(ns, prefix) {
if (prefix.length > ns.length) return false;
for (let i = 0; i < prefix.length; i += 1) if (ns[i] !== prefix[i]) return false;
return true;
}
/**
* Creates a native transformer that surfaces nested named agents on
* `run.subagents`.
*
* It watches `tasks` events to record each namespace's `lc_agent_name` (set by
* `createAgent({ name })`) and the triggering tool call, then — for any nested
* run one level below {@link scope} that carries an `lc_agent_name` — emits a
* typed {@link SubagentRunStream} handle.
*
* Each handle is backed by its own per-subagent transformer instances
* ({@link createMessagesTransformer}, {@link createToolCallTransformer}, and a
* nested {@link createSubagentTransformer}) scoped to the subagent's namespace.
* Every event in the subtree is fed straight into those transformers, which
* self-filter by namespace; the subagent's final `output` is resolved from its
* last `values` snapshot when its `lifecycle` completes.
*
* Marked `__native: true` — the `subagents` projection lands directly on the
* `GraphRunStream` instance as `run.subagents`.
*
* @param scope - Namespace prefix this transformer is scoped to. The root agent
* uses `[]`; nested handles use their subagent's namespace, so grandchild
* subagents are discovered recursively.
*/
function createSubagentTransformer(scope = []) {
return () => {
const subagentsLog = _langchain_langgraph.StreamChannel.local();
/** `lc_agent_name` observed per namespace (first task event wins). */
const lcByNs = /* @__PURE__ */ new Map();
/** Triggering task id -> originating LLM `tool_call_id`. */
const pendingToolCalls = /* @__PURE__ */ new Map();
/**
* Namespace key -> the `tool_call_id` of the most recent tool to start
* executing there. A tool that invokes a subagent emits its `tool-started`
* at the tools-node namespace (`tools:<task_id>`) where the subagent then
* roots, so this is the tool call that caused the subagent.
*/
const activeToolCallByNs = /* @__PURE__ */ new Map();
const handles = /* @__PURE__ */ new Map();
const depth = scope.length;
function recordIdentity(ns, data) {
const key = nsKey(ns);
if (lcByNs.has(key)) return;
const lc = (isRecord(data) && isRecord(data.metadata) ? data.metadata : void 0)?.lc_agent_name;
lcByNs.set(key, typeof lc === "string" ? lc : void 0);
}
function recordPendingToolCalls(data) {
if (!isRecord(data)) return;
const taskId = data.id;
if (typeof taskId !== "string") return;
const input = data.input;
let toolCallId;
if (isRecord(input) && isRecord(input.tool_call)) {
const candidate = input.tool_call.id;
if (typeof candidate === "string") toolCallId = candidate;
} else if (Array.isArray(input)) {
for (const toolCall of input) if (isRecord(toolCall) && typeof toolCall.id === "string") {
toolCallId = toolCall.id;
break;
}
}
if (toolCallId != null) pendingToolCalls.set(taskId, toolCallId);
}
/**
* Derive the `toolCall` cause for a named-subagent namespace.
*
* Primary signal: the tool whose `tool-started` event fired at the
* subagent's own namespace (the tools node it roots under). Fallback: the
* namespace segment's task id (`node:<task_id>`) joined to a tool call
* harvested from a `tool_call_with_context`-shaped task input, so the
* derivation stays correct if that shape reaches the stream in the future.
*/
function deriveCause(ns) {
const active = activeToolCallByNs.get(nsKey(ns));
if (typeof active === "string" && active.length > 0) return {
type: "toolCall",
tool_call_id: active
};
const segment = ns[ns.length - 1];
const colon = segment.indexOf(":");
if (colon === -1) return void 0;
const triggerCallId = segment.slice(colon + 1);
if (triggerCallId.length === 0) return void 0;
const toolCallId = pendingToolCalls.get(triggerCallId);
if (typeof toolCallId !== "string" || toolCallId.length === 0) return;
return {
type: "toolCall",
tool_call_id: toolCallId
};
}
function maybeStartSubagent(ns) {
if (ns.length !== depth + 1 || !hasPrefix(ns, scope)) return;
const key = nsKey(ns);
if (handles.has(key)) return;
const lc = lcByNs.get(key);
if (typeof lc !== "string" || lc.length === 0) return;
const messages = (0, _langchain_langgraph.createMessagesTransformer)(ns);
const messagesProjection = messages.init();
const toolCall = require_tool_call.createToolCallTransformer(ns)();
const toolCallProjection = toolCall.init();
const nested = createSubagentTransformer(ns)();
const nestedProjection = nested.init();
let resolveOutput;
let rejectOutput;
const output = new Promise((resolve, reject) => {
resolveOutput = resolve;
rejectOutput = reject;
});
handles.set(key, {
key,
path: ns,
name: lc,
messages,
toolCall,
nested,
resolveOutput,
rejectOutput,
latestValues: void 0,
done: false
});
subagentsLog.push({
name: lc,
cause: deriveCause(ns),
output,
messages: messagesProjection.messages,
toolCalls: toolCallProjection.toolCalls,
subagents: nestedProjection.subagents
});
}
function finishHandle(handle, outcome) {
if (handle.done) return;
handle.done = true;
if (outcome.type === "resolve") handle.resolveOutput(handle.latestValues);
else handle.rejectOutput(outcome.error);
handle.messages.finalize?.();
handle.toolCall.finalize?.();
handle.nested.finalize?.();
}
return {
__native: true,
init: () => ({ subagents: subagentsLog }),
process(event) {
const ns = event.params.namespace;
const data = event.params.data;
const isTaskResult = event.method === "tasks" && isRecord(data) && "result" in data;
if (event.method === "tools" && isRecord(data) && data.event === "tool-started" && typeof data.tool_call_id === "string" && data.tool_call_id.length > 0) activeToolCallByNs.set(nsKey(ns), data.tool_call_id);
if (event.method === "tasks" && !isTaskResult) {
recordIdentity(ns, data);
recordPendingToolCalls(data);
maybeStartSubagent(ns);
}
for (const handle of handles.values()) {
if (handle.done) continue;
if (!hasPrefix(ns, handle.path)) continue;
handle.messages.process(event);
handle.toolCall.process(event);
handle.nested.process(event);
if (nsKey(ns) === handle.key) {
if (event.method === "values" && isRecord(data)) handle.latestValues = data;
else if (event.method === "lifecycle" && isRecord(data)) {
const status = data.event;
if (status === "completed" || status === "interrupted") finishHandle(handle, { type: "resolve" });
else if (status === "failed") finishHandle(handle, {
type: "reject",
error: /* @__PURE__ */ new Error(`Subagent ${handle.name} failed`)
});
}
}
}
return true;
},
finalize() {
for (const handle of handles.values()) finishHandle(handle, { type: "resolve" });
subagentsLog.close();
},
fail(err) {
for (const handle of handles.values()) finishHandle(handle, {
type: "reject",
error: err
});
subagentsLog.fail(err);
}
};
};
}
//#endregion
exports.createSubagentTransformer = createSubagentTransformer;
//# sourceMappingURL=subagent.cjs.map