mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 04:09:28 +00:00
chat - implement continue in dropdown (#277704)
This commit is contained in:
@@ -40,6 +40,9 @@ export interface IActionWidgetDropdownOptions extends IBaseDropdownOptions {
|
|||||||
* The benefits of this include non native features such as headers, descriptions, icons, and button bar
|
* The benefits of this include non native features such as headers, descriptions, icons, and button bar
|
||||||
*/
|
*/
|
||||||
export class ActionWidgetDropdown extends BaseDropdown {
|
export class ActionWidgetDropdown extends BaseDropdown {
|
||||||
|
|
||||||
|
private enabled: boolean = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
private readonly _options: IActionWidgetDropdownOptions,
|
private readonly _options: IActionWidgetDropdownOptions,
|
||||||
@@ -50,6 +53,10 @@ export class ActionWidgetDropdown extends BaseDropdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override show(): void {
|
override show(): void {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let actionBarActions = this._options.actionBarActions ?? this._options.actionBarActionProvider?.getActions() ?? [];
|
let actionBarActions = this._options.actionBarActions ?? this._options.actionBarActionProvider?.getActions() ?? [];
|
||||||
const actions = this._options.actions ?? this._options.actionProvider?.getActions() ?? [];
|
const actions = this._options.actions ?? this._options.actionProvider?.getActions() ?? [];
|
||||||
const actionWidgetItems: IActionListItem<IActionWidgetDropdownAction>[] = [];
|
const actionWidgetItems: IActionListItem<IActionWidgetDropdownAction>[] = [];
|
||||||
@@ -158,4 +165,8 @@ export class ActionWidgetDropdown extends BaseDropdown {
|
|||||||
accessibilityProvider
|
accessibilityProvider
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setEnabled(enabled: boolean): void {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export class ActionWidgetDropdownActionViewItem extends BaseActionViewItem {
|
|||||||
const disabled = !this.action.enabled;
|
const disabled = !this.action.enabled;
|
||||||
this.actionItem?.classList.toggle('disabled', disabled);
|
this.actionItem?.classList.toggle('disabled', disabled);
|
||||||
this.element?.classList.toggle('disabled', disabled);
|
this.element?.classList.toggle('disabled', disabled);
|
||||||
|
this.actionWidgetDropdown?.setEnabled(!disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,468 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Action2, MenuId, MenuItemAction } from '../../../../../platform/actions/common/actions.js';
|
||||||
|
import { IDisposable } from '../../../../../base/common/lifecycle.js';
|
||||||
|
import { ActionWidgetDropdownActionViewItem } from '../../../../../platform/actions/browser/actionWidgetDropdownActionViewItem.js';
|
||||||
|
import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js';
|
||||||
|
import { IActionWidgetDropdownAction, IActionWidgetDropdownActionProvider } from '../../../../../platform/actionWidget/browser/actionWidgetDropdown.js';
|
||||||
|
import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
|
||||||
|
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
|
||||||
|
import { IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js';
|
||||||
|
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||||
|
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||||
|
import { AgentSessionProviders } from '../agentSessions/agentSessions.js';
|
||||||
|
import { localize, localize2 } from '../../../../../nls.js';
|
||||||
|
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||||
|
import { basename, relativePath } from '../../../../../base/common/resources.js';
|
||||||
|
import { URI } from '../../../../../base/common/uri.js';
|
||||||
|
import { generateUuid } from '../../../../../base/common/uuid.js';
|
||||||
|
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
|
||||||
|
import { isLocation } from '../../../../../editor/common/languages.js';
|
||||||
|
import { isITextModel } from '../../../../../editor/common/model.js';
|
||||||
|
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
||||||
|
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||||
|
import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js';
|
||||||
|
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
|
||||||
|
import { IEditorService } from '../../../../services/editor/common/editorService.js';
|
||||||
|
import { IRemoteCodingAgent, IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js';
|
||||||
|
import { IChatAgentService, IChatAgent, IChatAgentHistoryEntry } from '../../common/chatAgents.js';
|
||||||
|
import { ChatContextKeys } from '../../common/chatContextKeys.js';
|
||||||
|
import { IChatModel, IChatRequestModel, toChatHistoryContent } from '../../common/chatModel.js';
|
||||||
|
import { ChatRequestParser } from '../../common/chatRequestParser.js';
|
||||||
|
import { IChatService, IChatPullRequestContent } from '../../common/chatService.js';
|
||||||
|
import { chatSessionResourceToId } from '../../common/chatUri.js';
|
||||||
|
import { ChatRequestVariableSet, isChatRequestFileEntry } from '../../common/chatVariableEntries.js';
|
||||||
|
import { ChatAgentLocation, ChatConfiguration } from '../../common/constants.js';
|
||||||
|
import { IChatWidget, IChatWidgetService } from '../chat.js';
|
||||||
|
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
|
||||||
|
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||||
|
|
||||||
|
export class ContinueChatInSessionAction extends Action2 {
|
||||||
|
|
||||||
|
static readonly ID = 'workbench.action.chat.continueChatInSession';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: ContinueChatInSessionAction.ID,
|
||||||
|
title: localize2('continueChatInSession', "Continue Chat in..."),
|
||||||
|
tooltip: localize('continueChatInSession', "Continue Chat in..."),
|
||||||
|
precondition: ContextKeyExpr.and(
|
||||||
|
ChatContextKeys.enabled,
|
||||||
|
ChatContextKeys.requestInProgress.negate(),
|
||||||
|
ChatContextKeys.remoteJobCreating.negate(),
|
||||||
|
),
|
||||||
|
menu: {
|
||||||
|
id: MenuId.ChatExecute,
|
||||||
|
group: 'navigation',
|
||||||
|
order: 3.4,
|
||||||
|
when: ContextKeyExpr.and(
|
||||||
|
ContextKeyExpr.or(
|
||||||
|
ChatContextKeys.hasRemoteCodingAgent,
|
||||||
|
ChatContextKeys.hasCloudButtonV2
|
||||||
|
),
|
||||||
|
ChatContextKeys.lockedToCodingAgent.negate(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override async run(): Promise<void> {
|
||||||
|
// Handled by a custom action item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChatContinueInSessionActionItem extends ActionWidgetDropdownActionViewItem {
|
||||||
|
constructor(
|
||||||
|
action: MenuItemAction,
|
||||||
|
@IActionWidgetService actionWidgetService: IActionWidgetService,
|
||||||
|
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||||
|
@IKeybindingService keybindingService: IKeybindingService,
|
||||||
|
@IChatSessionsService chatSessionsService: IChatSessionsService,
|
||||||
|
@IInstantiationService instantiationService: IInstantiationService
|
||||||
|
) {
|
||||||
|
super(action, {
|
||||||
|
actionProvider: ChatContinueInSessionActionItem.actionProvider(chatSessionsService, instantiationService)
|
||||||
|
}, actionWidgetService, keybindingService, contextKeyService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static actionProvider(chatSessionsService: IChatSessionsService, instantiationService: IInstantiationService): IActionWidgetDropdownActionProvider {
|
||||||
|
return {
|
||||||
|
getActions: () => {
|
||||||
|
const actions: IActionWidgetDropdownAction[] = [];
|
||||||
|
const contributions = chatSessionsService.getAllChatSessionContributions();
|
||||||
|
|
||||||
|
// Continue in Background
|
||||||
|
const backgroundContrib = contributions.find(contrib => contrib.type === AgentSessionProviders.Background);
|
||||||
|
if (backgroundContrib && backgroundContrib.canDelegate !== false) {
|
||||||
|
actions.push(this.toAction(backgroundContrib, instantiationService));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue in Cloud
|
||||||
|
const cloudContrib = contributions.find(contrib => contrib.type === AgentSessionProviders.Cloud);
|
||||||
|
if (cloudContrib && cloudContrib.canDelegate !== false) {
|
||||||
|
actions.push(this.toAction(cloudContrib, instantiationService));
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static toAction(contrib: IChatSessionsExtensionPoint, instantiationService: IInstantiationService): IActionWidgetDropdownAction {
|
||||||
|
return {
|
||||||
|
id: contrib.type,
|
||||||
|
enabled: true,
|
||||||
|
icon: contrib.type === AgentSessionProviders.Cloud ? Codicon.cloud : Codicon.collection,
|
||||||
|
class: undefined,
|
||||||
|
tooltip: contrib.displayName,
|
||||||
|
label: contrib.type === AgentSessionProviders.Cloud ?
|
||||||
|
localize('continueInCloud', "Continue in Cloud") :
|
||||||
|
localize('continueInBackground', "Continue in Background"),
|
||||||
|
run: () => instantiationService.invokeFunction(accessor => new CreateRemoteAgentJobAction().run(accessor, contrib))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override renderLabel(element: HTMLElement): IDisposable | null {
|
||||||
|
const icon = this.contextKeyService.contextMatchesRules(ChatContextKeys.remoteJobCreating) ? Codicon.sync : Codicon.export;
|
||||||
|
element.classList.add(...ThemeIcon.asClassNameArray(icon));
|
||||||
|
|
||||||
|
return super.renderLabel(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateRemoteAgentJobAction {
|
||||||
|
|
||||||
|
private static readonly markdownStringTrustedOptions = {
|
||||||
|
isTrusted: {
|
||||||
|
enabledCommands: [] as string[],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
private async pickCodingAgent<T extends IChatSessionsExtensionPoint | IRemoteCodingAgent>(
|
||||||
|
quickPickService: IQuickInputService,
|
||||||
|
options: T[]
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
if (options.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (options.length === 1) {
|
||||||
|
return options[0];
|
||||||
|
}
|
||||||
|
const pick = await quickPickService.pick(
|
||||||
|
options.map(a => ({
|
||||||
|
label: a.displayName,
|
||||||
|
description: a.description,
|
||||||
|
agent: a,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
placeHolder: localize('selectBackgroundAgent', "Select Agent to delegate the task to"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!pick) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return pick.agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createWithChatSessions(
|
||||||
|
targetAgentId: string,
|
||||||
|
chatService: IChatService,
|
||||||
|
sessionResource: URI,
|
||||||
|
attachedContext: ChatRequestVariableSet,
|
||||||
|
userPrompt: string,
|
||||||
|
chatSummary?: {
|
||||||
|
prompt?: string;
|
||||||
|
history?: string;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
await chatService.sendRequest(sessionResource, userPrompt, {
|
||||||
|
agentIdSilent: targetAgentId,
|
||||||
|
attachedContext: attachedContext.asArray(),
|
||||||
|
chatSummary,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createWithLegacy(
|
||||||
|
remoteCodingAgentService: IRemoteCodingAgentsService,
|
||||||
|
commandService: ICommandService,
|
||||||
|
quickPickService: IQuickInputService,
|
||||||
|
chatModel: IChatModel,
|
||||||
|
addedRequest: IChatRequestModel,
|
||||||
|
widget: IChatWidget,
|
||||||
|
userPrompt: string,
|
||||||
|
summary?: string,
|
||||||
|
) {
|
||||||
|
const agents = remoteCodingAgentService.getAvailableAgents();
|
||||||
|
const agent = await this.pickCodingAgent(quickPickService, agents);
|
||||||
|
if (!agent) {
|
||||||
|
chatModel.completeResponse(addedRequest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the remote command
|
||||||
|
const result: Omit<IChatPullRequestContent, 'kind'> | string | undefined = await commandService.executeCommand(agent.command, {
|
||||||
|
userPrompt,
|
||||||
|
summary,
|
||||||
|
_version: 2, // Signal that we support the new response format
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result && typeof result === 'object') { /* _version === 2 */
|
||||||
|
chatModel.acceptResponseProgress(addedRequest, { kind: 'pullRequest', ...result });
|
||||||
|
chatModel.acceptResponseProgress(addedRequest, {
|
||||||
|
kind: 'markdownContent', content: new MarkdownString(
|
||||||
|
localize('remoteAgentResponse2', "Your work will be continued in this pull request."),
|
||||||
|
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else if (typeof result === 'string') {
|
||||||
|
chatModel.acceptResponseProgress(addedRequest, {
|
||||||
|
kind: 'markdownContent',
|
||||||
|
content: new MarkdownString(
|
||||||
|
localize('remoteAgentResponse', "Coding agent response: {0}", result),
|
||||||
|
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
// Extension will open up the pull request in another view
|
||||||
|
widget.clear();
|
||||||
|
} else {
|
||||||
|
chatModel.acceptResponseProgress(addedRequest, {
|
||||||
|
kind: 'markdownContent',
|
||||||
|
content: new MarkdownString(
|
||||||
|
localize('remoteAgentError', "Coding agent session cancelled."),
|
||||||
|
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async run(accessor: ServicesAccessor, continuationTarget: IChatSessionsExtensionPoint) {
|
||||||
|
const contextKeyService = accessor.get(IContextKeyService);
|
||||||
|
const remoteJobCreatingKey = ChatContextKeys.remoteJobCreating.bindTo(contextKeyService);
|
||||||
|
|
||||||
|
try {
|
||||||
|
remoteJobCreatingKey.set(true);
|
||||||
|
|
||||||
|
const configurationService = accessor.get(IConfigurationService);
|
||||||
|
const widgetService = accessor.get(IChatWidgetService);
|
||||||
|
const chatAgentService = accessor.get(IChatAgentService);
|
||||||
|
const chatService = accessor.get(IChatService);
|
||||||
|
const commandService = accessor.get(ICommandService);
|
||||||
|
const quickPickService = accessor.get(IQuickInputService);
|
||||||
|
const remoteCodingAgentService = accessor.get(IRemoteCodingAgentsService);
|
||||||
|
const workspaceContextService = accessor.get(IWorkspaceContextService);
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
|
||||||
|
const widget = widgetService.lastFocusedWidget;
|
||||||
|
if (!widget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!widget.viewModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const chatModel = widget.viewModel.model;
|
||||||
|
if (!chatModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionResource = widget.viewModel.sessionResource;
|
||||||
|
const chatRequests = chatModel.getRequests();
|
||||||
|
let userPrompt = widget.getInput();
|
||||||
|
if (!userPrompt) {
|
||||||
|
if (!chatRequests.length) {
|
||||||
|
// Nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userPrompt = 'implement this.';
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachedContext = widget.input.getAttachedAndImplicitContext(sessionResource);
|
||||||
|
widget.input.acceptInput(true);
|
||||||
|
|
||||||
|
// For inline editor mode, add selection or cursor information
|
||||||
|
if (widget.location === ChatAgentLocation.EditorInline) {
|
||||||
|
const activeEditor = editorService.activeTextEditorControl;
|
||||||
|
if (activeEditor) {
|
||||||
|
const model = activeEditor.getModel();
|
||||||
|
let activeEditorUri: URI | undefined = undefined;
|
||||||
|
if (model && isITextModel(model)) {
|
||||||
|
activeEditorUri = model.uri as URI;
|
||||||
|
}
|
||||||
|
const selection = activeEditor.getSelection();
|
||||||
|
if (activeEditorUri && selection) {
|
||||||
|
attachedContext.add({
|
||||||
|
kind: 'file',
|
||||||
|
id: 'vscode.implicit.selection',
|
||||||
|
name: basename(activeEditorUri),
|
||||||
|
value: {
|
||||||
|
uri: activeEditorUri,
|
||||||
|
range: selection
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Chat);
|
||||||
|
const instantiationService = accessor.get(IInstantiationService);
|
||||||
|
const requestParser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
const continuationTargetType = continuationTarget.type;
|
||||||
|
|
||||||
|
// Add the request to the model first
|
||||||
|
const parsedRequest = requestParser.parseChatRequest(sessionResource, userPrompt, ChatAgentLocation.Chat);
|
||||||
|
const addedRequest = chatModel.addRequest(
|
||||||
|
parsedRequest,
|
||||||
|
{ variables: attachedContext.asArray() },
|
||||||
|
0,
|
||||||
|
undefined,
|
||||||
|
defaultAgent
|
||||||
|
);
|
||||||
|
|
||||||
|
let title: string | undefined = undefined;
|
||||||
|
|
||||||
|
// -- summarize userPrompt if necessary
|
||||||
|
let summarizedUserPrompt: string | undefined = undefined;
|
||||||
|
if (defaultAgent && userPrompt.length > 10_000) {
|
||||||
|
chatModel.acceptResponseProgress(addedRequest, {
|
||||||
|
kind: 'progressMessage',
|
||||||
|
content: new MarkdownString(
|
||||||
|
localize('summarizeUserPromptCreateRemoteJob', "Summarizing user prompt"),
|
||||||
|
CreateRemoteAgentJobAction.markdownStringTrustedOptions,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
({ title, summarizedUserPrompt } = await this.generateSummarizedUserPrompt(sessionResource, userPrompt, attachedContext, title, chatAgentService, defaultAgent, summarizedUserPrompt));
|
||||||
|
}
|
||||||
|
|
||||||
|
let summary: string = '';
|
||||||
|
|
||||||
|
// Add selection or cursor information to the summary
|
||||||
|
attachedContext.asArray().forEach(ctx => {
|
||||||
|
if (isChatRequestFileEntry(ctx) && ctx.value && isLocation(ctx.value)) {
|
||||||
|
const range = ctx.value.range;
|
||||||
|
const isSelection = range.startLineNumber !== range.endLineNumber || range.startColumn !== range.endColumn;
|
||||||
|
|
||||||
|
// Get relative path for the file
|
||||||
|
let filePath = ctx.name;
|
||||||
|
const workspaceFolder = workspaceContextService.getWorkspaceFolder(ctx.value.uri);
|
||||||
|
|
||||||
|
if (workspaceFolder && ctx.value.uri) {
|
||||||
|
const relativePathResult = relativePath(workspaceFolder.uri, ctx.value.uri);
|
||||||
|
if (relativePathResult) {
|
||||||
|
filePath = relativePathResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelection) {
|
||||||
|
summary += `User has selected text in file ${filePath} from ${range.startLineNumber}:${range.startColumn} to ${range.endLineNumber}:${range.endColumn}\n`;
|
||||||
|
} else {
|
||||||
|
summary += `User is on file ${filePath} at position ${range.startLineNumber}:${range.startColumn}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- summarize context if necessary
|
||||||
|
if (defaultAgent && chatRequests.length > 1) {
|
||||||
|
chatModel.acceptResponseProgress(addedRequest, {
|
||||||
|
kind: 'progressMessage',
|
||||||
|
content: new MarkdownString(
|
||||||
|
localize('analyzingChatHistory', "Analyzing chat history"),
|
||||||
|
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
({ title, summary } = await this.generateSummarizedChatHistory(chatRequests, sessionResource, title, chatAgentService, defaultAgent, summary));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
summary += `\nTITLE: ${title}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const isChatSessionsExperimentEnabled = configurationService.getValue<boolean>(ChatConfiguration.UseCloudButtonV2);
|
||||||
|
if (isChatSessionsExperimentEnabled) {
|
||||||
|
await chatService.removeRequest(sessionResource, addedRequest.id);
|
||||||
|
return await this.createWithChatSessions(
|
||||||
|
continuationTargetType,
|
||||||
|
chatService,
|
||||||
|
sessionResource,
|
||||||
|
attachedContext,
|
||||||
|
userPrompt,
|
||||||
|
{
|
||||||
|
prompt: summarizedUserPrompt,
|
||||||
|
history: summary,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Below is the legacy implementation
|
||||||
|
|
||||||
|
chatModel.acceptResponseProgress(addedRequest, {
|
||||||
|
kind: 'progressMessage',
|
||||||
|
content: new MarkdownString(
|
||||||
|
localize('creatingRemoteJob', "Delegating to coding agent"),
|
||||||
|
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.createWithLegacy(remoteCodingAgentService, commandService, quickPickService, chatModel, addedRequest, widget, summarizedUserPrompt || userPrompt, summary);
|
||||||
|
chatModel.setResponse(addedRequest, {});
|
||||||
|
chatModel.completeResponse(addedRequest);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error creating remote coding agent job', e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
remoteJobCreatingKey.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generateSummarizedChatHistory(chatRequests: IChatRequestModel[], sessionResource: URI, title: string | undefined, chatAgentService: IChatAgentService, defaultAgent: IChatAgent, summary: string) {
|
||||||
|
const historyEntries: IChatAgentHistoryEntry[] = chatRequests
|
||||||
|
.map((req): IChatAgentHistoryEntry => ({
|
||||||
|
request: {
|
||||||
|
sessionId: chatSessionResourceToId(sessionResource),
|
||||||
|
sessionResource,
|
||||||
|
requestId: req.id,
|
||||||
|
agentId: req.response?.agent?.id ?? '',
|
||||||
|
message: req.message.text,
|
||||||
|
command: req.response?.slashCommand?.name,
|
||||||
|
variables: req.variableData,
|
||||||
|
location: ChatAgentLocation.Chat,
|
||||||
|
editedFileEvents: req.editedFileEvents,
|
||||||
|
},
|
||||||
|
response: toChatHistoryContent(req.response!.response.value),
|
||||||
|
result: req.response?.result ?? {}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: Determine a cutoff point where we stop including earlier history
|
||||||
|
// For example, if the user has already delegated to a coding agent once,
|
||||||
|
// prefer the conversation afterwards.
|
||||||
|
title ??= await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None);
|
||||||
|
summary += await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None);
|
||||||
|
return { title, summary };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generateSummarizedUserPrompt(sessionResource: URI, userPrompt: string, attachedContext: ChatRequestVariableSet, title: string | undefined, chatAgentService: IChatAgentService, defaultAgent: IChatAgent, summarizedUserPrompt: string | undefined) {
|
||||||
|
const userPromptEntry: IChatAgentHistoryEntry = {
|
||||||
|
request: {
|
||||||
|
sessionId: chatSessionResourceToId(sessionResource),
|
||||||
|
sessionResource,
|
||||||
|
requestId: generateUuid(),
|
||||||
|
agentId: '',
|
||||||
|
message: userPrompt,
|
||||||
|
command: undefined,
|
||||||
|
variables: { variables: attachedContext.asArray() },
|
||||||
|
location: ChatAgentLocation.Chat,
|
||||||
|
editedFileEvents: [],
|
||||||
|
},
|
||||||
|
response: [],
|
||||||
|
result: {}
|
||||||
|
};
|
||||||
|
const historyEntries = [userPromptEntry];
|
||||||
|
title = await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None);
|
||||||
|
summarizedUserPrompt = await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None);
|
||||||
|
return { title, summarizedUserPrompt };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,40 +3,26 @@
|
|||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
|
||||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||||
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
|
|
||||||
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
|
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
|
||||||
import { basename, relativePath } from '../../../../../base/common/resources.js';
|
import { basename } from '../../../../../base/common/resources.js';
|
||||||
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||||
import { assertType } from '../../../../../base/common/types.js';
|
import { assertType } from '../../../../../base/common/types.js';
|
||||||
import { URI } from '../../../../../base/common/uri.js';
|
|
||||||
import { isLocation } from '../../../../../editor/common/languages.js';
|
|
||||||
import { generateUuid } from '../../../../../base/common/uuid.js';
|
|
||||||
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
|
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
|
||||||
import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';
|
import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';
|
||||||
import { localize, localize2 } from '../../../../../nls.js';
|
import { localize, localize2 } from '../../../../../nls.js';
|
||||||
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
|
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
|
||||||
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||||
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
|
||||||
import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
|
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
|
||||||
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
|
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
|
||||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||||
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
|
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||||
import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js';
|
|
||||||
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
|
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
|
||||||
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
|
|
||||||
import { IEditorService } from '../../../../services/editor/common/editorService.js';
|
|
||||||
import { IRemoteCodingAgent, IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js';
|
|
||||||
import { IChatAgent, IChatAgentHistoryEntry, IChatAgentService } from '../../common/chatAgents.js';
|
|
||||||
import { ChatContextKeys } from '../../common/chatContextKeys.js';
|
import { ChatContextKeys } from '../../common/chatContextKeys.js';
|
||||||
import { IChatModel, IChatRequestModel, toChatHistoryContent } from '../../common/chatModel.js';
|
|
||||||
import { IChatMode, IChatModeService } from '../../common/chatModes.js';
|
import { IChatMode, IChatModeService } from '../../common/chatModes.js';
|
||||||
import { chatVariableLeader } from '../../common/chatParserTypes.js';
|
import { chatVariableLeader } from '../../common/chatParserTypes.js';
|
||||||
import { ChatRequestParser } from '../../common/chatRequestParser.js';
|
import { IChatService } from '../../common/chatService.js';
|
||||||
import { IChatPullRequestContent, IChatService } from '../../common/chatService.js';
|
|
||||||
import { IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js';
|
|
||||||
import { ChatRequestVariableSet, isChatRequestFileEntry } from '../../common/chatVariableEntries.js';
|
|
||||||
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, } from '../../common/constants.js';
|
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, } from '../../common/constants.js';
|
||||||
import { ILanguageModelChatMetadata } from '../../common/languageModels.js';
|
import { ILanguageModelChatMetadata } from '../../common/languageModels.js';
|
||||||
import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js';
|
import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js';
|
||||||
@@ -44,8 +30,7 @@ import { IChatWidget, IChatWidgetService, showChatWidgetInViewOrEditor } from '.
|
|||||||
import { getEditingSessionContext } from '../chatEditing/chatEditingActions.js';
|
import { getEditingSessionContext } from '../chatEditing/chatEditingActions.js';
|
||||||
import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, handleCurrentEditingSession, handleModeSwitch } from './chatActions.js';
|
import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, handleCurrentEditingSession, handleModeSwitch } from './chatActions.js';
|
||||||
import { ctxHasEditorModification } from '../chatEditing/chatEditingEditorContextKeys.js';
|
import { ctxHasEditorModification } from '../chatEditing/chatEditingEditorContextKeys.js';
|
||||||
import { chatSessionResourceToId } from '../../common/chatUri.js';
|
import { ContinueChatInSessionAction } from './chatContinueInAction.js';
|
||||||
import { isITextModel } from '../../../../../editor/common/model.js';
|
|
||||||
|
|
||||||
export interface IVoiceChatExecuteActionContext {
|
export interface IVoiceChatExecuteActionContext {
|
||||||
readonly disableTimeout?: boolean;
|
readonly disableTimeout?: boolean;
|
||||||
@@ -621,416 +606,6 @@ class SubmitWithoutDispatchingAction extends Action2 {
|
|||||||
widget?.acceptInput(context?.inputValue, { noCommandDetection: true });
|
widget?.acceptInput(context?.inputValue, { noCommandDetection: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class CreateRemoteAgentJobAction extends Action2 {
|
|
||||||
static readonly ID = 'workbench.action.chat.createRemoteAgentJob';
|
|
||||||
|
|
||||||
static readonly markdownStringTrustedOptions = {
|
|
||||||
isTrusted: {
|
|
||||||
enabledCommands: [] as string[],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const precondition = ContextKeyExpr.and(
|
|
||||||
ChatContextKeys.inputHasText,
|
|
||||||
whenNotInProgress,
|
|
||||||
ChatContextKeys.remoteJobCreating.negate(),
|
|
||||||
);
|
|
||||||
|
|
||||||
super({
|
|
||||||
id: CreateRemoteAgentJobAction.ID,
|
|
||||||
// TODO(joshspicer): Generalize title/tooltip - pull from contribution
|
|
||||||
title: localize2('actions.chat.createRemoteJob', "Delegate to Agent"),
|
|
||||||
icon: Codicon.sendToRemoteAgent,
|
|
||||||
precondition,
|
|
||||||
toggled: {
|
|
||||||
condition: ChatContextKeys.remoteJobCreating,
|
|
||||||
icon: Codicon.sync,
|
|
||||||
tooltip: localize('remoteJobCreating', "Delegating to Agent"),
|
|
||||||
},
|
|
||||||
menu: [
|
|
||||||
{
|
|
||||||
id: MenuId.ChatExecute,
|
|
||||||
group: 'navigation',
|
|
||||||
order: 3.4,
|
|
||||||
when: ContextKeyExpr.and(
|
|
||||||
ContextKeyExpr.or(
|
|
||||||
ChatContextKeys.hasRemoteCodingAgent,
|
|
||||||
ChatContextKeys.hasCloudButtonV2
|
|
||||||
),
|
|
||||||
ChatContextKeys.lockedToCodingAgent.negate(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async pickCodingAgent<T extends IChatSessionsExtensionPoint | IRemoteCodingAgent>(
|
|
||||||
quickPickService: IQuickInputService,
|
|
||||||
options: T[]
|
|
||||||
): Promise<T | undefined> {
|
|
||||||
if (options.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (options.length === 1) {
|
|
||||||
return options[0];
|
|
||||||
}
|
|
||||||
const pick = await quickPickService.pick(
|
|
||||||
options.map(a => ({
|
|
||||||
label: a.displayName,
|
|
||||||
description: a.description,
|
|
||||||
agent: a,
|
|
||||||
})),
|
|
||||||
{
|
|
||||||
placeHolder: localize('selectBackgroundAgent', "Select Agent to delegate the task to"),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!pick) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return pick.agent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createWithChatSessions(
|
|
||||||
targetAgentId: string,
|
|
||||||
chatSessionsService: IChatSessionsService,
|
|
||||||
chatService: IChatService,
|
|
||||||
quickPickService: IQuickInputService,
|
|
||||||
sessionResource: URI,
|
|
||||||
attachedContext: ChatRequestVariableSet,
|
|
||||||
userPrompt: string,
|
|
||||||
chatSummary?: {
|
|
||||||
prompt?: string;
|
|
||||||
history?: string;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
await chatService.sendRequest(sessionResource, userPrompt, {
|
|
||||||
agentIdSilent: targetAgentId,
|
|
||||||
attachedContext: attachedContext.asArray(),
|
|
||||||
chatSummary,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createWithLegacy(
|
|
||||||
remoteCodingAgentService: IRemoteCodingAgentsService,
|
|
||||||
commandService: ICommandService,
|
|
||||||
quickPickService: IQuickInputService,
|
|
||||||
chatModel: IChatModel,
|
|
||||||
addedRequest: IChatRequestModel,
|
|
||||||
widget: IChatWidget,
|
|
||||||
userPrompt: string,
|
|
||||||
summary?: string,
|
|
||||||
) {
|
|
||||||
const agents = remoteCodingAgentService.getAvailableAgents();
|
|
||||||
const agent = await this.pickCodingAgent(quickPickService, agents);
|
|
||||||
if (!agent) {
|
|
||||||
chatModel.completeResponse(addedRequest);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the remote command
|
|
||||||
const result: Omit<IChatPullRequestContent, 'kind'> | string | undefined = await commandService.executeCommand(agent.command, {
|
|
||||||
userPrompt,
|
|
||||||
summary,
|
|
||||||
_version: 2, // Signal that we support the new response format
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result && typeof result === 'object') { /* _version === 2 */
|
|
||||||
chatModel.acceptResponseProgress(addedRequest, { kind: 'pullRequest', ...result });
|
|
||||||
chatModel.acceptResponseProgress(addedRequest, {
|
|
||||||
kind: 'markdownContent', content: new MarkdownString(
|
|
||||||
localize('remoteAgentResponse2', "Your work will be continued in this pull request."),
|
|
||||||
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
|
||||||
)
|
|
||||||
});
|
|
||||||
} else if (typeof result === 'string') {
|
|
||||||
chatModel.acceptResponseProgress(addedRequest, {
|
|
||||||
kind: 'markdownContent',
|
|
||||||
content: new MarkdownString(
|
|
||||||
localize('remoteAgentResponse', "Coding agent response: {0}", result),
|
|
||||||
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
|
||||||
)
|
|
||||||
});
|
|
||||||
// Extension will open up the pull request in another view
|
|
||||||
widget.clear();
|
|
||||||
} else {
|
|
||||||
chatModel.acceptResponseProgress(addedRequest, {
|
|
||||||
kind: 'markdownContent',
|
|
||||||
content: new MarkdownString(
|
|
||||||
localize('remoteAgentError', "Coding agent session cancelled."),
|
|
||||||
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async run(accessor: ServicesAccessor, ...args: unknown[]) {
|
|
||||||
const contextKeyService = accessor.get(IContextKeyService);
|
|
||||||
const remoteJobCreatingKey = ChatContextKeys.remoteJobCreating.bindTo(contextKeyService);
|
|
||||||
|
|
||||||
try {
|
|
||||||
remoteJobCreatingKey.set(true);
|
|
||||||
|
|
||||||
const configurationService = accessor.get(IConfigurationService);
|
|
||||||
const widgetService = accessor.get(IChatWidgetService);
|
|
||||||
const chatAgentService = accessor.get(IChatAgentService);
|
|
||||||
const chatService = accessor.get(IChatService);
|
|
||||||
const commandService = accessor.get(ICommandService);
|
|
||||||
const quickPickService = accessor.get(IQuickInputService);
|
|
||||||
const remoteCodingAgentService = accessor.get(IRemoteCodingAgentsService);
|
|
||||||
const chatSessionsService = accessor.get(IChatSessionsService);
|
|
||||||
const workspaceContextService = accessor.get(IWorkspaceContextService);
|
|
||||||
const editorService = accessor.get(IEditorService);
|
|
||||||
|
|
||||||
const widget = widgetService.lastFocusedWidget;
|
|
||||||
if (!widget) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!widget.viewModel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const chatModel = widget.viewModel.model;
|
|
||||||
if (!chatModel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionResource = widget.viewModel.sessionResource;
|
|
||||||
const chatRequests = chatModel.getRequests();
|
|
||||||
let userPrompt = widget.getInput();
|
|
||||||
if (!userPrompt) {
|
|
||||||
if (!chatRequests.length) {
|
|
||||||
// Nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
userPrompt = 'implement this.';
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachedContext = widget.input.getAttachedAndImplicitContext(sessionResource);
|
|
||||||
widget.input.acceptInput(true);
|
|
||||||
|
|
||||||
// For inline editor mode, add selection or cursor information
|
|
||||||
if (widget.location === ChatAgentLocation.EditorInline) {
|
|
||||||
const activeEditor = editorService.activeTextEditorControl;
|
|
||||||
if (activeEditor) {
|
|
||||||
const model = activeEditor.getModel();
|
|
||||||
let activeEditorUri: URI | undefined = undefined;
|
|
||||||
if (model && isITextModel(model)) {
|
|
||||||
activeEditorUri = model.uri as URI;
|
|
||||||
}
|
|
||||||
const selection = activeEditor.getSelection();
|
|
||||||
if (activeEditorUri && selection) {
|
|
||||||
attachedContext.add({
|
|
||||||
kind: 'file',
|
|
||||||
id: 'vscode.implicit.selection',
|
|
||||||
name: basename(activeEditorUri),
|
|
||||||
value: {
|
|
||||||
uri: activeEditorUri,
|
|
||||||
range: selection
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Chat);
|
|
||||||
const instantiationService = accessor.get(IInstantiationService);
|
|
||||||
const requestParser = instantiationService.createInstance(ChatRequestParser);
|
|
||||||
|
|
||||||
const contributions = chatSessionsService.getAllChatSessionContributions();
|
|
||||||
|
|
||||||
// Sort contributions by order, then alphabetically by display name
|
|
||||||
// Filter out contributions that have canDelegate set to false
|
|
||||||
const sortedContributions = [...contributions]
|
|
||||||
.filter(contrib => contrib.canDelegate !== false) // Default to true if not specified
|
|
||||||
.sort((a, b) => {
|
|
||||||
// Both have no order - sort by display name
|
|
||||||
if (a.order === undefined && b.order === undefined) {
|
|
||||||
return a.displayName.localeCompare(b.displayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only a has no order - push it to the end
|
|
||||||
if (a.order === undefined) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only b has no order - push it to the end
|
|
||||||
if (b.order === undefined) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both have orders - compare numerically
|
|
||||||
const orderCompare = a.order - b.order;
|
|
||||||
if (orderCompare !== 0) {
|
|
||||||
return orderCompare;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same order - sort by display name
|
|
||||||
return a.displayName.localeCompare(b.displayName);
|
|
||||||
});
|
|
||||||
|
|
||||||
const agent = await this.pickCodingAgent(quickPickService, sortedContributions);
|
|
||||||
if (!agent) {
|
|
||||||
widget.setInput(userPrompt); // Restore prompt
|
|
||||||
throw new Error('No coding agent selected');
|
|
||||||
}
|
|
||||||
const { type } = agent;
|
|
||||||
|
|
||||||
// Add the request to the model first
|
|
||||||
const parsedRequest = requestParser.parseChatRequest(sessionResource, userPrompt, ChatAgentLocation.Chat);
|
|
||||||
const addedRequest = chatModel.addRequest(
|
|
||||||
parsedRequest,
|
|
||||||
{ variables: attachedContext.asArray() },
|
|
||||||
0,
|
|
||||||
undefined,
|
|
||||||
defaultAgent
|
|
||||||
);
|
|
||||||
|
|
||||||
let title: string | undefined = undefined;
|
|
||||||
|
|
||||||
// -- summarize userPrompt if necessary
|
|
||||||
let summarizedUserPrompt: string | undefined = undefined;
|
|
||||||
if (defaultAgent && userPrompt.length > 10_000) {
|
|
||||||
chatModel.acceptResponseProgress(addedRequest, {
|
|
||||||
kind: 'progressMessage',
|
|
||||||
content: new MarkdownString(
|
|
||||||
localize('summarizeUserPromptCreateRemoteJob', "Summarizing user prompt"),
|
|
||||||
CreateRemoteAgentJobAction.markdownStringTrustedOptions,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
({ title, summarizedUserPrompt } = await this.generateSummarizedUserPrompt(sessionResource, userPrompt, attachedContext, title, chatAgentService, defaultAgent, summarizedUserPrompt));
|
|
||||||
}
|
|
||||||
|
|
||||||
let summary: string = '';
|
|
||||||
|
|
||||||
// Add selection or cursor information to the summary
|
|
||||||
attachedContext.asArray().forEach(ctx => {
|
|
||||||
if (isChatRequestFileEntry(ctx) && ctx.value && isLocation(ctx.value)) {
|
|
||||||
const range = ctx.value.range;
|
|
||||||
const isSelection = range.startLineNumber !== range.endLineNumber || range.startColumn !== range.endColumn;
|
|
||||||
|
|
||||||
// Get relative path for the file
|
|
||||||
let filePath = ctx.name;
|
|
||||||
const workspaceFolder = workspaceContextService.getWorkspaceFolder(ctx.value.uri);
|
|
||||||
|
|
||||||
if (workspaceFolder && ctx.value.uri) {
|
|
||||||
const relativePathResult = relativePath(workspaceFolder.uri, ctx.value.uri);
|
|
||||||
if (relativePathResult) {
|
|
||||||
filePath = relativePathResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSelection) {
|
|
||||||
summary += `User has selected text in file ${filePath} from ${range.startLineNumber}:${range.startColumn} to ${range.endLineNumber}:${range.endColumn}\n`;
|
|
||||||
} else {
|
|
||||||
summary += `User is on file ${filePath} at position ${range.startLineNumber}:${range.startColumn}\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// -- summarize context if necessary
|
|
||||||
if (defaultAgent && chatRequests.length > 1) {
|
|
||||||
chatModel.acceptResponseProgress(addedRequest, {
|
|
||||||
kind: 'progressMessage',
|
|
||||||
content: new MarkdownString(
|
|
||||||
localize('analyzingChatHistory', "Analyzing chat history"),
|
|
||||||
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
|
||||||
)
|
|
||||||
});
|
|
||||||
({ title, summary } = await this.generateSummarizedChatHistory(chatRequests, sessionResource, title, chatAgentService, defaultAgent, summary));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
summary += `\nTITLE: ${title}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const isChatSessionsExperimentEnabled = configurationService.getValue<boolean>(ChatConfiguration.UseCloudButtonV2);
|
|
||||||
if (isChatSessionsExperimentEnabled) {
|
|
||||||
await chatService.removeRequest(sessionResource, addedRequest.id);
|
|
||||||
return await this.createWithChatSessions(
|
|
||||||
type,
|
|
||||||
chatSessionsService,
|
|
||||||
chatService,
|
|
||||||
quickPickService,
|
|
||||||
sessionResource,
|
|
||||||
attachedContext,
|
|
||||||
userPrompt,
|
|
||||||
{
|
|
||||||
prompt: summarizedUserPrompt,
|
|
||||||
history: summary,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Below is the legacy implementation
|
|
||||||
|
|
||||||
chatModel.acceptResponseProgress(addedRequest, {
|
|
||||||
kind: 'progressMessage',
|
|
||||||
content: new MarkdownString(
|
|
||||||
localize('creatingRemoteJob', "Delegating to coding agent"),
|
|
||||||
CreateRemoteAgentJobAction.markdownStringTrustedOptions
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.createWithLegacy(remoteCodingAgentService, commandService, quickPickService, chatModel, addedRequest, widget, summarizedUserPrompt || userPrompt, summary);
|
|
||||||
chatModel.setResponse(addedRequest, {});
|
|
||||||
chatModel.completeResponse(addedRequest);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error creating remote coding agent job', e);
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
remoteJobCreatingKey.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async generateSummarizedChatHistory(chatRequests: IChatRequestModel[], sessionResource: URI, title: string | undefined, chatAgentService: IChatAgentService, defaultAgent: IChatAgent, summary: string) {
|
|
||||||
const historyEntries: IChatAgentHistoryEntry[] = chatRequests
|
|
||||||
.map((req): IChatAgentHistoryEntry => ({
|
|
||||||
request: {
|
|
||||||
sessionId: chatSessionResourceToId(sessionResource),
|
|
||||||
sessionResource,
|
|
||||||
requestId: req.id,
|
|
||||||
agentId: req.response?.agent?.id ?? '',
|
|
||||||
message: req.message.text,
|
|
||||||
command: req.response?.slashCommand?.name,
|
|
||||||
variables: req.variableData,
|
|
||||||
location: ChatAgentLocation.Chat,
|
|
||||||
editedFileEvents: req.editedFileEvents,
|
|
||||||
},
|
|
||||||
response: toChatHistoryContent(req.response!.response.value),
|
|
||||||
result: req.response?.result ?? {}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// TODO: Determine a cutoff point where we stop including earlier history
|
|
||||||
// For example, if the user has already delegated to a coding agent once,
|
|
||||||
// prefer the conversation afterwards.
|
|
||||||
title ??= await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None);
|
|
||||||
summary += await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None);
|
|
||||||
return { title, summary };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async generateSummarizedUserPrompt(sessionResource: URI, userPrompt: string, attachedContext: ChatRequestVariableSet, title: string | undefined, chatAgentService: IChatAgentService, defaultAgent: IChatAgent, summarizedUserPrompt: string | undefined) {
|
|
||||||
const userPromptEntry: IChatAgentHistoryEntry = {
|
|
||||||
request: {
|
|
||||||
sessionId: chatSessionResourceToId(sessionResource),
|
|
||||||
sessionResource,
|
|
||||||
requestId: generateUuid(),
|
|
||||||
agentId: '',
|
|
||||||
message: userPrompt,
|
|
||||||
command: undefined,
|
|
||||||
variables: { variables: attachedContext.asArray() },
|
|
||||||
location: ChatAgentLocation.Chat,
|
|
||||||
editedFileEvents: [],
|
|
||||||
},
|
|
||||||
response: [],
|
|
||||||
result: {}
|
|
||||||
};
|
|
||||||
const historyEntries = [userPromptEntry];
|
|
||||||
title = await chatAgentService.getChatTitle(defaultAgent.id, historyEntries, CancellationToken.None);
|
|
||||||
summarizedUserPrompt = await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None);
|
|
||||||
return { title, summarizedUserPrompt };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChatSubmitWithCodebaseAction extends Action2 {
|
export class ChatSubmitWithCodebaseAction extends Action2 {
|
||||||
static readonly ID = 'workbench.action.chat.submitWithCodebase';
|
static readonly ID = 'workbench.action.chat.submitWithCodebase';
|
||||||
@@ -1229,7 +804,7 @@ export function registerChatExecuteActions() {
|
|||||||
registerAction2(CancelAction);
|
registerAction2(CancelAction);
|
||||||
registerAction2(SendToNewChatAction);
|
registerAction2(SendToNewChatAction);
|
||||||
registerAction2(ChatSubmitWithCodebaseAction);
|
registerAction2(ChatSubmitWithCodebaseAction);
|
||||||
registerAction2(CreateRemoteAgentJobAction);
|
registerAction2(ContinueChatInSessionAction);
|
||||||
registerAction2(ToggleChatModeAction);
|
registerAction2(ToggleChatModeAction);
|
||||||
registerAction2(SwitchToNextModelAction);
|
registerAction2(SwitchToNextModelAction);
|
||||||
registerAction2(OpenModelPickerAction);
|
registerAction2(OpenModelPickerAction);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ import { ChatAgentLocation, ChatConfiguration, ChatModeKind, validateChatMode }
|
|||||||
import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js';
|
import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js';
|
||||||
import { ILanguageModelToolsService } from '../common/languageModelToolsService.js';
|
import { ILanguageModelToolsService } from '../common/languageModelToolsService.js';
|
||||||
import { ChatOpenModelPickerActionId, ChatSessionPrimaryPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js';
|
import { ChatOpenModelPickerActionId, ChatSessionPrimaryPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenModePickerAction } from './actions/chatExecuteActions.js';
|
||||||
|
import { ChatContinueInSessionActionItem, ContinueChatInSessionAction } from './actions/chatContinueInAction.js';
|
||||||
import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js';
|
import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js';
|
||||||
import { IChatWidget } from './chat.js';
|
import { IChatWidget } from './chat.js';
|
||||||
import { ChatAttachmentModel } from './chatAttachmentModel.js';
|
import { ChatAttachmentModel } from './chatAttachmentModel.js';
|
||||||
@@ -1461,6 +1462,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
|||||||
},
|
},
|
||||||
hoverDelegate,
|
hoverDelegate,
|
||||||
hiddenItemStrategy: HiddenItemStrategy.NoHide,
|
hiddenItemStrategy: HiddenItemStrategy.NoHide,
|
||||||
|
actionViewItemProvider: (action, options) => {
|
||||||
|
if (action.id === ContinueChatInSessionAction.ID && action instanceof MenuItemAction) {
|
||||||
|
return this.instantiationService.createInstance(ChatContinueInSessionActionItem, action);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
this.executeToolbar.getElement().classList.add('chat-execute-toolbar');
|
this.executeToolbar.getElement().classList.add('chat-execute-toolbar');
|
||||||
this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext;
|
this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext;
|
||||||
|
|||||||
Reference in New Issue
Block a user