@inkeep/agents-api 0.0.0-dev-20260126093157 → 0.0.0-dev-20260126174131
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/dist/.well-known/workflow/v1/manifest.debug.json +22 -22
- package/dist/createApp.js +3 -0
- package/dist/domains/evals/routes/datasetTriggers.d.ts +2 -2
- package/dist/domains/evals/routes/index.d.ts +2 -2
- package/dist/domains/evals/workflow/routes.d.ts +2 -2
- package/dist/domains/github/config.d.ts +14 -0
- package/dist/domains/github/config.js +47 -0
- package/dist/domains/github/index.d.ts +12 -0
- package/dist/domains/github/index.js +18 -0
- package/dist/domains/github/installation.d.ts +34 -0
- package/dist/domains/github/installation.js +172 -0
- package/dist/domains/github/jwks.d.ts +20 -0
- package/dist/domains/github/jwks.js +85 -0
- package/dist/domains/github/oidcToken.d.ts +22 -0
- package/dist/domains/github/oidcToken.js +140 -0
- package/dist/domains/github/routes/tokenExchange.d.ts +7 -0
- package/dist/domains/github/routes/tokenExchange.js +130 -0
- package/dist/domains/index.d.ts +2 -1
- package/dist/domains/index.js +2 -1
- package/dist/domains/manage/routes/conversations.d.ts +2 -2
- package/dist/domains/manage/routes/evals/evaluationResults.d.ts +2 -2
- package/dist/domains/manage/routes/index.d.ts +2 -2
- package/dist/domains/manage/routes/mcp.d.ts +2 -2
- package/dist/domains/manage/routes/signoz.d.ts +2 -2
- package/dist/domains/run/agents/relationTools.d.ts +2 -2
- package/dist/domains/run/context/ContextResolver.js +1 -1
- package/dist/domains/run/services/BaseCompressor.js +1 -1
- package/dist/domains/run/utils/token-estimator.d.ts +2 -2
- package/dist/factory.d.ts +22 -22
- package/dist/index.d.ts +22 -22
- package/dist/middleware/evalsAuth.d.ts +2 -2
- package/dist/middleware/manageAuth.d.ts +2 -2
- package/dist/middleware/projectAccess.d.ts +2 -2
- package/dist/middleware/projectConfig.d.ts +3 -3
- package/dist/middleware/requirePermission.d.ts +2 -2
- package/dist/middleware/sessionAuth.d.ts +3 -3
- package/dist/middleware/tenantAccess.d.ts +2 -2
- package/dist/middleware/tracing.d.ts +3 -3
- package/package.json +4 -3
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"steps": {
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"stepId": "
|
|
6
|
-
},
|
|
7
|
-
"createRelationStep": {
|
|
8
|
-
"stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//createRelationStep"
|
|
3
|
+
"node_modules/.pnpm/workflow@4.0.1-beta.33_@aws-sdk+client-sts@3.970.0_@opentelemetry+api@1.9.0_@types+reac_d0e39273ec53983ee1a59c0952eb17f2/node_modules/workflow/dist/internal/builtins.js": {
|
|
4
|
+
"__builtin_response_array_buffer": {
|
|
5
|
+
"stepId": "__builtin_response_array_buffer"
|
|
9
6
|
},
|
|
10
|
-
"
|
|
11
|
-
"stepId": "
|
|
7
|
+
"__builtin_response_json": {
|
|
8
|
+
"stepId": "__builtin_response_json"
|
|
12
9
|
},
|
|
13
|
-
"
|
|
14
|
-
"stepId": "
|
|
10
|
+
"__builtin_response_text": {
|
|
11
|
+
"stepId": "__builtin_response_text"
|
|
15
12
|
}
|
|
16
13
|
},
|
|
17
14
|
"src/domains/evals/workflow/functions/evaluateConversation.ts": {
|
|
@@ -28,28 +25,31 @@
|
|
|
28
25
|
"stepId": "step//src/domains/evals/workflow/functions/evaluateConversation.ts//logStep"
|
|
29
26
|
}
|
|
30
27
|
},
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"stepId": "
|
|
28
|
+
"src/domains/evals/workflow/functions/runDatasetItem.ts": {
|
|
29
|
+
"callChatApiStep": {
|
|
30
|
+
"stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//callChatApiStep"
|
|
34
31
|
},
|
|
35
|
-
"
|
|
36
|
-
"stepId": "
|
|
32
|
+
"createRelationStep": {
|
|
33
|
+
"stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//createRelationStep"
|
|
37
34
|
},
|
|
38
|
-
"
|
|
39
|
-
"stepId": "
|
|
35
|
+
"executeEvaluatorStep": {
|
|
36
|
+
"stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//executeEvaluatorStep"
|
|
37
|
+
},
|
|
38
|
+
"logStep": {
|
|
39
|
+
"stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//logStep"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"workflows": {
|
|
44
|
-
"src/domains/evals/workflow/functions/evaluateConversation.ts": {
|
|
45
|
-
"_evaluateConversationWorkflow": {
|
|
46
|
-
"workflowId": "workflow//src/domains/evals/workflow/functions/evaluateConversation.ts//_evaluateConversationWorkflow"
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
44
|
"src/domains/evals/workflow/functions/runDatasetItem.ts": {
|
|
50
45
|
"_runDatasetItemWorkflow": {
|
|
51
46
|
"workflowId": "workflow//src/domains/evals/workflow/functions/runDatasetItem.ts//_runDatasetItemWorkflow"
|
|
52
47
|
}
|
|
48
|
+
},
|
|
49
|
+
"src/domains/evals/workflow/functions/evaluateConversation.ts": {
|
|
50
|
+
"_evaluateConversationWorkflow": {
|
|
51
|
+
"workflowId": "workflow//src/domains/evals/workflow/functions/evaluateConversation.ts//_evaluateConversationWorkflow"
|
|
52
|
+
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
package/dist/createApp.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getLogger } 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";
|
|
5
6
|
import { sessionAuth, sessionContext } from "./middleware/sessionAuth.js";
|
|
6
7
|
import { manageRoutes } from "./domains/manage/index.js";
|
|
7
8
|
import { flushBatchProcessor } from "./instrumentation.js";
|
|
@@ -53,6 +54,7 @@ function createAgentsHono(config) {
|
|
|
53
54
|
if (c.req.path.startsWith("/run/")) return next();
|
|
54
55
|
if (c.req.path.includes("/playground/token")) return next();
|
|
55
56
|
if (c.req.path.includes("/signoz/")) return next();
|
|
57
|
+
if (c.req.path.includes("/api/github/")) return next();
|
|
56
58
|
return cors(defaultCorsConfig)(c, next);
|
|
57
59
|
});
|
|
58
60
|
app.use("*", async (c, next) => {
|
|
@@ -187,6 +189,7 @@ function createAgentsHono(config) {
|
|
|
187
189
|
return fetch(forwardedRequest);
|
|
188
190
|
});
|
|
189
191
|
app.route("/evals", evalRoutes);
|
|
192
|
+
app.route("/api/github", githubRoutes);
|
|
190
193
|
setupOpenAPIRoutes(app);
|
|
191
194
|
app.use("/run/*", async (_c, next) => {
|
|
192
195
|
await next();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
-
import * as
|
|
2
|
+
import * as hono16 from "hono";
|
|
3
3
|
|
|
4
4
|
//#region src/domains/evals/routes/datasetTriggers.d.ts
|
|
5
|
-
declare const app: OpenAPIHono<
|
|
5
|
+
declare const app: OpenAPIHono<hono16.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
|
|
2
|
+
import * as hono0 from "hono";
|
|
3
3
|
|
|
4
4
|
//#region src/domains/evals/routes/index.d.ts
|
|
5
|
-
declare const app: OpenAPIHono<
|
|
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
|
|
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<
|
|
5
|
+
declare const workflowRoutes: Hono<hono_types5.BlankEnv, hono_types5.BlankSchema, "/">;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { workflowRoutes };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "@hono/zod-openapi";
|
|
2
|
+
|
|
3
|
+
//#region src/domains/github/config.d.ts
|
|
4
|
+
declare const GitHubAppConfigSchema: z.ZodObject<{
|
|
5
|
+
appId: z.ZodString;
|
|
6
|
+
privateKey: z.ZodString;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
type GitHubAppConfig = z.infer<typeof GitHubAppConfigSchema>;
|
|
9
|
+
declare function getGitHubAppConfig(): GitHubAppConfig;
|
|
10
|
+
declare function isGitHubAppConfigured(): boolean;
|
|
11
|
+
declare function validateGitHubAppConfigOnStartup(): void;
|
|
12
|
+
declare function clearConfigCache(): void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { GitHubAppConfig, clearConfigCache, getGitHubAppConfig, isGitHubAppConfigured, validateGitHubAppConfigOnStartup };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getLogger } from "../../logger.js";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
|
|
4
|
+
//#region src/domains/github/config.ts
|
|
5
|
+
const logger = getLogger("github-config");
|
|
6
|
+
const GitHubAppConfigSchema = z.object({
|
|
7
|
+
appId: z.string().min(1, "GITHUB_APP_ID is required"),
|
|
8
|
+
privateKey: z.string().min(1, "GITHUB_APP_PRIVATE_KEY is required")
|
|
9
|
+
});
|
|
10
|
+
let cachedConfig = null;
|
|
11
|
+
function getGitHubAppConfig() {
|
|
12
|
+
if (cachedConfig) return cachedConfig;
|
|
13
|
+
const appId = process.env.GITHUB_APP_ID;
|
|
14
|
+
const privateKey = process.env.GITHUB_APP_PRIVATE_KEY?.replace(/\\n/g, "\n");
|
|
15
|
+
const result = GitHubAppConfigSchema.safeParse({
|
|
16
|
+
appId,
|
|
17
|
+
privateKey
|
|
18
|
+
});
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
const errorMessage = `GitHub App credentials are not configured. ${result.error.issues.map((issue) => issue.message).join(". ")}. Please set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY environment variables.`;
|
|
21
|
+
logger.error({}, errorMessage);
|
|
22
|
+
throw new Error(errorMessage);
|
|
23
|
+
}
|
|
24
|
+
cachedConfig = result.data;
|
|
25
|
+
logger.info({}, "GitHub App credentials loaded successfully");
|
|
26
|
+
return cachedConfig;
|
|
27
|
+
}
|
|
28
|
+
function isGitHubAppConfigured() {
|
|
29
|
+
return Boolean(process.env.GITHUB_APP_ID && process.env.GITHUB_APP_PRIVATE_KEY);
|
|
30
|
+
}
|
|
31
|
+
function validateGitHubAppConfigOnStartup() {
|
|
32
|
+
if (!isGitHubAppConfigured()) {
|
|
33
|
+
logger.warn({}, "GitHub App credentials not configured. Token exchange endpoint will return 500 errors. Set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY to enable the feature.");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
getGitHubAppConfig();
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error({ error }, "GitHub App credentials are invalid. Token exchange endpoint will return 500 errors.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function clearConfigCache() {
|
|
43
|
+
cachedConfig = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { clearConfigCache, getGitHubAppConfig, isGitHubAppConfigured, validateGitHubAppConfigOnStartup };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GitHubAppConfig, getGitHubAppConfig, isGitHubAppConfigured } from "./config.js";
|
|
2
|
+
import { GenerateInstallationAccessTokenResult, GenerateTokenError, GenerateTokenResult, InstallationAccessToken, InstallationInfo, LookupInstallationError, LookupInstallationForRepoResult, LookupInstallationResult, generateInstallationAccessToken, lookupInstallationForRepo } from "./installation.js";
|
|
3
|
+
import { GetJwkResult, JwksError, JwksResult, clearJwksCache, getJwkForToken, getJwksCacheStatus } from "./jwks.js";
|
|
4
|
+
import { GitHubOidcClaims, ValidateOidcTokenResult, ValidateTokenError, ValidateTokenResult, validateOidcToken } from "./oidcToken.js";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import * as hono_types9 from "hono/types";
|
|
7
|
+
|
|
8
|
+
//#region src/domains/github/index.d.ts
|
|
9
|
+
declare function createGithubRoutes(): Hono<hono_types9.BlankEnv, hono_types9.BlankSchema, "/">;
|
|
10
|
+
declare const githubRoutes: Hono<hono_types9.BlankEnv, hono_types9.BlankSchema, "/">;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { type GenerateInstallationAccessTokenResult, type GenerateTokenError, type GenerateTokenResult, type GetJwkResult, type GitHubAppConfig, type GitHubOidcClaims, type InstallationAccessToken, type InstallationInfo, type JwksError, type JwksResult, type LookupInstallationError, type LookupInstallationForRepoResult, type LookupInstallationResult, type ValidateOidcTokenResult, type ValidateTokenError, type ValidateTokenResult, clearJwksCache, createGithubRoutes, generateInstallationAccessToken, getGitHubAppConfig, getJwkForToken, getJwksCacheStatus, githubRoutes, isGitHubAppConfigured, lookupInstallationForRepo, validateOidcToken };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getGitHubAppConfig, isGitHubAppConfigured, validateGitHubAppConfigOnStartup } from "./config.js";
|
|
2
|
+
import { generateInstallationAccessToken, lookupInstallationForRepo } from "./installation.js";
|
|
3
|
+
import { clearJwksCache, getJwkForToken, getJwksCacheStatus } from "./jwks.js";
|
|
4
|
+
import { validateOidcToken } from "./oidcToken.js";
|
|
5
|
+
import tokenExchange_default from "./routes/tokenExchange.js";
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
|
|
8
|
+
//#region src/domains/github/index.ts
|
|
9
|
+
function createGithubRoutes() {
|
|
10
|
+
validateGitHubAppConfigOnStartup();
|
|
11
|
+
const app = new Hono();
|
|
12
|
+
app.route("/token-exchange", tokenExchange_default);
|
|
13
|
+
return app;
|
|
14
|
+
}
|
|
15
|
+
const githubRoutes = createGithubRoutes();
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { clearJwksCache, createGithubRoutes, generateInstallationAccessToken, getGitHubAppConfig, getJwkForToken, getJwksCacheStatus, githubRoutes, isGitHubAppConfigured, lookupInstallationForRepo, validateOidcToken };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/domains/github/installation.d.ts
|
|
2
|
+
interface InstallationInfo {
|
|
3
|
+
installationId: number;
|
|
4
|
+
accountLogin: string;
|
|
5
|
+
accountType: 'User' | 'Organization';
|
|
6
|
+
}
|
|
7
|
+
interface LookupInstallationResult {
|
|
8
|
+
success: true;
|
|
9
|
+
installation: InstallationInfo;
|
|
10
|
+
}
|
|
11
|
+
interface LookupInstallationError {
|
|
12
|
+
success: false;
|
|
13
|
+
errorType: 'not_installed' | 'api_error' | 'jwt_error';
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
type LookupInstallationForRepoResult = LookupInstallationResult | LookupInstallationError;
|
|
17
|
+
interface InstallationAccessToken {
|
|
18
|
+
token: string;
|
|
19
|
+
expiresAt: string;
|
|
20
|
+
}
|
|
21
|
+
interface GenerateTokenResult {
|
|
22
|
+
success: true;
|
|
23
|
+
accessToken: InstallationAccessToken;
|
|
24
|
+
}
|
|
25
|
+
interface GenerateTokenError {
|
|
26
|
+
success: false;
|
|
27
|
+
errorType: 'api_error' | 'jwt_error';
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
type GenerateInstallationAccessTokenResult = GenerateTokenResult | GenerateTokenError;
|
|
31
|
+
declare function lookupInstallationForRepo(repositoryOwner: string, repositoryName: string): Promise<LookupInstallationForRepoResult>;
|
|
32
|
+
declare function generateInstallationAccessToken(installationId: number): Promise<GenerateInstallationAccessTokenResult>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { GenerateInstallationAccessTokenResult, GenerateTokenError, GenerateTokenResult, InstallationAccessToken, InstallationInfo, LookupInstallationError, LookupInstallationForRepoResult, LookupInstallationResult, generateInstallationAccessToken, lookupInstallationForRepo };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { getLogger } from "../../logger.js";
|
|
2
|
+
import { getGitHubAppConfig } from "./config.js";
|
|
3
|
+
import { createPrivateKey } from "node:crypto";
|
|
4
|
+
import { SignJWT } from "jose";
|
|
5
|
+
|
|
6
|
+
//#region src/domains/github/installation.ts
|
|
7
|
+
const logger = getLogger("github-installation");
|
|
8
|
+
const GITHUB_API_BASE = "https://api.github.com";
|
|
9
|
+
async function createAppJwt() {
|
|
10
|
+
const config = getGitHubAppConfig();
|
|
11
|
+
const privateKey = createPrivateKey({
|
|
12
|
+
key: config.privateKey,
|
|
13
|
+
format: "pem"
|
|
14
|
+
});
|
|
15
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
16
|
+
return await new SignJWT({}).setProtectedHeader({ alg: "RS256" }).setIssuedAt(now - 60).setExpirationTime(now + 600).setIssuer(config.appId).sign(privateKey);
|
|
17
|
+
}
|
|
18
|
+
async function lookupInstallationForRepo(repositoryOwner, repositoryName) {
|
|
19
|
+
let appJwt;
|
|
20
|
+
try {
|
|
21
|
+
appJwt = await createAppJwt();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
24
|
+
logger.error({ error: message }, "Failed to create GitHub App JWT");
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
errorType: "jwt_error",
|
|
28
|
+
message: `Failed to create GitHub App authentication: ${message}`
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const url = `${GITHUB_API_BASE}/repos/${repositoryOwner}/${repositoryName}/installation`;
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${appJwt}`,
|
|
37
|
+
Accept: "application/vnd.github+json",
|
|
38
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
39
|
+
"User-Agent": "inkeep-agents-api"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (response.status === 404) return {
|
|
43
|
+
success: false,
|
|
44
|
+
errorType: "not_installed",
|
|
45
|
+
message: `GitHub App is not installed on repository ${repositoryOwner}/${repositoryName}. Please install the Inkeep Agents GitHub App on the repository to enable token exchange.`
|
|
46
|
+
};
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorText = await response.text();
|
|
49
|
+
logger.error({
|
|
50
|
+
status: response.status,
|
|
51
|
+
error: errorText,
|
|
52
|
+
repositoryOwner,
|
|
53
|
+
repositoryName
|
|
54
|
+
}, "GitHub API error looking up installation");
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
errorType: "api_error",
|
|
58
|
+
message: `GitHub API error (${response.status}): Failed to look up installation for repository`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const data = await response.json();
|
|
62
|
+
const installationId = data.id;
|
|
63
|
+
const accountLogin = data.account?.login;
|
|
64
|
+
const accountType = data.account?.type;
|
|
65
|
+
if (typeof installationId !== "number" || typeof accountLogin !== "string") {
|
|
66
|
+
logger.error({ data }, "Unexpected response format from GitHub API");
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
errorType: "api_error",
|
|
70
|
+
message: "Unexpected response format from GitHub API"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
logger.info({
|
|
74
|
+
installationId,
|
|
75
|
+
accountLogin,
|
|
76
|
+
accountType,
|
|
77
|
+
repositoryOwner,
|
|
78
|
+
repositoryName
|
|
79
|
+
}, "Found GitHub App installation for repository");
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
installation: {
|
|
83
|
+
installationId,
|
|
84
|
+
accountLogin,
|
|
85
|
+
accountType: accountType === "Organization" ? "Organization" : "User"
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
90
|
+
logger.error({
|
|
91
|
+
error: message,
|
|
92
|
+
repositoryOwner,
|
|
93
|
+
repositoryName
|
|
94
|
+
}, "Error calling GitHub API to look up installation");
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
errorType: "api_error",
|
|
98
|
+
message: `Failed to connect to GitHub API: ${message}`
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function generateInstallationAccessToken(installationId) {
|
|
103
|
+
let appJwt;
|
|
104
|
+
try {
|
|
105
|
+
appJwt = await createAppJwt();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
108
|
+
logger.error({ error: message }, "Failed to create GitHub App JWT for token generation");
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
errorType: "jwt_error",
|
|
112
|
+
message: `Failed to create GitHub App authentication: ${message}`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const url = `${GITHUB_API_BASE}/app/installations/${installationId}/access_tokens`;
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch(url, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: {
|
|
120
|
+
Authorization: `Bearer ${appJwt}`,
|
|
121
|
+
Accept: "application/vnd.github+json",
|
|
122
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
123
|
+
"User-Agent": "inkeep-agents-api"
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
const errorText = await response.text();
|
|
128
|
+
logger.error({
|
|
129
|
+
status: response.status,
|
|
130
|
+
error: errorText,
|
|
131
|
+
installationId
|
|
132
|
+
}, "GitHub API error generating installation access token");
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
errorType: "api_error",
|
|
136
|
+
message: `GitHub API error (${response.status}): Failed to generate installation access token`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const data = await response.json();
|
|
140
|
+
const token = data.token;
|
|
141
|
+
const expiresAt = data.expires_at;
|
|
142
|
+
if (typeof token !== "string" || typeof expiresAt !== "string") {
|
|
143
|
+
logger.error({ data }, "Unexpected response format from GitHub API for token generation");
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
errorType: "api_error",
|
|
147
|
+
message: "Unexpected response format from GitHub API"
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
success: true,
|
|
152
|
+
accessToken: {
|
|
153
|
+
token,
|
|
154
|
+
expiresAt
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
159
|
+
logger.error({
|
|
160
|
+
error: message,
|
|
161
|
+
installationId
|
|
162
|
+
}, "Error calling GitHub API to generate installation access token");
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
errorType: "api_error",
|
|
166
|
+
message: `Failed to connect to GitHub API: ${message}`
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
export { generateInstallationAccessToken, lookupInstallationForRepo };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CryptoKey, JWSHeaderParameters } from "jose";
|
|
2
|
+
|
|
3
|
+
//#region src/domains/github/jwks.d.ts
|
|
4
|
+
interface JwksResult {
|
|
5
|
+
success: true;
|
|
6
|
+
key: CryptoKey;
|
|
7
|
+
}
|
|
8
|
+
interface JwksError {
|
|
9
|
+
success: false;
|
|
10
|
+
error: string;
|
|
11
|
+
}
|
|
12
|
+
type GetJwkResult = JwksResult | JwksError;
|
|
13
|
+
declare function getJwkForToken(header: JWSHeaderParameters): Promise<GetJwkResult>;
|
|
14
|
+
declare function clearJwksCache(): void;
|
|
15
|
+
declare function getJwksCacheStatus(): {
|
|
16
|
+
cached: boolean;
|
|
17
|
+
expiresIn?: number;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { GetJwkResult, JwksError, JwksResult, clearJwksCache, getJwkForToken, getJwksCacheStatus };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getLogger } from "../../logger.js";
|
|
2
|
+
import { createRemoteJWKSet } from "jose";
|
|
3
|
+
|
|
4
|
+
//#region src/domains/github/jwks.ts
|
|
5
|
+
const logger = getLogger("github-jwks");
|
|
6
|
+
const GITHUB_OIDC_JWKS_URL = "https://token.actions.githubusercontent.com/.well-known/jwks";
|
|
7
|
+
const CACHE_TTL_MS = 3600 * 1e3;
|
|
8
|
+
let jwksCache = null;
|
|
9
|
+
function createJwksWithLogging() {
|
|
10
|
+
logger.info({}, "Creating new JWKS fetch function for GitHub OIDC");
|
|
11
|
+
return createRemoteJWKSet(new URL(GITHUB_OIDC_JWKS_URL), { cacheMaxAge: CACHE_TTL_MS });
|
|
12
|
+
}
|
|
13
|
+
function isCacheExpired() {
|
|
14
|
+
if (!jwksCache) return true;
|
|
15
|
+
return Date.now() - jwksCache.fetchedAt > CACHE_TTL_MS;
|
|
16
|
+
}
|
|
17
|
+
function getOrCreateJwksFunction() {
|
|
18
|
+
if (!jwksCache || isCacheExpired()) jwksCache = {
|
|
19
|
+
jwks: createJwksWithLogging(),
|
|
20
|
+
fetchedAt: Date.now()
|
|
21
|
+
};
|
|
22
|
+
return jwksCache.jwks;
|
|
23
|
+
}
|
|
24
|
+
async function getJwkForToken(header) {
|
|
25
|
+
const kid = header.kid;
|
|
26
|
+
if (!kid) return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: "Token is missing key ID (kid) in header"
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
const key = await getOrCreateJwksFunction()(header);
|
|
32
|
+
logger.debug({ kid }, "Successfully retrieved JWK for token");
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
key
|
|
36
|
+
};
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
39
|
+
if (errorMessage.includes("no applicable key found")) {
|
|
40
|
+
logger.warn({ kid }, "Key ID not found in JWKS, refreshing cache");
|
|
41
|
+
jwksCache = null;
|
|
42
|
+
try {
|
|
43
|
+
const key = await getOrCreateJwksFunction()(header);
|
|
44
|
+
logger.info({ kid }, "Successfully retrieved JWK after cache refresh");
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
key
|
|
48
|
+
};
|
|
49
|
+
} catch (retryError) {
|
|
50
|
+
const retryErrorMessage = retryError instanceof Error ? retryError.message : "Unknown error";
|
|
51
|
+
logger.error({
|
|
52
|
+
kid,
|
|
53
|
+
error: retryErrorMessage
|
|
54
|
+
}, "Failed to retrieve JWK after cache refresh");
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: `Key ID '${kid}' not found in GitHub OIDC JWKS`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
logger.error({
|
|
62
|
+
kid,
|
|
63
|
+
error: errorMessage
|
|
64
|
+
}, "Failed to fetch JWKS from GitHub");
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: `Failed to fetch GitHub OIDC JWKS: ${errorMessage}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function clearJwksCache() {
|
|
72
|
+
jwksCache = null;
|
|
73
|
+
logger.debug({}, "JWKS cache cleared");
|
|
74
|
+
}
|
|
75
|
+
function getJwksCacheStatus() {
|
|
76
|
+
if (!jwksCache) return { cached: false };
|
|
77
|
+
const expiresIn = CACHE_TTL_MS - (Date.now() - jwksCache.fetchedAt);
|
|
78
|
+
return {
|
|
79
|
+
cached: true,
|
|
80
|
+
expiresIn: Math.max(0, expiresIn)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { clearJwksCache, getJwkForToken, getJwksCacheStatus };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/domains/github/oidcToken.d.ts
|
|
2
|
+
interface GitHubOidcClaims {
|
|
3
|
+
repository: string;
|
|
4
|
+
repository_owner: string;
|
|
5
|
+
repository_id: string;
|
|
6
|
+
workflow: string;
|
|
7
|
+
actor: string;
|
|
8
|
+
ref: string;
|
|
9
|
+
}
|
|
10
|
+
interface ValidateTokenResult {
|
|
11
|
+
success: true;
|
|
12
|
+
claims: GitHubOidcClaims;
|
|
13
|
+
}
|
|
14
|
+
interface ValidateTokenError {
|
|
15
|
+
success: false;
|
|
16
|
+
errorType: 'invalid_signature' | 'expired' | 'wrong_issuer' | 'wrong_audience' | 'malformed' | 'jwks_error';
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
type ValidateOidcTokenResult = ValidateTokenResult | ValidateTokenError;
|
|
20
|
+
declare function validateOidcToken(token: string): Promise<ValidateOidcTokenResult>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { GitHubOidcClaims, ValidateOidcTokenResult, ValidateTokenError, ValidateTokenResult, validateOidcToken };
|