@shipfox/api-integration-github 12.6.0 → 13.1.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.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 31 files with swc (490.83ms)
2
+ Successfully compiled: 32 files with swc (434.7ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @shipfox/api-integration-github
2
2
 
3
+ ## 13.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7ae5396: Attributes new runner commits to the GitHub App bot account. Existing runner workspaces keep their first configured author until recreated. Update downstream author filters that match the previous App ID email address.
8
+
9
+ ## 12.7.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 4639dd9: Add GitHub pull request review-thread inspection and resolution tools.
14
+
3
15
  ## 12.6.0
4
16
 
5
17
  ### Patch Changes
@@ -42,7 +42,17 @@ export interface GithubUserInstallationPage {
42
42
  installationIds: number[];
43
43
  nextCursor: string | null;
44
44
  }
45
- export interface GithubApiClient {
45
+ export interface GithubBotUser {
46
+ id: number;
47
+ login: string;
48
+ }
49
+ export interface GithubBotUserClient {
50
+ getBotUser(input: {
51
+ username: string;
52
+ installationAccessToken: string;
53
+ }): Promise<GithubBotUser>;
54
+ }
55
+ export interface GithubApiClient extends Partial<GithubBotUserClient> {
46
56
  exchangeOAuthCode(code: string): Promise<string>;
47
57
  listUserInstallations(input: {
48
58
  userAccessToken: string;
@@ -85,6 +95,6 @@ export interface GithubInstallationAccessToken {
85
95
  expiresAt: Date;
86
96
  permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;
87
97
  }
88
- export declare function createGithubApiClient(): GithubApiClient;
89
- export declare function mapGithubError<T>(operation: () => Promise<T>, notFoundReason?: 'repository-not-found' | 'installation-not-found' | 'file-not-found'): Promise<T>;
98
+ export declare function createGithubApiClient(): GithubApiClient & GithubBotUserClient;
99
+ export declare function mapGithubError<T>(operation: () => Promise<T>, notFoundReason?: 'repository-not-found' | 'installation-not-found' | 'file-not-found' | 'provider-rejected'): Promise<T>;
90
100
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import { MAX_REPOSITORY_FILE_BYTES } from '@shipfox/api-integration-spi';
2
+ import { isRecord, MAX_REPOSITORY_FILE_BYTES } from '@shipfox/api-integration-spi';
3
+ import { logger } from '@shipfox/node-opentelemetry';
3
4
  import ky, { HTTPError, TimeoutError } from 'ky';
4
5
  import { App, Octokit, RequestError } from 'octokit';
5
6
  import { config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey } from '#config.js';
@@ -9,6 +10,7 @@ import { getGithubInstallationOctokit, githubInstallationTokenFormatPlugin } fro
9
10
  const NEXT_PAGE_RE = /[?&]page=(\d+)/;
10
11
  const TRAILING_SLASHES_RE = /\/+$/;
11
12
  const MAX_TREE_WALK_DEPTH = 10;
13
+ const GITHUB_API_TIMEOUT_MS = 10_000;
12
14
  export function createGithubApiClient() {
13
15
  return new OctokitGithubApiClient();
14
16
  }
@@ -29,6 +31,35 @@ let OctokitGithubApiClient = class OctokitGithubApiClient {
29
31
  }
30
32
  return body.access_token;
31
33
  }
34
+ getBotUser(input) {
35
+ const cacheKey = input.username.trim().toLowerCase();
36
+ const cached = this.botUsers.get(cacheKey);
37
+ if (cached) return Promise.resolve(cached);
38
+ const pending = this.botUserLookups.get(cacheKey);
39
+ if (pending?.installationAccessToken === input.installationAccessToken) {
40
+ return pending.promise;
41
+ }
42
+ const lookup = this.fetchBotUser(input).then((botUser)=>{
43
+ const resolved = this.botUsers.get(cacheKey);
44
+ if (resolved) return resolved;
45
+ this.botUsers.set(cacheKey, botUser);
46
+ logger().info({
47
+ githubAppBotLogin: botUser.login,
48
+ githubAppBotUserId: botUser.id
49
+ }, 'Resolved GitHub App bot identity');
50
+ return botUser;
51
+ });
52
+ const trackedLookup = lookup.finally(()=>{
53
+ if (this.botUserLookups.get(cacheKey)?.promise === trackedLookup) {
54
+ this.botUserLookups.delete(cacheKey);
55
+ }
56
+ });
57
+ this.botUserLookups.set(cacheKey, {
58
+ installationAccessToken: input.installationAccessToken,
59
+ promise: trackedLookup
60
+ });
61
+ return trackedLookup;
62
+ }
32
63
  async listUserInstallations(input) {
33
64
  const octokit = new Octokit({
34
65
  auth: input.userAccessToken,
@@ -218,6 +249,49 @@ let OctokitGithubApiClient = class OctokitGithubApiClient {
218
249
  }
219
250
  return this.app;
220
251
  }
252
+ async fetchBotUser(input) {
253
+ const octokit = new Octokit({
254
+ auth: input.installationAccessToken,
255
+ baseUrl: normalizedGithubApiBaseUrl()
256
+ });
257
+ let response;
258
+ try {
259
+ response = await mapGithubError(()=>octokit.rest.users.getByUsername({
260
+ username: input.username,
261
+ request: {
262
+ signal: AbortSignal.timeout(GITHUB_API_TIMEOUT_MS)
263
+ }
264
+ }), 'provider-rejected');
265
+ } catch (error) {
266
+ if (error instanceof GithubIntegrationProviderError && error.status === 404) {
267
+ throw new GithubIntegrationProviderError('provider-rejected', `Configured GitHub bot user ${input.username} was not found`, undefined, error.status);
268
+ }
269
+ throw error;
270
+ }
271
+ const data = response.data;
272
+ if (!isRecord(data)) {
273
+ throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub bot user response is missing required fields');
274
+ }
275
+ const { id, login, type } = data;
276
+ if (typeof id !== 'number' || !Number.isSafeInteger(id) || id <= 0 || typeof login !== 'string' || login.trim().length === 0) {
277
+ throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub bot user response is missing required fields');
278
+ }
279
+ if (type !== 'Bot') {
280
+ throw new GithubIntegrationProviderError('malformed-provider-response', 'Configured GitHub username is not a bot account');
281
+ }
282
+ const canonicalLogin = login.trim();
283
+ if (canonicalLogin.toLowerCase() !== input.username.trim().toLowerCase()) {
284
+ throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub bot user response did not match the configured username');
285
+ }
286
+ return {
287
+ id,
288
+ login: canonicalLogin
289
+ };
290
+ }
291
+ constructor(){
292
+ this.botUsers = new Map();
293
+ this.botUserLookups = new Map();
294
+ }
221
295
  };
222
296
  async function mapGithubOAuthError(operation) {
223
297
  try {
@@ -243,6 +317,9 @@ export async function mapGithubError(operation, notFoundReason = 'repository-not
243
317
  return await operation();
244
318
  } catch (error) {
245
319
  if (error instanceof GithubIntegrationProviderError) throw error;
320
+ if (isGithubTimeoutError(error)) {
321
+ throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');
322
+ }
246
323
  if (error instanceof RequestError) {
247
324
  if (error.status === 404) {
248
325
  throw new GithubIntegrationProviderError(notFoundReason, error.message, undefined, error.status);
@@ -260,12 +337,14 @@ export async function mapGithubError(operation, notFoundReason = 'repository-not
260
337
  throw new GithubIntegrationProviderError('provider-rejected', error.message, undefined, error.status);
261
338
  }
262
339
  }
263
- if (error instanceof Error && error.name === 'AbortError') {
264
- throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');
265
- }
266
340
  throw error;
267
341
  }
268
342
  }
343
+ function isGithubTimeoutError(error) {
344
+ if (!(error instanceof Error)) return false;
345
+ if (error.name === 'AbortError' || error.name === 'TimeoutError') return true;
346
+ return error.cause instanceof Error && isGithubTimeoutError(error.cause);
347
+ }
269
348
  function isGithubRateLimitError(error) {
270
349
  return error.status === 403 && error.response?.headers['x-ratelimit-remaining'] === '0';
271
350
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {MAX_REPOSITORY_FILE_BYTES} from '@shipfox/api-integration-spi';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {App, Octokit, RequestError} from 'octokit';\nimport {config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey} from '#config.js';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\nimport {recordInstallationTokenFormat} from '#metrics/index.js';\nimport {\n getGithubInstallationOctokit,\n githubInstallationTokenFormatPlugin,\n} from './github-octokit.js';\n\nconst NEXT_PAGE_RE = /[?&]page=(\\d+)/;\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst MAX_TREE_WALK_DEPTH = 10;\n\nexport interface GithubAccount {\n login: string;\n type: string;\n}\n\nexport interface GithubInstallationDetails {\n id: number;\n account: GithubAccount;\n repositorySelection: string;\n suspendedAt: Date | null;\n htmlUrl: string;\n raw: Record<string, unknown>;\n}\n\nexport interface GithubRepository {\n id: number;\n ownerLogin: string;\n name: string;\n fullName: string;\n defaultBranch: string;\n private: boolean;\n visibility?: string | undefined;\n cloneUrl: string;\n htmlUrl: string;\n}\n\nexport interface GithubRepositoryPage {\n repositories: GithubRepository[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileEntry {\n path: string;\n size: number | null;\n}\n\nexport interface GithubFilePage {\n files: GithubFileEntry[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileContent {\n path: string;\n content: string;\n size: number;\n}\n\nexport interface GithubUserInstallationPage {\n installationIds: number[];\n nextCursor: string | null;\n}\n\nexport interface GithubApiClient {\n exchangeOAuthCode(code: string): Promise<string>;\n listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage>;\n getInstallation(installationId: number): Promise<GithubInstallationDetails>;\n listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage>;\n getRepository(input: {installationId: number; repositoryId: number}): Promise<GithubRepository>;\n listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage>;\n fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent>;\n createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken>;\n}\n\nexport interface GithubInstallationAccessToken {\n token: string;\n expiresAt: Date;\n permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;\n}\n\nexport function createGithubApiClient(): GithubApiClient {\n return new OctokitGithubApiClient();\n}\n\nclass OctokitGithubApiClient implements GithubApiClient {\n private app: App | undefined;\n\n async exchangeOAuthCode(code: string): Promise<string> {\n const body = await mapGithubOAuthError(() =>\n ky\n .post('https://github.com/login/oauth/access_token', {\n headers: {accept: 'application/json'},\n json: {\n client_id: config.GITHUB_APP_CLIENT_ID,\n client_secret: config.GITHUB_APP_CLIENT_SECRET,\n code,\n },\n })\n .json<{access_token?: unknown}>(),\n );\n\n if (typeof body.access_token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub OAuth response did not include an access token',\n );\n }\n return body.access_token;\n }\n\n async listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage> {\n const octokit = new Octokit({\n auth: input.userAccessToken,\n baseUrl: normalizedGithubApiBaseUrl(),\n });\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.request('GET /user/installations', {\n per_page: 100,\n page,\n }),\n );\n\n const installations = response.data.installations ?? [];\n return {\n installationIds: installations.map((installation) => installation.id),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getInstallation(installationId: number): Promise<GithubInstallationDetails> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.getInstallation({installation_id: installationId}),\n );\n const data = response.data;\n const account = data.account;\n if (!account) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation response did not include an account',\n );\n }\n\n const login = 'login' in account ? account.login : account.slug;\n return {\n id: data.id,\n account: {\n login,\n type: 'type' in account ? account.type : 'Enterprise',\n },\n repositorySelection: data.repository_selection,\n suspendedAt: data.suspended_at ? new Date(data.suspended_at) : null,\n htmlUrl: data.html_url,\n raw: data as unknown as Record<string, unknown>,\n };\n }\n\n async listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.rest.apps.listReposAccessibleToInstallation({\n per_page: input.limit,\n page,\n }),\n );\n\n return {\n repositories: response.data.repositories.map(toGithubRepository),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getRepository(input: {\n installationId: number;\n repositoryId: number;\n }): Promise<GithubRepository> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const response = await mapGithubError(() =>\n octokit.request('GET /repositories/{repository_id}', {\n repository_id: input.repositoryId,\n }),\n );\n\n return toGithubRepository(response.data);\n }\n\n async listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const owner = repository.ownerLogin;\n const repo = repository.name;\n const startPath = input.prefix.replace(TRAILING_SLASHES_RE, '');\n const offset = input.cursor ? Number.parseInt(input.cursor, 10) : 0;\n const start = Number.isNaN(offset) || offset < 0 ? 0 : offset;\n const collected: GithubFileEntry[] = [];\n const overflowLimit = start + input.limit + 1;\n\n type GetContentData = Awaited<ReturnType<typeof octokit.rest.repos.getContent>>['data'];\n\n const walk = async (path: string, depth: number): Promise<void> => {\n if (collected.length >= overflowLimit) return;\n if (depth > MAX_TREE_WALK_DEPTH) return;\n\n let data: GetContentData;\n try {\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner,\n repo,\n path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n data = response.data;\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError && error.reason === 'file-not-found') {\n return;\n }\n throw error;\n }\n\n if (!Array.isArray(data)) {\n if (data.type === 'file' && data.path) {\n collected.push({path: data.path, size: typeof data.size === 'number' ? data.size : null});\n }\n return;\n }\n\n const entries = [...data].sort((a, b) => (a.path ?? '').localeCompare(b.path ?? ''));\n for (const entry of entries) {\n if (collected.length >= overflowLimit) return;\n if (!entry.path) continue;\n if (entry.type === 'file') {\n collected.push({\n path: entry.path,\n size: typeof entry.size === 'number' ? entry.size : null,\n });\n } else if (entry.type === 'dir') {\n await walk(entry.path, depth + 1);\n }\n }\n };\n\n await walk(startPath, 0);\n\n const sorted = collected.sort((a, b) => a.path.localeCompare(b.path));\n const page = sorted.slice(start, start + input.limit);\n const consumed = start + page.length;\n const hasMore = consumed < sorted.length;\n\n return {\n files: page,\n nextCursor: hasMore ? String(consumed) : null,\n };\n }\n\n async fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner: repository.ownerLogin,\n repo: repository.name,\n path: input.path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n const data = response.data;\n\n if (Array.isArray(data) || data.type !== 'file') {\n throw new GithubIntegrationProviderError('file-not-found', 'GitHub path is not a file');\n }\n if (data.size > MAX_REPOSITORY_FILE_BYTES) {\n throw new GithubIntegrationProviderError(\n 'content-too-large',\n 'GitHub file content is larger than the supported limit',\n );\n }\n if (typeof data.content !== 'string' || data.encoding !== 'base64') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub file response did not include base64 content',\n );\n }\n\n return {\n path: data.path,\n size: data.size,\n content: Buffer.from(data.content, 'base64').toString('utf8'),\n };\n }\n\n async createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.createInstallationAccessToken({\n installation_id: input.installationId,\n repository_ids: [input.repositoryId],\n permissions: input.permissions ?? {contents: 'read'},\n }),\n );\n\n if (typeof response.data.token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a token',\n );\n }\n\n recordInstallationTokenFormat(response.data.token);\n\n const expiresAt = new Date(response.data.expires_at);\n if (Number.isNaN(expiresAt.getTime())) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a valid expiry',\n );\n }\n\n return {\n token: response.data.token,\n expiresAt,\n ...(response.data.permissions === undefined ? {} : {permissions: response.data.permissions}),\n };\n }\n\n private getApp(): App {\n if (!this.app) {\n this.app = new App({\n appId: config.GITHUB_APP_ID,\n privateKey: normalizedGithubPrivateKey(),\n Octokit: Octokit.plugin(githubInstallationTokenFormatPlugin).defaults({\n baseUrl: normalizedGithubApiBaseUrl(),\n }),\n });\n }\n return this.app;\n }\n}\n\nasync function mapGithubOAuthError<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof HTTPError) {\n if (error.response.status === 429) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n 'GitHub OAuth code exchange failed',\n retryAfterSecondsFromHeaders(error.response.headers),\n );\n }\n if (error.response.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n 'GitHub OAuth code exchange failed',\n );\n }\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub OAuth code exchange failed',\n );\n }\n if (error instanceof TimeoutError) {\n throw new GithubIntegrationProviderError('timeout', 'GitHub OAuth request timed out');\n }\n throw error;\n }\n}\n\nexport async function mapGithubError<T>(\n operation: () => Promise<T>,\n notFoundReason:\n | 'repository-not-found'\n | 'installation-not-found'\n | 'file-not-found' = 'repository-not-found',\n): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError) throw error;\n if (error instanceof RequestError) {\n if (error.status === 404) {\n throw new GithubIntegrationProviderError(\n notFoundReason,\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status === 429 || isGithubRateLimitError(error)) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status === 401 || error.status === 403) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status >= 400) {\n throw new GithubIntegrationProviderError(\n 'provider-rejected',\n error.message,\n undefined,\n error.status,\n );\n }\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');\n }\n throw error;\n }\n}\n\nfunction isGithubRateLimitError(error: RequestError): boolean {\n return error.status === 403 && error.response?.headers['x-ratelimit-remaining'] === '0';\n}\n\nfunction retryAfterSeconds(error: RequestError): number | undefined {\n return parseRetryAfterSeconds(error.response?.headers['retry-after']);\n}\n\nfunction retryAfterSecondsFromHeaders(headers: Headers): number | undefined {\n return parseRetryAfterSeconds(headers.get('retry-after'));\n}\n\nfunction parseRetryAfterSeconds(\n retryAfter: string | number | null | undefined,\n): number | undefined {\n if (!retryAfter) return undefined;\n const parsed = Number.parseInt(String(retryAfter), 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction cursorToPage(cursor: string | undefined): number {\n if (!cursor) return 1;\n const page = Number.parseInt(cursor, 10);\n return Number.isNaN(page) || page < 1 ? 1 : page;\n}\n\nfunction nextCursorFromLink(link: string | undefined): string | null {\n if (!link) return null;\n const next = link.split(',').find((part) => part.includes('rel=\"next\"'));\n if (!next) return null;\n const match = next.match(NEXT_PAGE_RE);\n return match?.[1] ?? null;\n}\n\nfunction toGithubRepository(repository: {\n id: number;\n owner: {login: string};\n name: string;\n full_name: string;\n default_branch?: string | null | undefined;\n private: boolean;\n visibility?: string | undefined;\n clone_url?: string | null | undefined;\n html_url?: string | null | undefined;\n}): GithubRepository {\n if (!repository.default_branch || !repository.clone_url || !repository.html_url) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub repository response is missing required fields',\n );\n }\n return {\n id: repository.id,\n ownerLogin: repository.owner.login,\n name: repository.name,\n fullName: repository.full_name,\n defaultBranch: repository.default_branch,\n private: repository.private,\n visibility: repository.visibility,\n cloneUrl: repository.clone_url,\n htmlUrl: repository.html_url,\n };\n}\n"],"names":["Buffer","MAX_REPOSITORY_FILE_BYTES","ky","HTTPError","TimeoutError","App","Octokit","RequestError","config","normalizedGithubApiBaseUrl","normalizedGithubPrivateKey","GithubIntegrationProviderError","recordInstallationTokenFormat","getGithubInstallationOctokit","githubInstallationTokenFormatPlugin","NEXT_PAGE_RE","TRAILING_SLASHES_RE","MAX_TREE_WALK_DEPTH","createGithubApiClient","OctokitGithubApiClient","exchangeOAuthCode","code","body","mapGithubOAuthError","post","headers","accept","json","client_id","GITHUB_APP_CLIENT_ID","client_secret","GITHUB_APP_CLIENT_SECRET","access_token","listUserInstallations","input","octokit","auth","userAccessToken","baseUrl","page","cursorToPage","cursor","response","mapGithubError","request","per_page","installations","data","installationIds","map","installation","id","nextCursor","nextCursorFromLink","link","getInstallation","installationId","getApp","rest","apps","installation_id","account","login","slug","type","repositorySelection","repository_selection","suspendedAt","suspended_at","Date","htmlUrl","html_url","raw","listInstallationRepositories","listReposAccessibleToInstallation","limit","repositories","toGithubRepository","getRepository","repository_id","repositoryId","listRepositoryFiles","repository","owner","ownerLogin","repo","name","startPath","prefix","replace","offset","Number","parseInt","start","isNaN","collected","overflowLimit","walk","path","depth","length","repos","getContent","ref","error","reason","Array","isArray","push","size","entries","sort","a","b","localeCompare","entry","sorted","slice","consumed","hasMore","files","String","fetchRepositoryFile","content","encoding","from","toString","createInstallationAccessToken","repository_ids","permissions","contents","token","expiresAt","expires_at","getTime","undefined","app","appId","GITHUB_APP_ID","privateKey","plugin","defaults","operation","status","retryAfterSecondsFromHeaders","notFoundReason","message","isGithubRateLimitError","retryAfterSeconds","Error","parseRetryAfterSeconds","get","retryAfter","parsed","next","split","find","part","includes","match","default_branch","clone_url","fullName","full_name","defaultBranch","private","visibility","cloneUrl"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {isRecord, MAX_REPOSITORY_FILE_BYTES} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {App, Octokit, RequestError} from 'octokit';\nimport {config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey} from '#config.js';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\nimport {recordInstallationTokenFormat} from '#metrics/index.js';\nimport {\n getGithubInstallationOctokit,\n githubInstallationTokenFormatPlugin,\n} from './github-octokit.js';\n\nconst NEXT_PAGE_RE = /[?&]page=(\\d+)/;\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst MAX_TREE_WALK_DEPTH = 10;\nconst GITHUB_API_TIMEOUT_MS = 10_000;\n\nexport interface GithubAccount {\n login: string;\n type: string;\n}\n\nexport interface GithubInstallationDetails {\n id: number;\n account: GithubAccount;\n repositorySelection: string;\n suspendedAt: Date | null;\n htmlUrl: string;\n raw: Record<string, unknown>;\n}\n\nexport interface GithubRepository {\n id: number;\n ownerLogin: string;\n name: string;\n fullName: string;\n defaultBranch: string;\n private: boolean;\n visibility?: string | undefined;\n cloneUrl: string;\n htmlUrl: string;\n}\n\nexport interface GithubRepositoryPage {\n repositories: GithubRepository[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileEntry {\n path: string;\n size: number | null;\n}\n\nexport interface GithubFilePage {\n files: GithubFileEntry[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileContent {\n path: string;\n content: string;\n size: number;\n}\n\nexport interface GithubUserInstallationPage {\n installationIds: number[];\n nextCursor: string | null;\n}\n\nexport interface GithubBotUser {\n id: number;\n login: string;\n}\n\nexport interface GithubBotUserClient {\n getBotUser(input: {username: string; installationAccessToken: string}): Promise<GithubBotUser>;\n}\n\nexport interface GithubApiClient extends Partial<GithubBotUserClient> {\n exchangeOAuthCode(code: string): Promise<string>;\n listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage>;\n getInstallation(installationId: number): Promise<GithubInstallationDetails>;\n listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage>;\n getRepository(input: {installationId: number; repositoryId: number}): Promise<GithubRepository>;\n listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage>;\n fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent>;\n createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken>;\n}\n\nexport interface GithubInstallationAccessToken {\n token: string;\n expiresAt: Date;\n permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;\n}\n\nexport function createGithubApiClient(): GithubApiClient & GithubBotUserClient {\n return new OctokitGithubApiClient();\n}\n\nclass OctokitGithubApiClient implements GithubApiClient, GithubBotUserClient {\n private app: App | undefined;\n private readonly botUsers = new Map<string, GithubBotUser>();\n private readonly botUserLookups = new Map<\n string,\n {installationAccessToken: string; promise: Promise<GithubBotUser>}\n >();\n\n async exchangeOAuthCode(code: string): Promise<string> {\n const body = await mapGithubOAuthError(() =>\n ky\n .post('https://github.com/login/oauth/access_token', {\n headers: {accept: 'application/json'},\n json: {\n client_id: config.GITHUB_APP_CLIENT_ID,\n client_secret: config.GITHUB_APP_CLIENT_SECRET,\n code,\n },\n })\n .json<{access_token?: unknown}>(),\n );\n\n if (typeof body.access_token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub OAuth response did not include an access token',\n );\n }\n return body.access_token;\n }\n\n getBotUser(input: {username: string; installationAccessToken: string}): Promise<GithubBotUser> {\n const cacheKey = input.username.trim().toLowerCase();\n const cached = this.botUsers.get(cacheKey);\n if (cached) return Promise.resolve(cached);\n\n const pending = this.botUserLookups.get(cacheKey);\n if (pending?.installationAccessToken === input.installationAccessToken) {\n return pending.promise;\n }\n\n const lookup = this.fetchBotUser(input).then((botUser) => {\n const resolved = this.botUsers.get(cacheKey);\n if (resolved) return resolved;\n\n this.botUsers.set(cacheKey, botUser);\n logger().info(\n {githubAppBotLogin: botUser.login, githubAppBotUserId: botUser.id},\n 'Resolved GitHub App bot identity',\n );\n return botUser;\n });\n const trackedLookup = lookup.finally(() => {\n if (this.botUserLookups.get(cacheKey)?.promise === trackedLookup) {\n this.botUserLookups.delete(cacheKey);\n }\n });\n this.botUserLookups.set(cacheKey, {\n installationAccessToken: input.installationAccessToken,\n promise: trackedLookup,\n });\n return trackedLookup;\n }\n\n async listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage> {\n const octokit = new Octokit({\n auth: input.userAccessToken,\n baseUrl: normalizedGithubApiBaseUrl(),\n });\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.request('GET /user/installations', {\n per_page: 100,\n page,\n }),\n );\n\n const installations = response.data.installations ?? [];\n return {\n installationIds: installations.map((installation) => installation.id),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getInstallation(installationId: number): Promise<GithubInstallationDetails> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.getInstallation({installation_id: installationId}),\n );\n const data = response.data;\n const account = data.account;\n if (!account) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation response did not include an account',\n );\n }\n\n const login = 'login' in account ? account.login : account.slug;\n return {\n id: data.id,\n account: {\n login,\n type: 'type' in account ? account.type : 'Enterprise',\n },\n repositorySelection: data.repository_selection,\n suspendedAt: data.suspended_at ? new Date(data.suspended_at) : null,\n htmlUrl: data.html_url,\n raw: data as unknown as Record<string, unknown>,\n };\n }\n\n async listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.rest.apps.listReposAccessibleToInstallation({\n per_page: input.limit,\n page,\n }),\n );\n\n return {\n repositories: response.data.repositories.map(toGithubRepository),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getRepository(input: {\n installationId: number;\n repositoryId: number;\n }): Promise<GithubRepository> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const response = await mapGithubError(() =>\n octokit.request('GET /repositories/{repository_id}', {\n repository_id: input.repositoryId,\n }),\n );\n\n return toGithubRepository(response.data);\n }\n\n async listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const owner = repository.ownerLogin;\n const repo = repository.name;\n const startPath = input.prefix.replace(TRAILING_SLASHES_RE, '');\n const offset = input.cursor ? Number.parseInt(input.cursor, 10) : 0;\n const start = Number.isNaN(offset) || offset < 0 ? 0 : offset;\n const collected: GithubFileEntry[] = [];\n const overflowLimit = start + input.limit + 1;\n\n type GetContentData = Awaited<ReturnType<typeof octokit.rest.repos.getContent>>['data'];\n\n const walk = async (path: string, depth: number): Promise<void> => {\n if (collected.length >= overflowLimit) return;\n if (depth > MAX_TREE_WALK_DEPTH) return;\n\n let data: GetContentData;\n try {\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner,\n repo,\n path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n data = response.data;\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError && error.reason === 'file-not-found') {\n return;\n }\n throw error;\n }\n\n if (!Array.isArray(data)) {\n if (data.type === 'file' && data.path) {\n collected.push({path: data.path, size: typeof data.size === 'number' ? data.size : null});\n }\n return;\n }\n\n const entries = [...data].sort((a, b) => (a.path ?? '').localeCompare(b.path ?? ''));\n for (const entry of entries) {\n if (collected.length >= overflowLimit) return;\n if (!entry.path) continue;\n if (entry.type === 'file') {\n collected.push({\n path: entry.path,\n size: typeof entry.size === 'number' ? entry.size : null,\n });\n } else if (entry.type === 'dir') {\n await walk(entry.path, depth + 1);\n }\n }\n };\n\n await walk(startPath, 0);\n\n const sorted = collected.sort((a, b) => a.path.localeCompare(b.path));\n const page = sorted.slice(start, start + input.limit);\n const consumed = start + page.length;\n const hasMore = consumed < sorted.length;\n\n return {\n files: page,\n nextCursor: hasMore ? String(consumed) : null,\n };\n }\n\n async fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner: repository.ownerLogin,\n repo: repository.name,\n path: input.path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n const data = response.data;\n\n if (Array.isArray(data) || data.type !== 'file') {\n throw new GithubIntegrationProviderError('file-not-found', 'GitHub path is not a file');\n }\n if (data.size > MAX_REPOSITORY_FILE_BYTES) {\n throw new GithubIntegrationProviderError(\n 'content-too-large',\n 'GitHub file content is larger than the supported limit',\n );\n }\n if (typeof data.content !== 'string' || data.encoding !== 'base64') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub file response did not include base64 content',\n );\n }\n\n return {\n path: data.path,\n size: data.size,\n content: Buffer.from(data.content, 'base64').toString('utf8'),\n };\n }\n\n async createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.createInstallationAccessToken({\n installation_id: input.installationId,\n repository_ids: [input.repositoryId],\n permissions: input.permissions ?? {contents: 'read'},\n }),\n );\n\n if (typeof response.data.token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a token',\n );\n }\n\n recordInstallationTokenFormat(response.data.token);\n\n const expiresAt = new Date(response.data.expires_at);\n if (Number.isNaN(expiresAt.getTime())) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a valid expiry',\n );\n }\n\n return {\n token: response.data.token,\n expiresAt,\n ...(response.data.permissions === undefined ? {} : {permissions: response.data.permissions}),\n };\n }\n\n private getApp(): App {\n if (!this.app) {\n this.app = new App({\n appId: config.GITHUB_APP_ID,\n privateKey: normalizedGithubPrivateKey(),\n Octokit: Octokit.plugin(githubInstallationTokenFormatPlugin).defaults({\n baseUrl: normalizedGithubApiBaseUrl(),\n }),\n });\n }\n return this.app;\n }\n\n private async fetchBotUser(input: {\n username: string;\n installationAccessToken: string;\n }): Promise<GithubBotUser> {\n const octokit = new Octokit({\n auth: input.installationAccessToken,\n baseUrl: normalizedGithubApiBaseUrl(),\n });\n let response: Awaited<ReturnType<typeof octokit.rest.users.getByUsername>>;\n try {\n response = await mapGithubError(\n () =>\n octokit.rest.users.getByUsername({\n username: input.username,\n request: {signal: AbortSignal.timeout(GITHUB_API_TIMEOUT_MS)},\n }),\n 'provider-rejected',\n );\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError && error.status === 404) {\n throw new GithubIntegrationProviderError(\n 'provider-rejected',\n `Configured GitHub bot user ${input.username} was not found`,\n undefined,\n error.status,\n );\n }\n throw error;\n }\n\n const data: unknown = response.data;\n if (!isRecord(data)) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub bot user response is missing required fields',\n );\n }\n const {id, login, type} = data;\n if (\n typeof id !== 'number' ||\n !Number.isSafeInteger(id) ||\n id <= 0 ||\n typeof login !== 'string' ||\n login.trim().length === 0\n ) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub bot user response is missing required fields',\n );\n }\n if (type !== 'Bot') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'Configured GitHub username is not a bot account',\n );\n }\n const canonicalLogin = login.trim();\n if (canonicalLogin.toLowerCase() !== input.username.trim().toLowerCase()) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub bot user response did not match the configured username',\n );\n }\n return {id, login: canonicalLogin};\n }\n}\n\nasync function mapGithubOAuthError<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof HTTPError) {\n if (error.response.status === 429) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n 'GitHub OAuth code exchange failed',\n retryAfterSecondsFromHeaders(error.response.headers),\n );\n }\n if (error.response.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n 'GitHub OAuth code exchange failed',\n );\n }\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub OAuth code exchange failed',\n );\n }\n if (error instanceof TimeoutError) {\n throw new GithubIntegrationProviderError('timeout', 'GitHub OAuth request timed out');\n }\n throw error;\n }\n}\n\nexport async function mapGithubError<T>(\n operation: () => Promise<T>,\n notFoundReason:\n | 'repository-not-found'\n | 'installation-not-found'\n | 'file-not-found'\n | 'provider-rejected' = 'repository-not-found',\n): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError) throw error;\n if (isGithubTimeoutError(error)) {\n throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');\n }\n if (error instanceof RequestError) {\n if (error.status === 404) {\n throw new GithubIntegrationProviderError(\n notFoundReason,\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status === 429 || isGithubRateLimitError(error)) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status === 401 || error.status === 403) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status >= 400) {\n throw new GithubIntegrationProviderError(\n 'provider-rejected',\n error.message,\n undefined,\n error.status,\n );\n }\n }\n throw error;\n }\n}\n\nfunction isGithubTimeoutError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n if (error.name === 'AbortError' || error.name === 'TimeoutError') return true;\n return error.cause instanceof Error && isGithubTimeoutError(error.cause);\n}\n\nfunction isGithubRateLimitError(error: RequestError): boolean {\n return error.status === 403 && error.response?.headers['x-ratelimit-remaining'] === '0';\n}\n\nfunction retryAfterSeconds(error: RequestError): number | undefined {\n return parseRetryAfterSeconds(error.response?.headers['retry-after']);\n}\n\nfunction retryAfterSecondsFromHeaders(headers: Headers): number | undefined {\n return parseRetryAfterSeconds(headers.get('retry-after'));\n}\n\nfunction parseRetryAfterSeconds(\n retryAfter: string | number | null | undefined,\n): number | undefined {\n if (!retryAfter) return undefined;\n const parsed = Number.parseInt(String(retryAfter), 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction cursorToPage(cursor: string | undefined): number {\n if (!cursor) return 1;\n const page = Number.parseInt(cursor, 10);\n return Number.isNaN(page) || page < 1 ? 1 : page;\n}\n\nfunction nextCursorFromLink(link: string | undefined): string | null {\n if (!link) return null;\n const next = link.split(',').find((part) => part.includes('rel=\"next\"'));\n if (!next) return null;\n const match = next.match(NEXT_PAGE_RE);\n return match?.[1] ?? null;\n}\n\nfunction toGithubRepository(repository: {\n id: number;\n owner: {login: string};\n name: string;\n full_name: string;\n default_branch?: string | null | undefined;\n private: boolean;\n visibility?: string | undefined;\n clone_url?: string | null | undefined;\n html_url?: string | null | undefined;\n}): GithubRepository {\n if (!repository.default_branch || !repository.clone_url || !repository.html_url) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub repository response is missing required fields',\n );\n }\n return {\n id: repository.id,\n ownerLogin: repository.owner.login,\n name: repository.name,\n fullName: repository.full_name,\n defaultBranch: repository.default_branch,\n private: repository.private,\n visibility: repository.visibility,\n cloneUrl: repository.clone_url,\n htmlUrl: repository.html_url,\n };\n}\n"],"names":["Buffer","isRecord","MAX_REPOSITORY_FILE_BYTES","logger","ky","HTTPError","TimeoutError","App","Octokit","RequestError","config","normalizedGithubApiBaseUrl","normalizedGithubPrivateKey","GithubIntegrationProviderError","recordInstallationTokenFormat","getGithubInstallationOctokit","githubInstallationTokenFormatPlugin","NEXT_PAGE_RE","TRAILING_SLASHES_RE","MAX_TREE_WALK_DEPTH","GITHUB_API_TIMEOUT_MS","createGithubApiClient","OctokitGithubApiClient","exchangeOAuthCode","code","body","mapGithubOAuthError","post","headers","accept","json","client_id","GITHUB_APP_CLIENT_ID","client_secret","GITHUB_APP_CLIENT_SECRET","access_token","getBotUser","input","cacheKey","username","trim","toLowerCase","cached","botUsers","get","Promise","resolve","pending","botUserLookups","installationAccessToken","promise","lookup","fetchBotUser","then","botUser","resolved","set","info","githubAppBotLogin","login","githubAppBotUserId","id","trackedLookup","finally","delete","listUserInstallations","octokit","auth","userAccessToken","baseUrl","page","cursorToPage","cursor","response","mapGithubError","request","per_page","installations","data","installationIds","map","installation","nextCursor","nextCursorFromLink","link","getInstallation","installationId","getApp","rest","apps","installation_id","account","slug","type","repositorySelection","repository_selection","suspendedAt","suspended_at","Date","htmlUrl","html_url","raw","listInstallationRepositories","listReposAccessibleToInstallation","limit","repositories","toGithubRepository","getRepository","repository_id","repositoryId","listRepositoryFiles","repository","owner","ownerLogin","repo","name","startPath","prefix","replace","offset","Number","parseInt","start","isNaN","collected","overflowLimit","walk","path","depth","length","repos","getContent","ref","error","reason","Array","isArray","push","size","entries","sort","a","b","localeCompare","entry","sorted","slice","consumed","hasMore","files","String","fetchRepositoryFile","content","encoding","from","toString","createInstallationAccessToken","repository_ids","permissions","contents","token","expiresAt","expires_at","getTime","undefined","app","appId","GITHUB_APP_ID","privateKey","plugin","defaults","users","getByUsername","signal","AbortSignal","timeout","status","isSafeInteger","canonicalLogin","Map","operation","retryAfterSecondsFromHeaders","notFoundReason","isGithubTimeoutError","message","isGithubRateLimitError","retryAfterSeconds","Error","cause","parseRetryAfterSeconds","retryAfter","parsed","next","split","find","part","includes","match","default_branch","clone_url","fullName","full_name","defaultBranch","private","visibility","cloneUrl"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EACL,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EAGlC,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EACzB,+BAA+B,EAC/B,iCAAiC,EACjC,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,oCAAoC,EACpC,0BAA0B,EAC1B,sBAAsB,EACtB,+BAA+B,GAChC,MAAM,gCAAgC,CAAC;AAExC,KAAK,2BAA2B,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;AAEnE,KAAK,oBAAoB,GAAG;IAC1B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,SAAS;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,CAAC;AAuFF,qBAAa,wBACX,YACE,kBAAkB,CAChB,2BAA2B,EAC3B,4BAA4B,EAC5B,OAAO,EACP,oBAAoB,CACrB;IAIS,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,YAA6B,OAAO,GAAE,+BAAoC,EAEzE;IAED,OAAO,IAAI,SAAS,2BAA2B,EAAE,CAEhD;IAED,gBAAgB,IAAI,yBAAyB,CAE5C;IAEK,WAAW,CACf,KAAK,EAAE,0BAA0B,CAAC,2BAA2B,EAAE,4BAA4B,CAAC,GAC3F,OAAO,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CA8EjD;CACF;AAED,MAAM,WAAW,+BAA+B;IAC9C,6BAA6B,CAAC,EAC1B,CAAC,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC,GACnE,SAAS,CAAC;IACd,aAAa,CAAC,EAAE,+BAA+B,GAAG,SAAS,CAAC;IAC5D,YAAY,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IAClE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACzF,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CACjG;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC;AAgE1E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,GAAG,SAAS,CA6HpB;AAyED,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB"}
@@ -1,7 +1,8 @@
1
1
  import { Octokit } from 'octokit';
2
2
  import { mapGithubError } from '#api/client.js';
3
3
  import { createGithubInstallationTokenProvider } from '#api/installation-token-provider.js';
4
- import { config, normalizedGithubApiBaseUrl } from '#config.js';
4
+ import { normalizedGithubApiBaseUrl } from '#config.js';
5
+ import { githubAppBotLogin } from './bot-identity.js';
5
6
  import { GithubIntegrationProviderError } from './errors.js';
6
7
  import { githubAgentToolCatalog, githubAgentToolSelectionCatalog } from './github-agent-tool-catalog.js';
7
8
  export { buildGithubAgentToolSelectionCatalog, DEFAULT_JOB_LOG_TAIL_LINES, githubAgentToolCatalog, githubAgentToolSelectionCatalog } from './github-agent-tool-catalog.js';
@@ -9,7 +10,6 @@ const GITHUB_GRAPHQL_ROUTE = 'POST /graphql';
9
10
  const GITHUB_ARTIFACT_ARCHIVE_FORMAT = 'zip';
10
11
  const GITHUB_ARTIFACT_DOWNLOAD_ROUTE = `GET /repos/{owner}/{repo}/actions/artifacts/{resource_id}/${GITHUB_ARTIFACT_ARCHIVE_FORMAT}`;
11
12
  const GITHUB_ARTIFACT_DOWNLOAD_TIMEOUT_MS = 30_000;
12
- const GITHUB_APP_BOT_SUFFIX = '[bot]';
13
13
  const PENDING_REVIEW_PAGE_SIZE = 100;
14
14
  const PENDING_REVIEW_MAX_PAGE_REQUESTS = 5;
15
15
  const PENDING_REVIEW_LOOKUP_TIMEOUT_MS = 15_000;
@@ -25,6 +25,61 @@ const ADD_PENDING_REVIEW_COMMENT_MUTATION = `
25
25
  }
26
26
  }
27
27
  `;
28
+ const GET_PULL_REQUEST_REVIEW_THREADS_QUERY = `
29
+ query GetPullRequestReviewThreads(
30
+ $owner: String!
31
+ $repo: String!
32
+ $pullNumber: Int!
33
+ $after: String
34
+ ) {
35
+ repository(owner: $owner, name: $repo) {
36
+ pullRequest(number: $pullNumber) {
37
+ reviewThreads(first: 100, after: $after) {
38
+ nodes {
39
+ id
40
+ isResolved
41
+ comments(first: 100) {
42
+ nodes {
43
+ id
44
+ databaseId
45
+ body
46
+ author {
47
+ login
48
+ }
49
+ path
50
+ line
51
+ side
52
+ startLine
53
+ startSide
54
+ createdAt
55
+ updatedAt
56
+ url
57
+ }
58
+ pageInfo {
59
+ hasNextPage
60
+ endCursor
61
+ }
62
+ }
63
+ }
64
+ pageInfo {
65
+ hasNextPage
66
+ endCursor
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ `;
73
+ const RESOLVE_PULL_REQUEST_REVIEW_THREAD_MUTATION = `
74
+ mutation ResolvePullRequestReviewThread($input: ResolveReviewThreadInput!) {
75
+ resolveReviewThread(input: $input) {
76
+ thread {
77
+ id
78
+ isResolved
79
+ }
80
+ }
81
+ }
82
+ `;
28
83
  export class GithubAgentToolsProvider {
29
84
  constructor(options = {}){
30
85
  this.options = options;
@@ -62,8 +117,11 @@ export class GithubAgentToolsProvider {
62
117
  const client = (this.options.createClient ?? createOctokitClient)(token.token);
63
118
  const method = typeof call.arguments.method === 'string' ? call.arguments.method : undefined;
64
119
  if (operation.kind === 'graphql') {
65
- const data = await mapGithubError(()=>addCommentToPendingReview(client, operation.parameters));
66
- return data === undefined ? githubToolError(NO_PENDING_REVIEW_MESSAGE, 'provider-rejected') : githubToolResult(tool.id, data);
120
+ const data = await mapGithubError(()=>executeGithubGraphqlOperation(client, tool.id, method, operation.parameters));
121
+ if (data === undefined && tool.id === 'add_comment_to_pending_review') {
122
+ return githubToolError(NO_PENDING_REVIEW_MESSAGE, 'provider-rejected');
123
+ }
124
+ return githubToolResult(tool.id, data);
67
125
  }
68
126
  const operationParameters = await mapGithubError(()=>resolvePendingReviewParameters(client, operation.parameters, tool.id, method));
69
127
  if (operationParameters === undefined) {
@@ -172,6 +230,8 @@ export function githubOperationRoute(toolId, method, args) {
172
230
  return `GET ${repoPath}/pulls/${pull}/commits`;
173
231
  case 'pull_request_read.get_review_comments':
174
232
  return `GET ${repoPath}/pulls/${pull}/comments`;
233
+ case 'pull_request_read.get_review_threads':
234
+ return GITHUB_GRAPHQL_ROUTE;
175
235
  case 'pull_request_read.get_reviews':
176
236
  return `GET ${repoPath}/pulls/${pull}/reviews`;
177
237
  case 'pull_request_read.get_comments':
@@ -198,6 +258,8 @@ export function githubOperationRoute(toolId, method, args) {
198
258
  return `POST ${repoPath}/pulls/${pull}/reviews/{review_id}/events`;
199
259
  case 'pull_request_review_write.delete_pending':
200
260
  return `DELETE ${repoPath}/pulls/${pull}/reviews/{review_id}`;
261
+ case 'pull_request_review_thread_write.resolve':
262
+ return GITHUB_GRAPHQL_ROUTE;
201
263
  case 'add_comment_to_pending_review.':
202
264
  return GITHUB_GRAPHQL_ROUTE;
203
265
  case 'actions_list.list_workflows':
@@ -259,6 +321,33 @@ async function addCommentToPendingReview(client, args) {
259
321
  input
260
322
  });
261
323
  }
324
+ async function executeGithubGraphqlOperation(client, toolId, method, parameters) {
325
+ if (client.graphql === undefined) {
326
+ throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub client does not support GraphQL operations');
327
+ }
328
+ switch(`${toolId}.${method ?? ''}`){
329
+ case 'pull_request_read.get_review_threads':
330
+ {
331
+ const variables = {
332
+ owner: parameters.owner,
333
+ repo: parameters.repo,
334
+ pullNumber: parameters.pull_number
335
+ };
336
+ if (typeof parameters.cursor === 'string') variables.after = parameters.cursor;
337
+ return await client.graphql(GET_PULL_REQUEST_REVIEW_THREADS_QUERY, variables);
338
+ }
339
+ case 'pull_request_review_thread_write.resolve':
340
+ return await client.graphql(RESOLVE_PULL_REQUEST_REVIEW_THREAD_MUTATION, {
341
+ input: {
342
+ threadId: parameters.thread_id
343
+ }
344
+ });
345
+ case 'add_comment_to_pending_review.':
346
+ return await addCommentToPendingReview(client, parameters);
347
+ default:
348
+ throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub operation does not support GraphQL operations');
349
+ }
350
+ }
262
351
  export function projectGithubOperationParameters(toolId, method, args) {
263
352
  const parameters = {
264
353
  ...args
@@ -389,10 +478,6 @@ function latestPendingReviewOnPage(data, requiredIdentifier) {
389
478
  malformed
390
479
  };
391
480
  }
392
- function githubAppBotLogin() {
393
- const configuredUsername = config.GITHUB_APP_USERNAME?.trim() || config.GITHUB_APP_SLUG.trim();
394
- return configuredUsername.toLowerCase().endsWith(GITHUB_APP_BOT_SUFFIX) ? configuredUsername : `${configuredUsername}${GITHUB_APP_BOT_SUFFIX}`;
395
- }
396
481
  function githubToolResult(toolId, data, response, parameters, route) {
397
482
  const structuredContent = projectGithubToolOutput(toolId, data, response, parameters, route);
398
483
  if (structuredContent === undefined) {