@dxos/assistant 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29

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 (152) hide show
  1. package/README.md +1 -0
  2. package/dist/lib/neutral/extraction/index.mjs +202 -0
  3. package/dist/lib/neutral/extraction/index.mjs.map +7 -0
  4. package/dist/lib/neutral/index.mjs +1736 -0
  5. package/dist/lib/neutral/index.mjs.map +7 -0
  6. package/dist/lib/neutral/meta.json +1 -0
  7. package/dist/types/src/conversation/context.d.ts +124 -64
  8. package/dist/types/src/conversation/context.d.ts.map +1 -1
  9. package/dist/types/src/conversation/context.test.d.ts +2 -0
  10. package/dist/types/src/conversation/context.test.d.ts.map +1 -0
  11. package/dist/types/src/conversation/index.d.ts +1 -1
  12. package/dist/types/src/conversation/index.d.ts.map +1 -1
  13. package/dist/types/src/conversation/session.d.ts +73 -0
  14. package/dist/types/src/conversation/session.d.ts.map +1 -0
  15. package/dist/types/src/diff.d.ts +3 -1
  16. package/dist/types/src/diff.d.ts.map +1 -1
  17. package/dist/types/src/errors.d.ts +40 -48
  18. package/dist/types/src/errors.d.ts.map +1 -1
  19. package/dist/types/src/execution-graph.d.ts +85 -0
  20. package/dist/types/src/execution-graph.d.ts.map +1 -0
  21. package/dist/types/src/execution-graph.test.d.ts +2 -0
  22. package/dist/types/src/execution-graph.test.d.ts.map +1 -0
  23. package/dist/types/src/extraction/extraction-ner-function/index.d.ts +0 -1
  24. package/dist/types/src/extraction/extraction-ner-function/index.d.ts.map +1 -1
  25. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
  26. package/dist/types/src/extraction/extraction.d.ts +1066 -303
  27. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  28. package/dist/types/src/extraction/index.d.ts +0 -1
  29. package/dist/types/src/extraction/index.d.ts.map +1 -1
  30. package/dist/types/src/extraction/quotes.d.ts +3 -3
  31. package/dist/types/src/extraction/quotes.d.ts.map +1 -1
  32. package/dist/types/src/functions/services.d.ts +15 -5
  33. package/dist/types/src/functions/services.d.ts.map +1 -1
  34. package/dist/types/src/index.d.ts +3 -1
  35. package/dist/types/src/index.d.ts.map +1 -1
  36. package/dist/types/src/session/artifact-diff.d.ts +2 -2
  37. package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
  38. package/dist/types/src/session/error-handling.d.ts +2 -2
  39. package/dist/types/src/session/error-handling.d.ts.map +1 -1
  40. package/dist/types/src/session/format.d.ts +8 -175
  41. package/dist/types/src/session/format.d.ts.map +1 -1
  42. package/dist/types/src/session/index.d.ts +2 -1
  43. package/dist/types/src/session/index.d.ts.map +1 -1
  44. package/dist/types/src/session/observer.d.ts +7 -6
  45. package/dist/types/src/session/observer.d.ts.map +1 -1
  46. package/dist/types/src/session/request.d.ts +104 -0
  47. package/dist/types/src/session/request.d.ts.map +1 -0
  48. package/dist/types/src/session/toolkit.d.ts +11 -9
  49. package/dist/types/src/session/toolkit.d.ts.map +1 -1
  50. package/dist/types/src/session/version-pin.d.ts +4 -4
  51. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  52. package/dist/types/src/templates/system.d.ts +1 -2
  53. package/dist/types/src/templates/system.d.ts.map +1 -1
  54. package/dist/types/src/tracing.d.ts +233 -0
  55. package/dist/types/src/tracing.d.ts.map +1 -0
  56. package/dist/types/src/types/artifact.d.ts +28 -0
  57. package/dist/types/src/types/artifact.d.ts.map +1 -0
  58. package/dist/types/src/types/index.d.ts +2 -0
  59. package/dist/types/src/types/index.d.ts.map +1 -0
  60. package/dist/types/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +35 -46
  62. package/src/conversation/context.test.ts +52 -0
  63. package/src/conversation/context.ts +324 -75
  64. package/src/conversation/index.ts +1 -1
  65. package/src/conversation/session.ts +323 -0
  66. package/src/diff.test.ts +37 -6
  67. package/src/diff.ts +18 -4
  68. package/src/errors.ts +4 -4
  69. package/src/execution-graph.test.ts +740 -0
  70. package/src/execution-graph.ts +519 -0
  71. package/src/extraction/extraction-ner-function/index.ts +0 -1
  72. package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +12 -6
  73. package/src/extraction/extraction.ts +44 -15
  74. package/src/extraction/index.ts +0 -1
  75. package/src/extraction/quotes.test.ts +2 -2
  76. package/src/extraction/quotes.ts +6 -7
  77. package/src/functions/services.ts +156 -61
  78. package/src/index.ts +3 -1
  79. package/src/session/artifact-diff.ts +2 -2
  80. package/src/session/error-handling.ts +11 -6
  81. package/src/session/format.ts +35 -27
  82. package/src/session/index.ts +2 -1
  83. package/src/session/observer.ts +8 -4
  84. package/src/session/request.ts +347 -0
  85. package/src/session/toolkit.ts +32 -27
  86. package/src/session/version-pin.ts +7 -7
  87. package/src/templates/{format.tpl → instructions/format.tpl} +1 -4
  88. package/src/templates/instructions/system.tpl +96 -0
  89. package/src/templates/system.ts +9 -8
  90. package/src/tracing.ts +57 -0
  91. package/src/{types.ts → types/artifact.ts} +44 -7
  92. package/src/types/index.ts +5 -0
  93. package/dist/lib/browser/chunk-5GJYG2ML.mjs +0 -396
  94. package/dist/lib/browser/chunk-5GJYG2ML.mjs.map +0 -7
  95. package/dist/lib/browser/chunk-UB26N5UF.mjs +0 -89
  96. package/dist/lib/browser/chunk-UB26N5UF.mjs.map +0 -7
  97. package/dist/lib/browser/extraction/index.mjs +0 -268
  98. package/dist/lib/browser/extraction/index.mjs.map +0 -7
  99. package/dist/lib/browser/index.mjs +0 -469
  100. package/dist/lib/browser/index.mjs.map +0 -7
  101. package/dist/lib/browser/meta.json +0 -1
  102. package/dist/lib/browser/named-entity-recognition-QPYHML66.mjs +0 -13
  103. package/dist/lib/browser/named-entity-recognition-QPYHML66.mjs.map +0 -7
  104. package/dist/lib/node-esm/chunk-BCFXTNSK.mjs +0 -91
  105. package/dist/lib/node-esm/chunk-BCFXTNSK.mjs.map +0 -7
  106. package/dist/lib/node-esm/chunk-FLFCEB4P.mjs +0 -398
  107. package/dist/lib/node-esm/chunk-FLFCEB4P.mjs.map +0 -7
  108. package/dist/lib/node-esm/extraction/index.mjs +0 -269
  109. package/dist/lib/node-esm/extraction/index.mjs.map +0 -7
  110. package/dist/lib/node-esm/index.mjs +0 -470
  111. package/dist/lib/node-esm/index.mjs.map +0 -7
  112. package/dist/lib/node-esm/meta.json +0 -1
  113. package/dist/lib/node-esm/named-entity-recognition-2HDF46YX.mjs +0 -14
  114. package/dist/lib/node-esm/named-entity-recognition-2HDF46YX.mjs.map +0 -7
  115. package/dist/types/src/conversation/conversation.d.ts +0 -42
  116. package/dist/types/src/conversation/conversation.d.ts.map +0 -1
  117. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +0 -4
  118. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +0 -1
  119. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts +0 -2
  120. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts.map +0 -1
  121. package/dist/types/src/extraction/extraction-llm-function/index.d.ts +0 -2
  122. package/dist/types/src/extraction/extraction-llm-function/index.d.ts.map +0 -1
  123. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +0 -337
  124. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +0 -1
  125. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts +0 -2
  126. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts.map +0 -1
  127. package/dist/types/src/session/format.test.d.ts +0 -2
  128. package/dist/types/src/session/format.test.d.ts.map +0 -1
  129. package/dist/types/src/session/session.d.ts +0 -47
  130. package/dist/types/src/session/session.d.ts.map +0 -1
  131. package/dist/types/src/session/session.test.d.ts +0 -2
  132. package/dist/types/src/session/session.test.d.ts.map +0 -1
  133. package/dist/types/src/session/version-pin.test.d.ts +0 -2
  134. package/dist/types/src/session/version-pin.test.d.ts.map +0 -1
  135. package/dist/types/src/summarization/summarization.test.d.ts +0 -2
  136. package/dist/types/src/summarization/summarization.test.d.ts.map +0 -1
  137. package/dist/types/src/types.d.ts +0 -16
  138. package/dist/types/src/types.d.ts.map +0 -1
  139. package/src/conversation/conversation.ts +0 -123
  140. package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +0 -84
  141. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +0 -70
  142. package/src/extraction/extraction-llm-function/index.ts +0 -5
  143. package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +0 -115
  144. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +0 -62
  145. package/src/session/__snapshots__/session.test.ts.snap +0 -32
  146. package/src/session/format.test.ts +0 -60
  147. package/src/session/session.test.ts +0 -308
  148. package/src/session/session.ts +0 -201
  149. package/src/session/version-pin.test.ts +0 -7
  150. package/src/summarization/summarization.test.ts +0 -197
  151. package/src/templates/system.tpl +0 -50
  152. /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
