@dxos/assistant 0.8.2-main.5885341 → 0.8.2-main.600d381

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 (157) hide show
  1. package/dist/lib/browser/{chunk-DEIGOD4U.mjs → chunk-YSYP7S4W.mjs} +706 -242
  2. package/dist/lib/browser/chunk-YSYP7S4W.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +4 -443
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +64 -27
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/{chunk-7VH7NMIM.cjs → chunk-FTIDW4BA.cjs} +716 -259
  9. package/dist/lib/node/chunk-FTIDW4BA.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +34 -470
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +53 -18
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/lib/node-esm/{chunk-7JI4MDGL.mjs → chunk-GCJNPQDJ.mjs} +706 -242
  16. package/dist/lib/node-esm/chunk-GCJNPQDJ.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +4 -443
  18. package/dist/lib/node-esm/index.mjs.map +4 -4
  19. package/dist/lib/node-esm/meta.json +1 -1
  20. package/dist/lib/node-esm/testing/index.mjs +64 -27
  21. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  22. package/dist/types/src/ai-service/parser.d.ts.map +1 -1
  23. package/dist/types/src/ai-service/stream.d.ts.map +1 -1
  24. package/dist/types/src/ai-service/testing/test-stream.d.ts.map +1 -1
  25. package/dist/types/src/ai-service/types.d.ts +130 -130
  26. package/dist/types/src/ai-service/types.d.ts.map +1 -1
  27. package/dist/types/src/ai-service/util/sse.d.ts.map +1 -1
  28. package/dist/types/src/{conversation → experimental/conversation}/conversation.d.ts +2 -18
  29. package/dist/types/src/experimental/conversation/conversation.d.ts.map +1 -0
  30. package/dist/types/src/experimental/conversation/conversation.test.d.ts.map +1 -0
  31. package/dist/types/src/experimental/conversation/index.d.ts.map +1 -0
  32. package/dist/types/src/{conversation → experimental/conversation}/types.d.ts +9 -9
  33. package/dist/types/src/experimental/conversation/types.d.ts.map +1 -0
  34. package/dist/types/src/experimental/cypher/ast.d.ts.map +1 -0
  35. package/dist/types/src/experimental/cypher/combinators.d.ts.map +1 -0
  36. package/dist/types/src/experimental/cypher/combinators.test.d.ts.map +1 -0
  37. package/dist/types/src/experimental/cypher/echo-data-source.d.ts.map +1 -0
  38. package/dist/types/src/experimental/cypher/execution-plan.d.ts.map +1 -0
  39. package/dist/types/src/experimental/cypher/execution-plan.test.d.ts.map +1 -0
  40. package/dist/types/src/experimental/cypher/index.d.ts.map +1 -0
  41. package/dist/types/src/experimental/cypher/parjs.d.ts.map +1 -0
  42. package/dist/types/src/experimental/cypher/parser.d.ts.map +1 -0
  43. package/dist/types/src/experimental/cypher/query-executor.d.ts.map +1 -0
  44. package/dist/types/src/experimental/cypher/query-executor.test.d.ts.map +1 -0
  45. package/dist/types/src/experimental/cypher/schema.d.ts.map +1 -0
  46. package/dist/types/src/experimental/cypher/schema.test.d.ts.map +1 -0
  47. package/dist/types/src/experimental/index.d.ts +3 -0
  48. package/dist/types/src/experimental/index.d.ts.map +1 -0
  49. package/dist/types/src/experimental/ner/ner.test.d.ts +2 -0
  50. package/dist/types/src/experimental/ner/ner.test.d.ts.map +1 -0
  51. package/dist/types/src/experimental/research/exa.d.ts +6 -0
  52. package/dist/types/src/experimental/research/exa.d.ts.map +1 -0
  53. package/dist/types/src/experimental/research/research.test.d.ts +2 -0
  54. package/dist/types/src/experimental/research/research.test.d.ts.map +1 -0
  55. package/dist/types/src/index.d.ts +1 -2
  56. package/dist/types/src/index.d.ts.map +1 -1
  57. package/dist/types/src/session/index.d.ts +1 -0
  58. package/dist/types/src/session/index.d.ts.map +1 -1
  59. package/dist/types/src/session/session.d.ts +4 -3
  60. package/dist/types/src/session/session.d.ts.map +1 -1
  61. package/dist/types/src/session/tools.d.ts +18 -0
  62. package/dist/types/src/session/tools.d.ts.map +1 -0
  63. package/dist/types/src/session/version-pin.d.ts +5 -0
  64. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  65. package/dist/types/src/testing/index.d.ts +1 -0
  66. package/dist/types/src/testing/index.d.ts.map +1 -1
  67. package/dist/types/src/testing/logger.d.ts +1 -1
  68. package/dist/types/src/testing/logger.d.ts.map +1 -1
  69. package/dist/types/src/testing/ollama.d.ts.map +1 -1
  70. package/dist/types/src/testing/printer.d.ts +6 -0
  71. package/dist/types/src/testing/printer.d.ts.map +1 -0
  72. package/dist/types/src/testing/query-promts.d.ts +4 -4
  73. package/dist/types/src/testing/query-promts.d.ts.map +1 -1
  74. package/dist/types/src/testing/test-data.d.ts +5 -5
  75. package/dist/types/src/testing/test-data.d.ts.map +1 -1
  76. package/dist/types/src/testing/test-schema.d.ts +25 -23
  77. package/dist/types/src/testing/test-schema.d.ts.map +1 -1
  78. package/dist/types/src/util/trim.d.ts.map +1 -1
  79. package/dist/types/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +19 -13
  81. package/src/ai-service/client.test.ts +5 -5
  82. package/src/ai-service/ollama-client.ts +8 -8
  83. package/src/ai-service/types.ts +14 -14
  84. package/src/bin/chat.ts +1 -1
  85. package/src/{conversation → experimental/conversation}/conversation.test.ts +5 -5
  86. package/src/{conversation → experimental/conversation}/conversation.ts +3 -112
  87. package/src/{conversation → experimental/conversation}/types.ts +10 -10
  88. package/src/{cypher → experimental/cypher}/echo-data-source.ts +7 -5
  89. package/src/{cypher → experimental/cypher}/query-executor.test.ts +1 -1
  90. package/src/{cypher → experimental/cypher}/schema.test.ts +1 -1
  91. package/src/{cypher → experimental/cypher}/schema.ts +1 -1
  92. package/src/experimental/index.ts +6 -0
  93. package/src/experimental/ner/ner.test.ts +163 -0
  94. package/src/experimental/research/exa.ts +38 -0
  95. package/src/experimental/research/instructions.tpl +64 -0
  96. package/src/experimental/research/research.test.ts +263 -0
  97. package/src/index.ts +3 -2
  98. package/src/session/index.ts +1 -0
  99. package/src/session/session.test.ts +28 -57
  100. package/src/session/session.ts +25 -15
  101. package/src/session/tools.ts +117 -0
  102. package/src/session/version-pin.ts +6 -0
  103. package/src/testing/artifacts.test.ts +22 -22
  104. package/src/testing/index.ts +1 -0
  105. package/src/testing/logger.ts +1 -1
  106. package/src/testing/printer.ts +41 -0
  107. package/src/testing/query-promts.ts +6 -6
  108. package/src/testing/query.test.ts +1 -2
  109. package/src/testing/test-data.ts +5 -5
  110. package/src/testing/test-schema.ts +27 -28
  111. package/src/typedefs.d.ts +8 -0
  112. package/dist/lib/browser/chunk-DEIGOD4U.mjs.map +0 -7
  113. package/dist/lib/node/chunk-7VH7NMIM.cjs.map +0 -7
  114. package/dist/lib/node-esm/chunk-7JI4MDGL.mjs.map +0 -7
  115. package/dist/types/src/conversation/conversation.d.ts.map +0 -1
  116. package/dist/types/src/conversation/conversation.test.d.ts.map +0 -1
  117. package/dist/types/src/conversation/index.d.ts.map +0 -1
  118. package/dist/types/src/conversation/types.d.ts.map +0 -1
  119. package/dist/types/src/cypher/ast.d.ts.map +0 -1
  120. package/dist/types/src/cypher/combinators.d.ts.map +0 -1
  121. package/dist/types/src/cypher/combinators.test.d.ts.map +0 -1
  122. package/dist/types/src/cypher/echo-data-source.d.ts.map +0 -1
  123. package/dist/types/src/cypher/execution-plan.d.ts.map +0 -1
  124. package/dist/types/src/cypher/execution-plan.test.d.ts.map +0 -1
  125. package/dist/types/src/cypher/index.d.ts.map +0 -1
  126. package/dist/types/src/cypher/parjs.d.ts.map +0 -1
  127. package/dist/types/src/cypher/parser.d.ts.map +0 -1
  128. package/dist/types/src/cypher/query-executor.d.ts.map +0 -1
  129. package/dist/types/src/cypher/query-executor.test.d.ts.map +0 -1
  130. package/dist/types/src/cypher/schema.d.ts.map +0 -1
  131. package/dist/types/src/cypher/schema.test.d.ts.map +0 -1
  132. /package/dist/types/src/{conversation → experimental/conversation}/conversation.test.d.ts +0 -0
  133. /package/dist/types/src/{conversation → experimental/conversation}/index.d.ts +0 -0
  134. /package/dist/types/src/{cypher → experimental/cypher}/ast.d.ts +0 -0
  135. /package/dist/types/src/{cypher → experimental/cypher}/combinators.d.ts +0 -0
  136. /package/dist/types/src/{cypher → experimental/cypher}/combinators.test.d.ts +0 -0
  137. /package/dist/types/src/{cypher → experimental/cypher}/echo-data-source.d.ts +0 -0
  138. /package/dist/types/src/{cypher → experimental/cypher}/execution-plan.d.ts +0 -0
  139. /package/dist/types/src/{cypher → experimental/cypher}/execution-plan.test.d.ts +0 -0
  140. /package/dist/types/src/{cypher → experimental/cypher}/index.d.ts +0 -0
  141. /package/dist/types/src/{cypher → experimental/cypher}/parjs.d.ts +0 -0
  142. /package/dist/types/src/{cypher → experimental/cypher}/parser.d.ts +0 -0
  143. /package/dist/types/src/{cypher → experimental/cypher}/query-executor.d.ts +0 -0
  144. /package/dist/types/src/{cypher → experimental/cypher}/query-executor.test.d.ts +0 -0
  145. /package/dist/types/src/{cypher → experimental/cypher}/schema.d.ts +0 -0
  146. /package/dist/types/src/{cypher → experimental/cypher}/schema.test.d.ts +0 -0
  147. /package/src/{conversation → experimental/conversation}/index.ts +0 -0
  148. /package/src/{cypher → experimental/cypher}/ast.ts +0 -0
  149. /package/src/{cypher → experimental/cypher}/combinators.test.ts +0 -0
  150. /package/src/{cypher → experimental/cypher}/combinators.ts +0 -0
  151. /package/src/{cypher → experimental/cypher}/execution-plan.test.ts +0 -0
  152. /package/src/{cypher → experimental/cypher}/execution-plan.ts +0 -0
  153. /package/src/{cypher → experimental/cypher}/index.ts +0 -0
  154. /package/src/{cypher → experimental/cypher}/parjs-shims.d.ts +0 -0
  155. /package/src/{cypher → experimental/cypher}/parjs.ts +0 -0
  156. /package/src/{cypher → experimental/cypher}/parser.ts +0 -0
  157. /package/src/{cypher → experimental/cypher}/query-executor.ts +0 -0
