@loopstack/github-oauth-example 0.3.1 → 0.4.1

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.
@@ -1,14 +1,5 @@
1
1
  import { z } from 'zod';
2
- import {
3
- BaseWorkflow,
4
- Guard,
5
- Initial,
6
- InjectTool,
7
- InjectWorkflow,
8
- ToolResult,
9
- Transition,
10
- Workflow,
11
- } from '@loopstack/common';
2
+ import { BaseWorkflow, Guard, Transition, Workflow } from '@loopstack/common';
12
3
  import {
13
4
  GitHubCreateIssueCommentTool,
14
5
  GitHubCreateIssueTool,
@@ -46,98 +37,65 @@ import {
46
37
  import { OAuthWorkflow } from '@loopstack/oauth-module';
47
38
  import { AuthenticateGitHubTask } from '../tools/authenticate-github-task.tool';
48
39
 
49
- @Workflow({
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
-
40
+ interface GitHubAgentState {
134
41
  llmResult?: LlmGenerateTextResult;
135
42
  llmMeta?: LlmResultMeta;
136
43
  delegateResult?: LlmDelegateResult;
44
+ }
45
+
46
+ @Workflow({
47
+ title: 'GitHub Agent',
48
+ description:
49
+ '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.',
50
+ name: 'github_agent',
51
+ widget: __dirname + '/github-agent.ui.yaml',
52
+ })
53
+ export class GitHubAgentWorkflow extends BaseWorkflow<Record<string, unknown>, GitHubAgentState> {
54
+ constructor(
55
+ private readonly llmGenerateText: LlmGenerateTextTool,
56
+ private readonly llmDelegateToolCalls: LlmDelegateToolCallsTool,
57
+ private readonly llmUpdateToolResult: LlmUpdateToolResultTool,
58
+ private readonly authenticateGitHub: AuthenticateGitHubTask,
59
+ // GitHub Repos tools
60
+ private readonly gitHubListRepos: GitHubListReposTool,
61
+ private readonly gitHubGetRepo: GitHubGetRepoTool,
62
+ private readonly gitHubCreateRepo: GitHubCreateRepoTool,
63
+ private readonly gitHubListBranches: GitHubListBranchesTool,
64
+ // GitHub Issues tools
65
+ private readonly gitHubListIssues: GitHubListIssuesTool,
66
+ private readonly gitHubGetIssue: GitHubGetIssueTool,
67
+ private readonly gitHubCreateIssue: GitHubCreateIssueTool,
68
+ private readonly gitHubCreateIssueComment: GitHubCreateIssueCommentTool,
69
+ // GitHub Pull Requests tools
70
+ private readonly gitHubListPullRequests: GitHubListPullRequestsTool,
71
+ private readonly gitHubGetPullRequest: GitHubGetPullRequestTool,
72
+ private readonly gitHubCreatePullRequest: GitHubCreatePullRequestTool,
73
+ private readonly gitHubMergePullRequest: GitHubMergePullRequestTool,
74
+ private readonly gitHubListPrReviews: GitHubListPrReviewsTool,
75
+ // GitHub Content / Git Ops tools
76
+ private readonly gitHubGetFileContent: GitHubGetFileContentTool,
77
+ private readonly gitHubCreateOrUpdateFile: GitHubCreateOrUpdateFileTool,
78
+ private readonly gitHubListDirectory: GitHubListDirectoryTool,
79
+ private readonly gitHubGetCommit: GitHubGetCommitTool,
80
+ // GitHub Actions tools
81
+ private readonly gitHubListWorkflowRuns: GitHubListWorkflowRunsTool,
82
+ private readonly gitHubTriggerWorkflow: GitHubTriggerWorkflowTool,
83
+ private readonly gitHubGetWorkflowRun: GitHubGetWorkflowRunTool,
84
+ // GitHub Search tools
85
+ private readonly gitHubSearchCode: GitHubSearchCodeTool,
86
+ private readonly gitHubSearchRepos: GitHubSearchReposTool,
87
+ private readonly gitHubSearchIssues: GitHubSearchIssuesTool,
88
+ // GitHub Users & Orgs tools
89
+ private readonly gitHubGetAuthenticatedUser: GitHubGetAuthenticatedUserTool,
90
+ private readonly gitHubListUserOrgs: GitHubListUserOrgsTool,
91
+ private readonly oAuth: OAuthWorkflow,
92
+ ) {
93
+ super();
94
+ }
137
95
 
138
- @Initial({ to: 'waiting_for_user' })
139
- async setup() {
140
- await this.repository.save(
96
+ @Transition({ to: 'waiting_for_user' })
97
+ async setup(state: GitHubAgentState): Promise<GitHubAgentState> {
98
+ await this.documentStore.save(
141
99
  LlmMessageDocument,
142
100
  {
143
101
  role: 'user',
@@ -145,71 +103,119 @@ to let the user sign in, then retry. Be concise and format results using markdow
145
103
  },
146
104
  { meta: { hidden: true } },
147
105
  );
106
+ return state;
148
107
  }
149
108
 
150
109
  @Transition({ from: 'waiting_for_user', to: 'ready', wait: true, schema: z.string() })
151
- async userMessage(payload: string) {
152
- await this.repository.save(LlmMessageDocument, {
110
+ async userMessage(state: GitHubAgentState, payload: string): Promise<GitHubAgentState> {
111
+ await this.documentStore.save(LlmMessageDocument, {
153
112
  role: 'user',
154
113
  content: payload,
155
114
  });
115
+ return state;
156
116
  }
157
117
 
158
118
  @Transition({ from: 'ready', to: 'prompt_executed' })
159
- async llmTurn() {
160
- const result: ToolResult<LlmGenerateTextResult, LlmResultMeta> = await this.llmGenerateText.call();
161
- this.llmResult = result.data;
162
- this.llmMeta = result.metadata;
119
+ async llmTurn(state: GitHubAgentState): Promise<GitHubAgentState> {
120
+ const result = await this.llmGenerateText.call(
121
+ {},
122
+ {
123
+ config: {
124
+ provider: 'claude',
125
+ model: 'claude-sonnet-4-6',
126
+ system: `You are a helpful GitHub assistant with access to repository, issue, PR, code, actions,
127
+ and search tools. When a tool returns an unauthorized error, use authenticateGitHub
128
+ to let the user sign in, then retry. Be concise and format results using markdown.`,
129
+ tools: [
130
+ 'github_list_repos',
131
+ 'github_get_repo',
132
+ 'github_create_repo',
133
+ 'github_list_branches',
134
+ 'github_list_issues',
135
+ 'github_get_issue',
136
+ 'github_create_issue',
137
+ 'github_create_issue_comment',
138
+ 'github_list_pull_requests',
139
+ 'github_get_pull_request',
140
+ 'github_create_pull_request',
141
+ 'github_merge_pull_request',
142
+ 'github_list_pr_reviews',
143
+ 'github_get_file_content',
144
+ 'github_create_or_update_file',
145
+ 'github_list_directory',
146
+ 'github_get_commit',
147
+ 'github_list_workflow_runs',
148
+ 'github_trigger_workflow',
149
+ 'github_get_workflow_run',
150
+ 'github_search_code',
151
+ 'github_search_repos',
152
+ 'github_search_issues',
153
+ 'github_get_authenticated_user',
154
+ 'github_list_user_orgs',
155
+ 'authenticate_github',
156
+ ],
157
+ },
158
+ },
159
+ );
160
+ return { ...state, llmResult: result.data, llmMeta: result.metadata as LlmResultMeta | undefined };
163
161
  }
164
162
 
165
163
  @Transition({ from: 'prompt_executed', to: 'awaiting_tools', priority: 10 })
166
164
  @Guard('hasToolCalls')
167
- async executeToolCalls() {
168
- await this.repository.save(LlmMessageDocument, this.llmResult!.message, {
169
- meta: { response: this.llmResult!.response, provider: this.llmMeta!.provider },
165
+ async executeToolCalls(state: GitHubAgentState): Promise<GitHubAgentState> {
166
+ await this.documentStore.save(LlmMessageDocument, state.llmResult!.message, {
167
+ meta: { response: state.llmResult!.response, provider: state.llmMeta!.provider },
170
168
  });
171
- const result: ToolResult<LlmDelegateResult> = await this.llmDelegateToolCalls.call({
172
- message: this.llmResult!.message,
173
- callback: { transition: 'toolResultReceived' },
174
- });
175
- this.delegateResult = result.data;
169
+ const result = await this.llmDelegateToolCalls.call(
170
+ {
171
+ message: state.llmResult!.message,
172
+ callback: { transition: 'toolResultReceived' },
173
+ },
174
+ { config: { provider: 'claude' } },
175
+ );
176
+ return { ...state, delegateResult: result.data };
176
177
  }
177
178
 
178
- hasToolCalls(): boolean {
179
- return this.llmResult?.message.stopReason === 'tool_use';
179
+ hasToolCalls(state: GitHubAgentState): boolean {
180
+ return state.llmResult?.message.stopReason === 'tool_use';
180
181
  }
181
182
 
182
183
  @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: ToolResult<LlmDelegateResult> = await this.llmUpdateToolResult.call({
185
- delegateResult: this.delegateResult!,
186
- completedTool: payload,
187
- });
188
- this.delegateResult = result.data;
184
+ async toolResultReceived(state: GitHubAgentState, payload: Record<string, unknown>): Promise<GitHubAgentState> {
185
+ const result = await this.llmUpdateToolResult.call(
186
+ {
187
+ delegateResult: state.delegateResult!,
188
+ completedTool: payload,
189
+ },
190
+ { config: { provider: 'claude' } },
191
+ );
192
+ return { ...state, delegateResult: result.data };
189
193
  }
190
194
 
191
195
  @Transition({ from: 'awaiting_tools', to: 'ready' })
192
196
  @Guard('allToolsComplete')
193
- async allToolsCompleteTransition() {
194
- await this.repository.save(LlmMessageDocument, {
197
+ async allToolsCompleteTransition(state: GitHubAgentState): Promise<GitHubAgentState> {
198
+ await this.documentStore.save(LlmMessageDocument, {
195
199
  role: 'user',
196
- content: this.delegateResult!.toolResults.map((tr) => ({
200
+ content: state.delegateResult!.toolResults.map((tr) => ({
197
201
  type: 'tool_result' as const,
198
202
  toolCallId: tr.toolCallId,
199
203
  content: tr.content ?? '',
200
204
  isError: tr.isError ?? false,
201
205
  })),
202
206
  });
207
+ return state;
203
208
  }
204
209
 
205
- allToolsComplete(): boolean {
206
- return this.delegateResult?.allCompleted ?? false;
210
+ allToolsComplete(state: GitHubAgentState): boolean {
211
+ return state.delegateResult?.allCompleted ?? false;
207
212
  }
208
213
 
209
214
  @Transition({ from: 'prompt_executed', to: 'waiting_for_user' })
210
- async respond() {
211
- await this.repository.save(LlmMessageDocument, this.llmResult!.message, {
212
- meta: { response: this.llmResult!.response, provider: this.llmMeta!.provider },
215
+ async respond(state: GitHubAgentState): Promise<GitHubAgentState> {
216
+ await this.documentStore.save(LlmMessageDocument, state.llmResult!.message, {
217
+ meta: { response: state.llmResult!.response, provider: state.llmMeta!.provider },
213
218
  });
219
+ return state;
214
220
  }
215
221
  }