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
218 changes: 137 additions & 81 deletions src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,23 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com
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 { IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { IRemoteCodingAgent, IRemoteCodingAgentsService } from '../../../remoteCodingAgents/common/remoteCodingAgentsService.js';
import { IChatAgentHistoryEntry, IChatAgentService } from '../../common/chatAgents.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
import { toChatHistoryContent } from '../../common/chatModel.js';
import { IChatModel, IChatRequestModel, toChatHistoryContent } from '../../common/chatModel.js';
import { IChatMode, IChatModeService } from '../../common/chatModes.js';
import { chatVariableLeader } from '../../common/chatParserTypes.js';
import { ChatRequestParser } from '../../common/chatRequestParser.js';
import { IChatPullRequestContent, IChatService } from '../../common/chatService.js';
import { IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js';
import { ChatSessionUri } from '../../common/chatUri.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, } from '../../common/constants.js';
import { ILanguageModelChatMetadata } from '../../common/languageModels.js';
import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js';
import { IChatWidget, IChatWidgetService } from '../chat.js';
import { getEditingSessionContext } from '../chatEditing/chatEditingActions.js';
import { IChatEditorOptions } from '../chatEditor.js';
import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, handleCurrentEditingSession, handleModeSwitch } from './chatActions.js';

export interface IVoiceChatExecuteActionContext {
Expand Down Expand Up @@ -521,22 +525,145 @@ export class CreateRemoteAgentJobAction extends Action2 {
id: MenuId.ChatExecute,
group: 'navigation',
order: 3.4,
// TODO(jospicer): or hasChatSessionContributions
when: ContextKeyExpr.and(ChatContextKeys.hasRemoteCodingAgent, 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,
})),
{
title: localize('selectCodingAgent', "Select Coding Agent"),
}
);
if (!pick) {
return undefined;
}
return pick.agent;
}

