@shipfox/api-integration-github 12.2.0 → 12.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 30 files with swc (472.42ms)
2
+ Successfully compiled: 30 files with swc (346.69ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @shipfox/api-integration-github
2
2
 
3
+ ## 12.5.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 420ef97: GitHub agent artifact downloads now use GitHub's required zip format and return a temporary download URL.
8
+ - f2b20af: Preserve GitHub provider rejection details and classify terminal agent-tool failures without reporting them as provider outages.
9
+ - da02bd8: Add comments to the caller's latest pending pull request review.
10
+ - 4fa3667: Resolve the latest pending pull request review before submitting or deleting it.
11
+ - Updated dependencies [f2b20af]
12
+ - @shipfox/api-integration-spi@1.1.1
13
+
14
+ ## 12.3.0
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [e0110fe]
19
+ - @shipfox/api-integration-spi@1.1.0
20
+
3
21
  ## 12.2.0
4
22
 
5
23
  ### Patch Changes
@@ -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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"}
@@ -242,16 +242,19 @@ export async function mapGithubError(operation, notFoundReason = 'repository-not
242
242
  if (error instanceof GithubIntegrationProviderError) throw error;
243
243
  if (error instanceof RequestError) {
244
244
  if (error.status === 404) {
245
- throw new GithubIntegrationProviderError(notFoundReason, error.message);
245
+ throw new GithubIntegrationProviderError(notFoundReason, error.message, undefined, error.status);
246
246
  }
247
247
  if (error.status === 429 || isGithubRateLimitError(error)) {
248
- throw new GithubIntegrationProviderError('rate-limited', error.message, retryAfterSeconds(error));
248
+ throw new GithubIntegrationProviderError('rate-limited', error.message, retryAfterSeconds(error), error.status);
249
249
  }
250
250
  if (error.status === 401 || error.status === 403) {
251
- throw new GithubIntegrationProviderError('access-denied', error.message, retryAfterSeconds(error));
251
+ throw new GithubIntegrationProviderError('access-denied', error.message, retryAfterSeconds(error), error.status);
252
252
  }
253
253
  if (error.status >= 500) {
254
- throw new GithubIntegrationProviderError('provider-unavailable', error.message);
254
+ throw new GithubIntegrationProviderError('provider-unavailable', error.message, undefined, error.status);
255
+ }
256
+ if (error.status >= 400) {
257
+ throw new GithubIntegrationProviderError('provider-rejected', error.message, undefined, error.status);
255
258
  }
256
259
  }
257
260
  if (error instanceof Error && error.name === 'AbortError') {
@@ -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';\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 this.getApp().getInstallationOctokit(input.installationId);\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 this.getApp().getInstallationOctokit(input.installationId);\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 this.getApp().getInstallationOctokit(input.installationId);\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 this.getApp().getInstallationOctokit(input.installationId);\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 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.defaults({baseUrl: normalizedGithubApiBaseUrl()}),\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(notFoundReason, error.message);\n }\n if (error.status === 429 || isGithubRateLimitError(error)) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n error.message,\n retryAfterSeconds(error),\n );\n }\n if (error.status === 401 || error.status === 403) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n error.message,\n retryAfterSeconds(error),\n );\n }\n if (error.status >= 500) {\n throw new GithubIntegrationProviderError('provider-unavailable', error.message);\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","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","getInstallationOctokit","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","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 {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';\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 this.getApp().getInstallationOctokit(input.installationId);\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 this.getApp().getInstallationOctokit(input.installationId);\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 this.getApp().getInstallationOctokit(input.installationId);\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 this.getApp().getInstallationOctokit(input.installationId);\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 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.defaults({baseUrl: normalizedGithubApiBaseUrl()}),\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","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","getInstallationOctokit","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","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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"}
@@ -11,6 +11,10 @@ export interface InstallationTokenEnvelope {
11
11
  permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;
12
12
  backoffUntil?: Date | undefined;
13
13
  backoffReason?: IntegrationProviderErrorReason | undefined;
14
+ backoffError?: {
15
+ message: string;
16
+ status?: number | undefined;
17
+ } | undefined;
14
18
  }
15
19
  export type MintErrorClass = 'transient' | 'terminal';
16
20
  export interface ClassifiedMintError {
@@ -31,6 +35,6 @@ export declare function backoffActive(envelope: InstallationTokenEnvelope | unde
31
35
  export declare function classifyMintError(error: unknown): ClassifiedMintError;
32
36
  export declare function mintErrorClassForReason(reason: IntegrationProviderErrorReason): MintErrorClass;
33
37
  export declare function backoffMs(classified: ClassifiedMintError): number;
34
- export declare function providerErrorFromBackoff(reason: IntegrationProviderErrorReason, retryAfterMs: number): GithubIntegrationProviderError;
38
+ export declare function providerErrorFromBackoff(reason: IntegrationProviderErrorReason, retryAfterMs: number, backoffError?: InstallationTokenEnvelope['backoffError']): GithubIntegrationProviderError;
35
39
  export declare function toProviderError(error: unknown): GithubIntegrationProviderError;
36
40
  //# sourceMappingURL=installation-token-envelope.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"installation-token-envelope.d.ts","sourceRoot":"","sources":["../../src/api/installation-token-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAC,8BAA8B,EAAC,MAAM,iBAAiB,CAAC;AAE/D,eAAO,MAAM,uBAAuB,QAAgB,CAAC;AACrD,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAClD,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAClD,eAAO,MAAM,wBAAwB,QAAgB,CAAC;AACtD,eAAO,MAAM,mBAAmB,QAAiB,CAAC;AAqClD,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC;IACrE,YAAY,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,aAAa,CAAC,EAAE,8BAA8B,GAAG,SAAS,CAAC;CAC5D;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,UAAU,CAAC;AAEtD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,8BAA8B,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED,wBAAgB,gCAAgC,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,yBAAyB,GAAG,MAAM,CAU3F;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAkBjG;AAED,wBAAgB,MAAM,CACpB,QAAQ,EAAE,yBAAyB,GAAG,SAAS,EAC/C,GAAG,EAAE,IAAI,GACR,QAAQ,IAAI,yBAAyB,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,CAM1E;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAEhE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,IAAI,GAAG,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAE1E;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,yBAAyB,GAAG,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAMjG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAUrE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,8BAA8B,GAAG,cAAc,CAE9F;AAED,wBAAgB,SAAS,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,CAKjE;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,8BAA8B,EACtC,YAAY,EAAE,MAAM,GACnB,8BAA8B,CAMhC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,8BAA8B,CAS9E"}
1
+ {"version":3,"file":"installation-token-envelope.d.ts","sourceRoot":"","sources":["../../src/api/installation-token-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAC,8BAA8B,EAAC,MAAM,iBAAiB,CAAC;AAE/D,eAAO,MAAM,uBAAuB,QAAgB,CAAC;AACrD,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAClD,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAClD,eAAO,MAAM,wBAAwB,QAAgB,CAAC;AACtD,eAAO,MAAM,mBAAmB,QAAiB,CAAC;AA4ClD,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC;IACrE,YAAY,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,aAAa,CAAC,EAAE,8BAA8B,GAAG,SAAS,CAAC;IAC3D,YAAY,CAAC,EACT;QACE,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC7B,GACD,SAAS,CAAC;CACf;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,UAAU,CAAC;AAEtD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,8BAA8B,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED,wBAAgB,gCAAgC,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,yBAAyB,GAAG,MAAM,CAW3F;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAmBjG;AAED,wBAAgB,MAAM,CACpB,QAAQ,EAAE,yBAAyB,GAAG,SAAS,EAC/C,GAAG,EAAE,IAAI,GACR,QAAQ,IAAI,yBAAyB,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,CAM1E;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAEhE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,IAAI,GAAG,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAE1E;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,yBAAyB,GAAG,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAMjG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAUrE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,8BAA8B,GAAG,cAAc,CAE9F;AAED,wBAAgB,SAAS,CAAC,UAAU,EAAE,mBAAmB,GAAG,MAAM,CAKjE;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,8BAA8B,EACtC,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,yBAAyB,CAAC,cAAc,CAAC,GACvD,8BAA8B,CAOhC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,8BAA8B,CAc9E"}
@@ -14,14 +14,20 @@ const providerErrorReasons = [
14
14
  'rate-limited',
15
15
  'timeout',
16
16
  'provider-unavailable',
17
+ 'provider-rejected',
17
18
  'malformed-provider-response',
18
19
  'content-too-large',
19
20
  'too-many-files'
20
21
  ];
21
22
  const providerErrorReasonSchema = z.enum(providerErrorReasons);
23
+ const backoffErrorSchema = z.object({
24
+ message: z.string(),
25
+ status: z.number().int().optional()
26
+ });
22
27
  const terminalMintErrorReasons = new Set([
23
28
  'access-denied',
24
29
  'installation-not-found',
30
+ 'provider-rejected',
25
31
  'malformed-provider-response'
26
32
  ]);
27
33
  const providerErrorReasonSchemaCoversUnion = {};
@@ -35,7 +41,8 @@ const installationTokenEnvelopeSchema = z.object({
35
41
  'admin'
36
42
  ])).optional(),
37
43
  backoffUntil: z.string().datetime().optional(),
38
- backoffReason: providerErrorReasonSchema.optional()
44
+ backoffReason: providerErrorReasonSchema.optional(),
45
+ backoffError: backoffErrorSchema.optional()
39
46
  });
40
47
  export function githubInstallationTokenNamespace(installationId) {
41
48
  return `system/github/installation-token/${installationId}`;
@@ -56,6 +63,9 @@ export function encodeInstallationTokenEnvelope(envelope) {
56
63
  },
57
64
  ...envelope.backoffReason !== undefined && {
58
65
  backoffReason: envelope.backoffReason
66
+ },
67
+ ...envelope.backoffError !== undefined && {
68
+ backoffError: envelope.backoffError
59
69
  }
60
70
  });
61
71
  }
@@ -73,7 +83,8 @@ export function parseInstallationTokenEnvelope(raw) {
73
83
  expiresAt: result.data.expiresAt ? new Date(result.data.expiresAt) : undefined,
74
84
  permissions: result.data.permissions,
75
85
  backoffUntil: result.data.backoffUntil ? new Date(result.data.backoffUntil) : undefined,
76
- backoffReason: result.data.backoffReason
86
+ backoffReason: result.data.backoffReason,
87
+ backoffError: result.data.backoffError
77
88
  };
78
89
  }
