Skip to content

Comments

feat: export chat sessions as markdown and publish to private gists#296841

Draft
joaomoreno wants to merge 1 commit intomainfrom
joao/aquamarine-centipede
Draft

feat: export chat sessions as markdown and publish to private gists#296841
joaomoreno wants to merge 1 commit intomainfrom
joao/aquamarine-centipede

Conversation

@joaomoreno
Copy link
Member

No description provided.

Copilot AI review requested due to automatic review settings February 22, 2026 16:43
@joaomoreno joaomoreno self-assigned this Feb 22, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 formatChatSessionAsMarkdown to 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

  • githubAuthScopes includes the user:email scope, 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 just gist).
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

  • asBlockQuote splits only on \n, which will leave stray \r characters when exporting content that uses Windows line endings. Consider splitting on /\r?\n/ (or normalizing line endings first) to avoid > ...\r in 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/IOpenerService to 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 vulnerabilities and 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 message object and response array). Please add trailing commas to comply with the repo’s comma-dangle: only-multiline lint 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

  • formatChatSessionAsMarkdown hardcodes “Copilot” in the subtitle and assistant heading, ignoring data.responderUsername. This produces incorrect exports for non-default agents and makes the function less reusable; use data.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. response array and requests array in data). 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]
		};

Comment on lines +143 to +145
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28'
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
'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'

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant