Skip to content

Commit 136fbc9

Browse files
Fix #1280: chat tool-result details body renders the actual returned payload
ChatOutputHtmlRenderer.RenderToolResultBody now always emits the full payload via RenderToolPayload, so opening the <details> shows the actual result string. The compact "(N lines)" indicator remains in the <summary> for the collapsed state. CopilotToolEventMapper.BuildSuccessResult now prefers DetailedContent over Content so the renderer receives the full text rather than the short summary variant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a1ce20b commit 136fbc9

4 files changed

Lines changed: 69 additions & 12 deletions

File tree

Phantom.Workspaces.Agent.Gui.Tests/ChatOutputHtmlRendererTests.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,16 +1077,21 @@ public void RenderToolCallPair_WithLargeSingleLineResult_SummaryShowsCharacterCo
10771077
}
10781078

10791079
[Fact]
1080-
public void RenderToolCallPair_WithLargeResultJson_BodyDoesNotContainFullPayload()
1080+
public void RenderToolCallPair_WithLargeResultJson_BodyContainsFullPayload()
10811081
{
1082+
// #1280: the <details> body now renders the FULL escaped payload so the user can inspect
1083+
// the actual returned text; the compact "(N lines)" header stays in <summary>. Replaces
1084+
// the earlier wrong-behavior test that asserted the body did NOT contain the payload.
10821085
var lineCount = ChatOutputHtmlRenderer.MaxToolResultLines + 5;
10831086
var lines = Enumerable.Range(0, lineCount).Select(i => $"line-{i}").ToList();
10841087
lines[10] = "UNIQUE_PAYLOAD_MARKER_XYZ";
10851088
var largeResult = string.Join("\n", lines);
10861089

10871090
var html = ChatOutputHtmlRenderer.RenderToolCallPair("c0", "my_tool", "{}", largeResult);
10881091

1089-
Assert.DoesNotContain("UNIQUE_PAYLOAD_MARKER_XYZ", html, StringComparison.Ordinal);
1092+
Assert.Contains("UNIQUE_PAYLOAD_MARKER_XYZ", html, StringComparison.Ordinal);
1093+
// Summary line still shows the compact "(N lines)" header for the collapsed state.
1094+
Assert.Contains($"result ({lineCount} lines)", html, StringComparison.Ordinal);
10901095
}
10911096

10921097
[Fact]

Phantom.Workspaces.Agent.Gui/ViewModels/DocumentModels/ChatOutputHtmlRenderer.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -830,13 +830,15 @@ private static string SummarizeResult(string resultJson)
830830
? ToolResultOverflowSummary(resultJson)
831831
: FirstLine(resultJson);
832832

833-
// Body for the tool RESULT block. Small results render in full; oversized results render only
834-
// the short "(N lines)" / "(N characters)" summary instead of the fully-expanded payload tree
835-
// (issue #1069). The full payload stays available via data-details-target.
833+
// Body for the tool RESULT block. #1280: always render the full escaped payload inside the
834+
// <details> body so the user can inspect the actual returned text; the compact "(N lines)" /
835+
// "(N characters)" header lives in the collapsed <summary>. The full payload remains
836+
// available via data-details-target for the modal inspector as well.
836837
private static (string Html, bool Overflowed) RenderToolResultBody(string resultJson)
837-
=> ToolResultOverflows(resultJson)
838-
? (HtmlEscape(ToolResultOverflowSummary(resultJson)), true)
839-
: (RenderToolPayload(resultJson), false);
838+
{
839+
var overflowed = ToolResultOverflows(resultJson);
840+
return (RenderToolPayload(resultJson), overflowed);
841+
}
840842

841843
private static string DiagnosticHeader(string text)
842844
{

Phantom.Workspaces.Llm.Core.Tests/CopilotToolEventMapperTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,56 @@ public void MapToolComplete_MapsSuccessfulTextResult_PairedByCallId()
104104
Assert.Equal("the result text", result.Result);
105105
}
106106

107+
// ── Issue #1280: prefer DetailedContent (SDK-full text) for UI/timeline rendering ────
108+
109+
[Fact]
110+
public void MapToolComplete_WhenDetailedContentPresent_UsesDetailedContentAsResult()
111+
{
112+
// #1280: DetailedContent is documented by the SDK as the UI-oriented full text; Content
113+
// is potentially truncated for LLM token efficiency. For the chat surface we want the
114+
// full text — mapper prefers DetailedContent when both are present.
115+
var completeEvent = new ToolExecutionCompleteEvent
116+
{
117+
Data = new ToolExecutionCompleteData
118+
{
119+
ToolCallId = "call-2",
120+
Success = true,
121+
Result = new ToolExecutionCompleteResult
122+
{
123+
Content = "truncated-for-model",
124+
DetailedContent = "FULL UNTRUNCATED DETAIL FOR UI",
125+
},
126+
},
127+
};
128+
129+
var result = CopilotToolEventMapper.MapToolComplete(completeEvent);
130+
131+
Assert.Equal("FULL UNTRUNCATED DETAIL FOR UI", result.Result);
132+
}
133+
134+
[Fact]
135+
public void MapToolComplete_WhenOnlyContentPresent_FallsBackToContent()
136+
{
137+
// Fallback path: no DetailedContent — use Content.
138+
var completeEvent = new ToolExecutionCompleteEvent
139+
{
140+
Data = new ToolExecutionCompleteData
141+
{
142+
ToolCallId = "call-3",
143+
Success = true,
144+
Result = new ToolExecutionCompleteResult
145+
{
146+
Content = "content-only",
147+
DetailedContent = null,
148+
},
149+
},
150+
};
151+
152+
var result = CopilotToolEventMapper.MapToolComplete(completeEvent);
153+
154+
Assert.Equal("content-only", result.Result);
155+
}
156+
107157
[Fact]
108158
public void MapToolComplete_FallsBackToTextContent_WhenNoSummaryContent()
109159
{

Phantom.Workspaces.Llm.Core/CopilotToolEventMapper.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ private static object BuildSuccessResult(ToolExecutionCompleteResult? result)
131131
return string.Empty;
132132
}
133133

134-
if (!string.IsNullOrEmpty(result.Content))
134+
if (!string.IsNullOrEmpty(result.DetailedContent))
135135
{
136-
return result.Content;
136+
return result.DetailedContent;
137137
}
138138

139-
if (!string.IsNullOrEmpty(result.DetailedContent))
139+
if (!string.IsNullOrEmpty(result.Content))
140140
{
141-
return result.DetailedContent;
141+
return result.Content;
142142
}
143143

144144
if (result.Contents is { Length: > 0 })

0 commit comments

Comments
 (0)