feat: export chat sessions as markdown and publish to private gists#296841
feat: export chat sessions as markdown and publish to private gists#296841joaomoreno wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds chat session markdown export formatting and a new workbench action to publish the active chat session as a private GitHub Gist.
Changes:
- Implement
formatChatSessionAsMarkdownto render exported chat session data into a readable markdown transcript. - Add a new command/action to publish the current session to a private GitHub Gist (auth + request + copy URL + open external).
- Add unit tests covering the markdown formatting behavior (including filtering out tool invocations).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts |
Adds markdown transcript formatting and a new “Publish to Private Gist” action that authenticates with GitHub and creates a private gist. |
src/vs/workbench/contrib/chat/test/browser/actions/chatImportExport.test.ts |
Introduces unit tests validating the markdown formatting output for common response shapes. |
Comments suppressed due to low confidence (8)
src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts:39
githubAuthScopesincludes theuser:emailscope, but the gist publish flow here doesn’t use the user’s email. Requesting extra OAuth scopes increases permission surface and can trigger unnecessary consent prompts; please remove unused scopes and keep this to the minimum required (likely justgist).
const githubAuthProviderId = 'github';
const githubAuthScopes = ['gist', 'user:email'];
const githubGistApiUrl = 'https://api.github.com/gists';
src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts:76
- The markdown headings “### You” and “### Copilot” are user-visible/exported strings but aren’t localized, while surrounding strings are. Please localize these labels (or otherwise ensure consistent i18n behavior for exported markdown).
This issue also appears on line 59 of the same file.
lines.push('### You');
lines.push('');
lines.push(asBlockQuote(requestText));
lines.push('');
}
const assistantMessages = getAssistantMessages(request);
if (assistantMessages.length > 0) {
lines.push('### Copilot');
lines.push('');
lines.push(asBlockQuote(assistantMessages.join('\n\n')));
src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts:124
asBlockQuotesplits only on\n, which will leave stray\rcharacters when exporting content that uses Windows line endings. Consider splitting on/\r?\n/(or normalizing line endings first) to avoid> ...\rin the exported markdown.
function asBlockQuote(content: string): string {
return content
.split('\n')
.map(line => `> ${line}`)
.join('\n');
src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts:323
- New gist-publishing behavior isn’t covered by tests (success + common failure paths like auth cancellation / non-2xx responses). Given there are already unit tests for chat actions, please add targeted tests with mocked
IAuthenticationService/IRequestService/IClipboardService/IOpenerServiceto lock down this new command’s behavior.
async run(accessor: ServicesAccessor, context?: IChatViewTitleActionContext) {
const chatService = accessor.get(IChatService);
const chatWidgetService = accessor.get(IChatWidgetService);
const authenticationService = accessor.get(IAuthenticationService);
const requestService = accessor.get(IRequestService);
const notificationService = accessor.get(INotificationService);
const clipboardService = accessor.get(IClipboardService);
const openerService = accessor.get(IOpenerService);
const sessionResource = isChatViewTitleActionContext(context)
? context.sessionResource
: chatWidgetService.lastFocusedWidget?.viewModel?.sessionResource;
if (!sessionResource) {
notificationService.warn(localize('chat.publishGist.noSession', "No active chat session to publish."));
return;
}
const model = chatService.getSession(sessionResource);
if (!model || !model.hasRequests) {
notificationService.warn(localize('chat.publishGist.emptySession', "The active chat session is empty."));
return;
}
try {
const sessions = await authenticationService.getSessions(githubAuthProviderId, githubAuthScopes, undefined, true);
const session = sessions[0] ?? await authenticationService.createSession(githubAuthProviderId, githubAuthScopes);
const markdown = formatChatSessionAsMarkdown(model.toExport(), model.title);
const fileName = getGistMarkdownFileName(model.title);
const gistUrl = await createPrivateGist(requestService, session.accessToken, fileName, markdown);
await clipboardService.writeText(gistUrl);
await openerService.open(URI.parse(gistUrl), { openExternal: true });
notificationService.info(localize('chat.publishGist.success', "Published chat session to a private gist. The URL has been copied to your clipboard."));
} catch (error) {
notificationService.error(localize('chat.publishGist.failed', "Failed to publish chat session to a private gist: {0}", getErrorMessage(error)));
}
}
src/vs/workbench/contrib/chat/test/browser/actions/chatImportExport.test.ts:41
- This test introduces multiline object/array literals without trailing commas (e.g. after
vulnerabilitiesand array elements). The repo’s ESLint config warns on missing trailing commas for multiline constructs (comma-dangle: only-multiline); please add trailing commas to match the project style and avoid lint warnings.
This issue also appears in the following locations of the same file:
- line 63
- line 92
response: [
{
kind: 'markdownVuln',
content: { value: 'All tests passed.' },
vulnerabilities: []
},
toolInvocation
]
};
src/vs/workbench/contrib/chat/test/browser/actions/chatImportExport.test.ts:76
- This test has multiline object/array literals without trailing commas (e.g. the
messageobject andresponsearray). Please add trailing commas to comply with the repo’scomma-dangle: only-multilinelint rule and keep formatting consistent with nearby tests.
const request: ISerializableChatRequestData = {
requestId: 'request-2',
message: {
text: 'Summarize this session',
parts: []
},
variableData: { variables: [] },
response: [
{
kind: 'markdownContent',
content: { value: 'Here is the summary.' }
}
]
};
src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts:76
formatChatSessionAsMarkdownhardcodes “Copilot” in the subtitle and assistant heading, ignoringdata.responderUsername. This produces incorrect exports for non-default agents and makes the function less reusable; usedata.responderUsername(ideally via a localized placeholder) for the assistant name instead of a fixed string.
lines.push(`# ${sessionTitle || localize('chat.publishGist.defaultTitle', "Chat Session")}`);
lines.push('');
lines.push(`*${localize('chat.publishGist.pretty.subtitle', "A conversation between you and Copilot")}*`);
lines.push('');
for (const request of data.requests) {
const requestText = getRequestText(request);
if (requestText) {
lines.push('### You');
lines.push('');
lines.push(asBlockQuote(requestText));
lines.push('');
}
const assistantMessages = getAssistantMessages(request);
if (assistantMessages.length > 0) {
lines.push('### Copilot');
lines.push('');
lines.push(asBlockQuote(assistantMessages.join('\n\n')));
src/vs/workbench/contrib/chat/test/browser/actions/chatImportExport.test.ts:106
- This test uses multiline literals without trailing commas (e.g.
responsearray andrequestsarray indata). Please add trailing commas to match the repo’s lint/style (comma-dangle: only-multiline).
test('includes assistant messages exported as raw markdown strings', () => {
const request: ISerializableChatRequestData = {
requestId: 'request-3',
message: 'Explain the refactor',
variableData: { variables: [] },
response: [
{ value: 'I moved the compile CLI jobs into each platform pipeline.' }
]
};
const data: IExportableChatData = {
initialLocation: undefined,
responderUsername: 'assistant',
requests: [request]
};
| 'Authorization': `Bearer ${accessToken}`, | ||
| 'Content-Type': 'application/json', | ||
| 'X-GitHub-Api-Version': '2022-11-28' |
There was a problem hiding this comment.
GitHub’s REST API requires a User-Agent header for many clients (and we already set one for GitHub API calls elsewhere in the repo). createPrivateGist currently doesn’t set User-Agent, which can lead to 403 responses depending on the underlying request implementation; add an explicit User-Agent header (and consider aligning the Authorization scheme with other GitHub requests in the codebase).
| 'Authorization': `Bearer ${accessToken}`, | |
| 'Content-Type': 'application/json', | |
| 'X-GitHub-Api-Version': '2022-11-28' | |
| 'Authorization': `token ${accessToken}`, | |
| 'Content-Type': 'application/json', | |
| 'X-GitHub-Api-Version': '2022-11-28', | |
| 'User-Agent': 'Visual-Studio-Code' |
No description provided.