diff --git a/mem0-ts/src/oss/src/llms/anthropic.ts b/mem0-ts/src/oss/src/llms/anthropic.ts index a557dac90..0fcf43284 100644 --- a/mem0-ts/src/oss/src/llms/anthropic.ts +++ b/mem0-ts/src/oss/src/llms/anthropic.ts @@ -100,12 +100,16 @@ export class AnthropicLLM implements LLM { return { content, role: "assistant", toolCalls }; } - const firstBlock = response.content[0]; - if (firstBlock.type === "text") { - return firstBlock.text; - } else { - throw new Error("Unexpected response type from Anthropic API"); + // Thinking-enabled responses put a thinking block before the text block, + // and a response can carry no text block at all, so find the text block + // like the tools branch above instead of indexing content[0]. Mirrors the + // Python provider (#6481). + for (const block of response.content) { + if (block.type === "text") { + return block.text; + } } + return ""; } async generateChat(messages: Message[]): Promise { diff --git a/mem0-ts/src/oss/tests/anthropic-llm.test.ts b/mem0-ts/src/oss/tests/anthropic-llm.test.ts index b7b038fb5..af1262003 100644 --- a/mem0-ts/src/oss/tests/anthropic-llm.test.ts +++ b/mem0-ts/src/oss/tests/anthropic-llm.test.ts @@ -72,6 +72,37 @@ describe("AnthropicLLM (unit)", () => { expect(callArgs.tool_choice).toBeUndefined(); }); + // Regression: thinking-enabled models emit a thinking block before the text + // block. Indexing content[0] threw "Unexpected response type"; the text block + // must be found by type instead (TS parity with #6481). + it("returns the text block when a thinking block precedes it (no tools)", async () => { + mockCreate.mockResolvedValueOnce({ + content: [ + { type: "thinking", thinking: "Let me reason about this." }, + { type: "text", text: '{"facts": ["fact1"]}' }, + ], + }); + + const llm = new AnthropicLLM({ apiKey: "test-key" }); + const result = await llm.generateResponse([ + { role: "user", content: "Hello" }, + ]); + + expect(result).toBe('{"facts": ["fact1"]}'); + }); + + // A response carrying no text block at all must resolve to "" rather than throw. + it("returns an empty string when no text block is present (no tools)", async () => { + mockCreate.mockResolvedValueOnce({ + content: [{ type: "thinking", thinking: "Thinking only." }], + }); + + const llm = new AnthropicLLM({ apiKey: "test-key" }); + await expect( + llm.generateResponse([{ role: "user", content: "Hello" }]), + ).resolves.toBe(""); + }); + // Bug #1 regression: bare string "auto" must NOT be sent; object form required it("forwards tool_choice as { type: 'auto' } (not bare string) when tools are provided", async () => { mockCreate.mockResolvedValueOnce({