@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +7 -4
- package/dist/api/client.js.map +1 -1
- package/dist/api/installation-token-envelope.d.ts +5 -1
- package/dist/api/installation-token-envelope.d.ts.map +1 -1
- package/dist/api/installation-token-envelope.js +16 -5
- package/dist/api/installation-token-envelope.js.map +1 -1
- package/dist/api/shared-installation-token-cache.d.ts.map +1 -1
- package/dist/api/shared-installation-token-cache.js +10 -4
- package/dist/api/shared-installation-token-cache.js.map +1 -1
- package/dist/core/agent-tools.d.ts +11 -4
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent-tools.js +182 -17
- package/dist/core/agent-tools.js.map +1 -1
- package/dist/core/github-agent-tool-catalog.js +1 -1
- package/dist/core/github-agent-tool-catalog.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/api/client.test.ts +41 -5
- package/src/api/client.ts +22 -2
- package/src/api/installation-token-envelope.ts +24 -2
- package/src/api/shared-installation-token-cache.test.ts +28 -0
- package/src/api/shared-installation-token-cache.ts +7 -0
- package/src/core/agent-tools.test.ts +862 -8
- package/src/core/agent-tools.ts +271 -19
- package/src/core/github-agent-tool-catalog.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled: 30 files with swc (
|
|
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
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -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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"}
|
package/dist/api/client.js
CHANGED
|
@@ -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') {
|
package/dist/api/client.js.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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
|
-
|
|
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;
|
|
1
|
+
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"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"}
|