@langchain/langgraph-sdk
Version:
Client library for interacting with the LangGraph API
98 lines (97 loc) • 4.08 kB
JavaScript
import { AIMessage, HumanMessage, RemoveMessage, SystemMessage, ToolMessage, coerceMessageLikeToMessage } from "@langchain/core/messages";
//#region src/stream/message-coercion.ts
/**
* Stream-local message coercion for serialized messages returned by
* `getState()`, `getHistory()`, and `values` events.
*
* LangGraph API payloads may carry v1 content blocks as snake_case
* `content_blocks`, while `@langchain/core` message constructors only
* understand camelCase `contentBlocks` (or `content`). Normalize that
* boundary here so stream consumers always see `BaseMessage.text`.
*/
function tryCoerceMessageLikeToMessage(message) {
const normalized = normalizeAIMessageToolCalls(message);
if (normalized.type === "human" || normalized.type === "user") return new HumanMessage(normalized);
if (normalized.type === "ai" || normalized.type === "assistant") return new AIMessage(normalized);
if (normalized.type === "system") return new SystemMessage(normalized);
if (normalized.type === "tool" && "tool_call_id" in normalized) return new ToolMessage({
...normalized,
tool_call_id: normalized.tool_call_id
});
if (normalized.type === "remove" && normalized.id != null) return new RemoveMessage({
...normalized,
id: normalized.id
});
return coerceMessageLikeToMessage(normalized);
}
/**
* Ensures all messages in an array are BaseMessage class instances.
* Messages that are already class instances pass through unchanged.
*/
function ensureMessageInstances(messages) {
return messages.map((msg) => {
if (typeof msg.getType === "function") return msg;
return tryCoerceMessageLikeToMessage(msg);
});
}
function normalizeSerializedContentBlocks(message) {
const record = message;
const contentBlocks = record.contentBlocks ?? record.content_blocks;
if (!Array.isArray(contentBlocks) || contentBlocks.length === 0) return message;
const shouldPreferContentBlocks = isEmptyContent(record.content) || !hasTextContent(record.content) && hasTextContent(contentBlocks);
if (!shouldPreferContentBlocks && record.contentBlocks === contentBlocks) return message;
return {
...message,
content: shouldPreferContentBlocks ? contentBlocks : record.content,
contentBlocks
};
}
function normalizeAIMessageToolCalls(message) {
const normalized = normalizeSerializedContentBlocks(message);
const record = normalized;
if (Array.isArray(record.tool_calls) && record.tool_calls.length > 0) return normalized;
const toolCalls = extractToolCallsFromContent(record.content);
if (toolCalls.length === 0) return normalized;
return {
...normalized,
tool_calls: toolCalls
};
}
function extractToolCallsFromContent(content) {
if (!Array.isArray(content)) return [];
return content.flatMap((block) => {
if (block == null || typeof block !== "object") return [];
const record = block;
if (record.type !== "tool_call" && record.type !== "tool_use") return [];
return [{
id: record.id ?? "",
name: record.name ?? "",
args: normalizeToolCallArgs(record.args ?? record.input),
type: "tool_call"
}];
});
}
function normalizeToolCallArgs(value) {
if (value != null && typeof value === "object" && !Array.isArray(value)) return value;
if (typeof value === "string" && value.length > 0) try {
const parsed = JSON.parse(value);
if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
} catch {}
return {};
}
function isEmptyContent(content) {
return content == null || content === "" || Array.isArray(content) && content.length === 0;
}
function hasTextContent(content) {
if (typeof content === "string") return content.length > 0;
if (!Array.isArray(content)) return false;
return content.some((block) => {
if (typeof block === "string") return block.length > 0;
if (block == null || typeof block !== "object") return false;
const record = block;
return record.type === "text" && typeof record.text === "string" && record.text.length > 0;
});
}
//#endregion
export { ensureMessageInstances, normalizeAIMessageToolCalls, tryCoerceMessageLikeToMessage };
//# sourceMappingURL=message-coercion.js.map