@inkeep/agents-api 0.43.0 → 0.44.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.
Files changed (74) hide show
  1. package/dist/.well-known/workflow/v1/manifest.debug.json +20 -20
  2. package/dist/.well-known/workflow/v1/step.cjs +211763 -195914
  3. package/dist/createApp.js +11 -9
  4. package/dist/domains/evals/routes/datasetTriggers.d.ts +2 -2
  5. package/dist/domains/evals/routes/index.d.ts +2 -2
  6. package/dist/domains/evals/workflow/routes.d.ts +2 -2
  7. package/dist/domains/manage/index.js +6 -0
  8. package/dist/domains/manage/routes/conversations.d.ts +2 -2
  9. package/dist/domains/manage/routes/github.d.ts +16 -0
  10. package/dist/domains/manage/routes/github.js +511 -0
  11. package/dist/domains/manage/routes/index.d.ts +2 -2
  12. package/dist/domains/manage/routes/mcp.d.ts +2 -2
  13. package/dist/domains/manage/routes/mcpToolGithubAccess.d.ts +9 -0
  14. package/dist/domains/manage/routes/mcpToolGithubAccess.js +205 -0
  15. package/dist/domains/manage/routes/projectGithubAccess.d.ts +9 -0
  16. package/dist/domains/manage/routes/projectGithubAccess.js +167 -0
  17. package/dist/domains/manage/routes/projectMembers.js +1 -14
  18. package/dist/domains/manage/routes/projectPermissions.js +2 -9
  19. package/dist/domains/manage/routes/projects.js +14 -16
  20. package/dist/domains/manage/routes/signoz.d.ts +2 -2
  21. package/dist/domains/manage/routes/signoz.js +1 -1
  22. package/dist/domains/manage/routes/tools.js +4 -2
  23. package/dist/domains/manage/routes/userProjectMemberships.js +1 -2
  24. package/dist/domains/mcp/routes/mcp.d.ts +2 -2
  25. package/dist/domains/run/agents/Agent.js +29 -2
  26. package/dist/domains/run/constants/execution-limits/defaults.d.ts +1 -1
  27. package/dist/domains/run/constants/execution-limits/defaults.js +1 -1
  28. package/dist/domains/run/constants/execution-limits/index.d.ts +1 -1
  29. package/dist/domains/run/context/ContextResolver.js +1 -1
  30. package/dist/domains/run/context/validation.d.ts +1 -1
  31. package/dist/domains/run/services/AgentSession.js +5 -1
  32. package/dist/domains/run/services/BaseCompressor.js +1 -1
  33. package/dist/domains/run/services/TriggerService.d.ts +1 -1
  34. package/dist/domains/run/services/TriggerService.js +15 -13
  35. package/dist/domains/run/tools/sandbox-utils.js +1 -1
  36. package/dist/domains/run/types/executionContext.js +3 -1
  37. package/dist/env.d.ts +12 -2
  38. package/dist/env.js +37 -32
  39. package/dist/factory.d.ts +7 -7
  40. package/dist/factory.js +4 -10
  41. package/dist/index.d.ts +6 -5
  42. package/dist/index.js +3 -5
  43. package/dist/middleware/branchScopedDb.d.ts +1 -1
  44. package/dist/middleware/evalsAuth.d.ts +2 -2
  45. package/dist/middleware/manageAuth.d.ts +2 -2
  46. package/dist/middleware/projectAccess.d.ts +2 -11
  47. package/dist/middleware/projectAccess.js +7 -33
  48. package/dist/middleware/projectConfig.d.ts +3 -3
  49. package/dist/middleware/ref.d.ts +1 -1
  50. package/dist/middleware/requirePermission.d.ts +2 -2
  51. package/dist/middleware/requirePermission.js +1 -2
  52. package/dist/middleware/runAuth.d.ts +4 -4
  53. package/dist/middleware/sessionAuth.d.ts +3 -3
  54. package/dist/middleware/sessionAuth.js +1 -2
  55. package/dist/middleware/tenantAccess.d.ts +2 -2
  56. package/dist/middleware/tracing.d.ts +3 -3
  57. package/dist/openapi.d.ts +1 -0
  58. package/dist/openapi.js +1 -0
  59. package/dist/types/runExecutionContext.js +3 -1
  60. package/package.json +5 -4
  61. package/dist/domains/github/config.d.ts +0 -14
  62. package/dist/domains/github/config.js +0 -47
  63. package/dist/domains/github/index.d.ts +0 -12
  64. package/dist/domains/github/index.js +0 -18
  65. package/dist/domains/github/installation.d.ts +0 -34
  66. package/dist/domains/github/installation.js +0 -172
  67. package/dist/domains/github/jwks.d.ts +0 -20
  68. package/dist/domains/github/jwks.js +0 -85
  69. package/dist/domains/github/oidcToken.d.ts +0 -22
  70. package/dist/domains/github/oidcToken.js +0 -140
  71. package/dist/domains/github/routes/tokenExchange.d.ts +0 -7
  72. package/dist/domains/github/routes/tokenExchange.js +0 -130
  73. package/dist/initialization.d.ts +0 -6
  74. package/dist/initialization.js +0 -72
