diff --git a/src/core/task/__tests__/appendEnvironmentDetails.spec.ts b/src/core/task/__tests__/appendEnvironmentDetails.spec.ts
index 590d4ab5a0c..54ccb1ee1ed 100644
--- a/src/core/task/__tests__/appendEnvironmentDetails.spec.ts
+++ b/src/core/task/__tests__/appendEnvironmentDetails.spec.ts
@@ -1,5 +1,10 @@
import { Anthropic } from "@anthropic-ai/sdk"
-import { appendEnvironmentDetails, removeEnvironmentDetailsBlocks, UserContentBlock } from "../appendEnvironmentDetails"
+import {
+ appendEnvironmentDetails,
+ removeEnvironmentDetailsBlocks,
+ stripAppendedEnvironmentDetails,
+ UserContentBlock,
+} from "../appendEnvironmentDetails"
describe("appendEnvironmentDetails", () => {
const envDetails = "\n# Test\nSome details\n"
@@ -314,3 +319,96 @@ describe("removeEnvironmentDetailsBlocks", () => {
expect(result).toHaveLength(0)
})
})
+
+describe("stripAppendedEnvironmentDetails", () => {
+ const envDetails = "\n# Test\nSome details\n"
+
+ 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")
+ })
+})
diff --git a/src/core/task/appendEnvironmentDetails.ts b/src/core/task/appendEnvironmentDetails.ts
index 5e213477887..09495d89e7f 100644
--- a/src/core/task/appendEnvironmentDetails.ts
+++ b/src/core/task/appendEnvironmentDetails.ts
@@ -143,3 +143,96 @@ export function removeEnvironmentDetailsBlocks(content: UserContentBlock[]): Use
return true
})
}
+
+/**
+ * Strips environment details from the last text block or tool_result in the content.
+ * This handles the case where environment details were appended to an existing block
+ * rather than added as a standalone block.
+ *
+ * @param content - Array of content blocks
+ * @returns New array with environment details stripped from the last suitable block
+ */
+export function stripAppendedEnvironmentDetails(content: UserContentBlock[]): UserContentBlock[] {
+ if (content.length === 0) {
+ return content
+ }
+
+ // First, remove any standalone environment_details blocks
+ let result = removeEnvironmentDetailsBlocks(content)
+
+ if (result.length === 0) {
+ return result
+ }
+
+ // Then, strip appended environment details from the last block
+ const lastIndex = result.length - 1
+ const lastBlock = result[lastIndex]
+
+ if (lastBlock.type === "text") {
+ const strippedText = stripEnvDetailsFromText(lastBlock.text)
+ if (strippedText !== lastBlock.text) {
+ result = [...result]
+ result[lastIndex] = { type: "text" as const, text: strippedText }
+ }
+ } else if (lastBlock.type === "tool_result") {
+ const strippedToolResult = stripEnvDetailsFromToolResult(lastBlock)
+ if (strippedToolResult !== lastBlock) {
+ result = [...result]
+ result[lastIndex] = strippedToolResult
+ }
+ }
+
+ return result
+}
+
+/**
+ * Strips environment details from the end of a text string.
+ */
+function stripEnvDetailsFromText(text: string): string {
+ // Match environment details at the end of the string, with optional preceding newlines
+ const envDetailsPattern = /\n*[\s\S]*<\/environment_details>\s*$/
+ return text.replace(envDetailsPattern, "")
+}
+
+/**
+ * Strips environment details from a tool_result block's content.
+ */
+function stripEnvDetailsFromToolResult(
+ toolResult: Anthropic.Messages.ToolResultBlockParam,
+): Anthropic.Messages.ToolResultBlockParam {
+ const { content, ...rest } = toolResult
+
+ if (content === undefined || content === null) {
+ return toolResult
+ }
+
+ if (typeof content === "string") {
+ const strippedContent = stripEnvDetailsFromText(content)
+ if (strippedContent === content) {
+ return toolResult
+ }
+ return { ...rest, content: strippedContent }
+ }
+
+ if (Array.isArray(content)) {
+ let changed = false
+ const newContent = content.map((block) => {
+ if (block.type === "text") {
+ const strippedText = stripEnvDetailsFromText(block.text)
+ if (strippedText !== block.text) {
+ changed = true
+ return { type: "text" as const, text: strippedText }
+ }
+ }
+ return block
+ })
+
+ if (!changed) {
+ return toolResult
+ }
+
+ return { ...rest, content: newContent }
+ }
+
+ return toolResult
+}