|
|
@@ -1098,6 +1098,108 @@ describe("session.message-v2.toModelMessage", () => {
|
|
|
},
|
|
|
])
|
|
|
})
|
|
|
+
|
|
|
+ test("substitutes space for empty text between signed reasoning blocks", async () => {
|
|
|
+ // Reproduces the bug pattern: [reasoning(sig), text(""), reasoning(sig), text(full)]
|
|
|
+ const assistantID = "m-assistant"
|
|
|
+ const input: MessageV2.WithParts[] = [
|
|
|
+ {
|
|
|
+ info: assistantInfo(assistantID, "m-parent"),
|
|
|
+ parts: [
|
|
|
+ { ...basePart(assistantID, "p1"), type: "step-start" },
|
|
|
+ {
|
|
|
+ ...basePart(assistantID, "p2"),
|
|
|
+ type: "reasoning",
|
|
|
+ text: "thinking-one",
|
|
|
+ metadata: { anthropic: { signature: "sig1" } },
|
|
|
+ },
|
|
|
+ { ...basePart(assistantID, "p3"), type: "text", text: "" },
|
|
|
+ { ...basePart(assistantID, "p4"), type: "step-start" },
|
|
|
+ {
|
|
|
+ ...basePart(assistantID, "p5"),
|
|
|
+ type: "reasoning",
|
|
|
+ text: "thinking-two",
|
|
|
+ metadata: { anthropic: { signature: "sig2" } },
|
|
|
+ },
|
|
|
+ { ...basePart(assistantID, "p6"), type: "text", text: "the answer" },
|
|
|
+ ] as MessageV2.Part[],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = await MessageV2.toModelMessages(input, model)
|
|
|
+
|
|
|
+ // step-start splits into two assistant messages; SDK's groupIntoBlocks merges them later
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ expect((result[0].content as any[]).find((p) => p.type === "text").text).toBe(" ")
|
|
|
+ expect((result[1].content as any[]).find((p) => p.type === "text").text).toBe("the answer")
|
|
|
+ })
|
|
|
+
|
|
|
+ test("substitutes space for empty text when reasoning signature is under 'bedrock' namespace", async () => {
|
|
|
+ // AWS Bedrock hosts Anthropic Claude but stores signatures under metadata.bedrock
|
|
|
+ const assistantID = "m-assistant-bedrock"
|
|
|
+ const input: MessageV2.WithParts[] = [
|
|
|
+ {
|
|
|
+ info: assistantInfo(assistantID, "m-parent"),
|
|
|
+ parts: [
|
|
|
+ {
|
|
|
+ ...basePart(assistantID, "p1"),
|
|
|
+ type: "reasoning",
|
|
|
+ text: "thinking-bedrock",
|
|
|
+ metadata: { bedrock: { signature: "bedrock-sig" } },
|
|
|
+ },
|
|
|
+ { ...basePart(assistantID, "p2"), type: "text", text: "" },
|
|
|
+ { ...basePart(assistantID, "p3"), type: "text", text: "answer" },
|
|
|
+ ] as MessageV2.Part[],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = await MessageV2.toModelMessages(input, model)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ const texts = (result[0].content as any[]).filter((p) => p.type === "text")
|
|
|
+ expect(texts.map((t) => t.text)).toStrictEqual([" ", "answer"])
|
|
|
+ })
|
|
|
+
|
|
|
+ test("leaves empty text alone when reasoning has no Anthropic signature", async () => {
|
|
|
+ // Non-Anthropic providers' reasoning doesn't position-validate, so empty text
|
|
|
+ // should be filtered normally rather than substituted.
|
|
|
+ const assistantID = "m-assistant-unsigned"
|
|
|
+ const input: MessageV2.WithParts[] = [
|
|
|
+ {
|
|
|
+ info: assistantInfo(assistantID, "m-parent"),
|
|
|
+ parts: [
|
|
|
+ { ...basePart(assistantID, "p1"), type: "reasoning", text: "thinking" },
|
|
|
+ { ...basePart(assistantID, "p2"), type: "text", text: "" },
|
|
|
+ { ...basePart(assistantID, "p3"), type: "text", text: "answer" },
|
|
|
+ ] as MessageV2.Part[],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = await MessageV2.toModelMessages(input, model)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ const texts = (result[0].content as any[]).filter((p) => p.type === "text")
|
|
|
+ expect(texts.map((t) => t.text)).toStrictEqual(["", "answer"])
|
|
|
+ })
|
|
|
+
|
|
|
+ test("leaves empty text alone in assistant messages without reasoning", async () => {
|
|
|
+ const assistantID = "m-assistant-no-reasoning"
|
|
|
+ const input: MessageV2.WithParts[] = [
|
|
|
+ {
|
|
|
+ info: assistantInfo(assistantID, "m-parent"),
|
|
|
+ parts: [
|
|
|
+ { ...basePart(assistantID, "p1"), type: "text", text: "" },
|
|
|
+ { ...basePart(assistantID, "p2"), type: "text", text: "hello" },
|
|
|
+ ] as MessageV2.Part[],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = await MessageV2.toModelMessages(input, model)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ const texts = (result[0].content as any[]).filter((p) => p.type === "text")
|
|
|
+ expect(texts.map((t) => t.text)).toStrictEqual(["", "hello"])
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
describe("session.message-v2.fromError", () => {
|