@@ -7,12 +7,16 @@ import { BasePromptElementProps, PromptElement, PromptElementProps, PromptPiece,
7
7
import type { Diagnostic , DiagnosticSeverity , LanguageModelToolInformation } from 'vscode' ;
8
8
import { ChatFetchResponseType , ChatLocation } from '../../../../platform/chat/common/commonTypes' ;
9
9
import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider' ;
10
+ import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService' ;
11
+ import { FileType } from '../../../../platform/filesystem/common/fileTypes' ;
10
12
import { ILogService } from '../../../../platform/log/common/logService' ;
11
13
import { ICopilotToolCall } from '../../../../platform/networking/common/fetch' ;
12
14
import { IChatEndpoint } from '../../../../platform/networking/common/networking' ;
13
15
import { IAlternativeNotebookContentService } from '../../../../platform/notebook/common/alternativeContent' ;
14
16
import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService' ;
17
+ import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry' ;
15
18
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService' ;
19
+ import { createFencedCodeBlock } from '../../../../util/common/markdown' ;
16
20
import { getNotebookAndCellFromUri } from '../../../../util/common/notebooks' ;
17
21
import { isLocation } from '../../../../util/common/types' ;
18
22
import { CancellationToken } from '../../../../util/vs/base/common/cancellation' ;
@@ -34,7 +38,7 @@ import { Image } from './image';
34
38
import { NotebookCellOutputVariable } from './notebookVariables' ;
35
39
import { PanelChatBasePrompt } from './panelChatBasePrompt' ;
36
40
import { sendInvokedToolTelemetry , toolCallErrorToResult , ToolResult , ToolResultMetadata } from './toolCalling' ;
37
- import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry ' ;
41
+ import { IFileTreeData , workspaceVisualFileTree } from './workspace/visualFileTree ' ;
38
42
39
43
export interface ChatVariablesProps extends BasePromptElementProps , EmbeddedInsideUserMessage {
40
44
readonly chatVariables : ChatVariablesCollection ;
@@ -44,8 +48,15 @@ export interface ChatVariablesProps extends BasePromptElementProps, EmbeddedInsi
44
48
}
45
49
46
50
export class ChatVariables extends PromptElement < ChatVariablesProps , void > {
51
+ constructor (
52
+ props : ChatVariablesProps ,
53
+ @IFileSystemService private readonly fileSystemService : IFileSystemService ,
54
+ ) {
55
+ super ( props ) ;
56
+ }
57
+
47
58
override async render ( state : void , sizing : PromptSizing ) : Promise < PromptPiece < any , any > | undefined > {
48
- const elements = await renderChatVariables ( this . props . chatVariables , this . props . includeFilepath , this . props . omitReferences , this . props . isAgent ) ;
59
+ const elements = await renderChatVariables ( this . props . chatVariables , this . fileSystemService , this . props . includeFilepath , this . props . omitReferences , this . props . isAgent ) ;
49
60
if ( elements . length === 0 ) {
50
61
return undefined ;
51
62
}
@@ -87,9 +98,16 @@ export interface ChatVariablesAndQueryProps extends BasePromptElementProps, Embe
87
98
}
88
99
89
100
export class ChatVariablesAndQuery extends PromptElement < ChatVariablesAndQueryProps , void > {
101
+ constructor (
102
+ props : ChatVariablesAndQueryProps ,
103
+ @IFileSystemService private readonly fileSystemService : IFileSystemService ,
104
+ ) {
105
+ super ( props ) ;
106
+ }
107
+
90
108
override async render ( state : void , sizing : PromptSizing ) : Promise < PromptPiece < any , any > | undefined > {
91
109
const chatVariables = this . props . maintainOrder ? this . props . chatVariables : this . props . chatVariables . reverse ( ) ;
92
- const elements = await renderChatVariables ( chatVariables , this . props . includeFilepath , this . props . omitReferences ) ;
110
+ const elements = await renderChatVariables ( chatVariables , this . fileSystemService , this . props . includeFilepath , this . props . omitReferences , undefined ) ;
93
111
94
112
if ( this . props . embeddedInsideUserMessage ?? embeddedInsideUserMessageDefault ) {
95
113
if ( ! elements . length ) {
@@ -121,7 +139,7 @@ function asUserMessage(element: PromptElement, priority: number | undefined): Us
121
139
}
122
140
123
141
124
- export async function renderChatVariables ( chatVariables : ChatVariablesCollection , includeFilepathInCodeBlocks = true , omitReferences ?: boolean , isAgent ?: boolean ) : Promise < PromptElement [ ] > {
142
+ export async function renderChatVariables ( chatVariables : ChatVariablesCollection , fileSystemService : IFileSystemService , includeFilepathInCodeBlocks = true , omitReferences ?: boolean , isAgent ?: boolean ) : Promise < PromptElement [ ] > {
125
143
const elements = [ ] ;
126
144
for ( const variable of chatVariables ) {
127
145
const { uniqueName : variableName , value : variableValue , reference } = variable ;
@@ -130,20 +148,33 @@ export async function renderChatVariables(chatVariables: ChatVariablesCollection
130
148
}
131
149
132
150
if ( URI . isUri ( variableValue ) || isLocation ( variableValue ) ) {
133
- const filePathMode = ( isAgent && includeFilepathInCodeBlocks )
134
- ? FilePathMode . AsAttribute
135
- : includeFilepathInCodeBlocks
136
- ? FilePathMode . AsComment
137
- : FilePathMode . None ;
138
- const file = < FileVariable alwaysIncludeSummary = { true } filePathMode = { filePathMode } variableName = { variableName } variableValue = { variableValue } omitReferences = { omitReferences } description = { reference . modelDescription } /> ;
139
-
140
- if ( ! isAgent || ( ! URI . isUri ( variableValue ) || variableValue . scheme !== Schemas . vscodeNotebookCellOutput ) ) {
141
- // When attaching outupts, there's no need to add the entire notebook file again, as model can request the notebook file.
142
- // In non agent mode, we need to add the file for context.
143
- elements . push ( file ) ;
144
- }
145
- if ( URI . isUri ( variableValue ) && variableValue . scheme === Schemas . vscodeNotebookCellOutput ) {
146
- elements . push ( < NotebookCellOutputVariable outputUri = { variableValue } /> ) ;
151
+ const uri = 'uri' in variableValue ? variableValue . uri : variableValue ;
152
+
153
+ // Check if the variable is a directory
154
+ let isDirectory = false ;
155
+ try {
156
+ const stat = await fileSystemService . stat ( uri ) ;
157
+ isDirectory = stat . type === FileType . Directory ;
158
+ } catch { }
159
+
160
+ if ( isDirectory ) {
161
+ elements . push ( < FolderVariable variableName = { variableName } folderUri = { uri } omitReferences = { omitReferences } description = { reference . modelDescription } /> ) ;
162
+ } else {
163
+ const filePathMode = ( isAgent && includeFilepathInCodeBlocks )
164
+ ? FilePathMode . AsAttribute
165
+ : includeFilepathInCodeBlocks
166
+ ? FilePathMode . AsComment
167
+ : FilePathMode . None ;
168
+ const file = < FileVariable alwaysIncludeSummary = { true } filePathMode = { filePathMode } variableName = { variableName } variableValue = { variableValue } omitReferences = { omitReferences } description = { reference . modelDescription } /> ;
169
+
170
+ if ( ! isAgent || ( ! URI . isUri ( variableValue ) || variableValue . scheme !== Schemas . vscodeNotebookCellOutput ) ) {
171
+ // When attaching outupts, there's no need to add the entire notebook file again, as model can request the notebook file.
172
+ // In non agent mode, we need to add the file for context.
173
+ elements . push ( file ) ;
174
+ }
175
+ if ( URI . isUri ( variableValue ) && variableValue . scheme === Schemas . vscodeNotebookCellOutput ) {
176
+ elements . push ( < NotebookCellOutputVariable outputUri = { variableValue } /> ) ;
177
+ }
147
178
}
148
179
} else if ( typeof variableValue === 'string' ) {
149
180
elements . push (
@@ -225,6 +256,47 @@ function getDiagnosticCode(diagnostic: Diagnostic): string {
225
256
return String ( code ) ;
226
257
}
227
258
259
+ interface IFolderVariableProps extends BasePromptElementProps {
260
+ variableName : string ;
261
+ folderUri : Uri ;
262
+ omitReferences ?: boolean ;
263
+ description ?: string ;
264
+ }
265
+
266
+ class FolderVariable extends PromptElement < IFolderVariableProps , IFileTreeData | undefined > {
267
+ constructor (
268
+ props : PromptElementProps < IFolderVariableProps > ,
269
+ @IInstantiationService private readonly instantiationService : IInstantiationService ,
270
+ @IPromptPathRepresentationService private readonly promptPathRepresentationService : IPromptPathRepresentationService ,
271
+ ) {
272
+ super ( props ) ;
273
+ }
274
+
275
+ override async prepare ( sizing : PromptSizing ) : Promise < IFileTreeData | undefined > {
276
+ try {
277
+ return this . instantiationService . invokeFunction ( accessor =>
278
+ workspaceVisualFileTree ( accessor , this . props . folderUri , { maxLength : 2000 , excludeDotFiles : false } , CancellationToken . None )
279
+ ) ;
280
+ } catch {
281
+ // Directory doesn't exist or is not accessible
282
+ return undefined ;
283
+ }
284
+ }
285
+
286
+ render ( state : IFileTreeData | undefined ) {
287
+ const folderPath = this . promptPathRepresentationService . getFilePath ( this . props . folderUri ) ;
288
+ return (
289
+ < Tag name = 'attachment' attrs = { this . props . variableName ? { id : this . props . variableName , folderPath } : undefined } >
290
+ < TextChunk >
291
+ { ! this . props . omitReferences && < references value = { [ new PromptReference ( { variableName : this . props . variableName } ) ] } /> }
292
+ { this . props . description ? this . props . description + ':\n' : '' }
293
+ The user attached the folder `{ folderPath } `{ state ? ' which has the following structure: ' + createFencedCodeBlock ( '' , state . tree ) : '' }
294
+ </ TextChunk >
295
+ </ Tag >
296
+ ) ;
297
+ }
298
+ }
299
+
228
300
export interface ChatToolCallProps extends GenericBasePromptElementProps , EmbeddedInsideUserMessage {
229
301
}
230
302
0 commit comments