UNPKG

@langchain/core

Version:
1 lines 6.37 kB
{"version":3,"file":"openrouter.cjs","names":["_isString"],"sources":["../../../src/messages/block_translators/openrouter.ts"],"sourcesContent":["import { AIMessage } from \"../ai.js\";\nimport { ContentBlock } from \"../content/index.js\";\nimport type { StandardContentBlockTranslator } from \"./index.js\";\nimport { _isString } from \"./utils.js\";\n\n/**\n * Converts an OpenRouter AI message to an array of v1 standard content blocks.\n *\n * OpenRouter returns reasoning output through two places on the Chat\n * Completions response:\n *\n * 1. `message.reasoning` / `delta.reasoning` — a flat string that summarizes\n * the model's chain of thought. The `@langchain/openrouter` converter\n * normalizes this into `additional_kwargs.reasoning_content` so it matches\n * the DeepSeek convention already used elsewhere in LangChain.\n * 2. `message.reasoning_details` / `delta.reasoning_details` — a structured\n * array of provider-specific reasoning artifacts (see the\n * `reasoning.summary` / `reasoning.encrypted` / `reasoning.text` union in\n * the OpenRouter API types). The converter preserves these verbatim under\n * `additional_kwargs.reasoning_details` for round-tripping back to the\n * provider on subsequent turns (e.g. Anthropic extended thinking requires\n * the original `signature` to be echoed back).\n *\n * When `reasoning_details` is present, visible blocks are emitted from\n * `reasoning.summary` / `reasoning.text` entries. If the array contains only\n * opaque artifacts (e.g. `reasoning.encrypted`), the flat `reasoning_content`\n * string is used as a fallback when available.\n *\n * @param message - The AI message containing OpenRouter-formatted content\n * @returns Array of content blocks in v1 standard format\n *\n * @example\n * ```typescript\n * const message = new AIMessage({\n * content: \"The answer is 42\",\n * additional_kwargs: { reasoning_content: \"Let me think about this...\" },\n * response_metadata: { model_provider: \"openrouter\" },\n * });\n * message.contentBlocks;\n * // [\n * // { type: \"reasoning\", reasoning: \"Let me think about this...\" },\n * // { type: \"text\", text: \"The answer is 42\" }\n * // ]\n * ```\n */\nexport function convertToV1FromOpenRouterMessage(\n message: AIMessage\n): Array<ContentBlock.Standard> {\n const blocks: Array<ContentBlock.Standard> = [];\n\n // Prefer structured reasoning_details when present — they can carry\n // multiple distinct reasoning artifacts (summary, encrypted, text).\n const reasoningDetails = message.additional_kwargs?.reasoning_details;\n let hasVisibleReasoningFromDetails = false;\n if (Array.isArray(reasoningDetails) && reasoningDetails.length > 0) {\n for (const detail of reasoningDetails) {\n if (detail == null || typeof detail !== \"object\") continue;\n const type = (detail as { type?: unknown }).type;\n if (type === \"reasoning.summary\") {\n const summary = (detail as { summary?: unknown }).summary;\n if (_isString(summary) && summary.length > 0) {\n blocks.push({ type: \"reasoning\", reasoning: summary });\n hasVisibleReasoningFromDetails = true;\n }\n } else if (type === \"reasoning.text\") {\n const text = (detail as { text?: unknown }).text;\n if (_isString(text) && text.length > 0) {\n blocks.push({ type: \"reasoning\", reasoning: text });\n hasVisibleReasoningFromDetails = true;\n }\n }\n // `reasoning.encrypted` details carry no human-readable text (only an\n // opaque `data` blob that must be echoed back to the provider), so they\n // do not become visible reasoning blocks. They stay in\n // `additional_kwargs.reasoning_details` for round-tripping.\n }\n }\n\n if (!hasVisibleReasoningFromDetails) {\n const reasoningContent = message.additional_kwargs?.reasoning_content;\n if (_isString(reasoningContent) && reasoningContent.length > 0) {\n blocks.push({\n type: \"reasoning\",\n reasoning: reasoningContent,\n });\n }\n }\n\n // Handle text content (string or multi-block array).\n if (typeof message.content === \"string\") {\n if (message.content.length > 0) {\n blocks.push({\n type: \"text\",\n text: message.content,\n });\n }\n } else {\n for (const block of message.content) {\n if (\n typeof block === \"object\" &&\n \"type\" in block &&\n block.type === \"text\" &&\n \"text\" in block &&\n _isString(block.text)\n ) {\n blocks.push({\n type: \"text\",\n text: block.text,\n });\n }\n }\n }\n\n // Add tool calls if present.\n for (const toolCall of message.tool_calls ?? []) {\n blocks.push({\n type: \"tool_call\",\n id: toolCall.id,\n name: toolCall.name,\n args: toolCall.args,\n });\n }\n\n return blocks;\n}\n\nexport const ChatOpenRouterTranslator: StandardContentBlockTranslator = {\n translateContent: convertToV1FromOpenRouterMessage,\n translateContentChunk: convertToV1FromOpenRouterMessage,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,iCACd,SAC8B;CAC9B,MAAM,SAAuC,CAAC;CAI9C,MAAM,mBAAmB,QAAQ,mBAAmB;CACpD,IAAI,iCAAiC;CACrC,IAAI,MAAM,QAAQ,gBAAgB,KAAK,iBAAiB,SAAS,GAC/D,KAAK,MAAM,UAAU,kBAAkB;EACrC,IAAI,UAAU,QAAQ,OAAO,WAAW,UAAU;EAClD,MAAM,OAAQ,OAA8B;EAC5C,IAAI,SAAS,qBAAqB;GAChC,MAAM,UAAW,OAAiC;GAClD,IAAIA,cAAAA,UAAU,OAAO,KAAK,QAAQ,SAAS,GAAG;IAC5C,OAAO,KAAK;KAAE,MAAM;KAAa,WAAW;IAAQ,CAAC;IACrD,iCAAiC;GACnC;EACF,OAAO,IAAI,SAAS,kBAAkB;GACpC,MAAM,OAAQ,OAA8B;GAC5C,IAAIA,cAAAA,UAAU,IAAI,KAAK,KAAK,SAAS,GAAG;IACtC,OAAO,KAAK;KAAE,MAAM;KAAa,WAAW;IAAK,CAAC;IAClD,iCAAiC;GACnC;EACF;CAKF;CAGF,IAAI,CAAC,gCAAgC;EACnC,MAAM,mBAAmB,QAAQ,mBAAmB;EACpD,IAAIA,cAAAA,UAAU,gBAAgB,KAAK,iBAAiB,SAAS,GAC3D,OAAO,KAAK;GACV,MAAM;GACN,WAAW;EACb,CAAC;CAEL;CAGA,IAAI,OAAO,QAAQ,YAAY,UACzB;MAAA,QAAQ,QAAQ,SAAS,GAC3B,OAAO,KAAK;GACV,MAAM;GACN,MAAM,QAAQ;EAChB,CAAC;CAAA,OAGH,KAAK,MAAM,SAAS,QAAQ,SAC1B,IACE,OAAO,UAAU,YACjB,UAAU,SACV,MAAM,SAAS,UACf,UAAU,SACVA,cAAAA,UAAU,MAAM,IAAI,GAEpB,OAAO,KAAK;EACV,MAAM;EACN,MAAM,MAAM;CACd,CAAC;CAMP,KAAK,MAAM,YAAY,QAAQ,cAAc,CAAC,GAC5C,OAAO,KAAK;EACV,MAAM;EACN,IAAI,SAAS;EACb,MAAM,SAAS;EACf,MAAM,SAAS;CACjB,CAAC;CAGH,OAAO;AACT;AAEA,MAAa,2BAA2D;CACtE,kBAAkB;CAClB,uBAAuB;AACzB"}