import { parsePartialJson } from "@langchain/core/output_parsers"; import { useStreamContext } from "@/providers/Stream"; import { AIMessage, Checkpoint, Message } from "@langchain/langgraph-sdk"; import { getContentString } from "../utils"; import { BranchSwitcher, CommandBar } from "./shared"; import { MarkdownText } from "../markdown-text"; import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui"; import { cn } from "@/lib/utils"; import { ToolCalls, ToolResult } from "./tool-calls"; import { MessageContentComplex } from "@langchain/core/messages"; import { Fragment } from "react/jsx-runtime"; import { isAgentInboxInterruptSchema } from "@/lib/agent-inbox-interrupt"; import { ThreadView } from "../agent-inbox"; import { useQueryState, parseAsBoolean } from "nuqs"; import { GenericInterruptView } from "./generic-interrupt"; import { useArtifact } from "../artifact"; function CustomComponent({ message, thread, }: { message: Message; thread: ReturnType; }) { const artifact = useArtifact(); const { values } = useStreamContext(); const customComponents = values.ui?.filter( (ui) => ui.metadata?.message_id === message.id, ); if (!customComponents?.length) return null; return ( {customComponents.map((customComponent) => ( ))} ); } function parseAnthropicStreamedToolCalls( content: MessageContentComplex[], ): AIMessage["tool_calls"] { const toolCallContents = content.filter((c) => c.type === "tool_use" && c.id); return toolCallContents.map((tc) => { const toolCall = tc as Record; let json: Record = {}; if (toolCall?.input) { try { json = parsePartialJson(toolCall.input) ?? {}; } catch { // Pass } } return { name: toolCall.name ?? "", id: toolCall.id ?? "", args: json, type: "tool_call", }; }); } interface InterruptProps { interruptValue?: unknown; isLastMessage: boolean; hasNoAIOrToolMessages: boolean; } function Interrupt({ interruptValue, isLastMessage, hasNoAIOrToolMessages, }: InterruptProps) { return ( <> {isAgentInboxInterruptSchema(interruptValue) && (isLastMessage || hasNoAIOrToolMessages) && ( )} {interruptValue && !isAgentInboxInterruptSchema(interruptValue) && isLastMessage ? ( ) : null} ); } export function AssistantMessage({ message, isLoading, handleRegenerate, }: { message: Message | undefined; isLoading: boolean; handleRegenerate: (parentCheckpoint: Checkpoint | null | undefined) => void; }) { const content = message?.content ?? []; const contentString = getContentString(content); const [hideToolCalls] = useQueryState( "hideToolCalls", parseAsBoolean.withDefault(false), ); const thread = useStreamContext(); const isLastMessage = thread.messages[thread.messages.length - 1].id === message?.id; const hasNoAIOrToolMessages = !thread.messages.find( (m) => m.type === "ai" || m.type === "tool", ); const meta = message ? thread.getMessagesMetadata(message) : undefined; const threadInterrupt = thread.interrupt; const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint; const anthropicStreamedToolCalls = Array.isArray(content) ? parseAnthropicStreamedToolCalls(content) : undefined; const hasToolCalls = message && "tool_calls" in message && message.tool_calls && message.tool_calls.length > 0; const toolCallsHaveContents = hasToolCalls && message.tool_calls?.some( (tc) => tc.args && Object.keys(tc.args).length > 0, ); const hasAnthropicToolCalls = !!anthropicStreamedToolCalls?.length; const isToolResult = message?.type === "tool"; if (isToolResult && hideToolCalls) { return null; } return (
{isToolResult ? ( ) : ( {contentString.length > 0 && (
{contentString}
)} {!hideToolCalls && ( {(hasToolCalls && toolCallsHaveContents && ( )) || (hasAnthropicToolCalls && ( )) || (hasToolCalls && ( ))} )} {message && ( )}
thread.setBranch(branch)} isLoading={isLoading} /> handleRegenerate(parentCheckpoint)} />
)}
); } export function AssistantMessageLoading() { return (
); }