@inkeep/agents-run-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +117 -0
  2. package/dist/AgentExecutionServer.d.ts +23 -0
  3. package/dist/AgentExecutionServer.d.ts.map +1 -0
  4. package/dist/AgentExecutionServer.js +32 -0
  5. package/dist/__tests__/setup.d.ts +4 -0
  6. package/dist/__tests__/setup.d.ts.map +1 -0
  7. package/dist/__tests__/setup.js +50 -0
  8. package/dist/__tests__/utils/testProject.d.ts +18 -0
  9. package/dist/__tests__/utils/testProject.d.ts.map +1 -0
  10. package/dist/__tests__/utils/testProject.js +26 -0
  11. package/dist/__tests__/utils/testRequest.d.ts +8 -0
  12. package/dist/__tests__/utils/testRequest.d.ts.map +1 -0
  13. package/dist/__tests__/utils/testRequest.js +32 -0
  14. package/dist/__tests__/utils/testTenant.d.ts +64 -0
  15. package/dist/__tests__/utils/testTenant.d.ts.map +1 -0
  16. package/dist/__tests__/utils/testTenant.js +71 -0
  17. package/dist/a2a/client.d.ts +182 -0
  18. package/dist/a2a/client.d.ts.map +1 -0
  19. package/dist/a2a/client.js +645 -0
  20. package/dist/a2a/handlers.d.ts +4 -0
  21. package/dist/a2a/handlers.d.ts.map +1 -0
  22. package/dist/a2a/handlers.js +657 -0
  23. package/dist/a2a/transfer.d.ts +18 -0
  24. package/dist/a2a/transfer.d.ts.map +1 -0
  25. package/dist/a2a/transfer.js +22 -0
  26. package/dist/a2a/types.d.ts +63 -0
  27. package/dist/a2a/types.d.ts.map +1 -0
  28. package/dist/a2a/types.js +1 -0
  29. package/dist/agents/Agent.d.ts +154 -0
  30. package/dist/agents/Agent.d.ts.map +1 -0
  31. package/dist/agents/Agent.js +1105 -0
  32. package/dist/agents/ModelFactory.d.ts +62 -0
  33. package/dist/agents/ModelFactory.d.ts.map +1 -0
  34. package/dist/agents/ModelFactory.js +208 -0
  35. package/dist/agents/SystemPromptBuilder.d.ts +14 -0
  36. package/dist/agents/SystemPromptBuilder.d.ts.map +1 -0
  37. package/dist/agents/SystemPromptBuilder.js +62 -0
  38. package/dist/agents/ToolSessionManager.d.ts +61 -0
  39. package/dist/agents/ToolSessionManager.d.ts.map +1 -0
  40. package/dist/agents/ToolSessionManager.js +143 -0
  41. package/dist/agents/artifactTools.d.ts +30 -0
  42. package/dist/agents/artifactTools.d.ts.map +1 -0
  43. package/dist/agents/artifactTools.js +463 -0
  44. package/dist/agents/generateTaskHandler.d.ts +41 -0
  45. package/dist/agents/generateTaskHandler.d.ts.map +1 -0
  46. package/dist/agents/generateTaskHandler.js +350 -0
  47. package/dist/agents/relationTools.d.ts +33 -0
  48. package/dist/agents/relationTools.d.ts.map +1 -0
  49. package/dist/agents/relationTools.js +245 -0
  50. package/dist/agents/types.d.ts +23 -0
  51. package/dist/agents/types.d.ts.map +1 -0
  52. package/dist/agents/types.js +1 -0
  53. package/dist/agents/versions/V1Config.d.ts +21 -0
  54. package/dist/agents/versions/V1Config.d.ts.map +1 -0
  55. package/dist/agents/versions/V1Config.js +285 -0
  56. package/dist/app.d.ts +4 -0
  57. package/dist/app.d.ts.map +1 -0
  58. package/dist/app.js +194 -0
  59. package/dist/data/agentGraph.d.ts +4 -0
  60. package/dist/data/agentGraph.d.ts.map +1 -0
  61. package/dist/data/agentGraph.js +73 -0
  62. package/dist/data/agents.d.ts +4 -0
  63. package/dist/data/agents.d.ts.map +1 -0
  64. package/dist/data/agents.js +73 -0
  65. package/dist/data/conversations.d.ts +59 -0
  66. package/dist/data/conversations.d.ts.map +1 -0
  67. package/dist/data/conversations.js +216 -0
  68. package/dist/data/db/clean.d.ts +6 -0
  69. package/dist/data/db/clean.d.ts.map +1 -0
  70. package/dist/data/db/clean.js +77 -0
  71. package/dist/data/db/dbClient.d.ts +3 -0
  72. package/dist/data/db/dbClient.d.ts.map +1 -0
  73. package/dist/data/db/dbClient.js +13 -0
  74. package/dist/env.d.ts +43 -0
  75. package/dist/env.d.ts.map +1 -0
  76. package/dist/env.js +63 -0
  77. package/dist/handlers/executionHandler.d.ts +36 -0
  78. package/dist/handlers/executionHandler.d.ts.map +1 -0
  79. package/dist/handlers/executionHandler.js +402 -0
  80. package/dist/index.d.ts +5 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +43 -0
  83. package/dist/instrumentation.d.ts +13 -0
  84. package/dist/instrumentation.d.ts.map +1 -0
  85. package/dist/instrumentation.js +66 -0
  86. package/dist/logger.d.ts +4 -0
  87. package/dist/logger.d.ts.map +1 -0
  88. package/dist/logger.js +32 -0
  89. package/dist/middleware/api-key-auth.d.ts +22 -0
  90. package/dist/middleware/api-key-auth.d.ts.map +1 -0
  91. package/dist/middleware/api-key-auth.js +139 -0
  92. package/dist/middleware/index.d.ts +2 -0
  93. package/dist/middleware/index.d.ts.map +1 -0
  94. package/dist/middleware/index.js +1 -0
  95. package/dist/openapi.d.ts +2 -0
  96. package/dist/openapi.d.ts.map +1 -0
  97. package/dist/openapi.js +36 -0
  98. package/dist/routes/agents.d.ts +4 -0
  99. package/dist/routes/agents.d.ts.map +1 -0
  100. package/dist/routes/agents.js +155 -0
  101. package/dist/routes/chat.d.ts +4 -0
  102. package/dist/routes/chat.d.ts.map +1 -0
  103. package/dist/routes/chat.js +308 -0
  104. package/dist/routes/chatDataStream.d.ts +4 -0
  105. package/dist/routes/chatDataStream.d.ts.map +1 -0
  106. package/dist/routes/chatDataStream.js +179 -0
  107. package/dist/routes/mcp.d.ts +4 -0
  108. package/dist/routes/mcp.d.ts.map +1 -0
  109. package/dist/routes/mcp.js +500 -0
  110. package/dist/tracer.d.ts +24 -0
  111. package/dist/tracer.d.ts.map +1 -0
  112. package/dist/tracer.js +97 -0
  113. package/dist/types/chat.d.ts +25 -0
  114. package/dist/types/chat.d.ts.map +1 -0
  115. package/dist/types/chat.js +1 -0
  116. package/dist/types/execution-context.d.ts +14 -0
  117. package/dist/types/execution-context.d.ts.map +1 -0
  118. package/dist/types/execution-context.js +14 -0
  119. package/dist/utils/agent-operations.d.ts +79 -0
  120. package/dist/utils/agent-operations.d.ts.map +1 -0
  121. package/dist/utils/agent-operations.js +67 -0
  122. package/dist/utils/artifact-component-schema.d.ts +29 -0
  123. package/dist/utils/artifact-component-schema.d.ts.map +1 -0
  124. package/dist/utils/artifact-component-schema.js +119 -0
  125. package/dist/utils/artifact-parser.d.ts +71 -0
  126. package/dist/utils/artifact-parser.d.ts.map +1 -0
  127. package/dist/utils/artifact-parser.js +251 -0
  128. package/dist/utils/cleanup.d.ts +19 -0
  129. package/dist/utils/cleanup.d.ts.map +1 -0
  130. package/dist/utils/cleanup.js +66 -0
  131. package/dist/utils/data-component-schema.d.ts +6 -0
  132. package/dist/utils/data-component-schema.d.ts.map +1 -0
  133. package/dist/utils/data-component-schema.js +43 -0
  134. package/dist/utils/graph-session.d.ts +200 -0
  135. package/dist/utils/graph-session.d.ts.map +1 -0
  136. package/dist/utils/graph-session.js +1009 -0
  137. package/dist/utils/incremental-stream-parser.d.ts +57 -0
  138. package/dist/utils/incremental-stream-parser.d.ts.map +1 -0
  139. package/dist/utils/incremental-stream-parser.js +287 -0
  140. package/dist/utils/response-formatter.d.ts +27 -0
  141. package/dist/utils/response-formatter.d.ts.map +1 -0
  142. package/dist/utils/response-formatter.js +160 -0
  143. package/dist/utils/stream-helpers.d.ts +162 -0
  144. package/dist/utils/stream-helpers.d.ts.map +1 -0
  145. package/dist/utils/stream-helpers.js +385 -0
  146. package/dist/utils/stream-registry.d.ts +18 -0
  147. package/dist/utils/stream-registry.d.ts.map +1 -0
  148. package/dist/utils/stream-registry.js +33 -0
  149. package/package.json +88 -0
