From 5be2a9d5e23151f073b06be16dfd68a835001262 Mon Sep 17 00:00:00 2001 From: Rohit Ramkumar Date: Fri, 19 Sep 2025 17:47:20 -0400 Subject: [PATCH] Support rendering function calls and responses when sharing chat to markdown (#8693) Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com> --- .../cli/src/ui/commands/chatCommand.test.ts | 142 ++++++++++++++++-- packages/cli/src/ui/commands/chatCommand.ts | 25 ++- 2 files changed, 150 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/ui/commands/chatCommand.test.ts b/packages/cli/src/ui/commands/chatCommand.test.ts index 7b352c4aba..e85ae2c5f6 100644 --- a/packages/cli/src/ui/commands/chatCommand.test.ts +++ b/packages/cli/src/ui/commands/chatCommand.test.ts @@ -465,11 +465,27 @@ describe('chatCommand', () => { const expectedPath = path.join(process.cwd(), 'my-chat.md'); const [actualPath, actualContent] = mockFs.writeFile.mock.calls[0]; expect(actualPath).toEqual(expectedPath); - const expectedContent = - '**user**:\n\ncontext\n\n---\n\n' + - '**model**:\n\ncontext response\n\n---\n\n' + - '**user**:\n\nHello\n\n---\n\n' + - '**model**:\n\nHi there!'; + const expectedContent = `🧑‍💻 ## USER + +context + +--- + +✨ ## MODEL + +context response + +--- + +🧑‍💻 ## USER + +Hello + +--- + +✨ ## MODEL + +Hi there!`; expect(actualContent).toEqual(expectedContent); expect(result).toEqual({ type: 'message', @@ -540,13 +556,14 @@ describe('chatCommand', () => { entries.forEach((entry, index) => { const { role, parts } = mockHistory[index]; const text = parts.map((p) => p.text).join(''); - expect(entry).toBe(`**${role}**:\n\n${text}`); + const roleIcon = role === 'user' ? '🧑‍💻' : '✨'; + expect(entry).toBe(`${roleIcon} ## ${role.toUpperCase()}\n\n${text}`); }); }); }); describe('serializeHistoryToMarkdown', () => { - it('should correctly serialize chat history to Markdown', () => { + it('should correctly serialize chat history to Markdown with icons', () => { const history: Content[] = [ { role: 'user', parts: [{ text: 'Hello' }] }, { role: 'model', parts: [{ text: 'Hi there!' }] }, @@ -554,9 +571,9 @@ describe('chatCommand', () => { ]; const expectedMarkdown = - '**user**:\n\nHello\n\n---\n\n' + - '**model**:\n\nHi there!\n\n---\n\n' + - '**user**:\n\nHow are you?'; + '🧑‍💻 ## USER\n\nHello\n\n---\n\n' + + '✨ ## MODEL\n\nHi there!\n\n---\n\n' + + '🧑‍💻 ## USER\n\nHow are you?'; const result = serializeHistoryToMarkdown(history); expect(result).toBe(expectedMarkdown); @@ -575,13 +592,110 @@ describe('chatCommand', () => { { role: 'user', parts: [{ text: 'How are you?' }] }, ]; - const expectedMarkdown = - '**user**:\n\nHello\n\n---\n\n' + - '**model**:\n\n\n\n---\n\n' + - '**user**:\n\nHow are you?'; + const expectedMarkdown = `🧑‍💻 ## USER + +Hello + +--- + +✨ ## MODEL + + + +--- + +🧑‍💻 ## USER + +How are you?`; const result = serializeHistoryToMarkdown(history); expect(result).toBe(expectedMarkdown); }); + + it('should correctly serialize function calls and responses', () => { + const history: Content[] = [ + { + role: 'user', + parts: [{ text: 'Please call a function.' }], + }, + { + role: 'model', + parts: [ + { + functionCall: { + name: 'my-function', + args: { arg1: 'value1' }, + }, + }, + ], + }, + { + role: 'user', + parts: [ + { + functionResponse: { + name: 'my-function', + response: { result: 'success' }, + }, + }, + ], + }, + ]; + + const expectedMarkdown = `🧑‍💻 ## USER + +Please call a function. + +--- + +✨ ## MODEL + +**Tool Command**: +\`\`\`json +{ + "name": "my-function", + "args": { + "arg1": "value1" + } +} +\`\`\` + +--- + +🧑‍💻 ## USER + +**Tool Response**: +\`\`\`json +{ + "name": "my-function", + "response": { + "result": "success" + } +} +\`\`\``; + + const result = serializeHistoryToMarkdown(history); + expect(result).toBe(expectedMarkdown); + }); + + it('should handle items with undefined role', () => { + const history: Array> = [ + { role: 'user', parts: [{ text: 'Hello' }] }, + { parts: [{ text: 'Hi there!' }] }, + ]; + + const expectedMarkdown = `🧑‍💻 ## USER + +Hello + +--- + +✨ ## MODEL + +Hi there!`; + + const result = serializeHistoryToMarkdown(history as Content[]); + expect(result).toBe(expectedMarkdown); + }); }); }); diff --git a/packages/cli/src/ui/commands/chatCommand.ts b/packages/cli/src/ui/commands/chatCommand.ts index e0648f1403..8a8fa23478 100644 --- a/packages/cli/src/ui/commands/chatCommand.ts +++ b/packages/cli/src/ui/commands/chatCommand.ts @@ -280,10 +280,29 @@ export function serializeHistoryToMarkdown(history: Content[]): string { .map((item) => { const text = item.parts - ?.filter((m) => !!m.text) - .map((m) => m.text) + ?.map((part) => { + if (part.text) { + return part.text; + } + if (part.functionCall) { + return `**Tool Command**:\n\`\`\`json\n${JSON.stringify( + part.functionCall, + null, + 2, + )}\n\`\`\``; + } + if (part.functionResponse) { + return `**Tool Response**:\n\`\`\`json\n${JSON.stringify( + part.functionResponse, + null, + 2, + )}\n\`\`\``; + } + return ''; + }) .join('') || ''; - return `**${item.role}**:\n\n${text}`; + const roleIcon = item.role === 'user' ? '🧑‍💻' : '✨'; + return `${roleIcon} ## ${(item.role || 'model').toUpperCase()}\n\n${text}`; }) .join('\n\n---\n\n'); }