@dxos/assistant 0.8.2-main.fbd8ed0 → 0.8.2-staging.7ac8446

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 (175) hide show
  1. package/dist/lib/browser/{chunk-B4HAWUFW.mjs → chunk-PND5VR6D.mjs} +286 -757
  2. package/dist/lib/browser/chunk-PND5VR6D.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +324 -7
  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 +102 -139
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/{chunk-NOB7CIIC.cjs → chunk-IBOBRBDF.cjs} +303 -768
  9. package/dist/lib/node/chunk-IBOBRBDF.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +353 -36
  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 +93 -128
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/lib/node-esm/{chunk-SAWYU3V5.mjs → chunk-DCQJOLSR.mjs} +286 -757
  16. package/dist/lib/node-esm/chunk-DCQJOLSR.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +324 -7
  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 +102 -139
  21. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  22. package/dist/types/src/ai-service/edge-client.d.ts +5 -4
  23. package/dist/types/src/ai-service/edge-client.d.ts.map +1 -1
  24. package/dist/types/src/ai-service/message-collector.d.ts +6 -0
  25. package/dist/types/src/ai-service/message-collector.d.ts.map +1 -1
  26. package/dist/types/src/ai-service/ollama-client.d.ts +2 -6
  27. package/dist/types/src/ai-service/ollama-client.d.ts.map +1 -1
  28. package/dist/types/src/ai-service/parser.d.ts +3 -0
  29. package/dist/types/src/ai-service/parser.d.ts.map +1 -1
  30. package/dist/types/src/ai-service/service.d.ts +2 -18
  31. package/dist/types/src/ai-service/service.d.ts.map +1 -1
  32. package/dist/types/src/ai-service/stream.d.ts.map +1 -1
  33. package/dist/types/src/ai-service/testing/mock.d.ts +2 -6
  34. package/dist/types/src/ai-service/testing/mock.d.ts.map +1 -1
  35. package/dist/types/src/ai-service/testing/test-stream.d.ts.map +1 -1
  36. package/dist/types/src/ai-service/types.d.ts +101 -144
  37. package/dist/types/src/ai-service/types.d.ts.map +1 -1
  38. package/dist/types/src/ai-service/util/sse.d.ts.map +1 -1
  39. package/dist/types/src/{experimental/conversation → conversation}/conversation.d.ts +21 -2
  40. package/dist/types/src/conversation/conversation.d.ts.map +1 -0
  41. package/dist/types/src/conversation/conversation.test.d.ts.map +1 -0
  42. package/dist/types/src/conversation/index.d.ts.map +1 -0
  43. package/dist/types/src/{experimental/conversation → conversation}/types.d.ts +12 -11
  44. package/dist/types/src/conversation/types.d.ts.map +1 -0
  45. package/dist/types/src/cypher/ast.d.ts.map +1 -0
  46. package/dist/types/src/cypher/combinators.d.ts.map +1 -0
  47. package/dist/types/src/cypher/combinators.test.d.ts.map +1 -0
  48. package/dist/types/src/cypher/echo-data-source.d.ts.map +1 -0
  49. package/dist/types/src/cypher/execution-plan.d.ts.map +1 -0
  50. package/dist/types/src/cypher/execution-plan.test.d.ts.map +1 -0
  51. package/dist/types/src/cypher/index.d.ts.map +1 -0
  52. package/dist/types/src/cypher/parjs.d.ts.map +1 -0
  53. package/dist/types/src/cypher/parser.d.ts.map +1 -0
  54. package/dist/types/src/cypher/query-executor.d.ts.map +1 -0
  55. package/dist/types/src/cypher/query-executor.test.d.ts.map +1 -0
  56. package/dist/types/src/cypher/schema.d.ts.map +1 -0
  57. package/dist/types/src/cypher/schema.test.d.ts.map +1 -0
  58. package/dist/types/src/index.d.ts +2 -1
  59. package/dist/types/src/index.d.ts.map +1 -1
  60. package/dist/types/src/session/index.d.ts +0 -2
  61. package/dist/types/src/session/index.d.ts.map +1 -1
  62. package/dist/types/src/session/session.d.ts +13 -33
  63. package/dist/types/src/session/session.d.ts.map +1 -1
  64. package/dist/types/src/testing/index.d.ts +0 -1
  65. package/dist/types/src/testing/index.d.ts.map +1 -1
  66. package/dist/types/src/testing/logger.d.ts +1 -1
  67. package/dist/types/src/testing/logger.d.ts.map +1 -1
  68. package/dist/types/src/testing/ollama.d.ts.map +1 -1
  69. package/dist/types/src/testing/query-promts.d.ts +4 -4
  70. package/dist/types/src/testing/query-promts.d.ts.map +1 -1
  71. package/dist/types/src/testing/test-data.d.ts +5 -5
  72. package/dist/types/src/testing/test-data.d.ts.map +1 -1
  73. package/dist/types/src/testing/test-schema.d.ts +23 -25
  74. package/dist/types/src/testing/test-schema.d.ts.map +1 -1
  75. package/dist/types/src/util/trim.d.ts.map +1 -1
  76. package/dist/types/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +14 -20
  78. package/src/ai-service/client.test.ts +73 -55
  79. package/src/ai-service/edge-client.ts +20 -13
  80. package/src/ai-service/ollama-client.ts +10 -17
  81. package/src/ai-service/parser.ts +2 -13
  82. package/src/ai-service/service.ts +3 -19
  83. package/src/ai-service/testing/mock.ts +1 -9
  84. package/src/ai-service/types.ts +15 -24
  85. package/src/bin/chat.ts +4 -4
  86. package/src/{experimental/conversation → conversation}/conversation.test.ts +5 -5
  87. package/src/{experimental/conversation → conversation}/conversation.ts +119 -4
  88. package/src/{experimental/conversation → conversation}/types.ts +14 -11
  89. package/src/{experimental/cypher → cypher}/combinators.test.ts +4 -4
  90. package/src/{experimental/cypher → cypher}/echo-data-source.ts +11 -11
  91. package/src/{experimental/cypher → cypher}/execution-plan.test.ts +1 -1
  92. package/src/{experimental/cypher → cypher}/query-executor.test.ts +2 -2
  93. package/src/{experimental/cypher → cypher}/schema.test.ts +3 -3
  94. package/src/{experimental/cypher → cypher}/schema.ts +1 -1
  95. package/src/index.ts +2 -3
  96. package/src/session/index.ts +0 -2
  97. package/src/session/session.test.ts +63 -35
  98. package/src/session/session.ts +151 -297
  99. package/src/testing/artifacts.test.ts +22 -22
  100. package/src/testing/index.ts +0 -1
  101. package/src/testing/logger.ts +1 -1
  102. package/src/testing/query-promts.ts +6 -6
  103. package/src/testing/query.test.ts +21 -18
  104. package/src/testing/test-data.ts +72 -79
  105. package/src/testing/test-schema.ts +29 -28
  106. package/dist/lib/browser/chunk-B4HAWUFW.mjs.map +0 -7
  107. package/dist/lib/node/chunk-NOB7CIIC.cjs.map +0 -7
  108. package/dist/lib/node-esm/chunk-SAWYU3V5.mjs.map +0 -7
  109. package/dist/types/src/experimental/conversation/conversation.d.ts.map +0 -1
  110. package/dist/types/src/experimental/conversation/conversation.test.d.ts.map +0 -1
  111. package/dist/types/src/experimental/conversation/index.d.ts.map +0 -1
  112. package/dist/types/src/experimental/conversation/types.d.ts.map +0 -1
  113. package/dist/types/src/experimental/cypher/ast.d.ts.map +0 -1
  114. package/dist/types/src/experimental/cypher/combinators.d.ts.map +0 -1
  115. package/dist/types/src/experimental/cypher/combinators.test.d.ts.map +0 -1
  116. package/dist/types/src/experimental/cypher/echo-data-source.d.ts.map +0 -1
  117. package/dist/types/src/experimental/cypher/execution-plan.d.ts.map +0 -1
  118. package/dist/types/src/experimental/cypher/execution-plan.test.d.ts.map +0 -1
  119. package/dist/types/src/experimental/cypher/index.d.ts.map +0 -1
  120. package/dist/types/src/experimental/cypher/parjs.d.ts.map +0 -1
  121. package/dist/types/src/experimental/cypher/parser.d.ts.map +0 -1
  122. package/dist/types/src/experimental/cypher/query-executor.d.ts.map +0 -1
  123. package/dist/types/src/experimental/cypher/query-executor.test.d.ts.map +0 -1
  124. package/dist/types/src/experimental/cypher/schema.d.ts.map +0 -1
  125. package/dist/types/src/experimental/cypher/schema.test.d.ts.map +0 -1
  126. package/dist/types/src/experimental/index.d.ts +0 -3
  127. package/dist/types/src/experimental/index.d.ts.map +0 -1
  128. package/dist/types/src/experimental/ner/ner.test.d.ts +0 -2
  129. package/dist/types/src/experimental/ner/ner.test.d.ts.map +0 -1
  130. package/dist/types/src/experimental/research/exa.d.ts +0 -6
  131. package/dist/types/src/experimental/research/exa.d.ts.map +0 -1
  132. package/dist/types/src/experimental/research/research.test.d.ts +0 -2
  133. package/dist/types/src/experimental/research/research.test.d.ts.map +0 -1
  134. package/dist/types/src/session/tools.d.ts +0 -18
  135. package/dist/types/src/session/tools.d.ts.map +0 -1
  136. package/dist/types/src/session/version-pin.d.ts +0 -22
  137. package/dist/types/src/session/version-pin.d.ts.map +0 -1
  138. package/dist/types/src/session/version-pin.test.d.ts +0 -2
  139. package/dist/types/src/session/version-pin.test.d.ts.map +0 -1
  140. package/dist/types/src/testing/printer.d.ts +0 -6
  141. package/dist/types/src/testing/printer.d.ts.map +0 -1
  142. package/src/experimental/index.ts +0 -6
  143. package/src/experimental/ner/ner.test.ts +0 -163
  144. package/src/experimental/research/exa.ts +0 -38
  145. package/src/experimental/research/instructions.tpl +0 -64
  146. package/src/experimental/research/research.test.ts +0 -263
  147. package/src/session/tools.ts +0 -117
  148. package/src/session/version-pin.test.ts +0 -26
  149. package/src/session/version-pin.ts +0 -49
  150. package/src/testing/printer.ts +0 -41
  151. package/src/typedefs.d.ts +0 -8
  152. /package/dist/types/src/{experimental/conversation → conversation}/conversation.test.d.ts +0 -0
  153. /package/dist/types/src/{experimental/conversation → conversation}/index.d.ts +0 -0
  154. /package/dist/types/src/{experimental/cypher → cypher}/ast.d.ts +0 -0
  155. /package/dist/types/src/{experimental/cypher → cypher}/combinators.d.ts +0 -0
  156. /package/dist/types/src/{experimental/cypher → cypher}/combinators.test.d.ts +0 -0
  157. /package/dist/types/src/{experimental/cypher → cypher}/echo-data-source.d.ts +0 -0
  158. /package/dist/types/src/{experimental/cypher → cypher}/execution-plan.d.ts +0 -0
  159. /package/dist/types/src/{experimental/cypher → cypher}/execution-plan.test.d.ts +0 -0
  160. /package/dist/types/src/{experimental/cypher → cypher}/index.d.ts +0 -0
  161. /package/dist/types/src/{experimental/cypher → cypher}/parjs.d.ts +0 -0
  162. /package/dist/types/src/{experimental/cypher → cypher}/parser.d.ts +0 -0
  163. /package/dist/types/src/{experimental/cypher → cypher}/query-executor.d.ts +0 -0
  164. /package/dist/types/src/{experimental/cypher → cypher}/query-executor.test.d.ts +0 -0
  165. /package/dist/types/src/{experimental/cypher → cypher}/schema.d.ts +0 -0
  166. /package/dist/types/src/{experimental/cypher → cypher}/schema.test.d.ts +0 -0
  167. /package/src/{experimental/conversation → conversation}/index.ts +0 -0
  168. /package/src/{experimental/cypher → cypher}/ast.ts +0 -0
  169. /package/src/{experimental/cypher → cypher}/combinators.ts +0 -0
  170. /package/src/{experimental/cypher → cypher}/execution-plan.ts +0 -0
  171. /package/src/{experimental/cypher → cypher}/index.ts +0 -0
  172. /package/src/{experimental/cypher → cypher}/parjs-shims.d.ts +0 -0
  173. /package/src/{experimental/cypher → cypher}/parjs.ts +0 -0
  174. /package/src/{experimental/cypher → cypher}/parser.ts +0 -0
  175. /package/src/{experimental/cypher → cypher}/query-executor.ts +0 -0
