Skip to content

Commit bc8ca12

Browse files
committed
Merge branch 'main' into alexr00/nursing-mackerel
2 parents 50e6d87 + dd73519 commit bc8ca12

20 files changed

+466
-90
lines changed

package.json

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3935,12 +3935,58 @@
39353935
],
39363936
"toolReferenceName": "activePullRequest",
39373937
"displayName": "%languageModelTools.github-pull-request_activePullRequest.displayName%",
3938-
"modelDescription": "Get comprehensive information about the active GitHub pull request (PR). The active PR is the one that is currently checked out. This includes the PR title, full description, list of changed files, review comments, PR state, and status checks/CI results. For PRs created by Copilot, it also includes the session logs which indicate the development process and decisions made by the coding agent. When asked about the active or current pull request, do this first! Use this tool for any request related to \"current changes,\" \"pull request details,\" \"what changed,\" \"PR status,\" or similar queries even if the user does not explicitly mention \"pull request.\" When asked to use this tool, ALWAYS use it.",
3938+
"modelDescription": "Get comprehensive information about the active GitHub pull request (PR). The active PR is the one that is currently checked out. This includes the PR title, full description, list of changed files, review comments, and PR state. For PRs created by Copilot, it also includes the session logs which indicate the development process and decisions made by the coding agent. Does NOT include status checks/CI results; use the pullRequestStatusChecks tool instead. When asked about the active or current pull request, do this first! Use this tool for any request related to \"current changes,\" \"pull request details,\" \"what changed,\" or similar queries even if the user does not explicitly mention \"pull request.\" When asked to use this tool, ALWAYS use it.",
39393939
"icon": "$(git-pull-request)",
39403940
"canBeReferencedInPrompt": true,
39413941
"userDescription": "%languageModelTools.github-pull-request_activePullRequest.description%",
39423942
"when": "config.githubPullRequests.experimental.chat"
39433943
},
3944+
{
3945+
"name": "github-pull-request_pullRequestStatusChecks",
3946+
"tags": [
3947+
"github",
3948+
"pull request",
3949+
"ci",
3950+
"status checks"
3951+
],
3952+
"toolReferenceName": "pullRequestStatusChecks",
3953+
"displayName": "%languageModelTools.github-pull-request_pullRequestStatusChecks.displayName%",
3954+
"modelDescription": "Get the status checks and CI failures for a GitHub pull request (PR). This includes check run statuses, workflow names, failure logs, and review requirements (approvals needed, current approvals, requested changes). Use this tool when the user asks about CI status/failures, build results, check runs, status checks, whether a PR is ready to merge, or similar queries. Requires a pull request number.",
3955+
"icon": "$(check-all)",
3956+
"canBeReferencedInPrompt": true,
3957+
"inputSchema": {
3958+
"type": "object",
3959+
"properties": {
3960+
"repo": {
3961+
"type": "object",
3962+
"description": "The repository to get the pull request status checks from.",
3963+
"properties": {
3964+
"owner": {
3965+
"type": "string",
3966+
"description": "The owner of the repository."
3967+
},
3968+
"name": {
3969+
"type": "string",
3970+
"description": "The name of the repository."
3971+
}
3972+
},
3973+
"required": [
3974+
"owner",
3975+
"name"
3976+
]
3977+
},
3978+
"pullRequestNumber": {
3979+
"type": "number",
3980+
"description": "The number of the pull request to get status checks for."
3981+
}
3982+
},
3983+
"required": [
3984+
"pullRequestNumber"
3985+
]
3986+
},
3987+
"userDescription": "%languageModelTools.github-pull-request_pullRequestStatusChecks.description%",
3988+
"when": "config.githubPullRequests.experimental.chat"
3989+
},
39443990
{
39453991
"name": "github-pull-request_openPullRequest",
39463992
"tags": [
@@ -3949,7 +3995,7 @@
39493995
],
39503996
"toolReferenceName": "openPullRequest",
39513997
"displayName": "%languageModelTools.github-pull-request_openPullRequest.displayName%",
3952-
"modelDescription": "Get comprehensive information about the GitHub pull request (PR) which is currently visible, but not necessarily checked out. This is the pull request that the user is currently viewing. This includes the PR title, full description, list of changed files, review comments, PR state, and status checks/CI results. For PRs created by Copilot, it also includes the session logs which indicate the development process and decisions made by the coding agent. When asked about the currently open pull request, do this first! Use this tool for any request related to \"pull request details,\" \"what changed,\" \"PR status,\" or similar queries even if the user does not explicitly mention \"pull request.\" When asked to use this tool, ALWAYS use it.",
3998+
"modelDescription": "Get comprehensive information about the GitHub pull request (PR) which is currently visible, but not necessarily checked out. This is the pull request that the user is currently viewing. This includes the PR title, full description, list of changed files, review comments, and PR state. For PRs created by Copilot, it also includes the session logs which indicate the development process and decisions made by the coding agent. Does NOT include status checks/CI results; use the pullRequestStatusChecks tool instead. When asked about the currently open pull request, do this first! Use this tool for any request related to \"pull request details,\" \"what changed,\" or similar queries even if the user does not explicitly mention \"pull request.\" When asked to use this tool, ALWAYS use it.",
39533999
"icon": "$(git-pull-request)",
39544000
"canBeReferencedInPrompt": true,
39554001
"userDescription": "%languageModelTools.github-pull-request_openPullRequest.description%",
@@ -4075,7 +4121,7 @@
40754121
"js-yaml": "^4.1.1",
40764122
"jsonc-parser": "^3.3.1",
40774123
"lru-cache": "6.0.0",
4078-
"markdown-it": "^14.1.0",
4124+
"markdown-it": "^14.1.1",
40794125
"marked": "^4.0.10",
40804126
"react": "^16.12.0",
40814127
"react-dom": "^16.12.0",

package.nls.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,9 @@
434434
"languageModelTools.github-pull-request_doSearch.description": "Search for GitHub issues and pull requests.",
435435
"languageModelTools.github-pull-request_renderIssues.displayName": "Render issue items in a markdown table",
436436
"languageModelTools.github-pull-request_activePullRequest.displayName": "Active Pull Request",
437-
"languageModelTools.github-pull-request_activePullRequest.description": "Get information about the active GitHub pull request. This information includes: comments, files changed, pull request title + description, pull request state, and pull request status checks/CI.",
437+
"languageModelTools.github-pull-request_activePullRequest.description": "Get information about the active GitHub pull request. This information includes: comments, files changed, pull request title + description, and pull request state.",
438+
"languageModelTools.github-pull-request_pullRequestStatusChecks.displayName": "Pull Request Status Checks",
439+
"languageModelTools.github-pull-request_pullRequestStatusChecks.description": "Get the status checks and CI results for a GitHub pull request.",
438440
"languageModelTools.github-pull-request_openPullRequest.displayName": "Open Pull Request",
439-
"languageModelTools.github-pull-request_openPullRequest.description": "Get information about the open GitHub pull request. This information includes: comments, files changed, pull request title + description, pull request state, and pull request status checks/CI."
441+
"languageModelTools.github-pull-request_openPullRequest.description": "Get information about the open GitHub pull request. This information includes: comments, files changed, pull request title + description, and pull request state."
440442
}

