@inkeep/agents-api 0.0.0-dev-20260122085034 → 0.0.0-dev-20260122092303

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.
@@ -0,0 +1,391 @@
1
+ import { getLogger as getLogger$1 } from "../../../logger.js";
2
+ import { env } from "../../../env.js";
3
+ import manageDbPool_default from "../../../data/db/manageDbPool.js";
4
+ import runDbClient_default from "../../../data/db/runDbClient.js";
5
+ import { createSSEStreamHelper } from "../utils/stream-helpers.js";
6
+ import { ExecutionHandler } from "../handlers/executionHandler.js";
7
+ import { JsonTransformer, createMessage, createOrGetConversation, createTriggerInvocation, generateId, getConversationId, getFullProjectWithRelationIds, getTriggerById, interpolateTemplate, setActiveAgentForConversation, updateTriggerInvocationStatus, verifySigningSecret, verifyTriggerAuth, withRef } from "@inkeep/agents-core";
8
+ import { trace } from "@opentelemetry/api";
9
+ import Ajv from "ajv";
10
+
11
+ //#region src/domains/run/services/TriggerService.ts
12
+ const logger = getLogger$1("TriggerService");
13
+ const ajv = new Ajv({ allErrors: true });
14
+ /**
15
+ * Process a trigger webhook request.
16
+ * Handles validation, transformation, and dispatches async execution.
17
+ */
18
+ async function processWebhook(params) {
19
+ const { tenantId, projectId, agentId, triggerId, resolvedRef, rawBody, honoContext } = params;
20
+ const trigger = await loadTrigger({
21
+ tenantId,
22
+ projectId,
23
+ agentId,
24
+ triggerId,
25
+ resolvedRef
26
+ });
27
+ if (!trigger) return {
28
+ success: false,
29
+ error: `Trigger ${triggerId} not found`,
30
+ status: 404
31
+ };
32
+ if (!trigger.enabled) return {
33
+ success: false,
34
+ error: "Trigger is disabled",
35
+ status: 404
36
+ };
37
+ const payload = rawBody ? JSON.parse(rawBody) : {};
38
+ const authResult = await verifyAuthentication(trigger, honoContext);
39
+ if (!authResult.success) return authResult;
40
+ const signatureResult = verifySignature(trigger, honoContext, rawBody);
41
+ if (!signatureResult.success) return signatureResult;
42
+ const validationResult = validatePayload(trigger, payload);
43
+ if (!validationResult.success) return validationResult;
44
+ const transformResult = await transformPayload(trigger, payload, {
45
+ tenantId,
46
+ projectId,
47
+ triggerId
48
+ });
49
+ if (!transformResult.success) return transformResult;
50
+ const transformedPayload = transformResult.payload;
51
+ const { messageParts, userMessageText } = buildMessage(trigger, transformedPayload, triggerId);
52
+ const { invocationId, conversationId } = await dispatchExecution({
53
+ tenantId,
54
+ projectId,
55
+ agentId,
56
+ triggerId,
57
+ resolvedRef,
58
+ payload,
59
+ transformedPayload,
60
+ messageParts,
61
+ userMessageText
62
+ });
63
+ return {
64
+ success: true,
65
+ invocationId,
66
+ conversationId
67
+ };
68
+ }
69
+ async function loadTrigger(params) {
70
+ const { tenantId, projectId, agentId, triggerId, resolvedRef } = params;
71
+ return await withRef(manageDbPool_default, resolvedRef, (db) => getTriggerById(db)({
72
+ scopes: {
73
+ tenantId,
74
+ projectId,
75
+ agentId
76
+ },
77
+ triggerId
78
+ }));
79
+ }
80
+ async function verifyAuthentication(trigger, honoContext) {
81
+ if (!trigger.authentication) return { success: true };
82
+ const authResult = await verifyTriggerAuth(honoContext, trigger.authentication);
83
+ if (!authResult.success) {
84
+ if (authResult.status === 401) return {
85
+ success: false,
86
+ error: authResult.message || "Unauthorized",
87
+ status: 401
88
+ };
89
+ return {
90
+ success: false,
91
+ error: authResult.message || "Forbidden",
92
+ status: 403
93
+ };
94
+ }
95
+ return { success: true };
96
+ }
97
+ function verifySignature(trigger, honoContext, rawBody) {
98
+ if (!trigger.signingSecret) return { success: true };
99
+ const signatureResult = verifySigningSecret(honoContext, trigger.signingSecret, rawBody);
100
+ if (!signatureResult.success) return {
101
+ success: false,
102
+ error: signatureResult.message || "Invalid signature",
103
+ status: 403
104
+ };
105
+ return { success: true };
106
+ }
107
+ function validatePayload(trigger, payload) {
108
+ if (!trigger.inputSchema) return { success: true };
109
+ const validate = ajv.compile(trigger.inputSchema);
110
+ if (!validate(payload)) return {
111
+ success: false,
112
+ error: "Payload validation failed",
113
+ status: 400,
114
+ validationErrors: validate.errors?.map((err) => `${err.instancePath} ${err.message}`)
115
+ };
116
+ return { success: true };
117
+ }
118
+ async function transformPayload(trigger, payload, context$1) {
119
+ if (!trigger.outputTransform) return {
120
+ success: true,
121
+ payload
122
+ };
123
+ try {
124
+ const transformedPayload = await JsonTransformer.transformWithConfig(payload, trigger.outputTransform);
125
+ logger.debug(context$1, "Payload transformation successful");
126
+ return {
127
+ success: true,
128
+ payload: transformedPayload
129
+ };
130
+ } catch (error) {
131
+ const errorMessage = error instanceof Error ? error.message : String(error);
132
+ logger.error({
133
+ ...context$1,
134
+ error: errorMessage
135
+ }, "Payload transformation failed");
136
+ return {
137
+ success: false,
138
+ error: `Payload transformation failed: ${errorMessage}`,
139
+ status: 422
140
+ };
141
+ }
142
+ }
143
+ function buildMessage(trigger, transformedPayload, triggerId) {
144
+ const messageParts = [];
145
+ if (trigger.messageTemplate) {
146
+ const payloadForTemplate = typeof transformedPayload === "object" && transformedPayload !== null && !Array.isArray(transformedPayload) ? transformedPayload : {};
147
+ const interpolatedMessage = interpolateTemplate(trigger.messageTemplate, payloadForTemplate);
148
+ messageParts.push({
149
+ kind: "text",
150
+ text: interpolatedMessage
151
+ });
152
+ }
153
+ if (transformedPayload != null) messageParts.push({
154
+ kind: "data",
155
+ data: transformedPayload,
156
+ metadata: {
157
+ source: "trigger",
158
+ triggerId
159
+ }
160
+ });
161
+ return {
162
+ messageParts,
163
+ userMessageText: trigger.messageTemplate ? (() => {
164
+ const payloadForTemplate = typeof transformedPayload === "object" && transformedPayload !== null && !Array.isArray(transformedPayload) ? transformedPayload : {};
165
+ return interpolateTemplate(trigger.messageTemplate, payloadForTemplate);
166
+ })() : JSON.stringify(transformedPayload)
167
+ };
168
+ }
169
+ async function dispatchExecution(params) {
170
+ const { tenantId, projectId, agentId, triggerId, resolvedRef, payload, transformedPayload, messageParts, userMessageText } = params;
171
+ const conversationId = getConversationId();
172
+ const invocationId = generateId();
173
+ const activeSpan = trace.getActiveSpan();
174
+ if (activeSpan) activeSpan.setAttributes({
175
+ "conversation.id": conversationId,
176
+ "tenant.id": tenantId,
177
+ "project.id": projectId,
178
+ "agent.id": agentId,
179
+ "invocation.type": "trigger",
180
+ "trigger.id": triggerId,
181
+ "trigger.invocation.id": invocationId,
182
+ "message.content": userMessageText,
183
+ "message.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
184
+ "message.parts": JSON.stringify(messageParts)
185
+ });
186
+ await createTriggerInvocation(runDbClient_default)({
187
+ id: invocationId,
188
+ triggerId,
189
+ tenantId,
190
+ projectId,
191
+ agentId,
192
+ conversationId,
193
+ status: "pending",
194
+ requestPayload: payload,
195
+ transformedPayload
196
+ });
197
+ logger.info({
198
+ tenantId,
199
+ projectId,
200
+ agentId,
201
+ triggerId,
202
+ invocationId,
203
+ conversationId
204
+ }, "Trigger invocation created");
205
+ const executionPromise = executeAgentAsync({
206
+ tenantId,
207
+ projectId,
208
+ agentId,
209
+ triggerId,
210
+ invocationId,
211
+ conversationId,
212
+ userMessage: userMessageText,
213
+ messageParts,
214
+ resolvedRef
215
+ });
216
+ if (process.env.VERCEL) import("@vercel/functions").then(({ waitUntil }) => {
217
+ waitUntil(executionPromise);
218
+ }).catch((err) => {
219
+ logger.error({ err }, "Failed to import @vercel/functions");
220
+ });
221
+ else executionPromise.catch((error) => {
222
+ const errorMessage = error instanceof Error ? error.message : String(error);
223
+ const errorStack = error instanceof Error ? error.stack : void 0;
224
+ logger.error({
225
+ err: errorMessage,
226
+ errorStack,
227
+ tenantId,
228
+ projectId,
229
+ agentId,
230
+ triggerId,
231
+ invocationId
232
+ }, "Background trigger execution failed");
233
+ });
234
+ logger.info({
235
+ tenantId,
236
+ projectId,
237
+ agentId,
238
+ triggerId,
239
+ invocationId,
240
+ conversationId
241
+ }, "Async execution dispatched");
242
+ return {
243
+ invocationId,
244
+ conversationId
245
+ };
246
+ }
247
+ /**
248
+ * Execute the agent asynchronously.
249
+ * This runs after the webhook response is sent.
250
+ */
251
+ async function executeAgentAsync(params) {
252
+ const { tenantId, projectId, agentId, triggerId, invocationId, conversationId, userMessage, messageParts, resolvedRef } = params;
253
+ logger.info({
254
+ tenantId,
255
+ projectId,
256
+ agentId,
257
+ triggerId,
258
+ invocationId,
259
+ conversationId
260
+ }, "Starting async trigger execution");
261
+ try {
262
+ const project = await withRef(manageDbPool_default, resolvedRef, async (db) => {
263
+ return await getFullProjectWithRelationIds(db)({ scopes: {
264
+ tenantId,
265
+ projectId
266
+ } });
267
+ });
268
+ if (!project) throw new Error(`Project ${projectId} not found`);
269
+ const agent = project.agents?.[agentId];
270
+ if (!agent) throw new Error(`Agent ${agentId} not found in project`);
271
+ const defaultSubAgentId = agent.defaultSubAgentId;
272
+ if (!defaultSubAgentId) throw new Error(`Agent ${agentId} has no default sub-agent configured`);
273
+ await createOrGetConversation(runDbClient_default)({
274
+ id: conversationId,
275
+ tenantId,
276
+ projectId,
277
+ agentId,
278
+ activeSubAgentId: defaultSubAgentId,
279
+ ref: resolvedRef
280
+ });
281
+ await setActiveAgentForConversation(runDbClient_default)({
282
+ scopes: {
283
+ tenantId,
284
+ projectId
285
+ },
286
+ conversationId,
287
+ subAgentId: defaultSubAgentId,
288
+ agentId,
289
+ ref: resolvedRef
290
+ });
291
+ await createMessage(runDbClient_default)({
292
+ id: generateId(),
293
+ tenantId,
294
+ projectId,
295
+ conversationId,
296
+ role: "user",
297
+ content: {
298
+ text: userMessage,
299
+ parts: messageParts
300
+ },
301
+ metadata: { a2a_metadata: {
302
+ triggerId,
303
+ invocationId
304
+ } }
305
+ });
306
+ const executionContext = {
307
+ tenantId,
308
+ projectId,
309
+ agentId,
310
+ baseUrl: env.INKEEP_AGENTS_API_URL || "http://localhost:3002",
311
+ apiKey: env.INKEEP_AGENTS_RUN_API_BYPASS_SECRET || "",
312
+ apiKeyId: "trigger-invocation",
313
+ resolvedRef,
314
+ project,
315
+ metadata: { initiatedBy: {
316
+ type: "api_key",
317
+ id: triggerId
318
+ } }
319
+ };
320
+ const requestId = `trigger-${invocationId}`;
321
+ const noOpStreamHelper = createSSEStreamHelper({
322
+ writeSSE: async () => {},
323
+ sleep: async () => {}
324
+ }, requestId, Math.floor(Date.now() / 1e3));
325
+ await new ExecutionHandler().execute({
326
+ executionContext,
327
+ conversationId,
328
+ userMessage,
329
+ messageParts,
330
+ initialAgentId: agentId,
331
+ requestId,
332
+ sseHelper: noOpStreamHelper,
333
+ emitOperations: false
334
+ });
335
+ await updateTriggerInvocationStatus(runDbClient_default)({
336
+ scopes: {
337
+ tenantId,
338
+ projectId,
339
+ agentId
340
+ },
341
+ triggerId,
342
+ invocationId,
343
+ data: { status: "success" }
344
+ });
345
+ logger.info({
346
+ tenantId,
347
+ projectId,
348
+ agentId,
349
+ triggerId,
350
+ invocationId,
351
+ conversationId
352
+ }, "Async trigger execution completed successfully");
353
+ } catch (error) {
354
+ const errorMessage = error instanceof Error ? error.message : String(error);
355
+ const errorStack = error instanceof Error ? error.stack : void 0;
356
+ logger.error({
357
+ err: errorMessage,
358
+ errorStack,
359
+ tenantId,
360
+ projectId,
361
+ agentId,
362
+ triggerId,
363
+ invocationId
364
+ }, "Async trigger execution failed");
365
+ try {
366
+ await updateTriggerInvocationStatus(runDbClient_default)({
367
+ scopes: {
368
+ tenantId,
369
+ projectId,
370
+ agentId
371
+ },
372
+ triggerId,
373
+ invocationId,
374
+ data: {
375
+ status: "failed",
376
+ errorMessage
377
+ }
378
+ });
379
+ } catch (updateError) {
380
+ const updateErrorMessage = updateError instanceof Error ? updateError.message : String(updateError);
381
+ logger.error({
382
+ err: updateErrorMessage,
383
+ invocationId
384
+ }, "Failed to update invocation status to failed");
385
+ }
386
+ throw error;
387
+ }
388
+ }
389
+
390
+ //#endregion
391
+ export { processWebhook };
package/dist/env.d.ts CHANGED
@@ -14,11 +14,11 @@ declare const envSchema: z.ZodObject<{
14
14
  pentest: "pentest";
15
15
  }>>;
16
16
  LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
17
+ error: "error";
17
18
  trace: "trace";
18
19
  debug: "debug";
19
20
  info: "info";
20
21
  warn: "warn";
21
- error: "error";
22
22
  }>>;
23
23
  INKEEP_AGENTS_MANAGE_DATABASE_URL: z.ZodString;
24
24
  INKEEP_AGENTS_RUN_DATABASE_URL: z.ZodString;
@@ -53,7 +53,7 @@ declare const envSchema: z.ZodObject<{
53
53
  declare const env: {
54
54
  NODE_ENV: "development" | "production" | "test";
55
55
  ENVIRONMENT: "development" | "production" | "test" | "pentest";
56
- LOG_LEVEL: "trace" | "debug" | "info" | "warn" | "error";
56
+ LOG_LEVEL: "error" | "trace" | "debug" | "info" | "warn";
57
57
  INKEEP_AGENTS_MANAGE_DATABASE_URL: string;
58
58
  INKEEP_AGENTS_RUN_DATABASE_URL: string;
59
59
  INKEEP_AGENTS_API_URL: string;