UNPKG

@langchain/langgraph-sdk

Version:

Client library for interacting with the LangGraph API

1 lines • 12.5 kB
{"version":3,"file":"assembled-to-message.cjs","names":["HumanMessage","SystemMessage","ToolMessage","AIMessageChunk","AIMessage"],"sources":["../../src/stream/assembled-to-message.ts"],"sourcesContent":["/**\n * Convert the protocol-native {@link AssembledMessage} (a namespace +\n * content-block bag) into a class instance from\n * `@langchain/core/messages`.\n *\n * The v2 messages channel carries `role` on the `message-start`\n * event, but `MessageAssembler` drops it by the time it produces an\n * `AssembledMessage`. The selector-side caller therefore captures the\n * role separately (via the assembler's per-update hook, see\n * `projections/messages.ts`) and passes it in.\n *\n * The conversion is intentionally lossless in the limit: when the\n * message finishes we emit the same BaseMessage shape that a\n * `values.messages` entry would round-trip to via\n * {@link tryCoerceMessageLikeToMessage}. Mid-stream, partial blocks\n * (e.g. tool_call_chunk) are folded into the same shape so the UI\n * can render an incrementally-completing message.\n */\nimport {\n AIMessage,\n AIMessageChunk,\n HumanMessage,\n SystemMessage,\n ToolMessage,\n type BaseMessage,\n type UsageMetadata,\n} from \"@langchain/core/messages\";\nimport type { ContentBlock, MessageRole, UsageInfo } from \"@langchain/protocol\";\nimport type { AssembledMessage } from \"../client/stream/messages.js\";\n\nexport type ExtendedMessageRole = MessageRole | \"tool\";\n\nexport interface AssembledToMessageInput {\n /** Stable message id (from `MessageStartData.id`). */\n id?: string;\n /** Author role captured from the `message-start` event. */\n role: ExtendedMessageRole;\n /** Content blocks assembled so far. */\n blocks: ContentBlock[];\n /** Namespace associated with the streamed message. */\n namespace?: readonly string[];\n /** Graph node associated with the streamed message. */\n node?: string;\n runId?: string;\n /** Tool-call id a `role: \"tool\"` message is responding to, if any. */\n toolCallId?: string;\n /** Provider metadata (populated on `message-start`). */\n metadata?: Readonly<Record<string, unknown>>;\n /** Final-token usage (populated on `message-finish`). */\n usage?: UsageInfo;\n /** Response metadata (populated on `message-finish`). */\n finishMetadata?: Readonly<Record<string, unknown>>;\n /** Finish reason (populated on `message-finish`). */\n finishReason?: string;\n}\n\n/**\n * Produce a `BaseMessage` class instance from an in-progress or\n * finished assembled message. Safe to call repeatedly across deltas —\n * each call returns a new instance whose content reflects the\n * currently-observed blocks.\n */\nexport function assembledToBaseMessage(\n input: AssembledToMessageInput\n): BaseMessage {\n const {\n id,\n role,\n blocks,\n namespace,\n node,\n runId,\n toolCallId,\n metadata,\n usage,\n finishMetadata,\n finishReason,\n } = input;\n const textContent = extractContentString(blocks);\n const toolCalls = extractToolCalls(blocks);\n const toolCallChunks = extractToolCallChunks(blocks);\n const additionalKwargs = {\n ...(namespace != null ? { namespace } : {}),\n ...(node != null ? { node } : {}),\n ...(runId != null ? { run_id: runId } : {}),\n ...(metadata != null ? { metadata } : {}),\n ...(usage != null ? { usage } : {}),\n };\n const hasAdditionalKwargs = Object.keys(additionalKwargs).length > 0;\n const responseMetadata = {\n ...(finishReason != null ? { finish_reason: finishReason } : {}),\n ...finishMetadata,\n };\n\n switch (role) {\n case \"human\":\n return new HumanMessage({\n ...(id != null ? { id } : {}),\n content: textContent,\n ...(hasAdditionalKwargs ? { additional_kwargs: additionalKwargs } : {}),\n ...(Object.keys(responseMetadata).length > 0\n ? { response_metadata: responseMetadata }\n : {}),\n });\n case \"system\":\n return new SystemMessage({\n ...(id != null ? { id } : {}),\n content: textContent,\n ...(hasAdditionalKwargs ? { additional_kwargs: additionalKwargs } : {}),\n ...(Object.keys(responseMetadata).length > 0\n ? { response_metadata: responseMetadata }\n : {}),\n });\n case \"tool\":\n return new ToolMessage({\n ...(id != null ? { id } : {}),\n content: textContent,\n tool_call_id: toolCallId ?? \"\",\n ...(hasAdditionalKwargs ? { additional_kwargs: additionalKwargs } : {}),\n ...(Object.keys(responseMetadata).length > 0\n ? { response_metadata: responseMetadata }\n : {}),\n });\n case \"ai\":\n default: {\n // Use `AIMessageChunk` whenever tool_call_chunks are present —\n // the concrete `AIMessage` class silently DROPS the\n // `tool_call_chunks` field, which would leave mid-stream tool\n // calls invisible to the UI (it sees an AI message with empty\n // content and no tool calls, rendering a blank bubble until\n // `content-block-finish` finally promotes the chunks to\n // finalized `tool_calls`). The chunk class is assignment-compatible\n // with `BaseMessage` and round-trips through the merge logic.\n const payload = {\n ...(id != null ? { id } : {}),\n content: cloneContentBlocks(blocks),\n ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n ...(toolCallChunks.length > 0\n ? { tool_call_chunks: toolCallChunks }\n : {}),\n ...(hasAdditionalKwargs ? { additional_kwargs: additionalKwargs } : {}),\n usage_metadata: normalizeUsage(usage),\n response_metadata: {\n ...responseMetadata,\n output_version: \"v1\" as const,\n },\n };\n return toolCallChunks.length > 0\n ? new AIMessageChunk(\n payload as ConstructorParameters<typeof AIMessageChunk>[0]\n )\n : new AIMessage(payload as ConstructorParameters<typeof AIMessage>[0]);\n }\n }\n}\n\n/**\n * Convenience: given the raw assembled message + the role captured\n * from `message-start`, produce a `BaseMessage` with the same id.\n */\nexport function assembledMessageToBaseMessage(\n assembled: AssembledMessage,\n role: ExtendedMessageRole,\n extras: { toolCallId?: string } = {}\n): BaseMessage {\n return assembledToBaseMessage({\n id: assembled.id,\n role,\n blocks: assembled.blocks,\n namespace: assembled.namespace,\n node: assembled.node,\n runId: assembled.runId,\n toolCallId: extras.toolCallId,\n metadata: assembled.metadata,\n usage: assembled.usage,\n finishMetadata: assembled.finishMetadata,\n finishReason: assembled.finishReason,\n });\n}\n\n// ---------- helpers ----------\n\nfunction normalizeUsage(\n usage: UsageInfo | undefined\n): UsageMetadata | undefined {\n if (usage == null) return undefined;\n return {\n ...usage,\n input_tokens: usage.input_tokens ?? 0,\n output_tokens: usage.output_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n };\n}\n\nfunction extractContentString(blocks: ContentBlock[]): string {\n let out = \"\";\n for (const block of blocks) {\n if (\n block.type === \"text\" &&\n typeof (block as { text?: unknown }).text === \"string\"\n ) {\n out += (block as { text: string }).text;\n }\n }\n return out;\n}\n\nfunction cloneContentBlocks(blocks: ContentBlock[]): ContentBlock[] {\n return blocks.map((block) => structuredClone(block));\n}\n\ninterface LooseToolCall {\n id: string;\n name: string;\n args: Record<string, unknown>;\n type: \"tool_call\";\n}\n\nfunction extractToolCalls(blocks: ContentBlock[]): LooseToolCall[] {\n const out: LooseToolCall[] = [];\n for (const block of blocks) {\n if (block.type !== \"tool_call\" && block.type !== \"tool_use\") continue;\n const tc = block as ToolCallLikeBlock;\n out.push({\n id: tc.id ?? \"\",\n name: tc.name ?? \"\",\n args: normalizeToolCallArgs(tc.args ?? tc.input),\n type: \"tool_call\",\n });\n }\n return out;\n}\n\ninterface ToolCallLikeBlock {\n id?: string;\n name?: string;\n args?: unknown;\n input?: unknown;\n}\n\ninterface LooseToolCallChunk {\n id?: string;\n name?: string;\n args?: string;\n index?: number;\n type: \"tool_call_chunk\";\n}\n\nfunction extractToolCallChunks(blocks: ContentBlock[]): LooseToolCallChunk[] {\n const out: LooseToolCallChunk[] = [];\n for (const block of blocks) {\n if (block.type !== \"tool_call_chunk\") continue;\n const tc = block as {\n id?: string;\n name?: string;\n args?: string;\n index?: number;\n };\n out.push({\n id: tc.id,\n name: tc.name,\n args: tc.args,\n index: tc.index,\n type: \"tool_call_chunk\",\n });\n }\n return out;\n}\n\nfunction normalizeToolCallArgs(value: unknown): Record<string, unknown> {\n if (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n if (typeof value === \"string\" && value.length > 0) {\n try {\n const parsed = JSON.parse(value);\n if (\n parsed != null &&\n typeof parsed === \"object\" &&\n !Array.isArray(parsed)\n ) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // Partial streaming input is represented via tool_call_chunks.\n }\n }\n return {};\n}\n"],"mappings":"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"}