Skip to content

Commit f5451c2

Browse files
joshspicerCopilot
andauthored
fix cloud agent branch detection (#1786)
* fix cloud agent branch detection * Update src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/platform/github/common/githubAPI.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 2a1a44c commit f5451c2

File tree

5 files changed

+35
-11
lines changed

5 files changed

+35
-11
lines changed

src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Uri } from 'vscode';
99
import { IGitExtensionService } from '../../../platform/git/common/gitExtensionService';
1010
import { IGitService } from '../../../platform/git/common/gitService';
1111
import { PullRequestSearchItem, SessionInfo } from '../../../platform/github/common/githubAPI';
12-
import { IOctoKitService, JobInfo, RemoteAgentJobPayload } from '../../../platform/github/common/githubService';
12+
import { IOctoKitService, JobInfo, RemoteAgentJobPayload, RemoteAgentJobResponse } from '../../../platform/github/common/githubService';
1313
import { ILogService } from '../../../platform/log/common/logService';
1414
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
1515
import { Disposable, toDisposable } from '../../../util/vs/base/common/lifecycle';
@@ -1001,6 +1001,11 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
10011001
}
10021002
}
10031003

1004+
// https://github.com/github/sweagentd/blob/main/docs/adr/0001-create-job-api.md
1005+
private validateRemoteAgentJobResponse(response: unknown): response is RemoteAgentJobResponse {
1006+
return typeof response === 'object' && response !== null && 'job_id' in response && 'session_id' in response;
1007+
}
1008+
10041009
private async waitForJobWithPullRequest(
10051010
owner: string,
10061011
repo: string,
@@ -1056,16 +1061,19 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
10561061
if (!base_ref) {
10571062
return { error: vscode.l10n.t('Unable to determine the current branch.'), state: 'error' };
10581063
}
1059-
let head_ref: string | undefined; // This is the ref cloud agent starts work from (omitted unless we push local changes)
1064+
let head_ref: string | undefined; // TODO: UNUSED! This is the ref cloud agent starts work from (omitted unless we push local changes)
10601065

1066+
// TODO: Make this automatic instead of a fatal error.
10611067
const remoteName =
10621068
repo?.state.HEAD?.upstream?.remote ??
10631069
currentRepository?.upstreamRemote ??
10641070
repo?.state.remotes?.[0]?.name;
10651071

10661072
if (repo && remoteName && base_ref) {
10671073
try {
1068-
const remoteBranches = await repo.getBranches({ remote: true });
1074+
const remoteBranches =
1075+
(await repo.getBranches({ remote: true }))
1076+
.filter(b => b.remote); // Has an associated remote
10691077
const expectedRemoteBranch = `${remoteName}/${base_ref}`;
10701078
const alternateNames = new Set<string>([
10711079
expectedRemoteBranch,
@@ -1079,7 +1087,10 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
10791087
if (branch.remote && branch.remote !== remoteName) {
10801088
return false;
10811089
}
1082-
const candidateName = branch.remote ? `${branch.remote}/${branch.name}` : branch.name;
1090+
const candidateName =
1091+
(branch.remote && branch.name.startsWith(branch.remote + '/'))
1092+
? branch.name
1093+
: `${branch.remote}/${branch.name}`;
10831094
return alternateNames.has(candidateName);
10841095
});
10851096

@@ -1138,7 +1149,10 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
11381149
chatStream?.progress(vscode.l10n.t('Delegating to cloud agent'));
11391150
this.logService.trace(`Invoking cloud agent job with payload: ${JSON.stringify(payload)}`);
11401151
const response = await this._octoKitService.postCopilotAgentJob(repoId.org, repoId.repo, JOBS_API_VERSION, payload);
1141-
1152+
if (!this.validateRemoteAgentJobResponse(response)) {
1153+
const statusCode = response?.status;
1154+
return { error: vscode.l10n.t(`Received invalid response ${statusCode ? statusCode + ' ' : ''}from cloud agent.`), innerError: `Response ${JSON.stringify(response)}`, state: 'error' };
1155+
}
11421156
// For v1 API, we need to fetch the job details to get the PR info
11431157
// Since the PR might not be created immediately, we need to poll for it
11441158
chatStream?.progress(vscode.l10n.t('Creating pull request'));

src/extension/chatSessions/vscode/copilotCodingAgentUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { UriHandlerPaths, UriHandlers } from './chatSessionsUriHandler';
1111
export const MAX_PROBLEM_STATEMENT_LENGTH = 30_000 - 50; // 50 character buffer
1212
export const CONTINUE_TRUNCATION = vscode.l10n.t('Continue with truncation');
1313
export const body_suffix = vscode.l10n.t('Created from VS Code via the [GitHub Pull Request](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) extension.');
14+
// https://github.com/github/sweagentd/blob/main/docs/adr/0001-create-job-api.md
1415
export const JOBS_API_VERSION = 'v1';
1516
type RemoteAgentSuccessResult = { link: string; state: 'success'; number: number; webviewUri: vscode.Uri; llmDetails: string; sessionId: string };
1617
type RemoteAgentErrorResult = { error: string; innerError?: string; state: 'error' };

src/platform/github/common/githubAPI.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ export async function makeGitHubAPIRequest(
8686
body?: { [key: string]: any },
8787
version?: string,
8888
type: 'json' | 'text' = 'json',
89-
userAgent?: string) {
89+
userAgent?: string,
90+
returnStatusCodeOnError: boolean = false) {
9091
const headers: any = {
9192
'Accept': 'application/vnd.github+json',
9293
};
@@ -106,6 +107,10 @@ export async function makeGitHubAPIRequest(
106107
body: body ? JSON.stringify(body) : undefined
107108
});
108109
if (!response.ok) {
110+
logService.error(`[GitHubAPI] ${method} ${host}/${routeSlug} - Status: ${response?.status}`);
111+
if (returnStatusCodeOnError) {
112+
return { status: response.status };
113+
}
109114
return undefined;
110115
}
111116

src/platform/github/common/githubService.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ export interface RemoteAgentJobResponse {
102102
updated_at: string;
103103
}
104104

105+
export interface ErrorResponseWithStatusCode {
106+
status: number;
107+
}
108+
105109
export interface RemoteAgentJobPayload {
106110
problem_statement: string;
107111
event_type: string;
@@ -199,7 +203,7 @@ export interface IOctoKitService {
199203
name: string,
200204
apiVersion: string,
201205
payload: RemoteAgentJobPayload,
202-
): Promise<RemoteAgentJobResponse>;
206+
): Promise<RemoteAgentJobResponse | ErrorResponseWithStatusCode>;
203207

204208
/**
205209
* Gets a job by its job ID.
@@ -317,8 +321,8 @@ export class BaseOctoKitService {
317321
return makeGitHubAPIRequest(this._fetcherService, this._logService, this._telemetryService, 'https://api.githubcopilot.com', `agents/sessions/${sessionId}`, 'GET', token, undefined, undefined, 'text');
318322
}
319323

320-
protected async postCopilotAgentJobWithToken(owner: string, name: string, apiVersion: string, userAgent: string, payload: RemoteAgentJobPayload, token: string): Promise<RemoteAgentJobResponse> {
321-
return makeGitHubAPIRequest(this._fetcherService, this._logService, this._telemetryService, 'https://api.githubcopilot.com', `agents/swe/${apiVersion}/jobs/${owner}/${name}`, 'POST', token, payload, undefined, undefined, userAgent);
324+
protected async postCopilotAgentJobWithToken(owner: string, name: string, apiVersion: string, userAgent: string, payload: RemoteAgentJobPayload, token: string) {
325+
return makeGitHubAPIRequest(this._fetcherService, this._logService, this._telemetryService, 'https://api.githubcopilot.com', `agents/swe/${apiVersion}/jobs/${owner}/${name}`, 'POST', token, payload, undefined, undefined, userAgent, true);
322326
}
323327

324328
protected async getJobByJobIdWithToken(owner: string, repo: string, jobId: string, userAgent: string, token: string): Promise<JobInfo> {

src/platform/github/common/octoKitServiceImpl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ILogService } from '../../log/common/logService';
88
import { IFetcherService } from '../../networking/common/fetcherService';
99
import { ITelemetryService } from '../../telemetry/common/telemetry';
1010
import { PullRequestComment, PullRequestSearchItem, SessionInfo } from './githubAPI';
11-
import { BaseOctoKitService, CustomAgentDetails, CustomAgentListItem, IOctoKitService, IOctoKitUser, JobInfo, PullRequestFile, RemoteAgentJobPayload, RemoteAgentJobResponse } from './githubService';
11+
import { BaseOctoKitService, CustomAgentDetails, CustomAgentListItem, ErrorResponseWithStatusCode, IOctoKitService, IOctoKitUser, JobInfo, PullRequestFile, RemoteAgentJobPayload, RemoteAgentJobResponse } from './githubService';
1212

1313
export class OctoKitService extends BaseOctoKitService implements IOctoKitService {
1414
declare readonly _serviceBrand: undefined;
@@ -95,7 +95,7 @@ export class OctoKitService extends BaseOctoKitService implements IOctoKitServic
9595
return response;
9696
}
9797

98-
async postCopilotAgentJob(owner: string, name: string, apiVersion: string, payload: RemoteAgentJobPayload): Promise<RemoteAgentJobResponse> {
98+
async postCopilotAgentJob(owner: string, name: string, apiVersion: string, payload: RemoteAgentJobPayload): Promise<RemoteAgentJobResponse | ErrorResponseWithStatusCode> {
9999
const authToken = (await this._authService.getAnyGitHubSession())?.accessToken;
100100
if (!authToken) {
101101
throw new Error('No authentication token available');

0 commit comments

Comments
 (0)