@@ -2,35 +2,33 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema as S } from 'effect';
6
- import { inspect } from 'node:util';
5
+ import { Schema } from 'effect';
7
6
  import { describe, test } from 'vitest';
8
7
 
9
- import {
10
- ArtifactId,
11
- defineArtifact,
12
- defineTool,
13
- ToolResult,
14
- type Message,
15
- type MessageContentBlock,
16
- } from '@dxos/artifact';
8
+ import { ArtifactId, defineArtifact, defineTool, ToolResult } from '@dxos/artifact';
17
9
  import { AIServiceEdgeClient } from '@dxos/assistant';
18
- import { create, EchoObject, ObjectId } from '@dxos/echo-schema';
10
+ import { Type } from '@dxos/echo';
11
+ import { create, ObjectId } from '@dxos/echo-schema';
19
12
  import { DXN } from '@dxos/keys';
20
13
  import { log } from '@dxos/log';
21
14
 
22
15
  import { AISession } from './session';
23
- import { AI_SERVICE_ENDPOINT } from '../testing';
16
+ import { AI_SERVICE_ENDPOINT, ConsolePrinter } from '../testing';
24
17
 
25
18
  // Define a calendar event artifact schema.
26
- const CalendarEventSchema = S.Struct({
27
- title: S.String,
28
- startTime: S.String,
29
- endTime: S.String,
30
- description: S.String,
31
- }).pipe(EchoObject({ typename: 'example.com/type/CalendarEvent', version: '0.1.0' }));
19
+ const CalendarEventSchema = Schema.Struct({
20
+ title: Schema.String,
21
+ startTime: Schema.String,
22
+ endTime: Schema.String,
23
+ description: Schema.String,
24
+ }).pipe(
25
+ Type.def({
26
+ typename: 'example.com/type/CalendarEvent',
27
+ version: '0.1.0',
28
+ }),
29
+ );
32
30
 
