@langchain/langgraph
Version:
213 lines (212 loc) • 5.93 kB
JavaScript
//#region src/stream/convert.ts
/**
* The set of stream modes requested by
* `streamEvents(..., { version: "v3" })` — every mode the protocol maps
* to a channel.
*
* The verbose `"debug"` mode is intentionally excluded: it was a thin
* re-wrap of `checkpoints` + `tasks` carrying no new information.
*
* The `"checkpoints"` mode is likewise excluded from the stream-mode
* request because the protocol's `checkpoints` channel carries only a
* lightweight envelope (`id`, `parent_id`, `step`, `source`) emitted as a
* separate ``[namespace, "checkpoints", envelope]`` chunk before each paired
* `values` chunk — not the full-state shape from Pregel's `checkpoints`
* stream mode when subscribed via `debug`.
*/
const STREAM_EVENTS_V3_MODES = [
"values",
"updates",
"messages",
"tools",
"custom",
"tasks"
];
/**
* True when `payload` is a lightweight checkpoint envelope (not a full-state
* Pregel `checkpoints` debug payload).
*/
function isCheckpointEnvelope(payload) {
if (payload == null || typeof payload !== "object") return false;
const p = payload;
return typeof p.id === "string" && ("source" in p || typeof p.step === "number") && !("values" in p) && !("config" in p);
}
function unwrapMessagesPayload(payload) {
if (!Array.isArray(payload) || payload.length !== 2) return { data: payload };
const [data, metadata] = payload;
if (metadata == null || typeof metadata !== "object") return { data: payload };
const record = metadata;
const node = typeof record.langgraph_node === "string" ? record.langgraph_node : void 0;
const runId = typeof record.run_id === "string" ? record.run_id : void 0;
return {
data: runId != null && data != null && typeof data === "object" ? {
...data,
run_id: runId
} : data,
node
};
}
function convertToProtocolEvent({ namespace: ns, mode, payload, seq }) {
const timestamp = Date.now();
const base = { type: "event" };
switch (mode) {
case "messages": {
const { data, node } = unwrapMessagesPayload(payload);
return [{
...base,
seq,
method: "messages",
params: {
namespace: ns,
timestamp,
...node ? { node } : {},
data
}
}];
}
case "tools": return [{
...base,
seq,
method: "tools",
params: {
namespace: ns,
timestamp,
data: convertToolsPayload(payload)
}
}];
case "checkpoints":
if (!isCheckpointEnvelope(payload)) return [];
return [{
...base,
seq,
method: "checkpoints",
params: {
namespace: ns,
timestamp,
data: payload
}
}];
case "values": return [{
...base,
seq,
method: "values",
params: {
namespace: ns,
timestamp,
data: payload
}
}];
case "updates": {
const data = convertUpdatesPayload(payload);
return [{
...base,
seq,
method: "updates",
params: {
namespace: ns,
timestamp,
...typeof data.node === "string" ? { node: data.node } : {},
data
}
}];
}
case "custom": {
const data = typeof payload === "object" && payload !== null && !Array.isArray(payload) && "name" in payload ? payload : { payload };
return [{
...base,
seq,
method: "custom",
params: {
namespace: ns,
timestamp,
data
}
}];
}
case "tasks": return [{
...base,
seq,
method: "tasks",
params: {
namespace: ns,
timestamp,
data: payload
}
}];
default: return [];
}
}
/**
* Normalises a raw tools-mode payload into a typed {@link ToolsEventData}
* discriminated union, mapping internal lifecycle events (`on_tool_start`,
* `on_tool_end`, etc.) to their protocol counterparts.
*
* @param payload - The raw payload from a `"tools"` stream chunk.
* @returns A {@link ToolsEventData} object with the appropriate `event`
* discriminant and associated fields.
*/
function convertToolsPayload(payload) {
if (typeof payload !== "object" || payload === null) return {
event: "tool-error",
tool_call_id: "",
message: "Unexpected tools payload shape"
};
const p = payload;
const tool_call_id = String(p.toolCallId ?? "");
switch (p.event) {
case "on_tool_start": return {
event: "tool-started",
tool_call_id,
tool_name: String(p.name ?? "unknown"),
input: p.input
};
case "on_tool_event": return {
event: "tool-output-delta",
tool_call_id,
delta: typeof p.data === "string" ? p.data : JSON.stringify(p.data ?? "")
};
case "on_tool_end": return {
event: "tool-finished",
tool_call_id,
output: p.output
};
case "on_tool_error": {
const err = p.error;
return {
event: "tool-error",
tool_call_id,
message: typeof err === "object" && err !== null && "message" in err && typeof err.message === "string" ? err.message : String(err ?? "unknown error")
};
}
default: return {
event: "tool-error",
tool_call_id: "",
message: `Unknown tool event: ${String(p.event)}`
};
}
}
/**
* Extracts the first `{node: delta}` entry from an updates-mode payload and
* reshapes it into an {@link UpdatesEventData} with explicit `node` and
* `values` fields. Non-object payloads are coerced to `{ values: {} }`.
*
* @param payload - The raw payload from an `"updates"` stream chunk,
* expected to be a `Record<string, unknown>` keyed by node name.
* @returns An {@link UpdatesEventData} containing the extracted node name
* and its associated delta values.
*/
function convertUpdatesPayload(payload) {
if (typeof payload !== "object" || payload === null) return { values: {} };
const entries = Object.entries(payload);
if (entries.length === 0) return { values: {} };
const [node, values] = entries[0];
return {
node,
values: typeof values === "object" && values !== null ? values : { value: values }
};
}
//#endregion
exports.STREAM_EVENTS_V3_MODES = STREAM_EVENTS_V3_MODES;
exports.convertToProtocolEvent = convertToProtocolEvent;
exports.isCheckpointEnvelope = isCheckpointEnvelope;
//# sourceMappingURL=convert.cjs.map