Skip to content

sessions: add @ and # completion providers for new chat input#307738

Draft
osortega wants to merge 4 commits intomainfrom
copilot/cognitive-swordtail
Draft

sessions: add @ and # completion providers for new chat input#307738
osortega wants to merge 4 commits intomainfrom
copilot/cognitive-swordtail

Conversation

@osortega
Copy link
Copy Markdown
Contributor

@osortega osortega commented Apr 3, 2026

Summary

Adds @ (agent) and # (file) completion providers to the sessions new-chat welcome widget (NewChatViewPane).

Problem

The workbench chat completions (AgentCompletions, BuiltinDynamicCompletions) register exclusively for scheme: Schemas.vscodeChatInput. The sessions new-chat input uses a different URI scheme (sessions-chat), so @ and # completions never fire in the sessions window.

Since the sessions layer cannot directly depend on the workbench chat completion infrastructure, dedicated providers are needed.

Changes

New file: chatInputCompletions.ts

  • @ agent completions — queries IChatAgentService.getAgents(), shows non-default agents and their slash commands. Only allowed at the start of input (matching workbench behavior).
  • # file completions — searches workspace files via ISearchService when the user types #file:pattern. On accept, adds the file as a context attachment pill via a registered command.
  • Follows the established SlashCommandHandler pattern: registers on uri.scheme, uses _computeCompletionRanges() helper.
  • All imports respect sessions layering rules (base, editor, platform, workbench — no reverse dependency).

newChatContextAttachments.ts

  • Added public addAttachment(entry) method so the file completion command can add context pills (was previously only private _addAttachments).

newChatViewPane.ts

  • Instantiates ChatInputCompletions alongside the existing SlashCommandHandler, passing the editor and context attachments.

Register dedicated completion providers for the sessions-chat editor
scheme so that @ (agent) and # (file) completions work in the
NewChatViewPane welcome widget.

- Add ChatInputCompletions class with agent and file search providers
- Add public addAttachment() to NewChatContextAttachments for the
  file completion command
- Wire ChatInputCompletions into NewChatWidget alongside SlashCommandHandler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 3, 2026 22:10
Copy link
Copy Markdown
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

This PR adds sessions-layer completion support for the new-chat input so that @ (agent) and # (file/context) completions work in the Sessions window despite using a non-workbench chat input URI scheme.

Changes:

  • Added ChatInputCompletions to register @ agent and # file completion providers for the sessions new-chat editor.
  • Exposed a public addAttachment(...) API on NewChatContextAttachments to allow completion accept to create attachment pills.
  • Wired up the new completion registration in NewChatViewPane.
Show a summary per file
File Description
src/vs/sessions/contrib/chat/browser/newChatViewPane.ts Instantiates the new completions registrar alongside the existing slash command handler.
src/vs/sessions/contrib/chat/browser/newChatContextAttachments.ts Adds a public method to append a single attachment entry and re-render.
src/vs/sessions/contrib/chat/browser/chatInputCompletions.ts New sessions-layer completion providers for @ agents and #file search with an accept command to add attachment pills.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 4

Comment on lines +160 to +166
const text = `${typedLeader}file:${name}`;
const uriLabel = this.labelService.getUriLabel(resource, { relative: true });

return {
label: { label: name, description: uriLabel },
filterText: `${name} ${typedLeader}${name} ${uriLabel}`,
insertText: range.varWord?.endColumn === range.replace.endColumn ? `${text} ` : text,
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Accepting a file completion both inserts #file:… into the editor and also adds the file into attachedContext via the command. Since query is later sent as plain text alongside attachedContext, this can lead to duplicated/incorrect context (the text token may be parsed again by the workbench chat service). Consider removing the #file:… token from the input on accept (or using empty insertText and relying on the pill), similar to how the workbench reference command converts the typed token into an attachment.

Suggested change
const text = `${typedLeader}file:${name}`;
const uriLabel = this.labelService.getUriLabel(resource, { relative: true });
return {
label: { label: name, description: uriLabel },
filterText: `${name} ${typedLeader}${name} ${uriLabel}`,
insertText: range.varWord?.endColumn === range.replace.endColumn ? `${text} ` : text,
const uriLabel = this.labelService.getUriLabel(resource, { relative: true });
return {
label: { label: name, description: uriLabel },
filterText: `${name} ${typedLeader}${name} ${uriLabel}`,
insertText: '',

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +139
this._register(this.languageFeaturesService.completionProvider.register({ scheme: uri.scheme, hasAccessToAllModels: true }, {
_debugDisplayName: 'sessionsFileCompletions',
triggerCharacters: [chatVariableLeader, chatAgentLeader],
provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => {
const range = this._computeCompletionRanges(model, position, FileWord);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The file completion provider currently triggers for both # and @ and will run workspace file search for any @word (e.g. @w), mixing file results into agent completions and doing potentially expensive searches unnecessarily. If the intent is #file:… only, restrict triggerCharacters/regex to # and early-return unless the current word starts with #file:.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +91
const agents = this.chatAgentService.getAgents()
.filter(a => !a.isDefault);

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Agent completions are built from chatAgentService.getAgents().filter(a => !a.isDefault) without filtering by agent locations/modes or handling restricted agent names. The workbench completion logic filters by widget location/mode and uses getFullyQualifiedId(...) when agent.name isn’t allowed; otherwise the inserted @${agent.name} can be unresolvable in chat input. Consider aligning with src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts (AgentCompletions) for these filters/labels.

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +229
private _getOrCreateCacheKey(): { key: string; time: number } {
if (this._cacheKey && Date.now() - this._cacheKey.time > 60_000) {
this.searchService.clearCache(this._cacheKey.key);
this._cacheKey = undefined;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

_getOrCreateCacheKey clears the search cache only when the key expires and another completion request happens. Since this class is instantiated per new-chat widget, consider clearing the cache key in dispose() as well to avoid leaving search caches around after the widget is destroyed.

Suggested change
private _getOrCreateCacheKey(): { key: string; time: number } {
if (this._cacheKey && Date.now() - this._cacheKey.time > 60_000) {
this.searchService.clearCache(this._cacheKey.key);
this._cacheKey = undefined;
}
override dispose(): void {
this._clearCacheKey();
super.dispose();
}
private _clearCacheKey(): void {
if (this._cacheKey) {
this.searchService.clearCache(this._cacheKey.key);
this._cacheKey = undefined;
}
}
private _getOrCreateCacheKey(): { key: string; time: number } {
if (this._cacheKey && Date.now() - this._cacheKey.time > 60_000) {
this._clearCacheKey();
}

Copilot uses AI. Check for mistakes.
osortega and others added 3 commits April 3, 2026 15:48
The sessions window doesn't have a VS Code workspace context, so
IWorkspaceContextService returns no folders. Instead, use the same
selected project repo URI that the + button picker uses via
_getContextFolderUri().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tual filesystems

- Filter out chatSessionContributions (delegation agents) from @ completions,
  matching the workbench's AgentCompletions behavior
- Allow bare # to show files without requiring a typed pattern
- Add IFileService fallback for virtual filesystems (e.g. github-remote-file://)
  that ISearchService cannot query, walking the directory tree directly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When typing # in the sessions chat input, the file search may return
empty results (e.g., no workspace selected, virtual filesystem slow,
or search service unavailable). Previously this resulted in zero
suggestions, causing Monaco to not show the completion dropdown at all.

Now:
- Always show a '#file:' fallback entry that re-triggers suggest
- Only trigger file completions on # (not @, which is handled by the
  agent provider)
- Simplified pattern extraction to only strip # leader (not @)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants