@dxos/assistant 0.8.4-main.3a94e84 → 0.8.4-main.406dc2a

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 (168) hide show
  1. package/dist/lib/browser/chunk-CWGVG235.mjs +380 -0
  2. package/dist/lib/browser/chunk-CWGVG235.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-UB26N5UF.mjs +89 -0
  4. package/dist/lib/browser/chunk-UB26N5UF.mjs.map +7 -0
  5. package/dist/lib/browser/extraction/index.mjs +272 -0
  6. package/dist/lib/browser/extraction/index.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +747 -1645
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/named-entity-recognition-QPYHML66.mjs +13 -0
  11. package/dist/lib/browser/named-entity-recognition-QPYHML66.mjs.map +7 -0
  12. package/dist/lib/node-esm/chunk-BCFXTNSK.mjs +91 -0
  13. package/dist/lib/node-esm/chunk-BCFXTNSK.mjs.map +7 -0
  14. package/dist/lib/node-esm/chunk-OV5PMQPB.mjs +382 -0
  15. package/dist/lib/node-esm/chunk-OV5PMQPB.mjs.map +7 -0
  16. package/dist/lib/node-esm/extraction/index.mjs +273 -0
  17. package/dist/lib/node-esm/extraction/index.mjs.map +7 -0
  18. package/dist/lib/node-esm/index.mjs +746 -1645
  19. package/dist/lib/node-esm/index.mjs.map +4 -4
  20. package/dist/lib/node-esm/meta.json +1 -1
  21. package/dist/lib/node-esm/named-entity-recognition-2HDF46YX.mjs +14 -0
  22. package/dist/lib/node-esm/named-entity-recognition-2HDF46YX.mjs.map +7 -0
  23. package/dist/types/src/conversation/context.d.ts +103 -0
  24. package/dist/types/src/conversation/context.d.ts.map +1 -0
  25. package/dist/types/src/conversation/conversation.d.ts +28 -22
  26. package/dist/types/src/conversation/conversation.d.ts.map +1 -1
  27. package/dist/types/src/conversation/index.d.ts +1 -0
  28. package/dist/types/src/conversation/index.d.ts.map +1 -1
  29. package/dist/types/src/diff.d.ts +15 -0
  30. package/dist/types/src/diff.d.ts.map +1 -0
  31. package/dist/types/src/diff.test.d.ts +2 -0
  32. package/dist/types/src/diff.test.d.ts.map +1 -0
  33. package/dist/types/src/errors.d.ts +46 -9
  34. package/dist/types/src/errors.d.ts.map +1 -1
  35. package/dist/types/src/execution-graph.d.ts +82 -0
  36. package/dist/types/src/execution-graph.d.ts.map +1 -0
  37. package/dist/types/src/execution-graph.test.d.ts +2 -0
  38. package/dist/types/src/execution-graph.test.d.ts.map +1 -0
  39. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +1 -1
  40. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
  41. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +103 -64
  42. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
  43. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
  44. package/dist/types/src/extraction/extraction.d.ts +252 -218
  45. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  46. package/dist/types/src/extraction/quotes.d.ts +1 -1
  47. package/dist/types/src/extraction/quotes.d.ts.map +1 -1
  48. package/dist/types/src/functions/services.d.ts +17 -4
  49. package/dist/types/src/functions/services.d.ts.map +1 -1
  50. package/dist/types/src/index.d.ts +3 -3
  51. package/dist/types/src/index.d.ts.map +1 -1
  52. package/dist/types/src/session/artifact-diff.d.ts +27 -0
  53. package/dist/types/src/session/artifact-diff.d.ts.map +1 -0
  54. package/dist/types/src/session/error-handling.d.ts +4 -0
  55. package/dist/types/src/session/error-handling.d.ts.map +1 -0
  56. package/dist/types/src/session/format.d.ts +184 -0
  57. package/dist/types/src/session/format.d.ts.map +1 -0
  58. package/dist/types/src/session/format.test.d.ts +2 -0
  59. package/dist/types/src/session/format.test.d.ts.map +1 -0
  60. package/dist/types/src/session/index.d.ts +3 -0
  61. package/dist/types/src/session/index.d.ts.map +1 -1
  62. package/dist/types/src/session/observer.d.ts +22 -0
  63. package/dist/types/src/session/observer.d.ts.map +1 -0
  64. package/dist/types/src/session/session.d.ts +27 -236
  65. package/dist/types/src/session/session.d.ts.map +1 -1
  66. package/dist/types/src/session/toolkit.d.ts +15 -0
  67. package/dist/types/src/session/toolkit.d.ts.map +1 -0
  68. package/dist/types/src/session/version-pin.d.ts +3 -3
  69. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  70. package/dist/types/src/templates/index.d.ts +1 -1
  71. package/dist/types/src/templates/system.d.ts +18 -0
  72. package/dist/types/src/templates/system.d.ts.map +1 -0
  73. package/dist/types/src/{types.d.ts → types/artifact.d.ts} +6 -3
  74. package/dist/types/src/types/artifact.d.ts.map +1 -0
  75. package/dist/types/src/types/index.d.ts +2 -0
  76. package/dist/types/src/types/index.d.ts.map +1 -0
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +44 -32
  79. package/src/conversation/context.ts +186 -0
  80. package/src/conversation/conversation.ts +89 -53
  81. package/src/conversation/index.ts +1 -0
  82. package/src/diff.test.ts +43 -0
  83. package/src/diff.ts +79 -0
  84. package/src/errors.ts +6 -1
  85. package/src/execution-graph.test.ts +738 -0
  86. package/src/execution-graph.ts +500 -0
  87. package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +8 -7
  88. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +13 -11
  89. package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +9 -9
  90. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +15 -8
  91. package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +2 -1
  92. package/src/extraction/extraction-ner-function/named-entity-recognition.ts +2 -0
  93. package/src/extraction/extraction.ts +2 -2
  94. package/src/extraction/quotes.test.ts +3 -3
  95. package/src/extraction/quotes.ts +4 -4
  96. package/src/functions/services.ts +114 -95
  97. package/src/index.ts +3 -3
  98. package/src/session/artifact-diff.ts +41 -0
  99. package/src/session/error-handling.ts +41 -0
  100. package/src/session/format.test.ts +60 -0
  101. package/src/session/format.ts +142 -0
  102. package/src/session/index.ts +3 -0
  103. package/src/session/observer.ts +59 -0
  104. package/src/session/session.conversations.json +1 -0
  105. package/src/session/session.test.ts +37 -198
  106. package/src/session/session.ts +125 -249
  107. package/src/session/toolkit.ts +48 -0
  108. package/src/session/version-pin.ts +5 -5
  109. package/src/summarization/summarization.test.ts +16 -22
  110. package/src/templates/format.tpl +60 -0
  111. package/src/templates/index.ts +1 -1
  112. package/src/templates/system.tpl +48 -0
  113. package/src/templates/system.ts +41 -0
  114. package/src/{types.ts → types/artifact.ts} +12 -7
  115. package/src/types/index.ts +5 -0
  116. package/src/typings.d.ts +5 -0
  117. package/dist/types/bin/chat.d.ts +0 -2
  118. package/dist/types/bin/chat.d.ts.map +0 -1
  119. package/dist/types/src/context/binder.d.ts +0 -34
  120. package/dist/types/src/context/binder.d.ts.map +0 -1
  121. package/dist/types/src/context/binding.d.ts +0 -56
  122. package/dist/types/src/context/binding.d.ts.map +0 -1
  123. package/dist/types/src/context/index.d.ts +0 -3
  124. package/dist/types/src/context/index.d.ts.map +0 -1
  125. package/dist/types/src/research/exa.d.ts +0 -12
  126. package/dist/types/src/research/exa.d.ts.map +0 -1
  127. package/dist/types/src/research/graph.d.ts +0 -51
  128. package/dist/types/src/research/graph.d.ts.map +0 -1
  129. package/dist/types/src/research/graph.test.d.ts +0 -2
  130. package/dist/types/src/research/graph.test.d.ts.map +0 -1
  131. package/dist/types/src/research/index.d.ts +0 -4
  132. package/dist/types/src/research/index.d.ts.map +0 -1
  133. package/dist/types/src/research/research.d.ts +0 -10
  134. package/dist/types/src/research/research.d.ts.map +0 -1
  135. package/dist/types/src/research/research.test.d.ts +0 -2
  136. package/dist/types/src/research/research.test.d.ts.map +0 -1
  137. package/dist/types/src/session/version-pin.test.d.ts +0 -2
  138. package/dist/types/src/session/version-pin.test.d.ts.map +0 -1
  139. package/dist/types/src/templates/prompt.d.ts +0 -30
  140. package/dist/types/src/templates/prompt.d.ts.map +0 -1
  141. package/dist/types/src/testing/exa-search-1748337321991.d.ts +0 -38
  142. package/dist/types/src/testing/exa-search-1748337321991.d.ts.map +0 -1
  143. package/dist/types/src/testing/exa-search-1748337331526.d.ts +0 -37
  144. package/dist/types/src/testing/exa-search-1748337331526.d.ts.map +0 -1
  145. package/dist/types/src/testing/exa-search-1748337344119.d.ts +0 -58
  146. package/dist/types/src/testing/exa-search-1748337344119.d.ts.map +0 -1
  147. package/dist/types/src/testing/index.d.ts +0 -3
  148. package/dist/types/src/testing/index.d.ts.map +0 -1
  149. package/dist/types/src/types.d.ts.map +0 -1
  150. package/src/context/binder.ts +0 -103
  151. package/src/context/binding.ts +0 -32
  152. package/src/context/index.ts +0 -6
  153. package/src/research/exa.ts +0 -97
  154. package/src/research/graph.test.ts +0 -64
  155. package/src/research/graph.ts +0 -369
  156. package/src/research/index.ts +0 -7
  157. package/src/research/instructions-research.tpl +0 -98
  158. package/src/research/research.test.ts +0 -190
  159. package/src/research/research.ts +0 -87
  160. package/src/session/__snapshots__/session.test.ts.snap +0 -32
  161. package/src/session/version-pin.test.ts +0 -7
  162. package/src/templates/assistant.tpl +0 -111
  163. package/src/templates/prompt.ts +0 -45
  164. package/src/testing/exa-search-1748337321991.ts +0 -131
  165. package/src/testing/exa-search-1748337331526.ts +0 -144
  166. package/src/testing/exa-search-1748337344119.ts +0 -133
  167. package/src/testing/index.ts +0 -11
  168. package/src/typedefs.d.ts +0 -8
