@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
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import {RequestError} from 'octokit';
|
|
1
2
|
import type {GithubApiClient} from '#api/client.js';
|
|
2
3
|
import {DEFAULT_JOB_LOG_TAIL_LINES} from '#core/actions-logs.js';
|
|
3
4
|
import {
|
|
4
5
|
type GithubAgentToolId,
|
|
5
6
|
GithubAgentToolsProvider,
|
|
7
|
+
type GithubToolClient,
|
|
6
8
|
githubAgentToolCatalog,
|
|
7
9
|
githubAgentToolSelectionCatalog,
|
|
10
|
+
githubOperationRoute,
|
|
11
|
+
projectGithubOperationParameters,
|
|
8
12
|
} from '#core/agent-tools.js';
|
|
9
13
|
import {createGithubIntegrationProvider} from '#index.js';
|
|
10
14
|
|
|
@@ -200,6 +204,331 @@ const expectedCatalogRows = [
|
|
|
200
204
|
},
|
|
201
205
|
];
|
|
202
206
|
|
|
207
|
+
type GithubOperationRouteCase = {
|
|
208
|
+
toolId: GithubAgentToolId;
|
|
209
|
+
method?: string;
|
|
210
|
+
args: Record<string, unknown>;
|
|
211
|
+
expectedRoute: string;
|
|
212
|
+
runtimeInjectedProperties?: readonly string[];
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const githubOperationRouteCases = [
|
|
216
|
+
{
|
|
217
|
+
toolId: 'issue_read',
|
|
218
|
+
method: 'get',
|
|
219
|
+
args: {issue_number: 1},
|
|
220
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issues/{issue_number}',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
toolId: 'issue_read',
|
|
224
|
+
method: 'get_comments',
|
|
225
|
+
args: {issue_number: 1},
|
|
226
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issues/{issue_number}/comments',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
toolId: 'issue_read',
|
|
230
|
+
method: 'get_sub_issues',
|
|
231
|
+
args: {issue_number: 1},
|
|
232
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
toolId: 'issue_read',
|
|
236
|
+
method: 'get_parent',
|
|
237
|
+
args: {issue_number: 1},
|
|
238
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issues/{issue_number}/parent',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
toolId: 'issue_read',
|
|
242
|
+
method: 'get_labels',
|
|
243
|
+
args: {issue_number: 1},
|
|
244
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issues/{issue_number}/labels',
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
toolId: 'list_issue_types',
|
|
248
|
+
args: {owner: 'shipfox'},
|
|
249
|
+
expectedRoute: 'GET /orgs/{owner}/issue-types',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
toolId: 'list_issue_types',
|
|
253
|
+
args: {owner: 'shipfox', repo: 'platform'},
|
|
254
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issue-types',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
toolId: 'list_issues',
|
|
258
|
+
args: {},
|
|
259
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issues',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
toolId: 'search_issues',
|
|
263
|
+
args: {},
|
|
264
|
+
expectedRoute: 'GET /search/issues',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
toolId: 'add_issue_comment',
|
|
268
|
+
args: {issue_number: 1, body: 'Comment'},
|
|
269
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/issues/{issue_number}/comments',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
toolId: 'add_issue_comment',
|
|
273
|
+
args: {issue_number: 1, reaction: '+1'},
|
|
274
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/issues/{issue_number}/reactions',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
toolId: 'add_issue_comment',
|
|
278
|
+
args: {issue_number: 1, reaction: '+1', body: 'Comment'},
|
|
279
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/issues/{issue_number}/comments',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
toolId: 'add_issue_comment',
|
|
283
|
+
args: {comment_id: 1, reaction: '+1'},
|
|
284
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
toolId: 'issue_write',
|
|
288
|
+
method: 'create',
|
|
289
|
+
args: {},
|
|
290
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/issues',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
toolId: 'issue_write',
|
|
294
|
+
method: 'update',
|
|
295
|
+
args: {issue_number: 1},
|
|
296
|
+
expectedRoute: 'PATCH /repos/{owner}/{repo}/issues/{issue_number}',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
toolId: 'sub_issue_write',
|
|
300
|
+
method: 'add',
|
|
301
|
+
args: {issue_number: 1},
|
|
302
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues',
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
toolId: 'sub_issue_write',
|
|
306
|
+
method: 'remove',
|
|
307
|
+
args: {issue_number: 1, sub_issue_id: 2},
|
|
308
|
+
expectedRoute: 'DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/{sub_issue_id}',
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
toolId: 'sub_issue_write',
|
|
312
|
+
method: 'reprioritize',
|
|
313
|
+
args: {issue_number: 1, sub_issue_id: 2, after_id: 3},
|
|
314
|
+
expectedRoute: 'PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority',
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
toolId: 'pull_request_read',
|
|
318
|
+
method: 'get',
|
|
319
|
+
args: {pull_number: 1},
|
|
320
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/pulls/{pull_number}',
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
toolId: 'pull_request_read',
|
|
324
|
+
method: 'get_diff',
|
|
325
|
+
args: {pull_number: 1},
|
|
326
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/pulls/{pull_number}',
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
toolId: 'pull_request_read',
|
|
330
|
+
method: 'get_status',
|
|
331
|
+
args: {pull_number: 1, ref: 'main'},
|
|
332
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/commits/{ref}/status',
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
toolId: 'pull_request_read',
|
|
336
|
+
method: 'get_files',
|
|
337
|
+
args: {pull_number: 1},
|
|
338
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/pulls/{pull_number}/files',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
toolId: 'pull_request_read',
|
|
342
|
+
method: 'get_commits',
|
|
343
|
+
args: {pull_number: 1},
|
|
344
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/pulls/{pull_number}/commits',
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
toolId: 'pull_request_read',
|
|
348
|
+
method: 'get_review_comments',
|
|
349
|
+
args: {pull_number: 1},
|
|
350
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/pulls/{pull_number}/comments',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
toolId: 'pull_request_read',
|
|
354
|
+
method: 'get_reviews',
|
|
355
|
+
args: {pull_number: 1},
|
|
356
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
toolId: 'pull_request_read',
|
|
360
|
+
method: 'get_comments',
|
|
361
|
+
args: {pull_number: 1},
|
|
362
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/issues/{pull_number}/comments',
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
toolId: 'pull_request_read',
|
|
366
|
+
method: 'get_check_runs',
|
|
367
|
+
args: {pull_number: 1, ref: 'main'},
|
|
368
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/commits/{ref}/check-runs',
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
toolId: 'list_pull_requests',
|
|
372
|
+
args: {},
|
|
373
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/pulls',
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
toolId: 'search_pull_requests',
|
|
377
|
+
args: {},
|
|
378
|
+
expectedRoute: 'GET /search/issues',
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
toolId: 'create_pull_request',
|
|
382
|
+
args: {},
|
|
383
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/pulls',
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
toolId: 'update_pull_request',
|
|
387
|
+
args: {pull_number: 1},
|
|
388
|
+
expectedRoute: 'PATCH /repos/{owner}/{repo}/pulls/{pull_number}',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
toolId: 'add_reply_to_pull_request_comment',
|
|
392
|
+
args: {pull_number: 1, comment_id: 2, body: 'Reply'},
|
|
393
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies',
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
toolId: 'add_reply_to_pull_request_comment',
|
|
397
|
+
args: {comment_id: 2, reaction: '+1'},
|
|
398
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions',
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
toolId: 'merge_pull_request',
|
|
402
|
+
args: {pull_number: 1},
|
|
403
|
+
expectedRoute: 'PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge',
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
toolId: 'update_pull_request_branch',
|
|
407
|
+
args: {pull_number: 1},
|
|
408
|
+
expectedRoute: 'PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
toolId: 'pull_request_review_write',
|
|
412
|
+
method: 'create',
|
|
413
|
+
args: {pull_number: 1},
|
|
414
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
toolId: 'pull_request_review_write',
|
|
418
|
+
method: 'submit_pending',
|
|
419
|
+
args: {pull_number: 1},
|
|
420
|
+
runtimeInjectedProperties: ['review_id'],
|
|
421
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events',
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
toolId: 'pull_request_review_write',
|
|
425
|
+
method: 'delete_pending',
|
|
426
|
+
args: {pull_number: 1},
|
|
427
|
+
runtimeInjectedProperties: ['review_id'],
|
|
428
|
+
expectedRoute: 'DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}',
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
toolId: 'add_comment_to_pending_review',
|
|
432
|
+
args: {pull_number: 1, path: 'src/index.ts', body: 'Comment'},
|
|
433
|
+
expectedRoute: 'POST /graphql',
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
toolId: 'actions_list',
|
|
437
|
+
method: 'list_workflows',
|
|
438
|
+
args: {},
|
|
439
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/workflows',
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
toolId: 'actions_list',
|
|
443
|
+
method: 'list_workflow_runs',
|
|
444
|
+
args: {resource_id: 'workflow'},
|
|
445
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/workflows/{resource_id}/runs',
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
toolId: 'actions_list',
|
|
449
|
+
method: 'list_workflow_jobs',
|
|
450
|
+
args: {resource_id: 'run'},
|
|
451
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/runs/{resource_id}/jobs',
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
toolId: 'actions_list',
|
|
455
|
+
method: 'list_workflow_run_artifacts',
|
|
456
|
+
args: {resource_id: 'run'},
|
|
457
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/runs/{resource_id}/artifacts',
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
toolId: 'actions_get',
|
|
461
|
+
method: 'get_workflow',
|
|
462
|
+
args: {resource_id: 'workflow'},
|
|
463
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/workflows/{resource_id}',
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
toolId: 'actions_get',
|
|
467
|
+
method: 'get_workflow_run',
|
|
468
|
+
args: {resource_id: 'run'},
|
|
469
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/runs/{resource_id}',
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
toolId: 'actions_get',
|
|
473
|
+
method: 'get_workflow_job',
|
|
474
|
+
args: {resource_id: 'job'},
|
|
475
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/jobs/{resource_id}',
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
toolId: 'actions_get',
|
|
479
|
+
method: 'download_workflow_run_artifact',
|
|
480
|
+
args: {resource_id: 'artifact'},
|
|
481
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/artifacts/{resource_id}/zip',
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
toolId: 'actions_get',
|
|
485
|
+
method: 'get_workflow_run_usage',
|
|
486
|
+
args: {resource_id: 'run'},
|
|
487
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/runs/{resource_id}/timing',
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
toolId: 'actions_get',
|
|
491
|
+
method: 'get_workflow_run_logs_url',
|
|
492
|
+
args: {resource_id: 'run'},
|
|
493
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/runs/{resource_id}/logs',
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
toolId: 'actions_run_trigger',
|
|
497
|
+
method: 'run_workflow',
|
|
498
|
+
args: {workflow_id: 'ci.yml', ref: 'main'},
|
|
499
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches',
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
toolId: 'actions_run_trigger',
|
|
503
|
+
method: 'rerun_workflow_run',
|
|
504
|
+
args: {run_id: 1},
|
|
505
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun',
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
toolId: 'actions_run_trigger',
|
|
509
|
+
method: 'rerun_failed_jobs',
|
|
510
|
+
args: {run_id: 1},
|
|
511
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs',
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
toolId: 'actions_run_trigger',
|
|
515
|
+
method: 'cancel_workflow_run',
|
|
516
|
+
args: {run_id: 1},
|
|
517
|
+
expectedRoute: 'POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel',
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
toolId: 'actions_run_trigger',
|
|
521
|
+
method: 'delete_workflow_run_logs',
|
|
522
|
+
args: {run_id: 1},
|
|
523
|
+
expectedRoute: 'DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs',
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
toolId: 'get_job_logs',
|
|
527
|
+
args: {job_id: 1},
|
|
528
|
+
expectedRoute: 'GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs',
|
|
529
|
+
},
|
|
530
|
+
] satisfies readonly GithubOperationRouteCase[];
|
|
531
|
+
|
|
203
532
|
describe('github agent tool catalog', () => {
|
|
204
533
|
it('matches the GitHub MCP-style tool rows', () => {
|
|
205
534
|
const rows = githubAgentToolCatalog.map(
|
|
@@ -216,6 +545,57 @@ describe('github agent tool catalog', () => {
|
|
|
216
545
|
expect(rows).toEqual(expectedCatalogRows);
|
|
217
546
|
});
|
|
218
547
|
|
|
548
|
+
it('covers every catalog operation with an exact route case', () => {
|
|
549
|
+
const catalogOperationKeys = githubAgentToolCatalog.flatMap(
|
|
550
|
+
(entry) =>
|
|
551
|
+
entry.methods?.map((method) => operationKey(entry.id, method.id)) ?? [
|
|
552
|
+
operationKey(entry.id),
|
|
553
|
+
],
|
|
554
|
+
);
|
|
555
|
+
const routeCaseOperationKeys = githubOperationRouteCases.map(({toolId, method}) =>
|
|
556
|
+
operationKey(toolId, method),
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
expect([...new Set(routeCaseOperationKeys)].sort()).toEqual(
|
|
560
|
+
[...new Set(catalogOperationKeys)].sort(),
|
|
561
|
+
);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it.each(
|
|
565
|
+
githubOperationRouteCases,
|
|
566
|
+
)('asserts the $toolId.$method route and its input placeholders', ({
|
|
567
|
+
toolId,
|
|
568
|
+
method,
|
|
569
|
+
args,
|
|
570
|
+
expectedRoute,
|
|
571
|
+
runtimeInjectedProperties = [],
|
|
572
|
+
}) => {
|
|
573
|
+
const route = githubOperationRoute(toolId, method, args);
|
|
574
|
+
|
|
575
|
+
expect(route).toBe(expectedRoute);
|
|
576
|
+
if (route === undefined) return;
|
|
577
|
+
|
|
578
|
+
const inputProperties = new Set(Object.keys(inputSchemaFor(toolId).properties ?? {}));
|
|
579
|
+
const undeclaredArguments = Object.keys(args).filter((name) => !inputProperties.has(name));
|
|
580
|
+
expect(undeclaredArguments).toEqual([]);
|
|
581
|
+
|
|
582
|
+
const projectedParameters = projectGithubOperationParameters(toolId, method, args);
|
|
583
|
+
const injectedProperties = new Set([
|
|
584
|
+
...runtimeInjectedProperties,
|
|
585
|
+
...Object.keys(projectedParameters).filter(
|
|
586
|
+
(name) => !inputProperties.has(name) && !Object.hasOwn(args, name),
|
|
587
|
+
),
|
|
588
|
+
]);
|
|
589
|
+
const placeholders = Array.from(route.matchAll(/\{([^{}]+)\}/g), (match) => match[1]).filter(
|
|
590
|
+
(name): name is string => name !== undefined,
|
|
591
|
+
);
|
|
592
|
+
const undeclaredPlaceholders = placeholders.filter(
|
|
593
|
+
(name) => !inputProperties.has(name) && !injectedProperties.has(name),
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
expect(undeclaredPlaceholders).toEqual([]);
|
|
597
|
+
});
|
|
598
|
+
|
|
219
599
|
it('defines descriptions and schemas for every tool and method', () => {
|
|
220
600
|
const entriesMissingCatalogData = githubAgentToolCatalog.filter(
|
|
221
601
|
(entry) =>
|
|
@@ -369,6 +749,287 @@ describe('github agent tool catalog', () => {
|
|
|
369
749
|
});
|
|
370
750
|
});
|
|
371
751
|
|
|
752
|
+
it('returns artifact download metadata without buffering archive bytes', async () => {
|
|
753
|
+
const request = vi.fn(() =>
|
|
754
|
+
Promise.resolve({
|
|
755
|
+
status: 302,
|
|
756
|
+
headers: {
|
|
757
|
+
location: 'https://objects.example/artifact.zip?token=temporary',
|
|
758
|
+
'content-type': 'application/zip',
|
|
759
|
+
'content-length': '1234',
|
|
760
|
+
},
|
|
761
|
+
data: new ArrayBuffer(1024),
|
|
762
|
+
}),
|
|
763
|
+
);
|
|
764
|
+
const result = await callGithubToolWithRequest(
|
|
765
|
+
'actions_get',
|
|
766
|
+
{
|
|
767
|
+
method: 'download_workflow_run_artifact',
|
|
768
|
+
owner: 'shipfox',
|
|
769
|
+
repo: 'platform',
|
|
770
|
+
resource_id: '42',
|
|
771
|
+
},
|
|
772
|
+
request,
|
|
773
|
+
);
|
|
774
|
+
const expected = {
|
|
775
|
+
archive_format: 'zip',
|
|
776
|
+
download_url: 'https://objects.example/artifact.zip?token=temporary',
|
|
777
|
+
artifact_id: '42',
|
|
778
|
+
content_type: 'application/zip',
|
|
779
|
+
size_bytes: 1234,
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
expect(request).toHaveBeenCalledWith(
|
|
783
|
+
'GET /repos/{owner}/{repo}/actions/artifacts/{resource_id}/zip',
|
|
784
|
+
{owner: 'shipfox', repo: 'platform', resource_id: '42'},
|
|
785
|
+
);
|
|
786
|
+
expect(result).toEqual({
|
|
787
|
+
content: [{type: 'text', text: JSON.stringify(expected)}],
|
|
788
|
+
structuredContent: expected,
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('fails artifact downloads without a redirect URL instead of returning an empty success', async () => {
|
|
793
|
+
const result = await callGithubToolWithRequest(
|
|
794
|
+
'actions_get',
|
|
795
|
+
{
|
|
796
|
+
method: 'download_workflow_run_artifact',
|
|
797
|
+
owner: 'shipfox',
|
|
798
|
+
repo: 'platform',
|
|
799
|
+
resource_id: '42',
|
|
800
|
+
},
|
|
801
|
+
vi.fn(() =>
|
|
802
|
+
Promise.resolve({
|
|
803
|
+
status: 302,
|
|
804
|
+
headers: {},
|
|
805
|
+
data: new ArrayBuffer(0),
|
|
806
|
+
}),
|
|
807
|
+
),
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
expect(result).toEqual({
|
|
811
|
+
isError: true,
|
|
812
|
+
content: [{type: 'text', text: 'GitHub artifact download did not return a download URL'}],
|
|
813
|
+
});
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it('projects get_diff request headers through the provider session', async () => {
|
|
817
|
+
const request = vi.fn(() => Promise.resolve({data: 'diff --git a/file b/file'}));
|
|
818
|
+
const result = await callGithubToolWithRequest(
|
|
819
|
+
'pull_request_read',
|
|
820
|
+
{
|
|
821
|
+
method: 'get_diff',
|
|
822
|
+
owner: 'shipfox',
|
|
823
|
+
repo: 'platform',
|
|
824
|
+
pull_number: 2,
|
|
825
|
+
},
|
|
826
|
+
request,
|
|
827
|
+
);
|
|
828
|
+
|
|
829
|
+
expect(request).toHaveBeenCalledWith('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
|
|
830
|
+
owner: 'shipfox',
|
|
831
|
+
repo: 'platform',
|
|
832
|
+
pull_number: 2,
|
|
833
|
+
headers: {accept: 'application/vnd.github.diff'},
|
|
834
|
+
});
|
|
835
|
+
expect(result).toEqual({
|
|
836
|
+
content: [{type: 'text', text: '{"result":"diff --git a/file b/file"}'}],
|
|
837
|
+
structuredContent: {result: 'diff --git a/file b/file'},
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
it('projects issue comment reactions through the provider session', async () => {
|
|
842
|
+
const request = vi.fn(() => Promise.resolve({data: {id: 7}}));
|
|
843
|
+
const result = await callGithubToolWithRequest(
|
|
844
|
+
'add_issue_comment',
|
|
845
|
+
{owner: 'shipfox', repo: 'platform', issue_number: 1, reaction: '+1'},
|
|
846
|
+
request,
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
expect(request).toHaveBeenCalledWith(
|
|
850
|
+
'POST /repos/{owner}/{repo}/issues/{issue_number}/reactions',
|
|
851
|
+
{owner: 'shipfox', repo: 'platform', issue_number: 1, content: '+1'},
|
|
852
|
+
);
|
|
853
|
+
expect(result).toEqual({
|
|
854
|
+
content: [{type: 'text', text: '{"id":7}'}],
|
|
855
|
+
structuredContent: {id: 7},
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it('adds a comment to the latest pending review through GraphQL', async () => {
|
|
860
|
+
const request = vi.fn();
|
|
861
|
+
const graphql = vi
|
|
862
|
+
.fn()
|
|
863
|
+
.mockResolvedValueOnce({
|
|
864
|
+
viewer: {login: 'shipfox-ai[bot]'},
|
|
865
|
+
repository: {
|
|
866
|
+
pullRequest: {
|
|
867
|
+
reviews: {
|
|
868
|
+
nodes: [
|
|
869
|
+
{
|
|
870
|
+
id: 'review-older',
|
|
871
|
+
author: {login: 'shipfox-ai[bot]'},
|
|
872
|
+
createdAt: '2026-08-09T10:00:00Z',
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
id: 'review-latest',
|
|
876
|
+
author: {login: 'shipfox-ai[bot]'},
|
|
877
|
+
createdAt: '2026-08-09T10:01:00Z',
|
|
878
|
+
},
|
|
879
|
+
],
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
})
|
|
884
|
+
.mockResolvedValueOnce({
|
|
885
|
+
addPullRequestReviewThread: {thread: {id: 'thread-1'}},
|
|
886
|
+
});
|
|
887
|
+
const provider = createAgentToolsProvider({request, graphql});
|
|
888
|
+
const session = await provider.openSession({
|
|
889
|
+
connection: connection(),
|
|
890
|
+
tools: [pendingReviewTool()],
|
|
891
|
+
scope: undefined,
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
const result = await session.call({
|
|
895
|
+
toolId: 'add_comment_to_pending_review',
|
|
896
|
+
arguments: {
|
|
897
|
+
owner: 'shipfox',
|
|
898
|
+
repo: 'platform',
|
|
899
|
+
pull_number: 2,
|
|
900
|
+
path: 'src/agent-tools.ts',
|
|
901
|
+
body: 'Please handle this error.',
|
|
902
|
+
subject_type: 'LINE',
|
|
903
|
+
line: 42,
|
|
904
|
+
side: 'RIGHT',
|
|
905
|
+
start_line: 40,
|
|
906
|
+
start_side: 'RIGHT',
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
expect(request).not.toHaveBeenCalled();
|
|
911
|
+
expect(graphql).toHaveBeenNthCalledWith(
|
|
912
|
+
1,
|
|
913
|
+
expect.stringContaining('reviews(last: 100, states: [PENDING])'),
|
|
914
|
+
{owner: 'shipfox', repo: 'platform', pullNumber: 2},
|
|
915
|
+
);
|
|
916
|
+
expect(graphql).toHaveBeenNthCalledWith(
|
|
917
|
+
2,
|
|
918
|
+
expect.stringContaining('addPullRequestReviewThread'),
|
|
919
|
+
{
|
|
920
|
+
input: {
|
|
921
|
+
pullRequestReviewId: 'review-latest',
|
|
922
|
+
path: 'src/agent-tools.ts',
|
|
923
|
+
body: 'Please handle this error.',
|
|
924
|
+
subjectType: 'LINE',
|
|
925
|
+
line: 42,
|
|
926
|
+
side: 'RIGHT',
|
|
927
|
+
startLine: 40,
|
|
928
|
+
startSide: 'RIGHT',
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
);
|
|
932
|
+
expect(result).toEqual({
|
|
933
|
+
content: [
|
|
934
|
+
{
|
|
935
|
+
type: 'text',
|
|
936
|
+
text: '{"addPullRequestReviewThread":{"thread":{"id":"thread-1"}}}',
|
|
937
|
+
},
|
|
938
|
+
],
|
|
939
|
+
structuredContent: {addPullRequestReviewThread: {thread: {id: 'thread-1'}}},
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
it('returns an explicit error when there is no pending review for the caller', async () => {
|
|
944
|
+
const request = vi.fn();
|
|
945
|
+
const graphql = vi.fn().mockResolvedValueOnce({
|
|
946
|
+
viewer: {login: 'shipfox-ai[bot]'},
|
|
947
|
+
repository: {
|
|
948
|
+
pullRequest: {
|
|
949
|
+
reviews: {
|
|
950
|
+
nodes: [
|
|
951
|
+
{
|
|
952
|
+
id: 'review-other-user',
|
|
953
|
+
author: {login: 'another-user'},
|
|
954
|
+
createdAt: '2026-08-09T10:00:00Z',
|
|
955
|
+
},
|
|
956
|
+
],
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
},
|
|
960
|
+
});
|
|
961
|
+
const provider = createAgentToolsProvider({request, graphql});
|
|
962
|
+
const session = await provider.openSession({
|
|
963
|
+
connection: connection(),
|
|
964
|
+
tools: [pendingReviewTool()],
|
|
965
|
+
scope: undefined,
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
const result = await session.call({
|
|
969
|
+
toolId: 'add_comment_to_pending_review',
|
|
970
|
+
arguments: {
|
|
971
|
+
owner: 'shipfox',
|
|
972
|
+
repo: 'platform',
|
|
973
|
+
pull_number: 2,
|
|
974
|
+
path: 'src/agent-tools.ts',
|
|
975
|
+
body: 'Please handle this error.',
|
|
976
|
+
},
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
expect(graphql).toHaveBeenCalledTimes(1);
|
|
980
|
+
expect(request).not.toHaveBeenCalled();
|
|
981
|
+
expect(result).toEqual({
|
|
982
|
+
isError: true,
|
|
983
|
+
content: [
|
|
984
|
+
{
|
|
985
|
+
type: 'text',
|
|
986
|
+
text: 'No pending pull request review found for the authenticated GitHub user.',
|
|
987
|
+
},
|
|
988
|
+
],
|
|
989
|
+
});
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
it('maps Octokit 4xx failures to terminal provider errors', async () => {
|
|
993
|
+
const providerError = new RequestError('commit_id is missing', 422, {
|
|
994
|
+
request: {
|
|
995
|
+
method: 'POST',
|
|
996
|
+
url: 'https://api.github.com/repos/shipfox/platform/issues/1',
|
|
997
|
+
headers: {},
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
const provider = new GithubAgentToolsProvider({
|
|
1001
|
+
getInstallationByConnectionId: vi.fn(() => Promise.resolve(installation())),
|
|
1002
|
+
tokenProvider: {
|
|
1003
|
+
getInstallationAccessToken: vi.fn(() =>
|
|
1004
|
+
Promise.resolve({
|
|
1005
|
+
token: 'installation-token',
|
|
1006
|
+
expiresAt: new Date(),
|
|
1007
|
+
permissions: {issues: 'read' as const},
|
|
1008
|
+
}),
|
|
1009
|
+
),
|
|
1010
|
+
},
|
|
1011
|
+
createClient: vi.fn(() => ({
|
|
1012
|
+
request: vi.fn(() => Promise.reject(providerError)),
|
|
1013
|
+
})),
|
|
1014
|
+
});
|
|
1015
|
+
const session = await provider.openSession({
|
|
1016
|
+
connection: connection(),
|
|
1017
|
+
tools: [githubAgentToolCatalog[0]],
|
|
1018
|
+
scope: undefined,
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
await expect(
|
|
1022
|
+
session.call({
|
|
1023
|
+
toolId: 'issue_read',
|
|
1024
|
+
arguments: {method: 'get', owner: 'shipfox', repo: 'platform', issue_number: 1},
|
|
1025
|
+
}),
|
|
1026
|
+
).rejects.toMatchObject({
|
|
1027
|
+
reason: 'provider-rejected',
|
|
1028
|
+
message: 'commit_id is missing',
|
|
1029
|
+
status: 422,
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
|
|
372
1033
|
it('requires a ref for pull request status and check-run reads', async () => {
|
|
373
1034
|
const result = await callGithubTool(
|
|
374
1035
|
'pull_request_read',
|
|
@@ -382,6 +1043,173 @@ describe('github agent tool catalog', () => {
|
|
|
382
1043
|
});
|
|
383
1044
|
});
|
|
384
1045
|
|
|
1046
|
+
it.each([
|
|
1047
|
+
{
|
|
1048
|
+
method: 'submit_pending',
|
|
1049
|
+
arguments: {
|
|
1050
|
+
method: 'submit_pending',
|
|
1051
|
+
owner: 'shipfox',
|
|
1052
|
+
repo: 'platform',
|
|
1053
|
+
pull_number: 2,
|
|
1054
|
+
body: 'Please address this before merging.',
|
|
1055
|
+
event: 'REQUEST_CHANGES',
|
|
1056
|
+
},
|
|
1057
|
+
route: 'POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events',
|
|
1058
|
+
parameters: {
|
|
1059
|
+
owner: 'shipfox',
|
|
1060
|
+
repo: 'platform',
|
|
1061
|
+
pull_number: 2,
|
|
1062
|
+
body: 'Please address this before merging.',
|
|
1063
|
+
event: 'REQUEST_CHANGES',
|
|
1064
|
+
review_id: 42,
|
|
1065
|
+
},
|
|
1066
|
+
data: {id: 42, state: 'CHANGES_REQUESTED'},
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
method: 'delete_pending',
|
|
1070
|
+
arguments: {
|
|
1071
|
+
method: 'delete_pending',
|
|
1072
|
+
owner: 'shipfox',
|
|
1073
|
+
repo: 'platform',
|
|
1074
|
+
pull_number: 2,
|
|
1075
|
+
},
|
|
1076
|
+
route: 'DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}',
|
|
1077
|
+
parameters: {
|
|
1078
|
+
owner: 'shipfox',
|
|
1079
|
+
repo: 'platform',
|
|
1080
|
+
pull_number: 2,
|
|
1081
|
+
review_id: 42,
|
|
1082
|
+
},
|
|
1083
|
+
data: {id: 42, state: 'PENDING'},
|
|
1084
|
+
},
|
|
1085
|
+
] satisfies Array<{
|
|
1086
|
+
method: 'submit_pending' | 'delete_pending';
|
|
1087
|
+
arguments: Record<string, unknown>;
|
|
1088
|
+
route: string;
|
|
1089
|
+
parameters: Record<string, unknown>;
|
|
1090
|
+
data: unknown;
|
|
1091
|
+
}>)('$method resolves the latest pending review before writing', async (testCase) => {
|
|
1092
|
+
const request = vi
|
|
1093
|
+
.fn()
|
|
1094
|
+
.mockResolvedValueOnce({
|
|
1095
|
+
data: [
|
|
1096
|
+
{id: 40, state: 'APPROVED'},
|
|
1097
|
+
{id: 41, state: 'PENDING'},
|
|
1098
|
+
{id: 42, state: 'PENDING'},
|
|
1099
|
+
],
|
|
1100
|
+
})
|
|
1101
|
+
.mockResolvedValueOnce({data: testCase.data});
|
|
1102
|
+
|
|
1103
|
+
const result = await callGithubToolWithRequest(
|
|
1104
|
+
'pull_request_review_write',
|
|
1105
|
+
testCase.arguments,
|
|
1106
|
+
request,
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
expect(result).toEqual({
|
|
1110
|
+
content: [{type: 'text', text: JSON.stringify(testCase.data)}],
|
|
1111
|
+
structuredContent: testCase.data,
|
|
1112
|
+
});
|
|
1113
|
+
expect(request).toHaveBeenNthCalledWith(
|
|
1114
|
+
1,
|
|
1115
|
+
'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
|
|
1116
|
+
{
|
|
1117
|
+
owner: 'shipfox',
|
|
1118
|
+
repo: 'platform',
|
|
1119
|
+
pull_number: 2,
|
|
1120
|
+
per_page: 100,
|
|
1121
|
+
page: 1,
|
|
1122
|
+
},
|
|
1123
|
+
);
|
|
1124
|
+
expect(request).toHaveBeenNthCalledWith(2, testCase.route, testCase.parameters);
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
it('resolves a pending review from a later review page', async () => {
|
|
1128
|
+
const request = vi
|
|
1129
|
+
.fn()
|
|
1130
|
+
.mockResolvedValueOnce({
|
|
1131
|
+
data: Array.from({length: 100}, (_, index) => ({id: index + 1, state: 'APPROVED'})),
|
|
1132
|
+
})
|
|
1133
|
+
.mockResolvedValueOnce({data: [{id: 101, state: 'PENDING'}]})
|
|
1134
|
+
.mockResolvedValueOnce({data: {id: 101, state: 'COMMENTED'}});
|
|
1135
|
+
|
|
1136
|
+
const result = await callGithubToolWithRequest(
|
|
1137
|
+
'pull_request_review_write',
|
|
1138
|
+
{
|
|
1139
|
+
method: 'submit_pending',
|
|
1140
|
+
owner: 'shipfox',
|
|
1141
|
+
repo: 'platform',
|
|
1142
|
+
pull_number: 2,
|
|
1143
|
+
body: 'Looks good.',
|
|
1144
|
+
event: 'COMMENT',
|
|
1145
|
+
},
|
|
1146
|
+
request,
|
|
1147
|
+
);
|
|
1148
|
+
|
|
1149
|
+
expect(result).toEqual({
|
|
1150
|
+
content: [{type: 'text', text: JSON.stringify({id: 101, state: 'COMMENTED'})}],
|
|
1151
|
+
structuredContent: {id: 101, state: 'COMMENTED'},
|
|
1152
|
+
});
|
|
1153
|
+
expect(request).toHaveBeenNthCalledWith(
|
|
1154
|
+
1,
|
|
1155
|
+
'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
|
|
1156
|
+
{
|
|
1157
|
+
owner: 'shipfox',
|
|
1158
|
+
repo: 'platform',
|
|
1159
|
+
pull_number: 2,
|
|
1160
|
+
per_page: 100,
|
|
1161
|
+
page: 1,
|
|
1162
|
+
},
|
|
1163
|
+
);
|
|
1164
|
+
expect(request).toHaveBeenNthCalledWith(
|
|
1165
|
+
2,
|
|
1166
|
+
'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
|
|
1167
|
+
{
|
|
1168
|
+
owner: 'shipfox',
|
|
1169
|
+
repo: 'platform',
|
|
1170
|
+
pull_number: 2,
|
|
1171
|
+
per_page: 100,
|
|
1172
|
+
page: 2,
|
|
1173
|
+
},
|
|
1174
|
+
);
|
|
1175
|
+
expect(request).toHaveBeenNthCalledWith(
|
|
1176
|
+
3,
|
|
1177
|
+
'POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events',
|
|
1178
|
+
{
|
|
1179
|
+
owner: 'shipfox',
|
|
1180
|
+
repo: 'platform',
|
|
1181
|
+
pull_number: 2,
|
|
1182
|
+
body: 'Looks good.',
|
|
1183
|
+
event: 'COMMENT',
|
|
1184
|
+
review_id: 101,
|
|
1185
|
+
},
|
|
1186
|
+
);
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
it.each([
|
|
1190
|
+
'submit_pending',
|
|
1191
|
+
'delete_pending',
|
|
1192
|
+
] as const)('%s reports when no pending review exists', async (method) => {
|
|
1193
|
+
const request = vi.fn(() => Promise.resolve({data: []}));
|
|
1194
|
+
|
|
1195
|
+
const result = await callGithubToolWithRequest(
|
|
1196
|
+
'pull_request_review_write',
|
|
1197
|
+
{method, owner: 'shipfox', repo: 'platform', pull_number: 2},
|
|
1198
|
+
request,
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
expect(result).toEqual({
|
|
1202
|
+
isError: true,
|
|
1203
|
+
content: [
|
|
1204
|
+
{
|
|
1205
|
+
type: 'text',
|
|
1206
|
+
text: 'No pending pull request review found for the authenticated GitHub user.',
|
|
1207
|
+
},
|
|
1208
|
+
],
|
|
1209
|
+
});
|
|
1210
|
+
expect(request).toHaveBeenCalledOnce();
|
|
1211
|
+
});
|
|
1212
|
+
|
|
385
1213
|
it.each([
|
|
386
1214
|
{
|
|
387
1215
|
toolId: 'list_issue_types',
|
|
@@ -479,10 +1307,33 @@ async function callGithubTool(
|
|
|
479
1307
|
toolId: GithubAgentToolId,
|
|
480
1308
|
arguments_: Record<string, unknown>,
|
|
481
1309
|
data: unknown,
|
|
1310
|
+
) {
|
|
1311
|
+
return await callGithubToolWithRequest(
|
|
1312
|
+
toolId,
|
|
1313
|
+
arguments_,
|
|
1314
|
+
vi.fn(() => Promise.resolve({data})),
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
async function callGithubToolWithRequest(
|
|
1319
|
+
toolId: GithubAgentToolId,
|
|
1320
|
+
arguments_: Record<string, unknown>,
|
|
1321
|
+
request: GithubToolClient['request'],
|
|
482
1322
|
) {
|
|
483
1323
|
const tool = githubAgentToolCatalog.find((entry) => entry.id === toolId);
|
|
484
1324
|
if (!tool) throw new Error(`Missing GitHub tool: ${toolId}`);
|
|
485
|
-
const provider =
|
|
1325
|
+
const provider = createAgentToolsProvider({request});
|
|
1326
|
+
const session = await provider.openSession({
|
|
1327
|
+
connection: connection(),
|
|
1328
|
+
tools: [tool],
|
|
1329
|
+
scope: undefined,
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
return await session.call({toolId, arguments: arguments_});
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function createAgentToolsProvider(client: GithubToolClient) {
|
|
1336
|
+
return new GithubAgentToolsProvider({
|
|
486
1337
|
getInstallationByConnectionId: vi.fn(() => Promise.resolve(installation())),
|
|
487
1338
|
tokenProvider: {
|
|
488
1339
|
getInstallationAccessToken: vi.fn(() =>
|
|
@@ -498,15 +1349,14 @@ async function callGithubTool(
|
|
|
498
1349
|
}),
|
|
499
1350
|
),
|
|
500
1351
|
},
|
|
501
|
-
createClient: vi.fn(() =>
|
|
502
|
-
});
|
|
503
|
-
const session = await provider.openSession({
|
|
504
|
-
connection: connection(),
|
|
505
|
-
tools: [tool],
|
|
506
|
-
scope: undefined,
|
|
1352
|
+
createClient: vi.fn(() => client),
|
|
507
1353
|
});
|
|
1354
|
+
}
|
|
508
1355
|
|
|
509
|
-
|
|
1356
|
+
function pendingReviewTool() {
|
|
1357
|
+
const tool = githubAgentToolCatalog.find((entry) => entry.id === 'add_comment_to_pending_review');
|
|
1358
|
+
if (!tool) throw new Error('Missing add_comment_to_pending_review tool');
|
|
1359
|
+
return tool;
|
|
510
1360
|
}
|
|
511
1361
|
|
|
512
1362
|
function connection() {
|
|
@@ -575,3 +1425,7 @@ function inputSchemaFor(id: (typeof githubAgentToolCatalog)[number]['id']) {
|
|
|
575
1425
|
oneOf?: unknown[] | undefined;
|
|
576
1426
|
};
|
|
577
1427
|
}
|
|
1428
|
+
|
|
1429
|
+
function operationKey(toolId: GithubAgentToolId, method?: string) {
|
|
1430
|
+
return `${toolId}.${method ?? ''}`;
|
|
1431
|
+
}
|