@@ -2,12 +2,15 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { Schema as S } from 'effect';
6
+
7
+ import { Message } from '@dxos/artifact';
5
8
  import { assertArgument, invariant } from '@dxos/invariant';
6
9
  import { log } from '@dxos/log';
7
10
 
8
11
  import type { AIServiceClient, GenerationStream } from './service';
9
12
  import { createGenerationStream } from './stream';
10
- import { type GenerateRequest, type GenerateResponse } from './types';
13
+ import { type GenerateRequest } from './types';
11
14
 
12
15
  export type AIServiceEdgeClientOptions = {
13
16
  endpoint: string;
@@ -22,16 +25,28 @@ export class AIServiceEdgeClient implements AIServiceClient {
22
25
  }
23
26
 
24
27
  /**
25
- * Generate non-streaming response.
28
+ * @deprecated
26
29
  */
27
- async exec(request: GenerateRequest): Promise<GenerateResponse> {
28
- throw new Error('Not implemented');
30
+ // TODO(burdon): Remove.
31
+ async appendMessages(messages: Message[]): Promise<void> {
32
+ const url = `${this._endpoint}/message`;
33
+ const res = await fetch(url, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({
37
+ messages: await S.encodePromise(S.Array(Message))(messages),
38
+ }),
39
+ });
40
+
41
+ if (!res.ok) {
42
+ throw new Error(`${await res.text()} [${url}]`);
43
+ }
29
44
  }
30
45
 
31
46
  /**
32
47
  * Process request and open message stream.
33
48
  */
34
- async execStream(request: GenerateRequest): Promise<GenerationStream> {
49
+ async exec(request: GenerateRequest): Promise<GenerationStream> {
35
50
  assertArgument(typeof request.model === 'string', 'model is required');
36
51
  log('requesting', { endpoint: this._endpoint });
37
52
  const controller = new AbortController();
@@ -46,15 +61,7 @@ export class AIServiceEdgeClient implements AIServiceClient {
46
61
  }),
47
62
  });
