@ai-sdk/langchain 3.0.0-canary.172 → 3.0.0-canary.173

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @ai-sdk/langchain
2
2
 
3
+ ## 3.0.0-canary.173
4
+
5
+ ### Patch Changes
6
+
7
+ - c1afaed: fix(langchain): prevent polluting global object.prototype
8
+ - ai@7.0.0-canary.173
9
+
3
10
  ## 3.0.0-canary.172
4
11
 
5
12
  ### Patch Changes
package/dist/index.js CHANGED
@@ -558,8 +558,24 @@ function formatToolError(error) {
558
558
  return String(error);
559
559
  }
560
560
  }
561
+ function getOrCreateMessageSeen(messageSeen, msgId) {
562
+ let seen = messageSeen.get(msgId);
563
+ if (!seen) {
564
+ seen = {};
565
+ messageSeen.set(msgId, seen);
566
+ }
567
+ return seen;
568
+ }
569
+ function getOrCreateToolCallInfoByIndex(toolCallInfoByIndex, msgId) {
570
+ let toolCallInfo = toolCallInfoByIndex.get(msgId);
571
+ if (!toolCallInfo) {
572
+ toolCallInfo = /* @__PURE__ */ new Map();
573
+ toolCallInfoByIndex.set(msgId, toolCallInfo);
574
+ }
575
+ return toolCallInfo;
576
+ }
561
577
  function processLangGraphEvent(event, state, controller) {
562
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
578
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
563
579
  const {
564
580
  messageSeen,
565
581
  messageConcat,
@@ -601,16 +617,16 @@ function processLangGraphEvent(event, state, controller) {
601
617
  const langgraphStep = typeof (metadata == null ? void 0 : metadata.langgraph_step) === "number" ? metadata.langgraph_step : null;
602
618
  if (langgraphStep !== null && langgraphStep !== state.currentStep) {
603
619
  if (state.currentStep !== null) {
604
- for (const [id, seen] of Object.entries(messageSeen)) {
620
+ for (const [id, seen] of messageSeen) {
605
621
  if (seen.text) {
606
622
  controller.enqueue({ type: "text-end", id });
607
623
  }
608
624
  if (seen.reasoning) {
609
625
  controller.enqueue({ type: "reasoning-end", id });
610
626
  }
611
- delete messageSeen[id];
612
- delete messageConcat[id];
613
- delete messageReasoningIds[id];
627
+ messageSeen.delete(id);
628
+ messageConcat.delete(id);
629
+ messageReasoningIds.delete(id);
614
630
  }
615
631
  controller.enqueue({ type: "finish-step" });
616
632
  }
@@ -618,16 +634,18 @@ function processLangGraphEvent(event, state, controller) {
618
634
  state.currentStep = langgraphStep;
619
635
  }
620
636
  if (AIMessageChunk.isInstance(msg)) {
621
- if (messageConcat[msgId]) {
622
- messageConcat[msgId] = messageConcat[msgId].concat(
623
- msg
637
+ const existingMessage = messageConcat.get(msgId);
638
+ if (existingMessage) {
639
+ messageConcat.set(
640
+ msgId,
641
+ existingMessage.concat(msg)
624
642
  );
625
643
  } else {
626
- messageConcat[msgId] = msg;
644
+ messageConcat.set(msgId, msg);
627
645
  }
628
646
  }
629
647
  if (isAIMessageChunk(msg)) {
630
- const concatChunk = messageConcat[msgId];
648
+ const concatChunk = messageConcat.get(msgId);
631
649
  const msgObj = msg;
632
650
  const dataSource = msgObj.type === "constructor" && msgObj.kwargs && typeof msgObj.kwargs === "object" ? msgObj.kwargs : msgObj;
633
651
  const additionalKwargs = dataSource.additional_kwargs;
@@ -648,21 +666,25 @@ function processLangGraphEvent(event, state, controller) {
648
666
  for (const toolCallChunk of toolCallChunks) {
649
667
  const toolCallIndex = (_a = toolCallChunk.index) != null ? _a : 0;
650
668
  if (toolCallChunk.id) {
651
- (_b = toolCallInfoByIndex[msgId]) != null ? _b : toolCallInfoByIndex[msgId] = {};
652
- toolCallInfoByIndex[msgId][toolCallIndex] = {
653
- id: toolCallChunk.id,
654
- name: toolCallChunk.name || ((_d = (_c = concatChunk == null ? void 0 : concatChunk.tool_call_chunks) == null ? void 0 : _c[toolCallIndex]) == null ? void 0 : _d.name) || "unknown"
655
- };
669
+ getOrCreateToolCallInfoByIndex(toolCallInfoByIndex, msgId).set(
670
+ toolCallIndex,
671
+ {
672
+ id: toolCallChunk.id,
673
+ name: toolCallChunk.name || ((_c = (_b = concatChunk == null ? void 0 : concatChunk.tool_call_chunks) == null ? void 0 : _b[toolCallIndex]) == null ? void 0 : _c.name) || "unknown"
674
+ }
675
+ );
656
676
  }
657
- const toolCallId = toolCallChunk.id || ((_f = (_e = toolCallInfoByIndex[msgId]) == null ? void 0 : _e[toolCallIndex]) == null ? void 0 : _f.id) || ((_h = (_g = concatChunk == null ? void 0 : concatChunk.tool_call_chunks) == null ? void 0 : _g[toolCallIndex]) == null ? void 0 : _h.id);
677
+ const storedToolCallInfo = (_d = toolCallInfoByIndex.get(msgId)) == null ? void 0 : _d.get(toolCallIndex);
678
+ const toolCallId = toolCallChunk.id || (storedToolCallInfo == null ? void 0 : storedToolCallInfo.id) || ((_f = (_e = concatChunk == null ? void 0 : concatChunk.tool_call_chunks) == null ? void 0 : _e[toolCallIndex]) == null ? void 0 : _f.id);
658
679
  if (!toolCallId) {
659
680
  continue;
660
681
  }
661
- const toolName = toolCallChunk.name || ((_j = (_i = toolCallInfoByIndex[msgId]) == null ? void 0 : _i[toolCallIndex]) == null ? void 0 : _j.name) || ((_l = (_k = concatChunk == null ? void 0 : concatChunk.tool_call_chunks) == null ? void 0 : _k[toolCallIndex]) == null ? void 0 : _l.name) || "unknown";
662
- if (!((_n = (_m = messageSeen[msgId]) == null ? void 0 : _m.tool) == null ? void 0 : _n[toolCallId])) {
663
- (_o = messageSeen[msgId]) != null ? _o : messageSeen[msgId] = {};
664
- (_q = (_p = messageSeen[msgId]).tool) != null ? _q : _p.tool = {};
665
- messageSeen[msgId].tool[toolCallId] = true;
682
+ const toolName = toolCallChunk.name || (storedToolCallInfo == null ? void 0 : storedToolCallInfo.name) || ((_h = (_g = concatChunk == null ? void 0 : concatChunk.tool_call_chunks) == null ? void 0 : _g[toolCallIndex]) == null ? void 0 : _h.name) || "unknown";
683
+ const seen = messageSeen.get(msgId);
684
+ if (!((_i = seen == null ? void 0 : seen.tool) == null ? void 0 : _i.has(toolCallId))) {
685
+ const updatedSeen = getOrCreateMessageSeen(messageSeen, msgId);
686
+ (_j = updatedSeen.tool) != null ? _j : updatedSeen.tool = /* @__PURE__ */ new Set();
687
+ updatedSeen.tool.add(toolCallId);
666
688
  if (!emittedToolCalls.has(toolCallId)) {
667
689
  emittedToolCalls.add(toolCallId);
668
690
  controller.enqueue({
@@ -685,18 +707,18 @@ function processLangGraphEvent(event, state, controller) {
685
707
  }
686
708
  const chunkReasoningId = extractReasoningId(msg);
687
709
  if (chunkReasoningId) {
688
- if (!messageReasoningIds[msgId]) {
689
- messageReasoningIds[msgId] = chunkReasoningId;
710
+ if (!messageReasoningIds.has(msgId)) {
711
+ messageReasoningIds.set(msgId, chunkReasoningId);
690
712
  }
691
713
  emittedReasoningIds.add(chunkReasoningId);
692
714
  }
693
715
  const reasoning = extractReasoningFromContentBlocks(msg);
694
716
  if (reasoning) {
695
- const reasoningId = (_s = (_r = messageReasoningIds[msgId]) != null ? _r : chunkReasoningId) != null ? _s : msgId;
696
- if (!((_t = messageSeen[msgId]) == null ? void 0 : _t.reasoning)) {
717
+ const reasoningId = (_l = (_k = messageReasoningIds.get(msgId)) != null ? _k : chunkReasoningId) != null ? _l : msgId;
718
+ const seen = messageSeen.get(msgId);
719
+ if (!(seen == null ? void 0 : seen.reasoning)) {
697
720
  controller.enqueue({ type: "reasoning-start", id: msgId });
698
- (_u = messageSeen[msgId]) != null ? _u : messageSeen[msgId] = {};
699
- messageSeen[msgId].reasoning = true;
721
+ getOrCreateMessageSeen(messageSeen, msgId).reasoning = true;
700
722
  }
701
723
  controller.enqueue({
702
724
  type: "reasoning-delta",
@@ -707,10 +729,10 @@ function processLangGraphEvent(event, state, controller) {
707
729
  }
708
730
  const text = getMessageText(msg);
709
731
  if (text) {
710
- if (!((_v = messageSeen[msgId]) == null ? void 0 : _v.text)) {
732
+ const seen = messageSeen.get(msgId);
733
+ if (!(seen == null ? void 0 : seen.text)) {
711
734
  controller.enqueue({ type: "text-start", id: msgId });
712
- (_w = messageSeen[msgId]) != null ? _w : messageSeen[msgId] = {};
713
- messageSeen[msgId].text = true;
735
+ getOrCreateMessageSeen(messageSeen, msgId).text = true;
714
736
  }
715
737
  controller.enqueue({
716
738
  type: "text-delta",
@@ -818,15 +840,15 @@ function processLangGraphEvent(event, state, controller) {
818
840
  return;
819
841
  }
820
842
  case "values": {
821
- for (const [id, seen] of Object.entries(messageSeen)) {
843
+ for (const [id, seen] of messageSeen) {
822
844
  if (seen.text) controller.enqueue({ type: "text-end", id });
823
845
  if (seen.tool) {
824
- for (const [toolCallId, toolCallSeen] of Object.entries(seen.tool)) {
825
- const concatMsg = messageConcat[id];
826
- const toolCall = (_x = concatMsg == null ? void 0 : concatMsg.tool_calls) == null ? void 0 : _x.find(
846
+ for (const toolCallId of seen.tool) {
847
+ const concatMsg = messageConcat.get(id);
848
+ const toolCall = (_m = concatMsg == null ? void 0 : concatMsg.tool_calls) == null ? void 0 : _m.find(
827
849
  (call) => call.id === toolCallId
828
850
  );
829
- if (toolCallSeen && toolCall) {
851
+ if (toolCall) {
830
852
  emittedToolCalls.add(toolCallId);
831
853
  const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;
832
854
  emittedToolCallsByKey.set(toolCallKey, toolCallId);
@@ -846,9 +868,9 @@ function processLangGraphEvent(event, state, controller) {
846
868
  if (seen.reasoning) {
847
869
  controller.enqueue({ type: "reasoning-end", id });
848
870
  }
849
- delete messageSeen[id];
850
- delete messageConcat[id];
851
- delete messageReasoningIds[id];
871
+ messageSeen.delete(id);
872
+ messageConcat.delete(id);
873
+ messageReasoningIds.delete(id);
852
874
  }
853
875
  if (data != null && typeof data === "object" && "messages" in data) {
854
876
  const messages = data.messages;
@@ -932,7 +954,7 @@ function processLangGraphEvent(event, state, controller) {
932
954
  }
933
955
  }
934
956
  const reasoningId = extractReasoningId(msg);
935
- const wasStreamedThisRequest = !!messageSeen[msgId];
957
+ const wasStreamedThisRequest = messageSeen.has(msgId);
936
958
  const hasToolCalls = toolCalls && toolCalls.length > 0;
937
959
  const shouldEmitReasoning = reasoningId && !emittedReasoningIds.has(reasoningId) && (wasStreamedThisRequest || !hasToolCalls);
938
960
  if (shouldEmitReasoning) {
@@ -1183,14 +1205,14 @@ function toUIMessageStream(stream, callbacks) {
1183
1205
  emittedSourceIds: /* @__PURE__ */ new Set()
1184
1206
  };
1185
1207
  const langGraphState = {
1186
- messageSeen: {},
1187
- messageConcat: {},
1208
+ messageSeen: /* @__PURE__ */ new Map(),
1209
+ messageConcat: /* @__PURE__ */ new Map(),
1188
1210
  emittedToolCalls: /* @__PURE__ */ new Set(),
1189
1211
  emittedToolInputs: /* @__PURE__ */ new Set(),
1190
1212
  emittedImages: /* @__PURE__ */ new Set(),
1191
1213
  emittedReasoningIds: /* @__PURE__ */ new Set(),
1192
- messageReasoningIds: {},
1193
- toolCallInfoByIndex: {},
1214
+ messageReasoningIds: /* @__PURE__ */ new Map(),
1215
+ toolCallInfoByIndex: /* @__PURE__ */ new Map(),
1194
1216
  currentStep: null,
1195
1217
  emittedToolCallsByKey: /* @__PURE__ */ new Map(),
1196
1218
  emittedSourceIds: /* @__PURE__ */ new Set()
@@ -1286,7 +1308,7 @@ function toUIMessageStream(stream, callbacks) {
1286
1308
  }
1287
1309
  controller.enqueue({ type: "finish" });
1288
1310
  } else if (streamType === "langgraph") {
1289
- for (const [id, seen] of Object.entries(langGraphState.messageSeen)) {
1311
+ for (const [id, seen] of langGraphState.messageSeen) {
1290
1312
  if (seen.text) {
1291
1313
  controller.enqueue({ type: "text-end", id });
1292
1314
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapter.ts","../src/utils.ts","../src/transport.ts"],"sourcesContent":["import {\n SystemMessage,\n type BaseMessage,\n type AIMessageChunk,\n} from '@langchain/core/messages';\nimport {\n convertToModelMessages,\n type UIMessage,\n type UIMessageChunk,\n type ModelMessage,\n} from 'ai';\nimport {\n convertToolResultPart,\n convertAssistantContent,\n convertUserContent,\n processModelChunk,\n processLangGraphEvent,\n parseLangGraphEvent,\n isToolResultPart,\n extractReasoningFromContentBlocks,\n extractCitationsFromContentBlocks,\n emitSourceChunks,\n} from './utils';\nimport type { LangGraphEventState } from './types';\nimport type { StreamCallbacks } from './stream-callbacks';\n\n/**\n * Converts AI SDK UIMessages to LangChain BaseMessage objects.\n *\n * This function transforms the AI SDK's message format into LangChain's message\n * format, enabling seamless integration between the two frameworks.\n *\n * @param messages - Array of AI SDK UIMessage objects to convert.\n * @returns Promise resolving to an array of LangChain BaseMessage objects.\n *\n * @example\n * ```ts\n * import { toBaseMessages } from '@ai-sdk/langchain';\n *\n * const langchainMessages = await toBaseMessages(uiMessages);\n *\n * // Use with LangChain\n * const response = await model.invoke(langchainMessages);\n * ```\n */\nexport async function toBaseMessages(\n messages: UIMessage[],\n): Promise<BaseMessage[]> {\n const modelMessages = await convertToModelMessages(messages);\n return convertModelMessages(modelMessages);\n}\n\n/**\n * Converts ModelMessages to LangChain BaseMessage objects.\n *\n * @param modelMessages - Array of ModelMessage objects from convertToModelMessages.\n * @returns Array of LangChain BaseMessage objects.\n */\nexport function convertModelMessages(\n modelMessages: ModelMessage[],\n): BaseMessage[] {\n const result: BaseMessage[] = [];\n\n for (const message of modelMessages) {\n switch (message.role) {\n case 'tool': {\n // Tool messages contain an array of tool results\n for (const item of message.content) {\n if (isToolResultPart(item)) {\n result.push(convertToolResultPart(item));\n }\n }\n break;\n }\n\n case 'assistant': {\n result.push(convertAssistantContent(message.content));\n break;\n }\n\n case 'system': {\n result.push(new SystemMessage({ content: message.content }));\n break;\n }\n\n case 'user': {\n result.push(convertUserContent(message.content));\n break;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Type guard to check if a value is a streamEvents event object.\n * streamEvents produces objects with `event` and `data` properties.\n *\n * @param value - The value to check.\n * @returns True if the value is a streamEvents event object.\n */\nfunction isStreamEventsEvent(\n value: unknown,\n): value is { event: string; data: Record<string, unknown> } {\n if (value == null || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n // Check for event property being a string\n if (!('event' in obj) || typeof obj.event !== 'string') return false;\n // Check for data property being an object (but allow null/undefined)\n if (!('data' in obj)) return false;\n // data can be null in some events, treat as empty object\n return obj.data === null || typeof obj.data === 'object';\n}\n\n/**\n * Processes a streamEvents event and emits UI message chunks.\n *\n * @param event - The streamEvents event to process.\n * @param state - The state for tracking stream progress.\n * @param controller - The controller to emit UI message chunks.\n */\nfunction processStreamEventsEvent(\n event: {\n event: string;\n data: Record<string, unknown> | null;\n run_id?: string;\n name?: string;\n },\n state: {\n started: boolean;\n messageId: string;\n reasoningStarted: boolean;\n textStarted: boolean;\n textMessageId: string | null;\n reasoningMessageId: string | null;\n emittedSourceIds: Set<string>;\n },\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n /**\n * Capture run_id from event level if available (streamEvents v2 format)\n */\n if (event.run_id && !state.started) {\n state.messageId = event.run_id;\n }\n\n /**\n * Skip events with null/undefined data\n */\n if (!event.data) return;\n\n switch (event.event) {\n case 'on_chat_model_start': {\n /**\n * End the previous model turn's reasoning stream before a new LLM invocation.\n * Without this, reasoning-delta chunks from the next turn could attach to the\n * wrong reasoning part in the UI message stream.\n */\n if (state.reasoningStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id:\n state.reasoningMessageId != null\n ? state.reasoningMessageId\n : state.messageId,\n });\n state.reasoningStarted = false;\n state.reasoningMessageId = null;\n }\n\n /**\n * End the previous model turn's text stream before a new LLM invocation.\n * The streamEvents adapter otherwise leaves `textStarted` true, so all later\n * text-delta chunks append to the first text part — tools still grow the parts\n * array, which breaks chronological order in the assistant message.\n */\n if (state.textStarted) {\n controller.enqueue({\n type: 'text-end',\n id:\n state.textMessageId != null ? state.textMessageId : state.messageId,\n });\n state.textStarted = false;\n state.textMessageId = null;\n }\n\n /**\n * Handle model start — capture message metadata if available.\n * `run_id` is on the event in streamEvents v2; fall back to `data` for compatibility.\n */\n const runId = event.run_id || (event.data.run_id as string | undefined);\n if (runId) {\n state.messageId = runId;\n }\n break;\n }\n\n case 'on_chat_model_stream': {\n /**\n * Handle streaming token chunks\n */\n const chunk = event.data.chunk;\n if (chunk && typeof chunk === 'object') {\n /**\n * Get message ID from chunk if available\n */\n const chunkId = (chunk as { id?: string }).id;\n if (chunkId) {\n state.messageId = chunkId;\n }\n\n /**\n * Handle reasoning content from contentBlocks\n */\n const reasoning = extractReasoningFromContentBlocks(chunk);\n if (reasoning) {\n if (!state.reasoningStarted) {\n // Track the ID used for reasoning-start to ensure reasoning-end uses the same ID\n state.reasoningMessageId = state.messageId;\n controller.enqueue({\n type: 'reasoning-start',\n id: state.messageId,\n });\n state.reasoningStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: state.reasoningMessageId ?? state.messageId,\n });\n }\n\n /**\n * Extract text content from chunk\n */\n const content = (chunk as { content?: unknown }).content;\n const text =\n typeof content === 'string'\n ? content\n : Array.isArray(content)\n ? content\n .filter(\n (c): c is { type: 'text'; text: string } =>\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n c.type === 'text',\n )\n .map(c => c.text)\n .join('')\n : '';\n\n if (text) {\n /**\n * If reasoning was streamed before text, close reasoning first\n */\n if (state.reasoningStarted && !state.textStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id: state.reasoningMessageId ?? state.messageId,\n });\n state.reasoningStarted = false;\n }\n\n if (!state.textStarted) {\n // Track the ID used for text-start to ensure text-end uses the same ID\n state.textMessageId = state.messageId;\n controller.enqueue({ type: 'text-start', id: state.messageId });\n state.textStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'text-delta',\n delta: text,\n id: state.textMessageId ?? state.messageId,\n });\n }\n\n const citations = extractCitationsFromContentBlocks(chunk);\n if (citations.length > 0) {\n emitSourceChunks(\n citations,\n state.messageId,\n state.emittedSourceIds,\n controller,\n );\n }\n }\n break;\n }\n\n case 'on_tool_start': {\n /**\n * Handle tool call start\n * run_id and name are at event level in v2, check data for backwards compatibility\n */\n const runId = event.run_id || (event.data.run_id as string | undefined);\n const name = event.name || (event.data.name as string | undefined);\n\n if (runId && name) {\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId: runId,\n toolName: name,\n dynamic: true,\n });\n }\n break;\n }\n\n case 'on_tool_end': {\n /**\n * Handle tool call end\n * run_id is at event level in v2, check data for backwards compatibility\n */\n const runId = event.run_id || (event.data.run_id as string | undefined);\n const output = event.data.output;\n\n if (runId) {\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId: runId,\n output,\n });\n }\n break;\n }\n }\n}\n\n/**\n * Checks if an error is an abort error.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof Error) {\n return (\n error.name === 'AbortError' ||\n (error instanceof DOMException && error.name === 'AbortError')\n );\n }\n return false;\n}\n\n/**\n * Converts a LangChain stream to an AI SDK UIMessageStream.\n *\n * This function automatically detects the stream type and handles:\n * - Direct model streams (AsyncIterable from `model.stream()`)\n * - LangGraph streams (ReadableStream with `streamMode: ['values', 'messages']`)\n * - streamEvents streams (from `agent.streamEvents()` or `model.streamEvents()`)\n *\n * @param stream - A stream from LangChain model.stream(), graph.stream(), or streamEvents().\n * @param callbacks - Optional callbacks for stream lifecycle events.\n * @returns A ReadableStream of UIMessageChunk objects.\n *\n * @example\n * ```ts\n * // With a direct model stream\n * const model = new ChatOpenAI({ model: 'gpt-4o-mini' });\n * const stream = await model.stream(messages);\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream(stream),\n * });\n *\n * // With a LangGraph stream\n * const graphStream = await graph.stream(\n * { messages },\n * { streamMode: ['values', 'messages'] }\n * );\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream(graphStream),\n * });\n *\n * // With streamEvents\n * const streamEvents = agent.streamEvents(\n * { messages },\n * { version: \"v2\" }\n * );\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream(streamEvents),\n * });\n *\n * // With callbacks for LangGraph state\n * const graphStream = await graph.stream(\n * { messages },\n * { streamMode: ['values', 'messages'] }\n * );\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream<MyStateType>(graphStream, {\n * onFinish: async (finalState) => {\n * if (finalState) {\n * await saveToDatabase(finalState);\n * }\n * },\n * onError: (error) => console.error('Stream failed:', error),\n * onAbort: () => console.log('Stream aborted'),\n * }),\n * });\n * ```\n */\nexport function toUIMessageStream<TState = unknown>(\n stream: AsyncIterable<AIMessageChunk> | ReadableStream,\n callbacks?: StreamCallbacks<TState>,\n): ReadableStream<UIMessageChunk> {\n /**\n * Track text chunks for onFinal callback\n */\n const textChunks: string[] = [];\n\n /** Last LangGraph values event data for onFinish callback */\n let lastValuesData: TState | undefined;\n\n /**\n * State for model stream handling\n */\n const modelState = {\n started: false,\n messageId: 'langchain-msg-1',\n reasoningStarted: false,\n textStarted: false,\n /** Track the ID used for text-start to ensure text-end uses the same ID */\n textMessageId: null as string | null,\n /** Track the ID used for reasoning-start to ensure reasoning-end uses the same ID */\n reasoningMessageId: null as string | null,\n emittedSourceIds: new Set<string>(),\n };\n\n /**\n * State for LangGraph stream handling\n */\n const langGraphState: LangGraphEventState = {\n messageSeen: {} as Record<\n string,\n { text?: boolean; reasoning?: boolean; tool?: Record<string, boolean> }\n >,\n messageConcat: {} as Record<string, AIMessageChunk>,\n emittedToolCalls: new Set<string>(),\n emittedToolInputs: new Set<string>(),\n emittedImages: new Set<string>(),\n emittedReasoningIds: new Set<string>(),\n messageReasoningIds: {} as Record<string, string>,\n toolCallInfoByIndex: {} as Record<\n string,\n Record<number, { id: string; name: string }>\n >,\n currentStep: null as number | null,\n emittedToolCallsByKey: new Map<string, string>(),\n emittedSourceIds: new Set<string>(),\n };\n\n /**\n * Track detected stream type: null = not yet detected\n */\n let streamType: 'model' | 'langgraph' | 'streamEvents' | null = null;\n\n /**\n * Get async iterator from the stream (works for both AsyncIterable and ReadableStream)\n */\n const getAsyncIterator = (): AsyncIterator<unknown> => {\n if (Symbol.asyncIterator in stream) {\n return (stream as AsyncIterable<unknown>)[Symbol.asyncIterator]();\n }\n /**\n * For ReadableStream without Symbol.asyncIterator\n */\n const reader = (stream as ReadableStream).getReader();\n return {\n async next() {\n const { done, value } = await reader.read();\n return { done, value };\n },\n };\n };\n\n const iterator = getAsyncIterator();\n\n /**\n * Create a wrapper around the controller to intercept text chunks for callbacks\n */\n const createCallbackController = (\n originalController: ReadableStreamDefaultController<UIMessageChunk>,\n ): ReadableStreamDefaultController<UIMessageChunk> => {\n return {\n get desiredSize() {\n return originalController.desiredSize;\n },\n close: () => originalController.close(),\n error: (e?: unknown) => originalController.error(e),\n enqueue: (chunk: UIMessageChunk) => {\n /**\n * Intercept text-delta chunks for callbacks\n */\n if (callbacks && chunk.type === 'text-delta' && chunk.delta) {\n textChunks.push(chunk.delta);\n callbacks.onToken?.(chunk.delta);\n callbacks.onText?.(chunk.delta);\n }\n originalController.enqueue(chunk);\n },\n };\n };\n\n return new ReadableStream<UIMessageChunk>({\n async start(controller) {\n await callbacks?.onStart?.();\n\n const wrappedController = createCallbackController(controller);\n controller.enqueue({ type: 'start' });\n\n try {\n while (true) {\n const { done, value } = await iterator.next();\n if (done) break;\n\n /**\n * Detect stream type on first value\n */\n if (streamType === null) {\n if (Array.isArray(value)) {\n streamType = 'langgraph';\n } else if (isStreamEventsEvent(value)) {\n streamType = 'streamEvents';\n } else {\n streamType = 'model';\n }\n }\n\n /**\n * Process based on detected type\n */\n if (streamType === 'model') {\n processModelChunk(\n value as AIMessageChunk,\n modelState,\n wrappedController,\n );\n } else if (streamType === 'streamEvents') {\n processStreamEventsEvent(\n value as {\n event: string;\n data: Record<string, unknown> | null;\n run_id?: string;\n name?: string;\n },\n modelState,\n wrappedController,\n );\n } else {\n const eventArray = value as unknown[];\n const [type, data] = parseLangGraphEvent(eventArray);\n\n if (type === 'values') {\n lastValuesData = data as TState;\n }\n\n processLangGraphEvent(\n eventArray,\n langGraphState,\n wrappedController,\n );\n }\n }\n\n /**\n * Finalize based on stream type\n */\n if (streamType === 'model' || streamType === 'streamEvents') {\n if (modelState.reasoningStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id: modelState.reasoningMessageId ?? modelState.messageId,\n });\n }\n if (modelState.textStarted) {\n /**\n * Use the same ID that was used for text-start\n */\n controller.enqueue({\n type: 'text-end',\n id: modelState.textMessageId ?? modelState.messageId,\n });\n }\n controller.enqueue({ type: 'finish' });\n } else if (streamType === 'langgraph') {\n /**\n * Close any open text/reasoning parts before finishing.\n * This handles streams without values events (e.g. streamMode: 'messages')\n * where the values handler never ran to emit *-end events.\n */\n for (const [id, seen] of Object.entries(langGraphState.messageSeen)) {\n if (seen.text) {\n controller.enqueue({ type: 'text-end', id });\n }\n if (seen.reasoning) {\n controller.enqueue({ type: 'reasoning-end', id });\n }\n }\n\n /**\n * Emit finish-step if a step was started\n */\n if (langGraphState.currentStep !== null) {\n controller.enqueue({ type: 'finish-step' });\n }\n controller.enqueue({ type: 'finish' });\n }\n\n /**\n * Call onFinal callback with aggregated text\n */\n await callbacks?.onFinal?.(textChunks.join(''));\n await callbacks?.onFinish?.(lastValuesData);\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n await callbacks?.onFinal?.(textChunks.join(''));\n\n if (isAbortError(error)) {\n await callbacks?.onAbort?.();\n } else {\n await callbacks?.onError?.(errorObj);\n }\n\n controller.enqueue({\n type: 'error',\n errorText: errorObj.message,\n });\n } finally {\n controller.close();\n }\n },\n });\n}\n","import {\n AIMessage,\n HumanMessage,\n ToolMessage,\n AIMessageChunk,\n type ContentBlock,\n type ToolCall,\n type BaseMessage,\n type BaseMessageChunk,\n type ToolCallChunk,\n} from '@langchain/core/messages';\nimport type {\n UIMessageChunk,\n ToolResultPart,\n AssistantContent,\n UserContent,\n ProviderMetadata,\n JSONValue,\n} from 'ai';\n\nimport type {\n LangGraphEventState,\n ReasoningContentBlock,\n ThinkingContentBlock,\n GPT5ReasoningOutput,\n ImageGenerationOutput,\n NormalizedCitation,\n} from './types';\n\n/**\n * Parses a LangGraph event tuple into [type, data].\n * Handles both 2-element [type, data] and 3-element [namespace, type, data] formats.\n *\n * @param event - The raw LangGraph event array.\n * @returns A tuple of [type, data].\n */\nexport function parseLangGraphEvent(\n event: unknown[],\n): [type: unknown, data: unknown] {\n return event.length === 3 ? [event[1], event[2]] : [event[0], event[1]];\n}\n\n/**\n * Converts a ToolResultPart to a LangChain ToolMessage\n * @param block - The ToolResultPart to convert.\n * @returns The converted ToolMessage.\n */\nexport function convertToolResultPart(block: ToolResultPart): ToolMessage {\n const content = (() => {\n if (block.output.type === 'text' || block.output.type === 'error-text') {\n return block.output.value;\n }\n\n if (block.output.type === 'json' || block.output.type === 'error-json') {\n return JSON.stringify(block.output.value);\n }\n\n if (block.output.type === 'content') {\n return block.output.value\n .map(outputBlock => {\n if (outputBlock.type === 'text') {\n return outputBlock.text;\n }\n return '';\n })\n .join('');\n }\n\n return '';\n })();\n\n return new ToolMessage({\n tool_call_id: block.toolCallId,\n content,\n });\n}\n\n/**\n * Converts AssistantContent to LangChain AIMessage\n * @param content - The AssistantContent to convert.\n * @returns The converted AIMessage.\n */\nexport function convertAssistantContent(content: AssistantContent): AIMessage {\n if (typeof content === 'string') {\n return new AIMessage({ content });\n }\n\n const textParts: string[] = [];\n const toolCalls: Array<{\n id: string;\n name: string;\n args: Record<string, unknown>;\n }> = [];\n\n for (const part of content) {\n if (part.type === 'text') {\n textParts.push(part.text);\n } else if (part.type === 'tool-call') {\n toolCalls.push({\n id: part.toolCallId,\n name: part.toolName,\n args: part.input as Record<string, unknown>,\n });\n }\n }\n\n return new AIMessage({\n content: textParts.join(''),\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n });\n}\n\n/**\n * Helper to generate a default filename from mediaType\n */\nfunction getDefaultFilename(\n mediaType: string,\n prefix: string = 'file',\n): string {\n const fileExtension = mediaType.split('/')[1] || 'bin';\n return `${prefix}.${fileExtension}`;\n}\n\n/**\n * OpenAI-native content block type for images.\n * This format is passed through directly by ChatOpenAI to OpenAI's API.\n */\ntype OpenAIImageBlock = {\n type: 'image_url';\n image_url: {\n url: string;\n detail?: 'auto' | 'low' | 'high';\n };\n};\n\n/**\n * Content block type for HumanMessage that supports both text and OpenAI images.\n */\ntype HumanMessageContentBlock =\n | { type: 'text'; text: string }\n | OpenAIImageBlock\n | ContentBlock;\n\n/**\n * Converts UserContent to LangChain HumanMessage\n * @param content - The UserContent to convert.\n * @returns The converted HumanMessage.\n */\nexport function convertUserContent(content: UserContent): HumanMessage {\n if (typeof content === 'string') {\n return new HumanMessage({ content });\n }\n\n const contentBlocks: HumanMessageContentBlock[] = [];\n\n for (const part of content) {\n if (part.type === 'text') {\n contentBlocks.push({ type: 'text', text: part.text });\n } else if (part.type === 'image') {\n const imagePart = part as {\n type: 'image';\n image: string | Uint8Array | URL | ArrayBuffer;\n mediaType?: string;\n };\n\n /**\n * Use OpenAI's native image_url format which is passed through directly\n * handle URL objects\n */\n if (imagePart.image instanceof URL) {\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: imagePart.image.toString() },\n });\n } else if (typeof imagePart.image === 'string') {\n /**\n * Handle string (could be URL or base64)\n */\n /**\n * Check if it's a URL (including data: URLs)\n */\n if (\n imagePart.image.startsWith('http://') ||\n imagePart.image.startsWith('https://') ||\n imagePart.image.startsWith('data:')\n ) {\n /**\n * OpenAI accepts both http URLs and data URLs directly\n */\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: imagePart.image },\n });\n } else {\n /**\n * Assume base64 encoded data - wrap in data URL\n */\n const mimeType = imagePart.mediaType || 'image/png';\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: `data:${mimeType};base64,${imagePart.image}` },\n });\n }\n } else if (\n /**\n * Handle Uint8Array or ArrayBuffer (binary data)\n */\n imagePart.image instanceof Uint8Array ||\n imagePart.image instanceof ArrayBuffer\n ) {\n const bytes =\n imagePart.image instanceof ArrayBuffer\n ? new Uint8Array(imagePart.image)\n : imagePart.image;\n /**\n * Convert to base64 data URL\n */\n const base64 = btoa(String.fromCharCode(...bytes));\n const mimeType = imagePart.mediaType || 'image/png';\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: `data:${mimeType};base64,${base64}` },\n });\n }\n } else if (part.type === 'file') {\n const rawFilePart = part as {\n type: 'file';\n data:\n | string\n | Uint8Array\n | URL\n | ArrayBuffer\n | { type: 'data'; data: string | Uint8Array | ArrayBuffer }\n | { type: 'url'; url: URL }\n | { type: 'reference'; reference: Record<string, string> }\n | { type: 'text'; text: string };\n mediaType: string;\n filename?: string;\n };\n\n // Normalize tagged data shape into the legacy bare value this code expects.\n const normalizedData: string | Uint8Array | URL | ArrayBuffer = (() => {\n const data = rawFilePart.data;\n if (\n typeof data === 'object' &&\n data !== null &&\n !(data instanceof URL) &&\n !(data instanceof Uint8Array) &&\n !(data instanceof ArrayBuffer) &&\n 'type' in data\n ) {\n switch (data.type) {\n case 'data':\n return data.data;\n case 'url':\n return data.url;\n case 'text':\n return data.text;\n default:\n return '';\n }\n }\n return data as string | Uint8Array | URL | ArrayBuffer;\n })();\n\n const filePart = {\n type: 'file' as const,\n data: normalizedData,\n mediaType: rawFilePart.mediaType,\n filename: rawFilePart.filename,\n };\n\n /**\n * Check if this is an image file - if so, use OpenAI's image_url format\n */\n const isImage = filePart.mediaType?.startsWith('image/');\n\n if (isImage) {\n /**\n * Handle image files using OpenAI's native image_url format\n */\n if (filePart.data instanceof URL) {\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: filePart.data.toString() },\n });\n } else if (typeof filePart.data === 'string') {\n /**\n * URLs (including data URLs) can be passed directly\n */\n if (\n filePart.data.startsWith('http://') ||\n filePart.data.startsWith('https://') ||\n filePart.data.startsWith('data:')\n ) {\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: filePart.data },\n });\n } else {\n /**\n * Assume base64 - wrap in data URL\n */\n contentBlocks.push({\n type: 'image_url',\n image_url: {\n url: `data:${filePart.mediaType};base64,${filePart.data}`,\n },\n });\n }\n } else if (\n filePart.data instanceof Uint8Array ||\n filePart.data instanceof ArrayBuffer\n ) {\n const bytes =\n filePart.data instanceof ArrayBuffer\n ? new Uint8Array(filePart.data)\n : filePart.data;\n const base64 = btoa(String.fromCharCode(...bytes));\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: `data:${filePart.mediaType};base64,${base64}` },\n });\n }\n } else {\n // Handle non-image files using LangChain's ContentBlock format\n const filename =\n filePart.filename || getDefaultFilename(filePart.mediaType, 'file');\n\n if (filePart.data instanceof URL) {\n contentBlocks.push({\n type: 'file',\n url: filePart.data.toString(),\n mimeType: filePart.mediaType,\n filename,\n });\n } else if (typeof filePart.data === 'string') {\n if (\n filePart.data.startsWith('http://') ||\n filePart.data.startsWith('https://')\n ) {\n contentBlocks.push({\n type: 'file',\n url: filePart.data,\n mimeType: filePart.mediaType,\n filename,\n });\n } else if (filePart.data.startsWith('data:')) {\n const matches = filePart.data.match(/^data:([^;]+);base64,(.+)$/);\n if (matches) {\n contentBlocks.push({\n type: 'file',\n data: matches[2],\n mimeType: matches[1],\n filename,\n });\n } else {\n contentBlocks.push({\n type: 'file',\n url: filePart.data,\n mimeType: filePart.mediaType,\n filename,\n });\n }\n } else {\n contentBlocks.push({\n type: 'file',\n data: filePart.data,\n mimeType: filePart.mediaType,\n filename,\n });\n }\n } else if (\n filePart.data instanceof Uint8Array ||\n filePart.data instanceof ArrayBuffer\n ) {\n const bytes =\n filePart.data instanceof ArrayBuffer\n ? new Uint8Array(filePart.data)\n : filePart.data;\n const base64 = btoa(String.fromCharCode(...bytes));\n contentBlocks.push({\n type: 'file',\n data: base64,\n mimeType: filePart.mediaType,\n filename,\n });\n }\n }\n }\n }\n\n /**\n * If we only have text parts, join them as a simple string for efficiency\n */\n if (contentBlocks.every(block => block.type === 'text')) {\n return new HumanMessage({\n content: contentBlocks\n .map(block => (block as unknown as { text: string }).text)\n .join(''),\n });\n }\n\n return new HumanMessage({ content: contentBlocks });\n}\n\n/**\n * Helper to check if a content item is a ToolResultPart\n * @param item - The item to check.\n * @returns True if the item is a ToolResultPart, false otherwise.\n */\nexport function isToolResultPart(item: unknown): item is ToolResultPart {\n return (\n item != null &&\n typeof item === 'object' &&\n 'type' in item &&\n (item as { type: string }).type === 'tool-result'\n );\n}\n\n/**\n * Processes a model stream chunk and emits UI message chunks.\n * @param chunk - The AIMessageChunk to process.\n * @param state - The state of the model stream.\n * @param controller - The controller to use to emit the UI message chunks.\n */\nexport function processModelChunk(\n chunk: AIMessageChunk,\n state: {\n started: boolean;\n messageId: string;\n reasoningStarted?: boolean;\n textStarted?: boolean;\n /** Track the ID used for reasoning-start to ensure reasoning-end uses the same ID */\n reasoningMessageId?: string | null;\n /** Track the ID used for text-start to ensure text-end uses the same ID */\n textMessageId?: string | null;\n emittedImages?: Set<string>;\n emittedSourceIds?: Set<string>;\n },\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n /**\n * Initialize emittedImages set if not present\n */\n if (!state.emittedImages) {\n state.emittedImages = new Set<string>();\n }\n\n if (!state.emittedSourceIds) {\n state.emittedSourceIds = new Set<string>();\n }\n\n /**\n * Get the message ID from the chunk if available\n */\n if (chunk.id) {\n state.messageId = chunk.id;\n }\n\n /**\n * Handle image generation outputs from additional_kwargs.tool_outputs\n */\n const chunkObj = chunk as unknown as Record<string, unknown>;\n const additionalKwargs = chunkObj.additional_kwargs as\n | Record<string, unknown>\n | undefined;\n const imageOutputs = extractImageOutputs(additionalKwargs);\n\n for (const imageOutput of imageOutputs) {\n /**\n * Only emit if we have image data and haven't emitted this image yet\n */\n if (imageOutput.result && !state.emittedImages.has(imageOutput.id)) {\n state.emittedImages.add(imageOutput.id);\n\n /**\n * Emit as a file part using proper AI SDK multimodal format\n */\n const mediaType = `image/${imageOutput.output_format || 'png'}`;\n controller.enqueue({\n type: 'file',\n mediaType,\n url: `data:${mediaType};base64,${imageOutput.result}`,\n });\n state.started = true;\n }\n }\n\n /**\n * Handle reasoning content from contentBlocks or response_metadata.output\n * For direct model streams, we check both sources since there's no values event\n * that would cause duplication (unlike LangGraph streams)\n */\n const reasoning =\n extractReasoningFromContentBlocks(chunk) ||\n extractReasoningFromValuesMessage(chunk);\n if (reasoning) {\n if (!state.reasoningStarted) {\n // Track the ID used for reasoning-start to ensure subsequent chunks use the same ID\n state.reasoningMessageId = state.messageId;\n controller.enqueue({ type: 'reasoning-start', id: state.messageId });\n state.reasoningStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: state.reasoningMessageId ?? state.messageId,\n });\n }\n\n /**\n * Extract text content from AIMessageChunk\n */\n const text =\n typeof chunk.content === 'string'\n ? chunk.content\n : Array.isArray(chunk.content)\n ? chunk.content\n .filter(\n (c): c is { type: 'text'; text: string } =>\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n c.type === 'text',\n )\n .map(c => c.text)\n .join('')\n : '';\n\n if (text) {\n /**\n * If reasoning was streamed before text, close reasoning first\n */\n if (state.reasoningStarted && !state.textStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id: state.reasoningMessageId ?? state.messageId,\n });\n state.reasoningStarted = false;\n }\n\n if (!state.textStarted) {\n // Track the ID used for text-start to ensure subsequent chunks use the same ID\n state.textMessageId = state.messageId;\n controller.enqueue({ type: 'text-start', id: state.messageId });\n state.textStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'text-delta',\n delta: text,\n id: state.textMessageId ?? state.messageId,\n });\n }\n\n const citations = extractCitationsFromContentBlocks(chunk);\n if (citations.length > 0) {\n emitSourceChunks(\n citations,\n state.messageId,\n state.emittedSourceIds,\n controller,\n );\n }\n}\n\n/**\n * Checks if a message is a plain object (not a LangChain class instance).\n * LangChain class instances have a _getType method.\n *\n * @param msg - The message to check.\n * @returns True if the message is a plain object, false otherwise.\n */\nexport function isPlainMessageObject(msg: unknown): boolean {\n if (msg == null || typeof msg !== 'object') return false;\n /**\n * LangChain class instances have _getType method\n */\n return typeof (msg as { _getType?: unknown })._getType !== 'function';\n}\n\n/**\n * Extracts the actual message ID from a message.\n * Handles both class instances (msg.id) and serialized LangChain messages (msg.kwargs.id).\n *\n * @param msg - The message to extract the ID from.\n * @returns The message ID string, or undefined if not found.\n */\nexport function getMessageId(msg: unknown): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n const msgObj = msg as Record<string, unknown>;\n\n /**\n * For class instances, id is directly on the object\n */\n if (typeof msgObj.id === 'string') {\n return msgObj.id;\n }\n\n /**\n * For serialized LangChain messages, id is in kwargs\n */\n if (\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ) {\n const kwargs = msgObj.kwargs as Record<string, unknown>;\n if (typeof kwargs.id === 'string') {\n return kwargs.id;\n }\n }\n\n return undefined;\n}\n\n/**\n * Checks if a message is an AI message chunk (works for both class instances and plain objects).\n * For class instances, only AIMessageChunk is matched (not AIMessage).\n * For plain objects from RemoteGraph API, matches type === 'ai' (TypeScript langchain-core)\n * or type === 'AIMessageChunk' (Python langchain-core).\n * For serialized LangChain messages, matches type === 'constructor' with AIMessageChunk in id path.\n *\n * @param msg - The message to check.\n * @returns True if the message is an AI message chunk, false otherwise.\n */\nexport function isAIMessageChunk(\n msg: unknown,\n): msg is AIMessageChunk & { type?: string; content?: string } {\n /**\n * Actual AIMessageChunk class instance\n */\n if (AIMessageChunk.isInstance(msg)) return true;\n /**\n * Plain object from RemoteGraph API (not a LangChain class instance)\n */\n if (isPlainMessageObject(msg)) {\n const messageRecord = msg as Record<string, unknown>;\n /**\n * Direct type from RemoteGraph format. TypeScript langchain-core emits\n * type === 'ai'; Python langchain-core emits type === 'AIMessageChunk'.\n */\n if (\n 'type' in messageRecord &&\n (messageRecord.type === 'ai' || messageRecord.type === 'AIMessageChunk')\n ) {\n return true;\n }\n /**\n * Serialized LangChain message format: { lc: 1, type: \"constructor\", id: [\"...\", \"AIMessageChunk\"], kwargs: {...} }\n */\n if (\n messageRecord.type === 'constructor' &&\n Array.isArray(messageRecord.id) &&\n (messageRecord.id.includes('AIMessageChunk') ||\n messageRecord.id.includes('AIMessage'))\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Checks if a message is a Tool message (works for both class instances and plain objects).\n *\n * @param msg - The message to check.\n * @returns True if the message is a Tool message, false otherwise.\n */\nexport function isToolMessageType(\n msg: unknown,\n): msg is ToolMessage & { type?: string; tool_call_id?: string } {\n if (ToolMessage.isInstance(msg)) return true;\n /**\n * Plain object from RemoteGraph API (not a LangChain class instance)\n */\n if (isPlainMessageObject(msg)) {\n const messageRecord = msg as Record<string, unknown>;\n /**\n * Direct type === 'tool' (RemoteGraph format)\n */\n if ('type' in messageRecord && messageRecord.type === 'tool') return true;\n /**\n * Serialized LangChain message format\n */\n if (\n messageRecord.type === 'constructor' &&\n Array.isArray(messageRecord.id) &&\n messageRecord.id.includes('ToolMessage')\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Gets text content from a message (works for both class instances and plain objects).\n *\n * @param msg - The message to get the text from.\n * @returns The text content of the message.\n */\nexport function getMessageText(msg: unknown): string {\n if (AIMessageChunk.isInstance(msg)) {\n return msg.text ?? '';\n }\n\n if (msg == null || typeof msg !== 'object') return '';\n\n const msgObj = msg as Record<string, unknown>;\n\n // For serialized LangChain messages, content is in kwargs\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n if ('content' in dataSource) {\n const content = dataSource.content;\n /**\n * Handle string content\n */\n if (typeof content === 'string') {\n return content;\n }\n /**\n * Handle array of content blocks (e.g., [{ type: 'text', text: 'The', index: 0 }])\n */\n if (Array.isArray(content)) {\n return content\n .filter(\n (block): block is { type: 'text'; text: string } =>\n block != null &&\n typeof block === 'object' &&\n block.type === 'text' &&\n typeof block.text === 'string',\n )\n .map(block => block.text)\n .join('');\n }\n return '';\n }\n return '';\n}\n\n/**\n * Checks if an object is a reasoning content block\n *\n * @param obj - The object to check.\n * @returns True if the object is a reasoning content block, false otherwise.\n */\nexport function isReasoningContentBlock(\n obj: unknown,\n): obj is ReasoningContentBlock {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'reasoning' &&\n 'reasoning' in obj &&\n typeof (obj as { reasoning: unknown }).reasoning === 'string'\n );\n}\n\n/**\n * Checks if an object is a thinking content block (Anthropic-style)\n *\n * @param obj - The object to check.\n * @returns True if the object is a thinking content block, false otherwise.\n */\nexport function isThinkingContentBlock(\n obj: unknown,\n): obj is ThinkingContentBlock {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'thinking' &&\n 'thinking' in obj &&\n typeof (obj as { thinking: unknown }).thinking === 'string'\n );\n}\n\n/**\n * Checks if an object is a GPT-5 reasoning output block\n */\nfunction isGPT5ReasoningOutput(obj: unknown): obj is GPT5ReasoningOutput {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'reasoning' &&\n 'summary' in obj &&\n Array.isArray((obj as { summary: unknown }).summary)\n );\n}\n\n/**\n * Extracts the reasoning block ID from a message (GPT-5 format).\n * This ID is consistent across streaming and values events.\n * Handles both class instances and serialized LangChain message objects.\n *\n * @param msg - The message to extract the reasoning ID from.\n * @returns The reasoning block ID if found, undefined otherwise.\n */\nexport function extractReasoningId(msg: unknown): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n // For serialized LangChain messages, the data is in kwargs\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // Check additional_kwargs.reasoning.id (GPT-5 streaming format)\n const additionalKwargs = (\n kwargs as { additional_kwargs?: { reasoning?: { id?: string } } }\n ).additional_kwargs;\n if (additionalKwargs?.reasoning?.id) {\n return additionalKwargs.reasoning.id;\n }\n\n // Check response_metadata.output for reasoning block ID (GPT-5 final format)\n const responseMetadata = (\n kwargs as { response_metadata?: { output?: unknown[] } }\n ).response_metadata;\n if (responseMetadata && Array.isArray(responseMetadata.output)) {\n for (const item of responseMetadata.output) {\n if (isGPT5ReasoningOutput(item)) {\n return item.id;\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Extracts reasoning content from contentBlocks or additional_kwargs.reasoning.summary\n *\n * IMPORTANT: This function is designed for STREAMING chunks where content is delta-based.\n * It does NOT extract from response_metadata.output because that contains accumulated\n * content (not deltas) and would cause duplication during streaming.\n *\n * For non-streaming/values events, use extractReasoningFromValuesMessage instead.\n *\n * Handles both class instances and serialized LangChain message objects.\n *\n * @param msg - The message to extract reasoning from.\n * @returns The reasoning text if found, undefined otherwise.\n */\nexport function extractReasoningFromContentBlocks(\n msg: unknown,\n): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n // For serialized LangChain messages, the data is in kwargs\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // Check contentBlocks (Anthropic-style) - highest priority\n const contentBlocks = (kwargs as { contentBlocks?: unknown[] }).contentBlocks;\n if (Array.isArray(contentBlocks)) {\n const reasoningParts: string[] = [];\n for (const block of contentBlocks) {\n if (isReasoningContentBlock(block)) {\n reasoningParts.push(block.reasoning);\n } else if (isThinkingContentBlock(block)) {\n reasoningParts.push(block.thinking);\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n // Check additional_kwargs.reasoning.summary (GPT-5 streaming format)\n // This contains DELTA content during streaming, not accumulated content\n // Format can be either { type: \"summary_text\", text: \"...\" } or just { text: \"...\" }\n const additionalKwargs = (\n kwargs as { additional_kwargs?: { reasoning?: { summary?: unknown[] } } }\n ).additional_kwargs;\n if (\n additionalKwargs?.reasoning &&\n Array.isArray(additionalKwargs.reasoning.summary)\n ) {\n const reasoningParts: string[] = [];\n for (const summaryItem of additionalKwargs.reasoning.summary) {\n if (\n typeof summaryItem === 'object' &&\n summaryItem !== null &&\n 'text' in summaryItem &&\n typeof (summaryItem as { text: unknown }).text === 'string'\n ) {\n reasoningParts.push((summaryItem as { text: string }).text);\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n return undefined;\n}\n\n/**\n * Extracts reasoning content from a values event message.\n * This checks response_metadata.output which contains the full accumulated reasoning.\n *\n * @param msg - The message to extract reasoning from.\n * @returns The reasoning text if found, undefined otherwise.\n */\nexport function extractReasoningFromValuesMessage(\n msg: unknown,\n): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n // For serialized LangChain messages, the data is in kwargs\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // Check response_metadata.output (GPT-5 final style) - for values events\n const responseMetadata = (\n kwargs as { response_metadata?: { output?: unknown[] } }\n ).response_metadata;\n if (responseMetadata && Array.isArray(responseMetadata.output)) {\n const reasoningParts: string[] = [];\n for (const item of responseMetadata.output) {\n if (isGPT5ReasoningOutput(item)) {\n // Extract text from summary array - handles both { type: \"summary_text\", text } and { text } formats\n for (const summaryItem of item.summary) {\n if (typeof summaryItem === 'object' && summaryItem !== null) {\n const text = (summaryItem as { text?: string }).text;\n if (typeof text === 'string' && text) {\n reasoningParts.push(text);\n }\n }\n }\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n // Also check additional_kwargs.reasoning.summary as fallback\n const additionalKwargs = (\n kwargs as { additional_kwargs?: { reasoning?: { summary?: unknown[] } } }\n ).additional_kwargs;\n if (\n additionalKwargs?.reasoning &&\n Array.isArray(additionalKwargs.reasoning.summary)\n ) {\n const reasoningParts: string[] = [];\n for (const summaryItem of additionalKwargs.reasoning.summary) {\n if (\n typeof summaryItem === 'object' &&\n summaryItem !== null &&\n 'text' in summaryItem &&\n typeof (summaryItem as { text: unknown }).text === 'string'\n ) {\n reasoningParts.push((summaryItem as { text: string }).text);\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n return undefined;\n}\n\nexport function isCitationContentBlock(\n obj: unknown,\n): obj is ContentBlock.Citation {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: unknown }).type === 'citation'\n );\n}\n\n/**\n * LangChain Core standardizes citations as `{ type: 'citation', ... }` entries in\n * the `annotations` array of a text content block. The text extractors elsewhere\n * in this file only read the `text` field, so without this the citations would be\n * dropped instead of surfaced as AI SDK source parts.\n *\n * Handles both class instances and serialized LangChain message objects.\n */\nexport function extractCitationsFromContentBlocks(\n msg: unknown,\n): NormalizedCitation[] {\n if (msg == null || typeof msg !== 'object') return [];\n\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // `contentBlocks` is the normalized view derived from `content`; reading both\n // would double-count annotations, so prefer it and only fall back to `content`.\n const contentBlocks = (kwargs as { contentBlocks?: unknown }).contentBlocks;\n const content = (kwargs as { content?: unknown }).content;\n const blockSources: unknown[] = Array.isArray(contentBlocks)\n ? contentBlocks\n : Array.isArray(content)\n ? content\n : [];\n\n const citations: NormalizedCitation[] = [];\n\n for (const block of blockSources) {\n if (block == null || typeof block !== 'object') continue;\n\n const annotations = (block as { annotations?: unknown }).annotations;\n if (!Array.isArray(annotations)) continue;\n\n for (const annotation of annotations) {\n if (!isCitationContentBlock(annotation)) continue;\n\n citations.push({\n url: annotation.url,\n title: annotation.title,\n source: annotation.source,\n citedText: annotation.citedText,\n startIndex: annotation.startIndex,\n endIndex: annotation.endIndex,\n });\n }\n }\n\n return citations;\n}\n\nfunction buildSourceProviderMetadata(\n citation: NormalizedCitation,\n): ProviderMetadata | undefined {\n const langchain: Record<string, JSONValue> = {};\n\n if (typeof citation.citedText === 'string') {\n langchain.citedText = citation.citedText;\n }\n if (typeof citation.startIndex === 'number') {\n langchain.startIndex = citation.startIndex;\n }\n if (typeof citation.endIndex === 'number') {\n langchain.endIndex = citation.endIndex;\n }\n if (typeof citation.source === 'string') {\n langchain.source = citation.source;\n }\n\n if (Object.keys(langchain).length === 0) {\n return undefined;\n }\n\n return { langchain };\n}\n\n/**\n * Emits AI SDK source chunks (`source-url` / `source-document`) for the given\n * citations.\n *\n * Citations with a `url` become `source-url` parts keyed by that url; the rest\n * become `source-document` parts. Citation metadata that the source parts cannot\n * represent natively (`citedText`, `startIndex`, `endIndex`, `source`) is preserved\n * under `providerMetadata.langchain`.\n *\n * `emittedSourceIds` dedupes across the LangGraph messages and values events, which\n * can each surface the same citation. URL-less citations are keyed by their content\n * rather than position, so a differing citation subset/order between those two events\n * does not cause an id collision.\n */\nexport function emitSourceChunks(\n citations: NormalizedCitation[],\n messageId: string,\n emittedSourceIds: Set<string>,\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n for (const citation of citations) {\n if (citation.url) {\n if (emittedSourceIds.has(citation.url)) continue;\n emittedSourceIds.add(citation.url);\n\n const providerMetadata = buildSourceProviderMetadata(citation);\n controller.enqueue({\n type: 'source-url',\n sourceId: citation.url,\n url: citation.url,\n ...(citation.title ? { title: citation.title } : {}),\n ...(providerMetadata ? { providerMetadata } : {}),\n });\n continue;\n }\n\n // A citation with no url and no human-readable label carries no information a\n // UI could render, so skip it rather than emit a placeholder source.\n const title = citation.title ?? citation.source;\n if (!title) continue;\n\n const sourceId = `${messageId}:${title}:${citation.citedText ?? ''}:${citation.startIndex ?? ''}:${citation.endIndex ?? ''}`;\n if (emittedSourceIds.has(sourceId)) continue;\n emittedSourceIds.add(sourceId);\n\n const providerMetadata = buildSourceProviderMetadata(citation);\n controller.enqueue({\n type: 'source-document',\n sourceId,\n mediaType: 'text/plain',\n title,\n ...(providerMetadata ? { providerMetadata } : {}),\n });\n }\n}\n\n/**\n * Checks if an object is an image generation output\n *\n * @param obj - The object to check.\n * @returns True if the object is an image generation output, false otherwise.\n */\nexport function isImageGenerationOutput(\n obj: unknown,\n): obj is ImageGenerationOutput {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'image_generation_call'\n );\n}\n\n/**\n * Extracts image generation outputs from `additional_kwargs`\n *\n * @param additionalKwargs - The additional kwargs to extract the image generation outputs from.\n * @returns The image generation outputs.\n */\nexport function extractImageOutputs(\n additionalKwargs: Record<string, unknown> | undefined,\n): ImageGenerationOutput[] {\n if (!additionalKwargs) return [];\n\n const toolOutputs = additionalKwargs.tool_outputs;\n if (!Array.isArray(toolOutputs)) return [];\n\n return toolOutputs.filter(isImageGenerationOutput);\n}\n\nfunction formatToolError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n\n try {\n const serialized = JSON.stringify(error);\n return serialized ?? String(error);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Processes a LangGraph event and emits UI message chunks.\n *\n * @param event - The event to process.\n * @param state - The state of the LangGraph event.\n * @param controller - The controller to use to emit the UI message chunks.\n */\nexport function processLangGraphEvent(\n event: unknown[],\n state: LangGraphEventState,\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n const {\n messageSeen,\n messageConcat,\n emittedToolCalls,\n emittedImages,\n emittedReasoningIds,\n messageReasoningIds,\n toolCallInfoByIndex,\n emittedToolCallsByKey,\n emittedToolInputs,\n } = state;\n const [type, data] = parseLangGraphEvent(event);\n\n switch (type) {\n case 'custom': {\n /**\n * Extract custom event type from the data's 'type' field if present.\n * This allows users to emit custom events like:\n * writer({ type: 'progress', value: 50 }) -> { type: 'data-progress', data: {...} }\n * writer({ type: 'status', message: '...' }) -> { type: 'data-status', data: {...} }\n * writer({ key: 'value' }) -> { type: 'data-custom', data: {...} } (fallback)\n *\n * The 'id' field can be used to make parts persistent and updateable.\n * Parts with an 'id' are NOT transient (added to message.parts).\n * Parts without an 'id' are transient (only passed to onData callback).\n */\n let customTypeName = 'custom';\n let partId: string | undefined;\n\n if (data != null && typeof data === 'object' && !Array.isArray(data)) {\n const dataObj = data as Record<string, unknown>;\n if (typeof dataObj.type === 'string' && dataObj.type) {\n customTypeName = dataObj.type;\n }\n if (typeof dataObj.id === 'string' && dataObj.id) {\n partId = dataObj.id;\n }\n }\n\n controller.enqueue({\n type: `data-${customTypeName}` as `data-${string}`,\n id: partId,\n transient: partId == null,\n data,\n });\n break;\n }\n\n case 'messages': {\n const [rawMsg, metadata] = data as [\n BaseMessageChunk | BaseMessage | undefined,\n Record<string, unknown> | undefined,\n ];\n\n const msg = rawMsg;\n const msgId = getMessageId(msg);\n\n if (!msgId) return;\n\n /**\n * Track LangGraph step changes and emit start-step/finish-step events.\n * Before emitting finish-step, close any open text/reasoning parts so\n * the client does not receive orphaned deltas after its\n * activeReasoningParts / activeTextParts have been cleared.\n */\n const langgraphStep =\n typeof metadata?.langgraph_step === 'number'\n ? metadata.langgraph_step\n : null;\n if (langgraphStep !== null && langgraphStep !== state.currentStep) {\n if (state.currentStep !== null) {\n for (const [id, seen] of Object.entries(messageSeen)) {\n if (seen.text) {\n controller.enqueue({ type: 'text-end', id });\n }\n if (seen.reasoning) {\n controller.enqueue({ type: 'reasoning-end', id });\n }\n delete messageSeen[id];\n delete messageConcat[id];\n delete messageReasoningIds[id];\n }\n controller.enqueue({ type: 'finish-step' });\n }\n controller.enqueue({ type: 'start-step' });\n state.currentStep = langgraphStep;\n }\n\n /**\n * Accumulate message chunks for later reference\n * Note: Only works for actual class instances, not serialized messages\n */\n if (AIMessageChunk.isInstance(msg)) {\n if (messageConcat[msgId]) {\n messageConcat[msgId] = messageConcat[msgId].concat(\n msg,\n ) as AIMessageChunk;\n } else {\n messageConcat[msgId] = msg;\n }\n }\n\n if (isAIMessageChunk(msg)) {\n const concatChunk = messageConcat[msgId];\n\n /**\n * Handle image generation outputs from additional_kwargs.tool_outputs\n * Handle both direct properties and serialized messages (kwargs)\n */\n const msgObj = msg as unknown as Record<string, unknown>;\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n const additionalKwargs = dataSource.additional_kwargs as\n | Record<string, unknown>\n | undefined;\n const imageOutputs = extractImageOutputs(additionalKwargs);\n\n for (const imageOutput of imageOutputs) {\n /**\n * Only emit if we have image data and haven't emitted this image yet\n */\n if (imageOutput.result && !emittedImages.has(imageOutput.id)) {\n emittedImages.add(imageOutput.id);\n\n /**\n * Emit as a file part using proper AI SDK multimodal format\n */\n const mediaType = `image/${imageOutput.output_format || 'png'}`;\n controller.enqueue({\n type: 'file',\n mediaType,\n url: `data:${mediaType};base64,${imageOutput.result}`,\n });\n }\n }\n\n /**\n * Handle tool call chunks for streaming tool calls\n * Access from dataSource to handle both direct and serialized messages\n *\n * Tool call chunks are streamed as follows:\n * 1. First chunk: has name, id, but often empty args\n * 2. Subsequent chunks: have args but NO id or name\n *\n * We store tool call info by index when we first see it, then look it up\n * for subsequent chunks that don't include the id.\n */\n const toolCallChunks = dataSource.tool_call_chunks as\n | ToolCallChunk[]\n | undefined;\n if (toolCallChunks?.length) {\n for (const toolCallChunk of toolCallChunks) {\n const toolCallIndex = toolCallChunk.index ?? 0;\n\n /**\n * If this chunk has an id, store it for future lookups by index\n */\n if (toolCallChunk.id) {\n toolCallInfoByIndex[msgId] ??= {};\n toolCallInfoByIndex[msgId][toolCallIndex] = {\n id: toolCallChunk.id,\n name:\n toolCallChunk.name ||\n concatChunk?.tool_call_chunks?.[toolCallIndex]?.name ||\n 'unknown',\n };\n }\n\n /**\n * Get the tool call ID from the chunk, stored info, or accumulated chunks\n */\n const toolCallId =\n toolCallChunk.id ||\n toolCallInfoByIndex[msgId]?.[toolCallIndex]?.id ||\n concatChunk?.tool_call_chunks?.[toolCallIndex]?.id;\n\n /**\n * Skip if we don't have a proper tool call ID - we'll handle it in values\n */\n if (!toolCallId) {\n continue;\n }\n\n const toolName =\n toolCallChunk.name ||\n toolCallInfoByIndex[msgId]?.[toolCallIndex]?.name ||\n concatChunk?.tool_call_chunks?.[toolCallIndex]?.name ||\n 'unknown';\n\n /**\n * Emit tool-input-start when we first see this tool call\n * (even if args is empty - the first chunk often has empty args)\n * Set dynamic: true to enable HITL approval requests\n */\n if (!messageSeen[msgId]?.tool?.[toolCallId]) {\n messageSeen[msgId] ??= {};\n messageSeen[msgId].tool ??= {};\n messageSeen[msgId].tool![toolCallId] = true;\n\n if (!emittedToolCalls.has(toolCallId)) {\n emittedToolCalls.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId: toolCallId,\n toolName: toolName,\n dynamic: true,\n });\n }\n }\n\n /**\n * Only emit tool-input-delta when args is non-empty\n */\n if (toolCallChunk.args) {\n controller.enqueue({\n type: 'tool-input-delta',\n toolCallId: toolCallId,\n inputTextDelta: toolCallChunk.args,\n });\n }\n }\n\n return;\n }\n\n /**\n * Handle reasoning content from contentBlocks\n * Streaming chunks contain DELTA text (not accumulated), so emit directly.\n * Use reasoning block ID for deduplication as it's consistent across streaming and values events.\n *\n * Important: Early chunks may have reasoning ID but no content, later chunks may\n * have content but no reasoning ID. We capture the ID when first seen and reuse it.\n * We also immediately add to emittedReasoningIds to prevent values events from\n * emitting the same reasoning (values events can arrive between streaming chunks).\n */\n // Capture reasoning ID when we first see it (even if no content yet)\n const chunkReasoningId = extractReasoningId(msg);\n if (chunkReasoningId) {\n if (!messageReasoningIds[msgId]) {\n messageReasoningIds[msgId] = chunkReasoningId;\n }\n // Immediately mark as emitted to prevent values from duplicating\n // This must happen as soon as we see the ID, before content arrives\n emittedReasoningIds.add(chunkReasoningId);\n }\n\n const reasoning = extractReasoningFromContentBlocks(msg);\n if (reasoning) {\n // Use stored reasoning ID, or current chunk's ID, or fall back to message ID\n const reasoningId =\n messageReasoningIds[msgId] ?? chunkReasoningId ?? msgId;\n\n if (!messageSeen[msgId]?.reasoning) {\n controller.enqueue({ type: 'reasoning-start', id: msgId });\n messageSeen[msgId] ??= {};\n messageSeen[msgId].reasoning = true;\n }\n\n // Streaming chunks have delta text, emit directly without slicing\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: msgId,\n });\n // Also ensure the reasoning ID is marked (handles case where ID wasn't in first chunk)\n emittedReasoningIds.add(reasoningId);\n }\n\n /**\n * Handle text content\n */\n const text = getMessageText(msg);\n if (text) {\n if (!messageSeen[msgId]?.text) {\n controller.enqueue({ type: 'text-start', id: msgId });\n messageSeen[msgId] ??= {};\n messageSeen[msgId].text = true;\n }\n\n controller.enqueue({\n type: 'text-delta',\n delta: text,\n id: msgId,\n });\n }\n\n const citations = extractCitationsFromContentBlocks(msg);\n if (citations.length > 0) {\n emitSourceChunks(\n citations,\n msgId,\n state.emittedSourceIds,\n controller,\n );\n }\n } else if (isToolMessageType(msg)) {\n // Handle both direct properties and serialized messages (kwargs)\n const msgObj = msg as unknown as Record<string, unknown>;\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n const toolCallId = dataSource.tool_call_id as string | undefined;\n const status = dataSource.status as string | undefined;\n\n if (toolCallId) {\n if (status === 'error') {\n // Tool execution failed\n controller.enqueue({\n type: 'tool-output-error',\n toolCallId,\n errorText:\n typeof dataSource.content === 'string'\n ? dataSource.content\n : 'Tool execution failed',\n });\n } else {\n // Tool execution succeeded\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId,\n output: dataSource.content,\n });\n }\n }\n }\n\n return;\n }\n\n case 'tools': {\n if (data == null || typeof data !== 'object' || Array.isArray(data)) {\n return;\n }\n\n const payload = data as {\n event?: unknown;\n name?: unknown;\n input?: unknown;\n data?: unknown;\n output?: unknown;\n error?: unknown;\n toolCallId?: unknown;\n };\n const toolCallId =\n typeof payload.toolCallId === 'string' ? payload.toolCallId : undefined;\n const toolName =\n typeof payload.name === 'string' ? payload.name : 'unknown';\n\n if (!toolCallId) return;\n\n const ensureToolInputLifecycle = () => {\n if (!emittedToolCalls.has(toolCallId)) {\n emittedToolCalls.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId,\n toolName,\n dynamic: true,\n });\n }\n\n if (!emittedToolInputs.has(toolCallId)) {\n emittedToolInputs.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId,\n toolName,\n input: payload.input,\n dynamic: true,\n });\n }\n };\n\n switch (payload.event) {\n case 'on_tool_start': {\n const toolCallKey = `${toolName}:${JSON.stringify(payload.input)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCallId);\n\n ensureToolInputLifecycle();\n break;\n }\n\n case 'on_tool_event': {\n ensureToolInputLifecycle();\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId,\n output: payload.data,\n preliminary: true,\n });\n break;\n }\n\n case 'on_tool_end': {\n ensureToolInputLifecycle();\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId,\n output: payload.output,\n });\n break;\n }\n\n case 'on_tool_error': {\n ensureToolInputLifecycle();\n controller.enqueue({\n type: 'tool-output-error',\n toolCallId,\n errorText: formatToolError(payload.error),\n });\n break;\n }\n }\n\n return;\n }\n\n case 'values': {\n /**\n * Finalize all pending message chunks\n */\n for (const [id, seen] of Object.entries(messageSeen)) {\n if (seen.text) controller.enqueue({ type: 'text-end', id });\n if (seen.tool) {\n for (const [toolCallId, toolCallSeen] of Object.entries(seen.tool)) {\n const concatMsg = messageConcat[id];\n const toolCall = concatMsg?.tool_calls?.find(\n call => call.id === toolCallId,\n );\n\n if (toolCallSeen && toolCall) {\n emittedToolCalls.add(toolCallId);\n // Store mapping for HITL interrupt lookup\n const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCallId);\n if (!emittedToolInputs.has(toolCallId)) {\n emittedToolInputs.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId,\n toolName: toolCall.name,\n input: toolCall.args,\n dynamic: true,\n });\n }\n }\n }\n }\n\n if (seen.reasoning) {\n controller.enqueue({ type: 'reasoning-end', id });\n }\n\n delete messageSeen[id];\n delete messageConcat[id];\n delete messageReasoningIds[id];\n }\n\n /**\n * Also check for tool calls in the final state that weren't streamed\n * This handles cases where tool calls appear directly in values without being in messages events\n */\n if (data != null && typeof data === 'object' && 'messages' in data) {\n const messages = (data as { messages?: unknown[] }).messages;\n if (Array.isArray(messages)) {\n /**\n * First pass: Collect all tool call IDs that have been responded to by ToolMessages.\n * These are historical tool calls that are already complete.\n */\n const completedToolCallIds = new Set<string>();\n for (const msg of messages) {\n if (!msg || typeof msg !== 'object') continue;\n\n if (isToolMessageType(msg)) {\n // Handle both direct properties and serialized messages (kwargs)\n const msgObj = msg as unknown as Record<string, unknown>;\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n const toolCallId = dataSource.tool_call_id as string | undefined;\n if (toolCallId) {\n completedToolCallIds.add(toolCallId);\n }\n }\n }\n\n /**\n * Second pass: Process messages and emit tool events only for NEW tool calls\n * (those not already completed by a ToolMessage in the history)\n */\n for (const msg of messages) {\n if (!msg || typeof msg !== 'object') continue;\n\n // Use getMessageId to handle both class instances and serialized messages\n const msgId = getMessageId(msg);\n if (!msgId) continue;\n\n /**\n * Check if this is an AI message with tool calls\n */\n let toolCalls: ToolCall[] | undefined;\n\n /**\n * For class instances\n */\n if (AIMessageChunk.isInstance(msg) || AIMessage.isInstance(msg)) {\n toolCalls = msg.tool_calls;\n } else if (isPlainMessageObject(msg)) {\n /**\n * For plain objects from RemoteGraph API or serialized LangChain messages\n */\n const messageRecord = msg as Record<string, unknown>;\n\n /**\n * Determine the data source (handle both direct and serialized formats)\n */\n const isSerializedFormat =\n messageRecord.type === 'constructor' &&\n Array.isArray(messageRecord.id) &&\n ((messageRecord.id as string[]).includes('AIMessageChunk') ||\n (messageRecord.id as string[]).includes('AIMessage'));\n const dataSource = isSerializedFormat\n ? (messageRecord.kwargs as Record<string, unknown>)\n : messageRecord;\n\n if (\n messageRecord.type === 'ai' ||\n messageRecord.type === 'AIMessageChunk' ||\n isSerializedFormat\n ) {\n /**\n * Try tool_calls first (normalized format)\n */\n if (Array.isArray(dataSource?.tool_calls)) {\n toolCalls = dataSource.tool_calls as ToolCall[];\n } else if (\n /**\n * Fall back to additional_kwargs.tool_calls (OpenAI format)\n */\n dataSource?.additional_kwargs &&\n typeof dataSource.additional_kwargs === 'object'\n ) {\n const additionalKwargs =\n dataSource.additional_kwargs as Record<string, unknown>;\n if (Array.isArray(additionalKwargs.tool_calls)) {\n /**\n * Convert OpenAI format to normalized format\n */\n toolCalls = (\n additionalKwargs.tool_calls as Array<{\n id?: string;\n function?: { name?: string; arguments?: string };\n }>\n ).map((tc, idx) => {\n const functionData = tc.function;\n let args: unknown;\n try {\n args = functionData?.arguments\n ? JSON.parse(functionData.arguments)\n : {};\n } catch {\n args = {};\n }\n return {\n id: tc.id || `call_${idx}`,\n name: functionData?.name || 'unknown',\n args,\n } as ToolCall;\n });\n }\n }\n }\n }\n\n if (toolCalls && toolCalls.length > 0) {\n for (const toolCall of toolCalls) {\n /**\n * Only emit if we haven't already processed this tool call\n * AND if it's not a historical tool call that already has a ToolMessage response.\n * Historical completed tool calls should not be re-emitted as this would create\n * orphaned tool parts in the UI without corresponding outputs.\n */\n if (\n toolCall.id &&\n !emittedToolCalls.has(toolCall.id) &&\n !completedToolCallIds.has(toolCall.id)\n ) {\n emittedToolCalls.add(toolCall.id);\n // Store mapping for HITL interrupt lookup\n const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCall.id);\n /**\n * Emit tool-input-start first to ensure proper lifecycle.\n * Tool calls that weren't streamed (no tool_call_chunks) need\n * the start event before tool-input-available.\n */\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n dynamic: true,\n });\n emittedToolInputs.add(toolCall.id);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n input: toolCall.args,\n dynamic: true,\n });\n } else if (toolCall.id && emittedToolCalls.has(toolCall.id)) {\n // Register key mapping for tool calls already emitted via messages mode\n // so that __interrupt__ handling can match them by key\n const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCall.id);\n }\n }\n }\n\n /**\n * Check for reasoning content that wasn't streamed\n * Use reasoning block ID for deduplication as it's consistent across streaming and values.\n *\n * IMPORTANT: Handle two cases differently:\n * 1. Message has reasoning WITHOUT tool_calls → emit reasoning (pure reasoning case)\n * 2. Message has reasoning WITH tool_calls → only emit if streamed this request\n * (When resuming from HITL interrupt, historical messages have both reasoning\n * AND tool_calls. We skip those to avoid duplicate reasoning entries.)\n */\n const reasoningId = extractReasoningId(msg);\n const wasStreamedThisRequest = !!messageSeen[msgId];\n const hasToolCalls = toolCalls && toolCalls.length > 0;\n\n /**\n * Determine if we should emit reasoning:\n * - If we already emitted this reasoning ID, skip\n * - If the message was streamed this request, emit (normal flow)\n * - If NOT streamed but has NO tool_calls, emit (pure reasoning in values case)\n * - If NOT streamed but HAS tool_calls, skip (historical HITL message)\n */\n const shouldEmitReasoning =\n reasoningId &&\n !emittedReasoningIds.has(reasoningId) &&\n (wasStreamedThisRequest || !hasToolCalls);\n\n if (shouldEmitReasoning) {\n /**\n * Use extractReasoningFromValuesMessage which extracts from response_metadata.output\n * This is the full accumulated reasoning, not deltas\n */\n const reasoning = extractReasoningFromValuesMessage(msg);\n\n if (reasoning) {\n controller.enqueue({ type: 'reasoning-start', id: msgId });\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: msgId,\n });\n controller.enqueue({ type: 'reasoning-end', id: msgId });\n emittedReasoningIds.add(reasoningId);\n }\n }\n\n const valuesCitations = extractCitationsFromContentBlocks(msg);\n if (valuesCitations.length > 0) {\n emitSourceChunks(\n valuesCitations,\n msgId,\n state.emittedSourceIds,\n controller,\n );\n }\n }\n }\n }\n\n /**\n * Handle Human-in-the-Loop interrupts\n * When HITL middleware pauses execution, the interrupt data is in __interrupt__\n * Note: This is outside the 'messages' check because interrupt can come as a separate event\n */\n if (data != null && typeof data === 'object') {\n const interrupt = (data as Record<string, unknown>).__interrupt__;\n if (Array.isArray(interrupt) && interrupt.length > 0) {\n for (const interruptItem of interrupt) {\n const interruptValue = (interruptItem as { value?: unknown })\n ?.value as Record<string, unknown> | undefined;\n\n if (!interruptValue) continue;\n\n /**\n * Support both camelCase (JS SDK) and snake_case (Python SDK)\n */\n const actionRequests = (interruptValue.actionRequests ||\n interruptValue.action_requests) as\n | Array<{\n name: string;\n args?: Record<string, unknown>; // JS SDK uses 'args'\n arguments?: Record<string, unknown>; // Python SDK uses 'arguments'\n id?: string;\n }>\n | undefined;\n\n if (!Array.isArray(actionRequests)) continue;\n\n for (const actionRequest of actionRequests) {\n const toolName = actionRequest.name;\n /**\n * Support both 'args' (JS SDK) and 'arguments' (Python SDK)\n */\n const input = actionRequest.args || actionRequest.arguments;\n\n /**\n * Look up the original tool call ID using the name+args key\n * Fall back to action request ID or generate one if not found\n */\n const toolCallKey = `${toolName}:${JSON.stringify(input)}`;\n const toolCallId =\n emittedToolCallsByKey.get(toolCallKey) ||\n actionRequest.id ||\n `hitl-${toolName}-${Date.now()}`;\n\n /**\n * First emit tool-input-start then tool-input-available\n * so the UI knows what tool is being called with proper lifecycle\n */\n if (!emittedToolCalls.has(toolCallId)) {\n emittedToolCalls.add(toolCallId);\n emittedToolCallsByKey.set(toolCallKey, toolCallId);\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId,\n toolName,\n dynamic: true,\n });\n emittedToolInputs.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId,\n toolName,\n input,\n dynamic: true,\n });\n }\n\n /**\n * Then emit tool-approval-request to mark it as awaiting approval\n */\n controller.enqueue({\n type: 'tool-approval-request',\n approvalId: toolCallId,\n toolCallId,\n });\n }\n }\n }\n }\n\n break;\n }\n }\n}\n","import type { AIMessageChunk } from '@langchain/core/messages';\nimport type {\n UIMessage,\n UIMessageChunk,\n ChatTransport,\n ChatRequestOptions,\n} from 'ai';\nimport {\n RemoteGraph,\n type RemoteGraphParams,\n} from '@langchain/langgraph/remote';\nimport { toBaseMessages, toUIMessageStream } from './adapter';\n\n/**\n * Options for configuring a LangSmith deployment transport.\n * Extends RemoteGraphParams but makes graphId optional (defaults to 'agent').\n */\nexport type LangSmithDeploymentTransportOptions = Omit<\n RemoteGraphParams,\n 'graphId'\n> & {\n /**\n * The ID of the graph to connect to.\n * @default 'agent'\n */\n graphId?: string;\n};\n\n/**\n * A ChatTransport implementation for LangSmith/LangGraph deployments.\n *\n * This transport enables seamless integration between the AI SDK's useChat hook\n * and LangSmith deployed LangGraph agents.\n *\n * @example\n * ```ts\n * import { LangSmithDeploymentTransport } from '@ai-sdk/langchain';\n *\n * // Use with useChat\n * const { messages, input, handleSubmit } = useChat({\n * transport: new LangSmithDeploymentTransport({\n * url: 'https://your-deployment.us.langgraph.app',\n * apiKey: 'my-api-key',\n * }),\n * });\n * ```\n */\nexport class LangSmithDeploymentTransport<\n UI_MESSAGE extends UIMessage,\n> implements ChatTransport<UI_MESSAGE> {\n protected graph: RemoteGraph;\n\n constructor(options: LangSmithDeploymentTransportOptions) {\n this.graph = new RemoteGraph({\n ...options,\n graphId: options.graphId ?? 'agent',\n });\n }\n\n async sendMessages(\n options: {\n trigger: 'submit-message' | 'regenerate-message';\n chatId: string;\n messageId: string | undefined;\n messages: UI_MESSAGE[];\n abortSignal: AbortSignal | undefined;\n } & ChatRequestOptions,\n ): Promise<ReadableStream<UIMessageChunk>> {\n const baseMessages = await toBaseMessages(options.messages);\n\n const stream = await this.graph.stream(\n { messages: baseMessages },\n { streamMode: ['values', 'messages'] },\n );\n\n return toUIMessageStream(\n stream as AsyncIterable<AIMessageChunk> | ReadableStream,\n );\n }\n\n async reconnectToStream(\n _options: {\n chatId: string;\n } & ChatRequestOptions,\n ): Promise<ReadableStream<UIMessageChunk> | null> {\n throw new Error('Method not implemented.');\n }\n}\n"],"mappings":"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","names":["providerMetadata"]}
1
+ {"version":3,"sources":["../src/adapter.ts","../src/utils.ts","../src/transport.ts"],"sourcesContent":["import {\n SystemMessage,\n type BaseMessage,\n type AIMessageChunk,\n} from '@langchain/core/messages';\nimport {\n convertToModelMessages,\n type UIMessage,\n type UIMessageChunk,\n type ModelMessage,\n} from 'ai';\nimport {\n convertToolResultPart,\n convertAssistantContent,\n convertUserContent,\n processModelChunk,\n processLangGraphEvent,\n parseLangGraphEvent,\n isToolResultPart,\n extractReasoningFromContentBlocks,\n extractCitationsFromContentBlocks,\n emitSourceChunks,\n} from './utils';\nimport type { LangGraphEventState } from './types';\nimport type { StreamCallbacks } from './stream-callbacks';\n\n/**\n * Converts AI SDK UIMessages to LangChain BaseMessage objects.\n *\n * This function transforms the AI SDK's message format into LangChain's message\n * format, enabling seamless integration between the two frameworks.\n *\n * @param messages - Array of AI SDK UIMessage objects to convert.\n * @returns Promise resolving to an array of LangChain BaseMessage objects.\n *\n * @example\n * ```ts\n * import { toBaseMessages } from '@ai-sdk/langchain';\n *\n * const langchainMessages = await toBaseMessages(uiMessages);\n *\n * // Use with LangChain\n * const response = await model.invoke(langchainMessages);\n * ```\n */\nexport async function toBaseMessages(\n messages: UIMessage[],\n): Promise<BaseMessage[]> {\n const modelMessages = await convertToModelMessages(messages);\n return convertModelMessages(modelMessages);\n}\n\n/**\n * Converts ModelMessages to LangChain BaseMessage objects.\n *\n * @param modelMessages - Array of ModelMessage objects from convertToModelMessages.\n * @returns Array of LangChain BaseMessage objects.\n */\nexport function convertModelMessages(\n modelMessages: ModelMessage[],\n): BaseMessage[] {\n const result: BaseMessage[] = [];\n\n for (const message of modelMessages) {\n switch (message.role) {\n case 'tool': {\n // Tool messages contain an array of tool results\n for (const item of message.content) {\n if (isToolResultPart(item)) {\n result.push(convertToolResultPart(item));\n }\n }\n break;\n }\n\n case 'assistant': {\n result.push(convertAssistantContent(message.content));\n break;\n }\n\n case 'system': {\n result.push(new SystemMessage({ content: message.content }));\n break;\n }\n\n case 'user': {\n result.push(convertUserContent(message.content));\n break;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Type guard to check if a value is a streamEvents event object.\n * streamEvents produces objects with `event` and `data` properties.\n *\n * @param value - The value to check.\n * @returns True if the value is a streamEvents event object.\n */\nfunction isStreamEventsEvent(\n value: unknown,\n): value is { event: string; data: Record<string, unknown> } {\n if (value == null || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n // Check for event property being a string\n if (!('event' in obj) || typeof obj.event !== 'string') return false;\n // Check for data property being an object (but allow null/undefined)\n if (!('data' in obj)) return false;\n // data can be null in some events, treat as empty object\n return obj.data === null || typeof obj.data === 'object';\n}\n\n/**\n * Processes a streamEvents event and emits UI message chunks.\n *\n * @param event - The streamEvents event to process.\n * @param state - The state for tracking stream progress.\n * @param controller - The controller to emit UI message chunks.\n */\nfunction processStreamEventsEvent(\n event: {\n event: string;\n data: Record<string, unknown> | null;\n run_id?: string;\n name?: string;\n },\n state: {\n started: boolean;\n messageId: string;\n reasoningStarted: boolean;\n textStarted: boolean;\n textMessageId: string | null;\n reasoningMessageId: string | null;\n emittedSourceIds: Set<string>;\n },\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n /**\n * Capture run_id from event level if available (streamEvents v2 format)\n */\n if (event.run_id && !state.started) {\n state.messageId = event.run_id;\n }\n\n /**\n * Skip events with null/undefined data\n */\n if (!event.data) return;\n\n switch (event.event) {\n case 'on_chat_model_start': {\n /**\n * End the previous model turn's reasoning stream before a new LLM invocation.\n * Without this, reasoning-delta chunks from the next turn could attach to the\n * wrong reasoning part in the UI message stream.\n */\n if (state.reasoningStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id:\n state.reasoningMessageId != null\n ? state.reasoningMessageId\n : state.messageId,\n });\n state.reasoningStarted = false;\n state.reasoningMessageId = null;\n }\n\n /**\n * End the previous model turn's text stream before a new LLM invocation.\n * The streamEvents adapter otherwise leaves `textStarted` true, so all later\n * text-delta chunks append to the first text part — tools still grow the parts\n * array, which breaks chronological order in the assistant message.\n */\n if (state.textStarted) {\n controller.enqueue({\n type: 'text-end',\n id:\n state.textMessageId != null ? state.textMessageId : state.messageId,\n });\n state.textStarted = false;\n state.textMessageId = null;\n }\n\n /**\n * Handle model start — capture message metadata if available.\n * `run_id` is on the event in streamEvents v2; fall back to `data` for compatibility.\n */\n const runId = event.run_id || (event.data.run_id as string | undefined);\n if (runId) {\n state.messageId = runId;\n }\n break;\n }\n\n case 'on_chat_model_stream': {\n /**\n * Handle streaming token chunks\n */\n const chunk = event.data.chunk;\n if (chunk && typeof chunk === 'object') {\n /**\n * Get message ID from chunk if available\n */\n const chunkId = (chunk as { id?: string }).id;\n if (chunkId) {\n state.messageId = chunkId;\n }\n\n /**\n * Handle reasoning content from contentBlocks\n */\n const reasoning = extractReasoningFromContentBlocks(chunk);\n if (reasoning) {\n if (!state.reasoningStarted) {\n // Track the ID used for reasoning-start to ensure reasoning-end uses the same ID\n state.reasoningMessageId = state.messageId;\n controller.enqueue({\n type: 'reasoning-start',\n id: state.messageId,\n });\n state.reasoningStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: state.reasoningMessageId ?? state.messageId,\n });\n }\n\n /**\n * Extract text content from chunk\n */\n const content = (chunk as { content?: unknown }).content;\n const text =\n typeof content === 'string'\n ? content\n : Array.isArray(content)\n ? content\n .filter(\n (c): c is { type: 'text'; text: string } =>\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n c.type === 'text',\n )\n .map(c => c.text)\n .join('')\n : '';\n\n if (text) {\n /**\n * If reasoning was streamed before text, close reasoning first\n */\n if (state.reasoningStarted && !state.textStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id: state.reasoningMessageId ?? state.messageId,\n });\n state.reasoningStarted = false;\n }\n\n if (!state.textStarted) {\n // Track the ID used for text-start to ensure text-end uses the same ID\n state.textMessageId = state.messageId;\n controller.enqueue({ type: 'text-start', id: state.messageId });\n state.textStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'text-delta',\n delta: text,\n id: state.textMessageId ?? state.messageId,\n });\n }\n\n const citations = extractCitationsFromContentBlocks(chunk);\n if (citations.length > 0) {\n emitSourceChunks(\n citations,\n state.messageId,\n state.emittedSourceIds,\n controller,\n );\n }\n }\n break;\n }\n\n case 'on_tool_start': {\n /**\n * Handle tool call start\n * run_id and name are at event level in v2, check data for backwards compatibility\n */\n const runId = event.run_id || (event.data.run_id as string | undefined);\n const name = event.name || (event.data.name as string | undefined);\n\n if (runId && name) {\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId: runId,\n toolName: name,\n dynamic: true,\n });\n }\n break;\n }\n\n case 'on_tool_end': {\n /**\n * Handle tool call end\n * run_id is at event level in v2, check data for backwards compatibility\n */\n const runId = event.run_id || (event.data.run_id as string | undefined);\n const output = event.data.output;\n\n if (runId) {\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId: runId,\n output,\n });\n }\n break;\n }\n }\n}\n\n/**\n * Checks if an error is an abort error.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof Error) {\n return (\n error.name === 'AbortError' ||\n (error instanceof DOMException && error.name === 'AbortError')\n );\n }\n return false;\n}\n\n/**\n * Converts a LangChain stream to an AI SDK UIMessageStream.\n *\n * This function automatically detects the stream type and handles:\n * - Direct model streams (AsyncIterable from `model.stream()`)\n * - LangGraph streams (ReadableStream with `streamMode: ['values', 'messages']`)\n * - streamEvents streams (from `agent.streamEvents()` or `model.streamEvents()`)\n *\n * @param stream - A stream from LangChain model.stream(), graph.stream(), or streamEvents().\n * @param callbacks - Optional callbacks for stream lifecycle events.\n * @returns A ReadableStream of UIMessageChunk objects.\n *\n * @example\n * ```ts\n * // With a direct model stream\n * const model = new ChatOpenAI({ model: 'gpt-4o-mini' });\n * const stream = await model.stream(messages);\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream(stream),\n * });\n *\n * // With a LangGraph stream\n * const graphStream = await graph.stream(\n * { messages },\n * { streamMode: ['values', 'messages'] }\n * );\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream(graphStream),\n * });\n *\n * // With streamEvents\n * const streamEvents = agent.streamEvents(\n * { messages },\n * { version: \"v2\" }\n * );\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream(streamEvents),\n * });\n *\n * // With callbacks for LangGraph state\n * const graphStream = await graph.stream(\n * { messages },\n * { streamMode: ['values', 'messages'] }\n * );\n * return createUIMessageStreamResponse({\n * stream: toUIMessageStream<MyStateType>(graphStream, {\n * onFinish: async (finalState) => {\n * if (finalState) {\n * await saveToDatabase(finalState);\n * }\n * },\n * onError: (error) => console.error('Stream failed:', error),\n * onAbort: () => console.log('Stream aborted'),\n * }),\n * });\n * ```\n */\nexport function toUIMessageStream<TState = unknown>(\n stream: AsyncIterable<AIMessageChunk> | ReadableStream,\n callbacks?: StreamCallbacks<TState>,\n): ReadableStream<UIMessageChunk> {\n /**\n * Track text chunks for onFinal callback\n */\n const textChunks: string[] = [];\n\n /** Last LangGraph values event data for onFinish callback */\n let lastValuesData: TState | undefined;\n\n /**\n * State for model stream handling\n */\n const modelState = {\n started: false,\n messageId: 'langchain-msg-1',\n reasoningStarted: false,\n textStarted: false,\n /** Track the ID used for text-start to ensure text-end uses the same ID */\n textMessageId: null as string | null,\n /** Track the ID used for reasoning-start to ensure reasoning-end uses the same ID */\n reasoningMessageId: null as string | null,\n emittedSourceIds: new Set<string>(),\n };\n\n /**\n * State for LangGraph stream handling\n */\n const langGraphState: LangGraphEventState = {\n messageSeen: new Map(),\n messageConcat: new Map(),\n emittedToolCalls: new Set<string>(),\n emittedToolInputs: new Set<string>(),\n emittedImages: new Set<string>(),\n emittedReasoningIds: new Set<string>(),\n messageReasoningIds: new Map(),\n toolCallInfoByIndex: new Map(),\n currentStep: null as number | null,\n emittedToolCallsByKey: new Map<string, string>(),\n emittedSourceIds: new Set<string>(),\n };\n\n /**\n * Track detected stream type: null = not yet detected\n */\n let streamType: 'model' | 'langgraph' | 'streamEvents' | null = null;\n\n /**\n * Get async iterator from the stream (works for both AsyncIterable and ReadableStream)\n */\n const getAsyncIterator = (): AsyncIterator<unknown> => {\n if (Symbol.asyncIterator in stream) {\n return (stream as AsyncIterable<unknown>)[Symbol.asyncIterator]();\n }\n /**\n * For ReadableStream without Symbol.asyncIterator\n */\n const reader = (stream as ReadableStream).getReader();\n return {\n async next() {\n const { done, value } = await reader.read();\n return { done, value };\n },\n };\n };\n\n const iterator = getAsyncIterator();\n\n /**\n * Create a wrapper around the controller to intercept text chunks for callbacks\n */\n const createCallbackController = (\n originalController: ReadableStreamDefaultController<UIMessageChunk>,\n ): ReadableStreamDefaultController<UIMessageChunk> => {\n return {\n get desiredSize() {\n return originalController.desiredSize;\n },\n close: () => originalController.close(),\n error: (e?: unknown) => originalController.error(e),\n enqueue: (chunk: UIMessageChunk) => {\n /**\n * Intercept text-delta chunks for callbacks\n */\n if (callbacks && chunk.type === 'text-delta' && chunk.delta) {\n textChunks.push(chunk.delta);\n callbacks.onToken?.(chunk.delta);\n callbacks.onText?.(chunk.delta);\n }\n originalController.enqueue(chunk);\n },\n };\n };\n\n return new ReadableStream<UIMessageChunk>({\n async start(controller) {\n await callbacks?.onStart?.();\n\n const wrappedController = createCallbackController(controller);\n controller.enqueue({ type: 'start' });\n\n try {\n while (true) {\n const { done, value } = await iterator.next();\n if (done) break;\n\n /**\n * Detect stream type on first value\n */\n if (streamType === null) {\n if (Array.isArray(value)) {\n streamType = 'langgraph';\n } else if (isStreamEventsEvent(value)) {\n streamType = 'streamEvents';\n } else {\n streamType = 'model';\n }\n }\n\n /**\n * Process based on detected type\n */\n if (streamType === 'model') {\n processModelChunk(\n value as AIMessageChunk,\n modelState,\n wrappedController,\n );\n } else if (streamType === 'streamEvents') {\n processStreamEventsEvent(\n value as {\n event: string;\n data: Record<string, unknown> | null;\n run_id?: string;\n name?: string;\n },\n modelState,\n wrappedController,\n );\n } else {\n const eventArray = value as unknown[];\n const [type, data] = parseLangGraphEvent(eventArray);\n\n if (type === 'values') {\n lastValuesData = data as TState;\n }\n\n processLangGraphEvent(\n eventArray,\n langGraphState,\n wrappedController,\n );\n }\n }\n\n /**\n * Finalize based on stream type\n */\n if (streamType === 'model' || streamType === 'streamEvents') {\n if (modelState.reasoningStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id: modelState.reasoningMessageId ?? modelState.messageId,\n });\n }\n if (modelState.textStarted) {\n /**\n * Use the same ID that was used for text-start\n */\n controller.enqueue({\n type: 'text-end',\n id: modelState.textMessageId ?? modelState.messageId,\n });\n }\n controller.enqueue({ type: 'finish' });\n } else if (streamType === 'langgraph') {\n /**\n * Close any open text/reasoning parts before finishing.\n * This handles streams without values events (e.g. streamMode: 'messages')\n * where the values handler never ran to emit *-end events.\n */\n for (const [id, seen] of langGraphState.messageSeen) {\n if (seen.text) {\n controller.enqueue({ type: 'text-end', id });\n }\n if (seen.reasoning) {\n controller.enqueue({ type: 'reasoning-end', id });\n }\n }\n\n /**\n * Emit finish-step if a step was started\n */\n if (langGraphState.currentStep !== null) {\n controller.enqueue({ type: 'finish-step' });\n }\n controller.enqueue({ type: 'finish' });\n }\n\n /**\n * Call onFinal callback with aggregated text\n */\n await callbacks?.onFinal?.(textChunks.join(''));\n await callbacks?.onFinish?.(lastValuesData);\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n\n await callbacks?.onFinal?.(textChunks.join(''));\n\n if (isAbortError(error)) {\n await callbacks?.onAbort?.();\n } else {\n await callbacks?.onError?.(errorObj);\n }\n\n controller.enqueue({\n type: 'error',\n errorText: errorObj.message,\n });\n } finally {\n controller.close();\n }\n },\n });\n}\n","import {\n AIMessage,\n HumanMessage,\n ToolMessage,\n AIMessageChunk,\n type ContentBlock,\n type ToolCall,\n type BaseMessage,\n type BaseMessageChunk,\n type ToolCallChunk,\n} from '@langchain/core/messages';\nimport type {\n UIMessageChunk,\n ToolResultPart,\n AssistantContent,\n UserContent,\n ProviderMetadata,\n JSONValue,\n} from 'ai';\n\nimport type {\n LangGraphEventState,\n LangGraphMessageSeen,\n ReasoningContentBlock,\n ThinkingContentBlock,\n GPT5ReasoningOutput,\n ImageGenerationOutput,\n NormalizedCitation,\n} from './types';\n\n/**\n * Parses a LangGraph event tuple into [type, data].\n * Handles both 2-element [type, data] and 3-element [namespace, type, data] formats.\n *\n * @param event - The raw LangGraph event array.\n * @returns A tuple of [type, data].\n */\nexport function parseLangGraphEvent(\n event: unknown[],\n): [type: unknown, data: unknown] {\n return event.length === 3 ? [event[1], event[2]] : [event[0], event[1]];\n}\n\n/**\n * Converts a ToolResultPart to a LangChain ToolMessage\n * @param block - The ToolResultPart to convert.\n * @returns The converted ToolMessage.\n */\nexport function convertToolResultPart(block: ToolResultPart): ToolMessage {\n const content = (() => {\n if (block.output.type === 'text' || block.output.type === 'error-text') {\n return block.output.value;\n }\n\n if (block.output.type === 'json' || block.output.type === 'error-json') {\n return JSON.stringify(block.output.value);\n }\n\n if (block.output.type === 'content') {\n return block.output.value\n .map(outputBlock => {\n if (outputBlock.type === 'text') {\n return outputBlock.text;\n }\n return '';\n })\n .join('');\n }\n\n return '';\n })();\n\n return new ToolMessage({\n tool_call_id: block.toolCallId,\n content,\n });\n}\n\n/**\n * Converts AssistantContent to LangChain AIMessage\n * @param content - The AssistantContent to convert.\n * @returns The converted AIMessage.\n */\nexport function convertAssistantContent(content: AssistantContent): AIMessage {\n if (typeof content === 'string') {\n return new AIMessage({ content });\n }\n\n const textParts: string[] = [];\n const toolCalls: Array<{\n id: string;\n name: string;\n args: Record<string, unknown>;\n }> = [];\n\n for (const part of content) {\n if (part.type === 'text') {\n textParts.push(part.text);\n } else if (part.type === 'tool-call') {\n toolCalls.push({\n id: part.toolCallId,\n name: part.toolName,\n args: part.input as Record<string, unknown>,\n });\n }\n }\n\n return new AIMessage({\n content: textParts.join(''),\n tool_calls: toolCalls.length > 0 ? toolCalls : undefined,\n });\n}\n\n/**\n * Helper to generate a default filename from mediaType\n */\nfunction getDefaultFilename(\n mediaType: string,\n prefix: string = 'file',\n): string {\n const fileExtension = mediaType.split('/')[1] || 'bin';\n return `${prefix}.${fileExtension}`;\n}\n\n/**\n * OpenAI-native content block type for images.\n * This format is passed through directly by ChatOpenAI to OpenAI's API.\n */\ntype OpenAIImageBlock = {\n type: 'image_url';\n image_url: {\n url: string;\n detail?: 'auto' | 'low' | 'high';\n };\n};\n\n/**\n * Content block type for HumanMessage that supports both text and OpenAI images.\n */\ntype HumanMessageContentBlock =\n | { type: 'text'; text: string }\n | OpenAIImageBlock\n | ContentBlock;\n\n/**\n * Converts UserContent to LangChain HumanMessage\n * @param content - The UserContent to convert.\n * @returns The converted HumanMessage.\n */\nexport function convertUserContent(content: UserContent): HumanMessage {\n if (typeof content === 'string') {\n return new HumanMessage({ content });\n }\n\n const contentBlocks: HumanMessageContentBlock[] = [];\n\n for (const part of content) {\n if (part.type === 'text') {\n contentBlocks.push({ type: 'text', text: part.text });\n } else if (part.type === 'image') {\n const imagePart = part as {\n type: 'image';\n image: string | Uint8Array | URL | ArrayBuffer;\n mediaType?: string;\n };\n\n /**\n * Use OpenAI's native image_url format which is passed through directly\n * handle URL objects\n */\n if (imagePart.image instanceof URL) {\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: imagePart.image.toString() },\n });\n } else if (typeof imagePart.image === 'string') {\n /**\n * Handle string (could be URL or base64)\n */\n /**\n * Check if it's a URL (including data: URLs)\n */\n if (\n imagePart.image.startsWith('http://') ||\n imagePart.image.startsWith('https://') ||\n imagePart.image.startsWith('data:')\n ) {\n /**\n * OpenAI accepts both http URLs and data URLs directly\n */\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: imagePart.image },\n });\n } else {\n /**\n * Assume base64 encoded data - wrap in data URL\n */\n const mimeType = imagePart.mediaType || 'image/png';\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: `data:${mimeType};base64,${imagePart.image}` },\n });\n }\n } else if (\n /**\n * Handle Uint8Array or ArrayBuffer (binary data)\n */\n imagePart.image instanceof Uint8Array ||\n imagePart.image instanceof ArrayBuffer\n ) {\n const bytes =\n imagePart.image instanceof ArrayBuffer\n ? new Uint8Array(imagePart.image)\n : imagePart.image;\n /**\n * Convert to base64 data URL\n */\n const base64 = btoa(String.fromCharCode(...bytes));\n const mimeType = imagePart.mediaType || 'image/png';\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: `data:${mimeType};base64,${base64}` },\n });\n }\n } else if (part.type === 'file') {\n const rawFilePart = part as {\n type: 'file';\n data:\n | string\n | Uint8Array\n | URL\n | ArrayBuffer\n | { type: 'data'; data: string | Uint8Array | ArrayBuffer }\n | { type: 'url'; url: URL }\n | { type: 'reference'; reference: Record<string, string> }\n | { type: 'text'; text: string };\n mediaType: string;\n filename?: string;\n };\n\n // Normalize tagged data shape into the legacy bare value this code expects.\n const normalizedData: string | Uint8Array | URL | ArrayBuffer = (() => {\n const data = rawFilePart.data;\n if (\n typeof data === 'object' &&\n data !== null &&\n !(data instanceof URL) &&\n !(data instanceof Uint8Array) &&\n !(data instanceof ArrayBuffer) &&\n 'type' in data\n ) {\n switch (data.type) {\n case 'data':\n return data.data;\n case 'url':\n return data.url;\n case 'text':\n return data.text;\n default:\n return '';\n }\n }\n return data as string | Uint8Array | URL | ArrayBuffer;\n })();\n\n const filePart = {\n type: 'file' as const,\n data: normalizedData,\n mediaType: rawFilePart.mediaType,\n filename: rawFilePart.filename,\n };\n\n /**\n * Check if this is an image file - if so, use OpenAI's image_url format\n */\n const isImage = filePart.mediaType?.startsWith('image/');\n\n if (isImage) {\n /**\n * Handle image files using OpenAI's native image_url format\n */\n if (filePart.data instanceof URL) {\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: filePart.data.toString() },\n });\n } else if (typeof filePart.data === 'string') {\n /**\n * URLs (including data URLs) can be passed directly\n */\n if (\n filePart.data.startsWith('http://') ||\n filePart.data.startsWith('https://') ||\n filePart.data.startsWith('data:')\n ) {\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: filePart.data },\n });\n } else {\n /**\n * Assume base64 - wrap in data URL\n */\n contentBlocks.push({\n type: 'image_url',\n image_url: {\n url: `data:${filePart.mediaType};base64,${filePart.data}`,\n },\n });\n }\n } else if (\n filePart.data instanceof Uint8Array ||\n filePart.data instanceof ArrayBuffer\n ) {\n const bytes =\n filePart.data instanceof ArrayBuffer\n ? new Uint8Array(filePart.data)\n : filePart.data;\n const base64 = btoa(String.fromCharCode(...bytes));\n contentBlocks.push({\n type: 'image_url',\n image_url: { url: `data:${filePart.mediaType};base64,${base64}` },\n });\n }\n } else {\n // Handle non-image files using LangChain's ContentBlock format\n const filename =\n filePart.filename || getDefaultFilename(filePart.mediaType, 'file');\n\n if (filePart.data instanceof URL) {\n contentBlocks.push({\n type: 'file',\n url: filePart.data.toString(),\n mimeType: filePart.mediaType,\n filename,\n });\n } else if (typeof filePart.data === 'string') {\n if (\n filePart.data.startsWith('http://') ||\n filePart.data.startsWith('https://')\n ) {\n contentBlocks.push({\n type: 'file',\n url: filePart.data,\n mimeType: filePart.mediaType,\n filename,\n });\n } else if (filePart.data.startsWith('data:')) {\n const matches = filePart.data.match(/^data:([^;]+);base64,(.+)$/);\n if (matches) {\n contentBlocks.push({\n type: 'file',\n data: matches[2],\n mimeType: matches[1],\n filename,\n });\n } else {\n contentBlocks.push({\n type: 'file',\n url: filePart.data,\n mimeType: filePart.mediaType,\n filename,\n });\n }\n } else {\n contentBlocks.push({\n type: 'file',\n data: filePart.data,\n mimeType: filePart.mediaType,\n filename,\n });\n }\n } else if (\n filePart.data instanceof Uint8Array ||\n filePart.data instanceof ArrayBuffer\n ) {\n const bytes =\n filePart.data instanceof ArrayBuffer\n ? new Uint8Array(filePart.data)\n : filePart.data;\n const base64 = btoa(String.fromCharCode(...bytes));\n contentBlocks.push({\n type: 'file',\n data: base64,\n mimeType: filePart.mediaType,\n filename,\n });\n }\n }\n }\n }\n\n /**\n * If we only have text parts, join them as a simple string for efficiency\n */\n if (contentBlocks.every(block => block.type === 'text')) {\n return new HumanMessage({\n content: contentBlocks\n .map(block => (block as unknown as { text: string }).text)\n .join(''),\n });\n }\n\n return new HumanMessage({ content: contentBlocks });\n}\n\n/**\n * Helper to check if a content item is a ToolResultPart\n * @param item - The item to check.\n * @returns True if the item is a ToolResultPart, false otherwise.\n */\nexport function isToolResultPart(item: unknown): item is ToolResultPart {\n return (\n item != null &&\n typeof item === 'object' &&\n 'type' in item &&\n (item as { type: string }).type === 'tool-result'\n );\n}\n\n/**\n * Processes a model stream chunk and emits UI message chunks.\n * @param chunk - The AIMessageChunk to process.\n * @param state - The state of the model stream.\n * @param controller - The controller to use to emit the UI message chunks.\n */\nexport function processModelChunk(\n chunk: AIMessageChunk,\n state: {\n started: boolean;\n messageId: string;\n reasoningStarted?: boolean;\n textStarted?: boolean;\n /** Track the ID used for reasoning-start to ensure reasoning-end uses the same ID */\n reasoningMessageId?: string | null;\n /** Track the ID used for text-start to ensure text-end uses the same ID */\n textMessageId?: string | null;\n emittedImages?: Set<string>;\n emittedSourceIds?: Set<string>;\n },\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n /**\n * Initialize emittedImages set if not present\n */\n if (!state.emittedImages) {\n state.emittedImages = new Set<string>();\n }\n\n if (!state.emittedSourceIds) {\n state.emittedSourceIds = new Set<string>();\n }\n\n /**\n * Get the message ID from the chunk if available\n */\n if (chunk.id) {\n state.messageId = chunk.id;\n }\n\n /**\n * Handle image generation outputs from additional_kwargs.tool_outputs\n */\n const chunkObj = chunk as unknown as Record<string, unknown>;\n const additionalKwargs = chunkObj.additional_kwargs as\n | Record<string, unknown>\n | undefined;\n const imageOutputs = extractImageOutputs(additionalKwargs);\n\n for (const imageOutput of imageOutputs) {\n /**\n * Only emit if we have image data and haven't emitted this image yet\n */\n if (imageOutput.result && !state.emittedImages.has(imageOutput.id)) {\n state.emittedImages.add(imageOutput.id);\n\n /**\n * Emit as a file part using proper AI SDK multimodal format\n */\n const mediaType = `image/${imageOutput.output_format || 'png'}`;\n controller.enqueue({\n type: 'file',\n mediaType,\n url: `data:${mediaType};base64,${imageOutput.result}`,\n });\n state.started = true;\n }\n }\n\n /**\n * Handle reasoning content from contentBlocks or response_metadata.output\n * For direct model streams, we check both sources since there's no values event\n * that would cause duplication (unlike LangGraph streams)\n */\n const reasoning =\n extractReasoningFromContentBlocks(chunk) ||\n extractReasoningFromValuesMessage(chunk);\n if (reasoning) {\n if (!state.reasoningStarted) {\n // Track the ID used for reasoning-start to ensure subsequent chunks use the same ID\n state.reasoningMessageId = state.messageId;\n controller.enqueue({ type: 'reasoning-start', id: state.messageId });\n state.reasoningStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: state.reasoningMessageId ?? state.messageId,\n });\n }\n\n /**\n * Extract text content from AIMessageChunk\n */\n const text =\n typeof chunk.content === 'string'\n ? chunk.content\n : Array.isArray(chunk.content)\n ? chunk.content\n .filter(\n (c): c is { type: 'text'; text: string } =>\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n c.type === 'text',\n )\n .map(c => c.text)\n .join('')\n : '';\n\n if (text) {\n /**\n * If reasoning was streamed before text, close reasoning first\n */\n if (state.reasoningStarted && !state.textStarted) {\n controller.enqueue({\n type: 'reasoning-end',\n id: state.reasoningMessageId ?? state.messageId,\n });\n state.reasoningStarted = false;\n }\n\n if (!state.textStarted) {\n // Track the ID used for text-start to ensure subsequent chunks use the same ID\n state.textMessageId = state.messageId;\n controller.enqueue({ type: 'text-start', id: state.messageId });\n state.textStarted = true;\n state.started = true;\n }\n controller.enqueue({\n type: 'text-delta',\n delta: text,\n id: state.textMessageId ?? state.messageId,\n });\n }\n\n const citations = extractCitationsFromContentBlocks(chunk);\n if (citations.length > 0) {\n emitSourceChunks(\n citations,\n state.messageId,\n state.emittedSourceIds,\n controller,\n );\n }\n}\n\n/**\n * Checks if a message is a plain object (not a LangChain class instance).\n * LangChain class instances have a _getType method.\n *\n * @param msg - The message to check.\n * @returns True if the message is a plain object, false otherwise.\n */\nexport function isPlainMessageObject(msg: unknown): boolean {\n if (msg == null || typeof msg !== 'object') return false;\n /**\n * LangChain class instances have _getType method\n */\n return typeof (msg as { _getType?: unknown })._getType !== 'function';\n}\n\n/**\n * Extracts the actual message ID from a message.\n * Handles both class instances (msg.id) and serialized LangChain messages (msg.kwargs.id).\n *\n * @param msg - The message to extract the ID from.\n * @returns The message ID string, or undefined if not found.\n */\nexport function getMessageId(msg: unknown): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n const msgObj = msg as Record<string, unknown>;\n\n /**\n * For class instances, id is directly on the object\n */\n if (typeof msgObj.id === 'string') {\n return msgObj.id;\n }\n\n /**\n * For serialized LangChain messages, id is in kwargs\n */\n if (\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ) {\n const kwargs = msgObj.kwargs as Record<string, unknown>;\n if (typeof kwargs.id === 'string') {\n return kwargs.id;\n }\n }\n\n return undefined;\n}\n\n/**\n * Checks if a message is an AI message chunk (works for both class instances and plain objects).\n * For class instances, only AIMessageChunk is matched (not AIMessage).\n * For plain objects from RemoteGraph API, matches type === 'ai' (TypeScript langchain-core)\n * or type === 'AIMessageChunk' (Python langchain-core).\n * For serialized LangChain messages, matches type === 'constructor' with AIMessageChunk in id path.\n *\n * @param msg - The message to check.\n * @returns True if the message is an AI message chunk, false otherwise.\n */\nexport function isAIMessageChunk(\n msg: unknown,\n): msg is AIMessageChunk & { type?: string; content?: string } {\n /**\n * Actual AIMessageChunk class instance\n */\n if (AIMessageChunk.isInstance(msg)) return true;\n /**\n * Plain object from RemoteGraph API (not a LangChain class instance)\n */\n if (isPlainMessageObject(msg)) {\n const messageRecord = msg as Record<string, unknown>;\n /**\n * Direct type from RemoteGraph format. TypeScript langchain-core emits\n * type === 'ai'; Python langchain-core emits type === 'AIMessageChunk'.\n */\n if (\n 'type' in messageRecord &&\n (messageRecord.type === 'ai' || messageRecord.type === 'AIMessageChunk')\n ) {\n return true;\n }\n /**\n * Serialized LangChain message format: { lc: 1, type: \"constructor\", id: [\"...\", \"AIMessageChunk\"], kwargs: {...} }\n */\n if (\n messageRecord.type === 'constructor' &&\n Array.isArray(messageRecord.id) &&\n (messageRecord.id.includes('AIMessageChunk') ||\n messageRecord.id.includes('AIMessage'))\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Checks if a message is a Tool message (works for both class instances and plain objects).\n *\n * @param msg - The message to check.\n * @returns True if the message is a Tool message, false otherwise.\n */\nexport function isToolMessageType(\n msg: unknown,\n): msg is ToolMessage & { type?: string; tool_call_id?: string } {\n if (ToolMessage.isInstance(msg)) return true;\n /**\n * Plain object from RemoteGraph API (not a LangChain class instance)\n */\n if (isPlainMessageObject(msg)) {\n const messageRecord = msg as Record<string, unknown>;\n /**\n * Direct type === 'tool' (RemoteGraph format)\n */\n if ('type' in messageRecord && messageRecord.type === 'tool') return true;\n /**\n * Serialized LangChain message format\n */\n if (\n messageRecord.type === 'constructor' &&\n Array.isArray(messageRecord.id) &&\n messageRecord.id.includes('ToolMessage')\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Gets text content from a message (works for both class instances and plain objects).\n *\n * @param msg - The message to get the text from.\n * @returns The text content of the message.\n */\nexport function getMessageText(msg: unknown): string {\n if (AIMessageChunk.isInstance(msg)) {\n return msg.text ?? '';\n }\n\n if (msg == null || typeof msg !== 'object') return '';\n\n const msgObj = msg as Record<string, unknown>;\n\n // For serialized LangChain messages, content is in kwargs\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n if ('content' in dataSource) {\n const content = dataSource.content;\n /**\n * Handle string content\n */\n if (typeof content === 'string') {\n return content;\n }\n /**\n * Handle array of content blocks (e.g., [{ type: 'text', text: 'The', index: 0 }])\n */\n if (Array.isArray(content)) {\n return content\n .filter(\n (block): block is { type: 'text'; text: string } =>\n block != null &&\n typeof block === 'object' &&\n block.type === 'text' &&\n typeof block.text === 'string',\n )\n .map(block => block.text)\n .join('');\n }\n return '';\n }\n return '';\n}\n\n/**\n * Checks if an object is a reasoning content block\n *\n * @param obj - The object to check.\n * @returns True if the object is a reasoning content block, false otherwise.\n */\nexport function isReasoningContentBlock(\n obj: unknown,\n): obj is ReasoningContentBlock {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'reasoning' &&\n 'reasoning' in obj &&\n typeof (obj as { reasoning: unknown }).reasoning === 'string'\n );\n}\n\n/**\n * Checks if an object is a thinking content block (Anthropic-style)\n *\n * @param obj - The object to check.\n * @returns True if the object is a thinking content block, false otherwise.\n */\nexport function isThinkingContentBlock(\n obj: unknown,\n): obj is ThinkingContentBlock {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'thinking' &&\n 'thinking' in obj &&\n typeof (obj as { thinking: unknown }).thinking === 'string'\n );\n}\n\n/**\n * Checks if an object is a GPT-5 reasoning output block\n */\nfunction isGPT5ReasoningOutput(obj: unknown): obj is GPT5ReasoningOutput {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'reasoning' &&\n 'summary' in obj &&\n Array.isArray((obj as { summary: unknown }).summary)\n );\n}\n\n/**\n * Extracts the reasoning block ID from a message (GPT-5 format).\n * This ID is consistent across streaming and values events.\n * Handles both class instances and serialized LangChain message objects.\n *\n * @param msg - The message to extract the reasoning ID from.\n * @returns The reasoning block ID if found, undefined otherwise.\n */\nexport function extractReasoningId(msg: unknown): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n // For serialized LangChain messages, the data is in kwargs\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // Check additional_kwargs.reasoning.id (GPT-5 streaming format)\n const additionalKwargs = (\n kwargs as { additional_kwargs?: { reasoning?: { id?: string } } }\n ).additional_kwargs;\n if (additionalKwargs?.reasoning?.id) {\n return additionalKwargs.reasoning.id;\n }\n\n // Check response_metadata.output for reasoning block ID (GPT-5 final format)\n const responseMetadata = (\n kwargs as { response_metadata?: { output?: unknown[] } }\n ).response_metadata;\n if (responseMetadata && Array.isArray(responseMetadata.output)) {\n for (const item of responseMetadata.output) {\n if (isGPT5ReasoningOutput(item)) {\n return item.id;\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Extracts reasoning content from contentBlocks or additional_kwargs.reasoning.summary\n *\n * IMPORTANT: This function is designed for STREAMING chunks where content is delta-based.\n * It does NOT extract from response_metadata.output because that contains accumulated\n * content (not deltas) and would cause duplication during streaming.\n *\n * For non-streaming/values events, use extractReasoningFromValuesMessage instead.\n *\n * Handles both class instances and serialized LangChain message objects.\n *\n * @param msg - The message to extract reasoning from.\n * @returns The reasoning text if found, undefined otherwise.\n */\nexport function extractReasoningFromContentBlocks(\n msg: unknown,\n): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n // For serialized LangChain messages, the data is in kwargs\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // Check contentBlocks (Anthropic-style) - highest priority\n const contentBlocks = (kwargs as { contentBlocks?: unknown[] }).contentBlocks;\n if (Array.isArray(contentBlocks)) {\n const reasoningParts: string[] = [];\n for (const block of contentBlocks) {\n if (isReasoningContentBlock(block)) {\n reasoningParts.push(block.reasoning);\n } else if (isThinkingContentBlock(block)) {\n reasoningParts.push(block.thinking);\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n // Check additional_kwargs.reasoning.summary (GPT-5 streaming format)\n // This contains DELTA content during streaming, not accumulated content\n // Format can be either { type: \"summary_text\", text: \"...\" } or just { text: \"...\" }\n const additionalKwargs = (\n kwargs as { additional_kwargs?: { reasoning?: { summary?: unknown[] } } }\n ).additional_kwargs;\n if (\n additionalKwargs?.reasoning &&\n Array.isArray(additionalKwargs.reasoning.summary)\n ) {\n const reasoningParts: string[] = [];\n for (const summaryItem of additionalKwargs.reasoning.summary) {\n if (\n typeof summaryItem === 'object' &&\n summaryItem !== null &&\n 'text' in summaryItem &&\n typeof (summaryItem as { text: unknown }).text === 'string'\n ) {\n reasoningParts.push((summaryItem as { text: string }).text);\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n return undefined;\n}\n\n/**\n * Extracts reasoning content from a values event message.\n * This checks response_metadata.output which contains the full accumulated reasoning.\n *\n * @param msg - The message to extract reasoning from.\n * @returns The reasoning text if found, undefined otherwise.\n */\nexport function extractReasoningFromValuesMessage(\n msg: unknown,\n): string | undefined {\n if (msg == null || typeof msg !== 'object') return undefined;\n\n // For serialized LangChain messages, the data is in kwargs\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // Check response_metadata.output (GPT-5 final style) - for values events\n const responseMetadata = (\n kwargs as { response_metadata?: { output?: unknown[] } }\n ).response_metadata;\n if (responseMetadata && Array.isArray(responseMetadata.output)) {\n const reasoningParts: string[] = [];\n for (const item of responseMetadata.output) {\n if (isGPT5ReasoningOutput(item)) {\n // Extract text from summary array - handles both { type: \"summary_text\", text } and { text } formats\n for (const summaryItem of item.summary) {\n if (typeof summaryItem === 'object' && summaryItem !== null) {\n const text = (summaryItem as { text?: string }).text;\n if (typeof text === 'string' && text) {\n reasoningParts.push(text);\n }\n }\n }\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n // Also check additional_kwargs.reasoning.summary as fallback\n const additionalKwargs = (\n kwargs as { additional_kwargs?: { reasoning?: { summary?: unknown[] } } }\n ).additional_kwargs;\n if (\n additionalKwargs?.reasoning &&\n Array.isArray(additionalKwargs.reasoning.summary)\n ) {\n const reasoningParts: string[] = [];\n for (const summaryItem of additionalKwargs.reasoning.summary) {\n if (\n typeof summaryItem === 'object' &&\n summaryItem !== null &&\n 'text' in summaryItem &&\n typeof (summaryItem as { text: unknown }).text === 'string'\n ) {\n reasoningParts.push((summaryItem as { text: string }).text);\n }\n }\n if (reasoningParts.length > 0) {\n return reasoningParts.join('');\n }\n }\n\n return undefined;\n}\n\nexport function isCitationContentBlock(\n obj: unknown,\n): obj is ContentBlock.Citation {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: unknown }).type === 'citation'\n );\n}\n\n/**\n * LangChain Core standardizes citations as `{ type: 'citation', ... }` entries in\n * the `annotations` array of a text content block. The text extractors elsewhere\n * in this file only read the `text` field, so without this the citations would be\n * dropped instead of surfaced as AI SDK source parts.\n *\n * Handles both class instances and serialized LangChain message objects.\n */\nexport function extractCitationsFromContentBlocks(\n msg: unknown,\n): NormalizedCitation[] {\n if (msg == null || typeof msg !== 'object') return [];\n\n const msgObj = msg as Record<string, unknown>;\n const kwargs =\n msgObj.kwargs && typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n // `contentBlocks` is the normalized view derived from `content`; reading both\n // would double-count annotations, so prefer it and only fall back to `content`.\n const contentBlocks = (kwargs as { contentBlocks?: unknown }).contentBlocks;\n const content = (kwargs as { content?: unknown }).content;\n const blockSources: unknown[] = Array.isArray(contentBlocks)\n ? contentBlocks\n : Array.isArray(content)\n ? content\n : [];\n\n const citations: NormalizedCitation[] = [];\n\n for (const block of blockSources) {\n if (block == null || typeof block !== 'object') continue;\n\n const annotations = (block as { annotations?: unknown }).annotations;\n if (!Array.isArray(annotations)) continue;\n\n for (const annotation of annotations) {\n if (!isCitationContentBlock(annotation)) continue;\n\n citations.push({\n url: annotation.url,\n title: annotation.title,\n source: annotation.source,\n citedText: annotation.citedText,\n startIndex: annotation.startIndex,\n endIndex: annotation.endIndex,\n });\n }\n }\n\n return citations;\n}\n\nfunction buildSourceProviderMetadata(\n citation: NormalizedCitation,\n): ProviderMetadata | undefined {\n const langchain: Record<string, JSONValue> = {};\n\n if (typeof citation.citedText === 'string') {\n langchain.citedText = citation.citedText;\n }\n if (typeof citation.startIndex === 'number') {\n langchain.startIndex = citation.startIndex;\n }\n if (typeof citation.endIndex === 'number') {\n langchain.endIndex = citation.endIndex;\n }\n if (typeof citation.source === 'string') {\n langchain.source = citation.source;\n }\n\n if (Object.keys(langchain).length === 0) {\n return undefined;\n }\n\n return { langchain };\n}\n\n/**\n * Emits AI SDK source chunks (`source-url` / `source-document`) for the given\n * citations.\n *\n * Citations with a `url` become `source-url` parts keyed by that url; the rest\n * become `source-document` parts. Citation metadata that the source parts cannot\n * represent natively (`citedText`, `startIndex`, `endIndex`, `source`) is preserved\n * under `providerMetadata.langchain`.\n *\n * `emittedSourceIds` dedupes across the LangGraph messages and values events, which\n * can each surface the same citation. URL-less citations are keyed by their content\n * rather than position, so a differing citation subset/order between those two events\n * does not cause an id collision.\n */\nexport function emitSourceChunks(\n citations: NormalizedCitation[],\n messageId: string,\n emittedSourceIds: Set<string>,\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n for (const citation of citations) {\n if (citation.url) {\n if (emittedSourceIds.has(citation.url)) continue;\n emittedSourceIds.add(citation.url);\n\n const providerMetadata = buildSourceProviderMetadata(citation);\n controller.enqueue({\n type: 'source-url',\n sourceId: citation.url,\n url: citation.url,\n ...(citation.title ? { title: citation.title } : {}),\n ...(providerMetadata ? { providerMetadata } : {}),\n });\n continue;\n }\n\n // A citation with no url and no human-readable label carries no information a\n // UI could render, so skip it rather than emit a placeholder source.\n const title = citation.title ?? citation.source;\n if (!title) continue;\n\n const sourceId = `${messageId}:${title}:${citation.citedText ?? ''}:${citation.startIndex ?? ''}:${citation.endIndex ?? ''}`;\n if (emittedSourceIds.has(sourceId)) continue;\n emittedSourceIds.add(sourceId);\n\n const providerMetadata = buildSourceProviderMetadata(citation);\n controller.enqueue({\n type: 'source-document',\n sourceId,\n mediaType: 'text/plain',\n title,\n ...(providerMetadata ? { providerMetadata } : {}),\n });\n }\n}\n\n/**\n * Checks if an object is an image generation output\n *\n * @param obj - The object to check.\n * @returns True if the object is an image generation output, false otherwise.\n */\nexport function isImageGenerationOutput(\n obj: unknown,\n): obj is ImageGenerationOutput {\n return (\n obj != null &&\n typeof obj === 'object' &&\n 'type' in obj &&\n (obj as { type: string }).type === 'image_generation_call'\n );\n}\n\n/**\n * Extracts image generation outputs from `additional_kwargs`\n *\n * @param additionalKwargs - The additional kwargs to extract the image generation outputs from.\n * @returns The image generation outputs.\n */\nexport function extractImageOutputs(\n additionalKwargs: Record<string, unknown> | undefined,\n): ImageGenerationOutput[] {\n if (!additionalKwargs) return [];\n\n const toolOutputs = additionalKwargs.tool_outputs;\n if (!Array.isArray(toolOutputs)) return [];\n\n return toolOutputs.filter(isImageGenerationOutput);\n}\n\nfunction formatToolError(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === 'string') return error;\n\n try {\n const serialized = JSON.stringify(error);\n return serialized ?? String(error);\n } catch {\n return String(error);\n }\n}\n\n/**\n * Returns per-message bookkeeping, creating it on first use.\n * Map keys keep remote-controlled message IDs like \"__proto__\" from touching Object.prototype.\n */\nfunction getOrCreateMessageSeen(\n messageSeen: LangGraphEventState['messageSeen'],\n msgId: string,\n): LangGraphMessageSeen {\n let seen = messageSeen.get(msgId);\n if (!seen) {\n seen = {};\n messageSeen.set(msgId, seen);\n }\n return seen;\n}\n\n/**\n * Returns the per-index tool call metadata for a message, creating it on first use.\n * Later streamed chunks use this to recover tool call IDs/names from earlier chunks.\n */\nfunction getOrCreateToolCallInfoByIndex(\n toolCallInfoByIndex: LangGraphEventState['toolCallInfoByIndex'],\n msgId: string,\n): Map<number, { id: string; name: string }> {\n let toolCallInfo = toolCallInfoByIndex.get(msgId);\n if (!toolCallInfo) {\n toolCallInfo = new Map();\n toolCallInfoByIndex.set(msgId, toolCallInfo);\n }\n return toolCallInfo;\n}\n\n/**\n * Processes a LangGraph event and emits UI message chunks.\n *\n * @param event - The event to process.\n * @param state - The state of the LangGraph event.\n * @param controller - The controller to use to emit the UI message chunks.\n */\nexport function processLangGraphEvent(\n event: unknown[],\n state: LangGraphEventState,\n controller: ReadableStreamDefaultController<UIMessageChunk>,\n): void {\n const {\n messageSeen,\n messageConcat,\n emittedToolCalls,\n emittedImages,\n emittedReasoningIds,\n messageReasoningIds,\n toolCallInfoByIndex,\n emittedToolCallsByKey,\n emittedToolInputs,\n } = state;\n const [type, data] = parseLangGraphEvent(event);\n\n switch (type) {\n case 'custom': {\n /**\n * Extract custom event type from the data's 'type' field if present.\n * This allows users to emit custom events like:\n * writer({ type: 'progress', value: 50 }) -> { type: 'data-progress', data: {...} }\n * writer({ type: 'status', message: '...' }) -> { type: 'data-status', data: {...} }\n * writer({ key: 'value' }) -> { type: 'data-custom', data: {...} } (fallback)\n *\n * The 'id' field can be used to make parts persistent and updateable.\n * Parts with an 'id' are NOT transient (added to message.parts).\n * Parts without an 'id' are transient (only passed to onData callback).\n */\n let customTypeName = 'custom';\n let partId: string | undefined;\n\n if (data != null && typeof data === 'object' && !Array.isArray(data)) {\n const dataObj = data as Record<string, unknown>;\n if (typeof dataObj.type === 'string' && dataObj.type) {\n customTypeName = dataObj.type;\n }\n if (typeof dataObj.id === 'string' && dataObj.id) {\n partId = dataObj.id;\n }\n }\n\n controller.enqueue({\n type: `data-${customTypeName}` as `data-${string}`,\n id: partId,\n transient: partId == null,\n data,\n });\n break;\n }\n\n case 'messages': {\n const [rawMsg, metadata] = data as [\n BaseMessageChunk | BaseMessage | undefined,\n Record<string, unknown> | undefined,\n ];\n\n const msg = rawMsg;\n const msgId = getMessageId(msg);\n\n if (!msgId) return;\n\n /**\n * Track LangGraph step changes and emit start-step/finish-step events.\n * Before emitting finish-step, close any open text/reasoning parts so\n * the client does not receive orphaned deltas after its\n * activeReasoningParts / activeTextParts have been cleared.\n */\n const langgraphStep =\n typeof metadata?.langgraph_step === 'number'\n ? metadata.langgraph_step\n : null;\n if (langgraphStep !== null && langgraphStep !== state.currentStep) {\n if (state.currentStep !== null) {\n for (const [id, seen] of messageSeen) {\n if (seen.text) {\n controller.enqueue({ type: 'text-end', id });\n }\n if (seen.reasoning) {\n controller.enqueue({ type: 'reasoning-end', id });\n }\n messageSeen.delete(id);\n messageConcat.delete(id);\n messageReasoningIds.delete(id);\n }\n controller.enqueue({ type: 'finish-step' });\n }\n controller.enqueue({ type: 'start-step' });\n state.currentStep = langgraphStep;\n }\n\n /**\n * Accumulate message chunks for later reference\n * Note: Only works for actual class instances, not serialized messages\n */\n if (AIMessageChunk.isInstance(msg)) {\n const existingMessage = messageConcat.get(msgId);\n if (existingMessage) {\n messageConcat.set(\n msgId,\n existingMessage.concat(msg) as AIMessageChunk,\n );\n } else {\n messageConcat.set(msgId, msg);\n }\n }\n\n if (isAIMessageChunk(msg)) {\n const concatChunk = messageConcat.get(msgId);\n\n /**\n * Handle image generation outputs from additional_kwargs.tool_outputs\n * Handle both direct properties and serialized messages (kwargs)\n */\n const msgObj = msg as unknown as Record<string, unknown>;\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n const additionalKwargs = dataSource.additional_kwargs as\n | Record<string, unknown>\n | undefined;\n const imageOutputs = extractImageOutputs(additionalKwargs);\n\n for (const imageOutput of imageOutputs) {\n /**\n * Only emit if we have image data and haven't emitted this image yet\n */\n if (imageOutput.result && !emittedImages.has(imageOutput.id)) {\n emittedImages.add(imageOutput.id);\n\n /**\n * Emit as a file part using proper AI SDK multimodal format\n */\n const mediaType = `image/${imageOutput.output_format || 'png'}`;\n controller.enqueue({\n type: 'file',\n mediaType,\n url: `data:${mediaType};base64,${imageOutput.result}`,\n });\n }\n }\n\n /**\n * Handle tool call chunks for streaming tool calls\n * Access from dataSource to handle both direct and serialized messages\n *\n * Tool call chunks are streamed as follows:\n * 1. First chunk: has name, id, but often empty args\n * 2. Subsequent chunks: have args but NO id or name\n *\n * We store tool call info by index when we first see it, then look it up\n * for subsequent chunks that don't include the id.\n */\n const toolCallChunks = dataSource.tool_call_chunks as\n | ToolCallChunk[]\n | undefined;\n if (toolCallChunks?.length) {\n for (const toolCallChunk of toolCallChunks) {\n const toolCallIndex = toolCallChunk.index ?? 0;\n\n /**\n * If this chunk has an id, store it for future lookups by index\n */\n if (toolCallChunk.id) {\n getOrCreateToolCallInfoByIndex(toolCallInfoByIndex, msgId).set(\n toolCallIndex,\n {\n id: toolCallChunk.id,\n name:\n toolCallChunk.name ||\n concatChunk?.tool_call_chunks?.[toolCallIndex]?.name ||\n 'unknown',\n },\n );\n }\n\n /**\n * Get the tool call ID from the chunk, stored info, or accumulated chunks\n */\n const storedToolCallInfo = toolCallInfoByIndex\n .get(msgId)\n ?.get(toolCallIndex);\n const toolCallId =\n toolCallChunk.id ||\n storedToolCallInfo?.id ||\n concatChunk?.tool_call_chunks?.[toolCallIndex]?.id;\n\n /**\n * Skip if we don't have a proper tool call ID - we'll handle it in values\n */\n if (!toolCallId) {\n continue;\n }\n\n const toolName =\n toolCallChunk.name ||\n storedToolCallInfo?.name ||\n concatChunk?.tool_call_chunks?.[toolCallIndex]?.name ||\n 'unknown';\n\n /**\n * Emit tool-input-start when we first see this tool call\n * (even if args is empty - the first chunk often has empty args)\n * Set dynamic: true to enable HITL approval requests\n */\n const seen = messageSeen.get(msgId);\n if (!seen?.tool?.has(toolCallId)) {\n const updatedSeen = getOrCreateMessageSeen(messageSeen, msgId);\n updatedSeen.tool ??= new Set();\n updatedSeen.tool.add(toolCallId);\n\n if (!emittedToolCalls.has(toolCallId)) {\n emittedToolCalls.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId: toolCallId,\n toolName: toolName,\n dynamic: true,\n });\n }\n }\n\n /**\n * Only emit tool-input-delta when args is non-empty\n */\n if (toolCallChunk.args) {\n controller.enqueue({\n type: 'tool-input-delta',\n toolCallId: toolCallId,\n inputTextDelta: toolCallChunk.args,\n });\n }\n }\n\n return;\n }\n\n /**\n * Handle reasoning content from contentBlocks\n * Streaming chunks contain DELTA text (not accumulated), so emit directly.\n * Use reasoning block ID for deduplication as it's consistent across streaming and values events.\n *\n * Important: Early chunks may have reasoning ID but no content, later chunks may\n * have content but no reasoning ID. We capture the ID when first seen and reuse it.\n * We also immediately add to emittedReasoningIds to prevent values events from\n * emitting the same reasoning (values events can arrive between streaming chunks).\n */\n // Capture reasoning ID when we first see it (even if no content yet)\n const chunkReasoningId = extractReasoningId(msg);\n if (chunkReasoningId) {\n if (!messageReasoningIds.has(msgId)) {\n messageReasoningIds.set(msgId, chunkReasoningId);\n }\n // Immediately mark as emitted to prevent values from duplicating\n // This must happen as soon as we see the ID, before content arrives\n emittedReasoningIds.add(chunkReasoningId);\n }\n\n const reasoning = extractReasoningFromContentBlocks(msg);\n if (reasoning) {\n // Use stored reasoning ID, or current chunk's ID, or fall back to message ID\n const reasoningId =\n messageReasoningIds.get(msgId) ?? chunkReasoningId ?? msgId;\n\n const seen = messageSeen.get(msgId);\n if (!seen?.reasoning) {\n controller.enqueue({ type: 'reasoning-start', id: msgId });\n getOrCreateMessageSeen(messageSeen, msgId).reasoning = true;\n }\n\n // Streaming chunks have delta text, emit directly without slicing\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: msgId,\n });\n // Also ensure the reasoning ID is marked (handles case where ID wasn't in first chunk)\n emittedReasoningIds.add(reasoningId);\n }\n\n /**\n * Handle text content\n */\n const text = getMessageText(msg);\n if (text) {\n const seen = messageSeen.get(msgId);\n if (!seen?.text) {\n controller.enqueue({ type: 'text-start', id: msgId });\n getOrCreateMessageSeen(messageSeen, msgId).text = true;\n }\n\n controller.enqueue({\n type: 'text-delta',\n delta: text,\n id: msgId,\n });\n }\n\n const citations = extractCitationsFromContentBlocks(msg);\n if (citations.length > 0) {\n emitSourceChunks(\n citations,\n msgId,\n state.emittedSourceIds,\n controller,\n );\n }\n } else if (isToolMessageType(msg)) {\n // Handle both direct properties and serialized messages (kwargs)\n const msgObj = msg as unknown as Record<string, unknown>;\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n const toolCallId = dataSource.tool_call_id as string | undefined;\n const status = dataSource.status as string | undefined;\n\n if (toolCallId) {\n if (status === 'error') {\n // Tool execution failed\n controller.enqueue({\n type: 'tool-output-error',\n toolCallId,\n errorText:\n typeof dataSource.content === 'string'\n ? dataSource.content\n : 'Tool execution failed',\n });\n } else {\n // Tool execution succeeded\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId,\n output: dataSource.content,\n });\n }\n }\n }\n\n return;\n }\n\n case 'tools': {\n if (data == null || typeof data !== 'object' || Array.isArray(data)) {\n return;\n }\n\n const payload = data as {\n event?: unknown;\n name?: unknown;\n input?: unknown;\n data?: unknown;\n output?: unknown;\n error?: unknown;\n toolCallId?: unknown;\n };\n const toolCallId =\n typeof payload.toolCallId === 'string' ? payload.toolCallId : undefined;\n const toolName =\n typeof payload.name === 'string' ? payload.name : 'unknown';\n\n if (!toolCallId) return;\n\n const ensureToolInputLifecycle = () => {\n if (!emittedToolCalls.has(toolCallId)) {\n emittedToolCalls.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId,\n toolName,\n dynamic: true,\n });\n }\n\n if (!emittedToolInputs.has(toolCallId)) {\n emittedToolInputs.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId,\n toolName,\n input: payload.input,\n dynamic: true,\n });\n }\n };\n\n switch (payload.event) {\n case 'on_tool_start': {\n const toolCallKey = `${toolName}:${JSON.stringify(payload.input)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCallId);\n\n ensureToolInputLifecycle();\n break;\n }\n\n case 'on_tool_event': {\n ensureToolInputLifecycle();\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId,\n output: payload.data,\n preliminary: true,\n });\n break;\n }\n\n case 'on_tool_end': {\n ensureToolInputLifecycle();\n controller.enqueue({\n type: 'tool-output-available',\n toolCallId,\n output: payload.output,\n });\n break;\n }\n\n case 'on_tool_error': {\n ensureToolInputLifecycle();\n controller.enqueue({\n type: 'tool-output-error',\n toolCallId,\n errorText: formatToolError(payload.error),\n });\n break;\n }\n }\n\n return;\n }\n\n case 'values': {\n /**\n * Finalize all pending message chunks\n */\n for (const [id, seen] of messageSeen) {\n if (seen.text) controller.enqueue({ type: 'text-end', id });\n if (seen.tool) {\n for (const toolCallId of seen.tool) {\n const concatMsg = messageConcat.get(id);\n const toolCall = concatMsg?.tool_calls?.find(\n call => call.id === toolCallId,\n );\n\n if (toolCall) {\n emittedToolCalls.add(toolCallId);\n // Store mapping for HITL interrupt lookup\n const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCallId);\n if (!emittedToolInputs.has(toolCallId)) {\n emittedToolInputs.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId,\n toolName: toolCall.name,\n input: toolCall.args,\n dynamic: true,\n });\n }\n }\n }\n }\n\n if (seen.reasoning) {\n controller.enqueue({ type: 'reasoning-end', id });\n }\n\n messageSeen.delete(id);\n messageConcat.delete(id);\n messageReasoningIds.delete(id);\n }\n\n /**\n * Also check for tool calls in the final state that weren't streamed\n * This handles cases where tool calls appear directly in values without being in messages events\n */\n if (data != null && typeof data === 'object' && 'messages' in data) {\n const messages = (data as { messages?: unknown[] }).messages;\n if (Array.isArray(messages)) {\n /**\n * First pass: Collect all tool call IDs that have been responded to by ToolMessages.\n * These are historical tool calls that are already complete.\n */\n const completedToolCallIds = new Set<string>();\n for (const msg of messages) {\n if (!msg || typeof msg !== 'object') continue;\n\n if (isToolMessageType(msg)) {\n // Handle both direct properties and serialized messages (kwargs)\n const msgObj = msg as unknown as Record<string, unknown>;\n const dataSource =\n msgObj.type === 'constructor' &&\n msgObj.kwargs &&\n typeof msgObj.kwargs === 'object'\n ? (msgObj.kwargs as Record<string, unknown>)\n : msgObj;\n\n const toolCallId = dataSource.tool_call_id as string | undefined;\n if (toolCallId) {\n completedToolCallIds.add(toolCallId);\n }\n }\n }\n\n /**\n * Second pass: Process messages and emit tool events only for NEW tool calls\n * (those not already completed by a ToolMessage in the history)\n */\n for (const msg of messages) {\n if (!msg || typeof msg !== 'object') continue;\n\n // Use getMessageId to handle both class instances and serialized messages\n const msgId = getMessageId(msg);\n if (!msgId) continue;\n\n /**\n * Check if this is an AI message with tool calls\n */\n let toolCalls: ToolCall[] | undefined;\n\n /**\n * For class instances\n */\n if (AIMessageChunk.isInstance(msg) || AIMessage.isInstance(msg)) {\n toolCalls = msg.tool_calls;\n } else if (isPlainMessageObject(msg)) {\n /**\n * For plain objects from RemoteGraph API or serialized LangChain messages\n */\n const messageRecord = msg as Record<string, unknown>;\n\n /**\n * Determine the data source (handle both direct and serialized formats)\n */\n const isSerializedFormat =\n messageRecord.type === 'constructor' &&\n Array.isArray(messageRecord.id) &&\n ((messageRecord.id as string[]).includes('AIMessageChunk') ||\n (messageRecord.id as string[]).includes('AIMessage'));\n const dataSource = isSerializedFormat\n ? (messageRecord.kwargs as Record<string, unknown>)\n : messageRecord;\n\n if (\n messageRecord.type === 'ai' ||\n messageRecord.type === 'AIMessageChunk' ||\n isSerializedFormat\n ) {\n /**\n * Try tool_calls first (normalized format)\n */\n if (Array.isArray(dataSource?.tool_calls)) {\n toolCalls = dataSource.tool_calls as ToolCall[];\n } else if (\n /**\n * Fall back to additional_kwargs.tool_calls (OpenAI format)\n */\n dataSource?.additional_kwargs &&\n typeof dataSource.additional_kwargs === 'object'\n ) {\n const additionalKwargs =\n dataSource.additional_kwargs as Record<string, unknown>;\n if (Array.isArray(additionalKwargs.tool_calls)) {\n /**\n * Convert OpenAI format to normalized format\n */\n toolCalls = (\n additionalKwargs.tool_calls as Array<{\n id?: string;\n function?: { name?: string; arguments?: string };\n }>\n ).map((tc, idx) => {\n const functionData = tc.function;\n let args: unknown;\n try {\n args = functionData?.arguments\n ? JSON.parse(functionData.arguments)\n : {};\n } catch {\n args = {};\n }\n return {\n id: tc.id || `call_${idx}`,\n name: functionData?.name || 'unknown',\n args,\n } as ToolCall;\n });\n }\n }\n }\n }\n\n if (toolCalls && toolCalls.length > 0) {\n for (const toolCall of toolCalls) {\n /**\n * Only emit if we haven't already processed this tool call\n * AND if it's not a historical tool call that already has a ToolMessage response.\n * Historical completed tool calls should not be re-emitted as this would create\n * orphaned tool parts in the UI without corresponding outputs.\n */\n if (\n toolCall.id &&\n !emittedToolCalls.has(toolCall.id) &&\n !completedToolCallIds.has(toolCall.id)\n ) {\n emittedToolCalls.add(toolCall.id);\n // Store mapping for HITL interrupt lookup\n const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCall.id);\n /**\n * Emit tool-input-start first to ensure proper lifecycle.\n * Tool calls that weren't streamed (no tool_call_chunks) need\n * the start event before tool-input-available.\n */\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n dynamic: true,\n });\n emittedToolInputs.add(toolCall.id);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n input: toolCall.args,\n dynamic: true,\n });\n } else if (toolCall.id && emittedToolCalls.has(toolCall.id)) {\n // Register key mapping for tool calls already emitted via messages mode\n // so that __interrupt__ handling can match them by key\n const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;\n emittedToolCallsByKey.set(toolCallKey, toolCall.id);\n }\n }\n }\n\n /**\n * Check for reasoning content that wasn't streamed\n * Use reasoning block ID for deduplication as it's consistent across streaming and values.\n *\n * IMPORTANT: Handle two cases differently:\n * 1. Message has reasoning WITHOUT tool_calls → emit reasoning (pure reasoning case)\n * 2. Message has reasoning WITH tool_calls → only emit if streamed this request\n * (When resuming from HITL interrupt, historical messages have both reasoning\n * AND tool_calls. We skip those to avoid duplicate reasoning entries.)\n */\n const reasoningId = extractReasoningId(msg);\n const wasStreamedThisRequest = messageSeen.has(msgId);\n const hasToolCalls = toolCalls && toolCalls.length > 0;\n\n /**\n * Determine if we should emit reasoning:\n * - If we already emitted this reasoning ID, skip\n * - If the message was streamed this request, emit (normal flow)\n * - If NOT streamed but has NO tool_calls, emit (pure reasoning in values case)\n * - If NOT streamed but HAS tool_calls, skip (historical HITL message)\n */\n const shouldEmitReasoning =\n reasoningId &&\n !emittedReasoningIds.has(reasoningId) &&\n (wasStreamedThisRequest || !hasToolCalls);\n\n if (shouldEmitReasoning) {\n /**\n * Use extractReasoningFromValuesMessage which extracts from response_metadata.output\n * This is the full accumulated reasoning, not deltas\n */\n const reasoning = extractReasoningFromValuesMessage(msg);\n\n if (reasoning) {\n controller.enqueue({ type: 'reasoning-start', id: msgId });\n controller.enqueue({\n type: 'reasoning-delta',\n delta: reasoning,\n id: msgId,\n });\n controller.enqueue({ type: 'reasoning-end', id: msgId });\n emittedReasoningIds.add(reasoningId);\n }\n }\n\n const valuesCitations = extractCitationsFromContentBlocks(msg);\n if (valuesCitations.length > 0) {\n emitSourceChunks(\n valuesCitations,\n msgId,\n state.emittedSourceIds,\n controller,\n );\n }\n }\n }\n }\n\n /**\n * Handle Human-in-the-Loop interrupts\n * When HITL middleware pauses execution, the interrupt data is in __interrupt__\n * Note: This is outside the 'messages' check because interrupt can come as a separate event\n */\n if (data != null && typeof data === 'object') {\n const interrupt = (data as Record<string, unknown>).__interrupt__;\n if (Array.isArray(interrupt) && interrupt.length > 0) {\n for (const interruptItem of interrupt) {\n const interruptValue = (interruptItem as { value?: unknown })\n ?.value as Record<string, unknown> | undefined;\n\n if (!interruptValue) continue;\n\n /**\n * Support both camelCase (JS SDK) and snake_case (Python SDK)\n */\n const actionRequests = (interruptValue.actionRequests ||\n interruptValue.action_requests) as\n | Array<{\n name: string;\n args?: Record<string, unknown>; // JS SDK uses 'args'\n arguments?: Record<string, unknown>; // Python SDK uses 'arguments'\n id?: string;\n }>\n | undefined;\n\n if (!Array.isArray(actionRequests)) continue;\n\n for (const actionRequest of actionRequests) {\n const toolName = actionRequest.name;\n /**\n * Support both 'args' (JS SDK) and 'arguments' (Python SDK)\n */\n const input = actionRequest.args || actionRequest.arguments;\n\n /**\n * Look up the original tool call ID using the name+args key\n * Fall back to action request ID or generate one if not found\n */\n const toolCallKey = `${toolName}:${JSON.stringify(input)}`;\n const toolCallId =\n emittedToolCallsByKey.get(toolCallKey) ||\n actionRequest.id ||\n `hitl-${toolName}-${Date.now()}`;\n\n /**\n * First emit tool-input-start then tool-input-available\n * so the UI knows what tool is being called with proper lifecycle\n */\n if (!emittedToolCalls.has(toolCallId)) {\n emittedToolCalls.add(toolCallId);\n emittedToolCallsByKey.set(toolCallKey, toolCallId);\n controller.enqueue({\n type: 'tool-input-start',\n toolCallId,\n toolName,\n dynamic: true,\n });\n emittedToolInputs.add(toolCallId);\n controller.enqueue({\n type: 'tool-input-available',\n toolCallId,\n toolName,\n input,\n dynamic: true,\n });\n }\n\n /**\n * Then emit tool-approval-request to mark it as awaiting approval\n */\n controller.enqueue({\n type: 'tool-approval-request',\n approvalId: toolCallId,\n toolCallId,\n });\n }\n }\n }\n }\n\n break;\n }\n }\n}\n","import type { AIMessageChunk } from '@langchain/core/messages';\nimport type {\n UIMessage,\n UIMessageChunk,\n ChatTransport,\n ChatRequestOptions,\n} from 'ai';\nimport {\n RemoteGraph,\n type RemoteGraphParams,\n} from '@langchain/langgraph/remote';\nimport { toBaseMessages, toUIMessageStream } from './adapter';\n\n/**\n * Options for configuring a LangSmith deployment transport.\n * Extends RemoteGraphParams but makes graphId optional (defaults to 'agent').\n */\nexport type LangSmithDeploymentTransportOptions = Omit<\n RemoteGraphParams,\n 'graphId'\n> & {\n /**\n * The ID of the graph to connect to.\n * @default 'agent'\n */\n graphId?: string;\n};\n\n/**\n * A ChatTransport implementation for LangSmith/LangGraph deployments.\n *\n * This transport enables seamless integration between the AI SDK's useChat hook\n * and LangSmith deployed LangGraph agents.\n *\n * @example\n * ```ts\n * import { LangSmithDeploymentTransport } from '@ai-sdk/langchain';\n *\n * // Use with useChat\n * const { messages, input, handleSubmit } = useChat({\n * transport: new LangSmithDeploymentTransport({\n * url: 'https://your-deployment.us.langgraph.app',\n * apiKey: 'my-api-key',\n * }),\n * });\n * ```\n */\nexport class LangSmithDeploymentTransport<\n UI_MESSAGE extends UIMessage,\n> implements ChatTransport<UI_MESSAGE> {\n protected graph: RemoteGraph;\n\n constructor(options: LangSmithDeploymentTransportOptions) {\n this.graph = new RemoteGraph({\n ...options,\n graphId: options.graphId ?? 'agent',\n });\n }\n\n async sendMessages(\n options: {\n trigger: 'submit-message' | 'regenerate-message';\n chatId: string;\n messageId: string | undefined;\n messages: UI_MESSAGE[];\n abortSignal: AbortSignal | undefined;\n } & ChatRequestOptions,\n ): Promise<ReadableStream<UIMessageChunk>> {\n const baseMessages = await toBaseMessages(options.messages);\n\n const stream = await this.graph.stream(\n { messages: baseMessages },\n { streamMode: ['values', 'messages'] },\n );\n\n return toUIMessageStream(\n stream as AsyncIterable<AIMessageChunk> | ReadableStream,\n );\n }\n\n async reconnectToStream(\n _options: {\n chatId: string;\n } & ChatRequestOptions,\n ): Promise<ReadableStream<UIMessageChunk> | null> {\n throw new Error('Method not implemented.');\n }\n}\n"],"mappings":"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","names":["providerMetadata"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/langchain",
3
- "version": "3.0.0-canary.172",
3
+ "version": "3.0.0-canary.173",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "ai": "7.0.0-canary.172"
28
+ "ai": "7.0.0-canary.173"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@langchain/core": "^1.1.46",
package/src/adapter.ts CHANGED
@@ -431,20 +431,14 @@ export function toUIMessageStream<TState = unknown>(
431
431
  * State for LangGraph stream handling
432
432
  */
433
433
  const langGraphState: LangGraphEventState = {
434
- messageSeen: {} as Record<
435
- string,
436
- { text?: boolean; reasoning?: boolean; tool?: Record<string, boolean> }
437
- >,
438
- messageConcat: {} as Record<string, AIMessageChunk>,
434
+ messageSeen: new Map(),
435
+ messageConcat: new Map(),
439
436
  emittedToolCalls: new Set<string>(),
440
437
  emittedToolInputs: new Set<string>(),
441
438
  emittedImages: new Set<string>(),
442
439
  emittedReasoningIds: new Set<string>(),
443
- messageReasoningIds: {} as Record<string, string>,
444
- toolCallInfoByIndex: {} as Record<
445
- string,
446
- Record<number, { id: string; name: string }>
447
- >,
440
+ messageReasoningIds: new Map(),
441
+ toolCallInfoByIndex: new Map(),
448
442
  currentStep: null as number | null,
449
443
  emittedToolCallsByKey: new Map<string, string>(),
450
444
  emittedSourceIds: new Set<string>(),
@@ -589,7 +583,7 @@ export function toUIMessageStream<TState = unknown>(
589
583
  * This handles streams without values events (e.g. streamMode: 'messages')
590
584
  * where the values handler never ran to emit *-end events.
591
585
  */
592
- for (const [id, seen] of Object.entries(langGraphState.messageSeen)) {
586
+ for (const [id, seen] of langGraphState.messageSeen) {
593
587
  if (seen.text) {
594
588
  controller.enqueue({ type: 'text-end', id });
595
589
  }
package/src/types.ts CHANGED
@@ -1,16 +1,19 @@
1
1
  import type { AIMessageChunk } from '@langchain/core/messages';
2
2
 
3
+ export interface LangGraphMessageSeen {
4
+ text?: boolean;
5
+ reasoning?: boolean;
6
+ tool?: Set<string>;
7
+ }
8
+
3
9
  /**
4
10
  * State for LangGraph event processing
5
11
  */
6
12
  export interface LangGraphEventState {
7
13
  /** Tracks which message IDs have been seen */
8
- messageSeen: Record<
9
- string,
10
- { text?: boolean; reasoning?: boolean; tool?: Record<string, boolean> }
11
- >;
14
+ messageSeen: Map<string, LangGraphMessageSeen>;
12
15
  /** Accumulates message chunks for later reference */
13
- messageConcat: Record<string, AIMessageChunk>;
16
+ messageConcat: Map<string, AIMessageChunk>;
14
17
  /** Tracks which tool call IDs have emitted tool-input-start */
15
18
  emittedToolCalls: Set<string>;
16
19
  /** Tracks which tool call IDs have emitted complete tool inputs */
@@ -20,12 +23,9 @@ export interface LangGraphEventState {
20
23
  /** Maps reasoning block IDs to their message IDs (for chunks that don't include the ID) */
21
24
  emittedReasoningIds: Set<string>;
22
25
  /** Maps message IDs to their reasoning block IDs (for chunks that don't include the ID) */
23
- messageReasoningIds: Record<string, string>;
26
+ messageReasoningIds: Map<string, string>;
24
27
  /** Maps message ID + tool call index to tool call info (for streaming chunks without ID) */
25
- toolCallInfoByIndex: Record<
26
- string,
27
- Record<number, { id: string; name: string }>
28
- >;
28
+ toolCallInfoByIndex: Map<string, Map<number, { id: string; name: string }>>;
29
29
  /** Tracks the current LangGraph step for start-step/finish-step events */
30
30
  currentStep: number | null;
31
31
  /** Maps tool call key (name:argsJson) to tool call ID for HITL interrupt handling */
package/src/utils.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
 
21
21
  import type {
22
22
  LangGraphEventState,
23
+ LangGraphMessageSeen,
23
24
  ReasoningContentBlock,
24
25
  ThinkingContentBlock,
25
26
  GPT5ReasoningOutput,
@@ -1174,6 +1175,38 @@ function formatToolError(error: unknown): string {
1174
1175
  }
1175
1176
  }
1176
1177
 
1178
+ /**
1179
+ * Returns per-message bookkeeping, creating it on first use.
1180
+ * Map keys keep remote-controlled message IDs like "__proto__" from touching Object.prototype.
1181
+ */
1182
+ function getOrCreateMessageSeen(
1183
+ messageSeen: LangGraphEventState['messageSeen'],
1184
+ msgId: string,
1185
+ ): LangGraphMessageSeen {
1186
+ let seen = messageSeen.get(msgId);
1187
+ if (!seen) {
1188
+ seen = {};
1189
+ messageSeen.set(msgId, seen);
1190
+ }
1191
+ return seen;
1192
+ }
1193
+
1194
+ /**
1195
+ * Returns the per-index tool call metadata for a message, creating it on first use.
1196
+ * Later streamed chunks use this to recover tool call IDs/names from earlier chunks.
1197
+ */
1198
+ function getOrCreateToolCallInfoByIndex(
1199
+ toolCallInfoByIndex: LangGraphEventState['toolCallInfoByIndex'],
1200
+ msgId: string,
1201
+ ): Map<number, { id: string; name: string }> {
1202
+ let toolCallInfo = toolCallInfoByIndex.get(msgId);
1203
+ if (!toolCallInfo) {
1204
+ toolCallInfo = new Map();
1205
+ toolCallInfoByIndex.set(msgId, toolCallInfo);
1206
+ }
1207
+ return toolCallInfo;
1208
+ }
1209
+
1177
1210
  /**
1178
1211
  * Processes a LangGraph event and emits UI message chunks.
1179
1212
  *
@@ -1257,16 +1290,16 @@ export function processLangGraphEvent(
1257
1290
  : null;
1258
1291
  if (langgraphStep !== null && langgraphStep !== state.currentStep) {
1259
1292
  if (state.currentStep !== null) {
1260
- for (const [id, seen] of Object.entries(messageSeen)) {
1293
+ for (const [id, seen] of messageSeen) {
1261
1294
  if (seen.text) {
1262
1295
  controller.enqueue({ type: 'text-end', id });
1263
1296
  }
1264
1297
  if (seen.reasoning) {
1265
1298
  controller.enqueue({ type: 'reasoning-end', id });
1266
1299
  }
1267
- delete messageSeen[id];
1268
- delete messageConcat[id];
1269
- delete messageReasoningIds[id];
1300
+ messageSeen.delete(id);
1301
+ messageConcat.delete(id);
1302
+ messageReasoningIds.delete(id);
1270
1303
  }
1271
1304
  controller.enqueue({ type: 'finish-step' });
1272
1305
  }
@@ -1279,17 +1312,19 @@ export function processLangGraphEvent(
1279
1312
  * Note: Only works for actual class instances, not serialized messages
1280
1313
  */
1281
1314
  if (AIMessageChunk.isInstance(msg)) {
1282
- if (messageConcat[msgId]) {
1283
- messageConcat[msgId] = messageConcat[msgId].concat(
1284
- msg,
1285
- ) as AIMessageChunk;
1315
+ const existingMessage = messageConcat.get(msgId);
1316
+ if (existingMessage) {
1317
+ messageConcat.set(
1318
+ msgId,
1319
+ existingMessage.concat(msg) as AIMessageChunk,
1320
+ );
1286
1321
  } else {
1287
- messageConcat[msgId] = msg;
1322
+ messageConcat.set(msgId, msg);
1288
1323
  }
1289
1324
  }
1290
1325
 
1291
1326
  if (isAIMessageChunk(msg)) {
1292
- const concatChunk = messageConcat[msgId];
1327
+ const concatChunk = messageConcat.get(msgId);
1293
1328
 
1294
1329
  /**
1295
1330
  * Handle image generation outputs from additional_kwargs.tool_outputs
@@ -1348,22 +1383,27 @@ export function processLangGraphEvent(
1348
1383
  * If this chunk has an id, store it for future lookups by index
1349
1384
  */
1350
1385
  if (toolCallChunk.id) {
1351
- toolCallInfoByIndex[msgId] ??= {};
1352
- toolCallInfoByIndex[msgId][toolCallIndex] = {
1353
- id: toolCallChunk.id,
1354
- name:
1355
- toolCallChunk.name ||
1356
- concatChunk?.tool_call_chunks?.[toolCallIndex]?.name ||
1357
- 'unknown',
1358
- };
1386
+ getOrCreateToolCallInfoByIndex(toolCallInfoByIndex, msgId).set(
1387
+ toolCallIndex,
1388
+ {
1389
+ id: toolCallChunk.id,
1390
+ name:
1391
+ toolCallChunk.name ||
1392
+ concatChunk?.tool_call_chunks?.[toolCallIndex]?.name ||
1393
+ 'unknown',
1394
+ },
1395
+ );
1359
1396
  }
1360
1397
 
1361
1398
  /**
1362
1399
  * Get the tool call ID from the chunk, stored info, or accumulated chunks
1363
1400
  */
1401
+ const storedToolCallInfo = toolCallInfoByIndex
1402
+ .get(msgId)
1403
+ ?.get(toolCallIndex);
1364
1404
  const toolCallId =
1365
1405
  toolCallChunk.id ||
1366
- toolCallInfoByIndex[msgId]?.[toolCallIndex]?.id ||
1406
+ storedToolCallInfo?.id ||
1367
1407
  concatChunk?.tool_call_chunks?.[toolCallIndex]?.id;
1368
1408
 
1369
1409
  /**
@@ -1375,7 +1415,7 @@ export function processLangGraphEvent(
1375
1415
 
1376
1416
  const toolName =
1377
1417
  toolCallChunk.name ||
1378
- toolCallInfoByIndex[msgId]?.[toolCallIndex]?.name ||
1418
+ storedToolCallInfo?.name ||
1379
1419
  concatChunk?.tool_call_chunks?.[toolCallIndex]?.name ||
1380
1420
  'unknown';
1381
1421
 
@@ -1384,10 +1424,11 @@ export function processLangGraphEvent(
1384
1424
  * (even if args is empty - the first chunk often has empty args)
1385
1425
  * Set dynamic: true to enable HITL approval requests
1386
1426
  */
1387
- if (!messageSeen[msgId]?.tool?.[toolCallId]) {
1388
- messageSeen[msgId] ??= {};
1389
- messageSeen[msgId].tool ??= {};
1390
- messageSeen[msgId].tool![toolCallId] = true;
1427
+ const seen = messageSeen.get(msgId);
1428
+ if (!seen?.tool?.has(toolCallId)) {
1429
+ const updatedSeen = getOrCreateMessageSeen(messageSeen, msgId);
1430
+ updatedSeen.tool ??= new Set();
1431
+ updatedSeen.tool.add(toolCallId);
1391
1432
 
1392
1433
  if (!emittedToolCalls.has(toolCallId)) {
1393
1434
  emittedToolCalls.add(toolCallId);
@@ -1428,8 +1469,8 @@ export function processLangGraphEvent(
1428
1469
  // Capture reasoning ID when we first see it (even if no content yet)
1429
1470
  const chunkReasoningId = extractReasoningId(msg);
1430
1471
  if (chunkReasoningId) {
1431
- if (!messageReasoningIds[msgId]) {
1432
- messageReasoningIds[msgId] = chunkReasoningId;
1472
+ if (!messageReasoningIds.has(msgId)) {
1473
+ messageReasoningIds.set(msgId, chunkReasoningId);
1433
1474
  }
1434
1475
  // Immediately mark as emitted to prevent values from duplicating
1435
1476
  // This must happen as soon as we see the ID, before content arrives
@@ -1440,12 +1481,12 @@ export function processLangGraphEvent(
1440
1481
  if (reasoning) {
1441
1482
  // Use stored reasoning ID, or current chunk's ID, or fall back to message ID
1442
1483
  const reasoningId =
1443
- messageReasoningIds[msgId] ?? chunkReasoningId ?? msgId;
1484
+ messageReasoningIds.get(msgId) ?? chunkReasoningId ?? msgId;
1444
1485
 
1445
- if (!messageSeen[msgId]?.reasoning) {
1486
+ const seen = messageSeen.get(msgId);
1487
+ if (!seen?.reasoning) {
1446
1488
  controller.enqueue({ type: 'reasoning-start', id: msgId });
1447
- messageSeen[msgId] ??= {};
1448
- messageSeen[msgId].reasoning = true;
1489
+ getOrCreateMessageSeen(messageSeen, msgId).reasoning = true;
1449
1490
  }
1450
1491
 
1451
1492
  // Streaming chunks have delta text, emit directly without slicing
@@ -1463,10 +1504,10 @@ export function processLangGraphEvent(
1463
1504
  */
1464
1505
  const text = getMessageText(msg);
1465
1506
  if (text) {
1466
- if (!messageSeen[msgId]?.text) {
1507
+ const seen = messageSeen.get(msgId);
1508
+ if (!seen?.text) {
1467
1509
  controller.enqueue({ type: 'text-start', id: msgId });
1468
- messageSeen[msgId] ??= {};
1469
- messageSeen[msgId].text = true;
1510
+ getOrCreateMessageSeen(messageSeen, msgId).text = true;
1470
1511
  }
1471
1512
 
1472
1513
  controller.enqueue({
@@ -1615,16 +1656,16 @@ export function processLangGraphEvent(
1615
1656
  /**
1616
1657
  * Finalize all pending message chunks
1617
1658
  */
1618
- for (const [id, seen] of Object.entries(messageSeen)) {
1659
+ for (const [id, seen] of messageSeen) {
1619
1660
  if (seen.text) controller.enqueue({ type: 'text-end', id });
1620
1661
  if (seen.tool) {
1621
- for (const [toolCallId, toolCallSeen] of Object.entries(seen.tool)) {
1622
- const concatMsg = messageConcat[id];
1662
+ for (const toolCallId of seen.tool) {
1663
+ const concatMsg = messageConcat.get(id);
1623
1664
  const toolCall = concatMsg?.tool_calls?.find(
1624
1665
  call => call.id === toolCallId,
1625
1666
  );
1626
1667
 
1627
- if (toolCallSeen && toolCall) {
1668
+ if (toolCall) {
1628
1669
  emittedToolCalls.add(toolCallId);
1629
1670
  // Store mapping for HITL interrupt lookup
1630
1671
  const toolCallKey = `${toolCall.name}:${JSON.stringify(toolCall.args)}`;
@@ -1647,9 +1688,9 @@ export function processLangGraphEvent(
1647
1688
  controller.enqueue({ type: 'reasoning-end', id });
1648
1689
  }
1649
1690
 
1650
- delete messageSeen[id];
1651
- delete messageConcat[id];
1652
- delete messageReasoningIds[id];
1691
+ messageSeen.delete(id);
1692
+ messageConcat.delete(id);
1693
+ messageReasoningIds.delete(id);
1653
1694
  }
1654
1695
 
1655
1696
  /**
@@ -1828,7 +1869,7 @@ export function processLangGraphEvent(
1828
1869
  * AND tool_calls. We skip those to avoid duplicate reasoning entries.)
1829
1870
  */
1830
1871
  const reasoningId = extractReasoningId(msg);
1831
- const wasStreamedThisRequest = !!messageSeen[msgId];
1872
+ const wasStreamedThisRequest = messageSeen.has(msgId);
1832
1873
  const hasToolCalls = toolCalls && toolCalls.length > 0;
1833
1874
 
1834
1875
  /**