@inkeep/agents-run-api 0.1.2 → 0.1.6

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 (163) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +1 -1
  3. package/SUPPLEMENTAL_TERMS.md +40 -0
  4. package/dist/chunk-P6IQZWFC.js +239 -0
  5. package/dist/conversations-EUPRCMQZ.js +1 -0
  6. package/dist/index.cjs +8992 -0
  7. package/dist/index.d.cts +15 -0
  8. package/dist/index.d.ts +13 -21
  9. package/dist/index.js +8644 -27
  10. package/package.json +14 -11
  11. package/templates/v1/artifact.xml +7 -0
  12. package/templates/v1/data-component.xml +9 -0
  13. package/templates/v1/system-prompt.xml +52 -0
  14. package/templates/v1/thinking-preparation.xml +34 -0
  15. package/templates/v1/tool.xml +12 -0
  16. package/dist/AgentExecutionServer.d.ts +0 -28
  17. package/dist/AgentExecutionServer.d.ts.map +0 -1
  18. package/dist/AgentExecutionServer.js +0 -41
  19. package/dist/__tests__/setup.d.ts +0 -4
  20. package/dist/__tests__/setup.d.ts.map +0 -1
  21. package/dist/__tests__/setup.js +0 -50
  22. package/dist/__tests__/utils/testProject.d.ts +0 -18
  23. package/dist/__tests__/utils/testProject.d.ts.map +0 -1
  24. package/dist/__tests__/utils/testProject.js +0 -26
  25. package/dist/__tests__/utils/testRequest.d.ts +0 -8
  26. package/dist/__tests__/utils/testRequest.d.ts.map +0 -1
  27. package/dist/__tests__/utils/testRequest.js +0 -32
  28. package/dist/__tests__/utils/testTenant.d.ts +0 -64
  29. package/dist/__tests__/utils/testTenant.d.ts.map +0 -1
  30. package/dist/__tests__/utils/testTenant.js +0 -71
  31. package/dist/a2a/client.d.ts +0 -182
  32. package/dist/a2a/client.d.ts.map +0 -1
  33. package/dist/a2a/client.js +0 -645
  34. package/dist/a2a/handlers.d.ts +0 -4
  35. package/dist/a2a/handlers.d.ts.map +0 -1
  36. package/dist/a2a/handlers.js +0 -657
  37. package/dist/a2a/transfer.d.ts +0 -18
  38. package/dist/a2a/transfer.d.ts.map +0 -1
  39. package/dist/a2a/transfer.js +0 -22
  40. package/dist/a2a/types.d.ts +0 -63
  41. package/dist/a2a/types.d.ts.map +0 -1
  42. package/dist/a2a/types.js +0 -1
  43. package/dist/agents/Agent.d.ts +0 -154
  44. package/dist/agents/Agent.d.ts.map +0 -1
  45. package/dist/agents/Agent.js +0 -1107
  46. package/dist/agents/ModelFactory.d.ts +0 -62
  47. package/dist/agents/ModelFactory.d.ts.map +0 -1
  48. package/dist/agents/ModelFactory.js +0 -208
  49. package/dist/agents/SystemPromptBuilder.d.ts +0 -14
  50. package/dist/agents/SystemPromptBuilder.d.ts.map +0 -1
  51. package/dist/agents/SystemPromptBuilder.js +0 -62
  52. package/dist/agents/ToolSessionManager.d.ts +0 -61
  53. package/dist/agents/ToolSessionManager.d.ts.map +0 -1
  54. package/dist/agents/ToolSessionManager.js +0 -143
  55. package/dist/agents/artifactTools.d.ts +0 -30
  56. package/dist/agents/artifactTools.d.ts.map +0 -1
  57. package/dist/agents/artifactTools.js +0 -463
  58. package/dist/agents/generateTaskHandler.d.ts +0 -41
  59. package/dist/agents/generateTaskHandler.d.ts.map +0 -1
  60. package/dist/agents/generateTaskHandler.js +0 -350
  61. package/dist/agents/relationTools.d.ts +0 -35
  62. package/dist/agents/relationTools.d.ts.map +0 -1
  63. package/dist/agents/relationTools.js +0 -244
  64. package/dist/agents/types.d.ts +0 -23
  65. package/dist/agents/types.d.ts.map +0 -1
  66. package/dist/agents/types.js +0 -1
  67. package/dist/agents/versions/V1Config.d.ts +0 -21
  68. package/dist/agents/versions/V1Config.d.ts.map +0 -1
  69. package/dist/agents/versions/V1Config.js +0 -285
  70. package/dist/app.d.ts +0 -12
  71. package/dist/app.d.ts.map +0 -1
  72. package/dist/app.js +0 -202
  73. package/dist/data/agentGraph.d.ts +0 -4
  74. package/dist/data/agentGraph.d.ts.map +0 -1
  75. package/dist/data/agentGraph.js +0 -73
  76. package/dist/data/agents.d.ts +0 -4
  77. package/dist/data/agents.d.ts.map +0 -1
  78. package/dist/data/agents.js +0 -73
  79. package/dist/data/conversations.d.ts +0 -59
  80. package/dist/data/conversations.d.ts.map +0 -1
  81. package/dist/data/conversations.js +0 -216
  82. package/dist/data/db/clean.d.ts +0 -6
  83. package/dist/data/db/clean.d.ts.map +0 -1
  84. package/dist/data/db/clean.js +0 -77
  85. package/dist/data/db/dbClient.d.ts +0 -3
  86. package/dist/data/db/dbClient.d.ts.map +0 -1
  87. package/dist/data/db/dbClient.js +0 -13
  88. package/dist/env.d.ts +0 -45
  89. package/dist/env.d.ts.map +0 -1
  90. package/dist/env.js +0 -64
  91. package/dist/handlers/executionHandler.d.ts +0 -36
  92. package/dist/handlers/executionHandler.d.ts.map +0 -1
  93. package/dist/handlers/executionHandler.js +0 -399
  94. package/dist/index.d.ts.map +0 -1
  95. package/dist/instrumentation.d.ts +0 -13
  96. package/dist/instrumentation.d.ts.map +0 -1
  97. package/dist/instrumentation.js +0 -66
  98. package/dist/logger.d.ts +0 -4
  99. package/dist/logger.d.ts.map +0 -1
  100. package/dist/logger.js +0 -32
  101. package/dist/middleware/api-key-auth.d.ts +0 -22
  102. package/dist/middleware/api-key-auth.d.ts.map +0 -1
  103. package/dist/middleware/api-key-auth.js +0 -139
  104. package/dist/middleware/index.d.ts +0 -2
  105. package/dist/middleware/index.d.ts.map +0 -1
  106. package/dist/middleware/index.js +0 -1
  107. package/dist/openapi.d.ts +0 -2
  108. package/dist/openapi.d.ts.map +0 -1
  109. package/dist/openapi.js +0 -36
  110. package/dist/routes/agents.d.ts +0 -10
  111. package/dist/routes/agents.d.ts.map +0 -1
  112. package/dist/routes/agents.js +0 -158
  113. package/dist/routes/chat.d.ts +0 -4
  114. package/dist/routes/chat.d.ts.map +0 -1
  115. package/dist/routes/chat.js +0 -308
  116. package/dist/routes/chatDataStream.d.ts +0 -4
  117. package/dist/routes/chatDataStream.d.ts.map +0 -1
  118. package/dist/routes/chatDataStream.js +0 -179
  119. package/dist/routes/mcp.d.ts +0 -4
  120. package/dist/routes/mcp.d.ts.map +0 -1
  121. package/dist/routes/mcp.js +0 -500
  122. package/dist/server.d.ts +0 -5
  123. package/dist/server.d.ts.map +0 -1
  124. package/dist/server.js +0 -61
  125. package/dist/tracer.d.ts +0 -24
  126. package/dist/tracer.d.ts.map +0 -1
  127. package/dist/tracer.js +0 -106
  128. package/dist/types/chat.d.ts +0 -25
  129. package/dist/types/chat.d.ts.map +0 -1
  130. package/dist/types/chat.js +0 -1
  131. package/dist/types/execution-context.d.ts +0 -14
  132. package/dist/types/execution-context.d.ts.map +0 -1
  133. package/dist/types/execution-context.js +0 -14
  134. package/dist/utils/agent-operations.d.ts +0 -92
  135. package/dist/utils/agent-operations.d.ts.map +0 -1
  136. package/dist/utils/agent-operations.js +0 -78
  137. package/dist/utils/artifact-component-schema.d.ts +0 -29
  138. package/dist/utils/artifact-component-schema.d.ts.map +0 -1
  139. package/dist/utils/artifact-component-schema.js +0 -119
  140. package/dist/utils/artifact-parser.d.ts +0 -71
  141. package/dist/utils/artifact-parser.d.ts.map +0 -1
  142. package/dist/utils/artifact-parser.js +0 -251
  143. package/dist/utils/cleanup.d.ts +0 -19
  144. package/dist/utils/cleanup.d.ts.map +0 -1
  145. package/dist/utils/cleanup.js +0 -66
  146. package/dist/utils/data-component-schema.d.ts +0 -6
  147. package/dist/utils/data-component-schema.d.ts.map +0 -1
  148. package/dist/utils/data-component-schema.js +0 -43
  149. package/dist/utils/graph-session.d.ts +0 -200
  150. package/dist/utils/graph-session.d.ts.map +0 -1
  151. package/dist/utils/graph-session.js +0 -1012
  152. package/dist/utils/incremental-stream-parser.d.ts +0 -57
  153. package/dist/utils/incremental-stream-parser.d.ts.map +0 -1
  154. package/dist/utils/incremental-stream-parser.js +0 -287
  155. package/dist/utils/response-formatter.d.ts +0 -27
  156. package/dist/utils/response-formatter.d.ts.map +0 -1
  157. package/dist/utils/response-formatter.js +0 -160
  158. package/dist/utils/stream-helpers.d.ts +0 -174
  159. package/dist/utils/stream-helpers.d.ts.map +0 -1
  160. package/dist/utils/stream-helpers.js +0 -466
  161. package/dist/utils/stream-registry.d.ts +0 -18
  162. package/dist/utils/stream-registry.d.ts.map +0 -1
  163. package/dist/utils/stream-registry.js +0 -33