48
63
 
49
- if (!response.ok) {
50
- throw await parseErrorResponse(response);
51
- }
52
-
53
64
  invariant(response.body instanceof ReadableStream);
54
65
  return createGenerationStream(response, controller);
55
66
  }
56
67
  }
57
-
58
- const parseErrorResponse = async (response: Response): Promise<Error> => {
59
- throw new Error(`AI Service error: ${response.status} ${response.statusText} ${await response.text()}`);
60
- };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import { Schema as S } from 'effect';
6
6
 
7
7
  import { type MessageContentBlock, defineTool, type Tool, ToolResult } from '@dxos/artifact';
8
8
  import { ObjectId } from '@dxos/echo-schema';
@@ -13,8 +13,8 @@ import { DEFAULT_OLLAMA_ENDPOINT } from './defs';
13
13
  import { MessageCollector, emitMessageAsEvents } from './message-collector';
14
14
  import { type AIServiceClient, type GenerationStream } from './service';
15
15
  import { GenerationStreamImpl } from './stream';
16
- import { ToolTypes, type GenerateRequest, type GenerateResponse, type GenerationStreamEvent } from './types';
17
- import { isToolUse, runTools } from '../session';
16
+ import { ToolTypes, type GenerateRequest, type GenerationStreamEvent } from './types';
17
+ import { isToolUse, runTools } from '../conversation';
18
18
 
