@inkeep/agents-core 0.41.2 → 0.42.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/dist/api-client/base-client.d.ts +87 -8
- package/dist/api-client/base-client.js +174 -1
- package/dist/api-client/eval-api-client.d.ts +47 -0
- package/dist/api-client/eval-api-client.js +65 -0
- package/dist/api-client/index.d.ts +4 -0
- package/dist/api-client/index.js +5 -0
- package/dist/api-client/manage-api-client.d.ts +34 -0
- package/dist/api-client/manage-api-client.js +104 -0
- package/dist/auth/auth.d.ts +86 -20
- package/dist/auth/auth.js +55 -1
- package/dist/auth/authz/client.d.ts +81 -0
- package/dist/auth/authz/client.js +189 -0
- package/dist/auth/authz/config.d.ts +76 -0
- package/dist/auth/authz/config.js +76 -0
- package/dist/auth/authz/index.d.ts +5 -0
- package/dist/auth/authz/index.js +6 -0
- package/dist/auth/authz/permissions.d.ts +57 -0
- package/dist/auth/authz/permissions.js +83 -0
- package/dist/auth/authz/sync.d.ts +85 -0
- package/dist/auth/authz/sync.js +237 -0
- package/dist/auth/permissions.d.ts +13 -13
- package/dist/auth/permissions.js +2 -181
- package/dist/client-exports.d.ts +8 -3
- package/dist/client-exports.js +3 -2
- package/dist/constants/context-breakdown.d.ts +61 -0
- package/dist/constants/context-breakdown.js +124 -0
- package/dist/constants/otel-attributes.d.ts +4 -0
- package/dist/constants/otel-attributes.js +4 -0
- package/dist/context/ContextConfig.d.ts +2 -2
- package/dist/context/ContextConfig.js +3 -3
- package/dist/context/TemplateEngine.js +0 -1
- package/dist/context/index.d.ts +1 -5
- package/dist/context/index.js +1 -5
- package/dist/credential-stuffer/CredentialStuffer.d.ts +1 -1
- package/dist/data-access/index.d.ts +34 -26
- package/dist/data-access/index.js +34 -26
- package/dist/data-access/manage/agentFull.d.ts +36 -0
- package/dist/data-access/{agentFull.js → manage/agentFull.js} +205 -7
- package/dist/data-access/{agents.d.ts → manage/agents.d.ts} +23 -22
- package/dist/data-access/{agents.js → manage/agents.js} +52 -7
- package/dist/data-access/{artifactComponents.d.ts → manage/artifactComponents.d.ts} +21 -21
- package/dist/data-access/{artifactComponents.js → manage/artifactComponents.js} +5 -5
- package/dist/data-access/{contextConfigs.d.ts → manage/contextConfigs.d.ts} +14 -14
- package/dist/data-access/{contextConfigs.js → manage/contextConfigs.js} +3 -3
- package/dist/data-access/{credentialReferences.d.ts → manage/credentialReferences.d.ts} +17 -17
- package/dist/data-access/{credentialReferences.js → manage/credentialReferences.js} +2 -2
- package/dist/data-access/{dataComponents.d.ts → manage/dataComponents.d.ts} +20 -20
- package/dist/data-access/{dataComponents.js → manage/dataComponents.js} +7 -7
- package/dist/data-access/manage/evalConfig.d.ts +221 -0
- package/dist/data-access/manage/evalConfig.js +275 -0
- package/dist/data-access/{externalAgents.d.ts → manage/externalAgents.d.ts} +16 -16
- package/dist/data-access/{externalAgents.js → manage/externalAgents.js} +2 -2
- package/dist/data-access/{functionTools.d.ts → manage/functionTools.d.ts} +65 -15
- package/dist/data-access/{functionTools.js → manage/functionTools.js} +90 -8
- package/dist/data-access/{functions.d.ts → manage/functions.d.ts} +9 -9
- package/dist/data-access/{functions.js → manage/functions.js} +3 -3
- package/dist/data-access/manage/projectFull.d.ts +38 -0
- package/dist/data-access/{projectFull.js → manage/projectFull.js} +64 -65
- package/dist/data-access/manage/projectLifecycle.d.ts +119 -0
- package/dist/data-access/manage/projectLifecycle.js +234 -0
- package/dist/data-access/manage/projects.d.ts +75 -0
- package/dist/data-access/{projects.js → manage/projects.js} +15 -16
- package/dist/data-access/{subAgentExternalAgentRelations.d.ts → manage/subAgentExternalAgentRelations.d.ts} +19 -19
- package/dist/data-access/{subAgentExternalAgentRelations.js → manage/subAgentExternalAgentRelations.js} +2 -2
- package/dist/data-access/{subAgentRelations.d.ts → manage/subAgentRelations.d.ts} +29 -29
- package/dist/data-access/{subAgentRelations.js → manage/subAgentRelations.js} +3 -3
- package/dist/data-access/{subAgentTeamAgentRelations.d.ts → manage/subAgentTeamAgentRelations.d.ts} +19 -19
- package/dist/data-access/{subAgentTeamAgentRelations.js → manage/subAgentTeamAgentRelations.js} +2 -2
- package/dist/data-access/{subAgents.d.ts → manage/subAgents.d.ts} +13 -13
- package/dist/data-access/{subAgents.js → manage/subAgents.js} +4 -4
- package/dist/data-access/{tools.d.ts → manage/tools.d.ts} +26 -19
- package/dist/data-access/{tools.js → manage/tools.js} +57 -35
- package/dist/data-access/manage/triggers.d.ts +80 -0
- package/dist/data-access/manage/triggers.js +81 -0
- package/dist/data-access/{apiKeys.d.ts → runtime/apiKeys.d.ts} +17 -17
- package/dist/data-access/{apiKeys.js → runtime/apiKeys.js} +3 -3
- package/dist/data-access/runtime/cascade-delete.d.ts +77 -0
- package/dist/data-access/runtime/cascade-delete.js +111 -0
- package/dist/data-access/{contextCache.d.ts → runtime/contextCache.d.ts} +13 -13
- package/dist/data-access/{contextCache.js → runtime/contextCache.js} +5 -5
- package/dist/data-access/{conversations.d.ts → runtime/conversations.d.ts} +68 -19
- package/dist/data-access/{conversations.js → runtime/conversations.js} +13 -7
- package/dist/data-access/runtime/evalRuns.d.ts +120 -0
- package/dist/data-access/runtime/evalRuns.js +168 -0
- package/dist/data-access/{ledgerArtifacts.d.ts → runtime/ledgerArtifacts.d.ts} +13 -13
- package/dist/data-access/{ledgerArtifacts.js → runtime/ledgerArtifacts.js} +3 -3
- package/dist/data-access/{messages.d.ts → runtime/messages.d.ts} +15 -15
- package/dist/data-access/{messages.js → runtime/messages.js} +2 -2
- package/dist/data-access/{organizations.d.ts → runtime/organizations.d.ts} +16 -7
- package/dist/data-access/{organizations.js → runtime/organizations.js} +15 -3
- package/dist/data-access/runtime/projects.d.ts +62 -0
- package/dist/data-access/runtime/projects.js +90 -0
- package/dist/data-access/runtime/tasks.d.ts +55 -0
- package/dist/data-access/{tasks.js → runtime/tasks.js} +2 -2
- package/dist/data-access/runtime/triggerInvocations.d.ts +62 -0
- package/dist/data-access/runtime/triggerInvocations.js +54 -0
- package/dist/data-access/runtime/users.d.ts +19 -0
- package/dist/data-access/{users.js → runtime/users.js} +2 -2
- package/dist/data-access/validation.d.ts +4 -4
- package/dist/data-access/validation.js +1 -1
- package/dist/db/clean.d.ts +8 -4
- package/dist/db/clean.js +14 -105
- package/dist/db/delete.d.ts +1 -1
- package/dist/db/delete.js +7 -10
- package/dist/db/manage/dolt-cleanup.d.ts +51 -0
- package/dist/db/manage/dolt-cleanup.js +132 -0
- package/dist/db/manage/manage-client.d.ts +26 -0
- package/dist/db/manage/manage-client.js +68 -0
- package/dist/db/{schema.d.ts → manage/manage-schema.d.ts} +1459 -1285
- package/dist/db/{schema.js → manage/manage-schema.js} +433 -341
- package/dist/db/manage/test-manage-client.d.ts +27 -0
- package/dist/db/manage/test-manage-client.js +68 -0
- package/dist/db/runtime/runtime-client.d.ts +20 -0
- package/dist/db/runtime/runtime-client.js +30 -0
- package/dist/db/runtime/runtime-schema.d.ts +2834 -0
- package/dist/db/runtime/runtime-schema.js +483 -0
- package/dist/db/runtime/test-runtime-client.d.ts +27 -0
- package/dist/db/{test-client.js → runtime/test-runtime-client.js} +11 -25
- package/dist/dolt/branch.d.ts +62 -0
- package/dist/dolt/branch.js +82 -0
- package/dist/dolt/branches-api.d.ts +108 -0
- package/dist/dolt/branches-api.js +162 -0
- package/dist/dolt/commit.d.ts +94 -0
- package/dist/dolt/commit.js +103 -0
- package/dist/dolt/diff.d.ts +27 -0
- package/dist/dolt/diff.js +21 -0
- package/dist/dolt/index.d.ts +10 -0
- package/dist/dolt/index.js +11 -0
- package/dist/dolt/merge.d.ts +63 -0
- package/dist/dolt/merge.js +81 -0
- package/dist/dolt/migrate-all-branches.d.ts +4 -0
- package/dist/dolt/migrate-all-branches.js +78 -0
- package/dist/dolt/migrate-dolt.d.ts +1 -0
- package/dist/dolt/migrate-dolt.js +22 -0
- package/dist/dolt/ref-helpers.d.ts +19 -0
- package/dist/dolt/ref-helpers.js +65 -0
- package/dist/dolt/ref-middleware.d.ts +82 -0
- package/dist/dolt/ref-middleware.js +217 -0
- package/dist/dolt/ref-scope.d.ts +101 -0
- package/dist/dolt/ref-scope.js +231 -0
- package/dist/dolt/schema-sync.d.ts +134 -0
- package/dist/dolt/schema-sync.js +246 -0
- package/dist/env.d.ts +6 -4
- package/dist/env.js +3 -2
- package/dist/index.d.ts +71 -44
- package/dist/index.js +74 -47
- package/dist/types/entities.d.ts +81 -2
- package/dist/types/index.d.ts +3 -3
- package/dist/types/utility.d.ts +45 -4
- package/dist/utils/JsonTransformer.d.ts +44 -0
- package/dist/utils/JsonTransformer.js +112 -0
- package/dist/utils/apiKeys.d.ts +5 -1
- package/dist/utils/apiKeys.js +11 -1
- package/dist/utils/colors.d.ts +34 -0
- package/dist/utils/colors.js +49 -0
- package/dist/utils/credential-store-utils.d.ts +1 -1
- package/dist/utils/format-messages.d.ts +1 -1
- package/dist/utils/index.d.ts +7 -3
- package/dist/utils/index.js +7 -3
- package/dist/utils/internal-service-auth.d.ts +79 -0
- package/dist/utils/internal-service-auth.js +140 -0
- package/dist/utils/jwt-helpers.d.ts +56 -0
- package/dist/utils/jwt-helpers.js +90 -0
- package/dist/utils/service-token-auth.d.ts +9 -27
- package/dist/utils/service-token-auth.js +48 -96
- package/dist/utils/template-interpolation.d.ts +22 -0
- package/dist/utils/template-interpolation.js +62 -0
- package/dist/utils/third-party-mcp-servers/composio-client.js +23 -23
- package/dist/utils/trigger-auth.d.ts +62 -0
- package/dist/utils/trigger-auth.js +125 -0
- package/dist/validation/agentFull.js +2 -4
- package/dist/validation/dolt-schemas.d.ts +49 -0
- package/dist/validation/dolt-schemas.js +44 -0
- package/dist/validation/drizzle-schema-helpers.d.ts +4 -26
- package/dist/validation/drizzle-schema-helpers.js +5 -151
- package/dist/validation/index.d.ts +4 -3
- package/dist/validation/index.js +3 -2
- package/dist/validation/schemas.d.ts +17647 -4789
- package/dist/validation/schemas.js +328 -11
- package/drizzle/manage/0000_tearful_rhodey.sql +414 -0
- package/drizzle/manage/0001_broken_wendell_vaughn.sql +19 -0
- package/drizzle/manage/0002_bent_sunfire.sql +1 -0
- package/drizzle/manage/meta/0000_snapshot.json +2987 -0
- package/drizzle/manage/meta/0001_snapshot.json +3115 -0
- package/drizzle/manage/meta/0002_snapshot.json +3115 -0
- package/drizzle/manage/meta/_journal.json +27 -0
- package/drizzle/runtime/0008_silly_preak.sql +127 -0
- package/drizzle/runtime/0009_freezing_leo.sql +17 -0
- package/drizzle/runtime/meta/0008_snapshot.json +2263 -0
- package/drizzle/runtime/meta/0009_snapshot.json +2397 -0
- package/drizzle/{meta → runtime/meta}/_journal.json +14 -0
- package/package.json +48 -15
- package/dist/context/ContextFetcher.d.ts +0 -73
- package/dist/context/ContextFetcher.js +0 -291
- package/dist/context/ContextResolver.d.ts +0 -60
- package/dist/context/ContextResolver.js +0 -278
- package/dist/context/context.d.ts +0 -27
- package/dist/context/context.js +0 -128
- package/dist/context/contextCache.d.ts +0 -58
- package/dist/context/contextCache.js +0 -177
- package/dist/data-access/agentFull.d.ts +0 -33
- package/dist/data-access/projectFull.d.ts +0 -32
- package/dist/data-access/projects.d.ts +0 -71
- package/dist/data-access/tasks.d.ts +0 -45
- package/dist/data-access/users.d.ts +0 -19
- package/dist/db/client.d.ts +0 -20
- package/dist/db/client.js +0 -28
- package/dist/db/test-client.d.ts +0 -31
- package/dist/middleware/contextValidation.d.ts +0 -46
- package/dist/middleware/contextValidation.js +0 -280
- package/dist/middleware/index.d.ts +0 -2
- package/dist/middleware/index.js +0 -3
- package/dist/utils/execution.d.ts +0 -22
- package/dist/utils/execution.js +0 -25
- /package/drizzle/{0000_exotic_mysterio.sql → runtime/0000_exotic_mysterio.sql} +0 -0
- /package/drizzle/{0001_calm_sheva_callister.sql → runtime/0001_calm_sheva_callister.sql} +0 -0
- /package/drizzle/{0002_puzzling_goblin_queen.sql → runtime/0002_puzzling_goblin_queen.sql} +0 -0
- /package/drizzle/{0003_sweet_human_robot.sql → runtime/0003_sweet_human_robot.sql} +0 -0
- /package/drizzle/{0004_cuddly_shooting_star.sql → runtime/0004_cuddly_shooting_star.sql} +0 -0
- /package/drizzle/{0005_reflective_starfox.sql → runtime/0005_reflective_starfox.sql} +0 -0
- /package/drizzle/{0006_stale_thaddeus_ross.sql → runtime/0006_stale_thaddeus_ross.sql} +0 -0
- /package/drizzle/{0007_slim_karma.sql → runtime/0007_slim_karma.sql} +0 -0
- /package/drizzle/{meta → runtime/meta}/0000_snapshot.json +0 -0
- /package/drizzle/{meta → runtime/meta}/0001_snapshot.json +0 -0
- /package/drizzle/{meta → runtime/meta}/0003_snapshot.json +0 -0
- /package/drizzle/{meta → runtime/meta}/0005_snapshot.json +0 -0
- /package/drizzle/{meta → runtime/meta}/0006_snapshot.json +0 -0
- /package/drizzle/{meta → runtime/meta}/0007_snapshot.json +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { getLogger } from "./logger.js";
|
|
2
|
+
import { extractBearerToken, hasIssuer, signJwt, verifyJwt } from "./jwt-helpers.js";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/internal-service-auth.ts
|
|
5
|
+
const logger = getLogger("internal-service-auth");
|
|
6
|
+
const ISSUER = "inkeep-agents-internal";
|
|
7
|
+
/**
|
|
8
|
+
* Known internal services that can authenticate
|
|
9
|
+
*/
|
|
10
|
+
const InternalServices = {
|
|
11
|
+
INKEEP_AGENTS_RUN_API: "inkeep-agents-run-api",
|
|
12
|
+
INKEEP_AGENTS_MANAGE_API: "inkeep-agents-manage-api",
|
|
13
|
+
INKEEP_AGENTS_EVAL_API: "inkeep-agents-eval-api"
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Generate an internal service token for service-to-service authentication
|
|
17
|
+
*/
|
|
18
|
+
async function generateInternalServiceToken(params) {
|
|
19
|
+
try {
|
|
20
|
+
const claims = {};
|
|
21
|
+
if (params.tenantId) claims.tenantId = params.tenantId;
|
|
22
|
+
if (params.projectId) claims.projectId = params.projectId;
|
|
23
|
+
if (params.userId) claims.userId = params.userId;
|
|
24
|
+
const token = await signJwt({
|
|
25
|
+
issuer: ISSUER,
|
|
26
|
+
subject: params.serviceId,
|
|
27
|
+
expiresIn: params.expiresIn || "5m",
|
|
28
|
+
claims
|
|
29
|
+
});
|
|
30
|
+
logger.debug({
|
|
31
|
+
serviceId: params.serviceId,
|
|
32
|
+
tenantId: params.tenantId,
|
|
33
|
+
projectId: params.projectId
|
|
34
|
+
}, "Generated internal service token");
|
|
35
|
+
return token;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
logger.error({ error }, "Failed to generate internal service token");
|
|
38
|
+
throw new Error("Failed to generate internal service token");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Verify and decode an internal service token
|
|
43
|
+
*/
|
|
44
|
+
async function verifyInternalServiceToken(token) {
|
|
45
|
+
const result = await verifyJwt(token, { issuer: ISSUER });
|
|
46
|
+
if (!result.valid || !result.payload) {
|
|
47
|
+
logger.warn({ error: result.error }, "Internal service token verification failed");
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
error: result.error
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const payload = result.payload;
|
|
54
|
+
if (typeof payload.sub !== "string") {
|
|
55
|
+
logger.warn({ payload }, "Invalid internal service token: missing subject");
|
|
56
|
+
return {
|
|
57
|
+
valid: false,
|
|
58
|
+
error: "Invalid token: missing service identifier"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (!Object.values(InternalServices).includes(payload.sub)) {
|
|
62
|
+
logger.warn({ serviceId: payload.sub }, "Unknown service identifier in token");
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
error: `Unknown service identifier: ${payload.sub}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const validPayload = {
|
|
69
|
+
iss: payload.iss,
|
|
70
|
+
sub: payload.sub,
|
|
71
|
+
tenantId: payload.tenantId,
|
|
72
|
+
projectId: payload.projectId,
|
|
73
|
+
userId: payload.userId,
|
|
74
|
+
iat: payload.iat,
|
|
75
|
+
exp: payload.exp
|
|
76
|
+
};
|
|
77
|
+
logger.debug({
|
|
78
|
+
serviceId: validPayload.sub,
|
|
79
|
+
tenantId: validPayload.tenantId,
|
|
80
|
+
projectId: validPayload.projectId,
|
|
81
|
+
userId: validPayload.userId
|
|
82
|
+
}, "Successfully verified internal service token");
|
|
83
|
+
return {
|
|
84
|
+
valid: true,
|
|
85
|
+
payload: validPayload
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract and verify an internal service token from Authorization header
|
|
90
|
+
*/
|
|
91
|
+
async function verifyInternalServiceAuthHeader(authHeader) {
|
|
92
|
+
const extracted = extractBearerToken(authHeader);
|
|
93
|
+
if (!extracted.token) return {
|
|
94
|
+
valid: false,
|
|
95
|
+
error: extracted.error
|
|
96
|
+
};
|
|
97
|
+
return verifyInternalServiceToken(extracted.token);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a token is an internal service token (vs user/agent token)
|
|
101
|
+
* by checking the issuer claim without full verification
|
|
102
|
+
*/
|
|
103
|
+
function isInternalServiceToken(token) {
|
|
104
|
+
return hasIssuer(token, ISSUER);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Validate that the token has access to the specified tenant.
|
|
108
|
+
* If token has no tenantId claim, it has access to all tenants (superuser service).
|
|
109
|
+
*/
|
|
110
|
+
function validateInternalServiceTenantAccess(payload, tenantId) {
|
|
111
|
+
if (!payload.tenantId) return true;
|
|
112
|
+
if (payload.tenantId !== tenantId) {
|
|
113
|
+
logger.warn({
|
|
114
|
+
tokenTenantId: payload.tenantId,
|
|
115
|
+
requestedTenantId: tenantId,
|
|
116
|
+
serviceId: payload.sub
|
|
117
|
+
}, "Internal service token tenant mismatch");
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Validate that the token has access to the specified project.
|
|
124
|
+
* If token has no projectId claim, it has access to all projects in the allowed tenant(s).
|
|
125
|
+
*/
|
|
126
|
+
function validateInternalServiceProjectAccess(payload, projectId) {
|
|
127
|
+
if (!payload.projectId) return true;
|
|
128
|
+
if (payload.projectId !== projectId) {
|
|
129
|
+
logger.warn({
|
|
130
|
+
tokenProjectId: payload.projectId,
|
|
131
|
+
requestedProjectId: projectId,
|
|
132
|
+
serviceId: payload.sub
|
|
133
|
+
}, "Internal service token project mismatch");
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
export { InternalServices, generateInternalServiceToken, isInternalServiceToken, validateInternalServiceProjectAccess, validateInternalServiceTenantAccess, verifyInternalServiceAuthHeader, verifyInternalServiceToken };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/utils/jwt-helpers.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Get the JWT signing secret from environment variables.
|
|
4
|
+
* Falls back to an insecure default in non-production environments.
|
|
5
|
+
*/
|
|
6
|
+
declare function getJwtSecret(): Uint8Array;
|
|
7
|
+
/**
|
|
8
|
+
* Common verification result structure
|
|
9
|
+
*/
|
|
10
|
+
interface JwtVerifyResult<T> {
|
|
11
|
+
valid: boolean;
|
|
12
|
+
payload?: T;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for signing a JWT
|
|
17
|
+
*/
|
|
18
|
+
interface SignJwtOptions {
|
|
19
|
+
issuer: string;
|
|
20
|
+
subject: string;
|
|
21
|
+
audience?: string;
|
|
22
|
+
expiresIn?: string;
|
|
23
|
+
claims?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sign a JWT with the shared secret
|
|
27
|
+
*/
|
|
28
|
+
declare function signJwt(options: SignJwtOptions): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Options for verifying a JWT
|
|
31
|
+
*/
|
|
32
|
+
interface VerifyJwtOptions {
|
|
33
|
+
issuer: string;
|
|
34
|
+
audience?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Verify a JWT and return the raw payload
|
|
38
|
+
*/
|
|
39
|
+
declare function verifyJwt(token: string, options: VerifyJwtOptions): Promise<JwtVerifyResult<Record<string, unknown>>>;
|
|
40
|
+
/**
|
|
41
|
+
* Extract bearer token from Authorization header
|
|
42
|
+
*/
|
|
43
|
+
declare function extractBearerToken(authHeader: string | undefined): {
|
|
44
|
+
token?: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Decode JWT payload without verification (for checking issuer before full verify)
|
|
49
|
+
*/
|
|
50
|
+
declare function decodeJwtPayload(token: string): Record<string, unknown> | null;
|
|
51
|
+
/**
|
|
52
|
+
* Check if a token has a specific issuer (without full verification)
|
|
53
|
+
*/
|
|
54
|
+
declare function hasIssuer(token: string, issuer: string): boolean;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { JwtVerifyResult, SignJwtOptions, VerifyJwtOptions, decodeJwtPayload, extractBearerToken, getJwtSecret, hasIssuer, signJwt, verifyJwt };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { env } from "../env.js";
|
|
2
|
+
import { getLogger } from "./logger.js";
|
|
3
|
+
import { SignJWT, jwtVerify } from "jose";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/jwt-helpers.ts
|
|
6
|
+
const logger = getLogger("jwt-helpers");
|
|
7
|
+
const DEV_SECRET = "insecure-dev-secret-change-in-production-min-32-chars";
|
|
8
|
+
/**
|
|
9
|
+
* Get the JWT signing secret from environment variables.
|
|
10
|
+
* Falls back to an insecure default in non-production environments.
|
|
11
|
+
*/
|
|
12
|
+
function getJwtSecret() {
|
|
13
|
+
const secret = env.INKEEP_AGENTS_JWT_SIGNING_SECRET;
|
|
14
|
+
if (!secret) {
|
|
15
|
+
if (env.ENVIRONMENT === "production") throw new Error("INKEEP_AGENTS_JWT_SIGNING_SECRET environment variable is required in production");
|
|
16
|
+
logger.warn({}, "INKEEP_AGENTS_JWT_SIGNING_SECRET not set, using insecure default. DO NOT USE IN PRODUCTION!");
|
|
17
|
+
return new TextEncoder().encode(DEV_SECRET);
|
|
18
|
+
}
|
|
19
|
+
return new TextEncoder().encode(secret);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Sign a JWT with the shared secret
|
|
23
|
+
*/
|
|
24
|
+
async function signJwt(options) {
|
|
25
|
+
const secret = getJwtSecret();
|
|
26
|
+
const builder = new SignJWT(options.claims || {}).setProtectedHeader({
|
|
27
|
+
alg: "HS256",
|
|
28
|
+
typ: "JWT"
|
|
29
|
+
}).setIssuer(options.issuer).setSubject(options.subject).setIssuedAt().setExpirationTime(options.expiresIn || "5m");
|
|
30
|
+
if (options.audience) builder.setAudience(options.audience);
|
|
31
|
+
return builder.sign(secret);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify a JWT and return the raw payload
|
|
35
|
+
*/
|
|
36
|
+
async function verifyJwt(token, options) {
|
|
37
|
+
const secret = getJwtSecret();
|
|
38
|
+
try {
|
|
39
|
+
const verifyOptions = {
|
|
40
|
+
issuer: options.issuer,
|
|
41
|
+
algorithms: ["HS256"]
|
|
42
|
+
};
|
|
43
|
+
if (options.audience) verifyOptions.audience = options.audience;
|
|
44
|
+
const { payload } = await jwtVerify(token, secret, verifyOptions);
|
|
45
|
+
return {
|
|
46
|
+
valid: true,
|
|
47
|
+
payload
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof Error) return {
|
|
51
|
+
valid: false,
|
|
52
|
+
error: error.message
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: "Token verification failed"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Extract bearer token from Authorization header
|
|
62
|
+
*/
|
|
63
|
+
function extractBearerToken(authHeader) {
|
|
64
|
+
if (!authHeader) return { error: "Missing Authorization header" };
|
|
65
|
+
if (!authHeader.startsWith("Bearer ")) return { error: "Invalid Authorization header format. Expected: Bearer <token>" };
|
|
66
|
+
const token = authHeader.substring(7);
|
|
67
|
+
if (!token) return { error: "Empty token in Authorization header" };
|
|
68
|
+
return { token };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Decode JWT payload without verification (for checking issuer before full verify)
|
|
72
|
+
*/
|
|
73
|
+
function decodeJwtPayload(token) {
|
|
74
|
+
try {
|
|
75
|
+
const parts = token.split(".");
|
|
76
|
+
if (parts.length !== 3) return null;
|
|
77
|
+
return JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a token has a specific issuer (without full verification)
|
|
84
|
+
*/
|
|
85
|
+
function hasIssuer(token, issuer) {
|
|
86
|
+
return decodeJwtPayload(token)?.iss === issuer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { decodeJwtPayload, extractBearerToken, getJwtSecret, hasIssuer, signJwt, verifyJwt };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { JwtVerifyResult } from "./jwt-helpers.js";
|
|
2
|
+
|
|
1
3
|
//#region src/utils/service-token-auth.d.ts
|
|
4
|
+
|
|
2
5
|
/**
|
|
3
|
-
* Service Token JWT Claims
|
|
6
|
+
* Service Token JWT Claims (for agent-to-agent communication)
|
|
4
7
|
*/
|
|
5
8
|
interface ServiceTokenPayload {
|
|
6
9
|
/** Issuer - always 'inkeep-agents' */
|
|
@@ -30,48 +33,27 @@ interface GenerateServiceTokenParams {
|
|
|
30
33
|
/**
|
|
31
34
|
* Result of verifying a service token
|
|
32
35
|
*/
|
|
33
|
-
|
|
34
|
-
valid: boolean;
|
|
35
|
-
payload?: ServiceTokenPayload;
|
|
36
|
-
error?: string;
|
|
37
|
-
}
|
|
36
|
+
type VerifyServiceTokenResult = JwtVerifyResult<ServiceTokenPayload>;
|
|
38
37
|
/**
|
|
39
|
-
* Generate a JWT token for
|
|
40
|
-
* Token expires in 5 minutes
|
|
41
|
-
*
|
|
42
|
-
* @param params - Token generation parameters
|
|
43
|
-
* @returns Signed JWT token string
|
|
38
|
+
* Generate a JWT token for agent-to-agent authentication.
|
|
39
|
+
* Token expires in 5 minutes.
|
|
44
40
|
*/
|
|
45
41
|
declare function generateServiceToken(params: GenerateServiceTokenParams): Promise<string>;
|
|
46
42
|
/**
|
|
47
43
|
* Verify and decode a service JWT token
|
|
48
|
-
*
|
|
49
|
-
* @param token - JWT token string to verify
|
|
50
|
-
* @returns Verification result with payload if valid
|
|
51
44
|
*/
|
|
52
45
|
declare function verifyServiceToken(token: string): Promise<VerifyServiceTokenResult>;
|
|
53
46
|
/**
|
|
54
|
-
* Validate that the token's tenant ID matches the expected tenant
|
|
55
|
-
* This prevents cross-tenant delegation attempts
|
|
56
|
-
*
|
|
57
|
-
* @param payload - Decoded token payload
|
|
58
|
-
* @param expectedTenantId - The tenant ID to validate against
|
|
59
|
-
* @returns true if tenant IDs match, false otherwise
|
|
47
|
+
* Validate that the token's tenant ID matches the expected tenant.
|
|
48
|
+
* This prevents cross-tenant delegation attempts.
|
|
60
49
|
*/
|
|
61
50
|
declare function validateTenantId(payload: ServiceTokenPayload, expectedTenantId: string): boolean;
|
|
62
51
|
/**
|
|
63
52
|
* Validate that the token's target agent ID matches the expected agent
|
|
64
|
-
*
|
|
65
|
-
* @param payload - Decoded token payload
|
|
66
|
-
* @param expectedTargetAgentId - The agent ID to validate against
|
|
67
|
-
* @returns true if agent IDs match, false otherwise
|
|
68
53
|
*/
|
|
69
54
|
declare function validateTargetAgent(payload: ServiceTokenPayload, expectedTargetAgentId: string): boolean;
|
|
70
55
|
/**
|
|
71
56
|
* Extract the Authorization header and verify the bearer token
|
|
72
|
-
*
|
|
73
|
-
* @param authHeader - The Authorization header value (e.g., "Bearer <token>")
|
|
74
|
-
* @returns Verification result with payload if valid
|
|
75
57
|
*/
|
|
76
58
|
declare function verifyAuthorizationHeader(authHeader: string | undefined): Promise<VerifyServiceTokenResult>;
|
|
77
59
|
//#endregion
|
|
@@ -1,40 +1,25 @@
|
|
|
1
|
-
import { env } from "../env.js";
|
|
2
1
|
import { getLogger } from "./logger.js";
|
|
3
|
-
import {
|
|
2
|
+
import { extractBearerToken, signJwt, verifyJwt } from "./jwt-helpers.js";
|
|
4
3
|
|
|
5
4
|
//#region src/utils/service-token-auth.ts
|
|
6
5
|
const logger = getLogger("service-token-auth");
|
|
6
|
+
const ISSUER = "inkeep-agents";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
function getJwtSecret() {
|
|
12
|
-
const secret = env.INKEEP_AGENTS_JWT_SIGNING_SECRET;
|
|
13
|
-
const dev_secret = "insecure-dev-secret-change-in-production-min-32-chars";
|
|
14
|
-
if (!secret) {
|
|
15
|
-
if (env.ENVIRONMENT === "production") throw new Error("INKEEP_AGENTS_JWT_SIGNING_SECRET environment variable is required in production");
|
|
16
|
-
logger.warn({}, "INKEEP_AGENTS_JWT_SIGNING_SECRET not set, using insecure default. DO NOT USE IN PRODUCTION!");
|
|
17
|
-
return new TextEncoder().encode(dev_secret);
|
|
18
|
-
}
|
|
19
|
-
return new TextEncoder().encode(secret);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Generate a JWT token for team agent authentication
|
|
23
|
-
* Token expires in 5 minutes
|
|
24
|
-
*
|
|
25
|
-
* @param params - Token generation parameters
|
|
26
|
-
* @returns Signed JWT token string
|
|
8
|
+
* Generate a JWT token for agent-to-agent authentication.
|
|
9
|
+
* Token expires in 5 minutes.
|
|
27
10
|
*/
|
|
28
11
|
async function generateServiceToken(params) {
|
|
29
|
-
const secret = getJwtSecret();
|
|
30
12
|
try {
|
|
31
|
-
const token = await
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
13
|
+
const token = await signJwt({
|
|
14
|
+
issuer: ISSUER,
|
|
15
|
+
subject: params.originAgentId,
|
|
16
|
+
audience: params.targetAgentId,
|
|
17
|
+
expiresIn: "5m",
|
|
18
|
+
claims: {
|
|
19
|
+
tenantId: params.tenantId,
|
|
20
|
+
projectId: params.projectId
|
|
21
|
+
}
|
|
22
|
+
});
|
|
38
23
|
logger.debug({
|
|
39
24
|
originAgentId: params.originAgentId,
|
|
40
25
|
targetAgentId: params.targetAgentId,
|
|
@@ -48,64 +33,46 @@ async function generateServiceToken(params) {
|
|
|
48
33
|
}
|
|
49
34
|
/**
|
|
50
35
|
* Verify and decode a service JWT token
|
|
51
|
-
*
|
|
52
|
-
* @param token - JWT token string to verify
|
|
53
|
-
* @returns Verification result with payload if valid
|
|
54
36
|
*/
|
|
55
37
|
async function verifyServiceToken(token) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
issuer: "inkeep-agents",
|
|
60
|
-
algorithms: ["HS256"]
|
|
61
|
-
});
|
|
62
|
-
if (typeof payload.sub !== "string" || typeof payload.aud !== "string" || typeof payload.tenantId !== "string" || typeof payload.projectId !== "string") {
|
|
63
|
-
logger.warn({ payload }, "Invalid service token: missing required claims");
|
|
64
|
-
return {
|
|
65
|
-
valid: false,
|
|
66
|
-
error: "Invalid token: missing required claims"
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
const validPayload = {
|
|
70
|
-
iss: payload.iss,
|
|
71
|
-
aud: payload.aud,
|
|
72
|
-
sub: payload.sub,
|
|
73
|
-
tenantId: payload.tenantId,
|
|
74
|
-
projectId: payload.projectId,
|
|
75
|
-
iat: payload.iat,
|
|
76
|
-
exp: payload.exp
|
|
77
|
-
};
|
|
78
|
-
logger.debug({
|
|
79
|
-
originAgentId: validPayload.sub,
|
|
80
|
-
targetAgentId: validPayload.aud,
|
|
81
|
-
tenantId: validPayload.tenantId
|
|
82
|
-
}, "Successfully verified team agent token");
|
|
38
|
+
const result = await verifyJwt(token, { issuer: ISSUER });
|
|
39
|
+
if (!result.valid || !result.payload) {
|
|
40
|
+
logger.warn({ error: result.error }, "Team agent token verification failed");
|
|
83
41
|
return {
|
|
84
|
-
valid:
|
|
85
|
-
|
|
42
|
+
valid: false,
|
|
43
|
+
error: result.error
|
|
86
44
|
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
valid: false,
|
|
92
|
-
error: error.message
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
logger.warn({ error }, "Team agent token verification failed with unknown error");
|
|
45
|
+
}
|
|
46
|
+
const payload = result.payload;
|
|
47
|
+
if (typeof payload.sub !== "string" || typeof payload.aud !== "string" || typeof payload.tenantId !== "string" || typeof payload.projectId !== "string") {
|
|
48
|
+
logger.warn({ payload }, "Invalid service token: missing required claims");
|
|
96
49
|
return {
|
|
97
50
|
valid: false,
|
|
98
|
-
error: "
|
|
51
|
+
error: "Invalid token: missing required claims"
|
|
99
52
|
};
|
|
100
53
|
}
|
|
54
|
+
const validPayload = {
|
|
55
|
+
iss: payload.iss,
|
|
56
|
+
aud: payload.aud,
|
|
57
|
+
sub: payload.sub,
|
|
58
|
+
tenantId: payload.tenantId,
|
|
59
|
+
projectId: payload.projectId,
|
|
60
|
+
iat: payload.iat,
|
|
61
|
+
exp: payload.exp
|
|
62
|
+
};
|
|
63
|
+
logger.debug({
|
|
64
|
+
originAgentId: validPayload.sub,
|
|
65
|
+
targetAgentId: validPayload.aud,
|
|
66
|
+
tenantId: validPayload.tenantId
|
|
67
|
+
}, "Successfully verified team agent token");
|
|
68
|
+
return {
|
|
69
|
+
valid: true,
|
|
70
|
+
payload: validPayload
|
|
71
|
+
};
|
|
101
72
|
}
|
|
102
73
|
/**
|
|
103
|
-
* Validate that the token's tenant ID matches the expected tenant
|
|
104
|
-
* This prevents cross-tenant delegation attempts
|
|
105
|
-
*
|
|
106
|
-
* @param payload - Decoded token payload
|
|
107
|
-
* @param expectedTenantId - The tenant ID to validate against
|
|
108
|
-
* @returns true if tenant IDs match, false otherwise
|
|
74
|
+
* Validate that the token's tenant ID matches the expected tenant.
|
|
75
|
+
* This prevents cross-tenant delegation attempts.
|
|
109
76
|
*/
|
|
110
77
|
function validateTenantId(payload, expectedTenantId) {
|
|
111
78
|
if (payload.tenantId !== expectedTenantId) {
|
|
@@ -121,10 +88,6 @@ function validateTenantId(payload, expectedTenantId) {
|
|
|
121
88
|
}
|
|
122
89
|
/**
|
|
123
90
|
* Validate that the token's target agent ID matches the expected agent
|
|
124
|
-
*
|
|
125
|
-
* @param payload - Decoded token payload
|
|
126
|
-
* @param expectedTargetAgentId - The agent ID to validate against
|
|
127
|
-
* @returns true if agent IDs match, false otherwise
|
|
128
91
|
*/
|
|
129
92
|
function validateTargetAgent(payload, expectedTargetAgentId) {
|
|
130
93
|
if (payload.aud !== expectedTargetAgentId) {
|
|
@@ -139,25 +102,14 @@ function validateTargetAgent(payload, expectedTargetAgentId) {
|
|
|
139
102
|
}
|
|
140
103
|
/**
|
|
141
104
|
* Extract the Authorization header and verify the bearer token
|
|
142
|
-
*
|
|
143
|
-
* @param authHeader - The Authorization header value (e.g., "Bearer <token>")
|
|
144
|
-
* @returns Verification result with payload if valid
|
|
145
105
|
*/
|
|
146
106
|
async function verifyAuthorizationHeader(authHeader) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
error: "Missing Authorization header"
|
|
150
|
-
};
|
|
151
|
-
if (!authHeader.startsWith("Bearer ")) return {
|
|
152
|
-
valid: false,
|
|
153
|
-
error: "Invalid Authorization header format. Expected: Bearer <token>"
|
|
154
|
-
};
|
|
155
|
-
const token = authHeader.substring(7);
|
|
156
|
-
if (!token) return {
|
|
107
|
+
const extracted = extractBearerToken(authHeader);
|
|
108
|
+
if (!extracted.token) return {
|
|
157
109
|
valid: false,
|
|
158
|
-
error:
|
|
110
|
+
error: extracted.error
|
|
159
111
|
};
|
|
160
|
-
return verifyServiceToken(token);
|
|
112
|
+
return verifyServiceToken(extracted.token);
|
|
161
113
|
}
|
|
162
114
|
|
|
163
115
|
//#endregion
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/utils/template-interpolation.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Interpolates a message template with placeholders from a payload object.
|
|
4
|
+
* Supports {{path.to.value}} placeholder syntax with dot notation for nested paths.
|
|
5
|
+
* Missing values are replaced with empty strings.
|
|
6
|
+
*
|
|
7
|
+
* @param template - Message template with {{placeholder}} syntax
|
|
8
|
+
* @param payload - Object containing values to interpolate
|
|
9
|
+
* @returns Interpolated message with all placeholders resolved
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const template = "User {{user.name}} from {{user.profile.location}} submitted: {{message}}";
|
|
13
|
+
* const payload = {
|
|
14
|
+
* user: { name: "Alice", profile: { location: "NYC" } },
|
|
15
|
+
* message: "Hello World"
|
|
16
|
+
* };
|
|
17
|
+
* interpolateTemplate(template, payload);
|
|
18
|
+
* // => "User Alice from NYC submitted: Hello World"
|
|
19
|
+
*/
|
|
20
|
+
declare function interpolateTemplate(template: string, payload: Record<string, unknown>): string;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { interpolateTemplate };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/utils/template-interpolation.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a nested path from an object using dot notation.
|
|
4
|
+
* Example: getValue({ user: { profile: { name: 'John' } } }, 'user.profile.name') => 'John'
|
|
5
|
+
*
|
|
6
|
+
* @param obj - The object to traverse
|
|
7
|
+
* @param path - Dot-separated path (e.g., 'user.profile.name')
|
|
8
|
+
* @returns The value at the path, or undefined if not found
|
|
9
|
+
*/
|
|
10
|
+
function getValue(obj, path) {
|
|
11
|
+
if (!obj || typeof obj !== "object") return;
|
|
12
|
+
const keys = path.split(".");
|
|
13
|
+
let current = obj;
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
if (current === null || current === void 0 || typeof current !== "object") return;
|
|
16
|
+
current = current[key];
|
|
17
|
+
}
|
|
18
|
+
return current;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Converts a value to string for template interpolation.
|
|
22
|
+
* Handles primitives, null, undefined gracefully.
|
|
23
|
+
*
|
|
24
|
+
* @param value - The value to convert
|
|
25
|
+
* @returns String representation or empty string if undefined/null
|
|
26
|
+
*/
|
|
27
|
+
function valueToString(value) {
|
|
28
|
+
if (value === null || value === void 0) return "";
|
|
29
|
+
if (typeof value === "string") return value;
|
|
30
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
31
|
+
try {
|
|
32
|
+
return JSON.stringify(value);
|
|
33
|
+
} catch {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Interpolates a message template with placeholders from a payload object.
|
|
39
|
+
* Supports {{path.to.value}} placeholder syntax with dot notation for nested paths.
|
|
40
|
+
* Missing values are replaced with empty strings.
|
|
41
|
+
*
|
|
42
|
+
* @param template - Message template with {{placeholder}} syntax
|
|
43
|
+
* @param payload - Object containing values to interpolate
|
|
44
|
+
* @returns Interpolated message with all placeholders resolved
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const template = "User {{user.name}} from {{user.profile.location}} submitted: {{message}}";
|
|
48
|
+
* const payload = {
|
|
49
|
+
* user: { name: "Alice", profile: { location: "NYC" } },
|
|
50
|
+
* message: "Hello World"
|
|
51
|
+
* };
|
|
52
|
+
* interpolateTemplate(template, payload);
|
|
53
|
+
* // => "User Alice from NYC submitted: Hello World"
|
|
54
|
+
*/
|
|
55
|
+
function interpolateTemplate(template, payload) {
|
|
56
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_match, path) => {
|
|
57
|
+
return valueToString(getValue(payload, path.trim()));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { interpolateTemplate };
|