@inkeep/agents-run-api 0.1.3 → 0.1.7

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 (158) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +5 -5
  3. package/SUPPLEMENTAL_TERMS.md +40 -0
  4. package/dist/chunk-HO5J26MO.js +234 -0
  5. package/dist/conversations-ZQXUNCNE.js +1 -0
  6. package/dist/index.cjs +9048 -0
  7. package/dist/index.d.cts +15 -0
  8. package/dist/index.d.ts +13 -21
  9. package/dist/index.js +8702 -27
  10. package/package.json +9 -8
  11. package/dist/AgentExecutionServer.d.ts +0 -28
  12. package/dist/AgentExecutionServer.d.ts.map +0 -1
  13. package/dist/AgentExecutionServer.js +0 -41
  14. package/dist/__tests__/setup.d.ts +0 -4
  15. package/dist/__tests__/setup.d.ts.map +0 -1
  16. package/dist/__tests__/setup.js +0 -80
  17. package/dist/__tests__/utils/testProject.d.ts +0 -18
  18. package/dist/__tests__/utils/testProject.d.ts.map +0 -1
  19. package/dist/__tests__/utils/testProject.js +0 -26
  20. package/dist/__tests__/utils/testRequest.d.ts +0 -8
  21. package/dist/__tests__/utils/testRequest.d.ts.map +0 -1
  22. package/dist/__tests__/utils/testRequest.js +0 -32
  23. package/dist/__tests__/utils/testTenant.d.ts +0 -64
  24. package/dist/__tests__/utils/testTenant.d.ts.map +0 -1
  25. package/dist/__tests__/utils/testTenant.js +0 -71
  26. package/dist/a2a/client.d.ts +0 -182
  27. package/dist/a2a/client.d.ts.map +0 -1
  28. package/dist/a2a/client.js +0 -645
  29. package/dist/a2a/handlers.d.ts +0 -4
  30. package/dist/a2a/handlers.d.ts.map +0 -1
  31. package/dist/a2a/handlers.js +0 -656
  32. package/dist/a2a/transfer.d.ts +0 -18
  33. package/dist/a2a/transfer.d.ts.map +0 -1
  34. package/dist/a2a/transfer.js +0 -22
  35. package/dist/a2a/types.d.ts +0 -63
  36. package/dist/a2a/types.d.ts.map +0 -1
  37. package/dist/a2a/types.js +0 -1
  38. package/dist/agents/Agent.d.ts +0 -154
  39. package/dist/agents/Agent.d.ts.map +0 -1
  40. package/dist/agents/Agent.js +0 -1110
  41. package/dist/agents/ModelFactory.d.ts +0 -62
  42. package/dist/agents/ModelFactory.d.ts.map +0 -1
  43. package/dist/agents/ModelFactory.js +0 -208
  44. package/dist/agents/SystemPromptBuilder.d.ts +0 -14
  45. package/dist/agents/SystemPromptBuilder.d.ts.map +0 -1
  46. package/dist/agents/SystemPromptBuilder.js +0 -62
  47. package/dist/agents/ToolSessionManager.d.ts +0 -61
  48. package/dist/agents/ToolSessionManager.d.ts.map +0 -1
  49. package/dist/agents/ToolSessionManager.js +0 -143
  50. package/dist/agents/artifactTools.d.ts +0 -30
  51. package/dist/agents/artifactTools.d.ts.map +0 -1
  52. package/dist/agents/artifactTools.js +0 -463
  53. package/dist/agents/generateTaskHandler.d.ts +0 -41
  54. package/dist/agents/generateTaskHandler.d.ts.map +0 -1
  55. package/dist/agents/generateTaskHandler.js +0 -350
  56. package/dist/agents/relationTools.d.ts +0 -35
  57. package/dist/agents/relationTools.d.ts.map +0 -1
  58. package/dist/agents/relationTools.js +0 -246
  59. package/dist/agents/types.d.ts +0 -23
  60. package/dist/agents/types.d.ts.map +0 -1
  61. package/dist/agents/types.js +0 -1
  62. package/dist/agents/versions/V1Config.d.ts +0 -21
  63. package/dist/agents/versions/V1Config.d.ts.map +0 -1
  64. package/dist/agents/versions/V1Config.js +0 -285
  65. package/dist/app.d.ts +0 -12
  66. package/dist/app.d.ts.map +0 -1
  67. package/dist/app.js +0 -216
  68. package/dist/data/agentGraph.d.ts +0 -4
  69. package/dist/data/agentGraph.d.ts.map +0 -1
  70. package/dist/data/agentGraph.js +0 -73
  71. package/dist/data/agents.d.ts +0 -4
  72. package/dist/data/agents.d.ts.map +0 -1
  73. package/dist/data/agents.js +0 -78
  74. package/dist/data/conversations.d.ts +0 -59
  75. package/dist/data/conversations.d.ts.map +0 -1
  76. package/dist/data/conversations.js +0 -216
  77. package/dist/data/db/clean.d.ts +0 -6
  78. package/dist/data/db/clean.d.ts.map +0 -1
  79. package/dist/data/db/clean.js +0 -77
  80. package/dist/data/db/dbClient.d.ts +0 -3
  81. package/dist/data/db/dbClient.d.ts.map +0 -1
  82. package/dist/data/db/dbClient.js +0 -13
  83. package/dist/env.d.ts +0 -45
  84. package/dist/env.d.ts.map +0 -1
  85. package/dist/env.js +0 -64
  86. package/dist/handlers/executionHandler.d.ts +0 -36
  87. package/dist/handlers/executionHandler.d.ts.map +0 -1
  88. package/dist/handlers/executionHandler.js +0 -399
  89. package/dist/index.d.ts.map +0 -1
  90. package/dist/instrumentation.d.ts +0 -13
  91. package/dist/instrumentation.d.ts.map +0 -1
  92. package/dist/instrumentation.js +0 -66
  93. package/dist/logger.d.ts +0 -4
  94. package/dist/logger.d.ts.map +0 -1
  95. package/dist/logger.js +0 -32
  96. package/dist/middleware/api-key-auth.d.ts +0 -22
  97. package/dist/middleware/api-key-auth.d.ts.map +0 -1
  98. package/dist/middleware/api-key-auth.js +0 -139
  99. package/dist/middleware/index.d.ts +0 -2
  100. package/dist/middleware/index.d.ts.map +0 -1
  101. package/dist/middleware/index.js +0 -1
  102. package/dist/openapi.d.ts +0 -2
  103. package/dist/openapi.d.ts.map +0 -1
  104. package/dist/openapi.js +0 -36
  105. package/dist/routes/agents.d.ts +0 -10
  106. package/dist/routes/agents.d.ts.map +0 -1
  107. package/dist/routes/agents.js +0 -158
  108. package/dist/routes/chat.d.ts +0 -10
  109. package/dist/routes/chat.d.ts.map +0 -1
  110. package/dist/routes/chat.js +0 -307
  111. package/dist/routes/chatDataStream.d.ts +0 -10
  112. package/dist/routes/chatDataStream.d.ts.map +0 -1
  113. package/dist/routes/chatDataStream.js +0 -179
  114. package/dist/routes/mcp.d.ts +0 -10
  115. package/dist/routes/mcp.d.ts.map +0 -1
  116. package/dist/routes/mcp.js +0 -500
  117. package/dist/server.d.ts +0 -5
  118. package/dist/server.d.ts.map +0 -1
  119. package/dist/server.js +0 -61
  120. package/dist/tracer.d.ts +0 -24
  121. package/dist/tracer.d.ts.map +0 -1
  122. package/dist/tracer.js +0 -107
  123. package/dist/types/chat.d.ts +0 -25
  124. package/dist/types/chat.d.ts.map +0 -1
  125. package/dist/types/chat.js +0 -1
  126. package/dist/types/execution-context.d.ts +0 -14
  127. package/dist/types/execution-context.d.ts.map +0 -1
  128. package/dist/types/execution-context.js +0 -14
  129. package/dist/utils/agent-operations.d.ts +0 -92
  130. package/dist/utils/agent-operations.d.ts.map +0 -1
  131. package/dist/utils/agent-operations.js +0 -78
  132. package/dist/utils/artifact-component-schema.d.ts +0 -29
  133. package/dist/utils/artifact-component-schema.d.ts.map +0 -1
  134. package/dist/utils/artifact-component-schema.js +0 -119
  135. package/dist/utils/artifact-parser.d.ts +0 -71
  136. package/dist/utils/artifact-parser.d.ts.map +0 -1
  137. package/dist/utils/artifact-parser.js +0 -251
  138. package/dist/utils/cleanup.d.ts +0 -19
  139. package/dist/utils/cleanup.d.ts.map +0 -1
  140. package/dist/utils/cleanup.js +0 -66
  141. package/dist/utils/data-component-schema.d.ts +0 -6
  142. package/dist/utils/data-component-schema.d.ts.map +0 -1
  143. package/dist/utils/data-component-schema.js +0 -43
  144. package/dist/utils/graph-session.d.ts +0 -200
  145. package/dist/utils/graph-session.d.ts.map +0 -1
  146. package/dist/utils/graph-session.js +0 -1016
  147. package/dist/utils/incremental-stream-parser.d.ts +0 -57
  148. package/dist/utils/incremental-stream-parser.d.ts.map +0 -1
  149. package/dist/utils/incremental-stream-parser.js +0 -289
  150. package/dist/utils/response-formatter.d.ts +0 -27
  151. package/dist/utils/response-formatter.d.ts.map +0 -1
  152. package/dist/utils/response-formatter.js +0 -160
  153. package/dist/utils/stream-helpers.d.ts +0 -174
  154. package/dist/utils/stream-helpers.d.ts.map +0 -1
  155. package/dist/utils/stream-helpers.js +0 -466
  156. package/dist/utils/stream-registry.d.ts +0 -18
  157. package/dist/utils/stream-registry.d.ts.map +0 -1
  158. package/dist/utils/stream-registry.js +0 -33