@@ -0,0 +1,500 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { AgentStatus } from '@dxos/ai';
6
+ import { Obj, type Ref } from '@dxos/echo';
7
+ import { MESSAGE_PROPERTY_TOOL_CALL_ID } from '@dxos/functions';
8
+ import { type ObjectId } from '@dxos/keys';
9
+ import { LogLevel } from '@dxos/log';
10
+ import { ContentBlock, DataType } from '@dxos/schema';
11
+ import { isNonNullable } from '@dxos/util';
12
+
13
+ /**
14
+ * Mercurial-style Commit.
15
+ */
16
+ // TODO(wittjosiah): Reconcile with @dxos/react-ui-components.
17
+ export type Commit = {
18
+ id: string;
19
+ parents: string[];
20
+ branch: string;
21
+ icon?: string;
22
+ level?: LogLevel;
23
+ message: string;
24
+ timestamp?: Date;
25
+ tags?: string[];
26
+ };
27
+
28
+ // TODO(burdon): Add colors?
29
+ enum IconType {
30
+ // General status.
31
+ SUCCESS = 'ph--check-circle--regular',
32
+ WARNING = 'ph--warning-circle--regular',
33
+ ERROR = 'ph--x-circle--regular',
34
+ FLAG = 'ph--flag--regular',
35
+ ROCKET = 'ph--rocket--regular',
36
+ TIMER = 'ph--timer--regular',
37
+
38
+ // Interactions.
39
+ USER = 'ph--user--regular',
40
+ USER_INTERACTION = 'ph--user-sound--regular',
41
+ AGENT = 'ph--robot--regular',
42
+ THINKING = 'ph--brain--regular',
43
+ LINK = 'ph--link--regular',
44
+ TOOL = 'ph--wrench--regular',
45
+ }
46
+
47
+ /**
48
+ * Models the execution graph based on a stream of events.
49
+ */
50
+ export class ExecutionGraph {
51
+ // TODO(dmaretskyi): Evolve the internal state to support chats, functions, circuit workflows, etc.
52
+ private _commits: Commit[] = [];
53
+ private _branchNames = new Set<string>();
54
+ private _lastBlockId: string | undefined;
55
+
56
+ // Tool call tracking.
57
+ private _toolCallCommitIds = new Map<string, string>(); // toolCallId -> commitId
58
+ private _lastCommitByBranch = new Map<string, string>(); // branch -> last commitId
59
+ private _pendingToolResults = new Map<string, string>(); // toolCallId -> toolResultCommitId
60
+
61
+ /**
62
+ * Adds events to the graph.
63
+ */
64
+ addEvents(events: Obj.Any[]) {
65
+ const sortedEvents = this.sortObjectsByCreated(events);
66
+ for (const event of sortedEvents) {
67
+ if (Obj.instanceOf(DataType.Message, event)) {
68
+ this._processMessage(event);
69
+ } else if (Obj.instanceOf(AgentStatus, event)) {
70
+ this._processAgentStatus(event);
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Processes a message event and creates commits for its blocks.
77
+ */
78
+ private _processMessage(message: DataType.Message) {
79
+ const messageCommits = messageToCommits(
80
+ message,
81
+ this._lastBlockId,
82
+ this._toolCallCommitIds,
83
+ this._lastCommitByBranch,
84
+ );
85
+ this._commits.push(...messageCommits);
86
+
87
+ // Track branches.
88
+ messageCommits.forEach((commit) => this._branchNames.add(commit.branch));
89
+
90
+ // Track tool calls and update pending tool results.
91
+ this._trackToolCalls(messageCommits);
92
+
93
+ // Update last block ID for sequential chaining.
94
+ if (messageCommits.length > 0) {
95
+ this._lastBlockId = messageCommits[messageCommits.length - 1].id;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Processes an AgentStatus event.
101
+ */
102
+ private _processAgentStatus(event: AgentStatus) {
103
+ const branch = this._getToolCallBranch(event.parentMessage, event.toolCallId);
104
+ this._branchNames.add(branch);
105
+
106
+ const agentStatusCommit: Commit = {
107
+ id: event.id,
108
+ branch,
109
+ message: event.message,
110
+ icon: IconType.FLAG,
111
+ parents: this._getAgentStatusParents(event),
112
+ timestamp: new Date(event.created),
113
+ };
114
+
115
+ this._commits.push(agentStatusCommit);
116
+
117
+ // Update last block ID for sequential chaining.
118
+ this._lastBlockId = event.id;
119
+
120
+ // Update pending tool results to point to this AgentStatus.
121
+ if (event.toolCallId) {
122
+ this._lastCommitByBranch.set(branch, event.id);
123
+ this._updatePendingToolResults(event.toolCallId, event.id);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Stable sort: objects with 'created' field are sorted by it, others remain in place.
129
+ */
130
+ private sortObjectsByCreated(objects: any[]) {
131
+ const sortedObjects = objects.slice();
132
+ // Find indices of objects with 'created'
133
+ const createdObjects: { idx: number; obj: any }[] = [];
134
+ sortedObjects.forEach((obj, idx) => {
135
+ if (obj && typeof obj.created === 'string') {
136
+ createdObjects.push({ idx, obj });
137
+ }
138
+ });
139
+ // Sort only the objects with 'created' by their date, stable
140
+ createdObjects.sort((a, b) => {
141
+ const aDate = new Date(a.obj.created).getTime();
142
+ const bDate = new Date(b.obj.created).getTime();
143
+ return aDate - bDate;
144
+ });
145
+ // Place sorted createdObjects back into their original positions
146
+ let createdIdx = 0;
147
+ return sortedObjects.map((obj, _idx) => {
148
+ if (obj && typeof obj.created === 'string') {
149
+ return createdObjects[createdIdx++].obj;
150
+ }
151
+ return obj;
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Returns the current state of the graph.
157
+ */
158
+ getGraph(lastRequest = false): { branches: string[]; commits: Commit[] } {
159
+ const idx = lastRequest ? this._commits.findLastIndex((c) => c.tags?.includes('user')) : -1;
160
+ const commits = idx === -1 ? this._commits : this._commits.slice(idx);
161
+
162
+ return {
163
+ branches: Array.from(this._branchNames),
164
+ commits,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Tracks tool calls and handles pending tool result updates.
170
+ */
171
+ private _trackToolCalls(commits: Commit[]) {
172
+ commits.forEach((commit) => {
173
+ if (this._isToolCallCommit(commit)) {
174
+ const toolCallId = this._extractToolCallId(commit.id);
175
+ if (toolCallId) {
176
+ this._toolCallCommitIds.set(toolCallId, commit.id);
177
+ }
178
+ }
179
+
180
+ if (this._isToolResultCommit(commit)) {
181
+ const toolCallId = this._extractToolCallId(commit.id);
182
+ if (toolCallId) {
183
+ this._pendingToolResults.set(toolCallId, commit.id);
184
+ this._handleLateToolResultUpdate(toolCallId, commit.id);
185
+ }
186
+ }
187
+
188
+ this._lastCommitByBranch.set(commit.branch, commit.id);
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Updates pending tool results to point to an AgentStatus event.
194
+ */
195
+ private _updatePendingToolResults(toolCallId: string, agentStatusId: string) {
196
+ const toolResultId = this._pendingToolResults.get(toolCallId);
197
+ if (!toolResultId) return;
198
+
199
+ const toolResultCommit = this._commits.find((c) => c.id === toolResultId);
200
+ if (!toolResultCommit) return;
201
+
202
+ const toolCallCommitId = this._toolCallCommitIds.get(toolCallId);
203
+ if (!toolCallCommitId) return;
204
+
205
+ // Replace tool call commit with AgentStatus commit.
206
+ const toolCallIndex = toolResultCommit.parents.indexOf(toolCallCommitId);
207
+ if (toolCallIndex !== -1) {
208
+ toolResultCommit.parents[toolCallIndex] = agentStatusId;
209
+ }
210
+
211
+ this._pendingToolResults.delete(toolCallId);
212
+ }
213
+
214
+ /**
215
+ * Handles late updates when AgentStatus is processed before tool result.
216
+ */
217
+ private _handleLateToolResultUpdate(toolCallId: string, toolResultId: string) {
218
+ const toolCallCommitId = this._toolCallCommitIds.get(toolCallId);
219
+ if (!toolCallCommitId) return;
220
+
221
+ const messageId = toolCallCommitId.split('_toolCall_')[0];
222
+ const toolCallBranch = `${messageId}_${toolCallId}`;
223
+ const agentStatusCommitId = this._lastCommitByBranch.get(toolCallBranch);
224
+
225
+ if (agentStatusCommitId && agentStatusCommitId !== toolCallCommitId) {
226
+ const toolResultCommit = this._commits.find((c) => c.id === toolResultId);
227
+ if (toolResultCommit) {
228
+ const toolCallIndex = toolResultCommit.parents.indexOf(toolCallCommitId);
229
+ if (toolCallIndex !== -1) {
230
+ toolResultCommit.parents[toolCallIndex] = agentStatusCommitId;
231
+ }
232
+ }
233
+ this._pendingToolResults.delete(toolCallId);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Gets the branch name for a tool call.
239
+ */
240
+ private _getToolCallBranch(parentMessage?: ObjectId, toolCallId?: string): string {
241
+ if (toolCallId) {
242
+ return toolCallId;
243
+ }
244
+ return parentMessage || 'main';
245
+ }
246
+
247
+ /**
248
+ * Gets the parents for an AgentStatus commit.
249
+ */
250
+ private _getAgentStatusParents(event: AgentStatus): string[] {
251
+ if (event.parentMessage && event.toolCallId) {
252
+ return [getToolCallId(event.parentMessage, event.toolCallId)];
253
+ }
254
+ return [];
255
+ }
256
+
257
+ /**
258
+ * Checks if a commit is a tool call commit.
259
+ */
260
+ private _isToolCallCommit(commit: Commit): boolean {
261
+ return commit.message.includes('Calling tool');
262
+ }
263
+
264
+ /**
265
+ * Checks if a commit is a tool result commit.
266
+ */
267
+ private _isToolResultCommit(commit: Commit): boolean {
268
+ return commit.message.includes('Tool call succeeded') || commit.message.includes('Tool error:');
269
+ }
270
+
271
+ /**
272
+ * Extracts tool call ID from a commit ID.
273
+ */
274
+ private _extractToolCallId(commitId: string): string | undefined {
275
+ const toolCallMatch = commitId.match(/_toolCall_(.+)$/);
276
+ const toolResultMatch = commitId.match(/_toolResult_(.+)$/);
277
+ return toolCallMatch?.[1] || toolResultMatch?.[1];
278
+ }
279
+ }
280
+
281
+ // TODO(burdon): Pass in AiToolProvider.
282
+ /**
283
+ * Creates commits for all blocks in a message.
284
+ */
285
+ const messageToCommits = (
286
+ message: DataType.Message,
287
+ lastBlockId?: string,
288
+ toolCallIds?: Map<string, string>,
289
+ lastCommitByBranch?: Map<string, string>,
290
+ ): Commit[] => {
291
+ let previousBlockId: string | undefined = lastBlockId;
292
+
293
+ return message.blocks
294
+ .map((block, idx) => {
295
+ const branch = getMessageBranch(message);
296
+ const parents = getBlockParents(block, previousBlockId, message, toolCallIds, lastCommitByBranch);
297
+
298
+ const commit = createBlockCommit(block, message, branch, parents, idx);
299
+ if (commit) {
300
+ previousBlockId = commit.id;
301
+ }
302
+
303
+ return commit;
304
+ })
305
+ .filter(isNonNullable);
306
+ };
307
+
308
+ /**
309
+ * Determines the parent commits for a block based on its type and context.
310
+ */
311
+ const getBlockParents = (
312
+ block: ContentBlock.Any,
313
+ previousBlockId: string | undefined,
314
+ message: DataType.Message,
315
+ toolCallIds?: Map<string, string>,
316
+ lastCommitByBranch?: Map<string, string>,
317
+ ): string[] => {
318
+ const parents: string[] = [];
319
+
320
+ if (block._tag === 'toolResult') {
321
+ // Tool results have two parents: previous block and last block from tool call branch
322
+ if (previousBlockId) {
323
+ parents.push(previousBlockId);
324
+ }
325
+
326
+ const toolCallCommitId = toolCallIds?.get(block.toolCallId);
327
+ if (toolCallCommitId) {
328
+ const toolCallBranch = getToolCallBranchFromCommitId(toolCallCommitId, block.toolCallId);
329
+ const lastBlockFromToolCallBranch = lastCommitByBranch?.get(toolCallBranch);
330
+
331
+ if (lastBlockFromToolCallBranch) {
332
+ parents.push(lastBlockFromToolCallBranch);
333
+ } else {
334
+ // Fallback to tool call commit if no AgentStatus exists yet.
335
+ parents.push(toolCallCommitId);
336
+ }
337
+ }
338
+ } else {
339
+ // For other blocks, use previous block as parent (or tool call parent for first block).
340
+ if (previousBlockId) {
341
+ parents.push(previousBlockId);
342
+ } else {
343
+ const toolCallParent = getParentId(message);
344
+ if (toolCallParent) {
345
+ parents.push(toolCallParent);
346
+ }
347
+ }
348
+ }
349
+
350
+ return parents;
351
+ };
352
+
353
+ /**
354
+ * Creates a commit for a specific block.
355
+ */
356
+ const createBlockCommit = (
357
+ block: ContentBlock.Any,
358
+ message: DataType.Message,
359
+ branch: string,
360
+ parents: string[],
361
+ idx: number,
362
+ ): Commit | null => {
363
+ const timestamp = new Date(message.created);
364
+ switch (block._tag) {
365
+ case 'text':
366
+ if (!block.text.trim().length) return null;
367
+ return {
368
+ id: getGenericBlockId(message.id, idx),
369
+ branch,
370
+ parents,
371
+ timestamp,
372
+ ...(message.sender.role === 'user'
373
+ ? {
374
+ icon: IconType.USER,
375
+ tags: ['user'],
376
+ message: 'Processing request...',
377
+ }
378
+ : {
379
+ icon: IconType.AGENT,
380
+ message: `Response (${block.text.split(' ').length} words)`,
381
+ }),
382
+ } satisfies Commit;
383
+
384
+ case 'toolCall':
385
+ return {
386
+ id: getToolCallId(message.id, block.toolCallId),
387
+ branch,
388
+ parents,
389
+ timestamp,
390
+ icon: IconType.TOOL,
391
+ level: LogLevel.INFO,
392
+ message: `Calling tool (${block.name})`,
393
+ } satisfies Commit;
394
+
395
+ case 'toolResult':
396
+ return {
397
+ id: getToolResultId(message.id, block.toolCallId),
398
+ branch,
399
+ parents,
400
+ timestamp,
401
+ icon: block.error ? IconType.ERROR : IconType.SUCCESS,
402
+ level: block.error ? LogLevel.ERROR : LogLevel.INFO,
403
+ message: block.error ? 'Tool error: ' + block.error : 'Tool call succeeded',
404
+ } satisfies Commit;
405
+
406
+ case 'summary':
407
+ return {
408
+ id: getGenericBlockId(message.id, idx),
409
+ branch,
410
+ parents,
411
+ timestamp,
412
+ icon: IconType.ROCKET,
413
+ level: LogLevel.INFO,
414
+ message: ContentBlock.createSummaryMessage(block),
415
+ } satisfies Commit;
416
+
417
+ case 'status':
418
+ return {
419
+ id: getGenericBlockId(message.id, idx),
420
+ branch,
421
+ parents,
422
+ timestamp,
423
+ message: block.statusText,
424
+ level: LogLevel.INFO,
425
+ icon: IconType.FLAG,
426
+ } satisfies Commit;
427
+
428
+ case 'reasoning':
429
+ return {
430
+ id: getGenericBlockId(message.id, idx),
431
+ branch,
432
+ parents,
433
+ timestamp,
434
+ message: block.reasoningText ?? 'Thinking...',
435
+ icon: IconType.THINKING,
436
+ } satisfies Commit;
437
+
438
+ case 'reference':
439
+ return {
440
+ id: getGenericBlockId(message.id, idx),
441
+ branch,
442
+ parents,
443
+ timestamp,
444
+ icon: IconType.LINK,
445
+ message: stringifyRef(block.reference),
446
+ } satisfies Commit;
447
+
448
+ default:
449
+ return null;
450
+ }
451
+ };
452
+
453
+ /**
454
+ * Gets the tool call branch name from a tool call commit ID.
455
+ */
456
+ const getToolCallBranchFromCommitId = (toolCallCommitId: string, toolCallId: string): string => {
457
+ const messageId = toolCallCommitId.split('_toolCall_')[0];
458
+ return `${messageId}_${toolCallId}`;
459
+ };
460
+
461
+ const getToolCallId = (messageId: ObjectId, toolCallId: string) => `${messageId}_toolCall_${toolCallId}`;
462
+ const getToolResultId = (messageId: ObjectId, toolCallId: string) => `${messageId}_toolResult_${toolCallId}`;
463
+ const getGenericBlockId = (messageId: ObjectId, idx: number) => `${messageId}_block_${idx}`;
464
+
465
+ const getBranchName = (options: { parentMessage?: ObjectId; toolCallId?: string }) => {
466
+ if (options.toolCallId) {
467
+ return options.toolCallId;
468
+ } else if (options.parentMessage) {
469
+ return options.parentMessage;
470
+ } else {
471
+ return 'main';
472
+ }
473
+ };
474
+
475
+ const getMessageBranch = (message: DataType.Message) => {
476
+ return getBranchName({
477
+ parentMessage: message.parentMessage,
478
+ toolCallId: message.properties?.[MESSAGE_PROPERTY_TOOL_CALL_ID],
479
+ });
480
+ };
481
+
482
+ const getParentId = (message: DataType.Message) => {
483
+ if (message.parentMessage && message.properties?.[MESSAGE_PROPERTY_TOOL_CALL_ID]) {
484
+ return getToolCallId(message.parentMessage, message.properties[MESSAGE_PROPERTY_TOOL_CALL_ID]);
485
+ } else {
486
+ return undefined;
487
+ }
488
+ };
489
+
490
+ const stringifyRef = (ref: Ref.Any) => {
491
+ if (ref.target) {
492
+ return stringifyObject(ref.target);
493
+ }
494
+
495
+ return ref.dxn.asEchoDXN()?.echoId ?? ref.dxn.asQueueDXN()?.objectId ?? '';
496
+ };
497
+
498
+ const stringifyObject = (obj: Obj.Any) => {
499
+ return Obj.getLabel(obj) ?? Obj.getTypename(obj) ?? obj.id;
500
+ };
@@ -11,7 +11,8 @@ import { createTestData } from '@dxos/schema/testing';
11
11
  import { range } from '@dxos/util';
12
12
 
13
13
  import { processTranscriptMessage } from '../extraction';
14
- import { extractionAnthropicFn } from './extraction-llm-function';
14
+
15
+ import { extractionAnthropicFunction } from './extraction-llm-function';
15
16
 
16
17
  // TODO(burdon): Rewrite test.
17
18
  describe.skip('LLM EntityExtraction', () => {
@@ -32,7 +33,7 @@ describe.skip('LLM EntityExtraction', () => {
32
33
  message,
33
34
  objects: [...documents, ...Object.values(contacts)],
34
35
  },
35
- function: extractionAnthropicFn,
36
+ function: extractionAnthropicFunction,
36
37
  executor,
37
38
  });
38
39
  log.info('output', enhancedMessage);
@@ -41,8 +42,8 @@ describe.skip('LLM EntityExtraction', () => {
41
42
 
42
43
  test('computational irreducibility', async () => {
43
44
  const { transcriptWoflram, documents, contacts } = createTestData();
44
-
45
45
  log.info('context', { documents, contacts });
46
+
46
47
  const message = transcriptWoflram[0];
47
48
  log.info('input', message);
48
49
 
@@ -53,9 +54,10 @@ describe.skip('LLM EntityExtraction', () => {
53
54
  message,
54
55
  objects: [...documents, ...Object.values(contacts)],
55
56
  },
56
- function: extractionAnthropicFn,
57
+ function: extractionAnthropicFunction,
57
58
  executor,
58
59
  });
60
+
59
61
  log.info('output', { message: enhancedMessage.blocks[0], timeElapsed });
60
62
  }),
61
63
  );
@@ -63,20 +65,19 @@ describe.skip('LLM EntityExtraction', () => {
63
65
 
64
66
  test('org and document linking', async () => {
65
67
  const { transcriptJosiah, documents, contacts, organizations } = createTestData();
66
-
67
68
  log.info('context', { contacts, organizations, documents });
68
69
 
69
70
  for (const message of transcriptJosiah) {
70
71
  log.info('input', message);
71
-
72
72
  const { message: enhancedMessage } = await processTranscriptMessage({
73
73
  input: {
74
74
  message,
75
75
  objects: [...documents, ...Object.values(contacts), ...Object.values(organizations)],
76
76
  },
77
- function: extractionAnthropicFn,
77
+ function: extractionAnthropicFunction,
78
78
  executor,
79
79
  });
80
+
80
81
  log.info('output', enhancedMessage);
81
82
  }
82
83
  });
@@ -2,27 +2,30 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- // TODO(burdon): Fix.
5
+ // ISSUE(burdon): defineFunction
6
6
  // @ts-nocheck
7
7
 
8
- import { ToolRegistry, AiService } from '@dxos/ai';
8
+ import { AiService } from '@dxos/ai';
9
9
  import { Obj } from '@dxos/echo';
10
- import { create } from '@dxos/echo-schema';
11
- import { defineFunction, type FunctionDefinition } from '@dxos/functions';
10
+ import { create } from '@dxos/echo/internal';
11
+ import { type FunctionDefinition, defineFunction } from '@dxos/functions';
12
12
  import { type ContentBlock, DataType } from '@dxos/schema';
13
13
 
14
- import PROMPT from './instructions.tpl?raw';
15
14
  import { AiSession } from '../../session';
16
15
  import { ExtractionInput, ExtractionOutput } from '../extraction';
17
- import { insertReferences, ReferencedQuotes } from '../quotes';
16
+ import { ReferencedQuotes, insertReferences } from '../quotes';
17
+
18
+ import PROMPT from './instructions.tpl?raw';
18
19
 
19
- export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, ExtractionOutput> = defineFunction({
20
+ export const extractionAnthropicFunction: FunctionDefinition<ExtractionInput, ExtractionOutput> = defineFunction({
21
+ key: 'dxos.org/function/extraction/extract-entities',
22
+ name: 'Extract Entities',
20
23
  description: 'Extract entities from the transcript message and add them to the message.',
21
24
  inputSchema: ExtractionInput,
22
25
  outputSchema: ExtractionOutput,
23
26
  handler: async ({ data: { message, objects }, context }) => {
24
27
  const startTime = performance.now();
25
- const ai = context.getService(AiService);
28
+ const ai = context.getService(AiService.AiService);
26
29
  const session = new AiSession({ operationModel: 'configured' });
27
30
  const result = await session.runStructured(ReferencedQuotes, {
28
31
  generationOptions: {
@@ -49,13 +52,13 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
49
52
  artifacts: [],
50
53
  prompt: '',
51
54
  tools: [],
52
- toolResolver: new ToolRegistry([]),
53
55
  } as any); // TODO(burdon): Rewrite test.
54
56
 
55
57
  return {
58
+ timeElapsed: performance.now() - startTime,
56
59
  message: create(DataType.Message, {
57
60
  ...message,
58
- blocks: message.blocks.map((block, i) =>
61
+ blocks: message.blocks.map((block) =>
59
62
  block._tag !== 'transcript'
60
63
  ? block
61
64
  : {
@@ -64,7 +67,6 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
64
67
  },
65
68
  ),
66
69
  }),
67
- timeElapsed: performance.now() - startTime,
68
70
  };
69
71
  },
70
72
  });
@@ -2,19 +2,20 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { describe, test, beforeAll } from 'vitest';
5
+ import { beforeAll, describe, test } from 'vitest';
6
6
 
7
7
  import { type EchoDatabase } from '@dxos/echo-db';
8
8
  import { EchoTestBuilder } from '@dxos/echo-db/testing';
9
9
  import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
10
10
  import { log } from '@dxos/log';
11
11
  import { DataType } from '@dxos/schema';
12
- import { createTestData, Testing } from '@dxos/schema/testing';
12
+ import { Testing, createTestData } from '@dxos/schema/testing';
13
13
  import { range } from '@dxos/util';
14
14
 
15
- import { extractionNerFn } from './extraction-ner-function';
16
15
  import { processTranscriptMessage } from '../extraction';
17
16
 
17
+ import { extractionNerFunction } from './extraction-ner-function';
18
+
18
19
  describe.skip('NER EntityExtraction', () => {
19
20
  let builder: EchoTestBuilder;
20
21
  let db: EchoDatabase;
@@ -27,6 +28,7 @@ describe.skip('NER EntityExtraction', () => {
27
28
  contacts: Record<string, DataType.Person>;
28
29
  organizations: Record<string, DataType.Organization>;
29
30
  };
31
+
30
32
  const TYPES = [DataType.Organization, DataType.Person, Testing.Contact, Testing.DocumentType];
31
33
 
32
34
  beforeAll(async () => {
@@ -44,8 +46,8 @@ describe.skip('NER EntityExtraction', () => {
44
46
  contacts: Object.fromEntries(Object.entries(data.contacts).map(([key, value]) => [key, db.add(value)])),
45
47
  organizations: Object.fromEntries(Object.entries(data.organizations).map(([key, value]) => [key, db.add(value)])),
46
48
  };
47
- await db.flush();
48
49
 
50
+ await db.flush();
49
51
  executor = new FunctionExecutor(
50
52
  new ServiceContainer().setServices({
51
53
  database: { db },
@@ -64,7 +66,7 @@ describe.skip('NER EntityExtraction', () => {
64
66
  message,
65
67
  objects: [...documents, ...Object.values(contacts)],
66
68
  },
67
- function: extractionNerFn,
69
+ function: extractionNerFunction,
68
70
  executor,
69
71
  });
70
72
  log.info('output', enhancedMessage);
@@ -73,7 +75,6 @@ describe.skip('NER EntityExtraction', () => {
73
75
 
74
76
  test('computational irreducibility', async () => {
75
77
  const { transcriptWoflram, documents, contacts } = testData;
76
-
77
78
  log.info('context', { documents, contacts });
78
79
  const message = transcriptWoflram[0];
79
80
  log.info('input', message);
@@ -85,7 +86,7 @@ describe.skip('NER EntityExtraction', () => {
85
86
  message,
86
87
  objects: [...documents, ...Object.values(contacts)],
87
88
  },
88
- function: extractionNerFn,
89
+ function: extractionNerFunction,
89
90
  executor,
90
91
  });
91
92
  log.info('output', { message: enhancedMessage.blocks[0], timeElapsed });
@@ -95,7 +96,6 @@ describe.skip('NER EntityExtraction', () => {
95
96
 
96
97
  test('org and document linking', async () => {
97
98
  const { transcriptJosiah, documents, contacts, organizations } = testData;
98
-
99
99
  log.info('context', { contacts, organizations, documents });
100
100
 
101
101
  for (const message of transcriptJosiah) {
@@ -106,7 +106,7 @@ describe.skip('NER EntityExtraction', () => {
106
106
  message,
107
107
  objects: [...documents, ...Object.values(contacts), ...Object.values(organizations)],
108
108
  },
109
- function: extractionNerFn,
109
+ function: extractionNerFunction,
110
110
  executor,
111
111
  });
112
112
  log.info('output', enhancedMessage);