33
- type CalendarEvent = S.Schema.Type<typeof CalendarEventSchema>;
31
+ type CalendarEvent = Schema.Schema.Type<typeof CalendarEventSchema>;
34
32
 
35
33
  // TODO(burdon): Flaky.
36
34
  describe.skip('AISession with Ollama', () => {
@@ -53,7 +51,7 @@ describe.skip('AISession with Ollama', () => {
53
51
  defineTool('calendar', {
54
52
  name: 'query',
55
53
  description: 'Query the calendar for events',
56
- schema: S.Struct({}),
54
+ schema: Schema.Struct({}),
57
55
  execute: async () => {
58
56
  return ToolResult.Success(CALENDAR_EVENTS);
59
57
  },
@@ -65,13 +63,13 @@ describe.skip('AISession with Ollama', () => {
65
63
  id: 'artifact:dxos.org/plugin/table',
66
64
  name: 'Table',
67
65
  instructions: 'Use this to create and manage tables. Each table has a unique id.',
68
- schema: S.Struct({}),
66
+ schema: Schema.Struct({}),
69
67
  tools: [
70
68
  defineTool('table', {
71
69
  name: 'create',
72
70
  description: 'Create a table',
73
- schema: S.Struct({
74
- data: S.Array(S.Any).annotations({ description: 'Array of data payloads to add as rows' }),
71
+ schema: Schema.Struct({
72
+ data: Schema.Array(Schema.Any).annotations({ description: 'Array of data payloads to add as rows' }),
75
73
  }),
76
74
  execute: async ({ data }) => {
77
75
  log('create table', { data });
@@ -89,12 +87,12 @@ describe.skip('AISession with Ollama', () => {
89
87
  name: 'Map',
90
88
  instructions:
91
89
  'Use this to create and manage maps. Maps source data from tables. Table id is required to create a map.',
92
- schema: S.Struct({}),
90
+ schema: Schema.Struct({}),
93
91
  tools: [
94
92
  defineTool('map', {
95
93
  name: 'create',
96
94
  description: 'Create a map',
97
- schema: S.Struct({
95
+ schema: Schema.Struct({
98
96
  source: ArtifactId.annotations({
99
97
  description: 'The table that will be used as the source of the map',
100
98
  }),
@@ -119,7 +117,7 @@ describe.skip('AISession with Ollama', () => {
119
117
  id: 'artifact:dxos.org/plugin/script',
120
118
  name: 'Script',
121
119
  instructions: 'Use this to create and manage scripts',
122
- schema: S.Struct({}),
120
+ schema: Schema.Struct({}),
123
121
  tools: [],
124
122
  });
125
123
 
@@ -127,9 +125,10 @@ describe.skip('AISession with Ollama', () => {
127
125
  // printStreamEvent(event);
128
126
  // });
129
127
 
130
- session.message.on(printMessage);
131
- session.userMessage.on(printMessage);
132
- session.block.on(printContentBlock);
128
+ const printer = new ConsolePrinter();
129
+ session.message.on((message) => printer.printMessage(message));
130
+ session.userMessage.on((message) => printer.printMessage(message));
131
+ session.block.on((block) => printer.printContentBlock(block));
133
132
 
134
133
  // session.update.on((update) => {
135
134
  // log('update', { update });
@@ -189,34 +188,6 @@ const CALENDAR_EVENTS: CalendarEvent[] = [
189
188
  }),
190
189
  ];
191
190
 
192
- const printMessage = (message: Message) => {
193
- console.log(`${message.role.toUpperCase()}\n`);
194
- for (const content of message.content) {
195
- printContentBlock(content);
196
- }
197
- };
198
-
199
- const printContentBlock = (content: MessageContentBlock) => {
200
- switch (content.type) {
201
- case 'text':
202
- console.log(content.text);
203
- break;
204
- case 'tool_use':
205
- console.log(`⚙️ [Tool Use] ${content.name} ${inspect(content.input, { depth: null, colors: true })}`);
206
- break;
207
- case 'tool_result': {
208
- let data: any;
209
- try {
210
- data = JSON.parse(content.content);
211
- } catch {
212
- data = content.content;
213
- }
214
- console.log(`⚙️ [Tool Result] ${content.toolUseId} ${inspect(data, { depth: null, colors: true })}`);
215
- break;
216
- }
217
- }
218
- };
219
-
220
191
  // const printStreamEvent = (event: GenerationStreamEvent) => {
221
192
  // switch (event.type) {
222
193
  // case 'message_start': {
@@ -2,11 +2,12 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Option, Schema as S } from 'effect';
5
+ import { Option, Schema } from 'effect';
6
6
 
7
7
  import {
8
8
  defineTool,
9
9
  Message,
10
+ structuredOutputParser,
10
11
  ToolResult,
11
12
  type ArtifactDefinition,
12
13
  type MessageContentBlock,
@@ -18,9 +19,9 @@ import { create, type ObjectId } from '@dxos/echo-schema';
18
19
  import { invariant } from '@dxos/invariant';
19
20
  import { log } from '@dxos/log';
20
21
 
22
+ import { isToolUse, runTools } from './tools';
21
23
  import { VersionPin } from './version-pin';
22
24
  import { MixedStreamParser, type AIServiceClient, type GenerateRequest, type GenerationStream } from '../ai-service';
23
- import { isToolUse, runTools } from '../conversation';
24
25
 
25
26
  /**
26
27
  * Contains message history, tools, current context.
@@ -245,6 +246,15 @@ export class AISession {
245
246
  return this._pending;
246
247
  }
247
248
 
249
+ async runStructured<S extends Schema.Schema.AnyNoContext>(schema: S, options: SessionRunOptions) {
250
+ const parser = structuredOutputParser(schema);
251
+ const result = await this.run({
252
+ ...options,
253
+ tools: [...options.tools, parser.tool],
254
+ });
255
+ return parser.getResult(result);
256
+ }
257
+
248
258
  private async _formatUserPrompt(
249
259
  artifactDiffResolver: ArtifactDiffResolver | undefined,
250
260
  prompt: string,
@@ -287,7 +297,7 @@ export class AISession {
287
297
  return defineTool('system', {
288
298
  name: 'query_artifact_definitions',
289
299
  description: 'Query the available artifact definitions',
290
- schema: S.Struct({}),
300
+ schema: Schema.Struct({}),
291
301
  execute: async () => {
292
302
  return ToolResult.Success({
293
303
  artifactDefinitions: artifacts.map((artifact) => ({
@@ -308,8 +318,8 @@ export class AISession {
308
318
  name: 'require_artifact_definitions',
309
319
  description:
310
320
  'Require the use of specific artifact definitions. This will allow the model to interact with artifact definitions and use their tools.',
311
- schema: S.Struct({
312
- artifactDefinitionIds: S.Array(S.String).annotations({
321
+ schema: Schema.Struct({
322
+ artifactDefinitionIds: Schema.Array(Schema.String).annotations({
313
323
  description: 'The ids of the artifact definitions to require',
314
324
  examples: [['artifact:dxos.org/example/Test']],
315
325
  }),
@@ -336,20 +346,20 @@ export class AISession {
336
346
  name: 'create_plan',
337
347
  description:
338
348
  'Create a plan. Make sure that each step is independent and can be executed solely on the data returned by the previous step. The steps only share the data that is specified in the plan.',
339
- schema: S.Struct({
340
- goal: S.String.annotations({ description: 'The goal that the plan will achieve.' }),
341
- steps: S.Array(
342
- S.Struct({
343
- action: S.String.annotations({
349
+ schema: Schema.Struct({
350
+ goal: Schema.String.annotations({ description: 'The goal that the plan will achieve.' }),
351
+ steps: Schema.Array(
352
+ Schema.Struct({
353
+ action: Schema.String.annotations({
344
354
  description: 'Complete, detailed action to perform.',
345
355
  }),
346
- input: S.String.annotations({
356
+ input: Schema.String.annotations({
347
357
  description: 'The required input data for this step. First step must not require any data.',
348
358
  }),
349
- output: S.String.annotations({
359
+ output: Schema.String.annotations({
350
360
  description: 'The output data from this step. This is passed to the next step as input.',
351
361
  }),
352
- requiredArtifactIds: S.Array(S.String).annotations({
362
+ requiredArtifactIds: Schema.Array(Schema.String).annotations({
353
363
  description: 'The ids of the artifacts required to perform this step.',
354
364
  examples: [['artifact:dxos.org/example/Test']],
355
365
  }),
@@ -473,7 +483,7 @@ const createBaseInstructions = ({
473
483
  ${availableArtifacts.length > 0 ? `Artifacts already in context: ${availableArtifacts.join('\n')}` : ''}
474
484
  `;
475
485
 
476
- export class AIServiecOverloadedError extends S.TaggedError<AIServiecOverloadedError>()(
486
+ export class AIServiecOverloadedError extends Schema.TaggedError<AIServiecOverloadedError>()(
477
487
  'AIServiecOverloadedError',
478
488
  {},
479
489
  ) {}
@@ -483,7 +493,7 @@ const gatherObjectVersions = (messages: Message[]): Map<ObjectId, ObjectVersion>
483
493
  for (const message of messages) {
484
494
  for (const block of message.content) {
485
495
  if (block.type === 'json' && block.disposition === VersionPin.DISPOSITION) {
486
- const pin = VersionPin.pipe(S.decodeOption)(JSON.parse(block.json)).pipe(Option.getOrUndefined);
496
+ const pin = VersionPin.pipe(Schema.decodeOption)(JSON.parse(block.json)).pipe(Option.getOrUndefined);
487
497
  if (!pin) {
488
498
  continue;
489
499
  }
@@ -0,0 +1,117 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Message, type Tool, ToolResult } from '@dxos/artifact';
6
+ import { create } from '@dxos/echo-schema';
7
+ import { invariant } from '@dxos/invariant';
8
+ import { log } from '@dxos/log';
9
+
10
+ export const isToolUse = (message: Message, { onlyToolNames }: { onlyToolNames?: string[] } = {}) => {
11
+ const block = message.content.at(-1);
12
+ invariant(block);
13
+ return block.type === 'tool_use' && (!onlyToolNames || onlyToolNames.includes(block.name));
14
+ };
15
+
16
+ export type RunToolsOptions = {
17
+ message: Message;
18
+ tools: Tool[];
19
+ extensions?: ToolContextExtensions;
20
+ };
21
+
22
+ export type RunToolsResult =
23
+ | {
24
+ type: 'continue';
25
+ message: Message;
26
+ }
27
+ | {
28
+ type: 'break';
29
+ result: unknown;
30
+ };
31
+
32
+ export const runTools = async ({ message, tools, extensions }: RunToolsOptions): Promise<RunToolsResult> => {
33
+ const toolCalls = message.content.filter((block) => block.type === 'tool_use');
34
+ invariant(toolCalls.length === 1);
35
+ const toolCall = toolCalls[0];
36
+ const tool = tools.find((tool) => tool.name === toolCall.name);
37
+ if (!tool) {
38
+ const resultMessage = create(Message, {
39
+ role: 'user',
40
+ content: [
41
+ {
42
+ type: 'tool_result',
43
+ toolUseId: toolCall.id,
44
+ content: `Tool not found: ${toolCall.name}`,
45
+ isError: true,
46
+ },
47
+ ],
48
+ });
49
+
50
+ // TODO(burdon): Retry count?
51
+ return {
52
+ type: 'continue',
53
+ message: resultMessage,
54
+ };
55
+ }
56
+
57
+ let toolResult: ToolResult;
58
+ try {
59
+ invariant(tool.execute);
60
+ toolResult = await tool.execute(toolCall.input, { extensions });
61
+ } catch (error: any) {
62
+ log('tool error', { error });
63
+ toolResult = ToolResult.Error(error.message);
64
+ }
65
+
66
+ switch (toolResult.kind) {
67
+ case 'error': {
68
+ log('tool error', { message: toolResult.message });
69
+ const resultMessage = create(Message, {
70
+ role: 'user',
71
+ content: [
72
+ {
73
+ type: 'tool_result',
74
+ toolUseId: toolCall.id,
75
+ content: toolResult.message,
76
+ isError: true,
77
+ },
78
+ ],
79
+ });
80
+
81
+ // TODO(burdon): Retry count?
82
+ return {
83
+ type: 'continue',
84
+ message: resultMessage,
85
+ };
86
+ }
87
+
88
+ case 'success': {
89
+ log('tool success', { result: toolResult.result });
90
+ const resultMessage = create(Message, {
91
+ role: 'user',
92
+ content: [
93
+ {
94
+ type: 'tool_result',
95
+ toolUseId: toolCall.id,
96
+ content:
97
+ typeof toolResult.result === 'string' ? toolResult.result : JSON.stringify(toolResult.result) ?? '',
98
+ },
99
+ ...(toolResult.extractContentBlocks ?? []),
100
+ ],
101
+ });
102
+
103
+ return {
104
+ type: 'continue',
105
+ message: resultMessage,
106
+ };
107
+ }
108
+
109
+ case 'break': {
110
+ log('tool break', { result: toolResult.result });
111
+ return {
112
+ type: 'break',
113
+ result: toolResult.result,
114
+ };
115
+ }
116
+ }
117
+ };
@@ -18,6 +18,12 @@ const VersionPinSchema = Schema.Struct({
18
18
  // TODO(dmaretskyi): Could be opaque
19
19
  version: ObjectVersionSchema,
20
20
  });
21
+
22
+ /**
23
+ * Used to pin a specific version of an object during the course of a conversation.
24
+ *
25
+ * This allows us to detect when the object changes during the course of the conversation.
26
+ */
21
27
  export interface VersionPin extends Schema.Schema.Type<typeof VersionPinSchema> {}
22
28
 
23
29
  export const VersionPin: typeof VersionPinSchema & {
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  /*
6
- import { Schema as S } from '@effect/schema';
6
+ import { Schema } from '@effect/schema';
7
7
  import { describe, test } from 'vitest';
8
8
 
9
9
  import { ObjectId, toJsonSchema } from '@dxos/echo-schema';
@@ -15,26 +15,26 @@ import { AIServiceEdgeClient, Tool } from '../ai-service';
15
15
  import { defineTool, ToolResult } from '../conversation';
16
16
 
17
17
  // TODO(dmaretskyi): Effect schema.
18
- const ArtifactDef = S.Struct({
19
- typename: S.String,
20
- description: S.String,
21
- schema: S.Any,
22
- actions: S.Array(Tool),
23
- }).pipe(S.mutable);
18
+ const ArtifactDef = Schema.Struct({
19
+ typename: Schema.String,
20
+ description: Schema.String,
21
+ schema: Schema.Any,
22
+ actions: Schema.Array(Tool),
23
+ }).pipe(Schema.mutable);
24
24
 
25
- type ArtifactDef = S.Schema.Type<typeof ArtifactDef>;
25
+ type ArtifactDef = Schema.Schema.Type<typeof ArtifactDef>;
26
26
 
27
- const ListItem = S.Struct({
28
- name: S.String,
27
+ const ListItem = Schema.Struct({
28
+ name: Schema.String,
29
29
  });
30
30
 
31
- const ListSchema = S.Struct({
31
+ const ListSchema = Schema.Struct({
32
32
  id: ObjectId,
33
- title: S.String,
34
- items: S.Array(ListItem).pipe(S.mutable),
35
- }).pipe(S.mutable);
33
+ title: Schema.String,
34
+ items: Schema.Array(ListItem).pipe(Schema.mutable),
35
+ }).pipe(Schema.mutable);
36
36
 
37
- type ListSchema = S.Schema.Type<typeof ListSchema>;
37
+ type ListSchema = Schema.Schema.Type<typeof ListSchema>;
38
38
 
39
39
  const list: ArtifactDef = {
40
40
  typename: 'example.com/type/List',
@@ -44,7 +44,7 @@ const list: ArtifactDef = {
44
44
  defineTool('list', {
45
45
  name: 'query',
46
46
  description: 'Query all lists',
47
- schema: S.Struct({}),
47
+ schema: Schema.Struct({}),
48
48
  execute: async ({}, context) => {
49
49
  return ToolResult.Success(LISTS[id]);
50
50
  },
@@ -52,7 +52,7 @@ const list: ArtifactDef = {
52
52
  defineTool('list', {
53
53
  name: 'inspect',
54
54
  description: 'Get contents of the list',
55
- schema: S.Struct({
55
+ schema: Schema.Struct({
56
56
  id: ObjectId.annotations({ description: 'The list to inspect' }),
57
57
  }),
58
58
  execute: async ({ id }, context) => {
@@ -62,9 +62,9 @@ const list: ArtifactDef = {
62
62
  defineTool('list', {
63
63
  name: 'create',
64
64
  description: 'Add one or more items to an existing list',
65
- schema: S.Struct({
65
+ schema: Schema.Struct({
66
66
  id: ObjectId.annotations({ description: 'The list to add items to' }),
67
- items: S.Array(ListItem).annotations({ description: 'The items to add to the list' }),
67
+ items: Schema.Array(ListItem).annotations({ description: 'The items to add to the list' }),
68
68
  }),
69
69
  execute: async ({ id, items }, context) => {
70
70
  LISTS[id].items.push(...items);
@@ -81,7 +81,7 @@ const artifacts = [list];
81
81
  const getArtifactDefinitions = defineTool('system', {
82
82
  name: 'getArtifactDefinitions',
83
83
  description: 'Queries the definitions and metadata of artifacts defined in the system',
84
- schema: S.Any,
84
+ schema: Schema.Any,
85
85
  execute: async (_, context) => {
86
86
  return ToolResult.Success(
87
87
  artifacts.map((artifact) => ({
@@ -116,8 +116,8 @@ describe('Artifacts', () => {
116
116
  name: 'custodian',
117
117
  description: 'Custodian can tell you the password if you say the magic word',
118
118
  parameters: toJsonSchema(
119
- S.Struct({
120
- magicWord: S.String.annotations({ description: 'The magic word. Should be exactly "pretty please"' }),
119
+ Schema.Struct({
120
+ magicWord: Schema.String.annotations({ description: 'The magic word. Should be exactly "pretty please"' }),
121
121
  }),
122
122
  ),
123
123
  };
@@ -8,3 +8,4 @@ export * from './ollama';
8
8
  export * from './query-promts';
9
9
  export * from './test-data';
10
10
  export * from './test-schema';
11
+ export * from './printer';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type { ImageSource, MessageContentBlock } from '@dxos/artifact';
6
6
 
7
- import type { ConversationEvent } from '../conversation';
7
+ import type { ConversationEvent } from '../experimental';
8
8
 
9
9
  export const createLogger = ({
10
10
  stream,
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { inspect } from 'node:util';
6
+
7
+ import type { Message, MessageContentBlock } from '@dxos/artifact';
8
+
9
+ export class ConsolePrinter {
10
+ printMessage = (message: Message) => {
11
+ // eslint-disable-next-line no-console
12
+ console.log(`${message.role.toUpperCase()}\n`);
13
+ for (const content of message.content) {
14
+ this.printContentBlock(content);
15
+ }
16
+ };
17
+
18
+ printContentBlock = (content: MessageContentBlock) => {
19
+ switch (content.type) {
20
+ case 'text':
21
+ // eslint-disable-next-line no-console
22
+ console.log(content.text);
23
+ break;
24
+ case 'tool_use':
25
+ // eslint-disable-next-line no-console
26
+ console.log(`⚙️ [Tool Use] ${content.name} ${inspect(content.input, { depth: null, colors: true })}`);
27
+ break;
28
+ case 'tool_result': {
29
+ let data: any;
30
+ try {
31
+ data = JSON.parse(content.content);
32
+ } catch {
33
+ data = content.content;
34
+ }
35
+ // eslint-disable-next-line no-console
36
+ console.log(`⚙️ [Tool Result] ${content.toolUseId} ${inspect(data, { depth: null, colors: true })}`);
37
+ break;
38
+ }
39
+ }
40
+ };
41
+ }
@@ -2,23 +2,23 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema as S } from 'effect';
5
+ import { Schema } from 'effect';
6
6
 
7
7
  import { defineTool, ToolResult } from '@dxos/artifact';
8
8
  import { toJsonSchema } from '@dxos/echo-schema';
9
9
  import { log } from '@dxos/log';
10
10
 
11
- import { executeQuery, formatJsonSchemaForLLM, type DataSource } from '../cypher';
11
+ import { executeQuery, formatJsonSchemaForLLM, type DataSource } from '../experimental';
12
12
  import { trim } from '../util';
13
13
 
14
- export const createCypherTool = (dataSource: DataSource, schemaTypes: S.Schema.Any[] = []) =>
14
+ export const createCypherTool = (dataSource: DataSource, schemaTypes: Schema.Schema.Any[] = []) =>
15
15
  defineTool('dxos.org/echo', {
16
16
  name: 'query',
17
17
  description:
18
18
  'Query the ECHO graph database in cypher query language. Returns data from the database.' +
19
19
  (schemaTypes.length > 0 ? `\n\n${createSystemPrompt(schemaTypes)}` : ''),
20
- schema: S.Struct({
21
- query: S.String.annotations({
20
+ schema: Schema.Struct({
21
+ query: Schema.String.annotations({
22
22
  description: `
23
23
  A valid cypher query string to execute.
24
24
  Query must have one MATCH clause.
@@ -55,7 +55,7 @@ export const createCypherTool = (dataSource: DataSource, schemaTypes: S.Schema.A
55
55
  },
56
56
  });
57
57
 
58
- export const createSystemPrompt = (schemaTypes: S.Schema.Any[]) => trim`
58
+ export const createSystemPrompt = (schemaTypes: Schema.Schema.Any[]) => trim`
59
59
  You have access to ECHO graph database. You can query the database to get data to satisfy user prompts.
60
60
 
61
61
  Database schema is defined in pseud-cypher syntax. The schema is:
@@ -16,8 +16,7 @@ import { createCypherTool, createSystemPrompt } from './query-promts';
16
16
  import { createTestData, seedTestData } from './test-data';
17
17
  import { Contact, Organization, Project, Task } from './test-schema';
18
18
  import { AIServiceEdgeClient, DEFAULT_EDGE_MODEL } from '../ai-service';
19
- import { runLLM } from '../conversation';
20
- import { EchoDataSource } from '../cypher';
19
+ import { runLLM, EchoDataSource } from '../experimental';
21
20
 
22
21
  const client = new AIServiceEdgeClient({
23
22
  endpoint: AI_SERVICE_ENDPOINT.LOCAL,
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema as S } from 'effect';
5
+ import { type Schema } from 'effect';
6
6
 
7
7
  import { raise } from '@dxos/debug';
8
8
  import type { EchoDatabase } from '@dxos/echo-db';
@@ -23,7 +23,7 @@ import {
23
23
  type Relationship,
24
24
  formatInferredRelationshipLabel,
25
25
  formatNodeLabel,
26
- } from '../cypher';
26
+ } from '../experimental';
27
27
  import { Contact, Organization, Project, Task } from '../testing';
28
28
 
29
29
  export const seedTestData = (db: EchoDatabase) => {
@@ -254,7 +254,7 @@ export class MockDataSource implements DataSource {
254
254
  objects: Record<
255
255
  string,
256
256
  {
257
- schema: S.Schema.All;
257
+ schema: Schema.Schema.All;
258
258
  typeDxn: string;
259
259
  id: string;
260
260
  data: unknown;
@@ -265,7 +265,7 @@ export class MockDataSource implements DataSource {
265
265
  string,
266
266
  {
267
267
  json: JsonSchemaType;
268
- instance: S.Schema.All;
268
+ instance: Schema.Schema.All;
269
269
  }
270
270
  > = {};
271
271
 
@@ -280,7 +280,7 @@ export class MockDataSource implements DataSource {
280
280
  return this.relationships.filter((relationship) => !label || relationship.label === label);
281
281
  }
282
282
 
283
- add<S extends S.Schema.All>(schema: S, data: S.Schema.Type<S>) {
283
+ add<S extends Schema.Schema.All>(schema: S, data: Schema.Schema.Type<S>) {
284
284
  invariant(typeof data.id === 'string', 'Data must have an id');
285
285
  const jsonSchema = toJsonSchema(schema);
286
286
  if (!this.schema[jsonSchema.$id!]) {