@langchain/core
Version:
Core LangChain.js abstractions and schemas
101 lines (100 loc) • 3.78 kB
JavaScript
import { _isString } from "./utils.js";
//#region src/messages/block_translators/openrouter.ts
/**
* Converts an OpenRouter AI message to an array of v1 standard content blocks.
*
* OpenRouter returns reasoning output through two places on the Chat
* Completions response:
*
* 1. `message.reasoning` / `delta.reasoning` — a flat string that summarizes
* the model's chain of thought. The `@langchain/openrouter` converter
* normalizes this into `additional_kwargs.reasoning_content` so it matches
* the DeepSeek convention already used elsewhere in LangChain.
* 2. `message.reasoning_details` / `delta.reasoning_details` — a structured
* array of provider-specific reasoning artifacts (see the
* `reasoning.summary` / `reasoning.encrypted` / `reasoning.text` union in
* the OpenRouter API types). The converter preserves these verbatim under
* `additional_kwargs.reasoning_details` for round-tripping back to the
* provider on subsequent turns (e.g. Anthropic extended thinking requires
* the original `signature` to be echoed back).
*
* When `reasoning_details` is present, visible blocks are emitted from
* `reasoning.summary` / `reasoning.text` entries. If the array contains only
* opaque artifacts (e.g. `reasoning.encrypted`), the flat `reasoning_content`
* string is used as a fallback when available.
*
* @param message - The AI message containing OpenRouter-formatted content
* @returns Array of content blocks in v1 standard format
*
* @example
* ```typescript
* const message = new AIMessage({
* content: "The answer is 42",
* additional_kwargs: { reasoning_content: "Let me think about this..." },
* response_metadata: { model_provider: "openrouter" },
* });
* message.contentBlocks;
* // [
* // { type: "reasoning", reasoning: "Let me think about this..." },
* // { type: "text", text: "The answer is 42" }
* // ]
* ```
*/
function convertToV1FromOpenRouterMessage(message) {
const blocks = [];
const reasoningDetails = message.additional_kwargs?.reasoning_details;
let hasVisibleReasoningFromDetails = false;
if (Array.isArray(reasoningDetails) && reasoningDetails.length > 0) for (const detail of reasoningDetails) {
if (detail == null || typeof detail !== "object") continue;
const type = detail.type;
if (type === "reasoning.summary") {
const summary = detail.summary;
if (_isString(summary) && summary.length > 0) {
blocks.push({
type: "reasoning",
reasoning: summary
});
hasVisibleReasoningFromDetails = true;
}
} else if (type === "reasoning.text") {
const text = detail.text;
if (_isString(text) && text.length > 0) {
blocks.push({
type: "reasoning",
reasoning: text
});
hasVisibleReasoningFromDetails = true;
}
}
}
if (!hasVisibleReasoningFromDetails) {
const reasoningContent = message.additional_kwargs?.reasoning_content;
if (_isString(reasoningContent) && reasoningContent.length > 0) blocks.push({
type: "reasoning",
reasoning: reasoningContent
});
}
if (typeof message.content === "string") {
if (message.content.length > 0) blocks.push({
type: "text",
text: message.content
});
} else for (const block of message.content) if (typeof block === "object" && "type" in block && block.type === "text" && "text" in block && _isString(block.text)) blocks.push({
type: "text",
text: block.text
});
for (const toolCall of message.tool_calls ?? []) blocks.push({
type: "tool_call",
id: toolCall.id,
name: toolCall.name,
args: toolCall.args
});
return blocks;
}
const ChatOpenRouterTranslator = {
translateContent: convertToV1FromOpenRouterMessage,
translateContentChunk: convertToV1FromOpenRouterMessage
};
//#endregion
export { ChatOpenRouterTranslator };
//# sourceMappingURL=openrouter.js.map