@@ -1,1012 +0,0 @@
1
- import { generateText, generateObject } from 'ai';
2
- import { z } from 'zod';
3
- import { SpanStatusCode } from '@opentelemetry/api';
4
- import { ModelFactory } from '../agents/ModelFactory.js';
5
- import { getLogger } from '../logger.js';
6
- import { getGlobalTracer, createSpanName, handleSpanError } from '../tracer.js';
7
- import { statusUpdateOp } from './agent-operations.js';
8
- import { getStreamHelper } from './stream-registry.js';
9
- import { getFormattedConversationHistory } from '../data/conversations.js';
10
- import dbClient from '../data/db/dbClient.js';
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()) ? summarizerModel : { model: 'openai/gpt-4.1-nano-2025-04-14' });
430
- const { text } = await generateText({
431
- model,
432
- prompt,
433
- experimental_telemetry: {
434
- isEnabled: true,
435
- functionId: `status_update_${this.sessionId}`,
436
- recordInputs: true,
437
- recordOutputs: true,
438
- metadata: {
439
- operation: 'progress_summary_generation',
440
- sessionId: this.sessionId,
441
- },
442
- },
443
- });
444
- span.setAttributes({
445
- 'summary.length': text.trim().length,
446
- 'user_activities.count': userVisibleActivities.length,
447
- });
448
- span.setStatus({ code: SpanStatusCode.OK });
449
- return text.trim();
450
- }
451
- catch (error) {
452
- handleSpanError(span, error);
453
- logger.error({ error }, 'Failed to generate summary, using fallback');
454
- return this.generateFallbackSummary(newEvents, elapsedTime);
455
- }
456
- finally {
457
- span.end();
458
- }
459
- });
460
- }
461
- /**
462
- * Generate structured status update using configured data components
463
- */
464
- async generateStructuredStatusUpdate(newEvents, elapsedTime, statusComponents, summarizerModel, previousSummaries = []) {
465
- return tracer.startActiveSpan(createSpanName('graph_session.generate_structured_update'), {
466
- attributes: {
467
- 'graph_session.id': this.sessionId,
468
- 'events.count': newEvents.length,
469
- 'elapsed_time.seconds': Math.round(elapsedTime / 1000),
470
- 'llm.model': summarizerModel?.model || 'openai/gpt-4.1-nano-2025-04-14',
471
- 'status_components.count': statusComponents.length,
472
- 'previous_summaries.count': previousSummaries.length,
473
- },
474
- }, async (span) => {
475
- try {
476
- // Extract user-visible activities
477
- const userVisibleActivities = this.extractUserVisibleActivities(newEvents);
478
- // Get conversation history to understand user's context and question
479
- let conversationContext = '';
480
- if (this.tenantId && this.projectId) {
481
- try {
482
- const conversationHistory = await getFormattedConversationHistory({
483
- tenantId: this.tenantId,
484
- projectId: this.projectId,
485
- conversationId: this.sessionId,
486
- options: {
487
- limit: 10, // Get recent conversation context
488
- maxOutputTokens: 2000,
489
- },
490
- filters: {},
491
- });
492
- conversationContext = conversationHistory.trim()
493
- ? `\nUser's Question/Context:\n${conversationHistory}\n`
494
- : '';
495
- }
496
- catch (error) {
497
- logger.warn({ sessionId: this.sessionId, error }, 'Failed to fetch conversation history for structured status update');
498
- }
499
- }
500
- const previousSummaryContext = previousSummaries.length > 0
501
- ? `\nPrevious updates sent to user:\n${previousSummaries.map((s, i) => `${i + 1}. ${s}`).join('\n')}\n`
502
- : '';
503
- // Build schema for data components and no_relevant_updates option
504
- const selectionSchema = z.object(Object.fromEntries([
505
- // Add no_relevant_updates schema
506
- [
507
- 'no_relevant_updates',
508
- z
509
- .object({
510
- no_updates: z.boolean().default(true),
511
- })
512
- .optional()
513
- .describe('Use when nothing substantially new to report. Should only use on its own.'),
514
- ],
515
- // Add all other component schemas
516
- ...statusComponents.map((component) => [
517
- component.id,
518
- this.buildZodSchema(component.schema)
519
- .optional()
520
- .describe(component.description || component.name),
521
- ]),
522
- ]));
523
- // Use custom prompt if provided, otherwise use default
524
- const basePrompt = `Generate status updates for relevant components based on what the user has asked for.${conversationContext}${previousSummaries.length > 0 ? `\n${previousSummaryContext}` : ''}
525
-
526
- Activities:\n${userVisibleActivities.join('\n') || 'No New Activities'}
527
-
528
- Available components: no_relevant_updates, ${statusComponents.map((c) => c.id).join(', ')}
529
-
530
- Rules:
531
- - Fill in data for relevant components only
532
- - Use 'no_relevant_updates' if nothing substantially new to report
533
- - Never repeat previous values
534
- - You are ONE AI (no agents/delegations)
535
-
536
- ${this.statusUpdateState?.config.prompt?.trim() || ''}`;
537
- const prompt = basePrompt;
538
- const model = ModelFactory.createModel((summarizerModel && summarizerModel.model?.trim()) ? summarizerModel : { model: 'openai/gpt-4.1-nano-2025-04-14' });
539
- const { object } = await generateObject({
540
- model,
541
- prompt,
542
- schema: selectionSchema,
543
- experimental_telemetry: {
544
- isEnabled: true,
545
- functionId: `structured_update_${this.sessionId}`,
546
- recordInputs: true,
547
- recordOutputs: true,
548
- metadata: {
549
- operation: 'structured_status_update_generation',
550
- sessionId: this.sessionId,
551
- },
552
- },
553
- });
554
- const result = object;
555
- // Extract components that have data (skip no_relevant_updates and empty components)
556
- const operations = [];
557
- for (const [componentId, data] of Object.entries(result)) {
558
- // Skip no_relevant_updates - we don't send any operation for this
559
- if (componentId === 'no_relevant_updates') {
560
- continue;
561
- }
562
- // Only include components that have actual data
563
- if (data && typeof data === 'object' && Object.keys(data).length > 0) {
564
- operations.push({
565
- type: componentId,
566
- data: data,
567
- });
568
- }
569
- }
570
- span.setAttributes({
571
- 'operations.count': operations.length,
572
- 'user_activities.count': userVisibleActivities.length,
573
- 'result_keys.count': Object.keys(result).length,
574
- });
575
- span.setStatus({ code: SpanStatusCode.OK });
576
- return { operations };
577
- }
578
- catch (error) {
579
- handleSpanError(span, error);
580
- logger.error({ error }, 'Failed to generate structured update, using fallback');
581
- return { operations: [] };
582
- }
583
- finally {
584
- span.end();
585
- }
586
- });
587
- }
588
- /**
589
- * Build Zod schema from JSON schema configuration
590
- */
591
- buildZodSchema(jsonSchema) {
592
- const properties = {};
593
- for (const [key, value] of Object.entries(jsonSchema.properties)) {
594
- // Simple type mapping - can be expanded as needed
595
- if (value.type === 'string') {
596
- properties[key] = z.string();
597
- }
598
- else if (value.type === 'number') {
599
- properties[key] = z.number();
600
- }
601
- else if (value.type === 'boolean') {
602
- properties[key] = z.boolean();
603
- }
604
- else if (value.type === 'array') {
605
- properties[key] = z.array(z.any());
606
- }
607
- else if (value.type === 'object') {
608
- properties[key] = z.record(z.string(), z.any());
609
- }
610
- else {
611
- properties[key] = z.any();
612
- }
613
- // Make optional if not in required array
614
- if (!jsonSchema.required?.includes(key)) {
615
- properties[key] = properties[key].optional();
616
- }
617
- }
618
- return z.object(properties);
619
- }
620
- /**
621
- * Extract user-visible activities with rich formatting and complete information
622
- */
623
- extractUserVisibleActivities(events) {
624
- const activities = [];
625
- for (const event of events) {
626
- switch (event.eventType) {
627
- case 'tool_execution': {
628
- const data = event.data;
629
- const resultStr = JSON.stringify(data.result);
630
- activities.push(`🔧 **${data.toolName}** ${data.duration ? `(${data.duration}ms)` : ''}\n` +
631
- ` 📥 Input: ${JSON.stringify(data.args)}\n` +
632
- ` 📤 Output: ${resultStr}`);
633
- break;
634
- }
635
- case 'transfer': {
636
- const data = event.data;
637
- activities.push(`🔄 **Transfer**: ${data.fromAgent} → ${data.targetAgent}\n` +
638
- ` ${data.reason ? `Reason: ${data.reason}` : 'Control transfer'}\n` +
639
- ` ${data.context ? `Context: ${JSON.stringify(data.context, null, 2)}` : ''}`);
640
- break;
641
- }
642
- case 'delegation_sent': {
643
- const data = event.data;
644
- activities.push(`📤 **Delegation Sent** [${data.delegationId}]: ${data.fromAgent} → ${data.targetAgent}\n` +
645
- ` Task: ${data.taskDescription}\n` +
646
- ` ${data.context ? `Context: ${JSON.stringify(data.context, null, 2)}` : ''}`);
647
- break;
648
- }
649
- case 'delegation_returned': {
650
- const data = event.data;
651
- activities.push(`📥 **Delegation Returned** [${data.delegationId}]: ${data.fromAgent} ← ${data.targetAgent}\n` +
652
- ` Result: ${JSON.stringify(data.result, null, 2)}`);
653
- break;
654
- }
655
- case 'artifact_saved': {
656
- const data = event.data;
657
- activities.push(`💾 **Artifact Saved**: ${data.artifactType}\n` +
658
- ` ID: ${data.artifactId}\n` +
659
- ` Task: ${data.taskId}\n` +
660
- ` ${data.summaryData ? `Summary: ${data.summaryData}` : ''}\n` +
661
- ` ${data.fullData ? `Full Data: ${data.fullData}` : ''}`);
662
- break;
663
- }
664
- case 'agent_generate': {
665
- const data = event.data;
666
- if (data.generationType !== 'artifact_name_description') {
667
- activities.push(`⚙️ **Generation**: ${data.generationType}\n` +
668
- ` Full Details: ${JSON.stringify(data.parts, null, 2)}`);
669
- }
670
- break;
671
- }
672
- default: {
673
- activities.push(`📋 **${event.eventType}**: ${JSON.stringify(event.data, null, 2)}`);
674
- break;
675
- }
676
- }
677
- }
678
- return activities;
679
- }
680
- /**
681
- * Generate fallback summary when LLM fails
682
- */
683
- generateFallbackSummary(events, elapsedTime) {
684
- const timeStr = Math.round(elapsedTime / 1000);
685
- const toolCalls = events.filter((e) => e.eventType === 'tool_execution').length;
686
- const artifacts = events.filter((e) => e.eventType === 'artifact_saved').length;
687
- if (artifacts > 0) {
688
- return `Generated ${artifacts} result${artifacts > 1 ? 's' : ''} so far (${timeStr}s elapsed)`;
689
- }
690
- else if (toolCalls > 0) {
691
- return `Used ${toolCalls} tool${toolCalls > 1 ? 's' : ''} to gather information (${timeStr}s elapsed)`;
692
- }
693
- else {
694
- return `Processing your request... (${timeStr}s elapsed)`;
695
- }
696
- }
697
- /**
698
- * Process a single artifact to generate name and description using conversation context
699
- */
700
- async processArtifact(artifactData) {
701
- return tracer.startActiveSpan(createSpanName('graph_session.process_artifact'), {
702
- attributes: {
703
- 'graph_session.id': this.sessionId,
704
- 'artifact.id': artifactData.artifactId,
705
- 'artifact.type': artifactData.artifactType || 'unknown',
706
- 'tenant.id': artifactData.tenantId || 'unknown',
707
- 'project.id': artifactData.projectId || 'unknown',
708
- 'context.id': artifactData.contextId || 'unknown',
709
- has_tenant_id: !!artifactData.tenantId,
710
- has_project_id: !!artifactData.projectId,
711
- has_context_id: !!artifactData.contextId,
712
- has_metadata: !!artifactData.metadata,
713
- tool_call_id: artifactData.metadata?.toolCallId || 'missing',
714
- pending_generation: !!artifactData.pendingGeneration,
715
- },
716
- }, async (span) => {
717
- try {
718
- // We need tenantId, projectId, and contextId to get conversation history
719
- if (!artifactData.tenantId || !artifactData.projectId || !artifactData.contextId) {
720
- span.setAttributes({
721
- 'validation.failed': true,
722
- missing_tenant_id: !artifactData.tenantId,
723
- missing_project_id: !artifactData.projectId,
724
- missing_context_id: !artifactData.contextId,
725
- });
726
- throw new Error('Missing required session info (tenantId, projectId, or contextId) for artifact processing');
727
- }
728
- span.setAttributes({ 'validation.passed': true });
729
- // Get conversation history for context
730
- span.setAttributes({ step: 'importing_conversations' });
731
- const { getFormattedConversationHistory } = await import('../data/conversations.js');
732
- span.setAttributes({ step: 'fetching_conversation_history' });
733
- const conversationHistory = await getFormattedConversationHistory({
734
- tenantId: artifactData.tenantId,
735
- projectId: artifactData.projectId,
736
- conversationId: artifactData.contextId,
737
- options: {
738
- limit: 10, // Only need recent context
739
- includeInternal: false, // Focus on user messages
740
- messageTypes: ['chat'],
741
- },
742
- });
743
- span.setAttributes({
744
- step: 'conversation_history_fetched',
745
- 'conversation_history.length': conversationHistory.length,
746
- });
747
- // Find the specific tool call that generated this artifact
748
- // Now toolId and toolCallId should be the same since we use AI SDK's toolCallId consistently
749
- const toolCallEvent = this.events.find((event) => event.eventType === 'tool_execution' &&
750
- event.data &&
751
- 'toolId' in event.data &&
752
- event.data.toolId === artifactData.metadata?.toolCallId);
753
- const toolResultEvent = this.events.find((event) => event.eventType === 'tool_execution' &&
754
- event.data &&
755
- 'toolId' in event.data &&
756
- event.data.toolId === artifactData.metadata?.toolCallId);
757
- // Prepare context for name/description generation
758
- const toolContext = toolCallEvent
759
- ? {
760
- toolName: toolCallEvent.data.toolName,
761
- args: toolCallEvent.data.args,
762
- }
763
- : null;
764
- const toolResult = toolResultEvent
765
- ? {
766
- result: toolResultEvent.data.result,
767
- }
768
- : null;
769
- const prompt = `Name this artifact (max 50 chars) and describe it (max 150 chars).
770
-
771
- Context: ${conversationHistory?.slice(-200) || 'Processing'}
772
- Type: ${artifactData.artifactType || 'data'}
773
- Summary: ${JSON.stringify(artifactData.summaryProps, null, 2)}
774
- Full: ${JSON.stringify(artifactData.fullProps, null, 2)}
775
-
776
- Make it specific and relevant.`;
777
- const model = ModelFactory.createModel(this.statusUpdateState?.summarizerModel || { model: 'openai/gpt-4.1-nano-2025-04-14' });
778
- const schema = z.object({
779
- name: z.string().max(50).describe('Concise, descriptive name for the artifact'),
780
- description: z
781
- .string()
782
- .max(150)
783
- .describe("Brief description of the artifact's relevance to the user's question"),
784
- });
785
- // Add nested span for LLM generation
786
- const { object: result } = await tracer.startActiveSpan(createSpanName('graph_session.generate_artifact_metadata'), {
787
- attributes: {
788
- 'llm.model': this.statusUpdateState?.summarizerModel?.model ||
789
- 'openai/gpt-4.1-nano-2025-04-14',
790
- 'llm.operation': 'generate_object',
791
- 'artifact.id': artifactData.artifactId,
792
- 'prompt.length': prompt.length,
793
- },
794
- }, async (generationSpan) => {
795
- try {
796
- const result = await generateObject({
797
- model,
798
- prompt,
799
- schema,
800
- experimental_telemetry: {
801
- isEnabled: true,
802
- functionId: `artifact_processing_${artifactData.artifactId}`,
803
- recordInputs: true,
804
- recordOutputs: true,
805
- metadata: {
806
- operation: 'artifact_name_description_generation',
807
- sessionId: this.sessionId,
808
- },
809
- },
810
- });
811
- generationSpan.setAttributes({
812
- 'generation.name_length': result.object.name.length,
813
- 'generation.description_length': result.object.description.length,
814
- });
815
- generationSpan.setStatus({ code: SpanStatusCode.OK });
816
- return result;
817
- }
818
- catch (error) {
819
- handleSpanError(generationSpan, error);
820
- throw error;
821
- }
822
- finally {
823
- generationSpan.end();
824
- }
825
- });
826
- // Now save the artifact to the ledger with the generated name and description
827
- const { addLedgerArtifacts } = await import('@inkeep/agents-core');
828
- const artifactToSave = {
829
- artifactId: artifactData.artifactId,
830
- name: result.name,
831
- description: result.description,
832
- type: 'source',
833
- taskId: artifactData.taskId,
834
- parts: [
835
- {
836
- kind: 'data',
837
- data: {
838
- summary: artifactData.summaryProps || {},
839
- full: artifactData.fullProps || {},
840
- },
841
- },
842
- ],
843
- metadata: artifactData.metadata || {},
844
- };
845
- await addLedgerArtifacts(dbClient)({
846
- scopes: {
847
- tenantId: artifactData.tenantId,
848
- projectId: artifactData.projectId,
849
- },
850
- contextId: artifactData.contextId,
851
- taskId: artifactData.taskId,
852
- artifacts: [artifactToSave],
853
- });
854
- logger.info({
855
- sessionId: this.sessionId,
856
- artifactId: artifactData.artifactId,
857
- name: result.name,
858
- description: result.description,
859
- }, 'Artifact successfully saved to ledger with generated name and description');
860
- // Mark main span as successful
861
- span.setAttributes({
862
- 'artifact.name': result.name,
863
- 'artifact.description': result.description,
864
- 'processing.success': true,
865
- });
866
- span.setStatus({ code: SpanStatusCode.OK });
867
- }
868
- catch (error) {
869
- // Handle span error
870
- handleSpanError(span, error);
871
- logger.error({
872
- sessionId: this.sessionId,
873
- artifactId: artifactData.artifactId,
874
- error: error instanceof Error ? error.message : 'Unknown error',
875
- }, 'Failed to process artifact');
876
- // Fallback: save artifact with basic info
877
- try {
878
- const { addLedgerArtifacts } = await import('@inkeep/agents-core');
879
- const fallbackArtifact = {
880
- artifactId: artifactData.artifactId,
881
- name: `Artifact ${artifactData.artifactId.substring(0, 8)}`,
882
- description: `${artifactData.artifactType || 'Data'} from ${artifactData.metadata?.toolName || 'tool results'}`,
883
- taskId: artifactData.taskId,
884
- parts: [
885
- {
886
- kind: 'data',
887
- data: {
888
- summary: artifactData.summaryProps || {},
889
- full: artifactData.fullProps || {},
890
- },
891
- },
892
- ],
893
- metadata: artifactData.metadata || {},
894
- };
895
- if (artifactData.tenantId && artifactData.projectId) {
896
- await addLedgerArtifacts(dbClient)({
897
- scopes: {
898
- tenantId: artifactData.tenantId,
899
- projectId: artifactData.projectId,
900
- },
901
- contextId: artifactData.contextId || 'unknown',
902
- taskId: artifactData.taskId,
903
- artifacts: [fallbackArtifact],
904
- });
905
- logger.info({
906
- sessionId: this.sessionId,
907
- artifactId: artifactData.artifactId,
908
- }, 'Saved artifact with fallback name/description after processing error');
909
- }
910
- }
911
- catch (fallbackError) {
912
- logger.error({
913
- sessionId: this.sessionId,
914
- artifactId: artifactData.artifactId,
915
- error: fallbackError instanceof Error ? fallbackError.message : 'Unknown error',
916
- }, 'Failed to save artifact even with fallback');
917
- }
918
- }
919
- finally {
920
- // Always end the main span
921
- span.end();
922
- }
923
- });
924
- }
925
- }
926
- /**
927
- * Manages GraphSession instances for message-level tracking
928
- */
929
- export class GraphSessionManager {
930
- sessions = new Map();
931
- /**
932
- * Create a new session for a message
933
- */
934
- createSession(messageId, graphId, tenantId, projectId) {
935
- const sessionId = messageId; // Use messageId directly as sessionId
936
- const session = new GraphSession(sessionId, messageId, graphId, tenantId, projectId);
937
- this.sessions.set(sessionId, session);
938
- logger.info({ sessionId, messageId, graphId, tenantId, projectId }, 'GraphSession created');
939
- return sessionId;
940
- }
941
- /**
942
- * Initialize status updates for a session
943
- */
944
- initializeStatusUpdates(sessionId, config, summarizerModel) {
945
- const session = this.sessions.get(sessionId);
946
- if (session) {
947
- session.initializeStatusUpdates(config, summarizerModel);
948
- }
949
- else {
950
- logger.error({
951
- sessionId,
952
- availableSessions: Array.from(this.sessions.keys()),
953
- }, 'Session not found for status updates initialization');
954
- }
955
- }
956
- /**
957
- * Get an existing session
958
- */
959
- getSession(sessionId) {
960
- return this.sessions.get(sessionId) || null;
961
- }
962
- /**
963
- * Record an event in a session
964
- */
965
- recordEvent(sessionId, eventType, agentId, data) {
966
- const session = this.sessions.get(sessionId);
967
- if (!session) {
968
- logger.warn({ sessionId }, 'Attempted to record event in non-existent session');
969
- return;
970
- }
971
- session.recordEvent(eventType, agentId, data);
972
- }
973
- /**
974
- * End a session and return the final event data
975
- */
976
- endSession(sessionId) {
977
- const session = this.sessions.get(sessionId);
978
- if (!session) {
979
- logger.warn({ sessionId }, 'Attempted to end non-existent session');
980
- return [];
981
- }
982
- const events = session.getEvents();
983
- const summary = session.getSummary();
984
- logger.info({ sessionId, summary }, 'GraphSession ended');
985
- // Clean up session resources including status update timers
986
- session.cleanup();
987
- // Clean up the session from memory
988
- this.sessions.delete(sessionId);
989
- return events;
990
- }
991
- /**
992
- * Set text streaming state for a session
993
- */
994
- setTextStreaming(sessionId, isStreaming) {
995
- const session = this.sessions.get(sessionId);
996
- if (session) {
997
- session.setTextStreaming(isStreaming);
998
- }
999
- }
1000
- /**
1001
- * Get summary of all active sessions
1002
- */
1003
- getActiveSessions() {
1004
- return Array.from(this.sessions.values()).map((session) => ({
1005
- sessionId: session.sessionId,
1006
- messageId: session.messageId,
1007
- eventCount: session.getEvents().length,
1008
- }));
1009
- }
1010
- }
1011
- // Global instance
1012
- export const graphSessionManager = new GraphSessionManager();