diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0cd4c7f52..725747ca31f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1414,6 +1414,7 @@ Docs: https://docs.openclaw.ai - Agents/compaction: cap summarization output reserve tokens to the selected model's `maxTokens` so 1M-context Anthropic compactions do not request more output than the API permits. Fixes #54383. - Control UI/login: replace raw connection failures with structured, actionable login guidance for auth, pairing, insecure HTTP, origin, protocol, and transport failures. Thanks @BunsDev. - Agents/tools: fail `exec host=node` before `system.run` when the selected node is known to be disconnected, with an actionable reconnect message instead of a raw node invoke failure. Thanks @BunsDev. +- Agents/tool-result guard: ignore internal tool-result `details` when estimating model-visible context, so large diagnostic metadata no longer triggers unnecessary truncation or compaction even though the provider boundary already strips `details` before model conversion. (#75525) Thanks @zqchris. - Agents/models: accept legacy `anthropic-cli/*` model refs as Claude CLI runtime refs instead of failing model resolution with `Unknown model`. Thanks @BunsDev. - Agents/tools: keep restrictive-profile tool-section warnings scoped to the configured sections whose tools are still missing from `alsoAllow`, so already re-allowed filesystem tools do not make exec-only fixes look broader than they are. Thanks @BunsDev. - Agents/tools: avoid warning messaging-only agents about inherited global `tools.exec` or `tools.fs` sections when the agent profile did not configure those tool sections itself. Thanks @BunsDev. diff --git a/src/agents/pi-embedded-runner/tool-result-char-estimator.ts b/src/agents/pi-embedded-runner/tool-result-char-estimator.ts index 6928bf3e7e7..b12f326dbab 100644 --- a/src/agents/pi-embedded-runner/tool-result-char-estimator.ts +++ b/src/agents/pi-embedded-runner/tool-result-char-estimator.ts @@ -125,10 +125,9 @@ function estimateMessageChars(msg: AgentMessage): number { } if (isToolResultMessage(msg)) { + // `details` is stripped before provider conversion; estimate only visible content. const content = getToolResultContent(msg); - let chars = estimateContentBlockChars(content); - const details = (msg as { details?: unknown }).details; - chars += estimateUnknownChars(details); + const chars = estimateContentBlockChars(content); const weightedChars = Math.ceil( chars * (CHARS_PER_TOKEN_ESTIMATE / TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE), ); diff --git a/src/agents/pi-embedded-runner/tool-result-context-guard.test.ts b/src/agents/pi-embedded-runner/tool-result-context-guard.test.ts index 14071d42909..7fdcf92f880 100644 --- a/src/agents/pi-embedded-runner/tool-result-context-guard.test.ts +++ b/src/agents/pi-embedded-runner/tool-result-context-guard.test.ts @@ -371,6 +371,36 @@ describe("installToolResultContextGuard", () => { expect((err as MidTurnPrecheckSignal).request.route).toBe("compact_only"); } }); + it("does not count tool-result details toward the context budget", async () => { + const agent = makeGuardableAgent(); + const contextForNextCall = [ + makeToolResultWithDetails("call_small_text", "x".repeat(100), "d".repeat(50_000)), + makeToolResultWithDetails("call_another", "y".repeat(120), "e".repeat(80_000)), + ]; + + const transformed = (await applyGuardToContext(agent, contextForNextCall)) as AgentMessage[]; + + expect(transformed).toBe(contextForNextCall); + expect(getToolResultText(transformed[0])).toBe("x".repeat(100)); + expect(getToolResultText(transformed[1])).toBe("y".repeat(120)); + expect((contextForNextCall[0] as { details?: unknown }).details).toBeDefined(); + expect((contextForNextCall[1] as { details?: unknown }).details).toBeDefined(); + }); + + it("ignores large tool-result details when deciding preemptive overflow", async () => { + const agent = makeGuardableAgent(); + const contextForNextCall = [ + makeUser("small user prompt"), + makeToolResultWithDetails("call_1", "a".repeat(50), "d".repeat(30_000)), + makeToolResultWithDetails("call_2", "b".repeat(50), "d".repeat(30_000)), + makeToolResultWithDetails("call_3", "c".repeat(50), "d".repeat(30_000)), + makeToolResultWithDetails("call_4", "e".repeat(50), "d".repeat(30_000)), + ]; + + const transformed = (await applyGuardToContext(agent, contextForNextCall)) as AgentMessage[]; + + expect(transformed).toBe(contextForNextCall); + }); }); type MockedEngine = ContextEngine & {