@shipfox/api-integration-github 12.3.0 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +11 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +7 -4
- package/dist/api/client.js.map +1 -1
- package/dist/api/installation-token-envelope.d.ts +5 -1
- package/dist/api/installation-token-envelope.d.ts.map +1 -1
- package/dist/api/installation-token-envelope.js +16 -5
- package/dist/api/installation-token-envelope.js.map +1 -1
- package/dist/api/shared-installation-token-cache.d.ts.map +1 -1
- package/dist/api/shared-installation-token-cache.js +10 -4
- package/dist/api/shared-installation-token-cache.js.map +1 -1
- package/dist/core/agent-tools.d.ts +11 -4
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent-tools.js +182 -17
- package/dist/core/agent-tools.js.map +1 -1
- package/dist/core/github-agent-tool-catalog.js +1 -1
- package/dist/core/github-agent-tool-catalog.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/api/client.test.ts +41 -5
- package/src/api/client.ts +22 -2
- package/src/api/installation-token-envelope.ts +24 -2
- package/src/api/shared-installation-token-cache.test.ts +28 -0
- package/src/api/shared-installation-token-cache.ts +7 -0
- package/src/core/agent-tools.test.ts +862 -8
- package/src/core/agent-tools.ts +271 -19
- package/src/core/github-agent-tool-catalog.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/core/agent-tools.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
OpenAgentToolsSessionInput,
|
|
9
9
|
} from '@shipfox/api-integration-spi';
|
|
10
10
|
import {Octokit} from 'octokit';
|
|
11
|
+
import {mapGithubError} from '#api/client.js';
|
|
11
12
|
import {
|
|
12
13
|
createGithubInstallationTokenProvider,
|
|
13
14
|
type GithubInstallationTokenProvider,
|
|
@@ -48,6 +49,44 @@ type GithubToolCallResult = {
|
|
|
48
49
|
structuredContent?: Record<string, unknown> | undefined;
|
|
49
50
|
};
|
|
50
51
|
|
|
52
|
+
const GITHUB_GRAPHQL_ROUTE = 'POST /graphql';
|
|
53
|
+
const GITHUB_ARTIFACT_ARCHIVE_FORMAT = 'zip';
|
|
54
|
+
const GITHUB_ARTIFACT_DOWNLOAD_ROUTE = `GET /repos/{owner}/{repo}/actions/artifacts/{resource_id}/${GITHUB_ARTIFACT_ARCHIVE_FORMAT}`;
|
|
55
|
+
const GITHUB_ARTIFACT_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
56
|
+
const NO_PENDING_REVIEW_MESSAGE =
|
|
57
|
+
'No pending pull request review found for the authenticated GitHub user.';
|
|
58
|
+
|
|
59
|
+
const LATEST_PENDING_REVIEW_QUERY = `
|
|
60
|
+
query LatestPendingPullRequestReview($owner: String!, $repo: String!, $pullNumber: Int!) {
|
|
61
|
+
viewer {
|
|
62
|
+
login
|
|
63
|
+
}
|
|
64
|
+
repository(owner: $owner, name: $repo) {
|
|
65
|
+
pullRequest(number: $pullNumber) {
|
|
66
|
+
reviews(last: 100, states: [PENDING]) {
|
|
67
|
+
nodes {
|
|
68
|
+
id
|
|
69
|
+
author {
|
|
70
|
+
login
|
|
71
|
+
}
|
|
72
|
+
createdAt
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const ADD_PENDING_REVIEW_COMMENT_MUTATION = `
|
|
81
|
+
mutation AddCommentToPendingReview($input: AddPullRequestReviewThreadInput!) {
|
|
82
|
+
addPullRequestReviewThread(input: $input) {
|
|
83
|
+
thread {
|
|
84
|
+
id
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
51
90
|
export class GithubAgentToolsProvider
|
|
52
91
|
implements
|
|
53
92
|
AgentToolsProvider<
|
|
@@ -108,15 +147,39 @@ export class GithubAgentToolsProvider
|
|
|
108
147
|
);
|
|
109
148
|
}
|
|
110
149
|
const client = (this.options.createClient ?? createOctokitClient)(token.token);
|
|
150
|
+
const method =
|
|
151
|
+
typeof call.arguments.method === 'string' ? call.arguments.method : undefined;
|
|
111
152
|
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
153
|
+
if (operation.kind === 'graphql') {
|
|
154
|
+
const data = await mapGithubError(() =>
|
|
155
|
+
addCommentToPendingReview(client, operation.parameters),
|
|
156
|
+
);
|
|
157
|
+
return data === undefined
|
|
158
|
+
? githubToolError(NO_PENDING_REVIEW_MESSAGE)
|
|
159
|
+
: githubToolResult(tool.id as GithubAgentToolId, data);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const operationParameters = await mapGithubError(() =>
|
|
163
|
+
resolvePendingReviewParameters(
|
|
164
|
+
client,
|
|
165
|
+
operation.parameters,
|
|
166
|
+
tool.id as GithubAgentToolId,
|
|
167
|
+
method,
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
if (operationParameters === undefined) {
|
|
171
|
+
return githubToolError(NO_PENDING_REVIEW_MESSAGE);
|
|
119
172
|
}
|
|
173
|
+
const response = await mapGithubError(() =>
|
|
174
|
+
client.request(operation.route, operationParameters),
|
|
175
|
+
);
|
|
176
|
+
return githubToolResult(
|
|
177
|
+
tool.id as GithubAgentToolId,
|
|
178
|
+
response.data,
|
|
179
|
+
response,
|
|
180
|
+
operationParameters,
|
|
181
|
+
operation.route,
|
|
182
|
+
);
|
|
120
183
|
},
|
|
121
184
|
};
|
|
122
185
|
}
|
|
@@ -130,8 +193,16 @@ export interface GithubAgentToolsProviderOptions {
|
|
|
130
193
|
createClient?: GithubToolClientFactory | undefined;
|
|
131
194
|
}
|
|
132
195
|
|
|
196
|
+
export interface GithubToolResponse {
|
|
197
|
+
data: unknown;
|
|
198
|
+
headers?: Record<string, string | number | undefined> | undefined;
|
|
199
|
+
status?: number | undefined;
|
|
200
|
+
url?: string | undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
133
203
|
export interface GithubToolClient {
|
|
134
|
-
request(route: string, parameters: Record<string, unknown>): Promise<
|
|
204
|
+
request(route: string, parameters: Record<string, unknown>): Promise<GithubToolResponse>;
|
|
205
|
+
graphql?: ((query: string, variables: Record<string, unknown>) => Promise<unknown>) | undefined;
|
|
135
206
|
}
|
|
136
207
|
|
|
137
208
|
export type GithubToolClientFactory = (token: string) => GithubToolClient;
|
|
@@ -139,6 +210,7 @@ export type GithubToolClientFactory = (token: string) => GithubToolClient;
|
|
|
139
210
|
interface GithubToolOperation {
|
|
140
211
|
route: string;
|
|
141
212
|
parameters: Record<string, unknown>;
|
|
213
|
+
kind: 'rest' | 'graphql';
|
|
142
214
|
}
|
|
143
215
|
|
|
144
216
|
function createOctokitClient(token: string): GithubToolClient {
|
|
@@ -148,7 +220,30 @@ function createOctokitClient(token: string): GithubToolClient {
|
|
|
148
220
|
retry: {enabled: false},
|
|
149
221
|
});
|
|
150
222
|
return {
|
|
151
|
-
request: async (route, parameters) =>
|
|
223
|
+
request: async (route, parameters) => {
|
|
224
|
+
if (route !== GITHUB_ARTIFACT_DOWNLOAD_ROUTE) {
|
|
225
|
+
return await octokit.request(route, parameters);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const abortController = new AbortController();
|
|
229
|
+
const timeout = setTimeout(
|
|
230
|
+
() => abortController.abort(),
|
|
231
|
+
GITHUB_ARTIFACT_DOWNLOAD_TIMEOUT_MS,
|
|
232
|
+
);
|
|
233
|
+
try {
|
|
234
|
+
return await octokit.request(route, {
|
|
235
|
+
...parameters,
|
|
236
|
+
request: {
|
|
237
|
+
redirect: 'manual',
|
|
238
|
+
parseSuccessResponseBody: false,
|
|
239
|
+
signal: abortController.signal,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
} finally {
|
|
243
|
+
clearTimeout(timeout);
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
graphql: async (query, variables) => await octokit.graphql(query, variables),
|
|
152
247
|
};
|
|
153
248
|
}
|
|
154
249
|
|
|
@@ -167,10 +262,14 @@ function resolveGithubOperation(
|
|
|
167
262
|
const route = githubOperationRoute(toolId, method, params);
|
|
168
263
|
return route === undefined
|
|
169
264
|
? undefined
|
|
170
|
-
: {
|
|
265
|
+
: {
|
|
266
|
+
route,
|
|
267
|
+
parameters: projectGithubOperationParameters(toolId, method, params),
|
|
268
|
+
kind: route === GITHUB_GRAPHQL_ROUTE ? 'graphql' : 'rest',
|
|
269
|
+
};
|
|
171
270
|
}
|
|
172
271
|
|
|
173
|
-
function githubOperationRoute(
|
|
272
|
+
export function githubOperationRoute(
|
|
174
273
|
toolId: GithubAgentToolId,
|
|
175
274
|
method: string | undefined,
|
|
176
275
|
args: Record<string, unknown>,
|
|
@@ -217,7 +316,7 @@ function githubOperationRoute(
|
|
|
217
316
|
case 'sub_issue_write.remove':
|
|
218
317
|
return `DELETE ${repoPath}/issues/${issue}/sub_issues/{sub_issue_id}`;
|
|
219
318
|
case 'sub_issue_write.reprioritize':
|
|
220
|
-
return `PATCH ${repoPath}/issues/${issue}/sub_issues/
|
|
319
|
+
return `PATCH ${repoPath}/issues/${issue}/sub_issues/priority`;
|
|
221
320
|
case 'pull_request_read.get':
|
|
222
321
|
return `GET ${repoPath}/pulls/${pull}`;
|
|
223
322
|
case 'pull_request_read.get_diff':
|
|
@@ -245,7 +344,9 @@ function githubOperationRoute(
|
|
|
245
344
|
case 'update_pull_request.':
|
|
246
345
|
return `PATCH ${repoPath}/pulls/${pull}`;
|
|
247
346
|
case 'add_reply_to_pull_request_comment.':
|
|
248
|
-
return
|
|
347
|
+
return args.reaction !== undefined && args.body === undefined
|
|
348
|
+
? `POST ${repoPath}/pulls/comments/{comment_id}/reactions`
|
|
349
|
+
: `POST ${repoPath}/pulls/${pull}/comments/{comment_id}/replies`;
|
|
249
350
|
case 'merge_pull_request.':
|
|
250
351
|
return `PUT ${repoPath}/pulls/${pull}/merge`;
|
|
251
352
|
case 'update_pull_request_branch.':
|
|
@@ -257,7 +358,7 @@ function githubOperationRoute(
|
|
|
257
358
|
case 'pull_request_review_write.delete_pending':
|
|
258
359
|
return `DELETE ${repoPath}/pulls/${pull}/reviews/{review_id}`;
|
|
259
360
|
case 'add_comment_to_pending_review.':
|
|
260
|
-
return
|
|
361
|
+
return GITHUB_GRAPHQL_ROUTE;
|
|
261
362
|
case 'actions_list.list_workflows':
|
|
262
363
|
return `GET ${repoPath}/actions/workflows`;
|
|
263
364
|
case 'actions_list.list_workflow_runs':
|
|
@@ -273,7 +374,7 @@ function githubOperationRoute(
|
|
|
273
374
|
case 'actions_get.get_workflow_job':
|
|
274
375
|
return `GET ${repoPath}/actions/jobs/${resource}`;
|
|
275
376
|
case 'actions_get.download_workflow_run_artifact':
|
|
276
|
-
return
|
|
377
|
+
return GITHUB_ARTIFACT_DOWNLOAD_ROUTE;
|
|
277
378
|
case 'actions_get.get_workflow_run_usage':
|
|
278
379
|
return `GET ${repoPath}/actions/runs/${resource}/timing`;
|
|
279
380
|
case 'actions_get.get_workflow_run_logs_url':
|
|
@@ -295,7 +396,65 @@ function githubOperationRoute(
|
|
|
295
396
|
}
|
|
296
397
|
}
|
|
297
398
|
|
|
298
|
-
function
|
|
399
|
+
async function addCommentToPendingReview(
|
|
400
|
+
client: GithubToolClient,
|
|
401
|
+
args: Record<string, unknown>,
|
|
402
|
+
): Promise<unknown | undefined> {
|
|
403
|
+
if (client.graphql === undefined) {
|
|
404
|
+
throw new GithubIntegrationProviderError(
|
|
405
|
+
'malformed-provider-response',
|
|
406
|
+
'GitHub client does not support GraphQL operations',
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const reviewLookup = await client.graphql(LATEST_PENDING_REVIEW_QUERY, {
|
|
411
|
+
owner: args.owner,
|
|
412
|
+
repo: args.repo,
|
|
413
|
+
pullNumber: args.pull_number,
|
|
414
|
+
});
|
|
415
|
+
const reviewId = latestPendingReviewNodeId(reviewLookup);
|
|
416
|
+
if (reviewId === undefined) return undefined;
|
|
417
|
+
|
|
418
|
+
const input: Record<string, unknown> = {
|
|
419
|
+
pullRequestReviewId: reviewId,
|
|
420
|
+
path: args.path,
|
|
421
|
+
body: args.body,
|
|
422
|
+
subjectType: args.subject_type,
|
|
423
|
+
};
|
|
424
|
+
if (args.line !== undefined) input.line = args.line;
|
|
425
|
+
if (args.side !== undefined) input.side = args.side;
|
|
426
|
+
if (args.start_line !== undefined) input.startLine = args.start_line;
|
|
427
|
+
if (args.start_side !== undefined) input.startSide = args.start_side;
|
|
428
|
+
|
|
429
|
+
return await client.graphql(ADD_PENDING_REVIEW_COMMENT_MUTATION, {input});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function latestPendingReviewNodeId(data: unknown): string | undefined {
|
|
433
|
+
if (!isRecord(data)) return undefined;
|
|
434
|
+
|
|
435
|
+
const viewer = isRecord(data.viewer) ? data.viewer.login : undefined;
|
|
436
|
+
if (typeof viewer !== 'string') return undefined;
|
|
437
|
+
|
|
438
|
+
const repository = isRecord(data.repository) ? data.repository : undefined;
|
|
439
|
+
const pullRequest =
|
|
440
|
+
repository && isRecord(repository.pullRequest) ? repository.pullRequest : undefined;
|
|
441
|
+
const reviews = pullRequest && isRecord(pullRequest.reviews) ? pullRequest.reviews : undefined;
|
|
442
|
+
const nodes = reviews && Array.isArray(reviews.nodes) ? reviews.nodes : [];
|
|
443
|
+
|
|
444
|
+
let latest: {createdAt: string; id: string} | undefined;
|
|
445
|
+
for (const node of nodes) {
|
|
446
|
+
if (!isRecord(node)) continue;
|
|
447
|
+
const author = isRecord(node.author) ? node.author.login : undefined;
|
|
448
|
+
const id = node.id;
|
|
449
|
+
const createdAt = node.createdAt;
|
|
450
|
+
if (author !== viewer || typeof id !== 'string' || typeof createdAt !== 'string') continue;
|
|
451
|
+
if (latest === undefined || createdAt > latest.createdAt) latest = {createdAt, id};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return latest?.id;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function projectGithubOperationParameters(
|
|
299
458
|
toolId: GithubAgentToolId,
|
|
300
459
|
method: string | undefined,
|
|
301
460
|
args: Record<string, unknown>,
|
|
@@ -312,8 +471,70 @@ function projectGithubOperationParameters(
|
|
|
312
471
|
return parameters;
|
|
313
472
|
}
|
|
314
473
|
|
|
315
|
-
function
|
|
316
|
-
|
|
474
|
+
async function resolvePendingReviewParameters(
|
|
475
|
+
client: GithubToolClient,
|
|
476
|
+
parameters: Record<string, unknown>,
|
|
477
|
+
toolId: GithubAgentToolId,
|
|
478
|
+
method: string | undefined,
|
|
479
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
480
|
+
if (!isPendingReviewOperation(toolId, method)) return parameters;
|
|
481
|
+
|
|
482
|
+
const reviewId = await latestPendingReviewId(client, parameters);
|
|
483
|
+
return reviewId === undefined ? undefined : {...parameters, review_id: reviewId};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function isPendingReviewOperation(toolId: GithubAgentToolId, method: string | undefined): boolean {
|
|
487
|
+
return (
|
|
488
|
+
toolId === 'pull_request_review_write' &&
|
|
489
|
+
(method === 'submit_pending' || method === 'delete_pending')
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function latestPendingReviewId(
|
|
494
|
+
client: GithubToolClient,
|
|
495
|
+
parameters: Record<string, unknown>,
|
|
496
|
+
): Promise<number | undefined> {
|
|
497
|
+
const perPage = 100;
|
|
498
|
+
let page = 1;
|
|
499
|
+
let reviewId: number | undefined;
|
|
500
|
+
|
|
501
|
+
while (true) {
|
|
502
|
+
const response = await client.request('GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews', {
|
|
503
|
+
owner: parameters.owner,
|
|
504
|
+
repo: parameters.repo,
|
|
505
|
+
pull_number: parameters.pull_number,
|
|
506
|
+
per_page: perPage,
|
|
507
|
+
page,
|
|
508
|
+
});
|
|
509
|
+
if (!Array.isArray(response.data)) return undefined;
|
|
510
|
+
|
|
511
|
+
reviewId = latestPendingReviewIdOnPage(response.data) ?? reviewId;
|
|
512
|
+
if (response.data.length < perPage) return reviewId;
|
|
513
|
+
page += 1;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function latestPendingReviewIdOnPage(data: readonly unknown[]): number | undefined {
|
|
518
|
+
for (let index = data.length - 1; index >= 0; index -= 1) {
|
|
519
|
+
const review = data[index];
|
|
520
|
+
if (!isRecord(review) || review.state !== 'PENDING') continue;
|
|
521
|
+
if (typeof review.id === 'number' && Number.isSafeInteger(review.id)) return review.id;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function githubToolResult(
|
|
528
|
+
toolId: GithubAgentToolId,
|
|
529
|
+
data: unknown,
|
|
530
|
+
response?: GithubToolResponse,
|
|
531
|
+
parameters?: Record<string, unknown>,
|
|
532
|
+
route?: string,
|
|
533
|
+
): GithubToolCallResult {
|
|
534
|
+
const structuredContent = projectGithubToolOutput(toolId, data, response, parameters, route);
|
|
535
|
+
if (structuredContent === undefined) {
|
|
536
|
+
return githubToolError('GitHub artifact download did not return a download URL');
|
|
537
|
+
}
|
|
317
538
|
return {
|
|
318
539
|
content: [{type: 'text', text: JSON.stringify(structuredContent)}],
|
|
319
540
|
structuredContent,
|
|
@@ -323,7 +544,14 @@ function githubToolResult(toolId: GithubAgentToolId, data: unknown): GithubToolC
|
|
|
323
544
|
function projectGithubToolOutput(
|
|
324
545
|
toolId: GithubAgentToolId,
|
|
325
546
|
data: unknown,
|
|
326
|
-
|
|
547
|
+
response?: GithubToolResponse,
|
|
548
|
+
parameters?: Record<string, unknown>,
|
|
549
|
+
route?: string,
|
|
550
|
+
): Record<string, unknown> | undefined {
|
|
551
|
+
if (route === GITHUB_ARTIFACT_DOWNLOAD_ROUTE) {
|
|
552
|
+
return projectGithubArtifactDownloadOutput(response, parameters);
|
|
553
|
+
}
|
|
554
|
+
|
|
327
555
|
switch (toolId) {
|
|
328
556
|
case 'list_issue_types':
|
|
329
557
|
return {issue_types: data};
|
|
@@ -345,6 +573,30 @@ function projectGithubToolOutput(
|
|
|
345
573
|
}
|
|
346
574
|
}
|
|
347
575
|
|
|
576
|
+
function projectGithubArtifactDownloadOutput(
|
|
577
|
+
response: GithubToolResponse | undefined,
|
|
578
|
+
parameters: Record<string, unknown> | undefined,
|
|
579
|
+
): Record<string, unknown> | undefined {
|
|
580
|
+
if (response === undefined) return undefined;
|
|
581
|
+
const downloadUrl = response.headers?.location;
|
|
582
|
+
if (typeof downloadUrl !== 'string' || downloadUrl.length === 0) return undefined;
|
|
583
|
+
|
|
584
|
+
const output: Record<string, unknown> = {
|
|
585
|
+
archive_format: GITHUB_ARTIFACT_ARCHIVE_FORMAT,
|
|
586
|
+
download_url: downloadUrl,
|
|
587
|
+
};
|
|
588
|
+
if (typeof parameters?.resource_id === 'string') output.artifact_id = parameters.resource_id;
|
|
589
|
+
|
|
590
|
+
const contentType = response.headers?.['content-type'];
|
|
591
|
+
if (typeof contentType === 'string') output.content_type = contentType;
|
|
592
|
+
|
|
593
|
+
const contentLength = response.headers?.['content-length'];
|
|
594
|
+
const sizeBytes = typeof contentLength === 'number' ? contentLength : Number(contentLength);
|
|
595
|
+
if (Number.isSafeInteger(sizeBytes) && sizeBytes >= 0) output.size_bytes = sizeBytes;
|
|
596
|
+
|
|
597
|
+
return output;
|
|
598
|
+
}
|
|
599
|
+
|
|
348
600
|
function githubSearchItems(data: unknown): unknown {
|
|
349
601
|
return isRecord(data) ? data.items : data;
|
|
350
602
|
}
|
|
@@ -670,7 +670,7 @@ export const githubAgentToolCatalog = [
|
|
|
670
670
|
id: 'add_comment_to_pending_review',
|
|
671
671
|
category: 'pull_requests',
|
|
672
672
|
description:
|
|
673
|
-
"Add review comment to the requester's latest pending pull request review.
|
|
673
|
+
"Add a review comment to the requester's latest pending pull request review. The comment remains part of that pending review until it is submitted; a pending review needs to already exist to call this.",
|
|
674
674
|
sensitivity: 'write',
|
|
675
675
|
sensitive: false,
|
|
676
676
|
requiredScope: scopes.pullRequestsWrite,
|