@@ -0,0 +1,402 @@
1
+ import { trace } from '@opentelemetry/api';
2
+ import { A2AClient } from '../a2a/client.js';
3
+ import { executeTransfer, isTransferResponse } from '../a2a/transfer.js';
4
+ import { createMessage, getActiveAgentForConversation, createTask, getTask, updateTask, getFullGraph, } from '@inkeep/agents-core';
5
+ import { getLogger } from '../logger.js';
6
+ import { agentInitializingOp, agentReadyOp, completionOp, errorOp, } from '../utils/agent-operations.js';
7
+ import { graphSessionManager } from '../utils/graph-session.js';
8
+ import { MCPStreamHelper } from '../utils/stream-helpers.js';
9
+ import { registerStreamHelper, unregisterStreamHelper } from '../utils/stream-registry.js';
10
+ import dbClient from '../data/db/dbClient.js';
11
+ import { nanoid } from 'nanoid';
12
+ const logger = getLogger('ExecutionHandler');
13
+ export class ExecutionHandler {
14
+ // Hardcoded error limit - separate from configurable stopWhen
15
+ MAX_ERRORS = 3;
16
+ /**
17
+ * performs exeuction loop
18
+ *
19
+ * Do up to limit of MAX_ITERATIONS
20
+ *
21
+ * 1. lookup active agent for thread
22
+ * 2. Send A2A message to selected agent
23
+ * 3. Parse A2A message response
24
+ * 4. Handle transfer messages (if any)
25
+ * 5. Handle completion messages (if any)
26
+ * 6. If no valid response or transfer, return error
27
+ * @param params
28
+ * @returns
29
+ */
30
+ async execute(params) {
31
+ const { executionContext, conversationId, userMessage, initialAgentId, requestId, sseHelper } = params;
32
+ const { tenantId, projectId, graphId, apiKey, baseUrl } = executionContext;
33
+ // Register streamHelper so agents can access it via requestId
34
+ registerStreamHelper(requestId, sseHelper);
35
+ // Create GraphSession for this entire message execution using requestId as the session ID
36
+ graphSessionManager.createSession(requestId, graphId, tenantId, projectId);
37
+ logger.info({ sessionId: requestId, graphId }, 'Created GraphSession for message execution');
38
+ // Initialize status updates if configured
39
+ let graphConfig = null;
40
+ try {
41
+ graphConfig = await getFullGraph(dbClient)({ scopes: { tenantId, projectId }, graphId });
42
+ if (graphConfig?.statusUpdates && graphConfig.statusUpdates.enabled !== false) {
43
+ graphSessionManager.initializeStatusUpdates(requestId, graphConfig.statusUpdates, graphConfig.models?.summarizer);
44
+ }
45
+ }
46
+ catch (error) {
47
+ logger.error({
48
+ error: error instanceof Error ? error.message : 'Unknown error',
49
+ stack: error instanceof Error ? error.stack : undefined,
50
+ }, '❌ Failed to initialize status updates, continuing without them');
51
+ }
52
+ let currentAgentId = initialAgentId;
53
+ let iterations = 0;
54
+ let errorCount = 0;
55
+ let task = null;
56
+ let fromAgentId; // Track the agent that executed a transfer
57
+ try {
58
+ // Send agent initializing and ready operations immediately to ensure UI rendering
59
+ await sseHelper.writeOperation(agentInitializingOp(requestId, graphId));
60
+ await sseHelper.writeOperation(agentReadyOp(requestId, graphId));
61
+ // Send agent thinking operation after ready
62
+ await sseHelper.writeData('data-operation', {
63
+ type: 'agent_thinking',
64
+ ctx: { agent: 'system' },
65
+ });
66
+ // Check for existing task first to prevent race conditions
67
+ const taskId = `task_${conversationId}-${requestId}`;
68
+ const existingTask = await getTask(dbClient)({ id: taskId });
69
+ if (existingTask) {
70
+ // Task already exists, use it instead of creating a new one
71
+ task = existingTask;
72
+ logger.info({ taskId, existingTask }, 'Reusing existing task to prevent race condition');
73
+ }
74
+ else {
75
+ // Task creation (data operations removed)
76
+ // Create initial task
77
+ logger.info({ taskId, currentAgentId, conversationId, requestId }, 'About to create task with streamRequestId');
78
+ task = await createTask(dbClient)({
79
+ id: taskId,
80
+ tenantId,
81
+ projectId,
82
+ agentId: currentAgentId,
83
+ contextId: conversationId,
84
+ status: 'pending',
85
+ metadata: {
86
+ conversation_id: conversationId,
87
+ message_id: requestId,
88
+ stream_request_id: requestId, // This also serves as the GraphSession ID
89
+ created_at: new Date().toISOString(),
90
+ updated_at: new Date().toISOString(),
91
+ root_agent_id: initialAgentId,
92
+ agent_id: currentAgentId,
93
+ },
94
+ });
95
+ logger.info({
96
+ taskId,
97
+ createdTaskMetadata: Array.isArray(task) ? task[0]?.metadata : task?.metadata,
98
+ }, 'Task created with metadata');
99
+ }
100
+ // Debug logging for execution handler (structured logging only)
101
+ logger.debug({
102
+ timestamp: new Date().toISOString(),
103
+ executionType: 'create_initial_task',
104
+ conversationId,
105
+ requestId,
106
+ currentAgentId,
107
+ taskId: Array.isArray(task) ? task[0]?.id : task?.id,
108
+ userMessage: userMessage.substring(0, 100), // Truncate for security
109
+ }, 'ExecutionHandler: Initial task created');
110
+ // If createTask returns an array, get the first element
111
+ if (Array.isArray(task))
112
+ task = task[0];
113
+ let currentMessage = userMessage;
114
+ // Get transfer limit from graph configuration
115
+ const maxTransfers = graphConfig?.stopWhen?.transferCountIs ?? 10;
116
+ // Start execution loop
117
+ while (iterations < maxTransfers) {
118
+ iterations++;
119
+ // Stream iteration start
120
+ // Iteration start (data operations removed)
121
+ logger.info({ iterations, currentAgentId, graphId, conversationId, fromAgentId }, `Execution loop iteration ${iterations} with agent ${currentAgentId}, transfer from: ${fromAgentId || 'none'}`);
122
+ // Step 1: Determine which agent should handle the message
123
+ const activeAgent = await getActiveAgentForConversation(dbClient)({
124
+ scopes: { tenantId, projectId },
125
+ conversationId,
126
+ });
127
+ logger.info({ activeAgent }, 'activeAgent');
128
+ if (activeAgent && activeAgent.activeAgentId !== currentAgentId) {
129
+ currentAgentId = activeAgent.activeAgentId;
130
+ logger.info({ currentAgentId }, `Updated current agent to: ${currentAgentId}`);
131
+ // Stream agent selection update
132
+ // Agent selection (data operations removed)
133
+ }
134
+ // Step 2: Send A2A message to selected agent
135
+ const agentBaseUrl = `${baseUrl}/agents`;
136
+ const a2aClient = new A2AClient(agentBaseUrl, {
137
+ headers: {
138
+ Authorization: `Bearer ${apiKey}`,
139
+ 'x-inkeep-tenant-id': tenantId,
140
+ 'x-inkeep-project-id': projectId,
141
+ 'x-inkeep-graph-id': graphId,
142
+ 'x-inkeep-agent-id': currentAgentId,
143
+ },
144
+ });
145
+ // Check if agent supports streaming
146
+ // const agentCard = await a2aClient.getAgentCard();
147
+ let messageResponse = null;
148
+ // Build message metadata - include fromAgentId only if this is a transfer
149
+ const messageMetadata = {
150
+ stream_request_id: requestId, // This also serves as the GraphSession ID
151
+ };
152
+ if (fromAgentId) {
153
+ messageMetadata.fromAgentId = fromAgentId;
154
+ }
155
+ messageResponse = await a2aClient.sendMessage({
156
+ message: {
157
+ role: 'user',
158
+ parts: [
159
+ {
160
+ kind: 'text',
161
+ text: currentMessage,
162
+ },
163
+ ],
164
+ messageId: `${requestId}-iter-${iterations}`,
165
+ kind: 'message',
166
+ contextId: conversationId,
167
+ metadata: messageMetadata,
168
+ },
169
+ configuration: {
170
+ acceptedOutputModes: ['text', 'text/plain'],
171
+ blocking: false,
172
+ },
173
+ });
174
+ // Step 3: Parse A2A message response
175
+ if (!messageResponse?.result) {
176
+ errorCount++;
177
+ logger.error({ currentAgentId, iterations, errorCount }, `No response from agent ${currentAgentId} on iteration ${iterations} (error ${errorCount}/${this.MAX_ERRORS})`);
178
+ // Check if we've hit the error limit
179
+ if (errorCount >= this.MAX_ERRORS) {
180
+ const errorMessage = `Maximum error limit (${this.MAX_ERRORS}) reached`;
181
+ logger.error({ maxErrors: this.MAX_ERRORS, errorCount }, errorMessage);
182
+ await sseHelper.writeError(errorMessage);
183
+ await sseHelper.writeOperation(errorOp(errorMessage, currentAgentId || 'system'));
184
+ if (task) {
185
+ await updateTask(dbClient)({
186
+ taskId: task.id,
187
+ data: {
188
+ status: 'failed',
189
+ metadata: {
190
+ ...task.metadata,
191
+ failed_at: new Date().toISOString(),
192
+ error: errorMessage,
193
+ },
194
+ },
195
+ });
196
+ }
197
+ graphSessionManager.endSession(requestId);
198
+ unregisterStreamHelper(requestId);
199
+ return { success: false, error: errorMessage, iterations };
200
+ }
201
+ continue;
202
+ }
203
+ // Step 4: Handle transfer messages
204
+ if (isTransferResponse(messageResponse.result)) {
205
+ const transferResponse = messageResponse.result;
206
+ // Extract targetAgentId from transfer response artifacts
207
+ const targetAgentId = transferResponse.artifacts?.[0]?.parts?.[0]?.data
208
+ ?.targetAgentId;
209
+ const transferReason = transferResponse.artifacts?.[0]?.parts?.[1]?.text;
210
+ // Transfer operation (data operations removed)
211
+ logger.info({ targetAgentId, transferReason }, 'transfer response');
212
+ // Update the current message to the transfer reason so as not to duplicate the user message on every transfer
213
+ // including the xml because the fromAgent does not always directly adress the toAgent in its text
214
+ currentMessage = `<transfer_context> ${transferReason} </transfer_context>`;
215
+ const { success, targetAgentId: newAgentId } = await executeTransfer({
216
+ projectId,
217
+ tenantId,
218
+ threadId: conversationId,
219
+ targetAgentId,
220
+ });
221
+ if (success) {
222
+ // Set fromAgentId to track which agent executed this transfer
223
+ fromAgentId = currentAgentId;
224
+ currentAgentId = newAgentId;
225
+ logger.info({
226
+ transferFrom: fromAgentId,
227
+ transferTo: currentAgentId,
228
+ reason: transferReason,
229
+ }, 'Transfer executed, tracking fromAgentId for next iteration');
230
+ }
231
+ // Continue to next iteration with new agent
232
+ continue;
233
+ }
234
+ const responseParts = messageResponse.result.artifacts?.flatMap((artifact) => artifact.parts || []) || [];
235
+ if (responseParts && responseParts.length > 0) {
236
+ // Log graph session data after completion response
237
+ const graphSessionData = graphSessionManager.getSession(requestId);
238
+ if (graphSessionData) {
239
+ const sessionSummary = graphSessionData.getSummary();
240
+ logger.info(sessionSummary, 'GraphSession data after completion');
241
+ }
242
+ // Process response parts for database storage and A2A protocol
243
+ // NOTE: Do NOT stream content here - agents handle their own streaming
244
+ let textContent = '';
245
+ for (const part of responseParts) {
246
+ const isTextPart = (part.kind === 'text' || part.type === 'text') && part.text;
247
+ if (isTextPart) {
248
+ textContent += part.text;
249
+ }
250
+ // Data parts are already processed by the agent's streaming logic
251
+ }
252
+ // Stream completion operation
253
+ // Completion operation (data operations removed)
254
+ const activeSpan = trace.getActiveSpan();
255
+ if (activeSpan) {
256
+ activeSpan.setAttributes({
257
+ 'ai.response.content': textContent || 'No response content',
258
+ 'ai.response.timestamp': new Date().toISOString(),
259
+ 'ai.agent.name': currentAgentId,
260
+ });
261
+ }
262
+ // Store the agent response in the database with both text and parts
263
+ await createMessage(dbClient)({
264
+ id: nanoid(),
265
+ tenantId,
266
+ projectId,
267
+ conversationId,
268
+ role: 'agent',
269
+ content: {
270
+ text: textContent || undefined,
271
+ parts: responseParts.map((part) => ({
272
+ type: part.kind === 'text' ? 'text' : 'data',
273
+ text: part.kind === 'text' ? part.text : undefined,
274
+ data: part.kind === 'data' ? JSON.stringify(part.data) : undefined,
275
+ })),
276
+ },
277
+ visibility: 'user-facing',
278
+ messageType: 'chat',
279
+ agentId: currentAgentId,
280
+ fromAgentId: currentAgentId,
281
+ taskId: task.id,
282
+ });
283
+ // Mark task as completed
284
+ const updateTaskStart = Date.now();
285
+ await updateTask(dbClient)({
286
+ taskId: task.id,
287
+ data: {
288
+ status: 'completed',
289
+ metadata: {
290
+ ...task.metadata,
291
+ completed_at: new Date().toISOString(),
292
+ response: {
293
+ text: textContent,
294
+ parts: responseParts,
295
+ hasText: !!textContent,
296
+ hasData: responseParts.some((p) => p.kind === 'data'),
297
+ },
298
+ },
299
+ },
300
+ });
301
+ const updateTaskEnd = Date.now();
302
+ logger.info({ duration: updateTaskEnd - updateTaskStart }, 'Completed updateTask operation');
303
+ // Send completion data operation before ending session
304
+ await sseHelper.writeOperation(completionOp(currentAgentId, iterations));
305
+ // End the GraphSession and clean up resources
306
+ logger.info('Ending GraphSession and cleaning up');
307
+ graphSessionManager.endSession(requestId);
308
+ // Clean up streamHelper
309
+ logger.info('Cleaning up streamHelper');
310
+ unregisterStreamHelper(requestId);
311
+ // Extract captured response if using MCPStreamHelper
312
+ let response;
313
+ if (sseHelper instanceof MCPStreamHelper) {
314
+ const captured = sseHelper.getCapturedResponse();
315
+ response = captured.text || 'No response content';
316
+ }
317
+ logger.info('ExecutionHandler returning success');
318
+ return { success: true, iterations, response };
319
+ }
320
+ // If we get here, we didn't get a valid response or transfer
321
+ errorCount++;
322
+ logger.warn({ iterations, errorCount }, `No valid response or transfer on iteration ${iterations} (error ${errorCount}/${this.MAX_ERRORS})`);
323
+ // Check if we've hit the error limit
324
+ if (errorCount >= this.MAX_ERRORS) {
325
+ const errorMessage = `Maximum error limit (${this.MAX_ERRORS}) reached`;
326
+ logger.error({ maxErrors: this.MAX_ERRORS, errorCount }, errorMessage);
327
+ await sseHelper.writeError(errorMessage);
328
+ await sseHelper.writeOperation(errorOp(errorMessage, currentAgentId || 'system'));
329
+ if (task) {
330
+ await updateTask(dbClient)({
331
+ taskId: task.id,
332
+ data: {
333
+ status: 'failed',
334
+ metadata: {
335
+ ...task.metadata,
336
+ failed_at: new Date().toISOString(),
337
+ error: errorMessage,
338
+ },
339
+ },
340
+ });
341
+ }
342
+ graphSessionManager.endSession(requestId);
343
+ unregisterStreamHelper(requestId);
344
+ return { success: false, error: errorMessage, iterations };
345
+ }
346
+ }
347
+ // Max transfers reached
348
+ const errorMessage = `Maximum transfer limit (${maxTransfers}) reached without completion`;
349
+ logger.error({ maxTransfers, iterations }, errorMessage);
350
+ // Stream error operation
351
+ // Error operation (data operations removed)
352
+ await sseHelper.writeError(errorMessage);
353
+ // Send error operation for max iterations reached
354
+ await sseHelper.writeOperation(errorOp(errorMessage, currentAgentId || 'system'));
355
+ // Mark task as failed
356
+ if (task) {
357
+ await updateTask(dbClient)({
358
+ taskId: task.id,
359
+ data: {
360
+ status: 'failed',
361
+ metadata: {
362
+ ...task.metadata,
363
+ failed_at: new Date().toISOString(),
364
+ error: errorMessage,
365
+ },
366
+ },
367
+ });
368
+ }
369
+ // Clean up GraphSession and streamHelper on error
370
+ graphSessionManager.endSession(requestId);
371
+ unregisterStreamHelper(requestId);
372
+ return { success: false, error: errorMessage, iterations };
373
+ }
374
+ catch (error) {
375
+ logger.error({ error }, 'Error in execution handler');
376
+ const errorMessage = error instanceof Error ? error.message : 'Unknown execution error';
377
+ // Stream error operation
378
+ // Error operation (data operations removed)
379
+ await sseHelper.writeError(`Execution error: ${errorMessage}`);
380
+ // Send error operation for execution exception
381
+ await sseHelper.writeOperation(errorOp(errorMessage, currentAgentId || 'system'));
382
+ // Mark task as failed
383
+ if (task) {
384
+ await updateTask(dbClient)({
385
+ taskId: task.id,
386
+ data: {
387
+ status: 'failed',
388
+ metadata: {
389
+ ...task.metadata,
390
+ failed_at: new Date().toISOString(),
391
+ error: errorMessage,
392
+ },
393
+ },
394
+ });
395
+ }
396
+ // Clean up GraphSession and streamHelper on exception
397
+ graphSessionManager.endSession(requestId);
398
+ unregisterStreamHelper(requestId);
399
+ return { success: false, error: errorMessage, iterations };
400
+ }
401
+ }
402
+ }
@@ -0,0 +1,5 @@
1
+ import './instrumentation.js';
2
+ import { AgentExecutionServer } from './AgentExecutionServer.js';
3
+ declare const executionServer: AgentExecutionServer;
4
+ export { executionServer };
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAsB,MAAM,2BAA2B,CAAC;AA0BrF,QAAA,MAAM,eAAe,sBAQnB,CAAC;AAiBH,OAAO,EAAE,eAAe,EAAE,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ import './instrumentation.js';
2
+ import { AgentExecutionServer, EXECUTION_API_PORT } from './AgentExecutionServer.js';
3
+ import { InMemoryCredentialStore, createNangoCredentialStore, createKeyChainStore, } from '@inkeep/agents-core';
4
+ import { env } from './env.js';
5
+ import { getLogger } from './logger.js';
6
+ const logger = getLogger('execution-api');
7
+ // Create credential stores
8
+ const credentialStores = [
9
+ new InMemoryCredentialStore('memory-default'), // In-memory store + env vars
10
+ // Nango store (only loads if NANGO_SECRET_KEY is set)
11
+ ...(process.env.NANGO_SECRET_KEY
12
+ ? [
13
+ createNangoCredentialStore('nango-default', {
14
+ apiUrl: process.env.NANGO_HOST || 'https://api.nango.dev',
15
+ secretKey: process.env.NANGO_SECRET_KEY,
16
+ }),
17
+ ]
18
+ : []),
19
+ createKeyChainStore('keychain-default'),
20
+ ];
21
+ // Initialize Execution Server
22
+ const executionServer = new AgentExecutionServer({
23
+ port: EXECUTION_API_PORT,
24
+ credentialStores,
25
+ serverOptions: {
26
+ requestTimeout: 120000, // 120 seconds for execution requests
27
+ keepAliveTimeout: 60000,
28
+ keepAlive: true,
29
+ },
30
+ });
31
+ // Start the server only if not in test environment
32
+ if (env.ENVIRONMENT !== 'test') {
33
+ executionServer
34
+ .serve()
35
+ .then(() => {
36
+ logger.info(`📝 OpenAPI documentation available at http://localhost:${EXECUTION_API_PORT}/openapi.json`);
37
+ })
38
+ .catch((error) => {
39
+ logger.error('Failed to start Execution API server:', error);
40
+ process.exit(1);
41
+ });
42
+ }
43
+ export { executionServer };
@@ -0,0 +1,13 @@
1
+ import { NodeSDK } from '@opentelemetry/sdk-node';
2
+ declare class FanOutSpanProcessor {
3
+ private inner;
4
+ constructor(inner: any[]);
5
+ onStart(span: any, parent: any): void;
6
+ onEnd(span: any): void;
7
+ forceFlush(): Promise<void>;
8
+ shutdown(): Promise<void>;
9
+ }
10
+ declare const spanProcessor: FanOutSpanProcessor;
11
+ export declare const sdk: NodeSDK;
12
+ export { spanProcessor };
13
+ //# sourceMappingURL=instrumentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../src/instrumentation.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAOlD,cAAM,mBAAmB;IACX,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,GAAG,EAAE;IAChC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG;IAG9B,KAAK,CAAC,IAAI,EAAE,GAAG;IAGf,UAAU;IAGV,QAAQ;CAGT;AAED,QAAA,MAAM,aAAa,qBAQjB,CAAC;AAEH,eAAO,MAAM,GAAG,SAyBd,CAAC;AAGH,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
2
+ import { ALLOW_ALL_BAGGAGE_KEYS, BaggageSpanProcessor, } from '@opentelemetry/baggage-span-processor';
3
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
4
+ import { NodeSDK } from '@opentelemetry/sdk-node';
5
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
6
+ const otlpUrl = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:14318/v1/traces';
7
+ const otlpExporter = new OTLPTraceExporter({ url: otlpUrl });
8
+ // Minimal fan-out so NodeSDK can accept ONE spanProcessor
9
+ class FanOutSpanProcessor {
10
+ inner;
11
+ constructor(inner) {
12
+ this.inner = inner;
13
+ }
14
+ onStart(span, parent) {
15
+ this.inner.forEach((p) => p.onStart(span, parent));
16
+ }
17
+ onEnd(span) {
18
+ this.inner.forEach((p) => p.onEnd(span));
19
+ }
20
+ forceFlush() {
21
+ return Promise.all(this.inner.map((p) => p.forceFlush?.())).then(() => { });
22
+ }
23
+ shutdown() {
24
+ return Promise.all(this.inner.map((p) => p.shutdown?.())).then(() => { });
25
+ }
26
+ }
27
+ const spanProcessor = new FanOutSpanProcessor([
28
+ new BaggageSpanProcessor(ALLOW_ALL_BAGGAGE_KEYS),
29
+ new BatchSpanProcessor(otlpExporter, {
30
+ maxExportBatchSize: 1, // Send immediately (vs 512)
31
+ scheduledDelayMillis: 100, // 100ms delay (vs 5000ms)
32
+ exportTimeoutMillis: 5000, // 5s timeout (vs 30s)
33
+ maxQueueSize: 512 // Smaller queue
34
+ })
35
+ ]);
36
+ export const sdk = new NodeSDK({
37
+ serviceName: 'inkeep-chat',
38
+ spanProcessor,
39
+ instrumentations: [
40
+ getNodeAutoInstrumentations({
41
+ '@opentelemetry/instrumentation-http': {
42
+ enabled: true,
43
+ requestHook: (span, request) => {
44
+ const url = request?.url ?? request?.path;
45
+ if (!url)
46
+ return;
47
+ const u = new URL(url, 'http://localhost');
48
+ span.updateName(`${request?.method || 'UNKNOWN'} ${u.pathname}`);
49
+ },
50
+ },
51
+ '@opentelemetry/instrumentation-undici': {
52
+ requestHook: (span) => {
53
+ const method = span.attributes?.['http.request.method'];
54
+ const host = span.attributes?.['server.address'];
55
+ const path = span.attributes?.['url.path'];
56
+ if (method && path)
57
+ span.updateName(host ? `${method} ${host}${path}` : `${method} ${path}`);
58
+ },
59
+ },
60
+ }),
61
+ ],
62
+ });
63
+ // Export the span processor for force flush access
64
+ export { spanProcessor };
65
+ // SDK starts automatically when imported
66
+ sdk.start();
@@ -0,0 +1,4 @@
1
+ import type { Next } from 'hono';
2
+ export declare function getLogger(name?: string): import("pino").Logger<never, boolean>;
3
+ export declare function withRequestContext(reqId: string, fn: Next): Promise<void>;
4
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAwBjC,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,yCAOtC;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,iBAEzD"}
package/dist/logger.js ADDED
@@ -0,0 +1,32 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { pino } from 'pino';
3
+ // import { createGcpLoggingPinoConfig } from '@google-cloud/pino-logging-gcp-config';
4
+ import { env } from './env.js';
5
+ const logger = pino({
6
+ level: env.LOG_LEVEL,
7
+ serializers: {
8
+ obj: (value) => ({ ...value }),
9
+ },
10
+ redact: ['req.headers.authorization', 'req.headers["x-inkeep-admin-authentication"]'],
11
+ transport: {
12
+ target: 'pino-pretty',
13
+ options: {
14
+ sync: true,
15
+ destination: 1, // stdout
16
+ colorize: true,
17
+ translateTime: 'SYS:standard',
18
+ },
19
+ },
20
+ });
21
+ const asyncLocalStorage = new AsyncLocalStorage();
22
+ export function getLogger(name) {
23
+ const store = asyncLocalStorage.getStore();
24
+ const reqId = store?.get('requestId') || undefined;
25
+ if (!reqId) {
26
+ return logger.child({ name });
27
+ }
28
+ return logger.child({ reqId, name });
29
+ }
30
+ export function withRequestContext(reqId, fn) {
31
+ return asyncLocalStorage.run(new Map([['requestId', reqId]]), fn);
32
+ }
@@ -0,0 +1,22 @@
1
+ import { type ExecutionContext } from '@inkeep/agents-core';
2
+ /**
3
+ * Middleware to authenticate API requests using Bearer token authentication
4
+ * First checks if token matches INKEEP_AGENTS_RUN_BYPASS_SECRET, then falls back to API key validation
5
+ * Extracts and validates API keys, then adds execution context to the request
6
+ */
7
+ export declare const apiKeyAuth: () => import("hono").MiddlewareHandler<{
8
+ Variables: {
9
+ executionContext: ExecutionContext;
10
+ };
11
+ }, string, {}>;
12
+ export declare const extractContextFromApiKey: (apiKey: string) => Promise<ExecutionContext>;
13
+ /**
14
+ * Helper middleware for endpoints that optionally support API key authentication
15
+ * If no auth header is present, it continues without setting the executionContext
16
+ */
17
+ export declare const optionalAuth: () => import("hono").MiddlewareHandler<{
18
+ Variables: {
19
+ executionContext?: ExecutionContext;
20
+ };
21
+ }, string, {}>;
22
+ //# sourceMappingURL=api-key-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-auth.d.ts","sourceRoot":"","sources":["../../src/middleware/api-key-auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAmC,MAAM,qBAAqB,CAAC;AAM7F;;;;GAIG;AACH,eAAO,MAAM,UAAU;eAER;QACT,gBAAgB,EAAE,gBAAgB,CAAC;KACpC;cAoHD,CAAC;AAEL,eAAO,MAAM,wBAAwB,GAAU,QAAQ,MAAM,8BAgB5D,CAAC;AACF;;;GAGG;AACH,eAAO,MAAM,YAAY;eAEV;QACT,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;KACrC;cAYD,CAAC"}