19
19
  export type OllamaClientParams = {
20
20
  endpoint?: string;
@@ -300,14 +300,7 @@ export class OllamaClient implements AIServiceClient {
300
300
  }
301
301
  }
302
302
 
303
- /**
304
- * Generate non-streaming response.
305
- */
306
- async exec(request: GenerateRequest): Promise<GenerateResponse> {
307
- throw new Error('Not implemented');
308
- }
309
-
310
- async execStream(request: GenerateRequest): Promise<GenerationStream> {
303
+ async exec(request: GenerateRequest): Promise<GenerationStream> {
311
304
  const controller = new AbortController();
312
305
 
313
306
  try {
@@ -388,8 +381,8 @@ const WELL_KNOWN_TOOLS: Record<string, Tool> = {
388
381
  [ToolTypes.TextToImage]: defineTool('system', {
389
382
  name: 'text-to-image',
390
383
  description: 'Generate an image from a text prompt',
391
- schema: Schema.Struct({
392
- prompt: Schema.String.annotations({ description: 'The text prompt describing the image to generate' }),
384
+ schema: S.Struct({
385
+ prompt: S.String.annotations({ description: 'The text prompt describing the image to generate' }),
393
386
  }),
394
387
  execute: async ({ prompt }) => {
395
388
  const image = await fetch(SAMPLE_IMAGE_URL).then(async (res) => Buffer.from(await res.arrayBuffer()));
@@ -467,15 +460,15 @@ class OllamaDecoderStream extends TransformStream<string, OllamaResponseData> {
467
460
  }
468
461
  }
469
462
 
470
- class ModelDoesNotSupportToolsError extends Schema.TaggedError<ModelDoesNotSupportToolsError>()(
463
+ class ModelDoesNotSupportToolsError extends S.TaggedError<ModelDoesNotSupportToolsError>()(
471
464
  'ModelDoesNotSupportToolsError',
472
465
  {
473
- model: Schema.String,
466
+ model: S.String,
474
467
  },
475
468
  ) {}
476
469
 
477
- class ModelNotFoundError extends Schema.TaggedError<ModelNotFoundError>()('ModelNotFoundError', {
478
- model: Schema.String,
470
+ class ModelNotFoundError extends S.TaggedError<ModelNotFoundError>()('ModelNotFoundError', {
471
+ model: S.String,
479
472
  }) {}
480
473
 
481
474
  const parseOllamaError = (error: string): Error => {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { Message, type MessageContentBlock } from '@dxos/artifact';
6
6
  import { Event } from '@dxos/async';
7
- import { create } from '@dxos/echo-schema';
7
+ import { createStatic } from '@dxos/echo-schema';
8
8
  import { invariant } from '@dxos/invariant';
9
9
  import { log } from '@dxos/log';
10
10
  import { isNotFalsy, safeParseJson } from '@dxos/util';
@@ -94,7 +94,7 @@ export class MixedStreamParser {
94
94
  log.warn('unexpected message_start');
95
95
  }
96
96
 
97
- this._message = create(Message, { role: event.message.role, content: [...event.message.content] });
97
+ this._message = createStatic(Message, { role: event.message.role, content: [...event.message.content] });
98
98
  this.message.emit(this._message);
99
99
  break;
100
100
  }
@@ -316,17 +316,6 @@ export const mergeMessageBlock = (
316
316
  break;
317
317
  }
318
318
 
319
- case 'proposal': {
320
- if (streamBlock.content.length === 1 && streamBlock.content[0].type === 'text') {
321
- return {
322
- type: 'json',
323
- disposition: 'proposal',
324
- json: JSON.stringify({ text: streamBlock.content[0].content }),
325
- };
326
- }
327
- break;
328
- }
329
-
330
319
  case 'select': {
331
320
  return {
332
321
  type: 'json',
@@ -2,29 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type GenerateRequest, type GenerationStreamEvent, type GenerateResponse } from './types';
5
+ import { type GenerateRequest, type GenerationStreamEvent } from './types';
6
6
 
7
- /**
8
- * Interface for AI service.
9
- * Generates chat completions, supports, history and tools.
10
- */
7
+ // TODO(burdon): Create wrapper for edge/ollama. Create session?
11
8
  export interface AIServiceClient {
12
- /**
13
- * Generate chat completions, supports, history and tools.
14
- * Generate non-streaming response.
15
- */
16
- exec(request: GenerateRequest): Promise<GenerateResponse>;
17
-
18
- /**
19
- * Generate chat completions, supports, history and tools.
20
- * Process request and open message stream.
21
- */
22
- execStream(request: GenerateRequest): Promise<GenerationStream>;
9
+ exec(request: GenerateRequest): Promise<GenerationStream>;
23
10
  }
24
11
 
25
- /**
26
- * Streaming chat completion stream.
27
- */
28
12
  export interface GenerationStream extends AsyncIterable<GenerationStreamEvent> {
29
13
  abort(): void;
30
14
  complete(): Promise<void>;
@@ -7,7 +7,6 @@ import {
7
7
  type AIServiceClient,
8
8
  type GenerateRequest,
9
9
  createGenerationStream,
10
- type GenerateResponse,
11
10
  } from '@dxos/assistant';
12
11
 
13
12
  import { createTestSSEStream } from './test-stream';
@@ -15,16 +14,9 @@ import { createTestSSEStream } from './test-stream';
15
14
  export class MockAIService implements AIServiceClient {
16
15
  constructor(private readonly _responses: string[]) {}
17
16
 
18
- /**
19
- * Generate non-streaming response.
20
- */
21
- async exec(request: GenerateRequest): Promise<GenerateResponse> {
22
- throw new Error('Not implemented');
23
- }
24
-
25
17
  // TODO(burdon): Match response to request.
26
18
  // TODO(burdon): See MockGpt (from conductor).
27
- async execStream(request: GenerateRequest): Promise<GenerationStream> {
19
+ async exec(request: GenerateRequest): Promise<GenerationStream> {
28
20
  return createGenerationStream(new Response(createTestSSEStream(this._responses)));
29
21
  }
30
22
  }
@@ -2,18 +2,18 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import { Schema as S } from 'effect';
6
6
 
7
- import { Tool, Message, type MessageContentBlock } from '@dxos/artifact';
8
- import { type ObjectId } from '@dxos/echo-schema';
7
+ import { Tool, Message, type MessageContentBlock, SpaceIdSchema } from '@dxos/artifact';
8
+ import { ObjectId } from '@dxos/echo-schema';
9
9
 
10
10
  import { DEFAULT_EDGE_MODELS, DEFAULT_OLLAMA_MODELS } from './defs';
11
11
 
12
12
  export const createArtifactElement = (id: ObjectId) => `<artifact id=${id} />`;
13
13
 
14
- export const LLMModel = Schema.Literal(...DEFAULT_EDGE_MODELS, ...DEFAULT_OLLAMA_MODELS);
14
+ export const LLMModel = S.Literal(...DEFAULT_EDGE_MODELS, ...DEFAULT_OLLAMA_MODELS);
15
15
 
16
- export type LLMModel = Schema.Schema.Type<typeof LLMModel>;
16
+ export type LLMModel = S.Schema.Type<typeof LLMModel>;
17
17
 
18
18
  export const ToolTypes = Object.freeze({
19
19
  // TODO(dmaretskyi): Not implemented yet.
@@ -24,47 +24,38 @@ export const ToolTypes = Object.freeze({
24
24
  /**
25
25
  * Client GPT request.
26
26
  */
27
- export const GenerateRequest = Schema.Struct({
27
+ export const GenerateRequest = S.Struct({
28
+ spaceId: S.optional(SpaceIdSchema),
29
+ threadId: S.optional(ObjectId),
30
+
28
31
  /**
29
32
  * Preferred model or system default.
30
33
  */
31
- model: Schema.optional(LLMModel),
34
+ model: S.optional(LLMModel),
32
35
 
33
36
  /**
34
37
  * Tools available for the LLM.
35
38
  */
36
- tools: Schema.optional(Schema.Array(Tool).pipe(Schema.mutable)),
39
+ tools: S.optional(S.Array(Tool).pipe(S.mutable)),
37
40
 
38
41
  /**
39
42
  * System instructions to the LLM.
40
43
  */
41
- systemPrompt: Schema.optional(Schema.String),
44
+ systemPrompt: S.optional(S.String),
42
45
 
43
46
  /**
44
47
  * History of messages to include in the context window.
45
48
  */
46
49
  // TODO(burdon): Rename messages.
47
- history: Schema.optional(Schema.Array(Message)),
50
+ history: S.optional(S.Array(Message)),
48
51
 
49
52
  /**
50
53
  * Current request.
51
54
  */
52
- prompt: Schema.optional(Message),
55
+ prompt: S.optional(Message),
53
56
  });
54
- export type GenerateRequest = Schema.Schema.Type<typeof GenerateRequest>;
55
-
56
- /**
57
- * Non-streaming response.
58
- */
59
- export const GenerateResponse = Schema.Struct({
60
- messages: Schema.Array(Message),
61
57
 
62
- /**
63
- * Number of tokens used in the response.
64
- */
65
- tokenCount: Schema.optional(Schema.Number),
66
- });
67
- export type GenerateResponse = Schema.Schema.Type<typeof GenerateResponse>;
58
+ export type GenerateRequest = S.Schema.Type<typeof GenerateRequest>;
68
59
 
69
60
  /**
70
61
  * Server-Sent Events (SSE) stream from the AI service.
package/src/bin/chat.ts CHANGED
@@ -11,7 +11,7 @@ import { SpaceId } from '@dxos/keys';
11
11
  import { log } from '@dxos/log';
12
12
 
13
13
  import { AIServiceEdgeClient, ToolTypes, DEFAULT_EDGE_MODEL } from '../ai-service';
14
- import { runLLM } from '../experimental';
14
+ import { runLLM } from '../conversation';
15
15
  import {
16
16
  AI_SERVICE_ENDPOINT,
17
17
  createLogger,
@@ -19,7 +19,7 @@ import {
19
19
  createSystemPrompt,
20
20
  createTestData,
21
21
  Contact,
22
- Organization,
22
+ Org,
23
23
  Project,
24
24
  Task,
25
25
  } from '../testing';
@@ -33,7 +33,7 @@ const dataSource = createTestData();
33
33
 
34
34
  const cypherTool = createCypherTool(dataSource);
35
35
 
36
- const schemaTypes = [Organization, Project, Task, Contact];
36
+ const schemaTypes = [Org, Project, Task, Contact];
37
37
 
38
38
  const spaceId = SpaceId.random();
39
39
  const threadId = ObjectId.random();
@@ -46,6 +46,7 @@ while (true) {
46
46
  message: 'Enter a message:',
47
47
  },
48
48
  ]);
49
+ await client.appendMessages([createUserMessage(spaceId, threadId, prompt.message)]);
49
50
 
50
51
  await runLLM({
51
52
  model: DEFAULT_EDGE_MODEL,
@@ -60,7 +61,6 @@ while (true) {
60
61
  },
61
62
  ],
62
63
  client,
63
- history: [createUserMessage(spaceId, threadId, prompt.message)],
64
64
  logger: createLogger({
65
65
  stream: true,
66
66
  filter: (e) => {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import { Schema as S } from 'effect';
6
6
  import { describe, test } from 'vitest';
7
7
 
8
8
  import { createUserMessage, defineTool, ToolResult } from '@dxos/artifact';
@@ -11,8 +11,8 @@ import { SpaceId } from '@dxos/keys';
11
11
  import { log } from '@dxos/log';
12
12
 
13
13
  import { runLLM, type ConversationEvent } from './conversation';
14
- import { AIServiceEdgeClient, DEFAULT_EDGE_MODEL } from '../../ai-service';
15
- import { AI_SERVICE_ENDPOINT } from '../../testing';
14
+ import { AIServiceEdgeClient, DEFAULT_EDGE_MODEL } from '../ai-service';
15
+ import { AI_SERVICE_ENDPOINT } from '../testing';
16
16
 
17
17
  // TODO(burdon): Local live LLM test.
18
18
  describe.skip('Conversation tests', () => {
@@ -40,8 +40,8 @@ describe.skip('Conversation tests', () => {
40
40
  const custodian = defineTool('testing', {
41
41
  name: 'custodian',
42
42
  description: 'Custodian can tell you the password if you say the magic word',
43
- schema: Schema.Struct({
44
- magicWord: Schema.String.annotations({ description: 'The magic word. Should be exactly "pretty please"' }),
43
+ schema: S.Struct({
44
+ magicWord: S.String.annotations({ description: 'The magic word. Should be exactly "pretty please"' }),
45
45
  }),
46
46
  execute: async ({ magicWord }) => {
47
47
  if (magicWord === 'pretty please') {
@@ -2,13 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Message, type Tool } from '@dxos/artifact';
6
- import { ObjectId } from '@dxos/echo-schema';
5
+ import { type Tool, ToolResult, Message } from '@dxos/artifact';
6
+ import { createStatic, ObjectId } from '@dxos/echo-schema';
7
7
  import { invariant } from '@dxos/invariant';
8
8
  import { type SpaceId } from '@dxos/keys';
9
9
  import { log } from '@dxos/log';
10
10
 
11
- import { type AIServiceClient, type GenerationStreamEvent, type LLMModel } from '../../ai-service';
11
+ import { type AIServiceClient, type GenerationStreamEvent, type LLMModel } from '../ai-service';
12
12
 
13
13
  export type CreateLLMConversationParams = {
14
14
  model: LLMModel;
@@ -43,8 +43,10 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
43
43
  const generate = async () => {
44
44
  log('llm generate', { tools: params.tools });
45
45
  const beginTs = Date.now();
46
- const stream = await params.client.execStream({
46
+ const stream = await params.client.exec({
47
47
  model: params.model,
48
+ spaceId: params.spaceId,
49
+ threadId: params.threadId,
48
50
  history,
49
51
  systemPrompt: params.system,
50
52
  tools: params.tools as any,
@@ -86,6 +88,8 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
86
88
  log.warn('tool error', { message: toolResult.message });
87
89
  const resultMessage: Message = {
88
90
  id: ObjectId.random(),
91
+ spaceId: params.spaceId,
92
+ threadId: params.threadId,
89
93
  role: 'user',
90
94
  content: [
91
95
  {
@@ -106,6 +110,8 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
106
110
  log('tool success', { result: toolResult.result });
107
111
  const resultMessage: Message = {
108
112
  id: ObjectId.random(),
113
+ spaceId: params.spaceId,
114
+ threadId: params.threadId,
109
115
  role: 'user',
110
116
  content: [
111
117
  {
@@ -140,3 +146,112 @@ export const runLLM = async (params: CreateLLMConversationParams) => {
140
146
  history,
141
147
  };
142
148
  };
149
+
150
+ export const isToolUse = (message: Message, { onlyToolNames }: { onlyToolNames?: string[] } = {}) => {
151
+ const block = message.content.at(-1);
152
+ invariant(block);
153
+ return block.type === 'tool_use' && (!onlyToolNames || onlyToolNames.includes(block.name));
154
+ };
155
+
156
+ export type RunToolsOptions = {
157
+ message: Message;
158
+ tools: Tool[];
159
+ extensions?: ToolContextExtensions;
160
+ };
161
+
162
+ export type RunToolsResult =
163
+ | {
164
+ type: 'continue';
165
+ message: Message;
166
+ }
167
+ | {
168
+ type: 'break';
169
+ result: unknown;
170
+ };
171
+
172
+ export const runTools = async ({ message, tools, extensions }: RunToolsOptions): Promise<RunToolsResult> => {
173
+ const toolCalls = message.content.filter((block) => block.type === 'tool_use');
174
+ invariant(toolCalls.length === 1);
175
+ const toolCall = toolCalls[0];
176
+ const tool = tools.find((tool) => tool.name === toolCall.name);
177
+ if (!tool) {
178
+ const resultMessage = createStatic(Message, {
179
+ role: 'user',
180
+ content: [
181
+ {
182
+ type: 'tool_result',
183
+ toolUseId: toolCall.id,
184
+ content: `Tool not found: ${toolCall.name}`,
185
+ isError: true,
186
+ },
187
+ ],
188
+ });
189
+
190
+ // TODO(burdon): Retry count?
191
+ return {
192
+ type: 'continue',
193
+ message: resultMessage,
194
+ };
195
+ }
196
+
197
+ let toolResult: ToolResult;
198
+ try {
199
+ invariant(tool.execute);
200
+ toolResult = await tool.execute(toolCall.input, { extensions });
201
+ } catch (error: any) {
202
+ log('tool error', { error });
203
+ toolResult = ToolResult.Error(error.message);
204
+ }
205
+
206
+ switch (toolResult.kind) {
207
+ case 'error': {
208
+ log('tool error', { message: toolResult.message });
209
+ const resultMessage = createStatic(Message, {
210
+ role: 'user',
211
+ content: [
212
+ {
213
+ type: 'tool_result',
214
+ toolUseId: toolCall.id,
215
+ content: toolResult.message,
216
+ isError: true,
217
+ },
218
+ ],
219
+ });
220
+
221
+ // TODO(burdon): Retry count?
222
+ return {
223
+ type: 'continue',
224
+ message: resultMessage,
225
+ };
226
+ }
227
+
228
+ case 'success': {
229
+ log('tool success', { result: toolResult.result });
230
+ const resultMessage = createStatic(Message, {
231
+ role: 'user',
232
+ content: [
233
+ {
234
+ type: 'tool_result',
235
+ toolUseId: toolCall.id,
236
+ content:
237
+ typeof toolResult.result === 'string' ? toolResult.result : JSON.stringify(toolResult.result) ?? '',
238
+ },
239
+ ...(toolResult.extractContentBlocks ?? []),
240
+ ],
241
+ });
242
+
243
+ return {
244
+ type: 'continue',
245
+ message: resultMessage,
246
+ };
247
+ }
248
+
249
+ case 'break': {
250
+ log('tool break', { result: toolResult.result });
251
+ return {
252
+ type: 'break',
253
+ result: toolResult.result,
254
+ };
255
+ }
256
+ }
257
+ };
@@ -2,14 +2,17 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import { Schema as S } from 'effect';
6
6
 
7
7
  import { type Message, type MessageContentBlock } from '@dxos/artifact';
8
8
  import { toJsonSchema, JsonSchemaType, ObjectId } from '@dxos/echo-schema';
9
+ import type { SpaceId } from '@dxos/keys';
9
10
  import { log } from '@dxos/log';
10
11
 
11
- export const createUserMessage = (text: string): Message => ({
12
+ export const createUserMessage = (spaceId: SpaceId, threadId: ObjectId, text: string): Message => ({
12
13
  id: ObjectId.random(),
14
+ spaceId,
15
+ threadId,
13
16
  role: 'user',
14
17
  content: [{ type: 'text', text }],
15
18
  });
@@ -17,11 +20,11 @@ export const createUserMessage = (text: string): Message => ({
17
20
  /**
18
21
  * @deprecated Use {@link LLMTool} instead.
19
22
  */
20
- export const ToolDefinition = Schema.Struct({
21
- name: Schema.String,
22
- description: Schema.String,
23
+ export const ToolDefinition = S.Struct({
24
+ name: S.String,
25
+ description: S.String,
23
26
  parameters: JsonSchemaType,
24
- execute: Schema.Any,
27
+ execute: S.Any,
25
28
  });
26
29
 
27
30
  /**
@@ -83,14 +86,14 @@ export const ToolResult = Object.freeze({
83
86
  Break: (result: unknown): ToolResult => ({ kind: 'break', result }),
84
87
  });
85
88
 
86
- export type DefineToolParams<Params extends Schema.Schema.AnyNoContext> = {
89
+ export type DefineToolParams<Params extends S.Schema.AnyNoContext> = {
87
90
  name: string;
88
91
  description: string;
89
92
  schema: Params;
90
- execute: (params: Schema.Schema.Type<Params>, context: ToolExecutionContext) => Promise<ToolResult>;
93
+ execute: (params: S.Schema.Type<Params>, context: ToolExecutionContext) => Promise<ToolResult>;
91
94
  };
92
95
 
93
- export const defineTool = <Params extends Schema.Schema.AnyNoContext>({
96
+ export const defineTool = <Params extends S.Schema.AnyNoContext>({
94
97
  name,
95
98
  description,
96
99
  schema,
@@ -101,7 +104,7 @@ export const defineTool = <Params extends Schema.Schema.AnyNoContext>({
101
104
  description,
102
105
  parameters: toFunctionParameterSchema(schema),
103
106
  execute: (params, context) => {
104
- const sanitized = Schema.decodeSync(schema)(params);
107
+ const sanitized = S.decodeSync(schema)(params);
105
108
  return execute(sanitized, context);
106
109
  },
107
110
  };
@@ -110,7 +113,7 @@ export const defineTool = <Params extends Schema.Schema.AnyNoContext>({
110
113
  /**
111
114
  * Adapts schems to be able to pass to AI providers.
112
115
  */
113
- const toFunctionParameterSchema = (schema: Schema.Schema.All) => {
116
+ const toFunctionParameterSchema = (schema: S.Schema.All) => {
114
117
  const jsonSchema = toJsonSchema(schema);
115
118
  log('tool schema', { jsonSchema });
116
119
  delete jsonSchema.anyOf;
@@ -77,7 +77,7 @@ describe('Cypher Combinators', () => {
77
77
  test('match clause', ({ expect }) => {
78
78
  testParser(matchClause, 'MATCH (n:Person)');
79
79
  testParser(matchClause, 'MATCH (n:Person)-[r:KNOWS]->(m:Person)');
80
- testParser(matchClause, 'MATCH (o:Organization)-[:HAS_EMPLOYEE]->(e:Employee)');
80
+ testParser(matchClause, 'MATCH (o:Org)-[:HAS_EMPLOYEE]->(e:Employee)');
81
81
  });
82
82
 
83
83
  test('where clause', ({ expect }) => {
@@ -116,15 +116,15 @@ describe('Cypher Combinators', () => {
116
116
  );
117
117
  testParser(
118
118
  cypherQuery,
119
- 'MATCH (o:Organization)-[:HAS_EMPLOYEE]->(e:Employee)-[:WORKS_ON]->(p:Project) WHERE o.name = "DXOS" AND p.name = "Composer" RETURN e.name, e.id, p.name, p.id',
119
+ 'MATCH (o:Org)-[:HAS_EMPLOYEE]->(e:Employee)-[:WORKS_ON]->(p:Project) WHERE o.name = "DXOS" AND p.name = "Composer" RETURN e.name, e.id, p.name, p.id',
120
120
  );
121
121
  testParser(
122
122
  cypherQuery,
123
- 'MATCH (n:Organization {name: "DXOS"})-[:HAS_EMPLOYEE]->(m:Employee)<-[:WORKS_ON]-(p:Project {name: "Composer"}) RETURN m.name AS employee',
123
+ 'MATCH (n:Org {name: "DXOS"})-[:HAS_EMPLOYEE]->(m:Employee)<-[:WORKS_ON]-(p:Project {name: "Composer"}) RETURN m.name AS employee',
124
124
  );
125
125
  testParser(
126
126
  cypherQuery,
127
- "MATCH (org:Organization {name: 'DXOS'})-[:ORG_EMPLOYEES]->(c:Contact)<-[:TASK_ASSIGNEE]-(t:Task)-[:TASK_PROJECT]->(p:Project {name: 'Composer'}) RETURN c.name",
127
+ "MATCH (org:Org {name: 'DXOS'})-[:ORG_EMPLOYEES]->(c:Contact)<-[:TASK_ASSIGNEE]-(t:Task)-[:TASK_PROJECT]->(p:Project {name: 'Composer'}) RETURN c.name",
128
128
  );
129
129
  });
130
130
  });