Skip to content

Commit 1d3706f

Browse files
authored
Initial implementation of new session flow (#7671)
* new chat session flow prototype * initial implementation of new session flow * tidy * comments
1 parent b4b4347 commit 1d3706f

2 files changed

Lines changed: 140 additions & 4 deletions

File tree

src/github/copilotApi.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export interface ChatSessionWithPR extends vscode.ChatSessionItem {
4444
pullRequest: PullRequestModel;
4545
}
4646

47+
export interface ChatSessionFromSummarizedChat extends vscode.ChatSessionItem {
48+
prompt: string;
49+
summary?: string;
50+
}
51+
4752
export class CopilotApi {
4853
protected static readonly ID = 'copilotApi';
4954

src/github/copilotRemoteAgent.ts

Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { CODING_AGENT, CODING_AGENT_AUTO_COMMIT_AND_PUSH } from '../common/setti
1818
import { ITelemetry } from '../common/telemetry';
1919
import { toOpenPullRequestWebviewUri } from '../common/uri';
2020
import { copilotEventToSessionStatus, copilotPRStatusToSessionStatus, IAPISessionLogs, ICopilotRemoteAgentCommandArgs, ICopilotRemoteAgentCommandResponse, OctokitCommon, RemoteAgentResult, RepoInfo } from './common';
21-
import { ChatSessionWithPR, CopilotApi, getCopilotApi, RemoteAgentJobPayload, SessionInfo, SessionSetupStep } from './copilotApi';
21+
import { ChatSessionFromSummarizedChat, ChatSessionWithPR, CopilotApi, getCopilotApi, RemoteAgentJobPayload, SessionInfo, SessionSetupStep } from './copilotApi';
2222
import { CopilotPRWatcher, CopilotStateModel } from './copilotPrWatcher';
2323
import { ChatSessionContentBuilder } from './copilotRemoteAgent/chatSessionContentBuilder';
2424
import { GitOperationsManager } from './copilotRemoteAgent/gitOperationsManager';
@@ -59,6 +59,7 @@ export class CopilotRemoteAgentManager extends Disposable {
5959
readonly onDidChangeChatSessions = this._onDidChangeChatSessions.event;
6060

6161
private readonly gitOperationsManager: GitOperationsManager;
62+
private readonly ephemeralChatSessions: Map<string, ChatSessionFromSummarizedChat> = new Map(); // TODO: Clean these up
6263

6364
constructor(private credentialStore: CredentialStore, public repositoriesManager: RepositoriesManager, private telemetry: ITelemetry, private context: vscode.ExtensionContext) {
6465
super();
@@ -723,11 +724,28 @@ export class CopilotRemoteAgentManager extends Disposable {
723724
return this._stateModel.getCounts();
724725
}
725726

726-
public async provideNewChatSessionItem(options: { prompt?: string; history: ReadonlyArray<vscode.ChatRequestTurn | vscode.ChatResponseTurn>; metadata?: any; }, _token: vscode.CancellationToken): Promise<ChatSessionWithPR> {
727+
public async provideNewChatSessionItem(options: { prompt?: string; history: ReadonlyArray<vscode.ChatRequestTurn | vscode.ChatResponseTurn>; metadata?: any; }, _token: vscode.CancellationToken): Promise<ChatSessionWithPR | ChatSessionFromSummarizedChat> {
727728
const { prompt } = options;
728729
if (!prompt) {
729730
throw new Error(`Prompt is expected to provide a new chat session item`);
730731
}
732+
733+
const { source, summary } = options.metadata || {};
734+
735+
// Ephemeral session for new session creation flow
736+
if (source === 'chatExecuteActions') {
737+
const id = `new-${Date.now()}`;
738+
const val = {
739+
id,
740+
label: vscode.l10n.t('New coding agent session'),
741+
iconPath: new vscode.ThemeIcon('plus'),
742+
prompt,
743+
summary,
744+
};
745+
this.ephemeralChatSessions.set(id, val);
746+
return val;
747+
}
748+
731749
const result = await this.invokeRemoteAgent(
732750
prompt,
733751
prompt,
@@ -799,21 +817,134 @@ export class CopilotRemoteAgentManager extends Disposable {
799817
return [];
800818
}
801819

820+
821+
private async newSessionFlowFromPrompt(id: string): Promise<vscode.ChatSession> {
822+
const session = this.ephemeralChatSessions.get(id);
823+
if (!session) {
824+
return this.createEmptySession();
825+
}
826+
827+
const repoInfo = await this.repoInfo();
828+
if (!repoInfo) {
829+
return this.createEmptySession(); // TODO: Explain how to enroll repo in coding agent, etc..?
830+
}
831+
const { repo, owner } = repoInfo;
832+
833+
// Remove from ephemeral sessions
834+
this.ephemeralChatSessions.delete(id);
835+
836+
// Create a placeholder session that will invoke the remote agent when confirmed
837+
838+
const { prompt } = session;
839+
const sessionRequest = new vscode.ChatRequestTurn2(
840+
prompt,
841+
undefined,
842+
[],
843+
COPILOT_SWE_AGENT,
844+
[],
845+
[]
846+
);
847+
848+
const placeholderParts = [
849+
new vscode.ChatResponseProgressPart(vscode.l10n.t('Starting coding agent session...')),
850+
new vscode.ChatResponseConfirmationPart(
851+
vscode.l10n.t('Copilot coding agent will continue your work in \'{0}\'.', `${owner}/${repo}`),
852+
vscode.l10n.t('Your chat context will be used to continue work in a new pull request.'),
853+
'invoke', // Next state
854+
['Continue', 'Cancel']
855+
)
856+
];
857+
858+
const placeholderTurn = new vscode.ChatResponseTurn2(placeholderParts, {}, COPILOT_SWE_AGENT);
859+
return {
860+
history: [sessionRequest, placeholderTurn],
861+
requestHandler: async (request: vscode.ChatRequest, _context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise<vscode.ChatResult> => {
862+
if (token.isCancellationRequested) {
863+
return {};
864+
}
865+
if (request.acceptedConfirmationData) {
866+
if (!Array.isArray(request.acceptedConfirmationData)) {
867+
Logger.error(`Invalid confirmation data: ${request.acceptedConfirmationData}`, CopilotRemoteAgentManager.ID);
868+
return {};
869+
}
870+
const states = request.acceptedConfirmationData as string[];
871+
while (states.length) {
872+
const state = states.shift();
873+
if (!state) {
874+
continue;
875+
}
876+
switch (state) {
877+
case 'invoke':
878+
// TODO: Refactor of invokeRemoteAgent needed to extract all user prompts
879+
// Move any user action to a state in this state machine.
880+
stream.progress('Delegating to coding agent');
881+
const result = await this.invokeRemoteAgent(
882+
prompt,
883+
prompt,
884+
false,
885+
);
886+
if (result.state !== 'success') {
887+
stream.warning(`Could not create coding agent session: ${result.error}`);
888+
return {};
889+
}
890+
891+
const pullRequest = await this.findPullRequestById(result.number, true);
892+
if (!pullRequest) {
893+
stream.warning(`Could not find coding agent session.`);
894+
return {};
895+
}
896+
const capi = await this.copilotApi;
897+
if (!capi) {
898+
stream.warning(vscode.l10n.t('Could not initialize Copilot API.'));
899+
return {};
900+
}
901+
// Poll for the new session
902+
const sessions = await capi.getAllSessions(pullRequest.id);
903+
const newSession = sessions.find(s => s.state === 'in_progress' || s.state === 'queued');
904+
if (!newSession) {
905+
stream.warning(vscode.l10n.t('Could not find coding agent session in progress.'));
906+
return {};
907+
}
908+
stream.markdown(vscode.l10n.t('Coding agent is now working on your request...'));
909+
stream.markdown('\n\n');
910+
await this.streamSessionLogs(stream, pullRequest, newSession.id, token);
911+
return {};
912+
default:
913+
Logger.error(`Unknown confirmation state: ${state}`, CopilotRemoteAgentManager.ID);
914+
stream.markdown('error!');
915+
return {};
916+
}
917+
}
918+
}
919+
if (request.rejectedConfirmationData) {
920+
stream.push(new vscode.ChatResponseProgressPart(vscode.l10n.t('Cancelled starting coding agent session.')));
921+
return {};
922+
}
923+
return {};
924+
},
925+
activeResponseCallback: undefined,
926+
};
927+
}
928+
802929
public async provideChatSessionContent(id: string, token: vscode.CancellationToken): Promise<vscode.ChatSession> {
803930
try {
804931
const capi = await this.copilotApi;
805932
if (!capi || token.isCancellationRequested) {
806933
return this.createEmptySession();
807934
}
808935

936+
await this.waitRepoManagerInitialization();
937+
938+
if (id.startsWith('new')) {
939+
return await this.newSessionFlowFromPrompt(id);
940+
}
941+
809942
const pullRequestNumber = parseInt(id);
810943
if (isNaN(pullRequestNumber)) {
811944
Logger.error(`Invalid pull request number: ${id}`, CopilotRemoteAgentManager.ID);
812945
return this.createEmptySession();
813946
}
814947

815-
await this.waitRepoManagerInitialization();
816-
817948
const pullRequest = await this.findPullRequestById(pullRequestNumber, true);
818949
if (!pullRequest) {
819950
Logger.error(`Pull request not found: ${pullRequestNumber}`, CopilotRemoteAgentManager.ID);

0 commit comments

Comments
 (0)