33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6+ import MarkdownIt from 'markdown-it' ;
67import * as pathLib from 'path' ;
78import * as vscode from 'vscode' ;
89import { Uri } from 'vscode' ;
@@ -60,6 +61,72 @@ const AGENTS_OPTION_GROUP_ID = 'agents';
6061const DEFAULT_AGENT_ID = '___vscode_default___' ;
6162const BACKGROUND_REFRESH_INTERVAL_MS = 5 * 60 * 1000 ; // 5 minutes
6263
64+ /**
65+ * Custom renderer for markdown-it that converts markdown to plain text
66+ */
67+ class PlainTextRenderer {
68+ private md : MarkdownIt ;
69+
70+ constructor ( ) {
71+ this . md = new MarkdownIt ( ) ;
72+ }
73+
74+ /**
75+ * Renders markdown text as plain text by extracting text content from all tokens
76+ */
77+ render ( markdown : string ) : string {
78+ const tokens = this . md . parse ( markdown , { } ) ;
79+ return this . renderTokens ( tokens ) . trim ( ) ;
80+ }
81+
82+ private renderTokens ( tokens : MarkdownIt . Token [ ] ) : string {
83+ let result = '' ;
84+ for ( const token of tokens ) {
85+ // Process child tokens recursively
86+ if ( token . children ) {
87+ result += this . renderTokens ( token . children ) ;
88+ }
89+
90+ // Handle different token types
91+ switch ( token . type ) {
92+ case 'text' :
93+ case 'code_inline' :
94+ // Only add content if no children were processed
95+ if ( ! token . children ) {
96+ result += token . content ;
97+ }
98+ break ;
99+
100+ case 'softbreak' :
101+ case 'hardbreak' :
102+ result += ' ' ; // Space instead of newline to match original
103+ break ;
104+
105+ case 'paragraph_close' :
106+ result += '\n' ; // Newline after paragraphs for separation
107+ break ;
108+
109+ case 'heading_close' :
110+ result += '\n' ; // Newline after headings
111+ break ;
112+
113+ case 'list_item_close' :
114+ result += '\n' ; // Newline after list items
115+ break ;
116+
117+ case 'fence' :
118+ case 'code_block' :
119+ case 'hr' :
120+ // Skip these entirely
121+ break ;
122+
123+ // Don't add default case - only explicitly handle what we want
124+ }
125+ }
126+ return result ;
127+ }
128+ }
129+
63130export class CopilotCloudSessionsProvider extends Disposable implements vscode . ChatSessionContentProvider , vscode . ChatSessionItemProvider {
64131 public static readonly TYPE = 'copilot-cloud-agent' ;
65132 private readonly DELEGATE_MODAL_DETAILS = vscode . l10n . t ( 'The agent will work asynchronously to create a pull request with your requested changes. This chat\'s history will be summarized and appended to the pull request as context.' ) ;
@@ -74,6 +141,7 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
74141 await this . chatParticipantImpl ( request , context , stream , token )
75142 ) ;
76143 private cachedSessionsSize : number = 0 ;
144+ private readonly plainTextRenderer = new PlainTextRenderer ( ) ;
77145
78146 constructor (
79147 @IOctoKitService private readonly _octoKitService : IOctoKitService ,
@@ -203,12 +271,14 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
203271 const uri = await toOpenPullRequestWebviewUri ( { owner : repoId . org , repo : repoId . repo , pullRequestNumber : pr . number } ) ;
204272 const prLinkTitle = vscode . l10n . t ( 'Open pull request in VS Code' ) ;
205273 const description = new vscode . MarkdownString ( `[#${ pr . number } ](${ uri . toString ( ) } "${ prLinkTitle } ")` ) ;
274+ const tooltip = this . createPullRequestTooltip ( pr ) ;
206275
207276 const session = {
208277 resource : vscode . Uri . from ( { scheme : CopilotCloudSessionsProvider . TYPE , path : '/' + pr . number } ) ,
209278 label : pr . title ,
210279 status : this . getSessionStatusFromSession ( sessionItem ) ,
211280 description,
281+ tooltip,
212282 timing : {
213283 startTime : new Date ( sessionItem . last_updated_at ) . getTime ( ) ,
214284 } ,
@@ -415,6 +485,46 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
415485 }
416486 }
417487
488+ private createPullRequestTooltip ( pr : PullRequestSearchItem ) : vscode . MarkdownString {
489+ const markdown = new vscode . MarkdownString ( undefined , true ) ;
490+ markdown . supportHtml = true ;
491+
492+ // Repository and date
493+ const date = new Date ( pr . createdAt ) ;
494+ const ownerName = `${ pr . repository . owner . login } /${ pr . repository . name } ` ;
495+ markdown . appendMarkdown (
496+ `[${ ownerName } ](https://github.com/${ ownerName } ) on ${ date . toLocaleString ( 'default' , {
497+ day : 'numeric' ,
498+ month : 'short' ,
499+ year : 'numeric' ,
500+ } ) } \n`
501+ ) ;
502+
503+ // Icon, title, and PR number
504+ const icon = this . getIconMarkdown ( pr ) ;
505+ // Strip markdown from title for plain text display
506+ const title = this . plainTextRenderer . render ( pr . title ) ;
507+ markdown . appendMarkdown (
508+ `${ icon } **${ title } ** [#${ pr . number } ](${ pr . url } ) \n`
509+ ) ;
510+
511+ // Body/Description (truncated if too long)
512+ markdown . appendMarkdown ( ' \n' ) ;
513+ const maxBodyLength = 200 ;
514+ let body = this . plainTextRenderer . render ( pr . body || '' ) ;
515+ // Convert plain text newlines to markdown line breaks (two spaces + newline)
516+ body = body . replace ( / \n / g, ' \n' ) ;
517+ body = body . length > maxBodyLength ? body . substring ( 0 , maxBodyLength ) + '...' : body ;
518+ markdown . appendMarkdown ( body + ' \n' ) ;
519+
520+ return markdown ;
521+ }
522+
523+ private getIconMarkdown ( pr : PullRequestSearchItem ) : string {
524+ const state = pr . state . toUpperCase ( ) ;
525+ return state === 'MERGED' ? '$(git-merge)' : '$(git-pull-request)' ;
526+ }
527+
418528 private async startSession ( stream : vscode . ChatResponseStream , token : vscode . CancellationToken , source : string , prompt : string , history ?: string , references ?: readonly vscode . ChatPromptReference [ ] , customAgentName ?: string ) {
419529 /* __GDPR__
420530 "copilot.codingAgent.editor.invoke" : {
0 commit comments