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

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