@@ -6,20 +6,19 @@ import type { ChunkParser } from "../types"
66/**
77 * Parses SSE chunks from LangGraph agents (stream_mode="messages").
88 *
9- * Each SSE line is a model_dump() of either AIMessageChunk or ToolMessage .
9+ * Uses LangChain's normalized message fields — works with any ChatModel backend .
1010 *
11- * AIMessageChunk shapes:
12- * - text: content: [{type:"text", text:"...", index:N}]
13- * - tool start: content: [{type:"tool_use", id, name, input:{}, index:N}] + tool_calls[]
14- * - tool delta: content: [{type:"tool_use", partial_json:"...", index:N}] + tool_call_chunks[]
15- * - stop: response_metadata: {stop_reason:"end_turn"|"tool_use"}
16- * - last: chunk_position: "last"
17- *
18- * ToolMessage shape:
19- * - type: "tool", content: "result", name: "tool_name", tool_call_id: "id"
11+ * Stream format:
12+ * AIMessageChunk:
13+ * - text: content (string)
14+ * - tool start: tool_call_chunks[{id, name, args}] — first chunk has id + name
15+ * - tool delta: tool_call_chunks[{args}] — subsequent chunks have args only
16+ * - stop: response_metadata.stop_reason
17+ * ToolMessage:
18+ * - type: "tool", content: string, tool_call_id: string
2019 */
2120
22- // Track current tool_use_id across chunks ( deltas don't carry the id)
21+ // Track current tool_use_id — streaming deltas may omit the id
2322let currentToolUseId = ""
2423
2524export const parseLanggraphChunk : ChunkParser = ( line , callback ) => {
@@ -43,39 +42,29 @@ export const parseLanggraphChunk: ChunkParser = (line, callback) => {
4342
4443 // AIMessageChunk
4544 if ( json . type === "AIMessageChunk" ) {
46- // Content as plain string (when model responds without tool use)
45+ // Text token — content can be a string or array of text blocks
4746 if ( typeof json . content === "string" && json . content ) {
4847 callback ( { type : "text" , content : json . content } )
49- }
50-
51- // Content as array of blocks (when model uses tools)
52- if ( Array . isArray ( json . content ) ) {
48+ } else if ( Array . isArray ( json . content ) ) {
5349 for ( const block of json . content ) {
54- // Text token
5550 if ( block . type === "text" && block . text ) {
5651 callback ( { type : "text" , content : block . text } )
5752 }
53+ }
54+ }
5855
59- // Tool use start — has id and name
60- if ( block . type === "tool_use" && block . id && block . name ) {
61- currentToolUseId = block . id
62- callback ( {
63- type : "tool_use_start" ,
64- toolUseId : block . id ,
65- name : block . name ,
66- } )
56+ // Tool calls — streamed via tool_call_chunks (LangChain's standard streaming field)
57+ if ( Array . isArray ( json . tool_call_chunks ) ) {
58+ for ( const chunk of json . tool_call_chunks ) {
59+ if ( chunk . id && chunk . name ) {
60+ currentToolUseId = chunk . id
61+ callback ( { type : "tool_use_start" , toolUseId : chunk . id , name : chunk . name } )
6762 }
68-
69- // Tool input streaming — has partial_json
70- if (
71- block . type === "tool_use" &&
72- typeof block . partial_json === "string" &&
73- block . partial_json
74- ) {
63+ if ( typeof chunk . args === "string" && chunk . args ) {
7564 callback ( {
7665 type : "tool_use_delta" ,
77- toolUseId : currentToolUseId ,
78- input : block . partial_json ,
66+ toolUseId : chunk . id || currentToolUseId ,
67+ input : chunk . args ,
7968 } )
8069 }
8170 }
0 commit comments