private async createWithChatSessions(
chatSessionsService: IChatSessionsService,
quickPickService: IQuickInputService,
editorService: IEditorService,
chatModel: IChatModel,
addedRequest: IChatRequestModel,
userPrompt: string,
summary?: string
) {
const contributions = chatSessionsService.getAllChatSessionContributions();
const agent = await this.pickCodingAgent(quickPickService, contributions);
if (!agent) {
chatModel.completeResponse(addedRequest);
return;
}
const { type } = agent;
const newChatSession = await chatSessionsService.provideNewChatSessionItem(
type,
{
prompt: userPrompt,
metadata: {
summary,
source: 'chatExecuteActions',
}
},
CancellationToken.None,
);
const options: IChatEditorOptions = {
pinned: true,
preferredTitle: newChatSession.label,
};
await editorService.openEditor({
resource: ChatSessionUri.forSession(type, newChatSession.id),
options,
});

}

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: summary || userPrompt,
_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: any[]) {
const contextKeyService = accessor.get(IContextKeyService);
const remoteJobCreatingKey = ChatContextKeys.remoteJobCreating.bindTo(contextKeyService);

try {
remoteJobCreatingKey.set(true);

const remoteCodingAgent = accessor.get(IRemoteCodingAgentsService);
const commandService = accessor.get(ICommandService);
const configurationService = accessor.get(IConfigurationService);
const widgetService = accessor.get(IChatWidgetService);
const chatAgentService = accessor.get(IChatAgentService);
const commandService = accessor.get(ICommandService);
const quickPickService = accessor.get(IQuickInputService);
const remoteCodingAgentService = accessor.get(IRemoteCodingAgentsService);
const chatSessionsService = accessor.get(IChatSessionsService);
const editorService = accessor.get(IEditorService);


const widget = widgetService.lastFocusedWidget;
if (!widget) {
Expand All @@ -554,23 +681,19 @@ export class CreateRemoteAgentJobAction extends Action2 {
const chatRequests = chatModel.getRequests();
let userPrompt = widget.getInput();
if (!userPrompt) {

if (!chatRequests.length) {
// Nothing to do
return;
}

userPrompt = 'implement this.';
}

widget.input.acceptInput(true);

const defaultAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.Panel);

// Complete implementation of adding request back into chat stream
const instantiationService = accessor.get(IInstantiationService);

// Parse the request text to create a structured request
const instantiationService = accessor.get(IInstantiationService);
const requestParser = instantiationService.createInstance(ChatRequestParser);
const parsedRequest = requestParser.parseChatRequest(session, userPrompt, ChatAgentLocation.Panel);

Expand All @@ -583,34 +706,7 @@ export class CreateRemoteAgentJobAction extends Action2 {
defaultAgent,
);

const agents = remoteCodingAgent.getAvailableAgents();
if (agents.length === 0) {
chatModel.completeResponse(addedRequest);
return;
}

let agent = agents[0];
if (agents.length > 1) {
const quickInputService = accessor.get(IQuickInputService);
const pick = await quickInputService.pick(
agents.map(a => ({
label: a.displayName,
description: a.description,
agent: a,
})),
{
title: localize('selectCodingAgent', "Select Coding Agent"),
}
);
if (!pick) {
chatModel.completeResponse(addedRequest);
return;
}
agent = pick.agent;
}

let summary: string | undefined;
let followup: string | undefined;
if (defaultAgent && chatRequests.length > 0) {
chatModel.acceptResponseProgress(addedRequest, {
kind: 'progressMessage',
Expand All @@ -619,16 +715,6 @@ export class CreateRemoteAgentJobAction extends Action2 {
CreateRemoteAgentJobAction.markdownStringTrustedOptions
)
});

// Forward useful metadata about conversation to the implementing extension
if (agent.followUpRegex) {
const regex = new RegExp(agent.followUpRegex);
followup = chatRequests
.map(req => req.response?.response.toString() ?? '')
.reverse()
.find(text => regex.test(text));
}

const historyEntries: IChatAgentHistoryEntry[] = chatRequests
.map(req => ({
request: {
Expand All @@ -648,7 +734,6 @@ export class CreateRemoteAgentJobAction extends Action2 {
summary = await chatAgentService.getChatSummary(defaultAgent.id, historyEntries, CancellationToken.None);
}

// Show progress for job creation
chatModel.acceptResponseProgress(addedRequest, {
kind: 'progressMessage',
content: new MarkdownString(
Expand All @@ -657,41 +742,13 @@ export class CreateRemoteAgentJobAction extends Action2 {
)
});

// Execute the remote command
const result: Omit<IChatPullRequestContent, 'kind'> | string | undefined = await commandService.executeCommand(agent.command, {
userPrompt,
summary: summary || userPrompt,
followup,
_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."), // TODO(jospicer): Generalize this
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();
const isChatSessionsEnabled = configurationService.getValue<boolean>(ChatConfiguration.UseChatSessionsForCloudButton);
if (isChatSessionsEnabled) {
await this.createWithChatSessions(chatSessionsService, quickPickService, editorService, chatModel, addedRequest, userPrompt, summary);
} else {
chatModel.acceptResponseProgress(addedRequest, {
kind: 'markdownContent',
content: new MarkdownString(
localize('remoteAgentError', "Coding agent session cancelled."),
CreateRemoteAgentJobAction.markdownStringTrustedOptions
)
});
await this.createWithLegacy(remoteCodingAgentService, commandService, quickPickService, chatModel, addedRequest, widget, userPrompt, summary);
}

chatModel.setResponse(addedRequest, {});
chatModel.completeResponse(addedRequest);
} finally {
Expand All @@ -700,7 +757,6 @@ export class CreateRemoteAgentJobAction extends Action2 {
}
}


export class ChatSubmitWithCodebaseAction extends Action2 {
static readonly ID = 'workbench.action.chat.submitWithCodebase';

Expand Down
7 changes: 7 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,13 @@ configurationRegistry.registerConfiguration({
default: false,
scope: ConfigurationScope.WINDOW
},
[ChatConfiguration.UseChatSessionsForCloudButton]: {
type: 'boolean',
description: nls.localize('chat.useChatSessionsForCloudButton', "Controls whether the 'Delegate to coding agent' button uses the new chat sessions API."),
default: false,
tags: ['experimental'],

}
}
});
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/chat/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum ChatConfiguration {
CheckpointsEnabled = 'chat.checkpoints.enabled',
AgentSessionsViewLocation = 'chat.agentSessionsViewLocation',
ShowThinking = 'chat.agent.showThinking',
UseChatSessionsForCloudButton = 'chat.useChatSessionsForCloudButton'
}

/**
Expand Down
Loading