|
|
@@ -0,0 +1,414 @@
|
|
|
+import { Anthropic } from "@anthropic-ai/sdk"
|
|
|
+import {
|
|
|
+ appendEnvironmentDetails,
|
|
|
+ removeEnvironmentDetailsBlocks,
|
|
|
+ stripAppendedEnvironmentDetails,
|
|
|
+ UserContentBlock,
|
|
|
+} from "../appendEnvironmentDetails"
|
|
|
+
|
|
|
+describe("appendEnvironmentDetails", () => {
|
|
|
+ const envDetails = "<environment_details>\n# Test\nSome details\n</environment_details>"
|
|
|
+
|
|
|
+ describe("empty content", () => {
|
|
|
+ it("should return a text block when content is empty", () => {
|
|
|
+ const result = appendEnvironmentDetails([], envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ expect(result[0].type).toBe("text")
|
|
|
+ expect((result[0] as Anthropic.Messages.TextBlockParam).text).toBe(envDetails)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe("text block handling", () => {
|
|
|
+ it("should append to the last text block", () => {
|
|
|
+ const content: UserContentBlock[] = [{ type: "text", text: "User message" }]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ expect(result[0].type).toBe("text")
|
|
|
+ expect((result[0] as Anthropic.Messages.TextBlockParam).text).toBe("User message\n\n" + envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should append to the last text block when multiple text blocks exist", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ { type: "text", text: "First message" },
|
|
|
+ { type: "text", text: "Second message" },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ expect((result[0] as Anthropic.Messages.TextBlockParam).text).toBe("First message")
|
|
|
+ expect((result[1] as Anthropic.Messages.TextBlockParam).text).toBe("Second message\n\n" + envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should not mutate the original content array", () => {
|
|
|
+ const content: UserContentBlock[] = [{ type: "text", text: "Original" }]
|
|
|
+
|
|
|
+ appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect((content[0] as Anthropic.Messages.TextBlockParam).text).toBe("Original")
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe("tool_result block handling", () => {
|
|
|
+ it("should append to tool_result with string content", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Tool result text",
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ expect(result[0].type).toBe("tool_result")
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ expect(toolResult.content).toBe("Tool result text\n\n" + envDetails)
|
|
|
+ expect(toolResult.tool_use_id).toBe("tool-123")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should append to tool_result with undefined content", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ } as Anthropic.Messages.ToolResultBlockParam,
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ expect(toolResult.content).toBe(envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should append to tool_result with array content containing text", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: [{ type: "text", text: "Result line 1" }],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ expect(Array.isArray(toolResult.content)).toBe(true)
|
|
|
+ const contentArray = toolResult.content as Anthropic.Messages.TextBlockParam[]
|
|
|
+ expect(contentArray).toHaveLength(1)
|
|
|
+ expect(contentArray[0].text).toBe("Result line 1\n\n" + envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should add text block to tool_result with array content containing only images", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: [
|
|
|
+ {
|
|
|
+ type: "image",
|
|
|
+ source: { type: "base64", media_type: "image/png", data: "abc123" },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ expect(Array.isArray(toolResult.content)).toBe(true)
|
|
|
+ const contentArray = toolResult.content as Array<
|
|
|
+ Anthropic.Messages.TextBlockParam | Anthropic.Messages.ImageBlockParam
|
|
|
+ >
|
|
|
+ expect(contentArray).toHaveLength(2)
|
|
|
+ expect(contentArray[0].type).toBe("image")
|
|
|
+ expect(contentArray[1].type).toBe("text")
|
|
|
+ expect((contentArray[1] as Anthropic.Messages.TextBlockParam).text).toBe(envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should append to the last text block in tool_result array content", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: [
|
|
|
+ { type: "text", text: "First text" },
|
|
|
+ {
|
|
|
+ type: "image",
|
|
|
+ source: { type: "base64", media_type: "image/png", data: "abc123" },
|
|
|
+ },
|
|
|
+ { type: "text", text: "Last text" },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ const contentArray = toolResult.content as Array<
|
|
|
+ Anthropic.Messages.TextBlockParam | Anthropic.Messages.ImageBlockParam
|
|
|
+ >
|
|
|
+ expect(contentArray).toHaveLength(3)
|
|
|
+ expect((contentArray[0] as Anthropic.Messages.TextBlockParam).text).toBe("First text")
|
|
|
+ expect((contentArray[2] as Anthropic.Messages.TextBlockParam).text).toBe("Last text\n\n" + envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should preserve is_error flag on tool_result", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Error message",
|
|
|
+ is_error: true,
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ expect(toolResult.is_error).toBe(true)
|
|
|
+ expect(toolResult.content).toBe("Error message\n\n" + envDetails)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe("mixed content handling", () => {
|
|
|
+ it("should append to last text block when text comes after tool_result", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Tool result",
|
|
|
+ },
|
|
|
+ { type: "text", text: "User comment" },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ expect((result[0] as Anthropic.Messages.ToolResultBlockParam).content).toBe("Tool result")
|
|
|
+ expect((result[1] as Anthropic.Messages.TextBlockParam).text).toBe("User comment\n\n" + envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should append to last tool_result when no text block follows", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ { type: "text", text: "User message" },
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Tool result",
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ expect((result[0] as Anthropic.Messages.TextBlockParam).text).toBe("User message")
|
|
|
+ expect((result[1] as Anthropic.Messages.ToolResultBlockParam).content).toBe("Tool result\n\n" + envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should add new text block when content only has images", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "image",
|
|
|
+ source: { type: "base64", media_type: "image/png", data: "abc123" },
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ expect(result[0].type).toBe("image")
|
|
|
+ expect(result[1].type).toBe("text")
|
|
|
+ expect((result[1] as Anthropic.Messages.TextBlockParam).text).toBe(envDetails)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should handle multiple tool_results and append to the last one", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-1",
|
|
|
+ content: "First result",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-2",
|
|
|
+ content: "Second result",
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = appendEnvironmentDetails(content, envDetails)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ expect((result[0] as Anthropic.Messages.ToolResultBlockParam).content).toBe("First result")
|
|
|
+ expect((result[1] as Anthropic.Messages.ToolResultBlockParam).content).toBe(
|
|
|
+ "Second result\n\n" + envDetails,
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+describe("removeEnvironmentDetailsBlocks", () => {
|
|
|
+ const envDetailsBlock: UserContentBlock = {
|
|
|
+ type: "text",
|
|
|
+ text: "<environment_details>\n# Test\nSome details\n</environment_details>",
|
|
|
+ }
|
|
|
+
|
|
|
+ it("should remove standalone environment_details text blocks", () => {
|
|
|
+ const content: UserContentBlock[] = [{ type: "text", text: "User message" }, envDetailsBlock]
|
|
|
+
|
|
|
+ const result = removeEnvironmentDetailsBlocks(content)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ expect((result[0] as Anthropic.Messages.TextBlockParam).text).toBe("User message")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should not remove text blocks that mention environment_details but aren't complete blocks", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ { type: "text", text: "Let me explain <environment_details> tags" },
|
|
|
+ { type: "text", text: "The closing tag is </environment_details>" },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = removeEnvironmentDetailsBlocks(content)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should preserve non-text blocks", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Result",
|
|
|
+ },
|
|
|
+ envDetailsBlock,
|
|
|
+ {
|
|
|
+ type: "image",
|
|
|
+ source: { type: "base64", media_type: "image/png", data: "abc123" },
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = removeEnvironmentDetailsBlocks(content)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(2)
|
|
|
+ expect(result[0].type).toBe("tool_result")
|
|
|
+ expect(result[1].type).toBe("image")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should handle empty content", () => {
|
|
|
+ const result = removeEnvironmentDetailsBlocks([])
|
|
|
+ expect(result).toHaveLength(0)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should handle whitespace around environment_details tags", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "text",
|
|
|
+ text: " <environment_details>\n# Test\nSome details\n</environment_details> ",
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = removeEnvironmentDetailsBlocks(content)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(0)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+describe("stripAppendedEnvironmentDetails", () => {
|
|
|
+ const envDetails = "<environment_details>\n# Test\nSome details\n</environment_details>"
|
|
|
+
|
|
|
+ it("should strip environment details from the end of a text block", () => {
|
|
|
+ const content: UserContentBlock[] = [{ type: "text", text: "User message\n\n" + envDetails }]
|
|
|
+
|
|
|
+ const result = stripAppendedEnvironmentDetails(content)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ expect((result[0] as Anthropic.Messages.TextBlockParam).text).toBe("User message")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should strip environment details from tool_result string content", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Tool result\n\n" + envDetails,
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = stripAppendedEnvironmentDetails(content)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ expect((result[0] as Anthropic.Messages.ToolResultBlockParam).content).toBe("Tool result")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should strip environment details from tool_result array content", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: [{ type: "text", text: "Result text\n\n" + envDetails }],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = stripAppendedEnvironmentDetails(content)
|
|
|
+
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ const contentArray = toolResult.content as Anthropic.Messages.TextBlockParam[]
|
|
|
+ expect(contentArray[0].text).toBe("Result text")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should also remove standalone environment_details blocks", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ { type: "text", text: "User message" },
|
|
|
+ { type: "text", text: envDetails },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = stripAppendedEnvironmentDetails(content)
|
|
|
+
|
|
|
+ expect(result).toHaveLength(1)
|
|
|
+ expect((result[0] as Anthropic.Messages.TextBlockParam).text).toBe("User message")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should handle content without environment details", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ { type: "text", text: "User message" },
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Tool result",
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = stripAppendedEnvironmentDetails(content)
|
|
|
+
|
|
|
+ expect(result).toEqual(content)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should handle empty content", () => {
|
|
|
+ const result = stripAppendedEnvironmentDetails([])
|
|
|
+ expect(result).toHaveLength(0)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should preserve is_error flag when stripping from tool_result", () => {
|
|
|
+ const content: UserContentBlock[] = [
|
|
|
+ {
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "tool-123",
|
|
|
+ content: "Error\n\n" + envDetails,
|
|
|
+ is_error: true,
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const result = stripAppendedEnvironmentDetails(content)
|
|
|
+
|
|
|
+ const toolResult = result[0] as Anthropic.Messages.ToolResultBlockParam
|
|
|
+ expect(toolResult.is_error).toBe(true)
|
|
|
+ expect(toolResult.content).toBe("Error")
|
|
|
+ })
|
|
|
+})
|