Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/vs/workbench/api/browser/mainThreadChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { isValidPromptType } from '../../contrib/chat/common/promptSyntax/prompt
import { IChatModel } from '../../contrib/chat/common/model/chatModel.js';
import { ChatRequestAgentPart } from '../../contrib/chat/common/requestParser/chatParserTypes.js';
import { ChatRequestParser } from '../../contrib/chat/common/requestParser/chatRequestParser.js';
import { getDynamicVariablesForWidget, getSelectedToolAndToolSetsForWidget } from '../../contrib/chat/browser/attachments/chatVariables.js';
import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatNotebookEdit, IChatProgress, IChatService, IChatTask, IChatTaskSerialized, IChatWarningMessage } from '../../contrib/chat/common/chatService/chatService.js';
import { IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js';
import { ChatAgentLocation, ChatModeKind } from '../../contrib/chat/common/constants.js';
Expand Down Expand Up @@ -459,7 +460,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
return;
}

const parsedRequest = this._instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionResource, model.getValue()).parts;
const parsedRequest = this._instantiationService.createInstance(ChatRequestParser).parseChatRequestWithReferences(getDynamicVariablesForWidget(widget), getSelectedToolAndToolSetsForWidget(widget), model.getValue()).parts;
const agentPart = parsedRequest.find((part): part is ChatRequestAgentPart => part instanceof ChatRequestAgentPart);
const thisAgentId = this._agents.get(handle)?.id;
if (agentPart?.agent.id !== thisAgentId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { chatEditingWidgetFileStateContextKey, ModifiedFileEntryState } from '../../common/editing/chatEditingService.js';
import { ChatModel } from '../../common/model/chatModel.js';
import { ChatRequestParser } from '../../common/requestParser/chatRequestParser.js';
import { getDynamicVariablesForWidget, getSelectedToolAndToolSetsForWidget } from '../attachments/chatVariables.js';
import { ChatSendResult, IChatService } from '../../common/chatService/chatService.js';
import { IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js';
import { ChatAgentLocation } from '../../common/constants.js';
Expand Down Expand Up @@ -403,7 +404,7 @@ export class CreateRemoteAgentJobAction {
userPrompt = 'implement this.';
}

const attachedContext = widget.input.getAttachedAndImplicitContext(sessionResource);
const attachedContext = widget.input.getAttachedAndImplicitContext();
widget.input.acceptInput(true);

// For inline editor mode, add selection or cursor information
Expand Down Expand Up @@ -479,7 +480,7 @@ export class CreateRemoteAgentJobAction {
const requestParser = instantiationService.createInstance(ChatRequestParser);

// Add the request to the model first
const parsedRequest = requestParser.parseChatRequest(sessionResource, userPrompt, ChatAgentLocation.Chat);
const parsedRequest = requestParser.parseChatRequestWithReferences(getDynamicVariablesForWidget(widget), getSelectedToolAndToolSetsForWidget(widget), userPrompt, ChatAgentLocation.Chat);
const addedRequest = chatModel.addRequest(
parsedRequest,
{ variables: attachedContext.asArray() },
Expand Down
124 changes: 65 additions & 59 deletions src/vs/workbench/contrib/chat/browser/attachments/chatVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,72 @@

import { IChatVariablesService, IDynamicVariable } from '../../common/attachments/chatVariables.js';
import { IToolAndToolSetEnablementMap } from '../../common/tools/languageModelToolsService.js';
import { IChatWidgetService } from '../chat.js';
import { IChatWidget, IChatWidgetService } from '../chat.js';
import { ChatDynamicVariableModel } from './chatDynamicVariables.js';
import { Range } from '../../../../../editor/common/core/range.js';
import { URI } from '../../../../../base/common/uri.js';

export function getDynamicVariablesForWidget(widget: IChatWidget): ReadonlyArray<IDynamicVariable> {
if (!widget.viewModel || !widget.supportsFileReferences) {
return [];
}

const model = widget.getContrib<ChatDynamicVariableModel>(ChatDynamicVariableModel.ID);
if (!model) {
return [];
}

// track for editing state
if (widget.viewModel.editing && model.variables.length > 0) {
return model.variables;
}

if (widget.input.attachmentModel.attachments.length > 0 && widget.viewModel.editing) {
const references: IDynamicVariable[] = [];
const editorModel = widget.inputEditor.getModel();
const modelTextLength = editorModel?.getValueLength() ?? 0;
for (const attachment of widget.input.attachmentModel.attachments) {
// If the attachment has a range, it is a dynamic variable
if (attachment.range) {
if (attachment.range.start >= attachment.range.endExclusive) {
continue;
}

if (attachment.range.start < 0 || attachment.range.endExclusive > modelTextLength) {
continue;
}

if (!editorModel) {
continue;
}

const startPos = editorModel.getPositionAt(attachment.range.start);
const endPos = editorModel.getPositionAt(attachment.range.endExclusive);

const referenceObj: IDynamicVariable = {
id: attachment.id,
fullName: attachment.name,
modelDescription: attachment.modelDescription,
range: new Range(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column),
icon: attachment.icon,
isFile: attachment.kind === 'file',
isDirectory: attachment.kind === 'directory',
data: attachment.value
};
references.push(referenceObj);
}
}

return references.length > 0 ? references : model.variables;
}

return model.variables;
}

export function getSelectedToolAndToolSetsForWidget(widget: IChatWidget): IToolAndToolSetEnablementMap {
return widget.input.selectedToolsModel.entriesMap.get();
}

export class ChatVariablesService implements IChatVariablesService {
declare _serviceBrand: undefined;

Expand All @@ -18,73 +79,18 @@ export class ChatVariablesService implements IChatVariablesService {
) { }

getDynamicVariables(sessionResource: URI): ReadonlyArray<IDynamicVariable> {
// This is slightly wrong... the parser pulls dynamic references from the input widget, but there is no guarantee that message came from the input here.
// Need to ...
// - Parser takes list of dynamic references (annoying)
// - Or the parser is known to implicitly act on the input widget, and we need to call it before calling the chat service (maybe incompatible with the future, but easy)
const widget = this.chatWidgetService.getWidgetBySessionResource(sessionResource);
if (!widget || !widget.viewModel || !widget.supportsFileReferences) {
return [];
}

const model = widget.getContrib<ChatDynamicVariableModel>(ChatDynamicVariableModel.ID);
if (!model) {
if (!widget) {
return [];
}

// track for editing state
if (widget.viewModel.editing && model.variables.length > 0) {
return model.variables;
}

if (widget.input.attachmentModel.attachments.length > 0 && widget.viewModel.editing) {
const references: IDynamicVariable[] = [];
const editorModel = widget.inputEditor.getModel();
const modelTextLength = editorModel?.getValueLength() ?? 0;
for (const attachment of widget.input.attachmentModel.attachments) {
// If the attachment has a range, it is a dynamic variable
if (attachment.range) {
if (attachment.range.start >= attachment.range.endExclusive) {
continue;
}

if (attachment.range.start < 0 || attachment.range.endExclusive > modelTextLength) {
continue;
}

if (!editorModel) {
continue;
}

const startPos = editorModel.getPositionAt(attachment.range.start);
const endPos = editorModel.getPositionAt(attachment.range.endExclusive);

const referenceObj: IDynamicVariable = {
id: attachment.id,
fullName: attachment.name,
modelDescription: attachment.modelDescription,
range: new Range(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column),
icon: attachment.icon,
isFile: attachment.kind === 'file',
isDirectory: attachment.kind === 'directory',
data: attachment.value
};
references.push(referenceObj);
}
}

return references.length > 0 ? references : model.variables;
}

return model.variables;
return getDynamicVariablesForWidget(widget);
}

getSelectedToolAndToolSets(sessionResource: URI): IToolAndToolSetEnablementMap {
const widget = this.chatWidgetService.getWidgetBySessionResource(sessionResource);
if (!widget) {
return new Map();
}
return widget.input.selectedToolsModel.entriesMap.get();

return getSelectedToolAndToolSetsForWidget(widget);
}
}
7 changes: 4 additions & 3 deletions src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { IChatModel, IChatModelInputState, IChatResponseModel } from '../../comm
import { ChatMode, getModeNameForTelemetry, IChatModeService } from '../../common/chatModes.js';
import { chatAgentLeader, ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestSlashPromptPart, ChatRequestToolPart, ChatRequestToolSetPart, chatSubcommandLeader, formatChatQuestion, IParsedChatRequest } from '../../common/requestParser/chatParserTypes.js';
import { ChatRequestParser } from '../../common/requestParser/chatRequestParser.js';
import { getDynamicVariablesForWidget, getSelectedToolAndToolSetsForWidget } from '../attachments/chatVariables.js';
import { ChatRequestQueueKind, ChatSendResult, IChatLocationData, IChatSendRequestOptions, IChatService } from '../../common/chatService/chatService.js';
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { IChatSlashCommandService } from '../../common/participants/chatSlashCommands.js';
Expand Down Expand Up @@ -333,7 +334,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
}

this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser)
.parseChatRequest(this.viewModel.sessionResource, this.getInput(), this.location, {
.parseChatRequestWithReferences(getDynamicVariablesForWidget(this), getSelectedToolAndToolSetsForWidget(this), this.getInput(), this.location, {
selectedAgent: this._lastSelectedAgent,
mode: this.input.currentModeKind,
attachmentCapabilities: this.attachmentCapabilities,
Expand Down Expand Up @@ -854,7 +855,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
}

const previous = this.parsedChatRequest;
this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel.sessionResource, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind, attachmentCapabilities: this.attachmentCapabilities });
this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequestWithReferences(getDynamicVariablesForWidget(this), getSelectedToolAndToolSetsForWidget(this), this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent, mode: this.input.currentModeKind, attachmentCapabilities: this.attachmentCapabilities });
if (!previous || !IParsedChatRequest.equals(previous, this.parsedChatRequest)) {
this._onDidChangeParsedInput.fire();
}
Expand Down Expand Up @@ -2218,7 +2219,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
const editorValue = this.getInput();
const requestInputs: IChatRequestInputOptions = {
input: !query ? editorValue : query.query,
attachedContext: options?.enableImplicitContext === false ? this.input.getAttachedContext(this.viewModel.sessionResource) : this.input.getAttachedAndImplicitContext(this.viewModel.sessionResource),
attachedContext: options?.enableImplicitContext === false ? this.input.getAttachedContext() : this.input.getAttachedAndImplicitContext(),
};

const isUserQuery = !query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge

readonly selectedToolsModel: ChatSelectedTools;

public getAttachedContext(sessionResource: URI) {
public getAttachedContext() {
const contextArr = new ChatRequestVariableSet();
contextArr.add(...this.attachmentModel.attachments, ...this.chatContextService.getWorkspaceContextItems());
return contextArr;
}

public getAttachedAndImplicitContext(sessionResource: URI): ChatRequestVariableSet {
public getAttachedAndImplicitContext(): ChatRequestVariableSet {

const contextArr = this.getAttachedContext(sessionResource);
const contextArr = this.getAttachedContext();

if (this.implicitContext) {
const implicitChatVariables = this.implicitContext.enabledBaseEntries(this.configurationService.getValue<boolean>('chat.implicitContext.suggestedContext'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IChatAgentCommand, IChatAgentData, IChatAgentService } from '../../../.
import { chatSlashCommandBackground, chatSlashCommandForeground } from '../../../../common/widget/chatColors.js';
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestSlashPromptPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestToolSetPart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader } from '../../../../common/requestParser/chatParserTypes.js';
import { ChatRequestParser } from '../../../../common/requestParser/chatRequestParser.js';
import { getDynamicVariablesForWidget, getSelectedToolAndToolSetsForWidget } from '../../../attachments/chatVariables.js';
import { IPromptsService } from '../../../../common/promptSyntax/service/promptsService.js';
import { IChatWidget } from '../../../chat.js';
import { ChatWidget } from '../../chatWidget.js';
Expand Down Expand Up @@ -411,7 +412,7 @@ class ChatTokenDeleter extends Disposable {
// If this was a simple delete, try to find out whether it was inside a token
if (!change.text && this.widget.viewModel) {
const attachmentCapabilities = previousSelectedAgent?.capabilities ?? this.widget.attachmentCapabilities;
const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionResource, previousInputValue, widget.location, { selectedAgent: previousSelectedAgent, mode: this.widget.input.currentModeKind, attachmentCapabilities });
const previousParsedValue = parser.parseChatRequestWithReferences(getDynamicVariablesForWidget(this.widget), getSelectedToolAndToolSetsForWidget(this.widget), previousInputValue, this.widget.location, { selectedAgent: previousSelectedAgent, mode: this.widget.input.currentModeKind, attachmentCapabilities });

// For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping
const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestSlashPromptPart || p instanceof ChatRequestToolPart);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import { IInstantiationService } from '../../../../../../../platform/instantiati
import { ILogService } from '../../../../../../../platform/log/common/log.js';
import { IExtensionService, isProposedApiEnabled } from '../../../../../../services/extensions/common/extensions.js';
import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry } from '../../../../common/attachments/chatVariableEntries.js';
import { IChatVariablesService, IDynamicVariable } from '../../../../common/attachments/chatVariables.js';
import { IDynamicVariable } from '../../../../common/attachments/chatVariables.js';
import { IChatWidgetService } from '../../../chat.js';
import { getDynamicVariablesForWidget } from '../../../attachments/chatVariables.js';
import { ChatDynamicVariableModel } from '../../../attachments/chatDynamicVariables.js';
import { cleanupOldImages, createFileForMedia, resizeImage } from '../../../chatImageUtils.js';

Expand Down Expand Up @@ -201,7 +202,6 @@ class CopyAttachmentsProvider implements DocumentPasteEditProvider {

constructor(
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
@IChatVariablesService private readonly chatVariableService: IChatVariablesService
) { }

async prepareDocumentPaste(model: ITextModel, _ranges: readonly IRange[], _dataTransfer: IReadonlyVSDataTransfer, _token: CancellationToken): Promise<undefined | IReadonlyVSDataTransfer> {
Expand All @@ -212,7 +212,7 @@ class CopyAttachmentsProvider implements DocumentPasteEditProvider {
}

const attachments = widget.attachmentModel.attachments;
const dynamicVariables = this.chatVariableService.getDynamicVariables(widget.viewModel.sessionResource);
const dynamicVariables = getDynamicVariablesForWidget(widget);

if (attachments.length === 0 && dynamicVariables.length === 0) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ChatAgentLocation, ChatModeKind } from '../constants.js';
import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentService } from '../participants/chatAgents.js';
import { IChatSlashCommandService } from '../participants/chatSlashCommands.js';
import { IPromptsService } from '../promptSyntax/service/promptsService.js';
import { IToolData, IToolSet, isToolSet } from '../tools/languageModelToolsService.js';
import { IToolAndToolSetEnablementMap, IToolData, IToolSet, isToolSet } from '../tools/languageModelToolsService.js';
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestSlashPromptPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestToolSetPart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from './chatParserTypes.js';

const agentReg = /^@([\w_\-\.]+)(?=(\s|$|\b))/i; // An @-agent
Expand All @@ -37,11 +37,16 @@ export class ChatRequestParser {
) { }

parseChatRequest(sessionResource: URI, message: string, location: ChatAgentLocation = ChatAgentLocation.Chat, context?: IChatParserContext): IParsedChatRequest {
const parts: IParsedChatRequestPart[] = [];
const references = this.variableService.getDynamicVariables(sessionResource); // must access this list before any async calls
const selectedToolAndToolSets = this.variableService.getSelectedToolAndToolSets(sessionResource);
return this.parseChatRequestWithReferences(references, selectedToolAndToolSets, message, location, context);
}

parseChatRequestWithReferences(references: ReadonlyArray<IDynamicVariable>, selectedToolAndToolSets: IToolAndToolSetEnablementMap, message: string, location: ChatAgentLocation = ChatAgentLocation.Chat, context?: IChatParserContext): IParsedChatRequest {
const parts: IParsedChatRequestPart[] = [];
const toolsByName = new Map<string, IToolData>();
const toolSetsByName = new Map<string, IToolSet>();
for (const [entry, enabled] of this.variableService.getSelectedToolAndToolSets(sessionResource)) {
for (const [entry, enabled] of selectedToolAndToolSets) {
if (enabled) {
if (isToolSet(entry)) {
toolSetsByName.set(entry.referenceName, entry);
Expand Down
Loading
Loading