@@ -0,0 +1,740 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, expect, it } from '@effect/vitest';
6
+
7
+ import { AgentStatus } from '@dxos/ai';
8
+ import { Obj } from '@dxos/echo';
9
+ import { ObjectId } from '@dxos/keys';
10
+ import { log } from '@dxos/log';
11
+ import { type ContentBlock, Message } from '@dxos/types';
12
+
13
+ import { ExecutionGraph } from './execution-graph';
14
+
15
+ // Helper function to create valid object IDs for testing.
16
+ const createTestId = () => ObjectId.random();
17
+
18
+ const SKIP_BLOCKS: ContentBlock.Any['_tag'][] = [];
19
+
20
+ describe('ExecutionGraph', () => {
21
+ describe('basic functionality', () => {
22
+ it('should create an empty graph initially', () => {
23
+ const graph = new ExecutionGraph();
24
+ const result = graph.getGraph();
25
+
26
+ expect(result.branches).toEqual([]);
27
+ expect(result.commits).toEqual([]);
28
+ });
29
+
30
+ it('should add a simple user message', () => {
31
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
32
+ const messageId = createTestId();
33
+ const message = Obj.make(Message.Message, {
34
+ id: messageId,
35
+ created: '2025-01-01T00:00:00Z',
36
+ sender: { role: 'user' },
37
+ blocks: [
38
+ {
39
+ _tag: 'text',
40
+ text: 'Hello, world!',
41
+ },
42
+ ],
43
+ properties: {},
44
+ });
45
+
46
+ graph.addEvents([message]);
47
+ const result = graph.getGraph();
48
+
49
+ expect(result.branches).toEqual(['main']);
50
+ expect(result.commits).toHaveLength(1);
51
+ expect(result.commits[0]).toMatchObject({
52
+ id: `${messageId}_block_0`,
53
+ branch: 'main',
54
+ message: 'Processing request...',
55
+ parents: [],
56
+ tags: ['user'],
57
+ });
58
+ });
59
+
60
+ it('should handle basic status & message processing', () => {
61
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
62
+ const status = Obj.make(AgentStatus, {
63
+ created: '2025-01-01T00:00:00Z',
64
+ message: 'Running Research',
65
+ });
66
+ const message = Obj.make(Message.Message, {
67
+ created: '2025-09-29T14:29:37.860Z',
68
+ sender: {
69
+ role: 'user',
70
+ },
71
+ blocks: [
72
+ {
73
+ _tag: 'text',
74
+ text: '{"id":"01K6AXZKK79H4E54RKP1YZDARK","@type":"dxn:type:org.dxos.type.organization:0.1.0","@meta":{"keys":[]},"name":"Blue Yard","website":"https://blueyard.com"}',
75
+ },
76
+ ],
77
+ properties: {},
78
+ });
79
+
80
+ graph.addEvents([status, message]);
81
+ const result = graph.getGraph();
82
+ log.info('result', result);
83
+
84
+ expect(result.branches).toEqual(['main']);
85
+ expect(result.commits).toHaveLength(2);
86
+ expect(result.commits[0].branch).toEqual('main');
87
+ expect(result.commits[0].message).toEqual('Running Research');
88
+ expect(result.commits[0].parents).toEqual([]);
89
+ expect(result.commits[1].branch).toEqual('main');
90
+ expect(result.commits[1].message).toEqual('Processing request...');
91
+ expect(result.commits[1].parents).toEqual([status.id]);
92
+ });
93
+
94
+ it('should add an assistant message with text', () => {
95
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
96
+ const messageId = createTestId();
97
+ const message = Obj.make(Message.Message, {
98
+ id: messageId,
99
+ created: '2025-01-01T00:00:00Z',
100
+ sender: { role: 'assistant' },
101
+ blocks: [
102
+ {
103
+ _tag: 'text',
104
+ text: 'Hello! How can I help you today?',
105
+ },
106
+ ],
107
+ properties: {},
108
+ });
109
+
110
+ graph.addEvents([message]);
111
+ const result = graph.getGraph();
112
+
113
+ expect(result.commits).toHaveLength(1);
114
+ expect(result.commits[0]).toMatchObject({
115
+ id: `${messageId}_block_0`,
116
+ branch: 'main',
117
+ message: 'Response (7 words)',
118
+ parents: [],
119
+ });
120
+ });
121
+ });
122
+
123
+ describe('sequential chaining within messages', () => {
124
+ it('should chain blocks within a message', () => {
125
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
126
+ const messageId = createTestId();
127
+ const message = Obj.make(Message.Message, {
128
+ id: messageId,
129
+ created: '2025-01-01T00:00:00Z',
130
+ sender: { role: 'assistant' },
131
+ blocks: [
132
+ {
133
+ _tag: 'text',
134
+ text: 'First block',
135
+ },
136
+ {
137
+ _tag: 'text',
138
+ text: 'Second block',
139
+ },
140
+ {
141
+ _tag: 'text',
142
+ text: 'Third block',
143
+ },
144
+ ],
145
+ properties: {},
146
+ });
147
+
148
+ graph.addEvents([message]);
149
+ const result = graph.getGraph();
150
+
151
+ expect(result.commits).toHaveLength(3);
152
+
153
+ // First block has no parents
154
+ expect(result.commits[0]).toMatchObject({
155
+ id: `${messageId}_block_0`,
156
+ parents: [],
157
+ });
158
+
159
+ // Second block has first block as parent
160
+ expect(result.commits[1]).toMatchObject({
161
+ id: `${messageId}_block_1`,
162
+ parents: [`${messageId}_block_0`],
163
+ });
164
+
165
+ // Third block has second block as parent
166
+ expect(result.commits[2]).toMatchObject({
167
+ id: `${messageId}_block_2`,
168
+ parents: [`${messageId}_block_1`],
169
+ });
170
+ });
171
+
172
+ it('should chain across multiple messages', () => {
173
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
174
+
175
+ // First message
176
+ const message1Id = createTestId();
177
+ const message1 = Obj.make(Message.Message, {
178
+ id: message1Id,
179
+ created: '2025-01-01T00:00:00Z',
180
+ sender: { role: 'user' },
181
+ blocks: [{ _tag: 'text', text: 'Hello' }],
182
+ properties: {},
183
+ });
184
+
185
+ // Second message
186
+ const message2Id = createTestId();
187
+ const message2 = Obj.make(Message.Message, {
188
+ id: message2Id,
189
+ created: '2025-01-01T00:01:00Z',
190
+ sender: { role: 'assistant' },
191
+ blocks: [{ _tag: 'text', text: 'Hi there!' }],
192
+ properties: {},
193
+ });
194
+
195
+ graph.addEvents([message1, message2]);
196
+ const result = graph.getGraph();
197
+
198
+ expect(result.commits).toHaveLength(2);
199
+
200
+ // First message block has no parents
201
+ expect(result.commits[0]).toMatchObject({
202
+ id: `${message1Id}_block_0`,
203
+ parents: [],
204
+ });
205
+
206
+ // Second message block has first message block as parent
207
+ expect(result.commits[1]).toMatchObject({
208
+ id: `${message2Id}_block_0`,
209
+ parents: [`${message1Id}_block_0`],
210
+ });
211
+ });
212
+ });
213
+
214
+ describe('tool calls and results', () => {
215
+ it('should handle tool calls and results with proper parent relationships', () => {
216
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
217
+
218
+ const message1Id = createTestId();
219
+ const message2Id = createTestId();
220
+ const statusId = createTestId();
221
+ const toolCallId = 'tool1';
222
+
223
+ // Assistant message with tool call
224
+ const toolCallMessage = Obj.make(Message.Message, {
225
+ id: message1Id,
226
+ created: '2025-01-01T00:00:00Z',
227
+ sender: { role: 'assistant' },
228
+ blocks: [
229
+ {
230
+ _tag: 'text',
231
+ text: 'I will help you with that.',
232
+ },
233
+ {
234
+ _tag: 'toolCall',
235
+ toolCallId,
236
+ name: 'test_tool',
237
+ input: '{"param": "value"}',
238
+ providerExecuted: false,
239
+ },
240
+ ],
241
+ properties: {},
242
+ });
243
+
244
+ // User message with tool result
245
+ const toolResultMessage = Obj.make(Message.Message, {
246
+ id: message2Id,
247
+ created: '2025-01-01T00:01:00Z',
248
+ sender: { role: 'user' },
249
+ blocks: [
250
+ {
251
+ _tag: 'toolResult',
252
+ toolCallId,
253
+ name: 'test_tool',
254
+ result: '{"result": "success"}',
255
+ providerExecuted: false,
256
+ },
257
+ ],
258
+ properties: {},
259
+ });
260
+
261
+ // AgentStatus event
262
+ const agentStatus = Obj.make(AgentStatus, {
263
+ id: statusId,
264
+ created: '2025-01-01T00:02:00Z',
265
+ parentMessage: message1Id,
266
+ toolCallId,
267
+ message: 'Processing tool call...',
268
+ });
269
+
270
+ graph.addEvents([toolCallMessage, toolResultMessage, agentStatus]);
271
+ const result = graph.getGraph();
272
+
273
+ expect(result.commits).toHaveLength(4);
274
+
275
+ // Text block
276
+ expect(result.commits[0]).toMatchObject({
277
+ id: `${message1Id}_block_0`,
278
+ parents: [],
279
+ });
280
+
281
+ // Tool call block
282
+ expect(result.commits[1]).toMatchObject({
283
+ id: `${message1Id}_toolCall_${toolCallId}`,
284
+ parents: [`${message1Id}_block_0`],
285
+ });
286
+
287
+ // Tool result should have two parents: AgentStatus and tool call
288
+ expect(result.commits[2]).toMatchObject({
289
+ id: `${message2Id}_toolResult_${toolCallId}`,
290
+ parents: [statusId, `${message1Id}_toolCall_${toolCallId}`],
291
+ });
292
+
293
+ // AgentStatus
294
+ expect(result.commits[3]).toMatchObject({
295
+ id: statusId,
296
+ parents: [`${message1Id}_toolCall_${toolCallId}`],
297
+ });
298
+ });
299
+
300
+ it('should handle tool errors with proper parent relationships', () => {
301
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
302
+
303
+ const message1Id = createTestId();
304
+ const message2Id = createTestId();
305
+ const statusId = createTestId();
306
+ const toolCallId = 'tool1';
307
+
308
+ // Assistant message with tool call
309
+ const toolCallMessage = Obj.make(Message.Message, {
310
+ id: message1Id,
311
+ created: '2025-01-01T00:00:00Z',
312
+ sender: { role: 'assistant' },
313
+ blocks: [
314
+ {
315
+ _tag: 'toolCall',
316
+ toolCallId,
317
+ name: 'test_tool',
318
+ input: '{"param": "value"}',
319
+ providerExecuted: false,
320
+ },
321
+ ],
322
+ properties: {},
323
+ });
324
+
325
+ // User message with tool error
326
+ const toolErrorMessage = Obj.make(Message.Message, {
327
+ id: message2Id,
328
+ created: '2025-01-01T00:01:00Z',
329
+ sender: { role: 'user' },
330
+ blocks: [
331
+ {
332
+ _tag: 'toolResult',
333
+ toolCallId,
334
+ name: 'test_tool',
335
+ error: 'Tool execution failed',
336
+ providerExecuted: false,
337
+ },
338
+ ],
339
+ properties: {},
340
+ });
341
+
342
+ // AgentStatus event
343
+ const agentStatus = Obj.make(AgentStatus, {
344
+ id: statusId,
345
+ created: '2025-01-01T00:02:00Z',
346
+ parentMessage: message1Id,
347
+ toolCallId,
348
+ message: 'Tool execution failed',
349
+ });
350
+
351
+ graph.addEvents([toolCallMessage, toolErrorMessage, agentStatus]);
352
+ const result = graph.getGraph();
353
+
354
+ expect(result.commits).toHaveLength(3);
355
+
356
+ // Tool call block
357
+ expect(result.commits[0]).toMatchObject({
358
+ id: `${message1Id}_toolCall_${toolCallId}`,
359
+ parents: [],
360
+ });
361
+
362
+ // Tool error should have two parents: AgentStatus and tool call
363
+ expect(result.commits[1]).toMatchObject({
364
+ id: `${message2Id}_toolResult_${toolCallId}`,
365
+ parents: [statusId, `${message1Id}_toolCall_${toolCallId}`],
366
+ });
367
+
368
+ // AgentStatus
369
+ expect(result.commits[2]).toMatchObject({
370
+ id: statusId,
371
+ parents: [`${message1Id}_toolCall_${toolCallId}`],
372
+ });
373
+ });
374
+ });
375
+
376
+ describe('different processing orders', () => {
377
+ it('should handle AgentStatus processed after tool result', () => {
378
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
379
+
380
+ const message1Id = createTestId();
381
+ const message2Id = createTestId();
382
+ const statusId = createTestId();
383
+ const toolCallId = 'tool1';
384
+
385
+ // Tool call message
386
+ const toolCallMessage = Obj.make(Message.Message, {
387
+ id: message1Id,
388
+ created: '2025-01-01T00:00:00Z',
389
+ sender: { role: 'assistant' },
390
+ blocks: [
391
+ {
392
+ _tag: 'toolCall',
393
+ toolCallId,
394
+ name: 'test_tool',
395
+ input: '{}',
396
+ providerExecuted: false,
397
+ },
398
+ ],
399
+ properties: {},
400
+ });
401
+
402
+ // Tool result message
403
+ const toolResultMessage = Obj.make(Message.Message, {
404
+ id: message2Id,
405
+ created: '2025-01-01T00:01:00Z',
406
+ sender: { role: 'user' },
407
+ blocks: [
408
+ {
409
+ _tag: 'toolResult',
410
+ toolCallId,
411
+ name: 'test_tool',
412
+ result: '{"result": "success"}',
413
+ providerExecuted: false,
414
+ },
415
+ ],
416
+ properties: {},
417
+ });
418
+
419
+ // AgentStatus event (processed after tool result)
420
+ const agentStatus = Obj.make(AgentStatus, {
421
+ id: statusId,
422
+ created: '2025-01-01T00:00:00Z',
423
+ parentMessage: message1Id,
424
+ toolCallId,
425
+ message: 'Processing...',
426
+ });
427
+
428
+ graph.addEvents([toolCallMessage, toolResultMessage, agentStatus]);
429
+ const result = graph.getGraph();
430
+
431
+ // Tool result should be updated to point to AgentStatus
432
+ const toolResult = result.commits.find((c) => c.id === `${message2Id}_toolResult_${toolCallId}`);
433
+ expect(toolResult?.parents).toContain(statusId);
434
+ });
435
+
436
+ it('should handle AgentStatus processed before tool result', () => {
437
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
438
+
439
+ const message1Id = createTestId();
440
+ const message2Id = createTestId();
441
+ const statusId = createTestId();
442
+ const toolCallId = 'tool1';
443
+
444
+ // Tool call message
445
+ const toolCallMessage = Obj.make(Message.Message, {
446
+ id: message1Id,
447
+ created: '2025-01-01T00:00:00Z',
448
+ sender: { role: 'assistant' },
449
+ blocks: [
450
+ {
451
+ _tag: 'toolCall',
452
+ toolCallId,
453
+ name: 'test_tool',
454
+ input: '{}',
455
+ providerExecuted: false,
456
+ },
457
+ ],
458
+ properties: {},
459
+ });
460
+
461
+ // AgentStatus event (processed before tool result)
462
+ const agentStatus = Obj.make(AgentStatus, {
463
+ id: statusId,
464
+ created: '2025-01-01T00:00:00Z',
465
+ parentMessage: message1Id,
466
+ toolCallId,
467
+ message: 'Processing...',
468
+ });
469
+
470
+ // Tool result message
471
+ const toolResultMessage = Obj.make(Message.Message, {
472
+ id: message2Id,
473
+ created: '2025-01-01T00:01:00Z',
474
+ sender: { role: 'user' },
475
+ blocks: [
476
+ {
477
+ _tag: 'toolResult',
478
+ toolCallId,
479
+ name: 'test_tool',
480
+ result: '{"result": "success"}',
481
+ providerExecuted: false,
482
+ },
483
+ ],
484
+ properties: {},
485
+ });
486
+
487
+ graph.addEvents([toolCallMessage, agentStatus, toolResultMessage]);
488
+ const result = graph.getGraph();
489
+
490
+ // Tool result should point to AgentStatus
491
+ const toolResult = result.commits.find((c) => c.id === `${message2Id}_toolResult_${toolCallId}`);
492
+ expect(toolResult?.parents).toContain(statusId);
493
+ });
494
+ });
495
+
496
+ describe('complex scenarios', () => {
497
+ it('should handle multiple tool calls in sequence', () => {
498
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
499
+
500
+ const msg1Id = createTestId();
501
+ const msg2Id = createTestId();
502
+ const msg3Id = createTestId();
503
+ const msg4Id = createTestId();
504
+ const msg5Id = createTestId();
505
+ const status1Id = createTestId();
506
+ const status2Id = createTestId();
507
+
508
+ const events = [
509
+ // User message
510
+ Obj.make(Message.Message, {
511
+ id: msg1Id,
512
+ created: '2025-01-01T00:00:00Z',
513
+ sender: { role: 'user' },
514
+ blocks: [{ _tag: 'text', text: 'Help me with two tasks' }],
515
+ properties: {},
516
+ }),
517
+
518
+ // Assistant with first tool call
519
+ Obj.make(Message.Message, {
520
+ id: msg2Id,
521
+ created: '2025-01-01T00:01:00Z',
522
+ sender: { role: 'assistant' },
523
+ blocks: [
524
+ { _tag: 'text', text: 'I will help with both tasks' },
525
+ { _tag: 'toolCall', toolCallId: 'tool1', name: 'task1', input: '{}', providerExecuted: false },
526
+ ],
527
+ properties: {},
528
+ }),
529
+
530
+ // First tool result
531
+ Obj.make(Message.Message, {
532
+ id: msg3Id,
533
+ created: '2025-01-01T00:02:00Z',
534
+ sender: { role: 'user' },
535
+ blocks: [
536
+ {
537
+ _tag: 'toolResult',
538
+ toolCallId: 'tool1',
539
+ name: 'task1',
540
+ result: '{"done": true}',
541
+ providerExecuted: false,
542
+ },
543
+ ],
544
+ properties: {},
545
+ }),
546
+
547
+ // Assistant with second tool call
548
+ Obj.make(Message.Message, {
549
+ id: msg4Id,
550
+ created: '2025-01-01T00:03:00Z',
551
+ sender: { role: 'assistant' },
552
+ blocks: [
553
+ { _tag: 'text', text: 'Now for the second task' },
554
+ { _tag: 'toolCall', toolCallId: 'tool2', name: 'task2', input: '{}', providerExecuted: false },
555
+ ],
556
+ properties: {},
557
+ }),
558
+
559
+ // Second tool result
560
+ Obj.make(Message.Message, {
561
+ id: msg5Id,
562
+ created: '2025-01-01T00:04:00Z',
563
+ sender: { role: 'user' },
564
+ blocks: [
565
+ {
566
+ _tag: 'toolResult',
567
+ toolCallId: 'tool2',
568
+ name: 'task2',
569
+ result: '{"done": true}',
570
+ providerExecuted: false,
571
+ },
572
+ ],
573
+ properties: {},
574
+ }),
575
+
576
+ // AgentStatus events
577
+ Obj.make(AgentStatus, {
578
+ id: status1Id,
579
+ created: '2025-01-01T00:05:00Z',
580
+ parentMessage: msg2Id,
581
+ toolCallId: 'tool1',
582
+ message: 'Processing task 1...',
583
+ }),
584
+
585
+ Obj.make(AgentStatus, {
586
+ id: status2Id,
587
+ created: '2025-01-01T00:06:00Z',
588
+ parentMessage: msg4Id,
589
+ toolCallId: 'tool2',
590
+ message: 'Processing task 2...',
591
+ }),
592
+ ];
593
+
594
+ graph.addEvents(events);
595
+ const result = graph.getGraph();
596
+
597
+ expect(result.commits).toHaveLength(9);
598
+
599
+ // Check sequential chaining
600
+ expect(result.commits[0].parents).toEqual([]); // msg1_block_0
601
+ expect(result.commits[1].parents).toEqual([`${msg1Id}_block_0`]); // msg2_block_0
602
+ expect(result.commits[2].parents).toEqual([`${msg2Id}_block_0`]); // msg2_toolCall_tool1
603
+ expect(result.commits[3].parents).toEqual([status1Id, `${msg2Id}_toolCall_tool1`]); // msg3_toolResult_tool1
604
+ expect(result.commits[4].parents).toEqual([`${msg3Id}_toolResult_tool1`]); // msg4_block_0
605
+ expect(result.commits[5].parents).toEqual([`${msg4Id}_block_0`]); // msg4_toolCall_tool2
606
+ expect(result.commits[6].parents).toEqual([status2Id, `${msg4Id}_toolCall_tool2`]); // msg5_toolResult_tool2
607
+ });
608
+
609
+ it('should handle mixed block types in a message', () => {
610
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
611
+
612
+ const messageId = createTestId();
613
+ const message = Obj.make(Message.Message, {
614
+ id: messageId,
615
+ created: '2025-01-01T00:00:00Z',
616
+ sender: { role: 'assistant' },
617
+ blocks: [
618
+ { _tag: 'text', text: 'Let me help you' },
619
+ { _tag: 'reasoning', reasoningText: 'I need to think about this' },
620
+ { _tag: 'toolCall', toolCallId: 'tool1', name: 'analyze', input: '{}', providerExecuted: false },
621
+ {
622
+ _tag: 'stats',
623
+ message: 'OK',
624
+ duration: 1000,
625
+ toolCalls: 1,
626
+ usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
627
+ },
628
+ ],
629
+ properties: {},
630
+ });
631
+
632
+ graph.addEvents([message]);
633
+ const result = graph.getGraph();
634
+
635
+ expect(result.commits).toHaveLength(4);
636
+
637
+ // All blocks should be chained sequentially
638
+ expect(result.commits[0].parents).toEqual([]); // text
639
+ expect(result.commits[1].parents).toEqual([`${messageId}_block_0`]); // reasoning
640
+ expect(result.commits[2].parents).toEqual([`${messageId}_block_1`]); // toolCall
641
+ expect(result.commits[3].parents).toEqual([`${messageId}_toolCall_tool1`]); // summary
642
+ });
643
+ });
644
+
645
+ describe('edge cases', () => {
646
+ it('should handle empty text blocks', () => {
647
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
648
+
649
+ const messageId = createTestId();
650
+ const message = Obj.make(Message.Message, {
651
+ id: messageId,
652
+ created: '2025-01-01T00:00:00Z',
653
+ sender: { role: 'assistant' },
654
+ blocks: [
655
+ { _tag: 'text', text: 'Valid text' },
656
+ { _tag: 'text', text: '' }, // Empty text should be filtered out
657
+ { _tag: 'text', text: 'More valid text' },
658
+ ],
659
+ properties: {},
660
+ });
661
+
662
+ graph.addEvents([message]);
663
+ const result = graph.getGraph();
664
+
665
+ expect(result.commits).toHaveLength(2); // Empty block should be filtered out
666
+ expect(result.commits[0].parents).toEqual([]); // First text block
667
+ expect(result.commits[1].parents).toEqual([`${messageId}_block_0`]); // Third text block
668
+ });
669
+
670
+ it('should handle messages with no blocks', () => {
671
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
672
+
673
+ const messageId = createTestId();
674
+ const message = Obj.make(Message.Message, {
675
+ id: messageId,
676
+ created: '2025-01-01T00:00:00Z',
677
+ sender: { role: 'assistant' },
678
+ blocks: [],
679
+ properties: {},
680
+ });
681
+
682
+ graph.addEvents([message]);
683
+ const result = graph.getGraph();
684
+
685
+ expect(result.commits).toHaveLength(0);
686
+ });
687
+
688
+ it('should handle getGraph with lastRequest filter', () => {
689
+ const graph = new ExecutionGraph(SKIP_BLOCKS);
690
+
691
+ const msg1Id = createTestId();
692
+ const msg2Id = createTestId();
693
+ const msg3Id = createTestId();
694
+ const msg4Id = createTestId();
695
+
696
+ const events = [
697
+ Obj.make(Message.Message, {
698
+ id: msg1Id,
699
+ created: '2025-01-01T00:00:00Z',
700
+ sender: { role: 'user' },
701
+ blocks: [{ _tag: 'text', text: 'First request' }],
702
+ properties: {},
703
+ }),
704
+ Obj.make(Message.Message, {
705
+ id: msg2Id,
706
+ created: '2025-01-01T00:01:00Z',
707
+ sender: { role: 'assistant' },
708
+ blocks: [{ _tag: 'text', text: 'Response 1' }],
709
+ properties: {},
710
+ }),
711
+ Obj.make(Message.Message, {
712
+ id: msg3Id,
713
+ created: '2025-01-01T00:02:00Z',
714
+ sender: { role: 'user' },
715
+ blocks: [{ _tag: 'text', text: 'Second request' }],
716
+ properties: {},
717
+ }),
718
+ Obj.make(Message.Message, {
719
+ id: msg4Id,
720
+ created: '2025-01-01T00:03:00Z',
721
+ sender: { role: 'assistant' },
722
+ blocks: [{ _tag: 'text', text: 'Response 2' }],
723
+ properties: {},
724
+ }),
725
+ ];
726
+
727
+ graph.addEvents(events);
728
+
729
+ // Get all commits
730
+ const allCommits = graph.getGraph();
731
+ expect(allCommits.commits).toHaveLength(4);
732
+
733
+ // Get commits from last user request
734
+ const lastRequestCommits = graph.getGraph(true);
735
+ expect(lastRequestCommits.commits).toHaveLength(2); // Only msg3 and msg4
736
+ expect(lastRequestCommits.commits[0].id).toBe(`${msg3Id}_block_0`);
737
+ expect(lastRequestCommits.commits[1].id).toBe(`${msg4Id}_block_0`);
738
+ });
739
+ });
740
+ });