79
90
  export function usable(envelope, now) {
@@ -109,13 +120,13 @@ export function backoffMs(classified) {
109
120
  const retryAfterMs = (classified.retryAfterSeconds ?? 0) * 1000;
110
121
  return Math.min(TRANSIENT_BACKOFF_MAX_MS, Math.max(TRANSIENT_BACKOFF_MIN_MS, retryAfterMs));
111
122
  }
112
- export function providerErrorFromBackoff(reason, retryAfterMs) {
113
- return new GithubIntegrationProviderError(reason, `GitHub installation token mint is backed off after ${reason}`, Math.max(1, Math.ceil(retryAfterMs / 1000)));
123
+ export function providerErrorFromBackoff(reason, retryAfterMs, backoffError) {
124
+ return new GithubIntegrationProviderError(reason, backoffError?.message ?? `GitHub installation token mint is backed off after ${reason}`, Math.max(1, Math.ceil(retryAfterMs / 1000)), backoffError?.status);
114
125
  }
115
126
  export function toProviderError(error) {
116
127
  if (error instanceof GithubIntegrationProviderError) return error;
117
128
  if (error instanceof IntegrationProviderError) {
118
- return new GithubIntegrationProviderError(error.reason, error.message, error.retryAfterSeconds);
129
+ return new GithubIntegrationProviderError(error.reason, error.message, error.retryAfterSeconds, error.status);
119
130
  }
120
131
  return new GithubIntegrationProviderError('provider-unavailable', error instanceof Error ? error.message : 'GitHub installation token mint failed');
121
132
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/installation-token-envelope.ts"],"sourcesContent":["import {\n IntegrationProviderError,\n type IntegrationProviderErrorReason,\n} from '@shipfox/api-integration-spi';\nimport {z} from 'zod';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\n\nexport const TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000;\nexport const TOKEN_VALIDITY_BUFFER_MS = 60 * 1000;\nexport const TRANSIENT_BACKOFF_MIN_MS = 30 * 1000;\nexport const TRANSIENT_BACKOFF_MAX_MS = 5 * 60 * 1000;\nexport const TERMINAL_BACKOFF_MS = 15 * 60 * 1000;\n\nconst providerErrorReasons = [\n 'repository-not-found',\n 'installation-not-found',\n 'file-not-found',\n 'access-denied',\n 'rate-limited',\n 'timeout',\n 'provider-unavailable',\n 'malformed-provider-response',\n 'content-too-large',\n 'too-many-files',\n] as const satisfies readonly IntegrationProviderErrorReason[];\n\nconst providerErrorReasonSchema = z.enum(providerErrorReasons);\nconst terminalMintErrorReasons = new Set<IntegrationProviderErrorReason>([\n 'access-denied',\n 'installation-not-found',\n 'malformed-provider-response',\n]);\n\ntype MissingProviderErrorReason = Exclude<\n IntegrationProviderErrorReason,\n (typeof providerErrorReasons)[number]\n>;\nconst providerErrorReasonSchemaCoversUnion: Record<MissingProviderErrorReason, never> = {};\nvoid providerErrorReasonSchemaCoversUnion;\n\nconst installationTokenEnvelopeSchema = z.object({\n token: z.string().min(1).optional(),\n expiresAt: z.string().datetime().optional(),\n permissions: z.record(z.string(), z.enum(['read', 'write', 'admin'])).optional(),\n backoffUntil: z.string().datetime().optional(),\n backoffReason: providerErrorReasonSchema.optional(),\n});\n\nexport interface InstallationTokenEnvelope {\n token?: string | undefined;\n expiresAt?: Date | undefined;\n permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;\n backoffUntil?: Date | undefined;\n backoffReason?: IntegrationProviderErrorReason | undefined;\n}\n\nexport type MintErrorClass = 'transient' | 'terminal';\n\nexport interface ClassifiedMintError {\n class: MintErrorClass;\n reason: IntegrationProviderErrorReason;\n retryAfterSeconds?: number | undefined;\n}\n\nexport function githubInstallationTokenNamespace(installationId: number): string {\n return `system/github/installation-token/${installationId}`;\n}\n\nexport function encodeInstallationTokenEnvelope(envelope: InstallationTokenEnvelope): string {\n return JSON.stringify({\n ...(envelope.token !== undefined && {token: envelope.token}),\n ...(envelope.expiresAt !== undefined && {expiresAt: envelope.expiresAt.toISOString()}),\n ...(envelope.permissions !== undefined && {permissions: envelope.permissions}),\n ...(envelope.backoffUntil !== undefined && {\n backoffUntil: envelope.backoffUntil.toISOString(),\n }),\n ...(envelope.backoffReason !== undefined && {backoffReason: envelope.backoffReason}),\n });\n}\n\nexport function parseInstallationTokenEnvelope(raw: string): InstallationTokenEnvelope | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n\n const result = installationTokenEnvelopeSchema.safeParse(parsed);\n if (!result.success) return undefined;\n\n return {\n token: result.data.token,\n expiresAt: result.data.expiresAt ? new Date(result.data.expiresAt) : undefined,\n permissions: result.data.permissions,\n backoffUntil: result.data.backoffUntil ? new Date(result.data.backoffUntil) : undefined,\n backoffReason: result.data.backoffReason,\n };\n}\n\nexport function usable(\n envelope: InstallationTokenEnvelope | undefined,\n now: Date,\n): envelope is InstallationTokenEnvelope & {token: string; expiresAt: Date} {\n return (\n envelope?.token !== undefined &&\n envelope.expiresAt !== undefined &&\n !needsRefresh(envelope.expiresAt, now)\n );\n}\n\nexport function needsRefresh(expiresAt: Date, now: Date): boolean {\n return expiresAt.getTime() <= now.getTime() + TOKEN_REFRESH_MARGIN_MS;\n}\n\nexport function stillValid(expiresAt: Date | undefined, now: Date): boolean {\n return expiresAt !== undefined && expiresAt.getTime() > now.getTime() + TOKEN_VALIDITY_BUFFER_MS;\n}\n\nexport function backoffActive(envelope: InstallationTokenEnvelope | undefined, now: Date): boolean {\n return (\n envelope?.backoffUntil !== undefined &&\n envelope.backoffReason !== undefined &&\n envelope.backoffUntil.getTime() > now.getTime()\n );\n}\n\nexport function classifyMintError(error: unknown): ClassifiedMintError {\n if (error instanceof IntegrationProviderError) {\n return {\n reason: error.reason,\n retryAfterSeconds: error.retryAfterSeconds,\n class: mintErrorClassForReason(error.reason),\n };\n }\n\n return {reason: 'provider-unavailable', class: 'transient'};\n}\n\nexport function mintErrorClassForReason(reason: IntegrationProviderErrorReason): MintErrorClass {\n return terminalMintErrorReasons.has(reason) ? 'terminal' : 'transient';\n}\n\nexport function backoffMs(classified: ClassifiedMintError): number {\n if (classified.class === 'terminal') return TERMINAL_BACKOFF_MS;\n\n const retryAfterMs = (classified.retryAfterSeconds ?? 0) * 1000;\n return Math.min(TRANSIENT_BACKOFF_MAX_MS, Math.max(TRANSIENT_BACKOFF_MIN_MS, retryAfterMs));\n}\n\nexport function providerErrorFromBackoff(\n reason: IntegrationProviderErrorReason,\n retryAfterMs: number,\n): GithubIntegrationProviderError {\n return new GithubIntegrationProviderError(\n reason,\n `GitHub installation token mint is backed off after ${reason}`,\n Math.max(1, Math.ceil(retryAfterMs / 1000)),\n );\n}\n\nexport function toProviderError(error: unknown): GithubIntegrationProviderError {\n if (error instanceof GithubIntegrationProviderError) return error;\n if (error instanceof IntegrationProviderError) {\n return new GithubIntegrationProviderError(error.reason, error.message, error.retryAfterSeconds);\n }\n return new GithubIntegrationProviderError(\n 'provider-unavailable',\n error instanceof Error ? error.message : 'GitHub installation token mint failed',\n );\n}\n"],"names":["IntegrationProviderError","z","GithubIntegrationProviderError","TOKEN_REFRESH_MARGIN_MS","TOKEN_VALIDITY_BUFFER_MS","TRANSIENT_BACKOFF_MIN_MS","TRANSIENT_BACKOFF_MAX_MS","TERMINAL_BACKOFF_MS","providerErrorReasons","providerErrorReasonSchema","enum","terminalMintErrorReasons","Set","providerErrorReasonSchemaCoversUnion","installationTokenEnvelopeSchema","object","token","string","min","optional","expiresAt","datetime","permissions","record","backoffUntil","backoffReason","githubInstallationTokenNamespace","installationId","encodeInstallationTokenEnvelope","envelope","JSON","stringify","undefined","toISOString","parseInstallationTokenEnvelope","raw","parsed","parse","result","safeParse","success","data","Date","usable","now","needsRefresh","getTime","stillValid","backoffActive","classifyMintError","error","reason","retryAfterSeconds","class","mintErrorClassForReason","has","backoffMs","classified","retryAfterMs","Math","max","providerErrorFromBackoff","ceil","toProviderError","message","Error"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/api/installation-token-envelope.ts"],"sourcesContent":["import {\n IntegrationProviderError,\n type IntegrationProviderErrorReason,\n} from '@shipfox/api-integration-spi';\nimport {z} from 'zod';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\n\nexport const TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000;\nexport const TOKEN_VALIDITY_BUFFER_MS = 60 * 1000;\nexport const TRANSIENT_BACKOFF_MIN_MS = 30 * 1000;\nexport const TRANSIENT_BACKOFF_MAX_MS = 5 * 60 * 1000;\nexport const TERMINAL_BACKOFF_MS = 15 * 60 * 1000;\n\nconst providerErrorReasons = [\n 'repository-not-found',\n 'installation-not-found',\n 'file-not-found',\n 'access-denied',\n 'rate-limited',\n 'timeout',\n 'provider-unavailable',\n 'provider-rejected',\n 'malformed-provider-response',\n 'content-too-large',\n 'too-many-files',\n] as const satisfies readonly IntegrationProviderErrorReason[];\n\nconst providerErrorReasonSchema = z.enum(providerErrorReasons);\nconst backoffErrorSchema = z.object({\n message: z.string(),\n status: z.number().int().optional(),\n});\nconst terminalMintErrorReasons = new Set<IntegrationProviderErrorReason>([\n 'access-denied',\n 'installation-not-found',\n 'provider-rejected',\n 'malformed-provider-response',\n]);\n\ntype MissingProviderErrorReason = Exclude<\n IntegrationProviderErrorReason,\n (typeof providerErrorReasons)[number]\n>;\nconst providerErrorReasonSchemaCoversUnion: Record<MissingProviderErrorReason, never> = {};\nvoid providerErrorReasonSchemaCoversUnion;\n\nconst installationTokenEnvelopeSchema = z.object({\n token: z.string().min(1).optional(),\n expiresAt: z.string().datetime().optional(),\n permissions: z.record(z.string(), z.enum(['read', 'write', 'admin'])).optional(),\n backoffUntil: z.string().datetime().optional(),\n backoffReason: providerErrorReasonSchema.optional(),\n backoffError: backoffErrorSchema.optional(),\n});\n\nexport interface InstallationTokenEnvelope {\n token?: string | undefined;\n expiresAt?: Date | undefined;\n permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;\n backoffUntil?: Date | undefined;\n backoffReason?: IntegrationProviderErrorReason | undefined;\n backoffError?:\n | {\n message: string;\n status?: number | undefined;\n }\n | undefined;\n}\n\nexport type MintErrorClass = 'transient' | 'terminal';\n\nexport interface ClassifiedMintError {\n class: MintErrorClass;\n reason: IntegrationProviderErrorReason;\n retryAfterSeconds?: number | undefined;\n}\n\nexport function githubInstallationTokenNamespace(installationId: number): string {\n return `system/github/installation-token/${installationId}`;\n}\n\nexport function encodeInstallationTokenEnvelope(envelope: InstallationTokenEnvelope): string {\n return JSON.stringify({\n ...(envelope.token !== undefined && {token: envelope.token}),\n ...(envelope.expiresAt !== undefined && {expiresAt: envelope.expiresAt.toISOString()}),\n ...(envelope.permissions !== undefined && {permissions: envelope.permissions}),\n ...(envelope.backoffUntil !== undefined && {\n backoffUntil: envelope.backoffUntil.toISOString(),\n }),\n ...(envelope.backoffReason !== undefined && {backoffReason: envelope.backoffReason}),\n ...(envelope.backoffError !== undefined && {backoffError: envelope.backoffError}),\n });\n}\n\nexport function parseInstallationTokenEnvelope(raw: string): InstallationTokenEnvelope | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n\n const result = installationTokenEnvelopeSchema.safeParse(parsed);\n if (!result.success) return undefined;\n\n return {\n token: result.data.token,\n expiresAt: result.data.expiresAt ? new Date(result.data.expiresAt) : undefined,\n permissions: result.data.permissions,\n backoffUntil: result.data.backoffUntil ? new Date(result.data.backoffUntil) : undefined,\n backoffReason: result.data.backoffReason,\n backoffError: result.data.backoffError,\n };\n}\n\nexport function usable(\n envelope: InstallationTokenEnvelope | undefined,\n now: Date,\n): envelope is InstallationTokenEnvelope & {token: string; expiresAt: Date} {\n return (\n envelope?.token !== undefined &&\n envelope.expiresAt !== undefined &&\n !needsRefresh(envelope.expiresAt, now)\n );\n}\n\nexport function needsRefresh(expiresAt: Date, now: Date): boolean {\n return expiresAt.getTime() <= now.getTime() + TOKEN_REFRESH_MARGIN_MS;\n}\n\nexport function stillValid(expiresAt: Date | undefined, now: Date): boolean {\n return expiresAt !== undefined && expiresAt.getTime() > now.getTime() + TOKEN_VALIDITY_BUFFER_MS;\n}\n\nexport function backoffActive(envelope: InstallationTokenEnvelope | undefined, now: Date): boolean {\n return (\n envelope?.backoffUntil !== undefined &&\n envelope.backoffReason !== undefined &&\n envelope.backoffUntil.getTime() > now.getTime()\n );\n}\n\nexport function classifyMintError(error: unknown): ClassifiedMintError {\n if (error instanceof IntegrationProviderError) {\n return {\n reason: error.reason,\n retryAfterSeconds: error.retryAfterSeconds,\n class: mintErrorClassForReason(error.reason),\n };\n }\n\n return {reason: 'provider-unavailable', class: 'transient'};\n}\n\nexport function mintErrorClassForReason(reason: IntegrationProviderErrorReason): MintErrorClass {\n return terminalMintErrorReasons.has(reason) ? 'terminal' : 'transient';\n}\n\nexport function backoffMs(classified: ClassifiedMintError): number {\n if (classified.class === 'terminal') return TERMINAL_BACKOFF_MS;\n\n const retryAfterMs = (classified.retryAfterSeconds ?? 0) * 1000;\n return Math.min(TRANSIENT_BACKOFF_MAX_MS, Math.max(TRANSIENT_BACKOFF_MIN_MS, retryAfterMs));\n}\n\nexport function providerErrorFromBackoff(\n reason: IntegrationProviderErrorReason,\n retryAfterMs: number,\n backoffError?: InstallationTokenEnvelope['backoffError'],\n): GithubIntegrationProviderError {\n return new GithubIntegrationProviderError(\n reason,\n backoffError?.message ?? `GitHub installation token mint is backed off after ${reason}`,\n Math.max(1, Math.ceil(retryAfterMs / 1000)),\n backoffError?.status,\n );\n}\n\nexport function toProviderError(error: unknown): GithubIntegrationProviderError {\n if (error instanceof GithubIntegrationProviderError) return error;\n if (error instanceof IntegrationProviderError) {\n return new GithubIntegrationProviderError(\n error.reason,\n error.message,\n error.retryAfterSeconds,\n error.status,\n );\n }\n return new GithubIntegrationProviderError(\n 'provider-unavailable',\n error instanceof Error ? error.message : 'GitHub installation token mint failed',\n );\n}\n"],"names":["IntegrationProviderError","z","GithubIntegrationProviderError","TOKEN_REFRESH_MARGIN_MS","TOKEN_VALIDITY_BUFFER_MS","TRANSIENT_BACKOFF_MIN_MS","TRANSIENT_BACKOFF_MAX_MS","TERMINAL_BACKOFF_MS","providerErrorReasons","providerErrorReasonSchema","enum","backoffErrorSchema","object","message","string","status","number","int","optional","terminalMintErrorReasons","Set","providerErrorReasonSchemaCoversUnion","installationTokenEnvelopeSchema","token","min","expiresAt","datetime","permissions","record","backoffUntil","backoffReason","backoffError","githubInstallationTokenNamespace","installationId","encodeInstallationTokenEnvelope","envelope","JSON","stringify","undefined","toISOString","parseInstallationTokenEnvelope","raw","parsed","parse","result","safeParse","success","data","Date","usable","now","needsRefresh","getTime","stillValid","backoffActive","classifyMintError","error","reason","retryAfterSeconds","class","mintErrorClassForReason","has","backoffMs","classified","retryAfterMs","Math","max","providerErrorFromBackoff","ceil","toProviderError","Error"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"shared-installation-token-cache.d.ts","sourceRoot":"","sources":["../../src/api/shared-installation-token-cache.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,6BAA6B,EAAC,MAAM,aAAa,CAAC;AAC/D,OAAO,EAIL,KAAK,yBAAyB,EAO/B,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,sBAAsB;IACrC,SAAS,CACP,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAC,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAC,CAAC;AAE5F,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1E,KAAK,CACH,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,mCAAmC;IAClD,WAAW,EAAE,4BAA4B,CAAC;IAC1C,QAAQ,EAAE,CAAC,CAAC,EACV,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KACjB,OAAO,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,kBAAkB,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACpD,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAMD,qBAAa,4BAA6B,YAAW,sBAAsB;IAQ7D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiE;IAC9F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,YAA6B,OAAO,EAAE,mCAAmC,EAMxE;IAEK,SAAS,CACb,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAcxC;YAEa,aAAa;YA4Gb,gBAAgB;YA0ChB,UAAU;YAcV,YAAY;YAcZ,aAAa;YAQb,kBAAkB;CAYjC"}
1
+ {"version":3,"file":"shared-installation-token-cache.d.ts","sourceRoot":"","sources":["../../src/api/shared-installation-token-cache.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,6BAA6B,EAAC,MAAM,aAAa,CAAC;AAC/D,OAAO,EAIL,KAAK,yBAAyB,EAO/B,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,sBAAsB;IACrC,SAAS,CACP,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAC,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAC,CAAC;AAE5F,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1E,KAAK,CACH,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,mCAAmC;IAClD,WAAW,EAAE,4BAA4B,CAAC;IAC1C,QAAQ,EAAE,CAAC,CAAC,EACV,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KACjB,OAAO,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,kBAAkB,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACpD,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAMD,qBAAa,4BAA6B,YAAW,sBAAsB;IAQ7D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiE;IAC9F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,YAA6B,OAAO,EAAE,mCAAmC,EAMxE;IAEK,SAAS,CACb,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,OAAO,CAAC,6BAA6B,CAAC,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAcxC;YAEa,aAAa;YAiHb,gBAAgB;YA4ChB,UAAU;YAcV,YAAY;YAcZ,aAAa;YAQb,kBAAkB;CAYjC"}
@@ -55,7 +55,7 @@ export class SharedInstallationTokenCache {
55
55
  return tokenFromEnvelope(envelope);
56
56
  }
57
57
  recordInstallationTokenLookup('backoff');
58
- throw providerErrorFromBackoff(envelope?.backoffReason ?? 'provider-unavailable', (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime());
58
+ throw providerErrorFromBackoff(envelope?.backoffReason ?? 'provider-unavailable', (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime(), envelope?.backoffError);
59
59
  }
60
60
  let token;
61
61
  try {
@@ -73,7 +73,13 @@ export class SharedInstallationTokenCache {
73
73
  expiresAt: envelope?.expiresAt,
74
74
  permissions: envelope?.permissions,
75
75
  backoffUntil: until,
76
- backoffReason: classified.reason
76
+ backoffReason: classified.reason,
77
+ backoffError: {
78
+ message: providerError.message,
79
+ ...providerError.status === undefined ? {} : {
80
+ status: providerError.status
81
+ }
82
+ }
77
83
  }).catch((writeError)=>{
78
84
  logger().warn({
79
85
  installationId: params.installationId,
@@ -142,7 +148,7 @@ export class SharedInstallationTokenCache {
142
148
  }
143
149
  if (activeBackoff(params.envelope, initialNow)) {
144
150
  recordInstallationTokenLookup('backoff');
145
- throw providerErrorFromBackoff(params.envelope.backoffReason, params.envelope.backoffUntil.getTime() - initialNow.getTime());
151
+ throw providerErrorFromBackoff(params.envelope.backoffReason, params.envelope.backoffUntil.getTime() - initialNow.getTime(), params.envelope.backoffError);
146
152
  }
147
153
  for (const delayMs of this.pollDelaysMs){
148
154
  await this.sleep(delayMs);
@@ -154,7 +160,7 @@ export class SharedInstallationTokenCache {
154
160
  }
155
161
  if (backoffActive(envelope, now)) {
156
162
  recordInstallationTokenLookup('backoff');
157
- throw providerErrorFromBackoff(envelope?.backoffReason ?? 'provider-unavailable', (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime());
163
+ throw providerErrorFromBackoff(envelope?.backoffReason ?? 'provider-unavailable', (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime(), envelope?.backoffError);
158
164
  }
159
165
  }
160
166
  throw new GithubIntegrationProviderError('provider-unavailable', 'GitHub installation token mint is still in progress', 1);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/api/shared-installation-token-cache.ts"],"sourcesContent":["import {setTimeout as sleepTimeout} from 'node:timers/promises';\nimport {reportError} from '@shipfox/node-error-monitoring';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\nimport {\n recordInstallationTokenBackoff,\n recordInstallationTokenLookup,\n recordInstallationTokenMint,\n} from '#metrics/index.js';\nimport type {GithubInstallationAccessToken} from './client.js';\nimport {\n backoffActive,\n backoffMs,\n classifyMintError,\n type InstallationTokenEnvelope,\n mintErrorClassForReason,\n parseInstallationTokenEnvelope,\n providerErrorFromBackoff,\n stillValid,\n toProviderError,\n usable,\n} from './installation-token-envelope.js';\n\nexport interface InstallationTokenCache {\n getOrMint(\n installationId: number,\n mint: () => Promise<GithubInstallationAccessToken>,\n ): Promise<GithubInstallationAccessToken>;\n}\n\nexport type InstallationTokenLockResult<T> = {acquired: true; value: T} | {acquired: false};\n\nexport interface InstallationTokenSecretStore {\n read(workspaceId: string, installationId: number): Promise<string | null>;\n write(\n workspaceId: string,\n installationId: number,\n envelope: InstallationTokenEnvelope,\n ): Promise<void>;\n}\n\nexport interface SharedInstallationTokenCacheOptions {\n secretStore: InstallationTokenSecretStore;\n withLock: <T>(\n installationId: number,\n fn: () => Promise<T>,\n ) => Promise<InstallationTokenLockResult<T>>;\n resolveWorkspaceId: (installationId: number) => Promise<string>;\n now?: (() => Date) | undefined;\n sleep?: ((ms: number) => Promise<void>) | undefined;\n pollDelaysMs?: number[] | undefined;\n workspaceCacheTtlMs?: number | undefined;\n mintTimeoutMs?: number | undefined;\n}\n\nconst DEFAULT_POLL_DELAYS_MS = [100, 200, 400, 500, 800];\nconst DEFAULT_WORKSPACE_CACHE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_MINT_TIMEOUT_MS = 30 * 1000;\n\nexport class SharedInstallationTokenCache implements InstallationTokenCache {\n private readonly workspaceIds = new Map<number, {workspaceId: string; expiresAtMs: number}>();\n private readonly now: () => Date;\n private readonly sleep: (ms: number) => Promise<void>;\n private readonly pollDelaysMs: number[];\n private readonly workspaceCacheTtlMs: number;\n private readonly mintTimeoutMs: number;\n\n constructor(private readonly options: SharedInstallationTokenCacheOptions) {\n this.now = options.now ?? (() => new Date());\n this.sleep = options.sleep ?? ((ms) => sleepTimeout(ms).then(() => undefined));\n this.pollDelaysMs = options.pollDelaysMs ?? DEFAULT_POLL_DELAYS_MS;\n this.workspaceCacheTtlMs = options.workspaceCacheTtlMs ?? DEFAULT_WORKSPACE_CACHE_TTL_MS;\n this.mintTimeoutMs = options.mintTimeoutMs ?? DEFAULT_MINT_TIMEOUT_MS;\n }\n\n async getOrMint(\n installationId: number,\n mint: () => Promise<GithubInstallationAccessToken>,\n ): Promise<GithubInstallationAccessToken> {\n const workspaceId = await this.resolveWorkspaceId(installationId);\n const envelope = await this.readEnvelope(workspaceId, installationId);\n if (usable(envelope, this.now())) {\n recordInstallationTokenLookup('db-hit');\n return tokenFromEnvelope(envelope);\n }\n\n const result = await this.options.withLock(installationId, () =>\n this.mintUnderLock({workspaceId, installationId, mint}),\n );\n if (result.acquired) return result.value;\n\n return await this.serveStaleOrPoll({workspaceId, installationId, envelope});\n }\n\n private async mintUnderLock(params: {\n workspaceId: string;\n installationId: number;\n mint: () => Promise<GithubInstallationAccessToken>;\n }): Promise<GithubInstallationAccessToken> {\n const envelope = await this.readEnvelope(params.workspaceId, params.installationId);\n const now = this.now();\n if (usable(envelope, now)) {\n recordInstallationTokenLookup('db-hit');\n return tokenFromEnvelope(envelope);\n }\n\n if (activeBackoff(envelope, now)) {\n if (canServeStale(envelope, now)) {\n recordInstallationTokenLookup('served-stale');\n return tokenFromEnvelope(envelope);\n }\n recordInstallationTokenLookup('backoff');\n throw providerErrorFromBackoff(\n envelope?.backoffReason ?? 'provider-unavailable',\n (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime(),\n );\n }\n\n let token: GithubInstallationAccessToken;\n try {\n token = await this.recordMint(params.mint);\n } catch (error) {\n const providerError = toProviderError(error);\n const classified = classifyMintError(providerError);\n const until = new Date(this.now().getTime() + backoffMs(classified));\n recordInstallationTokenBackoff({reason: classified.reason, class: classified.class});\n\n await this.writeEnvelope(params.workspaceId, params.installationId, {\n token: envelope?.token,\n expiresAt: envelope?.expiresAt,\n permissions: envelope?.permissions,\n backoffUntil: until,\n backoffReason: classified.reason,\n }).catch((writeError) => {\n logger().warn(\n {installationId: params.installationId, reason: classified.reason, error: writeError},\n 'github installation token backoff write failed',\n );\n reportError(writeError, {\n boundary: 'integration.cache',\n operation: 'write-backoff-envelope',\n extra: {installationId: params.installationId},\n });\n });\n\n if (\n classified.class === 'transient' &&\n envelope?.token &&\n stillValid(envelope.expiresAt, this.now())\n ) {\n logger().warn(\n {\n installationId: params.installationId,\n expiresAt: envelope.expiresAt?.toISOString(),\n reason: classified.reason,\n backoffUntil: until.toISOString(),\n },\n 'github installation token mint failed; serving stale token',\n );\n recordInstallationTokenLookup('served-stale');\n return tokenFromEnvelope(envelope);\n }\n\n logger().warn(\n {\n installationId: params.installationId,\n reason: classified.reason,\n backoffUntil: until.toISOString(),\n error: providerError,\n },\n 'github installation token mint failed; backoff recorded',\n );\n recordInstallationTokenLookup('backoff');\n throw providerError;\n }\n\n try {\n await this.writeEnvelope(params.workspaceId, params.installationId, {\n token: token.token,\n expiresAt: token.expiresAt,\n permissions: token.permissions,\n });\n } catch (error) {\n logger().warn(\n {installationId: params.installationId, expiresAt: token.expiresAt.toISOString(), error},\n 'github installation token cache write failed after mint',\n );\n reportError(error, {\n boundary: 'integration.cache',\n operation: 'write-minted-token',\n extra: {installationId: params.installationId},\n });\n }\n\n logger().info(\n {installationId: params.installationId, expiresAt: token.expiresAt.toISOString()},\n 'github installation token minted',\n );\n recordInstallationTokenLookup('minted');\n return token;\n }\n\n private async serveStaleOrPoll(params: {\n workspaceId: string;\n installationId: number;\n envelope: InstallationTokenEnvelope | undefined;\n }): Promise<GithubInstallationAccessToken> {\n const initialNow = this.now();\n if (canServeStale(params.envelope, initialNow)) {\n recordInstallationTokenLookup('served-stale');\n return tokenFromEnvelope(params.envelope);\n }\n if (activeBackoff(params.envelope, initialNow)) {\n recordInstallationTokenLookup('backoff');\n throw providerErrorFromBackoff(\n params.envelope.backoffReason,\n params.envelope.backoffUntil.getTime() - initialNow.getTime(),\n );\n }\n\n for (const delayMs of this.pollDelaysMs) {\n await this.sleep(delayMs);\n const envelope = await this.readEnvelope(params.workspaceId, params.installationId);\n const now = this.now();\n if (usable(envelope, now)) {\n recordInstallationTokenLookup('contended-poll');\n return tokenFromEnvelope(envelope);\n }\n if (backoffActive(envelope, now)) {\n recordInstallationTokenLookup('backoff');\n throw providerErrorFromBackoff(\n envelope?.backoffReason ?? 'provider-unavailable',\n (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime(),\n );\n }\n }\n\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n 'GitHub installation token mint is still in progress',\n 1,\n );\n }\n\n private async recordMint(\n mint: () => Promise<GithubInstallationAccessToken>,\n ): Promise<GithubInstallationAccessToken> {\n const startedAt = Date.now();\n try {\n const token = await withTimeout(mint(), this.mintTimeoutMs);\n recordInstallationTokenMint({outcome: 'success', durationMs: Date.now() - startedAt});\n return token;\n } catch (error) {\n recordInstallationTokenMint({outcome: 'failure', durationMs: Date.now() - startedAt});\n throw error;\n }\n }\n\n private async readEnvelope(\n workspaceId: string,\n installationId: number,\n ): Promise<InstallationTokenEnvelope | undefined> {\n const raw = await this.options.secretStore.read(workspaceId, installationId);\n if (raw === null) return undefined;\n\n const envelope = parseInstallationTokenEnvelope(raw);\n if (envelope === undefined) {\n logger().warn({installationId}, 'github installation token cache envelope failed to decode');\n }\n return envelope;\n }\n\n private async writeEnvelope(\n workspaceId: string,\n installationId: number,\n envelope: InstallationTokenEnvelope,\n ): Promise<void> {\n await this.options.secretStore.write(workspaceId, installationId, envelope);\n }\n\n private async resolveWorkspaceId(installationId: number): Promise<string> {\n const nowMs = this.now().getTime();\n const cached = this.workspaceIds.get(installationId);\n if (cached && cached.expiresAtMs > nowMs) return cached.workspaceId;\n\n const workspaceId = await this.options.resolveWorkspaceId(installationId);\n this.workspaceIds.set(installationId, {\n workspaceId,\n expiresAtMs: nowMs + this.workspaceCacheTtlMs,\n });\n return workspaceId;\n }\n}\n\ntype ActiveBackoffEnvelope = InstallationTokenEnvelope & {\n backoffUntil: Date;\n backoffReason: NonNullable<InstallationTokenEnvelope['backoffReason']>;\n};\n\ntype TokenEnvelope = InstallationTokenEnvelope & {token: string; expiresAt: Date};\n\nfunction activeBackoff(\n envelope: InstallationTokenEnvelope | undefined,\n now: Date,\n): envelope is ActiveBackoffEnvelope {\n return (\n backoffActive(envelope, now) &&\n envelope?.backoffUntil !== undefined &&\n envelope.backoffReason !== undefined\n );\n}\n\nfunction canServeStale(\n envelope: InstallationTokenEnvelope | undefined,\n now: Date,\n): envelope is TokenEnvelope {\n const terminalBackoff =\n activeBackoff(envelope, now) && mintErrorClassForReason(envelope.backoffReason) === 'terminal';\n return (\n envelope?.token !== undefined &&\n envelope.expiresAt !== undefined &&\n stillValid(envelope.expiresAt, now) &&\n !terminalBackoff\n );\n}\n\nfunction tokenFromEnvelope(envelope: InstallationTokenEnvelope): GithubInstallationAccessToken {\n if (!envelope.token || !envelope.expiresAt) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation token cache envelope is missing a token or expiry',\n );\n }\n return {\n token: envelope.token,\n expiresAt: envelope.expiresAt,\n ...(envelope.permissions === undefined ? {} : {permissions: envelope.permissions}),\n };\n}\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<T>((_, reject) => {\n timer = setTimeout(() => {\n reject(\n new GithubIntegrationProviderError(\n 'timeout',\n 'Timed out minting GitHub installation access token',\n ),\n );\n }, timeoutMs);\n });\n\n return Promise.race([promise, timeout]).finally(() => {\n if (timer) clearTimeout(timer);\n });\n}\n"],"names":["setTimeout","sleepTimeout","reportError","logger","GithubIntegrationProviderError","recordInstallationTokenBackoff","recordInstallationTokenLookup","recordInstallationTokenMint","backoffActive","backoffMs","classifyMintError","mintErrorClassForReason","parseInstallationTokenEnvelope","providerErrorFromBackoff","stillValid","toProviderError","usable","DEFAULT_POLL_DELAYS_MS","DEFAULT_WORKSPACE_CACHE_TTL_MS","DEFAULT_MINT_TIMEOUT_MS","SharedInstallationTokenCache","options","workspaceIds","Map","now","Date","sleep","ms","then","undefined","pollDelaysMs","workspaceCacheTtlMs","mintTimeoutMs","getOrMint","installationId","mint","workspaceId","resolveWorkspaceId","envelope","readEnvelope","tokenFromEnvelope","result","withLock","mintUnderLock","acquired","value","serveStaleOrPoll","params","activeBackoff","canServeStale","backoffReason","backoffUntil","getTime","token","recordMint","error","providerError","classified","until","reason","class","writeEnvelope","expiresAt","permissions","catch","writeError","warn","boundary","operation","extra","toISOString","info","initialNow","delayMs","startedAt","withTimeout","outcome","durationMs","raw","secretStore","read","write","nowMs","cached","get","expiresAtMs","set","terminalBackoff","promise","timeoutMs","timer","timeout","Promise","_","reject","race","finally","clearTimeout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/api/shared-installation-token-cache.ts"],"sourcesContent":["import {setTimeout as sleepTimeout} from 'node:timers/promises';\nimport {reportError} from '@shipfox/node-error-monitoring';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\nimport {\n recordInstallationTokenBackoff,\n recordInstallationTokenLookup,\n recordInstallationTokenMint,\n} from '#metrics/index.js';\nimport type {GithubInstallationAccessToken} from './client.js';\nimport {\n backoffActive,\n backoffMs,\n classifyMintError,\n type InstallationTokenEnvelope,\n mintErrorClassForReason,\n parseInstallationTokenEnvelope,\n providerErrorFromBackoff,\n stillValid,\n toProviderError,\n usable,\n} from './installation-token-envelope.js';\n\nexport interface InstallationTokenCache {\n getOrMint(\n installationId: number,\n mint: () => Promise<GithubInstallationAccessToken>,\n ): Promise<GithubInstallationAccessToken>;\n}\n\nexport type InstallationTokenLockResult<T> = {acquired: true; value: T} | {acquired: false};\n\nexport interface InstallationTokenSecretStore {\n read(workspaceId: string, installationId: number): Promise<string | null>;\n write(\n workspaceId: string,\n installationId: number,\n envelope: InstallationTokenEnvelope,\n ): Promise<void>;\n}\n\nexport interface SharedInstallationTokenCacheOptions {\n secretStore: InstallationTokenSecretStore;\n withLock: <T>(\n installationId: number,\n fn: () => Promise<T>,\n ) => Promise<InstallationTokenLockResult<T>>;\n resolveWorkspaceId: (installationId: number) => Promise<string>;\n now?: (() => Date) | undefined;\n sleep?: ((ms: number) => Promise<void>) | undefined;\n pollDelaysMs?: number[] | undefined;\n workspaceCacheTtlMs?: number | undefined;\n mintTimeoutMs?: number | undefined;\n}\n\nconst DEFAULT_POLL_DELAYS_MS = [100, 200, 400, 500, 800];\nconst DEFAULT_WORKSPACE_CACHE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_MINT_TIMEOUT_MS = 30 * 1000;\n\nexport class SharedInstallationTokenCache implements InstallationTokenCache {\n private readonly workspaceIds = new Map<number, {workspaceId: string; expiresAtMs: number}>();\n private readonly now: () => Date;\n private readonly sleep: (ms: number) => Promise<void>;\n private readonly pollDelaysMs: number[];\n private readonly workspaceCacheTtlMs: number;\n private readonly mintTimeoutMs: number;\n\n constructor(private readonly options: SharedInstallationTokenCacheOptions) {\n this.now = options.now ?? (() => new Date());\n this.sleep = options.sleep ?? ((ms) => sleepTimeout(ms).then(() => undefined));\n this.pollDelaysMs = options.pollDelaysMs ?? DEFAULT_POLL_DELAYS_MS;\n this.workspaceCacheTtlMs = options.workspaceCacheTtlMs ?? DEFAULT_WORKSPACE_CACHE_TTL_MS;\n this.mintTimeoutMs = options.mintTimeoutMs ?? DEFAULT_MINT_TIMEOUT_MS;\n }\n\n async getOrMint(\n installationId: number,\n mint: () => Promise<GithubInstallationAccessToken>,\n ): Promise<GithubInstallationAccessToken> {\n const workspaceId = await this.resolveWorkspaceId(installationId);\n const envelope = await this.readEnvelope(workspaceId, installationId);\n if (usable(envelope, this.now())) {\n recordInstallationTokenLookup('db-hit');\n return tokenFromEnvelope(envelope);\n }\n\n const result = await this.options.withLock(installationId, () =>\n this.mintUnderLock({workspaceId, installationId, mint}),\n );\n if (result.acquired) return result.value;\n\n return await this.serveStaleOrPoll({workspaceId, installationId, envelope});\n }\n\n private async mintUnderLock(params: {\n workspaceId: string;\n installationId: number;\n mint: () => Promise<GithubInstallationAccessToken>;\n }): Promise<GithubInstallationAccessToken> {\n const envelope = await this.readEnvelope(params.workspaceId, params.installationId);\n const now = this.now();\n if (usable(envelope, now)) {\n recordInstallationTokenLookup('db-hit');\n return tokenFromEnvelope(envelope);\n }\n\n if (activeBackoff(envelope, now)) {\n if (canServeStale(envelope, now)) {\n recordInstallationTokenLookup('served-stale');\n return tokenFromEnvelope(envelope);\n }\n recordInstallationTokenLookup('backoff');\n throw providerErrorFromBackoff(\n envelope?.backoffReason ?? 'provider-unavailable',\n (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime(),\n envelope?.backoffError,\n );\n }\n\n let token: GithubInstallationAccessToken;\n try {\n token = await this.recordMint(params.mint);\n } catch (error) {\n const providerError = toProviderError(error);\n const classified = classifyMintError(providerError);\n const until = new Date(this.now().getTime() + backoffMs(classified));\n recordInstallationTokenBackoff({reason: classified.reason, class: classified.class});\n\n await this.writeEnvelope(params.workspaceId, params.installationId, {\n token: envelope?.token,\n expiresAt: envelope?.expiresAt,\n permissions: envelope?.permissions,\n backoffUntil: until,\n backoffReason: classified.reason,\n backoffError: {\n message: providerError.message,\n ...(providerError.status === undefined ? {} : {status: providerError.status}),\n },\n }).catch((writeError) => {\n logger().warn(\n {installationId: params.installationId, reason: classified.reason, error: writeError},\n 'github installation token backoff write failed',\n );\n reportError(writeError, {\n boundary: 'integration.cache',\n operation: 'write-backoff-envelope',\n extra: {installationId: params.installationId},\n });\n });\n\n if (\n classified.class === 'transient' &&\n envelope?.token &&\n stillValid(envelope.expiresAt, this.now())\n ) {\n logger().warn(\n {\n installationId: params.installationId,\n expiresAt: envelope.expiresAt?.toISOString(),\n reason: classified.reason,\n backoffUntil: until.toISOString(),\n },\n 'github installation token mint failed; serving stale token',\n );\n recordInstallationTokenLookup('served-stale');\n return tokenFromEnvelope(envelope);\n }\n\n logger().warn(\n {\n installationId: params.installationId,\n reason: classified.reason,\n backoffUntil: until.toISOString(),\n error: providerError,\n },\n 'github installation token mint failed; backoff recorded',\n );\n recordInstallationTokenLookup('backoff');\n throw providerError;\n }\n\n try {\n await this.writeEnvelope(params.workspaceId, params.installationId, {\n token: token.token,\n expiresAt: token.expiresAt,\n permissions: token.permissions,\n });\n } catch (error) {\n logger().warn(\n {installationId: params.installationId, expiresAt: token.expiresAt.toISOString(), error},\n 'github installation token cache write failed after mint',\n );\n reportError(error, {\n boundary: 'integration.cache',\n operation: 'write-minted-token',\n extra: {installationId: params.installationId},\n });\n }\n\n logger().info(\n {installationId: params.installationId, expiresAt: token.expiresAt.toISOString()},\n 'github installation token minted',\n );\n recordInstallationTokenLookup('minted');\n return token;\n }\n\n private async serveStaleOrPoll(params: {\n workspaceId: string;\n installationId: number;\n envelope: InstallationTokenEnvelope | undefined;\n }): Promise<GithubInstallationAccessToken> {\n const initialNow = this.now();\n if (canServeStale(params.envelope, initialNow)) {\n recordInstallationTokenLookup('served-stale');\n return tokenFromEnvelope(params.envelope);\n }\n if (activeBackoff(params.envelope, initialNow)) {\n recordInstallationTokenLookup('backoff');\n throw providerErrorFromBackoff(\n params.envelope.backoffReason,\n params.envelope.backoffUntil.getTime() - initialNow.getTime(),\n params.envelope.backoffError,\n );\n }\n\n for (const delayMs of this.pollDelaysMs) {\n await this.sleep(delayMs);\n const envelope = await this.readEnvelope(params.workspaceId, params.installationId);\n const now = this.now();\n if (usable(envelope, now)) {\n recordInstallationTokenLookup('contended-poll');\n return tokenFromEnvelope(envelope);\n }\n if (backoffActive(envelope, now)) {\n recordInstallationTokenLookup('backoff');\n throw providerErrorFromBackoff(\n envelope?.backoffReason ?? 'provider-unavailable',\n (envelope?.backoffUntil?.getTime() ?? now.getTime()) - now.getTime(),\n envelope?.backoffError,\n );\n }\n }\n\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n 'GitHub installation token mint is still in progress',\n 1,\n );\n }\n\n private async recordMint(\n mint: () => Promise<GithubInstallationAccessToken>,\n ): Promise<GithubInstallationAccessToken> {\n const startedAt = Date.now();\n try {\n const token = await withTimeout(mint(), this.mintTimeoutMs);\n recordInstallationTokenMint({outcome: 'success', durationMs: Date.now() - startedAt});\n return token;\n } catch (error) {\n recordInstallationTokenMint({outcome: 'failure', durationMs: Date.now() - startedAt});\n throw error;\n }\n }\n\n private async readEnvelope(\n workspaceId: string,\n installationId: number,\n ): Promise<InstallationTokenEnvelope | undefined> {\n const raw = await this.options.secretStore.read(workspaceId, installationId);\n if (raw === null) return undefined;\n\n const envelope = parseInstallationTokenEnvelope(raw);\n if (envelope === undefined) {\n logger().warn({installationId}, 'github installation token cache envelope failed to decode');\n }\n return envelope;\n }\n\n private async writeEnvelope(\n workspaceId: string,\n installationId: number,\n envelope: InstallationTokenEnvelope,\n ): Promise<void> {\n await this.options.secretStore.write(workspaceId, installationId, envelope);\n }\n\n private async resolveWorkspaceId(installationId: number): Promise<string> {\n const nowMs = this.now().getTime();\n const cached = this.workspaceIds.get(installationId);\n if (cached && cached.expiresAtMs > nowMs) return cached.workspaceId;\n\n const workspaceId = await this.options.resolveWorkspaceId(installationId);\n this.workspaceIds.set(installationId, {\n workspaceId,\n expiresAtMs: nowMs + this.workspaceCacheTtlMs,\n });\n return workspaceId;\n }\n}\n\ntype ActiveBackoffEnvelope = InstallationTokenEnvelope & {\n backoffUntil: Date;\n backoffReason: NonNullable<InstallationTokenEnvelope['backoffReason']>;\n};\n\ntype TokenEnvelope = InstallationTokenEnvelope & {token: string; expiresAt: Date};\n\nfunction activeBackoff(\n envelope: InstallationTokenEnvelope | undefined,\n now: Date,\n): envelope is ActiveBackoffEnvelope {\n return (\n backoffActive(envelope, now) &&\n envelope?.backoffUntil !== undefined &&\n envelope.backoffReason !== undefined\n );\n}\n\nfunction canServeStale(\n envelope: InstallationTokenEnvelope | undefined,\n now: Date,\n): envelope is TokenEnvelope {\n const terminalBackoff =\n activeBackoff(envelope, now) && mintErrorClassForReason(envelope.backoffReason) === 'terminal';\n return (\n envelope?.token !== undefined &&\n envelope.expiresAt !== undefined &&\n stillValid(envelope.expiresAt, now) &&\n !terminalBackoff\n );\n}\n\nfunction tokenFromEnvelope(envelope: InstallationTokenEnvelope): GithubInstallationAccessToken {\n if (!envelope.token || !envelope.expiresAt) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation token cache envelope is missing a token or expiry',\n );\n }\n return {\n token: envelope.token,\n expiresAt: envelope.expiresAt,\n ...(envelope.permissions === undefined ? {} : {permissions: envelope.permissions}),\n };\n}\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<T>((_, reject) => {\n timer = setTimeout(() => {\n reject(\n new GithubIntegrationProviderError(\n 'timeout',\n 'Timed out minting GitHub installation access token',\n ),\n );\n }, timeoutMs);\n });\n\n return Promise.race([promise, timeout]).finally(() => {\n if (timer) clearTimeout(timer);\n });\n}\n"],"names":["setTimeout","sleepTimeout","reportError","logger","GithubIntegrationProviderError","recordInstallationTokenBackoff","recordInstallationTokenLookup","recordInstallationTokenMint","backoffActive","backoffMs","classifyMintError","mintErrorClassForReason","parseInstallationTokenEnvelope","providerErrorFromBackoff","stillValid","toProviderError","usable","DEFAULT_POLL_DELAYS_MS","DEFAULT_WORKSPACE_CACHE_TTL_MS","DEFAULT_MINT_TIMEOUT_MS","SharedInstallationTokenCache","options","workspaceIds","Map","now","Date","sleep","ms","then","undefined","pollDelaysMs","workspaceCacheTtlMs","mintTimeoutMs","getOrMint","installationId","mint","workspaceId","resolveWorkspaceId","envelope","readEnvelope","tokenFromEnvelope","result","withLock","mintUnderLock","acquired","value","serveStaleOrPoll","params","activeBackoff","canServeStale","backoffReason","backoffUntil","getTime","backoffError","token","recordMint","error","providerError","classified","until","reason","class","writeEnvelope","expiresAt","permissions","message","status","catch","writeError","warn","boundary","operation","extra","toISOString","info","initialNow","delayMs","startedAt","withTimeout","outcome","durationMs","raw","secretStore","read","write","nowMs","cached","get","expiresAtMs","set","terminalBackoff","promise","timeoutMs","timer","timeout","Promise","_","reject","race","finally","clearTimeout"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { AgentToolSelectionCatalog, AgentToolSession, AgentToolsProvider, IntegrationConnection, OpenAgentToolsSessionInput } from '@shipfox/api-integration-spi';
2
2
  import { type GithubInstallationTokenProvider } from '#api/installation-token-provider.js';
3
3
  import type { GithubInstallation } from '#db/installations.js';
4
- import { type GithubAgentToolCatalogEntry, type GithubAgentToolRequiredScope } from './github-agent-tool-catalog.js';
4
+ import { type GithubAgentToolCatalogEntry, type GithubAgentToolId, type GithubAgentToolRequiredScope } from './github-agent-tool-catalog.js';
5
5
  export type { GithubAgentToolCatalogEntry, GithubAgentToolCategory, GithubAgentToolId, GithubAgentToolPermission, GithubAgentToolPermissionAccess, GithubAgentToolRequiredPermission, GithubAgentToolRequiredScope, GithubAgentToolSensitivity, } from './github-agent-tool-catalog.js';
6
6
  export { buildGithubAgentToolSelectionCatalog, DEFAULT_JOB_LOG_TAIL_LINES, githubAgentToolCatalog, githubAgentToolSelectionCatalog, } from './github-agent-tool-catalog.js';
7
7
  type GithubIntegrationConnection = IntegrationConnection<'github'>;
@@ -26,10 +26,17 @@ export interface GithubAgentToolsProviderOptions {
26
26
  tokenProvider?: GithubInstallationTokenProvider | undefined;
27
27
  createClient?: GithubToolClientFactory | undefined;
28
28
  }
29
+ export interface GithubToolResponse {
30
+ data: unknown;
31
+ headers?: Record<string, string | number | undefined> | undefined;
32
+ status?: number | undefined;
33
+ url?: string | undefined;
34
+ }
29
35
  export interface GithubToolClient {
30
- request(route: string, parameters: Record<string, unknown>): Promise<{
31
- data: unknown;
32
- }>;
36
+ request(route: string, parameters: Record<string, unknown>): Promise<GithubToolResponse>;
37
+ graphql?: ((query: string, variables: Record<string, unknown>) => Promise<unknown>) | undefined;
33
38
  }
34
39
  export type GithubToolClientFactory = (token: string) => GithubToolClient;
40
+ export declare function githubOperationRoute(toolId: GithubAgentToolId, method: string | undefined, args: Record<string, unknown>): string | undefined;
41
+ export declare function projectGithubOperationParameters(toolId: GithubAgentToolId, method: string | undefined, args: Record<string, unknown>): Record<string, unknown>;
35
42
  //# sourceMappingURL=agent-tools.d.ts.map
@@ -1 +1 @@
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;AAEtC,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EACL,KAAK,2BAA2B,EAEhC,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;AAEF,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,CA8CjD;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,gBAAgB;IAC/B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;CACvF;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC"}
1
+ {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"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"}