package/dist/createApp.js CHANGED
@@ -1,8 +1,7 @@
1
- import { getLogger } from "./logger.js";
1
+ import { getLogger as getLogger$1 } from "./logger.js";
2
2
  import { env } from "./env.js";
3
3
  import { evalRoutes } from "./domains/evals/index.js";
4
4
  import { workflowRoutes } from "./domains/evals/workflow/routes.js";
5
- import { githubRoutes } from "./domains/github/index.js";
6
5
  import { sessionAuth, sessionContext } from "./middleware/sessionAuth.js";
7
6
  import { manageRoutes } from "./domains/manage/index.js";
8
7
  import mcp_default from "./domains/mcp/routes/mcp.js";
@@ -22,13 +21,15 @@ import { executionBaggageMiddleware } from "./middleware/tracing.js";
22
21
  import { setupOpenAPIRoutes } from "./openapi.js";
23
22
  import { healthChecksHandler } from "./routes/healthChecks.js";
24
23
  import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
24
+ import { OrgRoles } from "@inkeep/agents-core";
25
+ import { githubRoutes } from "@inkeep/agents-work-apps/github";
25
26
  import { Hono } from "hono";
26
27
  import { cors } from "hono/cors";
27
28
  import { requestId } from "hono/request-id";
28
29
  import { pinoLogger } from "hono-pino";
29
30
 
30
31
  //#region src/createApp.ts
31
- const logger = getLogger("agents-api");
32
+ const logger = getLogger$1("agents-api");
32
33
  const isTestEnvironment = () => env.ENVIRONMENT === "test";
33
34
  const isWebhookRoute = (path) => {
34
35
  return path.includes("/triggers/") && !path.endsWith("/triggers") && !path.endsWith("/triggers/");
@@ -56,7 +57,7 @@ function createAgentsHono(config) {
56
57
  if (c.req.path.startsWith("/run/")) return next();
57
58
  if (c.req.path.includes("/playground/token")) return next();
58
59
  if (c.req.path.includes("/signoz/")) return next();
59
- if (c.req.path.includes("/api/github/")) return next();
60
+ if (c.req.path.includes("/work-apps/github/")) return next();
60
61
  return cors(defaultCorsConfig)(c, next);
61
62
  });
