using ClawdDotNet.Core.Engine; using ClawdDotNet.Core.Tests.Infrastructure; using Shouldly; namespace ClawdDotNet.Core.Tests.Engine; /// /// T2 from the token analysis: a single tool result could blow up the context. /// A WebFetch with the default limit of 512 KB corresponds to about 130,000 tokens in /// ONE answer — compaction only applies afterwards, the request has long been paid for. /// public sealed class ToolResultTruncationTests { [Fact] public async Task A_huge_tool_result_is_capped_before_it_enters_the_context() { var veryLarge = new string('x', 500_000); var fixture = new EngineFixture().WithTool(FakeTool.Returning(veryLarge)); var agent = fixture.AddAgent("agent-trunc", "TestTool"); agent.MaxToolResultChars = 16_000; fixture.Client .RespondsWithToolCall("TestTool") .RespondsWithText("Fertig"); await fixture.Engine.ChatAsync(agent, "Hol die Daten", "test-instance", default); var context = fixture.Engine.GetChatContext(agent.AgentId); var toolMessage = context.Single(m => m.Role == "tool"); toolMessage.Content!.Length.ShouldBeLessThan(20_000); toolMessage.Content.ShouldContain("gekürzt"); } [Fact] public async Task A_small_tool_result_stays_unchanged() { const string small = "Alles in Ordnung."; var fixture = new EngineFixture().WithTool(FakeTool.Returning(small)); var agent = fixture.AddAgent("agent-klein", "TestTool"); fixture.Client .RespondsWithToolCall("TestTool") .RespondsWithText("Fertig"); await fixture.Engine.ChatAsync(agent, "Prüfe", "test-instance", default); var context = fixture.Engine.GetChatContext(agent.AgentId); // Since #193 unchanged means: untruncated, only framed. context.Single(m => m.Role == "tool").Content.ShouldBe(UntrustedContent.Frame("TestTool", small)); } [Theory] [InlineData(100, 50)] [InlineData(1_000, 999)] [InlineData(50_000, 16_000)] public void Capped_results_name_the_number_of_dropped_characters(int length, int limit) { var result = AgentEngine.TruncateToolResult(new string('a', length), limit); result.ShouldStartWith(new string('a', limit)); result.ShouldContain((length - limit).ToString("N0")); } [Fact] public void Without_limit_nothing_is_capped() { var original = new string('a', 100_000); AgentEngine.TruncateToolResult(original, 0).ShouldBe(original); } [Fact] public void Exactly_at_the_limit_nothing_is_capped() { var original = new string('a', 1_000); AgentEngine.TruncateToolResult(original, 1_000).ShouldBe(original); } }