src/github/createPRViewProvider.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -667,14 +667,17 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
667667
}
668668

669669
public async setDefaultCompareBranch(compareBranch: Branch | undefined) {
670-
this._defaultCompareBranch = compareBranch!.name!;
671-
this.model.setCompareBranch(compareBranch!.name);
672-
this.changeBranch(compareBranch!.name!, false).then(async titleAndDescription => {
670+
if (!compareBranch?.name) {
671+
return;
672+
}
673+
this._defaultCompareBranch = compareBranch.name;
674+
this.model.setCompareBranch(compareBranch.name);
675+
this.changeBranch(compareBranch.name, false).then(async titleAndDescription => {
673676
const params: Partial<CreateParamsNew> = {
674677
defaultTitle: titleAndDescription.title,
675678
defaultDescription: titleAndDescription.description,
676-
compareBranch: compareBranch?.name,
677-
defaultCompareBranch: compareBranch?.name,
679+
compareBranch: compareBranch.name,
680+
defaultCompareBranch: compareBranch.name,
678681
warning: await this.existingPRMessage(),
679682
};
680683
return this._postMessage({

src/github/credentials.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,32 @@ export class CredentialStore extends Disposable {
130130
await this.context.globalState.update(LAST_USED_SCOPES_GITHUB_KEY, this._scopes);
131131
await this.context.globalState.update(LAST_USED_SCOPES_ENTERPRISE_KEY, this._scopesEnterprise);
132132
}
133+
134+
private async tryInitializeFromEnvironmentToken(authProviderId: AuthProvider): Promise<AuthResult | undefined> {
135+
if (isEnterprise(authProviderId)) {
136+
return undefined;
137+
}
138+
const token = process.env.GITHUB_OAUTH_TOKEN;
139+
if (!token) {
140+
return undefined;
141+
}
142+
Logger.debug('Attempting authentication using GITHUB_OAUTH_TOKEN environment variable.', CredentialStore.ID);
143+
try {
144+
const github = await this.createHub(token, authProviderId);
145+
this._githubAPI = github;
146+
this._sessionId = 'environment-token';
147+
if (!this._isInitialized) {
148+
this._isInitialized = true;
149+
this._onDidInitialize.fire();
150+
}
151+
Logger.appendLine('Successfully authenticated using GITHUB_OAUTH_TOKEN environment variable.', CredentialStore.ID);
152+
return { canceled: false };
153+
} catch (e) {
154+
Logger.error(`Failed to authenticate using GITHUB_OAUTH_TOKEN: ${e.message}`, CredentialStore.ID);
155+
return undefined;
156+
}
157+
}
158+
133159
private async initialize(authProviderId: AuthProvider, getAuthSessionOptions: vscode.AuthenticationGetSessionOptions = {}, scopes: string[] = (!isEnterprise(authProviderId) ? this._scopes : this._scopesEnterprise), requireScopes?: boolean): Promise<AuthResult> {
134160
Logger.debug(`Initializing GitHub${getGitHubSuffix(authProviderId)} authentication provider.`, 'Authentication');
135161
if (isEnterprise(authProviderId)) {
@@ -139,6 +165,11 @@ export class CredentialStore extends Disposable {
139165
}
140166
}
141167

168+
const envResult = await this.tryInitializeFromEnvironmentToken(authProviderId);
169+
if (envResult) {
170+
return envResult;
171+
}
172+
142173
if (getAuthSessionOptions.createIfNone === undefined && getAuthSessionOptions.forceNewSession === undefined) {
143174
getAuthSessionOptions.createIfNone = false;
144175
}

src/github/issueOverview.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,30 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
4242
protected _folderRepositoryManager: FolderRepositoryManager;
4343
protected _scrollPosition = { x: 0, y: 0 };
4444

45+
protected static _getViewColumn(toTheSide: boolean, panel?: IssueOverviewPanel): number | undefined {
46+
const tabViewColumn = vscode.window.tabGroups.activeTabGroup.viewColumn;
47+
const activeColumn = toTheSide
48+
? vscode.ViewColumn.Beside
49+
: (panel ? undefined : tabViewColumn);
50+
return activeColumn;
51+
}
52+
4553
public static async createOrShow(
4654
telemetry: ITelemetry,
4755
extensionUri: vscode.Uri,
4856
folderRepositoryManager: FolderRepositoryManager,
4957
identity: UnresolvedIdentity,
5058
issue?: IssueModel,
51-
toTheSide: Boolean = false,
59+
toTheSide: boolean = false,
5260
_preserveFocus: boolean = true,
5361
existingPanel?: vscode.WebviewPanel
5462
) {
5563
await ensureEmojis(folderRepositoryManager.context);
56-
const activeColumn = toTheSide
57-
? vscode.ViewColumn.Beside
58-
: vscode.window.activeTextEditor
59-
? vscode.window.activeTextEditor.viewColumn
60-
: vscode.ViewColumn.One;
6164

6265
const key = panelKey(identity.owner, identity.repo, identity.number);
6366
let panel = this._panels.get(key);
67+
const activeColumn = IssueOverviewPanel._getViewColumn(toTheSide, panel);
68+
6469
if (panel) {
6570
panel._panel.reveal(activeColumn, true);
6671
} else {

src/github/pullRequestOverview.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,11 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
8282
*/
8383
telemetry.sendTelemetryEvent('pr.openDescription', { isCopilot: (issue?.author.login === COPILOT_SWE_AGENT) ? 'true' : 'false' });
8484

85-
const activeColumn = toTheSide
86-
? vscode.ViewColumn.Beside
87-
: vscode.window.activeTextEditor
88-
? vscode.window.activeTextEditor.viewColumn
89-
: vscode.ViewColumn.One;
90-
9185
const key = panelKey(identity.owner, identity.repo, identity.number);
9286
let panel = this._panels.get(key);
87+
88+
const activeColumn = IssueOverviewPanel._getViewColumn(toTheSide, panel);
89+
9390
if (panel) {
9491
panel._panel.reveal(activeColumn, preserveFocus);
9592
} else {

src/lm/tools/activePullRequestTool.ts

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import * as vscode from 'vscode';
88
import { FetchIssueResult } from './fetchIssueTool';
99
import { GitChangeType, InMemFileChange } from '../../common/file';
10-
import Logger from '../../common/logger';
1110
import { CommentEvent, EventType, ReviewEvent } from '../../common/timelineEvent';
12-
import { CheckState } from '../../github/interface';
1311
import { PullRequestModel } from '../../github/pullRequestModel';
1412
import { RepositoriesManager } from '../../github/repositoriesManager';
1513

@@ -51,28 +49,14 @@ export abstract class PullRequestTool implements vscode.LanguageModelTool<FetchI
5149
return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart('There is no active pull request')]);
5250
}
5351

54-
const status = await pullRequest.getStatusChecks();
55-
const statuses = status[0]?.statuses ?? [];
56-
57-
// Fetch logs for failed check runs in parallel
58-
const statusChecks = await Promise.all(statuses.map(async (s) => {
59-
const entry: Record<string, any> = {
60-
context: s.context,
61-
description: s.description,
62-
state: s.state,
63-
name: s.workflowName,
64-
targetUrl: s.targetUrl,
65-
};
66-
if (s.state === CheckState.Failure && s.isCheckRun && s.databaseId) {
67-
try {
68-
entry.logs = await pullRequest.githubRepository.getCheckRunLogs(s.databaseId);
69-
} catch (e) {
70-
Logger.error(`Failed to fetch check run logs for ${s.context}: ${e}`, 'PullRequestTool');
71-
}
72-
}
73-
return entry;
74-
}));
7552
const timeline = (pullRequest.timelineEvents && pullRequest.timelineEvents.length > 0) ? pullRequest.timelineEvents : await pullRequest.getTimelineEvents();
53+
const reviewAndCommentEvents = timeline.filter((event): event is ReviewEvent | CommentEvent => event.event === EventType.Reviewed || event.event === EventType.Commented);
54+
55+
if ((pullRequest.comments.length === 0) && (reviewAndCommentEvents.length !== 0)) {
56+
// Probably missing some comments
57+
await pullRequest.initializeReviewThreadCacheAndReviewComments();
58+
}
59+
7660
const pullRequestInfo = {
7761
title: pullRequest.title,
7862
body: pullRequest.body,
@@ -86,20 +70,14 @@ export abstract class PullRequestTool implements vscode.LanguageModelTool<FetchI
8670
file: comment.path
8771
};
8872
}),
89-
timelineComments: timeline.filter((event): event is ReviewEvent | CommentEvent => event.event === EventType.Reviewed || event.event === EventType.Commented).map(event => {
73+
timelineComments: reviewAndCommentEvents.map(event => {
9074
return {
9175
author: event.user?.login,
9276
body: event.body,
9377
commentType: event.event === EventType.Reviewed ? event.state : 'COMMENTED',
9478
};
9579
}),
9680
state: pullRequest.state,
97-
statusChecks,
98-
reviewRequirements: {
99-
approvalsNeeded: status[1]?.count ?? 0,
100-
currentApprovals: status[1]?.approvals.length ?? 0,
101-
areChangesRequested: (status[1]?.requestedChanges.length ?? 0) > 0,
102-
},
10381
isDraft: pullRequest.isDraft ? 'is a draft and cannot be merged until marked as ready for review' : 'false',
10482
changes: (await pullRequest.getFileChangesInfo()).map(change => {
10583
if (change instanceof InMemFileChange) {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
'use strict';
6+
7+
import * as vscode from 'vscode';
8+
import { RepoToolBase } from './toolsUtils';
9+
import Logger from '../../common/logger';
10+
import { CheckState } from '../../github/interface';
11+
import { PullRequestModel } from '../../github/pullRequestModel';
12+
13+
interface StatusChecksToolParameters {
14+
pullRequestNumber: number;
15+
repo?: {
16+
owner?: string;
17+
name?: string;
18+
};
19+
}
20+
21+
export class PullRequestStatusChecksTool extends RepoToolBase<StatusChecksToolParameters> {
22+
public static readonly toolId = 'github-pull-request_pullRequestStatusChecks';
23+
24+
async prepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions<StatusChecksToolParameters>): Promise<vscode.PreparedToolInvocation> {
25+
if (!options.input.pullRequestNumber) {
26+
return {
27+
invocationMessage: vscode.l10n.t('Fetching status checks from GitHub'),
28+
};
29+
}
30+
const { owner, name } = await this.getRepoInfo({ owner: options.input.repo?.owner, name: options.input.repo?.name });
31+
const url = (owner && name) ? `https://github.com/${owner}/${name}/pull/${options.input.pullRequestNumber}` : undefined;
32+
const message = url
33+
? new vscode.MarkdownString(vscode.l10n.t('Fetching status checks for [#{0}]({1}) from GitHub', options.input.pullRequestNumber, url))
34+
: vscode.l10n.t('Fetching status checks for #{0} from GitHub', options.input.pullRequestNumber);
35+
return {
36+
invocationMessage: message,
37+
};
38+
}
39+
40+
async invoke(options: vscode.LanguageModelToolInvocationOptions<StatusChecksToolParameters>, _token: vscode.CancellationToken): Promise<vscode.LanguageModelToolResult> {
41+
const pullRequestNumber = options.input.pullRequestNumber;
42+
if (!pullRequestNumber) {
43+
throw new Error('No pull request number provided.');
44+
}
45+
const { owner, name, folderManager } = await this.getRepoInfo({ owner: options.input.repo?.owner, name: options.input.repo?.name });
46+
const issueOrPullRequest = await folderManager.resolveIssueOrPullRequest(owner, name, pullRequestNumber);
47+
if (!(issueOrPullRequest instanceof PullRequestModel)) {
48+
throw new Error(`No pull request found for ${owner}/${name}#${pullRequestNumber}. Make sure the pull request exists.`);
49+
}
50+
51+
const pullRequest = issueOrPullRequest;
52+
const status = await pullRequest.getStatusChecks();
53+
const statuses = status[0]?.statuses ?? [];
54+
55+
// Return all status checks, but only fetch logs for failures
56+
const statusChecks = await Promise.all(statuses.map(async (s) => {
57+
const entry: Record<string, any> = {
58+
context: s.context,
59+
description: s.description,
60+
state: s.state,
61+
name: s.workflowName,
62+
targetUrl: s.targetUrl,
63+
};
64+
if (s.state === CheckState.Failure && s.isCheckRun && s.databaseId) {
65+
try {
66+
entry.logs = await pullRequest.githubRepository.getCheckRunLogs(s.databaseId);
67+
} catch (e) {
68+
Logger.error(`Failed to fetch check run logs for ${s.context}: ${e}`, 'PullRequestStatusChecksTool');
69+
}
70+
}
71+
return entry;
72+
}));
73+
74+
const statusChecksInfo = {
75+
statusChecks,
76+
reviewRequirements: {
77+
approvalsNeeded: status[1]?.count ?? 0,
78+
currentApprovals: status[1]?.approvals.length ?? 0,
79+
areChangesRequested: (status[1]?.requestedChanges.length ?? 0) > 0,
80+
},
81+
};
82+
83+
return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart(JSON.stringify(statusChecksInfo))]);
84+
}
85+
}

0 commit comments

Comments
 (0)