Skip to content

Commit fbed800

Browse files
authored
Change attached folder behaviour (#234)
1 parent d920fc1 commit fbed800

File tree

3 files changed

+98
-43
lines changed

3 files changed

+98
-43
lines changed

src/extension/prompt/node/chatParticipantRequestHandler.ts

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import type { ChatRequest, ChatRequestTurn2, ChatResponseStream, ChatResult, Loc
88
import { IAuthenticationChatUpgradeService } from '../../../platform/authentication/common/authenticationUpgrade';
99
import { getChatParticipantIdFromName, getChatParticipantNameFromId, workspaceAgentName } from '../../../platform/chat/common/chatAgents';
1010
import { CanceledMessage, ChatLocation } from '../../../platform/chat/common/commonTypes';
11-
import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';
12-
import { FileType } from '../../../platform/filesystem/common/fileTypes';
1311
import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';
1412
import { ILogService } from '../../../platform/log/common/logService';
1513
import { ITabsAndEditorsService } from '../../../platform/tabs/common/tabsAndEditorsService';
@@ -32,7 +30,7 @@ import { getAgentForIntent, Intent } from '../../common/constants';
3230
import { IConversationStore } from '../../conversationStore/node/conversationStore';
3331
import { IIntentService } from '../../intents/node/intentService';
3432
import { UnknownIntent } from '../../intents/node/unknownIntent';
35-
import { ContributedToolName, ToolName } from '../../tools/common/toolNames';
33+
import { ContributedToolName } from '../../tools/common/toolNames';
3634
import { ChatVariablesCollection } from '../common/chatVariablesCollection';
3735
import { Conversation, GlobalContextMessageMetadata, ICopilotChatResult, ICopilotChatResultIn, normalizeSummariesOnRounds, RenderedUserMessageMetadata, Turn, TurnStatus } from '../common/conversation';
3836
import { InternalToolReference } from '../common/intents';
@@ -80,7 +78,6 @@ export class ChatParticipantRequestHandler {
8078
@IConversationStore private readonly _conversationStore: IConversationStore,
8179
@ITabsAndEditorsService tabsAndEditorsService: ITabsAndEditorsService,
8280
@ILogService private readonly _logService: ILogService,
83-
@IFileSystemService private readonly _fileSystemService: IFileSystemService,
8481
@IAuthenticationChatUpgradeService private readonly _authenticationUpgradeService: IAuthenticationChatUpgradeService
8582
) {
8683
this.location = getLocation(request);
@@ -133,8 +130,6 @@ export class ChatParticipantRequestHandler {
133130
}
134131

135132
private async sanitizeVariables(): Promise<ChatRequest> {
136-
const directories: string[] = [];
137-
138133
const variablePromises = this.request.references.map(async (ref) => {
139134
const uri = isLocation(ref.value) ? ref.value.uri : URI.isUri(ref.value) ? ref.value : undefined;
140135
if (!uri) {
@@ -145,13 +140,10 @@ export class ChatParticipantRequestHandler {
145140
return ref;
146141
}
147142

148-
let removeVariable, stat;
143+
let removeVariable;
149144
try {
150-
[removeVariable, stat] = await Promise.all([
151-
// Filter out variables which contain paths which are ignored
152-
this._ignoreService.isCopilotIgnored(uri),
153-
this._fileSystemService.stat(uri)
154-
]);
145+
// Filter out variables which contain paths which are ignored
146+
removeVariable = await this._ignoreService.isCopilotIgnored(uri);
155147
} catch {
156148
// Non-existent files will be handled elsewhere. This might be a virtual document so it's ok if the fs service can't find it.
157149
}
@@ -161,23 +153,12 @@ export class ChatParticipantRequestHandler {
161153
this.turn.request.message = this.turn.request.message.slice(0, ref.range[0]) + this.turn.request.message.slice(ref.range[1]);
162154
}
163155

164-
// Check if the variable is a directory. Directories will be turned into a codebase tool reference
165-
if (!removeVariable && stat?.type === FileType.Directory) {
166-
removeVariable = true;
167-
directories.push(uri.fsPath);
168-
}
169-
170156
return removeVariable ? null : ref;
171157
});
172158

173159
const newVariables = coalesce(await Promise.all(variablePromises));
174160

175-
const newToolReferences: InternalToolReference[] = this.request.toolReferences.map(InternalToolReference.from);
176-
if (directories.length > 0) {
177-
newToolReferences.push({ name: ToolName.Codebase, id: generateUuid(), input: { scopedDirectories: directories, includeFileStructure: true } });
178-
}
179-
180-
return { ...this.request, references: newVariables, toolReferences: newToolReferences };
161+
return { ...this.request, references: newVariables };
181162
}
182163

183164
private async _shouldAskForPermissiveAuth(): Promise<boolean> {

src/extension/prompts/node/inline/inlineChatFix3Prompt.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { CancellationToken, ChatResponseStream, ChatVulnerability, Markdown
88
import { IResponsePart } from '../../../../platform/chat/common/chatMLFetcher';
99
import { ChatLocation } from '../../../../platform/chat/common/commonTypes';
1010
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
11+
import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';
1112
import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';
1213
import { ILanguageDiagnosticsService } from '../../../../platform/languages/common/languageDiagnosticsService';
1314
import { KnownSources } from '../../../../platform/languageServer/common/languageContextService';
@@ -49,6 +50,7 @@ export class InlineFix3Prompt extends PromptElement<InlineFixProps> {
4950

5051
constructor(props: InlineFixProps,
5152
@IIgnoreService private readonly ignoreService: IIgnoreService,
53+
@IFileSystemService private readonly fileSystemService: IFileSystemService,
5254
@IParserService private readonly parserService: IParserService,
5355
@ILanguageDiagnosticsService private readonly languageDiagnosticsService: ILanguageDiagnosticsService,
5456
@IConfigurationService private readonly configurationService: IConfigurationService,
@@ -96,7 +98,7 @@ export class InlineFix3Prompt extends PromptElement<InlineFixProps> {
9698
const GenerationRulesAndExample = enableCodeMapper ? CodeMapperRulesAndExample : PatchEditFixRulesAndExample;
9799
const InputCodeBlock = enableCodeMapper ? CodeMapperInputCodeBlock : PatchEditInputCodeBlock;
98100

99-
const renderedChatVariables = await renderChatVariables(chatVariables);
101+
const renderedChatVariables = await renderChatVariables(chatVariables, this.fileSystemService);
100102

101103
return (
102104
<>

src/extension/prompts/node/panel/chatVariables.tsx

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ import { BasePromptElementProps, PromptElement, PromptElementProps, PromptPiece,
77
import type { Diagnostic, DiagnosticSeverity, LanguageModelToolInformation } from 'vscode';
88
import { ChatFetchResponseType, ChatLocation } from '../../../../platform/chat/common/commonTypes';
99
import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';
10+
import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';
11+
import { FileType } from '../../../../platform/filesystem/common/fileTypes';
1012
import { ILogService } from '../../../../platform/log/common/logService';
1113
import { ICopilotToolCall } from '../../../../platform/networking/common/fetch';
1214
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
1315
import { IAlternativeNotebookContentService } from '../../../../platform/notebook/common/alternativeContent';
1416
import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';
17+
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
1518
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
19+
import { createFencedCodeBlock } from '../../../../util/common/markdown';
1620
import { getNotebookAndCellFromUri } from '../../../../util/common/notebooks';
1721
import { isLocation } from '../../../../util/common/types';
1822
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
@@ -34,7 +38,7 @@ import { Image } from './image';
3438
import { NotebookCellOutputVariable } from './notebookVariables';
3539
import { PanelChatBasePrompt } from './panelChatBasePrompt';
3640
import { sendInvokedToolTelemetry, toolCallErrorToResult, ToolResult, ToolResultMetadata } from './toolCalling';
37-
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
41+
import { IFileTreeData, workspaceVisualFileTree } from './workspace/visualFileTree';
3842

3943
export interface ChatVariablesProps extends BasePromptElementProps, EmbeddedInsideUserMessage {
4044
readonly chatVariables: ChatVariablesCollection;
@@ -44,8 +48,15 @@ export interface ChatVariablesProps extends BasePromptElementProps, EmbeddedInsi
4448
}
4549

4650
export class ChatVariables extends PromptElement<ChatVariablesProps, void> {
51+
constructor(
52+
props: ChatVariablesProps,
53+
@IFileSystemService private readonly fileSystemService: IFileSystemService,
54+
) {
55+
super(props);
56+
}
57+
4758
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);
4960
if (elements.length === 0) {
5061
return undefined;
5162
}
@@ -87,9 +98,16 @@ export interface ChatVariablesAndQueryProps extends BasePromptElementProps, Embe
8798
}
8899

89100
export class ChatVariablesAndQuery extends PromptElement<ChatVariablesAndQueryProps, void> {
101+
constructor(
102+
props: ChatVariablesAndQueryProps,
103+
@IFileSystemService private readonly fileSystemService: IFileSystemService,
104+
) {
105+
super(props);
106+
}
107+
90108
override async render(state: void, sizing: PromptSizing): Promise<PromptPiece<any, any> | undefined> {
91109
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);
93111

94112
if (this.props.embeddedInsideUserMessage ?? embeddedInsideUserMessageDefault) {
95113
if (!elements.length) {
@@ -121,7 +139,7 @@ function asUserMessage(element: PromptElement, priority: number | undefined): Us
121139
}
122140

123141

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[]> {
125143
const elements = [];
126144
for (const variable of chatVariables) {
127145
const { uniqueName: variableName, value: variableValue, reference } = variable;
@@ -130,20 +148,33 @@ export async function renderChatVariables(chatVariables: ChatVariablesCollection
130148
}
131149

132150
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+
}
147178
}
148179
} else if (typeof variableValue === 'string') {
149180
elements.push(
@@ -225,6 +256,47 @@ function getDiagnosticCode(diagnostic: Diagnostic): string {
225256
return String(code);
226257
}
227258

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+
228300
export interface ChatToolCallProps extends GenericBasePromptElementProps, EmbeddedInsideUserMessage {
229301
}
230302

0 commit comments

Comments
 (0)