-
Notifications
You must be signed in to change notification settings - Fork 430
Show tool output previews in Copilot CLI conversation rendering #40116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ | |
| * Event mapping: | ||
| * SDK "user.message" → JSONL "user.message" | ||
| * SDK "tool.execution_start" → JSONL "tool.execution_start" (toolName, mcpServerName) | ||
| * SDK "tool.execution_complete" → JSONL "tool.execution_complete" (toolName, mcpServerName, success) | ||
| * SDK "tool.execution_complete" → JSONL "tool.execution_complete" (toolName, mcpServerName, success, result) | ||
| * SDK "assistant.message" → JSONL "assistant.message" (content) | ||
| * | ||
| * The JSONL file is written to: | ||
|
|
@@ -265,9 +265,12 @@ async function runWithCopilotSDK({ sdkUri, prompt, logger, attempt = 0, model, c | |
| const mcpServerName = pending?.mcpServerName ?? ""; | ||
| if (toolCallId) pendingToolCalls.delete(toolCallId); | ||
| const success = event.data?.success ?? !event.data?.error; | ||
| // Include result.content (concise LLM-facing output) so that the log | ||
| // parser can render tool output previews from events.jsonl directly. | ||
| const result = event.data?.result ?? undefined; | ||
| // max-tool-denials intentionally tracks permission denials only. | ||
| // Tool execution failures are still logged, but do not increment the guardrail counter. | ||
|
Comment on lines
+268
to
272
|
||
| writeEvent("tool.execution_complete", { toolName, mcpServerName, success }, event.timestamp); | ||
| writeEvent("tool.execution_complete", { toolName, mcpServerName, success, result }, event.timestamp); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Full 💡 Suggested fixWrite only the fields the log parser actually reads: // current
const result = event.data?.result ?? undefined;
writeEvent("tool.execution_complete", { toolName, mcpServerName, success, result }, event.timestamp);
// suggested
const resultContent = event.data?.result?.content;
const result = resultContent !== undefined ? { content: resultContent } : undefined;
writeEvent("tool.execution_complete", { toolName, mcpServerName, success, result }, event.timestamp);The SDK's |
||
| break; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -928,8 +928,12 @@ function convertCopilotEventsToLegacyLogEntries(logEntries) { | |
| output = data.output; | ||
| } else if (typeof data.result === "string") { | ||
| output = data.result; | ||
| } else if (data.result && typeof data.result.content === "string") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnose] This branch only handles 💡 Suggested guardAdd an array case below the string check: } else if (data.result && Array.isArray(data.result.content)) {
// MCP-style content blocks: [{type:"text",text:"..."}]
output = data.result.content.map(c => (typeof c === "string" ? c : c.text || "")).join("\n");
}This mirrors the pattern in |
||
| // Native Copilot CLI events.jsonl format: result.content is the concise | ||
| // tool result text sent to the LLM (may be truncated for token efficiency). | ||
| output = data.result.content; | ||
| } else if (data.error) { | ||
|
Comment on lines
+931
to
935
|
||
| output = String(data.error); | ||
| output = typeof data.error === "object" && typeof data.error.message === "string" ? data.error.message : String(data.error); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error-extraction fix ( 💡 Suggested testAdd a case to the existing describe block covering failed tool execution with an Error object: it("renders error.message from a failed tool call in events.jsonl", () => {
const eventsLog = [
'{"type":"user.message","timestamp":"2026-06-05T00:44:01.367Z","data":{}}',
'{"type":"tool.execution_start","timestamp":"2026-06-05T00:44:04.520Z","data":{"toolName":"bash","mcpServerName":""}}',
'{"type":"tool.execution_complete","timestamp":"2026-06-05T00:44:04.700Z","data":{"toolName":"bash","mcpServerName":"","success":false,"error":{"message":"Permission denied"}}}',
'{"type":"assistant.message","timestamp":"2026-06-05T00:44:59.769Z","data":{"content":"Failed"}}',
].join("\n");
const result = parseCopilotLog(eventsLog);
expect(result.markdown).toContain("Permission denied");
// Must not contain the old serialization artifact
expect(result.markdown).not.toContain("[object Object]");
});Without this, a future refactor could revert the fix silently.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The error extraction fix ( 💡 Suggested testAdd alongside the existing it("renders error message from data.error.message in tool.execution_complete", () => {
const eventsLog = [
'{"type":"user.message","timestamp":"2026-06-05T00:44:01.367Z","data":{}}',
'{"type":"tool.execution_start","timestamp":"2026-06-05T00:44:04.520Z","data":{"toolName":"bash","mcpServerName":""}}',
'{"type":"tool.execution_complete","timestamp":"2026-06-05T00:44:04.700Z","data":{"toolName":"bash","mcpServerName":"","success":false,"error":{"message":"Permission denied"}}}',
'{"type":"assistant.message","timestamp":"2026-06-05T00:44:59.769Z","data":{"content":"Done"}}',
].join("\n");
const result = parseCopilotLog(eventsLog);
expect(result.markdown).toContain("Permission denied");
expect(result.markdown).not.toContain("[object Object]");
}); |
||
| } else if (success) { | ||
| output = "success"; | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,6 +124,20 @@ describe("parse_copilot_log.cjs", () => { | |
| expect(resultData?.numTurns).toBe(1); | ||
| }); | ||
|
|
||
| it("renders tool output preview from result.content in Copilot CLI events.jsonl", () => { | ||
| const eventsLog = [ | ||
| '{"type":"user.message","timestamp":"2026-06-05T00:44:01.367Z","data":{}}', | ||
| '{"type":"tool.execution_start","timestamp":"2026-06-05T00:44:04.520Z","data":{"toolName":"bash","mcpServerName":""}}', | ||
| '{"type":"tool.execution_complete","timestamp":"2026-06-05T00:44:04.700Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"file1.txt\\nfile2.txt\\nfile3.txt"}}}', | ||
| '{"type":"assistant.message","timestamp":"2026-06-05T00:44:59.769Z","data":{"content":"Done"}}', | ||
| ].join("\n"); | ||
|
|
||
| const result = parseCopilotLog(eventsLog); | ||
|
|
||
| expect(result.markdown).toContain("bash"); | ||
| expect(result.markdown).toContain("file1.txt"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The two assertions cover the happy path but leave important behaviour untested: (1) no check that the old 💡 Strengthen the testexpect(result.markdown).toContain("file1.txt");
expect(result.markdown).toContain("file2.txt"); // verify multiline output reaches markdown
expect(result.markdown).not.toContain("success"); // guard against fallthrough to placeholderA spec-style name would also help: |
||
| }); | ||
|
Comment on lines
+127
to
+139
|
||
|
|
||
| it("should handle tool calls with details in HTML format", () => { | ||
| const logWithHtmlDetails = JSON.stringify([ | ||
| { type: "system", subtype: "init", session_id: "html-test", tools: ["Bash"], model: "gpt-5" }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnose]
event.data?.result ?? undefinedis a no-op: optional chaining already returnsundefinedwhen the property is absent. Justevent.data?.resultis identical and clearer.