Skip to content

Commit effe1db

Browse files
committed
improve chat session list fetching
1 parent fa37e36 commit effe1db

File tree

3 files changed

+129
-58
lines changed

3 files changed

+129
-58
lines changed

src/github/copilotApi.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import Logger from '../common/logger';
1212
import { ITelemetry } from '../common/telemetry';
1313
import { CredentialStore, GitHub } from './credentials';
1414
import { PRType } from './interface';
15-
import { LoggingOctokit } from './loggingOctokit';
15+
import { LoggingApolloClient, LoggingOctokit } from './loggingOctokit';
1616
import { PullRequestModel } from './pullRequestModel';
17+
import defaultSchema from './queries.gql';
1718
import { RepositoriesManager } from './repositoriesManager';
1819
import { hasEnterpriseUri } from './utils';
1920

@@ -42,7 +43,7 @@ export interface RemoteAgentJobResponse {
4243
}
4344

4445
export interface ChatSessionWithPR extends vscode.ChatSessionItem {
45-
pullRequest: PullRequestModel;
46+
pullRequest?: PullRequestModel;
4647
}
4748

4849
export interface ChatSessionFromSummarizedChat extends vscode.ChatSessionItem {
@@ -58,6 +59,7 @@ export class CopilotApi {
5859

5960
constructor(
6061
private octokit: LoggingOctokit,
62+
private graphql: LoggingApolloClient,
6163
private token: string,
6264
private credentialStore: CredentialStore,
6365
private telemetry: ITelemetry
@@ -208,6 +210,23 @@ export class CopilotApi {
208210
return sessions.sessions;
209211
}
210212

213+
public async getPullRequestFromSession(globalId): Promise<SessionPullRequestInfo | undefined> {
214+
try {
215+
const { data } = await this.graphql.query({
216+
query: (defaultSchema as any).GetPullRequestGlobal,
217+
variables: {
218+
globalId: globalId
219+
}
220+
});
221+
222+
return data.node;
223+
} catch (ex) {
224+
console.log(ex);
225+
}
226+
227+
return undefined;
228+
}
229+
211230
public async getAllCodingAgentPRs(repositoriesManager: RepositoriesManager): Promise<PullRequestModel[]> {
212231
const hub = this.getHub();
213232
const username = (await hub?.currentUser)?.login;
@@ -311,6 +330,20 @@ export interface SessionInfo {
311330
workflow_run_id: number;
312331
premium_requests: number;
313332
error: string | null;
333+
resource_global_id?: string
334+
}
335+
336+
export interface SessionPullRequestInfo {
337+
number: number;
338+
title: string;
339+
additions: number;
340+
deletions: number;
341+
headRepository: {
342+
owner: {
343+
login: string;
344+
};
345+
name: string;
346+
};
314347
}
315348

316349
export interface SessionSetupStep {
@@ -363,5 +396,5 @@ export async function getCopilotApi(credentialStore: CredentialStore, telemetry:
363396
}
364397

365398
const { token } = await github.octokit.api.auth() as { token: string };
366-
return new CopilotApi(github.octokit, token, credentialStore, telemetry);
399+
return new CopilotApi(github.octokit, github.graphql, token, credentialStore, telemetry);
367400
}

src/github/copilotRemoteAgent.ts

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import vscode, { ChatPromptReference } from 'vscode';
99
import { parseSessionLogs, parseToolCallDetails, StrReplaceEditorToolData } from '../../common/sessionParsing';
1010
import { COPILOT_ACCOUNTS } from '../common/comment';
1111
import { CopilotRemoteAgentConfig } from '../common/config';
12-
import { COPILOT_LOGINS, COPILOT_SWE_AGENT, copilotEventToStatus, CopilotPRStatus, mostRecentCopilotEvent } from '../common/copilot';
12+
import { COPILOT_LOGINS, COPILOT_SWE_AGENT, CopilotPRStatus, mostRecentCopilotEvent } from '../common/copilot';
1313
import { commands } from '../common/executeCommands';
1414
import { Disposable } from '../common/lifecycle';
1515
import Logger from '../common/logger';
1616
import { GitHubRemote } from '../common/remote';
1717
import { CODING_AGENT, CODING_AGENT_AUTO_COMMIT_AND_PUSH } from '../common/settingKeys';
1818
import { ITelemetry } from '../common/telemetry';
1919
import { toOpenPullRequestWebviewUri } from '../common/uri';
20-
import { copilotEventToSessionStatus, copilotPRStatusToSessionStatus, IAPISessionLogs, ICopilotRemoteAgentCommandArgs, ICopilotRemoteAgentCommandResponse, OctokitCommon, RemoteAgentResult, RepoInfo } from './common';
21-
import { ChatSessionFromSummarizedChat, ChatSessionWithPR, CopilotApi, getCopilotApi, RemoteAgentJobPayload, SessionInfo, SessionSetupStep } from './copilotApi';
22-
import { CodingAgentPRAndStatus, CopilotPRWatcher, CopilotStateModel } from './copilotPrWatcher';
20+
import { copilotEventToSessionStatus, IAPISessionLogs, ICopilotRemoteAgentCommandArgs, ICopilotRemoteAgentCommandResponse, OctokitCommon, RemoteAgentResult, RepoInfo } from './common';
21+
import { ChatSessionFromSummarizedChat, ChatSessionWithPR, CopilotApi, getCopilotApi, RemoteAgentJobPayload, SessionInfo, SessionPullRequestInfo, SessionSetupStep } from './copilotApi';
22+
import { CopilotPRWatcher, CopilotStateModel } from './copilotPrWatcher';
2323
import { ChatSessionContentBuilder } from './copilotRemoteAgent/chatSessionContentBuilder';
2424
import { GitOperationsManager } from './copilotRemoteAgent/gitOperationsManager';
2525
import { CredentialStore } from './credentials';
@@ -82,10 +82,7 @@ export class CopilotRemoteAgentManager extends Disposable {
8282
private readonly gitOperationsManager: GitOperationsManager;
8383
private readonly ephemeralChatSessions: Map<string, ChatSessionFromSummarizedChat> = new Map();
8484

85-
private codingAgentPRsPromise: Promise<{
86-
item: PullRequestModel;
87-
status: CopilotPRStatus;
88-
}[]> | undefined;
85+
private codingAgentPRsPromise: Promise<ChatSessionWithPR[]> | undefined;
8986

9087
constructor(private credentialStore: CredentialStore, public repositoriesManager: RepositoriesManager, private telemetry: ITelemetry, private context: vscode.ExtensionContext) {
9188
super();
@@ -868,7 +865,7 @@ export class CopilotRemoteAgentManager extends Disposable {
868865
};
869866
}
870867

871-
public async provideChatSessions(token: vscode.CancellationToken): Promise<ChatSessionWithPR[]> {
868+
public async provideChatSessions(token: vscode.CancellationToken): Promise<vscode.ChatSessionItem[]> {
872869
try {
873870
const capi = await this.copilotApi;
874871
if (!capi) {
@@ -882,54 +879,78 @@ export class CopilotRemoteAgentManager extends Disposable {
882879

883880
await this.waitRepoManagerInitialization();
884881

885-
let codingAgentPRs: CodingAgentPRAndStatus[] = [];
886-
if (this._stateModel.isInitialized) {
887-
codingAgentPRs = this._stateModel.all;
888-
Logger.debug(`Fetched PRs from state model: ${codingAgentPRs.length}`, CopilotRemoteAgentManager.ID);
889-
} else {
890-
this.codingAgentPRsPromise = this.codingAgentPRsPromise ?? new Promise<CodingAgentPRAndStatus[]>(async (resolve) => {
891-
try {
892-
const sessions = await capi.getAllCodingAgentPRs(this.repositoriesManager);
893-
const prAndStatus = await Promise.all(sessions.map(async pr => {
894-
const timeline = await pr.getCopilotTimelineEvents(pr);
895-
const status = copilotEventToStatus(mostRecentCopilotEvent(timeline));
896-
return { item: pr, status };
897-
}));
898-
899-
resolve(prAndStatus);
900-
} catch (error) {
901-
Logger.error(`Failed to fetch coding agent PRs: ${error}`, CopilotRemoteAgentManager.ID);
902-
resolve([]);
882+
const currentRepositories = this.repositoriesManager.folderManagers.map(folder => folder.gitHubRepositories).flat();
883+
884+
this.codingAgentPRsPromise = this.codingAgentPRsPromise ?? new Promise<vscode.ChatSessionItem[]>(async (resolve) => {
885+
const sessions = await capi.getAllSessions(undefined);
886+
const sessionMap = new Map<string, SessionInfo[]>();
887+
888+
for (const session of sessions) {
889+
if (!session.resource_global_id) {
890+
continue;
903891
}
892+
const existing = sessionMap.get(session.resource_global_id) || [];
893+
existing.push(session);
894+
existing.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
895+
sessionMap.set(session.resource_global_id, existing);
896+
}
897+
898+
const groupedSessions = Array.from(sessionMap.values()).sort((a, b) => {
899+
const aFirstSession = a[0];
900+
const bFirstSession = b[0];
901+
return new Date(aFirstSession.created_at).getTime() - new Date(bFirstSession.created_at).getTime();
904902
});
905-
codingAgentPRs = await this.codingAgentPRsPromise;
906-
Logger.debug(`Fetched PRs from API: ${codingAgentPRs.length}`, CopilotRemoteAgentManager.ID);
907-
}
908-
return await Promise.all(codingAgentPRs.map(async prAndStatus => {
909-
const timestampNumber = new Date(prAndStatus.item.createdAt).getTime();
910-
const status = copilotPRStatusToSessionStatus(prAndStatus.status);
911-
const pullRequest = prAndStatus.item;
912-
const tooltip = await issueMarkdown(pullRequest, this.context, this.repositoriesManager);
913-
914-
const uri = await toOpenPullRequestWebviewUri({ owner: pullRequest.remote.owner, repo: pullRequest.remote.repositoryName, pullRequestNumber: pullRequest.number });
915-
const description = new vscode.MarkdownString(`[#${pullRequest.number}](${uri.toString()})`); // pullRequest.base.ref === defaultBranch ? `PR #${pullRequest.number}`: `PR #${pullRequest.number} → ${pullRequest.base.ref}`;
916-
return {
917-
id: `${pullRequest.number}`,
918-
label: pullRequest.title || `Session ${pullRequest.number}`,
919-
iconPath: this.getIconForSession(status),
920-
pullRequest: pullRequest,
921-
description: description,
922-
tooltip,
923-
status,
924-
timing: {
925-
startTime: timestampNumber
926-
},
927-
statistics: pullRequest.item.additions !== undefined && pullRequest.item.deletions !== undefined && (pullRequest.item.additions > 0 || pullRequest.item.deletions > 0) ? {
928-
insertions: pullRequest.item.additions,
929-
deletions: pullRequest.item.deletions
930-
} : undefined
931-
};
932-
}));
903+
904+
const filteredPRs = (await Promise.all(groupedSessions.map(async sessions => {
905+
const initialSession = sessions[0];
906+
907+
const pullRequestInfo = await capi.getPullRequestFromSession(initialSession.resource_global_id);
908+
if (!pullRequestInfo) {
909+
return;
910+
}
911+
912+
return {
913+
id: initialSession.resource_global_id,
914+
pullRequest: pullRequestInfo,
915+
sessions
916+
};
917+
}))).filter(pr => {
918+
if (!pr) {
919+
return false;
920+
}
921+
922+
// Filter out PRs that are not in the current repositories
923+
const prRepo = currentRepositories.find(repo =>
924+
repo.remote.owner === pr.pullRequest.headRepository.owner.login &&
925+
repo.remote.repositoryName === pr.pullRequest.headRepository.name
926+
);
927+
928+
if (!prRepo) {
929+
return false;
930+
}
931+
932+
return true;
933+
}).map((pr: { id: string, pullRequest: SessionPullRequestInfo; sessions: SessionInfo[] }) => {
934+
const { id, pullRequest, sessions } = pr;
935+
const latestSession = sessions[sessions.length - 1];
936+
const status = latestSession.state === 'completed' ? vscode.ChatSessionStatus.Completed : (latestSession.state === 'failed' ? vscode.ChatSessionStatus.Failed : vscode.ChatSessionStatus.InProgress);
937+
938+
return {
939+
id: id,
940+
label: pullRequest.title || `Session ${pullRequest.number}`,
941+
status: status,
942+
description: `${pullRequest.headRepository.owner.login}/${pullRequest.headRepository.name} #${pullRequest.number}`,
943+
statistics: pullRequest.additions !== undefined && pullRequest.deletions !== undefined && (pullRequest.additions > 0 || pullRequest.deletions > 0) ? {
944+
insertions: pullRequest.additions,
945+
deletions: pullRequest.deletions
946+
} : undefined
947+
};
948+
});
949+
950+
resolve(filteredPRs);
951+
});
952+
953+
return this.codingAgentPRsPromise;
933954
} catch (error) {
934955
Logger.error(`Failed to provide coding agents information: ${error}`, CopilotRemoteAgentManager.ID);
935956
} finally {

src/github/queries.gql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,23 @@ query GetSuggestedActors($owner: String!, $name: String!, $capabilities: [Reposi
378378
}
379379
}
380380

381+
query GetPullRequestGlobal($globalId: ID!) {
382+
node(id: $globalId) {
383+
... on PullRequest {
384+
number
385+
title
386+
additions
387+
deletions
388+
headRepository {
389+
owner {
390+
login
391+
}
392+
name
393+
}
394+
}
395+
}
396+
}
397+
381398
mutation DequeuePullRequest($input: DequeuePullRequestInput!) {
382399
dequeuePullRequest(input: $input) {
383400
mergeQueueEntry {

0 commit comments

Comments
 (0)