62
63
  app.use("*", async (c, next) => {
@@ -82,7 +83,7 @@ function createAgentsHono(config) {
82
83
  return next();
83
84
  });
84
85
  app.use(pinoLogger({
85
- pino: getLogger("agents-api").getPinoInstance(),
86
+ pino: getLogger$1("agents-api").getPinoInstance(),
86
87
  http: { onResLevel(c) {
87
88
  if (c.res.status >= 500) return "error";
88
89
  if (c.req.path.includes("/signoz/")) return "debug";
@@ -107,7 +108,7 @@ function createAgentsHono(config) {
107
108
  });
108
109
  });
109
110
  app.use("/manage/tenants/*", async (c, next) => {
110
- if (env.DISABLE_AUTH || isTestEnvironment()) {
111
+ if (isTestEnvironment()) {
111
112
  await next();
112
113
  return;
113
114
  }
@@ -115,7 +116,7 @@ function createAgentsHono(config) {
115
116
  return sessionAuth()(c, next);
116
117
  });
117
118
  app.use("/manage/capabilities", async (c, next) => {
118
- if (!auth || env.DISABLE_AUTH || isTestEnvironment()) {
119
+ if (!auth || isTestEnvironment()) {
119
120
  await next();
120
121
  return;
121
122
  }
@@ -140,11 +141,12 @@ function createAgentsHono(config) {
140
141
  runtime: sandboxConfig.runtime
141
142
  } });
142
143
  });
143
- if (env.DISABLE_AUTH || isTestEnvironment()) app.use("/manage/tenants/:tenantId/*", async (c, next) => {
144
+ if (isTestEnvironment()) app.use("/manage/tenants/:tenantId/*", async (c, next) => {
144
145
  const tenantId = c.req.param("tenantId");
145
146
  if (tenantId) {
146
147
  c.set("tenantId", tenantId);
147
148
  c.set("userId", "anonymous");
149
+ c.set("tenantRole", OrgRoles.OWNER);
148
150
  }
149
151
  await next();
150
152
  });
@@ -182,7 +184,7 @@ function createAgentsHono(config) {
182
184
  return fetch(forwardedRequest);
183
185
  });
184
186
  app.route("/evals", evalRoutes);
185
- app.route("/api/github", githubRoutes);
187
+ app.route("/work-apps/github", githubRoutes);
186
188
  app.route("/mcp", mcp_default);
187
189
  setupOpenAPIRoutes(app);
188
190
  app.use("/run/*", async (_c, next) => {
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
- import * as hono12 from "hono";
2
+ import * as hono17 from "hono";
3
3
 
4
4
  //#region src/domains/evals/routes/datasetTriggers.d.ts
5
- declare const app: OpenAPIHono<hono12.Env, {}, "/">;
5
+ declare const app: OpenAPIHono<hono17.Env, {}, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
- import * as hono13 from "hono";
2
+ import * as hono0 from "hono";
3
3
 
4
4
  //#region src/domains/evals/routes/index.d.ts
5
- declare const app: OpenAPIHono<hono13.Env, {}, "/">;
5
+ declare const app: OpenAPIHono<hono0.Env, {}, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types9 from "hono/types";
2
+ import * as hono_types5 from "hono/types";
3
3
 
4
4
  //#region src/domains/evals/workflow/routes.d.ts
5
- declare const workflowRoutes: Hono<hono_types9.BlankEnv, hono_types9.BlankSchema, "/">;
5
+ declare const workflowRoutes: Hono<hono_types5.BlankEnv, hono_types5.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { workflowRoutes };
@@ -1,10 +1,13 @@
1
1
  import cliAuth_default from "./routes/cliAuth.js";
2
+ import github_default from "./routes/github.js";
2
3
  import routes_default from "./routes/index.js";
3
4
  import invitations_default from "./routes/invitations.js";
4
5
  import mcp_default from "./routes/mcp.js";
6
+ import mcpToolGithubAccess_default from "./routes/mcpToolGithubAccess.js";
5
7
  import oauth_default from "./routes/oauth.js";
6
8
  import playgroundToken_default from "./routes/playgroundToken.js";
7
9
  import projectFull_default from "./routes/projectFull.js";
10
+ import projectGithubAccess_default from "./routes/projectGithubAccess.js";
8
11
  import signoz_default from "./routes/signoz.js";
9
12
  import userOrganizations_default from "./routes/userOrganizations.js";
10
13
  import { OpenAPIHono } from "@hono/zod-openapi";
@@ -18,6 +21,9 @@ function createManageRoutes() {
18
21
  app.route("/tenants/:tenantId", routes_default);
19
22
  app.route("/tenants/:tenantId/playground/token", playgroundToken_default);
20
23
  app.route("/tenants/:tenantId/signoz", signoz_default);
24
+ app.route("/tenants/:tenantId/github", github_default);
25
+ app.route("/tenants/:tenantId/projects/:projectId/github-access", projectGithubAccess_default);
26
+ app.route("/tenants/:tenantId/projects/:projectId/tools/:toolId/github-access", mcpToolGithubAccess_default);
21
27
  app.route("/tenants/:tenantId", projectFull_default);
22
28
  app.route("/oauth", oauth_default);
23
29
  app.route("/mcp", mcp_default);
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
- import * as hono16 from "hono";
2
+ import * as hono7 from "hono";
3
3
 
4
4
  //#region src/domains/manage/routes/conversations.d.ts
5
- declare const app: OpenAPIHono<hono16.Env, {}, "/">;
5
+ declare const app: OpenAPIHono<hono7.Env, {}, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -0,0 +1,16 @@
1
+ import { ManageAppVariables } from "../../../types/app.js";
2
+ import { OpenAPIHono } from "@hono/zod-openapi";
3
+
4
+ //#region src/domains/manage/routes/github.d.ts
5
+ declare const app: OpenAPIHono<{
6
+ Variables: ManageAppVariables;
7
+ }, {}, "/">;
8
+ declare const STATE_JWT_ISSUER = "inkeep-agents-api";
9
+ declare const STATE_JWT_AUDIENCE = "github-app-install";
10
+ /**
11
+ * Signs a JWT state token for the GitHub App installation flow.
12
+ * The state contains the tenantId and expires after 10 minutes.
13
+ */
14
+ declare function signStateToken(tenantId: string): Promise<string>;
15
+ //#endregion
16
+ export { STATE_JWT_AUDIENCE, STATE_JWT_ISSUER, app as default, signStateToken };
@@ -0,0 +1,511 @@
1
+ import { getLogger as getLogger$1 } from "../../../logger.js";
2
+ import runDbClient_default from "../../../data/db/runDbClient.js";
3
+ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
4
+ import { TenantParamsSchema, WorkAppGitHubRepositorySelectSchema, WorkAppGithubInstallationApiSelectSchema, commonCreateErrorResponses, commonDeleteErrorResponses, commonGetErrorResponses, createApiError, deleteInstallation, disconnectInstallation, getInstallationById, getInstallationsByTenantId, getRepositoriesByInstallationId, getRepositoryCountsByTenantId, syncRepositories, updateInstallationStatus } from "@inkeep/agents-core";
5
+ import { createAppJwt, fetchInstallationRepositories, getGitHubAppName, getStateSigningSecret, isGitHubAppNameConfigured, isStateSigningConfigured } from "@inkeep/agents-work-apps/github";
6
+ import { HTTPException } from "hono/http-exception";
7
+ import { SignJWT } from "jose";
8
+
9
+ //#region src/domains/manage/routes/github.ts
10
+ const logger = getLogger$1("github-manage");
11
+ const app = new OpenAPIHono();
12
+ const InstallUrlResponseSchema = z.object({ url: z.url().describe("GitHub App installation URL with signed state parameter") });
13
+ const STATE_JWT_ISSUER = "inkeep-agents-api";
14
+ const STATE_JWT_AUDIENCE = "github-app-install";
15
+ /**
16
+ * Signs a JWT state token for the GitHub App installation flow.
17
+ * The state contains the tenantId and expires after 10 minutes.
18
+ */
19
+ async function signStateToken(tenantId) {
20
+ const secret = getStateSigningSecret();
21
+ const secretKey = new TextEncoder().encode(secret);
22
+ return await new SignJWT({ tenantId }).setProtectedHeader({
23
+ alg: "HS256",
24
+ typ: "JWT"
25
+ }).setIssuer(STATE_JWT_ISSUER).setAudience(STATE_JWT_AUDIENCE).setIssuedAt().setExpirationTime("10m").sign(secretKey);
26
+ }
27
+ app.openapi(createRoute({
28
+ method: "get",
29
+ path: "/install-url",
30
+ summary: "Get GitHub App installation URL",
31
+ operationId: "get-github-install-url",
32
+ tags: ["GitHub"],
33
+ description: "Generates a URL to install the GitHub App on an organization or user account. The URL includes a signed state parameter that encodes the tenant ID and expires after 10 minutes. After installation, GitHub will redirect back to our callback endpoint with this state.",
34
+ request: { params: TenantParamsSchema },
35
+ responses: {
36
+ 200: {
37
+ description: "GitHub App installation URL generated successfully",
38
+ content: { "application/json": { schema: InstallUrlResponseSchema } }
39
+ },
40
+ ...commonGetErrorResponses
41
+ }
42
+ }), async (c) => {
43
+ const { tenantId } = c.req.valid("param");
44
+ if (!isStateSigningConfigured()) {
45
+ logger.error({}, "GITHUB_STATE_SIGNING_SECRET is not configured");
46
+ throw createApiError({
47
+ code: "internal_server_error",
48
+ message: "GitHub App installation is not configured"
49
+ });
50
+ }
51
+ if (!isGitHubAppNameConfigured()) {
52
+ logger.error({}, "GITHUB_APP_NAME is not configured");
53
+ throw createApiError({
54
+ code: "internal_server_error",
55
+ message: "GitHub App installation is not configured"
56
+ });
57
+ }
58
+ const appName = getGitHubAppName();
59
+ logger.info({ tenantId }, "Generating GitHub App installation URL");
60
+ const state = await signStateToken(tenantId);
61
+ const installUrl = `https://github.com/apps/${appName}/installations/new?state=${encodeURIComponent(state)}`;
62
+ logger.info({ tenantId }, "GitHub App installation URL generated");
63
+ return c.json({ url: installUrl }, 200);
64
+ });
65
+ const InstallationWithRepoCountSchema = WorkAppGithubInstallationApiSelectSchema.extend({ repositoryCount: z.number().describe("Number of repositories accessible to this installation") });
66
+ const ListInstallationsResponseSchema = z.object({ installations: z.array(InstallationWithRepoCountSchema).describe("List of GitHub App installations") });
67
+ const ListInstallationsQuerySchema = z.object({ includeDisconnected: z.string().optional().transform((val) => val === "true").describe("Include disconnected installations in the response") });
68
+ app.openapi(createRoute({
69
+ method: "get",
70
+ path: "/installations",
71
+ summary: "List GitHub App installations",
72
+ operationId: "list-github-installations",
73
+ tags: ["GitHub"],
74
+ description: "Returns a list of GitHub App installations connected to this tenant. By default, deleted installations are filtered out. Use the includeDisconnected query parameter to include them.",
75
+ request: {
76
+ params: TenantParamsSchema,
77
+ query: ListInstallationsQuerySchema
78
+ },
79
+ responses: {
80
+ 200: {
81
+ description: "List of GitHub App installations",
82
+ content: { "application/json": { schema: ListInstallationsResponseSchema } }
83
+ },
84
+ ...commonGetErrorResponses
85
+ }
86
+ }), async (c) => {
87
+ const { tenantId } = c.req.valid("param");
88
+ const { includeDisconnected } = c.req.valid("query");
89
+ logger.info({
90
+ tenantId,
91
+ includeDisconnected
92
+ }, "Listing GitHub App installations");
93
+ const [installations, repositoryCounts] = await Promise.all([getInstallationsByTenantId(runDbClient_default)({
94
+ tenantId,
95
+ includeDisconnected
96
+ }), getRepositoryCountsByTenantId(runDbClient_default)({
97
+ tenantId,
98
+ includeDisconnected
99
+ })]);
100
+ const installationsWithCounts = installations.map((installation) => ({
101
+ id: installation.id,
102
+ installationId: installation.installationId,
103
+ accountLogin: installation.accountLogin,
104
+ accountId: installation.accountId,
105
+ accountType: installation.accountType,
106
+ status: installation.status,
107
+ repositoryCount: repositoryCounts.get(installation.id) ?? 0,
108
+ createdAt: installation.createdAt,
109
+ updatedAt: installation.updatedAt
110
+ }));
111
+ logger.info({
112
+ tenantId,
113
+ count: installationsWithCounts.length
114
+ }, "Listed GitHub App installations");
115
+ return c.json({ installations: installationsWithCounts }, 200);
116
+ });
117
+ const InstallationIdParamSchema = z.object({ installationId: z.string().describe("The internal installation ID") });
118
+ const InstallationDetailResponseSchema = z.object({
119
+ installation: WorkAppGithubInstallationApiSelectSchema.describe("Installation details"),
120
+ repositories: z.array(WorkAppGitHubRepositorySelectSchema).describe("List of repositories")
121
+ });
122
+ app.openapi(createRoute({
123
+ method: "get",
124
+ path: "/installations/:installationId",
125
+ summary: "Get GitHub App installation details",
126
+ operationId: "get-github-installation-details",
127
+ tags: ["GitHub"],
128
+ description: "Returns detailed information about a specific GitHub App installation, including the full list of repositories.",
129
+ request: { params: TenantParamsSchema.merge(InstallationIdParamSchema) },
130
+ responses: {
131
+ 200: {
132
+ description: "Installation details retrieved successfully",
133
+ content: { "application/json": { schema: InstallationDetailResponseSchema } }
134
+ },
135
+ ...commonGetErrorResponses
136
+ }
137
+ }), async (c) => {
138
+ const { tenantId, installationId } = c.req.valid("param");
139
+ logger.info({
140
+ tenantId,
141
+ installationId
142
+ }, "Getting GitHub App installation details");
143
+ const [installation, repositories] = await Promise.all([getInstallationById(runDbClient_default)({
144
+ tenantId,
145
+ id: installationId
146
+ }), getRepositoriesByInstallationId(runDbClient_default)(installationId)]);
147
+ if (!installation) {
148
+ logger.warn({
149
+ tenantId,
150
+ installationId
151
+ }, "Installation not found");
152
+ throw createApiError({
153
+ code: "not_found",
154
+ message: "Installation not found"
155
+ });
156
+ }
157
+ logger.info({
158
+ tenantId,
159
+ installationId,
160
+ repositoryCount: repositories.length
161
+ }, "Got GitHub App installation details");
162
+ return c.json({
163
+ installation: {
164
+ id: installation.id,
165
+ installationId: installation.installationId,
166
+ accountLogin: installation.accountLogin,
167
+ accountId: installation.accountId,
168
+ accountType: installation.accountType,
169
+ status: installation.status,
170
+ createdAt: installation.createdAt,
171
+ updatedAt: installation.updatedAt
172
+ },
173
+ repositories
174
+ }, 200);
175
+ });
176
+ const DisconnectInstallationResponseSchema = z.object({ success: z.literal(true).describe("Whether the disconnection was successful") });
177
+ app.openapi(createRoute({
178
+ method: "post",
179
+ path: "/installations/:installationId/disconnect",
180
+ summary: "Disconnect a GitHub App installation",
181
+ operationId: "disconnect-github-installation",
182
+ tags: ["GitHub"],
183
+ description: "Disconnects a GitHub App installation from the tenant. This soft deletes the installation (sets status to \"disconnected\") and removes all project repository access entries. The installation record is preserved for audit purposes. Note: This does NOT uninstall the GitHub App from GitHub - the user can do that separately from GitHub settings.",
184
+ request: { params: TenantParamsSchema.merge(InstallationIdParamSchema) },
185
+ responses: {
186
+ 200: {
187
+ description: "Installation disconnected successfully",
188
+ content: { "application/json": { schema: DisconnectInstallationResponseSchema } }
189
+ },
190
+ ...commonCreateErrorResponses
191
+ }
192
+ }), async (c) => {
193
+ const { tenantId, installationId } = c.req.valid("param");
194
+ logger.info({
195
+ tenantId,
196
+ installationId
197
+ }, "Disconnecting GitHub App installation");
198
+ const installation = await getInstallationById(runDbClient_default)({
199
+ tenantId,
200
+ id: installationId
201
+ });
202
+ if (!installation) {
203
+ logger.warn({
204
+ tenantId,
205
+ installationId
206
+ }, "Installation not found");
207
+ throw createApiError({
208
+ code: "not_found",
209
+ message: "Installation not found"
210
+ });
211
+ }
212
+ if (installation.status === "disconnected") {
213
+ logger.warn({
214
+ tenantId,
215
+ installationId
216
+ }, "Installation already disconnected");
217
+ throw createApiError({
218
+ code: "bad_request",
219
+ message: "Installation is already disconnected"
220
+ });
221
+ }
222
+ if (!await disconnectInstallation(runDbClient_default)({
223
+ tenantId,
224
+ id: installationId
225
+ })) {
226
+ logger.error({
227
+ tenantId,
228
+ installationId
229
+ }, "Failed to disconnect installation");
230
+ throw createApiError({
231
+ code: "internal_server_error",
232
+ message: "Failed to disconnect installation"
233
+ });
234
+ }
235
+ logger.info({
236
+ tenantId,
237
+ installationId
238
+ }, "GitHub App installation disconnected");
239
+ return c.json({ success: true }, 200);
240
+ });
241
+ const ReconnectInstallationResponseSchema = z.object({
242
+ success: z.literal(true).describe("Whether the reconnection was successful"),
243
+ syncResult: z.object({
244
+ added: z.number().describe("Number of repositories added"),
245
+ removed: z.number().describe("Number of repositories removed"),
246
+ updated: z.number().describe("Number of repositories updated")
247
+ }).optional().describe("Repository sync results (if sync was performed)")
248
+ });
249
+ function createServiceUnavailableError(message) {
250
+ const responseBody = {
251
+ title: "Service Unavailable",
252
+ status: 503,
253
+ detail: message,
254
+ code: "service_unavailable",
255
+ error: {
256
+ code: "service_unavailable",
257
+ message: message.length > 100 ? `${message.substring(0, 97)}...` : message
258
+ }
259
+ };
260
+ return new HTTPException(503, {
261
+ message,
262
+ res: new Response(JSON.stringify(responseBody), {
263
+ status: 503,
264
+ headers: {
265
+ "Content-Type": "application/problem+json",
266
+ "X-Content-Type-Options": "nosniff"
267
+ }
268
+ })
269
+ });
270
+ }
271
+ const serviceUnavailableSchema = {
272
+ description: "Service Unavailable - GitHub API is not accessible",
273
+ content: { "application/problem+json": { schema: z.object({
274
+ title: z.string().openapi({ example: "Service Unavailable" }),
275
+ status: z.number().openapi({ example: 503 }),
276
+ detail: z.string().openapi({ example: "Failed to connect to GitHub API" }),
277
+ code: z.literal("service_unavailable").openapi({ example: "service_unavailable" }),
278
+ error: z.object({
279
+ code: z.literal("service_unavailable"),
280
+ message: z.string()
281
+ })
282
+ }) } }
283
+ };
284
+ app.openapi(createRoute({
285
+ method: "post",
286
+ path: "/installations/:installationId/reconnect",
287
+ summary: "Reconnect a disconnected GitHub App installation",
288
+ operationId: "reconnect-github-installation",
289
+ tags: ["GitHub"],
290
+ description: "Reconnects a previously disconnected GitHub App installation by setting its status back to \"active\" and syncing the available repositories from GitHub. This can only be used on installations with \"disconnected\" status.",
291
+ request: { params: TenantParamsSchema.merge(InstallationIdParamSchema) },
292
+ responses: {
293
+ 200: {
294
+ description: "Installation reconnected successfully",
295
+ content: { "application/json": { schema: ReconnectInstallationResponseSchema } }
296
+ },
297
+ ...commonCreateErrorResponses,
298
+ 503: serviceUnavailableSchema
299
+ }
300
+ }), async (c) => {
301
+ const { tenantId, installationId } = c.req.valid("param");
302
+ logger.info({
303
+ tenantId,
304
+ installationId
305
+ }, "Reconnecting GitHub App installation");
306
+ const installation = await getInstallationById(runDbClient_default)({
307
+ tenantId,
308
+ id: installationId
309
+ });
310
+ if (!installation) {
311
+ logger.warn({
312
+ tenantId,
313
+ installationId
314
+ }, "Installation not found");
315
+ throw createApiError({
316
+ code: "not_found",
317
+ message: "Installation not found"
318
+ });
319
+ }
320
+ if (installation.status !== "disconnected") {
321
+ logger.warn({
322
+ tenantId,
323
+ installationId,
324
+ status: installation.status
325
+ }, "Installation is not disconnected");
326
+ throw createApiError({
327
+ code: "bad_request",
328
+ message: "Installation is not disconnected"
329
+ });
330
+ }
331
+ if (!await updateInstallationStatus(runDbClient_default)({
332
+ tenantId,
333
+ id: installationId,
334
+ status: "active"
335
+ })) {
336
+ logger.error({
337
+ tenantId,
338
+ installationId
339
+ }, "Failed to reconnect installation");
340
+ throw createApiError({
341
+ code: "internal_server_error",
342
+ message: "Failed to reconnect installation"
343
+ });
344
+ }
345
+ logger.info({
346
+ tenantId,
347
+ installationId
348
+ }, "GitHub App installation reconnected, syncing repositories");
349
+ let appJwt;
350
+ try {
351
+ appJwt = await createAppJwt();
352
+ } catch (error) {
353
+ logger.error({ error }, "Failed to create GitHub App JWT");
354
+ throw createServiceUnavailableError("GitHub App not configured properly");
355
+ }
356
+ const reposResult = await fetchInstallationRepositories(installation.installationId, appJwt);
357
+ if (!reposResult.success) {
358
+ logger.error({
359
+ error: reposResult.error,
360
+ installationId
361
+ }, "Failed to fetch repositories from GitHub");
362
+ throw createServiceUnavailableError("Failed to fetch repositories from GitHub API");
363
+ }
364
+ const syncResult = await syncRepositories(runDbClient_default)({
365
+ installationId: installation.id,
366
+ repositories: reposResult.repositories.map((repo) => ({
367
+ repositoryId: String(repo.id),
368
+ repositoryName: repo.name,
369
+ repositoryFullName: repo.full_name,
370
+ private: repo.private
371
+ }))
372
+ });
373
+ logger.info({
374
+ tenantId,
375
+ installationId,
376
+ added: syncResult.added,
377
+ removed: syncResult.removed,
378
+ updated: syncResult.updated
379
+ }, "GitHub App installation reconnected and repositories synced");
380
+ return c.json({
381
+ success: true,
382
+ syncResult: {
383
+ added: syncResult.added,
384
+ removed: syncResult.removed,
385
+ updated: syncResult.updated
386
+ }
387
+ }, 200);
388
+ });
389
+ app.openapi(createRoute({
390
+ method: "delete",
391
+ path: "/installations/:installationId",
392
+ summary: "Delete a GitHub App installation permanently",
393
+ operationId: "delete-github-installation",
394
+ tags: ["GitHub"],
395
+ description: "Permanently deletes a GitHub App installation from the tenant. This hard deletes the installation record, all associated repositories, and project repository access entries. This action cannot be undone. Use POST /disconnect for soft delete instead. Note: This does NOT uninstall the GitHub App from GitHub - the user can do that separately from GitHub settings.",
396
+ request: { params: TenantParamsSchema.merge(InstallationIdParamSchema) },
397
+ responses: {
398
+ 200: {
399
+ description: "Installation deleted successfully",
400
+ content: { "application/json": { schema: z.object({ success: z.literal(true).describe("Whether the deletion was successful") }) } }
401
+ },
402
+ ...commonDeleteErrorResponses
403
+ }
404
+ }), async (c) => {
405
+ const { tenantId, installationId } = c.req.valid("param");
406
+ logger.info({
407
+ tenantId,
408
+ installationId
409
+ }, "Deleting GitHub App installation permanently");
410
+ if (!await deleteInstallation(runDbClient_default)({
411
+ tenantId,
412
+ id: installationId
413
+ })) throw createApiError({
414
+ code: "not_found",
415
+ message: "Installation not found"
416
+ });
417
+ logger.info({
418
+ tenantId,
419
+ installationId
420
+ }, "GitHub App installation deleted permanently");
421
+ return c.json({ success: true }, 200);
422
+ });
423
+ const SyncRepositoriesResponseSchema = z.object({
424
+ repositories: z.array(WorkAppGitHubRepositorySelectSchema).describe("Updated list of repositories"),
425
+ syncResult: z.object({
426
+ added: z.number().describe("Number of repositories added"),
427
+ removed: z.number().describe("Number of repositories removed"),
428
+ updated: z.number().describe("Number of repositories updated")
429
+ })
430
+ });
431
+ app.openapi(createRoute({
432
+ method: "post",
433
+ path: "/installations/:installationId/sync",
434
+ summary: "Sync repositories for a GitHub App installation",
435
+ operationId: "sync-github-installation-repositories",
436
+ tags: ["GitHub"],
437
+ description: "Manually refreshes the repository list for a GitHub App installation by fetching the current list from GitHub API. This is useful if webhooks were missed or to ensure the local data is in sync with GitHub.",
438
+ request: { params: TenantParamsSchema.merge(InstallationIdParamSchema) },
439
+ responses: {
440
+ 200: {
441
+ description: "Repositories synced successfully",
442
+ content: { "application/json": { schema: SyncRepositoriesResponseSchema } }
443
+ },
444
+ ...commonGetErrorResponses,
445
+ 503: serviceUnavailableSchema
446
+ }
447
+ }), async (c) => {
448
+ const { tenantId, installationId } = c.req.valid("param");
449
+ logger.info({
450
+ tenantId,
451
+ installationId
452
+ }, "Syncing repositories for GitHub App installation");
453
+ const installation = await getInstallationById(runDbClient_default)({
454
+ tenantId,
455
+ id: installationId
456
+ });
457
+ if (!installation) {
458
+ logger.warn({
459
+ tenantId,
460
+ installationId
461
+ }, "Installation not found");
462
+ throw createApiError({
463
+ code: "not_found",
464
+ message: "Installation not found"
465
+ });
466
+ }
467
+ let appJwt;
468
+ try {
469
+ appJwt = await createAppJwt();
470
+ } catch (error) {
471
+ logger.error({ error }, "Failed to create GitHub App JWT");
472
+ throw createServiceUnavailableError("GitHub App not configured properly");
473
+ }
474
+ const reposResult = await fetchInstallationRepositories(installation.installationId, appJwt);
475
+ if (!reposResult.success) {
476
+ logger.error({
477
+ error: reposResult.error,
478
+ installationId
479
+ }, "Failed to fetch repositories from GitHub");
480
+ throw createServiceUnavailableError("Failed to fetch repositories from GitHub API");
481
+ }
482
+ const syncResult = await syncRepositories(runDbClient_default)({
483
+ installationId: installation.id,
484
+ repositories: reposResult.repositories.map((repo) => ({
485
+ repositoryId: String(repo.id),
486
+ repositoryName: repo.name,
487
+ repositoryFullName: repo.full_name,
488
+ private: repo.private
489
+ }))
490
+ });
491
+ logger.info({
492
+ tenantId,
493
+ installationId,
494
+ added: syncResult.added,
495
+ removed: syncResult.removed,
496
+ updated: syncResult.updated
497
+ }, "Repositories synced successfully");
498
+ const updatedRepositories = await getRepositoriesByInstallationId(runDbClient_default)(installation.id);
499
+ return c.json({
500
+ repositories: updatedRepositories,
501
+ syncResult: {
502
+ added: syncResult.added,
503
+ removed: syncResult.removed,
504
+ updated: syncResult.updated
505
+ }
506
+ }, 200);
507
+ });
508
+ var github_default = app;
509
+
510
+ //#endregion
511
+ export { STATE_JWT_AUDIENCE, STATE_JWT_ISSUER, github_default as default, signStateToken };