@loopstack/github-oauth-example 0.3.0 → 0.4.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/README.md +3 -4
- package/dist/tools/authenticate-github-task.tool.d.ts +13 -5
- package/dist/tools/authenticate-github-task.tool.d.ts.map +1 -1
- package/dist/tools/authenticate-github-task.tool.js +17 -17
- package/dist/tools/authenticate-github-task.tool.js.map +1 -1
- package/dist/workflows/github-agent.ui.yaml +6 -16
- package/dist/workflows/github-agent.workflow.d.ts +46 -40
- package/dist/workflows/github-agent.workflow.d.ts.map +1 -1
- package/dist/workflows/github-agent.workflow.js +161 -206
- package/dist/workflows/github-agent.workflow.js.map +1 -1
- package/dist/workflows/github-repos-overview.workflow.d.ts +32 -28
- package/dist/workflows/github-repos-overview.workflow.d.ts.map +1 -1
- package/dist/workflows/github-repos-overview.workflow.js +111 -127
- package/dist/workflows/github-repos-overview.workflow.js.map +1 -1
- package/package.json +6 -7
- package/src/tools/authenticate-github-task.tool.ts +30 -18
- package/src/workflows/__tests__/github-repos-overview-workflow.spec.ts +18 -44
- package/src/workflows/github-agent.ui.yaml +6 -16
- package/src/workflows/github-agent.workflow.ts +137 -128
- package/src/workflows/github-repos-overview.workflow.ts +106 -150
- package/dist/workflows/github-repos-overview.ui.yaml +0 -17
- package/src/workflows/github-repos-overview.ui.yaml +0 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TestingModule } from '@nestjs/testing';
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { RunContext, WorkflowEntity, getBlockArgsSchema, getBlockConfig
|
|
4
|
+
import { RunContext, WorkflowEntity, getBlockArgsSchema, getBlockConfig } from '@loopstack/common';
|
|
5
5
|
import { WorkflowProcessorService } from '@loopstack/core';
|
|
6
6
|
import {
|
|
7
7
|
GitHubCreateIssueCommentTool,
|
|
@@ -35,7 +35,7 @@ import { ToolMock, createStatelessContext, createWorkflowTest } from '@loopstack
|
|
|
35
35
|
import { GitHubReposOverviewWorkflow } from '../github-repos-overview.workflow';
|
|
36
36
|
|
|
37
37
|
const mockOAuthWorkflow = {
|
|
38
|
-
run: vi.fn(),
|
|
38
|
+
run: vi.fn().mockResolvedValue({ workflowId: 'sub-1' }),
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
function applyAllGitHubToolMocks(builder: ReturnType<typeof createWorkflowTest>) {
|
|
@@ -72,7 +72,7 @@ function buildWorkflowTest() {
|
|
|
72
72
|
createWorkflowTest()
|
|
73
73
|
.forWorkflow(GitHubReposOverviewWorkflow)
|
|
74
74
|
.withImports(OAuthModule)
|
|
75
|
-
.
|
|
75
|
+
.withOverride(OAuthWorkflow, mockOAuthWorkflow),
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -130,20 +130,16 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
130
130
|
expect(getBlockConfig(workflow)).toBeDefined();
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
it('should have GitHub tools available', () => {
|
|
134
|
-
|
|
135
|
-
expect(
|
|
136
|
-
|
|
137
|
-
expect(
|
|
138
|
-
expect(
|
|
139
|
-
expect(
|
|
140
|
-
|
|
141
|
-
expect(
|
|
142
|
-
expect(
|
|
143
|
-
expect(tools).toContain('gitHubListPullRequests');
|
|
144
|
-
expect(tools).toContain('gitHubListDirectory');
|
|
145
|
-
expect(tools).toContain('gitHubListWorkflowRuns');
|
|
146
|
-
expect(tools).toContain('gitHubSearchCode');
|
|
133
|
+
it('should have GitHub tools available via constructor injection', () => {
|
|
134
|
+
expect(mockGetAuthenticatedUser).toBeDefined();
|
|
135
|
+
expect(mockListUserOrgs).toBeDefined();
|
|
136
|
+
expect(mockGetRepo).toBeDefined();
|
|
137
|
+
expect(mockListBranches).toBeDefined();
|
|
138
|
+
expect(mockListIssues).toBeDefined();
|
|
139
|
+
expect(mockListPullRequests).toBeDefined();
|
|
140
|
+
expect(mockListDirectory).toBeDefined();
|
|
141
|
+
expect(mockListWorkflowRuns).toBeDefined();
|
|
142
|
+
expect(mockSearchCode).toBeDefined();
|
|
147
143
|
});
|
|
148
144
|
});
|
|
149
145
|
|
|
@@ -330,9 +326,7 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
330
326
|
expect(result.place).toBe('end');
|
|
331
327
|
|
|
332
328
|
// Verify markdown document was created
|
|
333
|
-
expect(result.documents).toEqual(
|
|
334
|
-
expect.arrayContaining([expect.objectContaining({ className: 'MarkdownDocument' })]),
|
|
335
|
-
);
|
|
329
|
+
expect(result.documents).toEqual(expect.arrayContaining([expect.objectContaining({ documentName: 'markdown' })]));
|
|
336
330
|
|
|
337
331
|
// Verify all read tools were called
|
|
338
332
|
expect(mockGetAuthenticatedUser.call).toHaveBeenCalledTimes(1);
|
|
@@ -352,10 +346,7 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
352
346
|
|
|
353
347
|
await processor.process(workflow, args, context);
|
|
354
348
|
|
|
355
|
-
expect(mockGetRepo.call).toHaveBeenCalledWith(
|
|
356
|
-
expect.objectContaining({ owner: 'octocat', repo: 'Hello-World' }),
|
|
357
|
-
undefined,
|
|
358
|
-
);
|
|
349
|
+
expect(mockGetRepo.call).toHaveBeenCalledWith(expect.objectContaining({ owner: 'octocat', repo: 'Hello-World' }));
|
|
359
350
|
});
|
|
360
351
|
|
|
361
352
|
it('should pass owner and repo to gitHubListBranches', async () => {
|
|
@@ -366,7 +357,6 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
366
357
|
|
|
367
358
|
expect(mockListBranches.call).toHaveBeenCalledWith(
|
|
368
359
|
expect.objectContaining({ owner: 'octocat', repo: 'Hello-World' }),
|
|
369
|
-
undefined,
|
|
370
360
|
);
|
|
371
361
|
});
|
|
372
362
|
|
|
@@ -378,7 +368,6 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
378
368
|
|
|
379
369
|
expect(mockListIssues.call).toHaveBeenCalledWith(
|
|
380
370
|
expect.objectContaining({ owner: 'octocat', repo: 'Hello-World', state: 'open' }),
|
|
381
|
-
undefined,
|
|
382
371
|
);
|
|
383
372
|
});
|
|
384
373
|
|
|
@@ -390,7 +379,6 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
390
379
|
|
|
391
380
|
expect(mockListPullRequests.call).toHaveBeenCalledWith(
|
|
392
381
|
expect.objectContaining({ owner: 'octocat', repo: 'Hello-World', state: 'open' }),
|
|
393
|
-
undefined,
|
|
394
382
|
);
|
|
395
383
|
});
|
|
396
384
|
|
|
@@ -402,7 +390,6 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
402
390
|
|
|
403
391
|
expect(mockListDirectory.call).toHaveBeenCalledWith(
|
|
404
392
|
expect.objectContaining({ owner: 'octocat', repo: 'Hello-World' }),
|
|
405
|
-
undefined,
|
|
406
393
|
);
|
|
407
394
|
});
|
|
408
395
|
|
|
@@ -414,7 +401,6 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
414
401
|
|
|
415
402
|
expect(mockListWorkflowRuns.call).toHaveBeenCalledWith(
|
|
416
403
|
expect.objectContaining({ owner: 'octocat', repo: 'Hello-World' }),
|
|
417
|
-
undefined,
|
|
418
404
|
);
|
|
419
405
|
});
|
|
420
406
|
|
|
@@ -424,10 +410,7 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
424
410
|
|
|
425
411
|
await processor.process(workflow, args, context);
|
|
426
412
|
|
|
427
|
-
expect(mockSearchCode.call).toHaveBeenCalledWith(
|
|
428
|
-
expect.objectContaining({ query: 'repo:octocat/Hello-World' }),
|
|
429
|
-
undefined,
|
|
430
|
-
);
|
|
413
|
+
expect(mockSearchCode.call).toHaveBeenCalledWith(expect.objectContaining({ query: 'repo:octocat/Hello-World' }));
|
|
431
414
|
});
|
|
432
415
|
});
|
|
433
416
|
|
|
@@ -442,10 +425,6 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
442
425
|
},
|
|
443
426
|
});
|
|
444
427
|
|
|
445
|
-
mockOAuthWorkflow.run.mockResolvedValue({
|
|
446
|
-
workflowId: 'test-workflow-id',
|
|
447
|
-
});
|
|
448
|
-
|
|
449
428
|
const result = await processor.process(workflow, { owner: 'octocat', repo: 'Hello-World' }, context);
|
|
450
429
|
|
|
451
430
|
expect(result).toBeDefined();
|
|
@@ -457,10 +436,7 @@ describe('GitHubReposOverviewWorkflow', () => {
|
|
|
457
436
|
expect(mockOAuthWorkflow.run).toHaveBeenCalledTimes(1);
|
|
458
437
|
expect(mockOAuthWorkflow.run).toHaveBeenCalledWith(
|
|
459
438
|
{ provider: 'github', scopes: ['repo', 'read:org', 'workflow'] },
|
|
460
|
-
|
|
461
|
-
alias: 'oAuth',
|
|
462
|
-
callback: { transition: 'authCompleted' },
|
|
463
|
-
}),
|
|
439
|
+
{ callback: { transition: 'authCompleted' } },
|
|
464
440
|
);
|
|
465
441
|
|
|
466
442
|
// Subsequent tools should NOT be called
|
|
@@ -570,9 +546,7 @@ describe('GitHubReposOverviewWorkflow with existing entity', () => {
|
|
|
570
546
|
expect(result.place).toBe('end');
|
|
571
547
|
|
|
572
548
|
// Verify markdown document was created after auth resume
|
|
573
|
-
expect(result.documents).toEqual(
|
|
574
|
-
expect.arrayContaining([expect.objectContaining({ className: 'MarkdownDocument' })]),
|
|
575
|
-
);
|
|
549
|
+
expect(result.documents).toEqual(expect.arrayContaining([expect.objectContaining({ documentName: 'markdown' })]));
|
|
576
550
|
|
|
577
551
|
expect(mockGetAuthenticatedUser.call).toHaveBeenCalledTimes(1);
|
|
578
552
|
expect(mockGetRepo.call).toHaveBeenCalledTimes(1);
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
the agent detects unauthorized errors and launches authentication on its own.
|
|
8
|
-
|
|
9
|
-
ui:
|
|
10
|
-
widgets:
|
|
11
|
-
- widget: prompt-input
|
|
12
|
-
enabledWhen:
|
|
13
|
-
- waiting_for_user
|
|
14
|
-
options:
|
|
15
|
-
transition: userMessage
|
|
16
|
-
label: Send Message
|
|
1
|
+
widget: prompt-input
|
|
2
|
+
enabledWhen:
|
|
3
|
+
- waiting_for_user
|
|
4
|
+
options:
|
|
5
|
+
transition: userMessage
|
|
6
|
+
label: Send Message
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
+
import { Inject } from '@nestjs/common';
|
|
1
2
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
Guard,
|
|
5
|
-
Initial,
|
|
6
|
-
InjectTool,
|
|
7
|
-
InjectWorkflow,
|
|
8
|
-
ToolResult,
|
|
9
|
-
Transition,
|
|
10
|
-
Workflow,
|
|
11
|
-
} from '@loopstack/common';
|
|
3
|
+
import { BaseWorkflow, Guard, TEMPLATE_RENDERER, Transition, Workflow } from '@loopstack/common';
|
|
4
|
+
import type { TemplateRenderFn } from '@loopstack/common';
|
|
12
5
|
import {
|
|
13
6
|
GitHubCreateIssueCommentTool,
|
|
14
7
|
GitHubCreateIssueTool,
|
|
@@ -46,98 +39,66 @@ import {
|
|
|
46
39
|
import { OAuthWorkflow } from '@loopstack/oauth-module';
|
|
47
40
|
import { AuthenticateGitHubTask } from '../tools/authenticate-github-task.tool';
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
uiConfig: __dirname + '/github-agent.ui.yaml',
|
|
51
|
-
})
|
|
52
|
-
export class GitHubAgentWorkflow extends BaseWorkflow {
|
|
53
|
-
@InjectTool({
|
|
54
|
-
provider: 'claude',
|
|
55
|
-
model: 'claude-sonnet-4-6',
|
|
56
|
-
system: `You are a helpful GitHub assistant with access to repository, issue, PR, code, actions,
|
|
57
|
-
and search tools. When a tool returns an unauthorized error, use authenticateGitHub
|
|
58
|
-
to let the user sign in, then retry. Be concise and format results using markdown.`,
|
|
59
|
-
tools: [
|
|
60
|
-
'gitHubListRepos',
|
|
61
|
-
'gitHubGetRepo',
|
|
62
|
-
'gitHubCreateRepo',
|
|
63
|
-
'gitHubListBranches',
|
|
64
|
-
'gitHubListIssues',
|
|
65
|
-
'gitHubGetIssue',
|
|
66
|
-
'gitHubCreateIssue',
|
|
67
|
-
'gitHubCreateIssueComment',
|
|
68
|
-
'gitHubListPullRequests',
|
|
69
|
-
'gitHubGetPullRequest',
|
|
70
|
-
'gitHubCreatePullRequest',
|
|
71
|
-
'gitHubMergePullRequest',
|
|
72
|
-
'gitHubListPrReviews',
|
|
73
|
-
'gitHubGetFileContent',
|
|
74
|
-
'gitHubCreateOrUpdateFile',
|
|
75
|
-
'gitHubListDirectory',
|
|
76
|
-
'gitHubGetCommit',
|
|
77
|
-
'gitHubListWorkflowRuns',
|
|
78
|
-
'gitHubTriggerWorkflow',
|
|
79
|
-
'gitHubGetWorkflowRun',
|
|
80
|
-
'gitHubSearchCode',
|
|
81
|
-
'gitHubSearchRepos',
|
|
82
|
-
'gitHubSearchIssues',
|
|
83
|
-
'gitHubGetAuthenticatedUser',
|
|
84
|
-
'gitHubListUserOrgs',
|
|
85
|
-
'authenticateGitHub',
|
|
86
|
-
],
|
|
87
|
-
})
|
|
88
|
-
llmGenerateText: LlmGenerateTextTool;
|
|
89
|
-
@InjectTool({ provider: 'claude' }) llmDelegateToolCalls: LlmDelegateToolCallsTool;
|
|
90
|
-
@InjectTool({ provider: 'claude' }) llmUpdateToolResult: LlmUpdateToolResultTool;
|
|
91
|
-
@InjectTool() authenticateGitHub: AuthenticateGitHubTask;
|
|
92
|
-
|
|
93
|
-
// GitHub Repos tools
|
|
94
|
-
@InjectTool() gitHubListRepos: GitHubListReposTool;
|
|
95
|
-
@InjectTool() gitHubGetRepo: GitHubGetRepoTool;
|
|
96
|
-
@InjectTool() gitHubCreateRepo: GitHubCreateRepoTool;
|
|
97
|
-
@InjectTool() gitHubListBranches: GitHubListBranchesTool;
|
|
98
|
-
|
|
99
|
-
// GitHub Issues tools
|
|
100
|
-
@InjectTool() gitHubListIssues: GitHubListIssuesTool;
|
|
101
|
-
@InjectTool() gitHubGetIssue: GitHubGetIssueTool;
|
|
102
|
-
@InjectTool() gitHubCreateIssue: GitHubCreateIssueTool;
|
|
103
|
-
@InjectTool() gitHubCreateIssueComment: GitHubCreateIssueCommentTool;
|
|
104
|
-
|
|
105
|
-
// GitHub Pull Requests tools
|
|
106
|
-
@InjectTool() gitHubListPullRequests: GitHubListPullRequestsTool;
|
|
107
|
-
@InjectTool() gitHubGetPullRequest: GitHubGetPullRequestTool;
|
|
108
|
-
@InjectTool() gitHubCreatePullRequest: GitHubCreatePullRequestTool;
|
|
109
|
-
@InjectTool() gitHubMergePullRequest: GitHubMergePullRequestTool;
|
|
110
|
-
@InjectTool() gitHubListPrReviews: GitHubListPrReviewsTool;
|
|
111
|
-
|
|
112
|
-
// GitHub Content / Git Ops tools
|
|
113
|
-
@InjectTool() gitHubGetFileContent: GitHubGetFileContentTool;
|
|
114
|
-
@InjectTool() gitHubCreateOrUpdateFile: GitHubCreateOrUpdateFileTool;
|
|
115
|
-
@InjectTool() gitHubListDirectory: GitHubListDirectoryTool;
|
|
116
|
-
@InjectTool() gitHubGetCommit: GitHubGetCommitTool;
|
|
117
|
-
|
|
118
|
-
// GitHub Actions tools
|
|
119
|
-
@InjectTool() gitHubListWorkflowRuns: GitHubListWorkflowRunsTool;
|
|
120
|
-
@InjectTool() gitHubTriggerWorkflow: GitHubTriggerWorkflowTool;
|
|
121
|
-
@InjectTool() gitHubGetWorkflowRun: GitHubGetWorkflowRunTool;
|
|
122
|
-
|
|
123
|
-
// GitHub Search tools
|
|
124
|
-
@InjectTool() gitHubSearchCode: GitHubSearchCodeTool;
|
|
125
|
-
@InjectTool() gitHubSearchRepos: GitHubSearchReposTool;
|
|
126
|
-
@InjectTool() gitHubSearchIssues: GitHubSearchIssuesTool;
|
|
127
|
-
|
|
128
|
-
// GitHub Users & Orgs tools
|
|
129
|
-
@InjectTool() gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool;
|
|
130
|
-
@InjectTool() gitHubListUserOrgs: GitHubListUserOrgsTool;
|
|
131
|
-
|
|
132
|
-
@InjectWorkflow() oAuth: OAuthWorkflow;
|
|
133
|
-
|
|
42
|
+
interface GitHubAgentState {
|
|
134
43
|
llmResult?: LlmGenerateTextResult;
|
|
135
44
|
llmMeta?: LlmResultMeta;
|
|
136
45
|
delegateResult?: LlmDelegateResult;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Workflow({
|
|
49
|
+
title: 'GitHub Agent',
|
|
50
|
+
description:
|
|
51
|
+
'An interactive chat agent with access to GitHub.\nAsk it to manage repositories, issues, pull requests, browse code, check CI/CD status,\nsearch across GitHub, and more. Handles OAuth authentication automatically when needed —\nthe agent detects unauthorized errors and launches authentication on its own.',
|
|
52
|
+
name: 'github_agent',
|
|
53
|
+
widget: __dirname + '/github-agent.ui.yaml',
|
|
54
|
+
})
|
|
55
|
+
export class GitHubAgentWorkflow extends BaseWorkflow<Record<string, unknown>, GitHubAgentState> {
|
|
56
|
+
constructor(
|
|
57
|
+
private readonly llmGenerateText: LlmGenerateTextTool,
|
|
58
|
+
private readonly llmDelegateToolCalls: LlmDelegateToolCallsTool,
|
|
59
|
+
private readonly llmUpdateToolResult: LlmUpdateToolResultTool,
|
|
60
|
+
private readonly authenticateGitHub: AuthenticateGitHubTask,
|
|
61
|
+
// GitHub Repos tools
|
|
62
|
+
private readonly gitHubListRepos: GitHubListReposTool,
|
|
63
|
+
private readonly gitHubGetRepo: GitHubGetRepoTool,
|
|
64
|
+
private readonly gitHubCreateRepo: GitHubCreateRepoTool,
|
|
65
|
+
private readonly gitHubListBranches: GitHubListBranchesTool,
|
|
66
|
+
// GitHub Issues tools
|
|
67
|
+
private readonly gitHubListIssues: GitHubListIssuesTool,
|
|
68
|
+
private readonly gitHubGetIssue: GitHubGetIssueTool,
|
|
69
|
+
private readonly gitHubCreateIssue: GitHubCreateIssueTool,
|
|
70
|
+
private readonly gitHubCreateIssueComment: GitHubCreateIssueCommentTool,
|
|
71
|
+
// GitHub Pull Requests tools
|
|
72
|
+
private readonly gitHubListPullRequests: GitHubListPullRequestsTool,
|
|
73
|
+
private readonly gitHubGetPullRequest: GitHubGetPullRequestTool,
|
|
74
|
+
private readonly gitHubCreatePullRequest: GitHubCreatePullRequestTool,
|
|
75
|
+
private readonly gitHubMergePullRequest: GitHubMergePullRequestTool,
|
|
76
|
+
private readonly gitHubListPrReviews: GitHubListPrReviewsTool,
|
|
77
|
+
// GitHub Content / Git Ops tools
|
|
78
|
+
private readonly gitHubGetFileContent: GitHubGetFileContentTool,
|
|
79
|
+
private readonly gitHubCreateOrUpdateFile: GitHubCreateOrUpdateFileTool,
|
|
80
|
+
private readonly gitHubListDirectory: GitHubListDirectoryTool,
|
|
81
|
+
private readonly gitHubGetCommit: GitHubGetCommitTool,
|
|
82
|
+
// GitHub Actions tools
|
|
83
|
+
private readonly gitHubListWorkflowRuns: GitHubListWorkflowRunsTool,
|
|
84
|
+
private readonly gitHubTriggerWorkflow: GitHubTriggerWorkflowTool,
|
|
85
|
+
private readonly gitHubGetWorkflowRun: GitHubGetWorkflowRunTool,
|
|
86
|
+
// GitHub Search tools
|
|
87
|
+
private readonly gitHubSearchCode: GitHubSearchCodeTool,
|
|
88
|
+
private readonly gitHubSearchRepos: GitHubSearchReposTool,
|
|
89
|
+
private readonly gitHubSearchIssues: GitHubSearchIssuesTool,
|
|
90
|
+
// GitHub Users & Orgs tools
|
|
91
|
+
private readonly gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool,
|
|
92
|
+
private readonly gitHubListUserOrgs: GitHubListUserOrgsTool,
|
|
93
|
+
private readonly oAuth: OAuthWorkflow,
|
|
94
|
+
@Inject(TEMPLATE_RENDERER) private readonly render: TemplateRenderFn,
|
|
95
|
+
) {
|
|
96
|
+
super();
|
|
97
|
+
}
|
|
137
98
|
|
|
138
|
-
@
|
|
139
|
-
async setup() {
|
|
140
|
-
await this.
|
|
99
|
+
@Transition({ to: 'waiting_for_user' })
|
|
100
|
+
async setup(state: GitHubAgentState): Promise<GitHubAgentState> {
|
|
101
|
+
await this.documentStore.save(
|
|
141
102
|
LlmMessageDocument,
|
|
142
103
|
{
|
|
143
104
|
role: 'user',
|
|
@@ -145,71 +106,119 @@ to let the user sign in, then retry. Be concise and format results using markdow
|
|
|
145
106
|
},
|
|
146
107
|
{ meta: { hidden: true } },
|
|
147
108
|
);
|
|
109
|
+
return state;
|
|
148
110
|
}
|
|
149
111
|
|
|
150
112
|
@Transition({ from: 'waiting_for_user', to: 'ready', wait: true, schema: z.string() })
|
|
151
|
-
async userMessage(payload: string) {
|
|
152
|
-
await this.
|
|
113
|
+
async userMessage(state: GitHubAgentState, payload: string): Promise<GitHubAgentState> {
|
|
114
|
+
await this.documentStore.save(LlmMessageDocument, {
|
|
153
115
|
role: 'user',
|
|
154
116
|
content: payload,
|
|
155
117
|
});
|
|
118
|
+
return state;
|
|
156
119
|
}
|
|
157
120
|
|
|
158
121
|
@Transition({ from: 'ready', to: 'prompt_executed' })
|
|
159
|
-
async llmTurn() {
|
|
160
|
-
const result
|
|
161
|
-
|
|
162
|
-
|
|
122
|
+
async llmTurn(state: GitHubAgentState): Promise<GitHubAgentState> {
|
|
123
|
+
const result = await this.llmGenerateText.call(
|
|
124
|
+
{},
|
|
125
|
+
{
|
|
126
|
+
config: {
|
|
127
|
+
provider: 'claude',
|
|
128
|
+
model: 'claude-sonnet-4-6',
|
|
129
|
+
system: `You are a helpful GitHub assistant with access to repository, issue, PR, code, actions,
|
|
130
|
+
and search tools. When a tool returns an unauthorized error, use authenticateGitHub
|
|
131
|
+
to let the user sign in, then retry. Be concise and format results using markdown.`,
|
|
132
|
+
tools: [
|
|
133
|
+
'github_list_repos',
|
|
134
|
+
'github_get_repo',
|
|
135
|
+
'github_create_repo',
|
|
136
|
+
'github_list_branches',
|
|
137
|
+
'github_list_issues',
|
|
138
|
+
'github_get_issue',
|
|
139
|
+
'github_create_issue',
|
|
140
|
+
'github_create_issue_comment',
|
|
141
|
+
'github_list_pull_requests',
|
|
142
|
+
'github_get_pull_request',
|
|
143
|
+
'github_create_pull_request',
|
|
144
|
+
'github_merge_pull_request',
|
|
145
|
+
'github_list_pr_reviews',
|
|
146
|
+
'github_get_file_content',
|
|
147
|
+
'github_create_or_update_file',
|
|
148
|
+
'github_list_directory',
|
|
149
|
+
'github_get_commit',
|
|
150
|
+
'github_list_workflow_runs',
|
|
151
|
+
'github_trigger_workflow',
|
|
152
|
+
'github_get_workflow_run',
|
|
153
|
+
'github_search_code',
|
|
154
|
+
'github_search_repos',
|
|
155
|
+
'github_search_issues',
|
|
156
|
+
'github_get_authenticated_user',
|
|
157
|
+
'github_list_user_orgs',
|
|
158
|
+
'authenticate_github',
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
return { ...state, llmResult: result.data, llmMeta: result.metadata as LlmResultMeta | undefined };
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
@Transition({ from: 'prompt_executed', to: 'awaiting_tools', priority: 10 })
|
|
166
167
|
@Guard('hasToolCalls')
|
|
167
|
-
async executeToolCalls() {
|
|
168
|
-
await this.
|
|
169
|
-
meta: { response:
|
|
168
|
+
async executeToolCalls(state: GitHubAgentState): Promise<GitHubAgentState> {
|
|
169
|
+
await this.documentStore.save(LlmMessageDocument, state.llmResult!.message, {
|
|
170
|
+
meta: { response: state.llmResult!.response, provider: state.llmMeta!.provider },
|
|
170
171
|
});
|
|
171
|
-
const result
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
const result = await this.llmDelegateToolCalls.call(
|
|
173
|
+
{
|
|
174
|
+
message: state.llmResult!.message,
|
|
175
|
+
callback: { transition: 'toolResultReceived' },
|
|
176
|
+
},
|
|
177
|
+
{ config: { provider: 'claude' } },
|
|
178
|
+
);
|
|
179
|
+
return { ...state, delegateResult: result.data };
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
hasToolCalls(): boolean {
|
|
179
|
-
return
|
|
182
|
+
hasToolCalls(state: GitHubAgentState): boolean {
|
|
183
|
+
return state.llmResult?.message.stopReason === 'tool_use';
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
@Transition({ from: 'awaiting_tools', to: 'awaiting_tools', wait: true, schema: z.record(z.string(), z.unknown()) })
|
|
183
|
-
async toolResultReceived(payload: Record<string, unknown>) {
|
|
184
|
-
const result
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
async toolResultReceived(state: GitHubAgentState, payload: Record<string, unknown>): Promise<GitHubAgentState> {
|
|
188
|
+
const result = await this.llmUpdateToolResult.call(
|
|
189
|
+
{
|
|
190
|
+
delegateResult: state.delegateResult!,
|
|
191
|
+
completedTool: payload,
|
|
192
|
+
},
|
|
193
|
+
{ config: { provider: 'claude' } },
|
|
194
|
+
);
|
|
195
|
+
return { ...state, delegateResult: result.data };
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
@Transition({ from: 'awaiting_tools', to: 'ready' })
|
|
192
199
|
@Guard('allToolsComplete')
|
|
193
|
-
async allToolsCompleteTransition() {
|
|
194
|
-
await this.
|
|
200
|
+
async allToolsCompleteTransition(state: GitHubAgentState): Promise<GitHubAgentState> {
|
|
201
|
+
await this.documentStore.save(LlmMessageDocument, {
|
|
195
202
|
role: 'user',
|
|
196
|
-
content:
|
|
203
|
+
content: state.delegateResult!.toolResults.map((tr) => ({
|
|
197
204
|
type: 'tool_result' as const,
|
|
198
205
|
toolCallId: tr.toolCallId,
|
|
199
206
|
content: tr.content ?? '',
|
|
200
207
|
isError: tr.isError ?? false,
|
|
201
208
|
})),
|
|
202
209
|
});
|
|
210
|
+
return state;
|
|
203
211
|
}
|
|
204
212
|
|
|
205
|
-
allToolsComplete(): boolean {
|
|
206
|
-
return
|
|
213
|
+
allToolsComplete(state: GitHubAgentState): boolean {
|
|
214
|
+
return state.delegateResult?.allCompleted ?? false;
|
|
207
215
|
}
|
|
208
216
|
|
|
209
217
|
@Transition({ from: 'prompt_executed', to: 'waiting_for_user' })
|
|
210
|
-
async respond() {
|
|
211
|
-
await this.
|
|
212
|
-
meta: { response:
|
|
218
|
+
async respond(state: GitHubAgentState): Promise<GitHubAgentState> {
|
|
219
|
+
await this.documentStore.save(LlmMessageDocument, state.llmResult!.message, {
|
|
220
|
+
meta: { response: state.llmResult!.response, provider: state.llmMeta!.provider },
|
|
213
221
|
});
|
|
222
|
+
return state;
|
|
214
223
|
}
|
|
215
224
|
}
|