feat(api,sdk): add typing and support for "checkpoints" and "tasks" stream mode (#1416)

This commit is contained in:
David Duong
2025-07-17 15:05:21 +02:00
committed by GitHub
parent a12c1fb617
commit ee1defac05
8 changed files with 291 additions and 6 deletions
+2 -2
View File
@@ -2,14 +2,14 @@
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [
"fixed": [
[
"@langchain/langgraph-api",
"@langchain/langgraph-cli",
"@langchain/langgraph-ui"
]
],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
+5
View File
@@ -0,0 +1,5 @@
---
"@langchain/langgraph-api": patch
---
feat(api): pass through "tasks" and "checkpoints" stream mode
-2
View File
@@ -1,7 +1,5 @@
---
"@langchain/langgraph-cli": patch
"@langchain/langgraph-api": patch
"@langchain/langgraph-ui": patch
---
feat(cli): add sysinfo command to obtain actual version
+5
View File
@@ -0,0 +1,5 @@
---
"@langchain/langgraph-sdk": patch
---
feat(sdk): add typing for "tasks" and "checkpoints" stream mode
+4
View File
@@ -212,6 +212,8 @@ export const RunCreate = z
"messages-tuple",
"updates",
"events",
"tasks",
"checkpoints",
"debug",
"custom",
])
@@ -222,6 +224,8 @@ export const RunCreate = z
"messages-tuple",
"updates",
"events",
"tasks",
"checkpoints",
"debug",
"custom",
]),
+173
View File
@@ -2704,3 +2704,176 @@ it("resumable streams", { timeout: 10_000 }, async () => {
expect(join).toEqual(source);
});
it("tasks / checkpoints stream mode", async () => {
const assistant = await client.assistants.create({ graphId: "agent" });
const thread = await client.threads.create();
const stream = await gatherIterator(
client.runs.stream(thread.thread_id, assistant.assistant_id, {
input: { messages: [{ role: "human", content: "input" }] },
streamMode: ["tasks", "checkpoints"],
config: globalConfig,
})
);
expect(stream).toMatchObject([
{
event: "metadata",
data: { run_id: expect.any(String) },
},
{
event: "checkpoints",
data: {
values: { messages: [] },
metadata: { source: "input", step: -1 },
next: ["__start__"],
},
},
{
event: "checkpoints",
data: {
values: { messages: [{ content: "input", type: "human" }] },
metadata: { source: "loop", step: 0 },
next: ["agent"],
},
},
{
event: "tasks",
data: {
name: "agent",
input: { messages: [{ content: "input", type: "human" }] },
triggers: ["branch:to:agent"],
interrupts: [],
},
},
{
event: "tasks",
data: {
name: "agent",
result: expect.arrayContaining([
[
"messages",
[expect.objectContaining({ content: "begin", type: "ai" })],
],
]),
interrupts: [],
},
},
{
event: "checkpoints",
data: {
values: {
messages: [
{ content: "input", type: "human" },
{ content: "begin", type: "ai" },
],
},
metadata: { source: "loop", step: 1 },
next: ["tool"],
},
},
{
event: "tasks",
data: {
name: "tool",
input: {
messages: [
{ content: "input", type: "human" },
{ content: "begin", type: "ai" },
],
},
triggers: ["branch:to:tool"],
interrupts: [],
},
},
{
event: "tasks",
data: {
name: "tool",
result: expect.arrayContaining([
[
"messages",
[
expect.objectContaining({
content: "tool_call__begin",
tool_call_id: "tool_call_id",
type: "tool",
}),
],
],
]),
interrupts: [],
},
},
{
event: "checkpoints",
data: {
values: {
messages: [
{ content: "input", type: "human" },
{ content: "begin", type: "ai" },
{
content: "tool_call__begin",
tool_call_id: "tool_call_id",
type: "tool",
},
],
},
metadata: { source: "loop", step: 2 },
next: ["agent"],
},
},
{
event: "tasks",
data: {
name: "agent",
input: {
messages: [
{ content: "input", type: "human" },
{ content: "begin", type: "ai" },
{
content: "tool_call__begin",
tool_call_id: "tool_call_id",
type: "tool",
},
],
},
triggers: ["branch:to:agent"],
interrupts: [],
},
},
{
event: "tasks",
data: {
name: "agent",
result: expect.arrayContaining([
[
"messages",
[expect.objectContaining({ content: "end", type: "ai" })],
],
]),
interrupts: [],
},
},
{
event: "checkpoints",
data: {
values: {
messages: [
{ content: "input", type: "human" },
{ content: "begin", type: "ai" },
{
content: "tool_call__begin",
tool_call_id: "tool_call_id",
type: "tool",
},
{ content: "end", type: "ai" },
],
},
metadata: { source: "loop", step: 3 },
next: [],
},
},
]);
});
+34 -1
View File
@@ -33,6 +33,7 @@ import type {
ThreadState,
} from "../schema.js";
import type {
CheckpointsStreamEvent,
CustomStreamEvent,
DebugStreamEvent,
ErrorStreamEvent,
@@ -42,6 +43,7 @@ import type {
MessagesTupleStreamEvent,
MetadataStreamEvent,
StreamMode,
TasksStreamEvent,
UpdatesStreamEvent,
ValuesStreamEvent,
} from "../types.stream.js";
@@ -545,6 +547,18 @@ export interface UseStreamOptions<
*/
onDebugEvent?: (data: DebugStreamEvent["data"]) => void;
/**
* Callback that is called when a checkpoints event is received.
*/
onCheckpointEvent?: (data: CheckpointsStreamEvent<StateType>["data"]) => void;
/**
* Callback that is called when a tasks event is received.
*/
onTaskEvent?: (
data: TasksStreamEvent<StateType, GetUpdateType<Bag, StateType>>["data"]
) => void;
/**
* Callback that is called when the stream is stopped by the user.
* Provides a mutate function to update the stream state immediately
@@ -814,6 +828,8 @@ export function useStream<
| MessagesTupleStreamEvent
| EventsStreamEvent
| MetadataStreamEvent
| CheckpointsStreamEvent<StateType>
| TasksStreamEvent<StateType, UpdateType>
| ErrorStreamEvent
| FeedbackStreamEvent;
@@ -864,7 +880,15 @@ export function useStream<
const abortRef = useRef<AbortController | null>(null);
const trackStreamModeRef = useRef<
Array<"values" | "updates" | "events" | "custom" | "messages-tuple">
Array<
| "values"
| "updates"
| "events"
| "custom"
| "messages-tuple"
| "checkpoints"
| "tasks"
>
>([]);
const trackStreamMode = useCallback(
@@ -883,18 +907,25 @@ export function useStream<
const hasLangChainListener = options.onLangChainEvent != null;
const hasDebugListener = options.onDebugEvent != null;
const hasCheckpointListener = options.onCheckpointEvent != null;
const hasTaskListener = options.onTaskEvent != null;
const callbackStreamMode = useMemo(() => {
const modes: Exclude<StreamMode, "messages">[] = [];
if (hasUpdateListener) modes.push("updates");
if (hasCustomListener) modes.push("custom");
if (hasLangChainListener) modes.push("events");
if (hasDebugListener) modes.push("debug");
if (hasCheckpointListener) modes.push("checkpoints");
if (hasTaskListener) modes.push("tasks");
return modes;
}, [
hasUpdateListener,
hasCustomListener,
hasLangChainListener,
hasDebugListener,
hasCheckpointListener,
hasTaskListener,
]);
const clearCallbackRef = useRef<() => void>(null!);
@@ -1040,6 +1071,8 @@ export function useStream<
if (event === "metadata") options.onMetadataEvent?.(data);
if (event === "events") options.onLangChainEvent?.(data);
if (event === "debug") options.onDebugEvent?.(data);
if (event === "checkpoints") options.onCheckpointEvent?.(data);
if (event === "tasks") options.onTaskEvent?.(data);
if (event === "values") {
if ("__interrupt__" in data) {
+68 -1
View File
@@ -1,6 +1,7 @@
import type { Message } from "./types.messages.js";
import type { Interrupt, Metadata, Config, ThreadTask } from "./schema.js";
/**
import type { SubgraphCheckpointsStreamEvent } from "./types.stream.subgraph.js";
* Stream modes
* - "values": Stream only the state values.
* - "messages": Stream complete messages.
@@ -16,6 +17,8 @@ export type StreamMode =
| "updates"
| "events"
| "debug"
| "tasks"
| "checkpoints"
| "custom"
| "messages-tuple";
@@ -120,6 +123,66 @@ type MessagesPartialStreamEvent = {
data: Message[];
};
type TasksStreamCreateEvent<StateType> = {
id?: string;
event: "tasks";
data: {
id: string;
name: string;
interrupts: Interrupt[];
input: StateType;
triggers: string[];
};
};
type TasksStreamResultEvent<UpdateType> = {
id?: string;
event: "tasks";
data: {
id: string;
name: string;
interrupts: Interrupt[];
result: [string, UpdateType][];
};
};
type TasksStreamErrorEvent = {
id?: string;
event: "tasks";
data: {
id: string;
name: string;
interrupts: Interrupt[];
error: string;
};
};
export type TasksStreamEvent<StateType, UpdateType> =
| TasksStreamCreateEvent<StateType>
| TasksStreamResultEvent<UpdateType>
| TasksStreamErrorEvent;
type SubgraphTasksStreamEvent<StateType, UpdateType> =
| AsSubgraph<TasksStreamCreateEvent<StateType>>
| AsSubgraph<TasksStreamResultEvent<UpdateType>>
| AsSubgraph<TasksStreamErrorEvent>;
export type CheckpointsStreamEvent<StateType> = {
id?: string;
event: "checkpoints";
data: {
values: StateType;
next: string[];
config: Config;
metadata: Metadata;
tasks: ThreadTask[];
};
};
type SubgraphCheckpointsStreamEvent<StateType> = AsSubgraph<
CheckpointsStreamEvent<StateType>
>;
/**
* Message stream event specific to LangGraph Server.
* @deprecated Use `streamMode: "messages-tuple"` instead.
@@ -194,6 +257,8 @@ type GetStreamModeMap<
debug: DebugStreamEvent;
messages: MessagesStreamEvent;
"messages-tuple": MessagesTupleStreamEvent;
tasks: TasksStreamEvent<TStateType, TUpdateType>;
checkpoints: CheckpointsStreamEvent<TStateType>;
events: EventsStreamEvent;
}[TStreamMode extends StreamMode[] ? TStreamMode[number] : TStreamMode]
| ErrorStreamEvent
@@ -214,6 +279,8 @@ type GetSubgraphsStreamModeMap<
messages: SubgraphMessagesStreamEvent;
"messages-tuple": SubgraphMessagesTupleStreamEvent;
events: SubgraphEventsStreamEvent;
tasks: SubgraphTasksStreamEvent<TStateType, TUpdateType>;
checkpoints: SubgraphCheckpointsStreamEvent<TStateType>;
}[TStreamMode extends StreamMode[] ? TStreamMode[number] : TStreamMode]
| SubgraphErrorStreamEvent
| MetadataStreamEvent