@@ -1,1016 +0,0 @@
1
- import { SpanStatusCode } from '@opentelemetry/api';
2
- import { generateObject, generateText } from 'ai';
3
- import { z } from 'zod';
4
- import { ModelFactory } from '../agents/ModelFactory';
5
- import { getFormattedConversationHistory } from '../data/conversations';
6
- import dbClient from '../data/db/dbClient';
7
- import { getLogger } from '../logger';
8
- import { createSpanName, getGlobalTracer, handleSpanError } from '../tracer';
9
- import { statusUpdateOp } from './agent-operations';
10
- import { getStreamHelper } from './stream-registry';
11
- const logger = getLogger('GraphSession');
12
- const tracer = getGlobalTracer();
13
- /**
14
- * Tracks all agent operations and interactions for a single message
15
- * Now includes intelligent status update functionality
16
- */
17
- export class GraphSession {
18
- sessionId;
19
- messageId;
20
- graphId;
21
- tenantId;
22
- projectId;
23
- events = [];
24
- statusUpdateState;
25
- statusUpdateTimer;
26
- previousSummaries = [];
27
- isEnded = false;
28
- isTextStreaming = false;
29
- isGeneratingUpdate = false;
30
- constructor(sessionId, messageId, graphId, tenantId, projectId) {
31
- this.sessionId = sessionId;
32
- this.messageId = messageId;
33
- this.graphId = graphId;
34
- this.tenantId = tenantId;
35
- this.projectId = projectId;
36
- logger.debug({ sessionId, messageId, graphId }, 'GraphSession created');
37
- }
38
- /**
39
- * Initialize status updates for this session
40
- */
41
- initializeStatusUpdates(config, summarizerModel) {
42
- const now = Date.now();
43
- this.statusUpdateState = {
44
- lastUpdateTime: now,
45
- lastEventCount: 0,
46
- startTime: now,
47
- summarizerModel,
48
- config: {
49
- numEvents: config.numEvents || 10,
50
- timeInSeconds: config.timeInSeconds || 30,
51
- ...config,
52
- },
53
- };
54
- // Set up time-based updates if configured
55
- if (this.statusUpdateState.config.timeInSeconds) {
56
- this.statusUpdateTimer = setInterval(async () => {
57
- // Guard against cleanup race condition
58
- if (!this.statusUpdateState || this.isEnded) {
59
- logger.debug({ sessionId: this.sessionId }, 'Timer triggered but session already cleaned up or ended');
60
- if (this.statusUpdateTimer) {
61
- clearInterval(this.statusUpdateTimer);
62
- this.statusUpdateTimer = undefined;
63
- }
64
- return;
65
- }
66
- await this.checkAndSendTimeBasedUpdate();
67
- }, this.statusUpdateState.config.timeInSeconds * 1000);
68
- logger.info({
69
- sessionId: this.sessionId,
70
- intervalMs: this.statusUpdateState.config.timeInSeconds * 1000,
71
- }, 'Time-based status update timer started');
72
- }
73
- }
74
- /**
75
- * Record an event in the session and trigger status updates if configured
76
- */
77
- recordEvent(eventType, agentId, data) {
78
- // Don't record events or trigger updates if session has ended
79
- if (this.isEnded) {
80
- logger.debug({
81
- sessionId: this.sessionId,
82
- eventType,
83
- agentId,
84
- }, 'Event received after session ended - ignoring');
85
- return;
86
- }
87
- const event = {
88
- timestamp: Date.now(),
89
- eventType,
90
- agentId,
91
- data,
92
- };
93
- this.events.push(event);
94
- // Process artifact if it's pending generation
95
- if (eventType === 'artifact_saved' && data.pendingGeneration) {
96
- // Fire and forget - process artifact completely asynchronously without any blocking
97
- setImmediate(() => {
98
- // No await, no spans at trigger level - truly fire and forget
99
- this.processArtifact(data).catch((error) => {
100
- logger.error({
101
- sessionId: this.sessionId,
102
- artifactId: data.artifactId,
103
- error: error instanceof Error ? error.message : 'Unknown error',
104
- stack: error instanceof Error ? error.stack : undefined,
105
- artifactData: data,
106
- }, 'Failed to process artifact - fire and forget error');
107
- });
108
- });
109
- }
110
- // Trigger status updates check (only sends if thresholds met)
111
- if (!this.isEnded) {
112
- this.checkStatusUpdates();
113
- }
114
- }
115
- /**
116
- * Check and send status updates if configured (async, non-blocking)
117
- */
118
- checkStatusUpdates() {
119
- if (this.isEnded) {
120
- logger.debug({ sessionId: this.sessionId }, 'Session has ended - skipping status update check');
121
- return;
122
- }
123
- if (!this.statusUpdateState) {
124
- logger.debug({ sessionId: this.sessionId }, 'No status update state - skipping check');
125
- return;
126
- }
127
- // Status updates are enabled by having statusUpdateState
128
- // Store reference to prevent race condition during async execution
129
- const statusUpdateState = this.statusUpdateState;
130
- // Run async without blocking the main flow
131
- setImmediate(async () => {
132
- try {
133
- // Check if session is still active and statusUpdateState hasn't been cleaned up or text is streaming
134
- if (this.isEnded || !statusUpdateState || this.isTextStreaming) {
135
- return;
136
- }
137
- const currentEventCount = this.events.length;
138
- const numEventsThreshold = statusUpdateState.config.numEvents;
139
- const shouldUpdateByEvents = numEventsThreshold &&
140
- currentEventCount >= statusUpdateState.lastEventCount + numEventsThreshold;
141
- if (shouldUpdateByEvents) {
142
- await this.generateAndSendUpdate();
143
- }
144
- }
145
- catch (error) {
146
- logger.error({
147
- sessionId: this.sessionId,
148
- error: error instanceof Error ? error.message : 'Unknown error',
149
- }, 'Failed to check status updates during event recording');
150
- }
151
- });
152
- }
153
- /**
154
- * Check and send time-based status updates
155
- */
156
- async checkAndSendTimeBasedUpdate() {
157
- if (this.isEnded) {
158
- logger.debug({ sessionId: this.sessionId }, 'Session has ended - skipping time-based update');
159
- return;
160
- }
161
- if (!this.statusUpdateState) {
162
- logger.debug({ sessionId: this.sessionId }, 'No status updates configured for time-based check');
163
- return;
164
- }
165
- // Only send if we have new events since last update
166
- const newEventCount = this.events.length - this.statusUpdateState.lastEventCount;
167
- if (newEventCount === 0) {
168
- return;
169
- }
170
- try {
171
- // Always send time-based updates regardless of event count
172
- await this.generateAndSendUpdate();
173
- }
174
- catch (error) {
175
- logger.error({
176
- sessionId: this.sessionId,
177
- error: error instanceof Error ? error.message : 'Unknown error',
178
- }, 'Failed to send time-based status update');
179
- }
180
- }
181
- /**
182
- * Get all events in chronological order
183
- */
184
- getEvents() {
185
- return [...this.events];
186
- }
187
- /**
188
- * Get events filtered by type
189
- */
190
- getEventsByType(eventType) {
191
- return this.events.filter((event) => event.eventType === eventType);
192
- }
193
- /**
194
- * Get events filtered by agent
195
- */
196
- getEventsByAgent(agentId) {
197
- return this.events.filter((event) => event.agentId === agentId);
198
- }
199
- /**
200
- * Get summary of session activity
201
- */
202
- getSummary() {
203
- const eventCounts = this.events.reduce((counts, event) => {
204
- counts[event.eventType] = (counts[event.eventType] || 0) + 1;
205
- return counts;
206
- }, {});
207
- const agentCounts = this.events.reduce((counts, event) => {
208
- counts[event.agentId] = (counts[event.agentId] || 0) + 1;
209
- return counts;
210
- }, {});
211
- return {
212
- sessionId: this.sessionId,
213
- messageId: this.messageId,
214
- graphId: this.graphId,
215
- totalEvents: this.events.length,
216
- eventCounts,
217
- agentCounts,
218
- startTime: this.events[0]?.timestamp,
219
- endTime: this.events[this.events.length - 1]?.timestamp,
220
- duration: this.events.length > 0
221
- ? this.events[this.events.length - 1].timestamp - this.events[0].timestamp
222
- : 0,
223
- };
224
- }
225
- /**
226
- * Mark that text streaming has started (to suppress status updates)
227
- */
228
- setTextStreaming(isStreaming) {
229
- this.isTextStreaming = isStreaming;
230
- }
231
- /**
232
- * Check if text is currently being streamed
233
- */
234
- isCurrentlyStreaming() {
235
- return this.isTextStreaming;
236
- }
237
- /**
238
- * Clean up status update resources when session ends
239
- */
240
- cleanup() {
241
- // Mark session as ended
242
- this.isEnded = true;
243
- if (this.statusUpdateTimer) {
244
- clearInterval(this.statusUpdateTimer);
245
- this.statusUpdateTimer = undefined;
246
- }
247
- this.statusUpdateState = undefined;
248
- }
249
- /**
250
- * Generate and send a status update using graph-level summarizer
251
- */
252
- async generateAndSendUpdate() {
253
- if (this.isEnded) {
254
- logger.debug({ sessionId: this.sessionId }, 'Session has ended - not generating update');
255
- return;
256
- }
257
- if (this.isTextStreaming) {
258
- logger.debug({ sessionId: this.sessionId }, 'Text is currently streaming - skipping status update');
259
- return;
260
- }
261
- if (this.isGeneratingUpdate) {
262
- logger.debug({ sessionId: this.sessionId }, 'Update already in progress - skipping duplicate generation');
263
- return;
264
- }
265
- if (!this.statusUpdateState) {
266
- logger.warn({ sessionId: this.sessionId }, 'No status update state - cannot generate update');
267
- return;
268
- }
269
- if (!this.graphId) {
270
- logger.warn({ sessionId: this.sessionId }, 'No graph ID - cannot generate update');
271
- return;
272
- }
273
- // Only send if we have new events since last update
274
- const newEventCount = this.events.length - this.statusUpdateState.lastEventCount;
275
- if (newEventCount === 0) {
276
- return;
277
- }
278
- // Set flag to prevent concurrent updates
279
- this.isGeneratingUpdate = true;
280
- // Store references at start to prevent race conditions
281
- const statusUpdateState = this.statusUpdateState;
282
- const graphId = this.graphId;
283
- try {
284
- const streamHelper = getStreamHelper(this.sessionId);
285
- if (!streamHelper) {
286
- logger.warn({ sessionId: this.sessionId }, 'No stream helper found - cannot send status update');
287
- this.isGeneratingUpdate = false;
288
- return;
289
- }
290
- const now = Date.now();
291
- const elapsedTime = now - statusUpdateState.startTime;
292
- // Generate status update - either structured or text summary
293
- let operation;
294
- if (statusUpdateState.config.statusComponents &&
295
- statusUpdateState.config.statusComponents.length > 0) {
296
- // Use generateObject to intelligently select relevant data components
297
- const result = await this.generateStructuredStatusUpdate(this.events.slice(statusUpdateState.lastEventCount), elapsedTime, statusUpdateState.config.statusComponents, statusUpdateState.summarizerModel, this.previousSummaries);
298
- if (result.operations && result.operations.length > 0) {
299
- // Send each operation separately using writeData for dynamic types
300
- for (const op of result.operations) {
301
- // Guard against empty/invalid operations
302
- if (!op || !op.type || !op.data || Object.keys(op.data).length === 0) {
303
- logger.warn({
304
- sessionId: this.sessionId,
305
- operation: op,
306
- }, 'Skipping empty or invalid structured operation');
307
- continue;
308
- }
309
- const operationToSend = {
310
- type: 'status_update',
311
- ctx: {
312
- operationType: op.type,
313
- data: op.data,
314
- },
315
- };
316
- await streamHelper.writeOperation(operationToSend);
317
- }
318
- // Store summaries for next time - use full JSON for better comparison
319
- const summaryTexts = result.operations.map((op) => JSON.stringify({ type: op.type, data: op.data }));
320
- this.previousSummaries.push(...summaryTexts);
321
- // Update state after sending all operations
322
- if (this.statusUpdateState) {
323
- this.statusUpdateState.lastUpdateTime = now;
324
- this.statusUpdateState.lastEventCount = this.events.length;
325
- }
326
- return;
327
- }
328
- else {
329
- // Fall through to regular text summary if no structured updates
330
- }
331
- }
332
- else {
333
- // Use regular text generation for simple summaries
334
- const summary = await this.generateProgressSummary(this.events.slice(statusUpdateState.lastEventCount), elapsedTime, statusUpdateState.summarizerModel, this.previousSummaries);
335
- // Store this summary for next time
336
- this.previousSummaries.push(summary);
337
- // Create standard status update operation
338
- operation = statusUpdateOp({
339
- summary,
340
- eventCount: this.events.length,
341
- elapsedTime,
342
- currentPhase: 'processing',
343
- activeAgent: 'system',
344
- graphId,
345
- sessionId: this.sessionId,
346
- });
347
- }
348
- // Keep only last 3 summaries to avoid context getting too large
349
- if (this.previousSummaries.length > 3) {
350
- this.previousSummaries.shift();
351
- }
352
- // Guard against sending empty/undefined operations that break streams
353
- if (!operation || !operation.type || !operation.ctx) {
354
- logger.warn({
355
- sessionId: this.sessionId,
356
- operation,
357
- }, 'Skipping empty or invalid status update operation');
358
- return;
359
- }
360
- await streamHelper.writeOperation(operation);
361
- // Update state - check if still exists (could be cleaned up during async operation)
362
- if (this.statusUpdateState) {
363
- this.statusUpdateState.lastUpdateTime = now;
364
- this.statusUpdateState.lastEventCount = this.events.length;
365
- }
366
- }
367
- catch (error) {
368
- logger.error({
369
- sessionId: this.sessionId,
370
- error: error instanceof Error ? error.message : 'Unknown error',
371
- stack: error instanceof Error ? error.stack : undefined,
372
- }, '❌ Failed to generate status update');
373
- }
374
- finally {
375
- // Clear the flag to allow future updates
376
- this.isGeneratingUpdate = false;
377
- }
378
- }
379
- /**
380
- * Generate user-focused progress summary hiding internal operations
381
- */
382
- async generateProgressSummary(newEvents, elapsedTime, summarizerModel, previousSummaries = []) {
383
- return tracer.startActiveSpan(createSpanName('graph_session.generate_progress_summary'), {
384
- attributes: {
385
- 'graph_session.id': this.sessionId,
386
- 'events.count': newEvents.length,
387
- 'elapsed_time.seconds': Math.round(elapsedTime / 1000),
388
- 'llm.model': summarizerModel?.model || 'openai/gpt-4.1-nano-2025-04-14',
389
- 'previous_summaries.count': previousSummaries.length,
390
- },
391
- }, async (span) => {
392
- try {
393
- // Extract user-visible activities (hide internal agent operations)
394
- const userVisibleActivities = this.extractUserVisibleActivities(newEvents);
395
- // Get conversation history to understand user's context and question
396
- let conversationContext = '';
397
- if (this.tenantId && this.projectId) {
398
- try {
399
- const conversationHistory = await getFormattedConversationHistory({
400
- tenantId: this.tenantId,
401
- projectId: this.projectId,
402
- conversationId: this.sessionId,
403
- options: {
404
- limit: 10, // Get recent conversation context
405
- maxOutputTokens: 2000,
406
- },
407
- filters: {},
408
- });
409
- conversationContext = conversationHistory.trim()
410
- ? `\nUser's Question/Context:\n${conversationHistory}\n`
411
- : '';
412
- }
413
- catch (error) {
414
- logger.warn({ sessionId: this.sessionId, error }, 'Failed to fetch conversation history for status update');
415
- }
416
- }
417
- const previousSummaryContext = previousSummaries.length > 0
418
- ? `\nPrevious updates provided to user:\n${previousSummaries.map((s, i) => `${i + 1}. ${s}`).join('\n')}\n`
419
- : '';
420
- // Use custom prompt if provided, otherwise use default
421
- const basePrompt = `Generate a brief status update for what is happening now to the user. Please keep it short and concise and informative based on what the user has asked for.${conversationContext}${previousSummaries.length > 0 ? `\n${previousSummaryContext}` : ''}
422
-
423
- Activities:\n${userVisibleActivities.join('\n') || 'No New Activities'}
424
-
425
- What's happening now?
426
-
427
- ${this.statusUpdateState?.config.prompt?.trim() || ''}`;
428
- const prompt = basePrompt;
429
- const model = ModelFactory.createModel(summarizerModel && summarizerModel.model?.trim()
430
- ? summarizerModel
431
- : { model: 'openai/gpt-4.1-nano-2025-04-14' });
432
- const { text } = await generateText({
433
- model,
434
- prompt,
435
- experimental_telemetry: {
436
- isEnabled: true,
437
- functionId: `status_update_${this.sessionId}`,
438
- recordInputs: true,
439
- recordOutputs: true,
440
- metadata: {
441
- operation: 'progress_summary_generation',
442
- sessionId: this.sessionId,
443
- },
444
- },
445
- });
446
- span.setAttributes({
447
- 'summary.length': text.trim().length,
448
- 'user_activities.count': userVisibleActivities.length,
449
- });
450
- span.setStatus({ code: SpanStatusCode.OK });
451
- return text.trim();
452
- }
453
- catch (error) {
454
- handleSpanError(span, error);
455
- logger.error({ error }, 'Failed to generate summary, using fallback');
456
- return this.generateFallbackSummary(newEvents, elapsedTime);
457
- }
458
- finally {
459
- span.end();
460
- }
461
- });
462
- }
463
- /**
464
- * Generate structured status update using configured data components
465
- */
466
- async generateStructuredStatusUpdate(newEvents, elapsedTime, statusComponents, summarizerModel, previousSummaries = []) {
467
- return tracer.startActiveSpan(createSpanName('graph_session.generate_structured_update'), {
468
- attributes: {
469
- 'graph_session.id': this.sessionId,
470
- 'events.count': newEvents.length,
471
- 'elapsed_time.seconds': Math.round(elapsedTime / 1000),
472
- 'llm.model': summarizerModel?.model || 'openai/gpt-4.1-nano-2025-04-14',
473
- 'status_components.count': statusComponents.length,
474
- 'previous_summaries.count': previousSummaries.length,
475
- },
476
- }, async (span) => {
477
- try {
478
- // Extract user-visible activities
479
- const userVisibleActivities = this.extractUserVisibleActivities(newEvents);
480
- // Get conversation history to understand user's context and question
481
- let conversationContext = '';
482
- if (this.tenantId && this.projectId) {
483
- try {
484
- const conversationHistory = await getFormattedConversationHistory({
485
- tenantId: this.tenantId,
486
- projectId: this.projectId,
487
- conversationId: this.sessionId,
488
- options: {
489
- limit: 10, // Get recent conversation context
490
- maxOutputTokens: 2000,
491
- },
492
- filters: {},
493
- });
494
- conversationContext = conversationHistory.trim()
495
- ? `\nUser's Question/Context:\n${conversationHistory}\n`
496
- : '';
497
- }
498
- catch (error) {
499
- logger.warn({ sessionId: this.sessionId, error }, 'Failed to fetch conversation history for structured status update');
500
- }
501
- }
502
- const previousSummaryContext = previousSummaries.length > 0
503
- ? `\nPrevious updates sent to user:\n${previousSummaries.map((s, i) => `${i + 1}. ${s}`).join('\n')}\n`
504
- : '';
505
- // Build schema for data components and no_relevant_updates option
506
- const selectionSchema = z.object(Object.fromEntries([
507
- // Add no_relevant_updates schema
508
- [
509
- 'no_relevant_updates',
510
- z
511
- .object({
512
- no_updates: z.boolean().default(true),
513
- })
514
- .optional()
515
- .describe('Use when nothing substantially new to report. Should only use on its own.'),
516
- ],
517
- // Add all other component schemas
518
- ...statusComponents.map((component) => [
519
- component.id,
520
- this.buildZodSchema(component.schema)
521
- .optional()
522
- .describe(component.description || component.name),
523
- ]),
524
- ]));
525
- // Use custom prompt if provided, otherwise use default
526
- const basePrompt = `Generate status updates for relevant components based on what the user has asked for.${conversationContext}${previousSummaries.length > 0 ? `\n${previousSummaryContext}` : ''}
527
-
528
- Activities:\n${userVisibleActivities.join('\n') || 'No New Activities'}
529
-
530
- Available components: no_relevant_updates, ${statusComponents.map((c) => c.id).join(', ')}
531
-
532
- Rules:
533
- - Fill in data for relevant components only
534
- - Use 'no_relevant_updates' if nothing substantially new to report
535
- - Never repeat previous values
536
- - You are ONE AI (no agents/delegations)
537
-
538
- ${this.statusUpdateState?.config.prompt?.trim() || ''}`;
539
- const prompt = basePrompt;
540
- const model = ModelFactory.createModel(summarizerModel && summarizerModel.model?.trim()
541
- ? summarizerModel
542
- : { model: 'openai/gpt-4.1-nano-2025-04-14' });
543
- const { object } = await generateObject({
544
- model,
545
- prompt,
546
- schema: selectionSchema,
547
- experimental_telemetry: {
548
- isEnabled: true,
549
- functionId: `structured_update_${this.sessionId}`,
550
- recordInputs: true,
551
- recordOutputs: true,
552
- metadata: {
553
- operation: 'structured_status_update_generation',
554
- sessionId: this.sessionId,
555
- },
556
- },
557
- });
558
- const result = object;
559
- // Extract components that have data (skip no_relevant_updates and empty components)
560
- const operations = [];
561
- for (const [componentId, data] of Object.entries(result)) {
562
- // Skip no_relevant_updates - we don't send any operation for this
563
- if (componentId === 'no_relevant_updates') {
564
- continue;
565
- }
566
- // Only include components that have actual data
567
- if (data && typeof data === 'object' && Object.keys(data).length > 0) {
568
- operations.push({
569
- type: componentId,
570
- data: data,
571
- });
572
- }
573
- }
574
- span.setAttributes({
575
- 'operations.count': operations.length,
576
- 'user_activities.count': userVisibleActivities.length,
577
- 'result_keys.count': Object.keys(result).length,
578
- });
579
- span.setStatus({ code: SpanStatusCode.OK });
580
- return { operations };
581
- }
582
- catch (error) {
583
- handleSpanError(span, error);
584
- logger.error({ error }, 'Failed to generate structured update, using fallback');
585
- return { operations: [] };
586
- }
587
- finally {
588
- span.end();
589
- }
590
- });
591
- }
592
- /**
593
- * Build Zod schema from JSON schema configuration
594
- */
595
- buildZodSchema(jsonSchema) {
596
- const properties = {};
597
- for (const [key, value] of Object.entries(jsonSchema.properties)) {
598
- // Simple type mapping - can be expanded as needed
599
- if (value.type === 'string') {
600
- properties[key] = z.string();
601
- }
602
- else if (value.type === 'number') {
603
- properties[key] = z.number();
604
- }
605
- else if (value.type === 'boolean') {
606
- properties[key] = z.boolean();
607
- }
608
- else if (value.type === 'array') {
609
- properties[key] = z.array(z.any());
610
- }
611
- else if (value.type === 'object') {
612
- properties[key] = z.record(z.string(), z.any());
613
- }
614
- else {
615
- properties[key] = z.any();
616
- }
617
- // Make optional if not in required array
618
- if (!jsonSchema.required?.includes(key)) {
619
- properties[key] = properties[key].optional();
620
- }
621
- }
622
- return z.object(properties);
623
- }
624
- /**
625
- * Extract user-visible activities with rich formatting and complete information
626
- */
627
- extractUserVisibleActivities(events) {
628
- const activities = [];
629
- for (const event of events) {
630
- switch (event.eventType) {
631
- case 'tool_execution': {
632
- const data = event.data;
633
- const resultStr = JSON.stringify(data.result);
634
- activities.push(`🔧 **${data.toolName}** ${data.duration ? `(${data.duration}ms)` : ''}\n` +
635
- ` 📥 Input: ${JSON.stringify(data.args)}\n` +
636
- ` 📤 Output: ${resultStr}`);
637
- break;
638
- }
639
- case 'transfer': {
640
- const data = event.data;
641
- activities.push(`🔄 **Transfer**: ${data.fromAgent} → ${data.targetAgent}\n` +
642
- ` ${data.reason ? `Reason: ${data.reason}` : 'Control transfer'}\n` +
643
- ` ${data.context ? `Context: ${JSON.stringify(data.context, null, 2)}` : ''}`);
644
- break;
645
- }
646
- case 'delegation_sent': {
647
- const data = event.data;
648
- activities.push(`📤 **Delegation Sent** [${data.delegationId}]: ${data.fromAgent} → ${data.targetAgent}\n` +
649
- ` Task: ${data.taskDescription}\n` +
650
- ` ${data.context ? `Context: ${JSON.stringify(data.context, null, 2)}` : ''}`);
651
- break;
652
- }
653
- case 'delegation_returned': {
654
- const data = event.data;
655
- activities.push(`📥 **Delegation Returned** [${data.delegationId}]: ${data.fromAgent} ← ${data.targetAgent}\n` +
656
- ` Result: ${JSON.stringify(data.result, null, 2)}`);
657
- break;
658
- }
659
- case 'artifact_saved': {
660
- const data = event.data;
661
- activities.push(`💾 **Artifact Saved**: ${data.artifactType}\n` +
662
- ` ID: ${data.artifactId}\n` +
663
- ` Task: ${data.taskId}\n` +
664
- ` ${data.summaryData ? `Summary: ${data.summaryData}` : ''}\n` +
665
- ` ${data.fullData ? `Full Data: ${data.fullData}` : ''}`);
666
- break;
667
- }
668
- case 'agent_generate': {
669
- const data = event.data;
670
- if (data.generationType !== 'artifact_name_description') {
671
- activities.push(`⚙️ **Generation**: ${data.generationType}\n` +
672
- ` Full Details: ${JSON.stringify(data.parts, null, 2)}`);
673
- }
674
- break;
675
- }
676
- default: {
677
- activities.push(`📋 **${event.eventType}**: ${JSON.stringify(event.data, null, 2)}`);
678
- break;
679
- }
680
- }
681
- }
682
- return activities;
683
- }
684
- /**
685
- * Generate fallback summary when LLM fails
686
- */
687
- generateFallbackSummary(events, elapsedTime) {
688
- const timeStr = Math.round(elapsedTime / 1000);
689
- const toolCalls = events.filter((e) => e.eventType === 'tool_execution').length;
690
- const artifacts = events.filter((e) => e.eventType === 'artifact_saved').length;
691
- if (artifacts > 0) {
692
- return `Generated ${artifacts} result${artifacts > 1 ? 's' : ''} so far (${timeStr}s elapsed)`;
693
- }
694
- else if (toolCalls > 0) {
695
- return `Used ${toolCalls} tool${toolCalls > 1 ? 's' : ''} to gather information (${timeStr}s elapsed)`;
696
- }
697
- else {
698
- return `Processing your request... (${timeStr}s elapsed)`;
699
- }
700
- }
701
- /**
702
- * Process a single artifact to generate name and description using conversation context
703
- */
704
- async processArtifact(artifactData) {
705
- return tracer.startActiveSpan(createSpanName('graph_session.process_artifact'), {
706
- attributes: {
707
- 'graph_session.id': this.sessionId,
708
- 'artifact.id': artifactData.artifactId,
709
- 'artifact.type': artifactData.artifactType || 'unknown',
710
- 'tenant.id': artifactData.tenantId || 'unknown',
711
- 'project.id': artifactData.projectId || 'unknown',
712
- 'context.id': artifactData.contextId || 'unknown',
713
- has_tenant_id: !!artifactData.tenantId,
714
- has_project_id: !!artifactData.projectId,
715
- has_context_id: !!artifactData.contextId,
716
- has_metadata: !!artifactData.metadata,
717
- tool_call_id: artifactData.metadata?.toolCallId || 'missing',
718
- pending_generation: !!artifactData.pendingGeneration,
719
- },
720
- }, async (span) => {
721
- try {
722
- // We need tenantId, projectId, and contextId to get conversation history
723
- if (!artifactData.tenantId || !artifactData.projectId || !artifactData.contextId) {
724
- span.setAttributes({
725
- 'validation.failed': true,
726
- missing_tenant_id: !artifactData.tenantId,
727
- missing_project_id: !artifactData.projectId,
728
- missing_context_id: !artifactData.contextId,
729
- });
730
- throw new Error('Missing required session info (tenantId, projectId, or contextId) for artifact processing');
731
- }
732
- span.setAttributes({ 'validation.passed': true });
733
- // Get conversation history for context
734
- span.setAttributes({ step: 'importing_conversations' });
735
- const { getFormattedConversationHistory } = await import('../data/conversations.js');
736
- span.setAttributes({ step: 'fetching_conversation_history' });
737
- const conversationHistory = await getFormattedConversationHistory({
738
- tenantId: artifactData.tenantId,
739
- projectId: artifactData.projectId,
740
- conversationId: artifactData.contextId,
741
- options: {
742
- limit: 10, // Only need recent context
743
- includeInternal: false, // Focus on user messages
744
- messageTypes: ['chat'],
745
- },
746
- });
747
- span.setAttributes({
748
- step: 'conversation_history_fetched',
749
- 'conversation_history.length': conversationHistory.length,
750
- });
751
- // Find the specific tool call that generated this artifact
752
- // Now toolId and toolCallId should be the same since we use AI SDK's toolCallId consistently
753
- const toolCallEvent = this.events.find((event) => event.eventType === 'tool_execution' &&
754
- event.data &&
755
- 'toolId' in event.data &&
756
- event.data.toolId === artifactData.metadata?.toolCallId);
757
- const toolResultEvent = this.events.find((event) => event.eventType === 'tool_execution' &&
758
- event.data &&
759
- 'toolId' in event.data &&
760
- event.data.toolId === artifactData.metadata?.toolCallId);
761
- // Prepare context for name/description generation
762
- const toolContext = toolCallEvent
763
- ? {
764
- toolName: toolCallEvent.data.toolName,
765
- args: toolCallEvent.data.args,
766
- }
767
- : null;
768
- const toolResult = toolResultEvent
769
- ? {
770
- result: toolResultEvent.data.result,
771
- }
772
- : null;
773
- const prompt = `Name this artifact (max 50 chars) and describe it (max 150 chars).
774
-
775
- Context: ${conversationHistory?.slice(-200) || 'Processing'}
776
- Type: ${artifactData.artifactType || 'data'}
777
- Summary: ${JSON.stringify(artifactData.summaryProps, null, 2)}
778
- Full: ${JSON.stringify(artifactData.fullProps, null, 2)}
779
-
780
- Make it specific and relevant.`;
781
- const model = ModelFactory.createModel(this.statusUpdateState?.summarizerModel || { model: 'openai/gpt-4.1-nano-2025-04-14' });
782
- const schema = z.object({
783
- name: z.string().max(50).describe('Concise, descriptive name for the artifact'),
784
- description: z
785
- .string()
786
- .max(150)
787
- .describe("Brief description of the artifact's relevance to the user's question"),
788
- });
789
- // Add nested span for LLM generation
790
- const { object: result } = await tracer.startActiveSpan(createSpanName('graph_session.generate_artifact_metadata'), {
791
- attributes: {
792
- 'llm.model': this.statusUpdateState?.summarizerModel?.model ||
793
- 'openai/gpt-4.1-nano-2025-04-14',
794
- 'llm.operation': 'generate_object',
795
- 'artifact.id': artifactData.artifactId,
796
- 'prompt.length': prompt.length,
797
- },
798
- }, async (generationSpan) => {
799
- try {
800
- const result = await generateObject({
801
- model,
802
- prompt,
803
- schema,
804
- experimental_telemetry: {
805
- isEnabled: true,
806
- functionId: `artifact_processing_${artifactData.artifactId}`,
807
- recordInputs: true,
808
- recordOutputs: true,
809
- metadata: {
810
- operation: 'artifact_name_description_generation',
811
- sessionId: this.sessionId,
812
- },
813
- },
814
- });
815
- generationSpan.setAttributes({
816
- 'generation.name_length': result.object.name.length,
817
- 'generation.description_length': result.object.description.length,
818
- });
819
- generationSpan.setStatus({ code: SpanStatusCode.OK });
820
- return result;
821
- }
822
- catch (error) {
823
- handleSpanError(generationSpan, error);
824
- throw error;
825
- }
826
- finally {
827
- generationSpan.end();
828
- }
829
- });
830
- // Now save the artifact to the ledger with the generated name and description
831
- const { addLedgerArtifacts } = await import('@inkeep/agents-core');
832
- const artifactToSave = {
833
- artifactId: artifactData.artifactId,
834
- name: result.name,
835
- description: result.description,
836
- type: 'source',
837
- taskId: artifactData.taskId,
838
- parts: [
839
- {
840
- kind: 'data',
841
- data: {
842
- summary: artifactData.summaryProps || {},
843
- full: artifactData.fullProps || {},
844
- },
845
- },
846
- ],
847
- metadata: artifactData.metadata || {},
848
- };
849
- await addLedgerArtifacts(dbClient)({
850
- scopes: {
851
- tenantId: artifactData.tenantId,
852
- projectId: artifactData.projectId,
853
- },
854
- contextId: artifactData.contextId,
855
- taskId: artifactData.taskId,
856
- artifacts: [artifactToSave],
857
- });
858
- logger.info({
859
- sessionId: this.sessionId,
860
- artifactId: artifactData.artifactId,
861
- name: result.name,
862
- description: result.description,
863
- }, 'Artifact successfully saved to ledger with generated name and description');
864
- // Mark main span as successful
865
- span.setAttributes({
866
- 'artifact.name': result.name,
867
- 'artifact.description': result.description,
868
- 'processing.success': true,
869
- });
870
- span.setStatus({ code: SpanStatusCode.OK });
871
- }
872
- catch (error) {
873
- // Handle span error
874
- handleSpanError(span, error);
875
- logger.error({
876
- sessionId: this.sessionId,
877
- artifactId: artifactData.artifactId,
878
- error: error instanceof Error ? error.message : 'Unknown error',
879
- }, 'Failed to process artifact');
880
- // Fallback: save artifact with basic info
881
- try {
882
- const { addLedgerArtifacts } = await import('@inkeep/agents-core');
883
- const fallbackArtifact = {
884
- artifactId: artifactData.artifactId,
885
- name: `Artifact ${artifactData.artifactId.substring(0, 8)}`,
886
- description: `${artifactData.artifactType || 'Data'} from ${artifactData.metadata?.toolName || 'tool results'}`,
887
- taskId: artifactData.taskId,
888
- parts: [
889
- {
890
- kind: 'data',
891
- data: {
892
- summary: artifactData.summaryProps || {},
893
- full: artifactData.fullProps || {},
894
- },
895
- },
896
- ],
897
- metadata: artifactData.metadata || {},
898
- };
899
- if (artifactData.tenantId && artifactData.projectId) {
900
- await addLedgerArtifacts(dbClient)({
901
- scopes: {
902
- tenantId: artifactData.tenantId,
903
- projectId: artifactData.projectId,
904
- },
905
- contextId: artifactData.contextId || 'unknown',
906
- taskId: artifactData.taskId,
907
- artifacts: [fallbackArtifact],
908
- });
909
- logger.info({
910
- sessionId: this.sessionId,
911
- artifactId: artifactData.artifactId,
912
- }, 'Saved artifact with fallback name/description after processing error');
913
- }
914
- }
915
- catch (fallbackError) {
916
- logger.error({
917
- sessionId: this.sessionId,
918
- artifactId: artifactData.artifactId,
919
- error: fallbackError instanceof Error ? fallbackError.message : 'Unknown error',
920
- }, 'Failed to save artifact even with fallback');
921
- }
922
- }
923
- finally {
924
- // Always end the main span
925
- span.end();
926
- }
927
- });
928
- }
929
- }
930
- /**
931
- * Manages GraphSession instances for message-level tracking
932
- */
933
- export class GraphSessionManager {
934
- sessions = new Map();
935
- /**
936
- * Create a new session for a message
937
- */
938
- createSession(messageId, graphId, tenantId, projectId) {
939
- const sessionId = messageId; // Use messageId directly as sessionId
940
- const session = new GraphSession(sessionId, messageId, graphId, tenantId, projectId);
941
- this.sessions.set(sessionId, session);
942
- logger.info({ sessionId, messageId, graphId, tenantId, projectId }, 'GraphSession created');
943
- return sessionId;
944
- }
945
- /**
946
- * Initialize status updates for a session
947
- */
948
- initializeStatusUpdates(sessionId, config, summarizerModel) {
949
- const session = this.sessions.get(sessionId);
950
- if (session) {
951
- session.initializeStatusUpdates(config, summarizerModel);
952
- }
953
- else {
954
- logger.error({
955
- sessionId,
956
- availableSessions: Array.from(this.sessions.keys()),
957
- }, 'Session not found for status updates initialization');
958
- }
959
- }
960
- /**
961
- * Get an existing session
962
- */
963
- getSession(sessionId) {
964
- return this.sessions.get(sessionId) || null;
965
- }
966
- /**
967
- * Record an event in a session
968
- */
969
- recordEvent(sessionId, eventType, agentId, data) {
970
- const session = this.sessions.get(sessionId);
971
- if (!session) {
972
- logger.warn({ sessionId }, 'Attempted to record event in non-existent session');
973
- return;
974
- }
975
- session.recordEvent(eventType, agentId, data);
976
- }
977
- /**
978
- * End a session and return the final event data
979
- */
980
- endSession(sessionId) {
981
- const session = this.sessions.get(sessionId);
982
- if (!session) {
983
- logger.warn({ sessionId }, 'Attempted to end non-existent session');
984
- return [];
985
- }
986
- const events = session.getEvents();
987
- const summary = session.getSummary();
988
- logger.info({ sessionId, summary }, 'GraphSession ended');
989
- // Clean up session resources including status update timers
990
- session.cleanup();
991
- // Clean up the session from memory
992
- this.sessions.delete(sessionId);
993
- return events;
994
- }
995
- /**
996
- * Set text streaming state for a session
997
- */
998
- setTextStreaming(sessionId, isStreaming) {
999
- const session = this.sessions.get(sessionId);
1000
- if (session) {
1001
- session.setTextStreaming(isStreaming);
1002
- }
1003
- }
1004
- /**
1005
- * Get summary of all active sessions
1006
- */
1007
- getActiveSessions() {
1008
- return Array.from(this.sessions.values()).map((session) => ({
1009
- sessionId: session.sessionId,
1010
- messageId: session.messageId,
1011
- eventCount: session.getEvents().length,
1012
- }));
1013
- }
1014
- }
1015
- // Global instance
1016
- export const graphSessionManager = new GraphSessionManager();