33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import type { AgentOptions , ModelProvider , Session , SessionEvent } from '@github/copilot/sdk' ;
6+ import type { AgentOptions , Attachment , ModelProvider , Session , SessionEvent } from '@github/copilot/sdk' ;
7+ import * as fs from 'fs/promises' ;
78import type * as vscode from 'vscode' ;
89import { IAuthenticationService } from '../../../../platform/authentication/common/authentication' ;
910import { IEnvService } from '../../../../platform/env/common/envService' ;
1011import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext' ;
1112import { ILogService } from '../../../../platform/log/common/logService' ;
1213import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService' ;
14+ import { isLocation } from '../../../../util/common/types' ;
1315import { CancellationToken } from '../../../../util/vs/base/common/cancellation' ;
1416import { Disposable } from '../../../../util/vs/base/common/lifecycle' ;
17+ import * as path from '../../../../util/vs/base/common/path' ;
18+ import { URI } from '../../../../util/vs/base/common/uri' ;
1519import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation' ;
16- import { ChatResponseThinkingProgressPart , LanguageModelTextPart } from '../../../../vscodeTypes' ;
20+ import { ChatReferenceDiagnostic , ChatResponseThinkingProgressPart , LanguageModelTextPart } from '../../../../vscodeTypes' ;
1721import { ToolName } from '../../../tools/common/toolNames' ;
1822import { IToolsService } from '../../../tools/common/toolsService' ;
1923import { ICopilotCLISessionService } from './copilotcliSessionService' ;
@@ -48,13 +52,14 @@ export class CopilotCLIAgentManager extends Disposable {
4852 const sessionIdForLog = copilotcliSessionId ?? 'new' ;
4953 this . logService . trace ( `[CopilotCLIAgentManager] Handling request for sessionId=${ sessionIdForLog } .` ) ;
5054
55+ const { prompt, attachments } = await this . resolvePrompt ( request ) ;
5156 // Check if we already have a session wrapper
5257 let session = copilotcliSessionId ? this . sessionService . findSessionWrapper < CopilotCLISession > ( copilotcliSessionId ) : undefined ;
5358
5459 if ( session ) {
5560 this . logService . trace ( `[CopilotCLIAgentManager] Reusing CopilotCLI session ${ copilotcliSessionId } .` ) ;
5661 } else {
57- const sdkSession = await this . sessionService . getOrCreateSDKSession ( copilotcliSessionId , request . prompt ) ;
62+ const sdkSession = await this . sessionService . getOrCreateSDKSession ( copilotcliSessionId , prompt ) ;
5863 session = this . instantiationService . createInstance ( CopilotCLISession , sdkSession ) ;
5964 this . sessionService . trackSessionWrapper ( sdkSession . sessionId , session ) ;
6065 }
@@ -63,10 +68,91 @@ export class CopilotCLIAgentManager extends Disposable {
6368 this . sessionService . setPendingRequest ( session . sessionId ) ;
6469 }
6570
66- await session . invoke ( request . prompt , request . toolInvocationToken , stream , modelId , token ) ;
71+ await session . invoke ( prompt , attachments , request . toolInvocationToken , stream , modelId , token ) ;
6772
6873 return { copilotcliSessionId : session . sessionId } ;
6974 }
75+
76+ private async resolvePrompt ( request : vscode . ChatRequest ) : Promise < { prompt : string ; attachments : Attachment [ ] } > {
77+ if ( request . prompt . startsWith ( '/' ) ) {
78+ return { prompt : request . prompt , attachments : [ ] } ; // likely a slash command, don't modify
79+ }
80+
81+ const attachments : Attachment [ ] = [ ] ;
82+ const allRefsTexts : string [ ] = [ ] ;
83+ const diagnosticTexts : string [ ] = [ ] ;
84+ const files : { path : string ; name : string } [ ] = [ ] ;
85+ // TODO@rebornix : filter out implicit references for now. Will need to figure out how to support `<reminder>` without poluting user prompt
86+ request . references . filter ( ref => ! ref . id . startsWith ( 'vscode.prompt.instructions' ) ) . forEach ( ref => {
87+ if ( ref . value instanceof ChatReferenceDiagnostic ) {
88+ // Handle diagnostic reference
89+ for ( const [ uri , diagnostics ] of ref . value . diagnostics ) {
90+ for ( const diagnostic of diagnostics ) {
91+ const severityMap : { [ key : number ] : string } = {
92+ 0 : 'error' ,
93+ 1 : 'warning' ,
94+ 2 : 'info' ,
95+ 3 : 'hint'
96+ } ;
97+ const severity = severityMap [ diagnostic . severity ] ?? 'error' ;
98+ const code = ( typeof diagnostic . code === 'object' && diagnostic . code !== null ) ? diagnostic . code . value : diagnostic . code ;
99+ const codeStr = code ? ` [${ code } ]` : '' ;
100+ const line = diagnostic . range . start . line + 1 ;
101+ diagnosticTexts . push ( `- ${ severity } ${ codeStr } at ${ uri . fsPath } :${ line } : ${ diagnostic . message } ` ) ;
102+ files . push ( { path : uri . fsPath , name : path . basename ( uri . fsPath ) } ) ;
103+ }
104+ }
105+ } else {
106+ const filePath = URI . isUri ( ref . value ) ? ref . value . fsPath : isLocation ( ref . value ) ? ref . value . uri . fsPath : undefined ;
107+ if ( filePath ) {
108+ files . push ( { path : filePath , name : ref . name || path . basename ( filePath ) } ) ;
109+ }
110+ const valueText = URI . isUri ( ref . value ) ?
111+ ref . value . fsPath :
112+ isLocation ( ref . value ) ?
113+ `${ ref . value . uri . fsPath } :${ ref . value . range . start . line + 1 } ` :
114+ undefined ;
115+ if ( valueText && ref . range ) {
116+ // Keep the original prompt untouched, just collect resolved paths
117+ const variableText = request . prompt . substring ( ref . range [ 0 ] , ref . range [ 1 ] ) ;
118+ allRefsTexts . push ( `- ${ variableText } → ${ valueText } ` ) ;
119+ }
120+ }
121+ } ) ;
122+
123+ await Promise . all ( files . map ( async ( file ) => {
124+ try {
125+ const stat = await fs . stat ( file . path ) ;
126+ const type = stat . isDirectory ( ) ? 'directory' : stat . isFile ( ) ? 'file' : undefined ;
127+ if ( ! type ) {
128+ this . logService . error ( `[CopilotCLIAgentManager] Ignoring attachment as its not a file/directory (${ file . path } )` ) ;
129+ return ;
130+ }
131+ attachments . push ( {
132+ type,
133+ displayName : file . name ,
134+ path : file . path
135+ } ) ;
136+ } catch ( error ) {
137+ this . logService . error ( `[CopilotCLIAgentManager] Failed to attach ${ file . path } : ${ error } ` ) ;
138+ }
139+ } ) ) ;
140+
141+ const reminderParts : string [ ] = [ ] ;
142+ if ( allRefsTexts . length > 0 ) {
143+ reminderParts . push ( `The user provided the following references:\n${ allRefsTexts . join ( '\n' ) } ` ) ;
144+ }
145+ if ( diagnosticTexts . length > 0 ) {
146+ reminderParts . push ( `The user provided the following diagnostics:\n${ diagnosticTexts . join ( '\n' ) } ` ) ;
147+ }
148+
149+ let prompt = request . prompt ;
150+ if ( reminderParts . length > 0 ) {
151+ prompt = `<reminder>\n${ reminderParts . join ( '\n\n' ) } \n\nIMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.\n</reminder>\n\n${ prompt } ` ;
152+ }
153+
154+ return { prompt, attachments } ;
155+ }
70156}
71157
72158export class CopilotCLISession extends Disposable {
@@ -92,19 +178,20 @@ export class CopilotCLISession extends Disposable {
92178 super . dispose ( ) ;
93179 }
94180
95- async * query ( prompt : string , options : AgentOptions ) : AsyncGenerator < SessionEvent > {
181+ async * query ( prompt : string , attachments : Attachment [ ] , options : AgentOptions ) : AsyncGenerator < SessionEvent > {
96182 // Ensure node-pty shim exists before importing SDK
97183 // @github /copilot has hardcoded: import{spawn}from"node-pty"
98184 await ensureNodePtyShim ( this . extensionContext . extensionPath , this . envService . appRoot ) ;
99185
100186 // Dynamically import the SDK
101187 const { Agent } = await import ( '@github/copilot/sdk' ) ;
102188 const agent = new Agent ( options ) ;
103- yield * agent . query ( prompt ) ;
189+ yield * agent . query ( prompt , attachments ) ;
104190 }
105191
106192 public async invoke (
107193 prompt : string ,
194+ attachments : Attachment [ ] ,
108195 toolInvocationToken : vscode . ChatParticipantToolToken ,
109196 stream : vscode . ChatResponseStream ,
110197 modelId : ModelProvider | undefined ,
@@ -148,7 +235,7 @@ export class CopilotCLISession extends Disposable {
148235 } ;
149236
150237 try {
151- for await ( const event of this . query ( prompt , options ) ) {
238+ for await ( const event of this . query ( prompt , attachments , options ) ) {
152239 if ( token . isCancellationRequested ) {
153240 break ;
154241 }
0 commit comments