@dxos/assistant 0.8.1-main.ba2dec9 → 0.8.1-staging.31c3ee1

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 (75) hide show
  1. package/dist/lib/browser/chunk-PND5VR6D.mjs +2315 -0
  2. package/dist/lib/browser/chunk-PND5VR6D.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +313 -1631
  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 +20 -6
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/chunk-IBOBRBDF.cjs +2326 -0
  9. package/dist/lib/node/chunk-IBOBRBDF.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +321 -1629
  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 +22 -11
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/lib/node-esm/chunk-DCQJOLSR.mjs +2317 -0
  16. package/dist/lib/node-esm/chunk-DCQJOLSR.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +313 -1631
  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 +20 -6
  21. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  22. package/dist/types/src/ai-service/defs.d.ts +8 -2
  23. package/dist/types/src/ai-service/defs.d.ts.map +1 -1
  24. package/dist/types/src/ai-service/edge-client.d.ts.map +1 -1
  25. package/dist/types/src/ai-service/message-collector.d.ts +16 -16
  26. package/dist/types/src/ai-service/message-collector.d.ts.map +1 -1
  27. package/dist/types/src/ai-service/ollama-client.d.ts +1 -5
  28. package/dist/types/src/ai-service/ollama-client.d.ts.map +1 -1
  29. package/dist/types/src/ai-service/parser.d.ts +8 -8
  30. package/dist/types/src/ai-service/parser.d.ts.map +1 -1
  31. package/dist/types/src/ai-service/types.d.ts +90 -156
  32. package/dist/types/src/ai-service/types.d.ts.map +1 -1
  33. package/dist/types/src/conversation/conversation.d.ts +8 -8
  34. package/dist/types/src/conversation/conversation.d.ts.map +1 -1
  35. package/dist/types/src/index.d.ts +1 -0
  36. package/dist/types/src/index.d.ts.map +1 -1
  37. package/dist/types/src/session/index.d.ts +2 -0
  38. package/dist/types/src/session/index.d.ts.map +1 -0
  39. package/dist/types/src/session/session.d.ts +216 -0
  40. package/dist/types/src/session/session.d.ts.map +1 -0
  41. package/dist/types/src/session/session.test.d.ts +2 -0
  42. package/dist/types/src/session/session.test.d.ts.map +1 -0
  43. package/dist/types/src/testing/index.d.ts +1 -0
  44. package/dist/types/src/testing/index.d.ts.map +1 -1
  45. package/dist/types/src/testing/ollama.d.ts +6 -0
  46. package/dist/types/src/testing/ollama.d.ts.map +1 -0
  47. package/package.json +13 -13
  48. package/src/ai-service/client.test.ts +14 -14
  49. package/src/ai-service/defs.ts +18 -3
  50. package/src/ai-service/edge-client.ts +3 -2
  51. package/src/ai-service/ollama-client.ts +50 -24
  52. package/src/ai-service/parser.ts +4 -2
  53. package/src/ai-service/stream.test.ts +28 -4
  54. package/src/ai-service/types.ts +2 -2
  55. package/src/bin/chat.ts +3 -3
  56. package/src/conversation/conversation.test.ts +3 -3
  57. package/src/conversation/conversation.ts +17 -1
  58. package/src/cypher/query-executor.test.ts +3 -3
  59. package/src/cypher/schema.test.ts +1 -1
  60. package/src/index.ts +1 -0
  61. package/src/session/index.ts +5 -0
  62. package/src/session/session.test.ts +257 -0
  63. package/src/session/session.ts +375 -0
  64. package/src/testing/artifacts.test.ts +6 -6
  65. package/src/testing/index.ts +1 -0
  66. package/src/testing/object-extraction.test.ts +2 -2
  67. package/src/testing/ollama.ts +15 -0
  68. package/src/testing/query.test.ts +5 -5
  69. package/src/testing/test-schema.ts +5 -5
  70. package/dist/lib/browser/chunk-AXUUNA7O.mjs +0 -565
  71. package/dist/lib/browser/chunk-AXUUNA7O.mjs.map +0 -7
  72. package/dist/lib/node/chunk-QPHGQINW.cjs +0 -577
  73. package/dist/lib/node/chunk-QPHGQINW.cjs.map +0 -7
  74. package/dist/lib/node-esm/chunk-GOVFOBIB.mjs +0 -567
  75. package/dist/lib/node-esm/chunk-GOVFOBIB.mjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/assistant",
3
- "version": "0.8.1-main.ba2dec9",
3
+ "version": "0.8.1-staging.31c3ee1",
4
4
  "description": "Natural language interface to DXOS",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -40,18 +40,18 @@
40
40
  "parjs": "^1.3.9",
41
41
  "parsimmon": "^1.18.1",
42
42
  "ulidx": "^2.3.0",
43
- "@dxos/context": "0.8.1-main.ba2dec9",
44
- "@dxos/artifact": "0.8.1-main.ba2dec9",
45
- "@dxos/async": "0.8.1-main.ba2dec9",
46
- "@dxos/debug": "0.8.1-main.ba2dec9",
47
- "@dxos/echo-db": "0.8.1-main.ba2dec9",
48
- "@dxos/echo-schema": "0.8.1-main.ba2dec9",
49
- "@dxos/invariant": "0.8.1-main.ba2dec9",
50
- "@dxos/live-object": "0.8.1-main.ba2dec9",
51
- "@dxos/keys": "0.8.1-main.ba2dec9",
52
- "@dxos/log": "0.8.1-main.ba2dec9",
53
- "@dxos/util": "0.8.1-main.ba2dec9",
54
- "@dxos/node-std": "0.8.1-main.ba2dec9"
43
+ "@dxos/artifact": "0.8.1-staging.31c3ee1",
44
+ "@dxos/async": "0.8.1-staging.31c3ee1",
45
+ "@dxos/echo-db": "0.8.1-staging.31c3ee1",
46
+ "@dxos/debug": "0.8.1-staging.31c3ee1",
47
+ "@dxos/echo-schema": "0.8.1-staging.31c3ee1",
48
+ "@dxos/invariant": "0.8.1-staging.31c3ee1",
49
+ "@dxos/context": "0.8.1-staging.31c3ee1",
50
+ "@dxos/keys": "0.8.1-staging.31c3ee1",
51
+ "@dxos/live-object": "0.8.1-staging.31c3ee1",
52
+ "@dxos/log": "0.8.1-staging.31c3ee1",
53
+ "@dxos/node-std": "0.8.1-staging.31c3ee1",
54
+ "@dxos/util": "0.8.1-staging.31c3ee1"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/parsimmon": "^1.10.9",
@@ -11,12 +11,12 @@ import { invariant } from '@dxos/invariant';
11
11
  import { SpaceId } from '@dxos/keys';
12
12
  import { log } from '@dxos/log';
13
13
 
14
- import { DEFAULT_LLM_MODEL } from './defs';
14
+ import { DEFAULT_EDGE_MODEL } from './defs';
15
15
  import { AIServiceEdgeClient } from './edge-client';
16
16
  import { OllamaClient } from './ollama-client';
17
17
  import { MixedStreamParser } from './parser';
18
18
  import { ToolTypes } from './types';
19
- import { AI_SERVICE_ENDPOINT } from '../testing';
19
+ import { createTestOllamaClient, AI_SERVICE_ENDPOINT } from '../testing';
20
20
 
21
21
  // log.config({ filter: 'debug' });
22
22
 
@@ -40,7 +40,7 @@ describe.skip('AI Service Client', () => {
40
40
  ]);
41
41
 
42
42
  const stream = await client.exec({
43
- model: DEFAULT_LLM_MODEL,
43
+ model: DEFAULT_EDGE_MODEL,
44
44
  spaceId,
45
45
  threadId,
46
46
  systemPrompt: 'You are a poet',
@@ -85,7 +85,7 @@ describe.skip('AI Service Client', () => {
85
85
 
86
86
  {
87
87
  const stream1 = await client.exec({
88
- model: DEFAULT_LLM_MODEL,
88
+ model: DEFAULT_EDGE_MODEL,
89
89
  spaceId,
90
90
  threadId,
91
91
  systemPrompt: 'You are a helpful assistant.',
@@ -119,7 +119,7 @@ describe.skip('AI Service Client', () => {
119
119
 
120
120
  {
121
121
  const stream2 = await client.exec({
122
- model: DEFAULT_LLM_MODEL,
122
+ model: DEFAULT_EDGE_MODEL,
123
123
  spaceId,
124
124
  threadId,
125
125
  systemPrompt: 'You are a helpful assistant.',
@@ -157,7 +157,7 @@ describe.skip('AI Service Client', () => {
157
157
  ]);
158
158
 
159
159
  const stream = await client.exec({
160
- model: DEFAULT_LLM_MODEL,
160
+ model: DEFAULT_EDGE_MODEL,
161
161
  spaceId,
162
162
  threadId,
163
163
  tools: [
@@ -179,14 +179,14 @@ describe.skip('AI Service Client', () => {
179
179
  });
180
180
  });
181
181
 
182
- describe('Ollama Client', () => {
182
+ describe.skip('Ollama Client', () => {
183
183
  test('basic', async (ctx) => {
184
184
  const isRunning = await OllamaClient.isRunning();
185
185
  if (!isRunning) {
186
186
  ctx.skip();
187
187
  }
188
188
 
189
- const client = OllamaClient.createClient();
189
+ const client = createTestOllamaClient();
190
190
  const parser = new MixedStreamParser();
191
191
 
192
192
  const messages = await parser.parse(
@@ -198,7 +198,7 @@ describe('Ollama Client', () => {
198
198
  }),
199
199
  );
200
200
 
201
- log.info('messages', { messages });
201
+ log('messages', { messages });
202
202
  });
203
203
 
204
204
  test('tool calls', async (ctx) => {
@@ -207,7 +207,7 @@ describe('Ollama Client', () => {
207
207
  ctx.skip();
208
208
  }
209
209
 
210
- const client = OllamaClient.createClient({
210
+ const client = createTestOllamaClient({
211
211
  tools: [
212
212
  defineTool('test', {
213
213
  name: 'encrypt',
@@ -221,7 +221,7 @@ describe('Ollama Client', () => {
221
221
  });
222
222
  const parser = new MixedStreamParser();
223
223
  parser.streamEvent.on((event) => {
224
- // log.info('event', { event });
224
+ // log('event', { event });
225
225
  });
226
226
 
227
227
  const messages = await parser.parse(
@@ -233,7 +233,7 @@ describe('Ollama Client', () => {
233
233
  }),
234
234
  );
235
235
 
236
- log.info('messages', { messages });
236
+ log('messages', { messages });
237
237
  });
238
238
 
239
239
  test('text-to-image', async (ctx) => {
@@ -242,7 +242,7 @@ describe('Ollama Client', () => {
242
242
  ctx.skip();
243
243
  }
244
244
 
245
- const client = OllamaClient.createClient();
245
+ const client = createTestOllamaClient();
246
246
  const parser = new MixedStreamParser();
247
247
 
248
248
  const messages = await parser.parse(
@@ -260,6 +260,6 @@ describe('Ollama Client', () => {
260
260
  }),
261
261
  );
262
262
 
263
- log.info('messages', { messages });
263
+ log('messages', { messages });
264
264
  });
265
265
  });
@@ -5,9 +5,9 @@
5
5
  // TODO(burdon): Share with Edge.
6
6
  // TODO(burdon): Move to service config retrieved by HTTP?
7
7
 
8
- export const DEFAULT_LLM_MODEL = '@anthropic/claude-3-5-sonnet-20241022';
8
+ export const DEFAULT_EDGE_MODEL = '@anthropic/claude-3-5-sonnet-20241022';
9
9
 
10
- export const DEFAULT_LLM_MODELS = [
10
+ export const DEFAULT_EDGE_MODELS = [
11
11
  // AI Gateway.
12
12
  // https://developers.cloudflare.com/ai-gateway
13
13
  '@anthropic/claude-3-5-haiku-20241022',
@@ -21,4 +21,19 @@ export const DEFAULT_LLM_MODELS = [
21
21
  '@ollama/llama-3-1-nemotron-70b-instruct',
22
22
  '@ollama/llama-3-1-nemotron-mini-4b-instruct',
23
23
  '@ollama/llama-3-2-3b',
24
- ];
24
+ ] as const;
25
+
26
+ // TODO(burdon): Config.
27
+ export const DEFAULT_OLLAMA_ENDPOINT = 'http://localhost:11434';
28
+
29
+ export const DEFAULT_OLLAMA_MODEL = 'llama3.2:1b';
30
+
31
+ /**
32
+ * https://ollama.com/library
33
+ */
34
+ export const DEFAULT_OLLAMA_MODELS = [
35
+ //
36
+ 'llama3.2:1b',
37
+ 'llama3:70b',
38
+ 'deepseek-r1:latest',
39
+ ] as const;
@@ -5,7 +5,7 @@
5
5
  import { Schema as S } from 'effect';
6
6
 
7
7
  import { Message } from '@dxos/artifact';
8
- import { invariant } from '@dxos/invariant';
8
+ import { assertArgument, invariant } from '@dxos/invariant';
9
9
  import { log } from '@dxos/log';
10
10
 
11
11
  import type { AIServiceClient, GenerationStream } from './service';
@@ -47,7 +47,8 @@ export class AIServiceEdgeClient implements AIServiceClient {
47
47
  * Process request and open message stream.
48
48
  */
49
49
  async exec(request: GenerateRequest): Promise<GenerationStream> {
50
- log.info('requesting', { endpoint: this._endpoint });
50
+ assertArgument(typeof request.model === 'string', 'model is required');
51
+ log('requesting', { endpoint: this._endpoint });
51
52
  const controller = new AbortController();
52
53
  const response = await fetch(`${this._endpoint}/generate`, {
53
54
  signal: controller.signal,
@@ -2,21 +2,20 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { Schema as S } from 'effect';
6
+
5
7
  import { type MessageContentBlock, defineTool, type Tool, ToolResult } from '@dxos/artifact';
6
- import { ObjectId, S } from '@dxos/echo-schema';
8
+ import { ObjectId } from '@dxos/echo-schema';
7
9
  import { invariant } from '@dxos/invariant';
8
10
  import { log } from '@dxos/log';
9
11
 
12
+ import { DEFAULT_OLLAMA_ENDPOINT } from './defs';
10
13
  import { MessageCollector, emitMessageAsEvents } from './message-collector';
11
14
  import { type AIServiceClient, type GenerationStream } from './service';
12
15
  import { GenerationStreamImpl } from './stream';
13
16
  import { ToolTypes, type GenerateRequest, type GenerationStreamEvent } from './types';
14
17
  import { isToolUse, runTools } from '../conversation';
15
18
 
16
- // TODO(burdon): Config.
17
- const DEFAULT_OLLAMA_URL = 'http://localhost:11434';
18
- const DEFAULT_OLLAMA_MODEL = 'llama3.2:1b';
19
-
20
19
  export type OllamaClientParams = {
21
20
  endpoint?: string;
22
21
 
@@ -42,23 +41,13 @@ export type OllamaClientParams = {
42
41
  };
43
42
 
44
43
  export class OllamaClient implements AIServiceClient {
45
- /**
46
- * Create a test client with small local model and no temperature for predictable results.
47
- */
48
- static createClient(options?: Pick<OllamaClientParams, 'tools'>) {
49
- return new OllamaClient({
50
- tools: options?.tools,
51
- overrides: { model: DEFAULT_OLLAMA_MODEL, temperature: 0 },
52
- });
53
- }
54
-
55
44
  /**
56
45
  * Check if Ollama server is running and accessible.
57
46
  * @returns Promise that resolves to true if Ollama is running, false otherwise.
58
47
  */
59
48
  static async isRunning(): Promise<boolean> {
60
49
  try {
61
- const response = await fetch(`${DEFAULT_OLLAMA_URL}/api/version`, {
50
+ const response = await fetch(`${DEFAULT_OLLAMA_ENDPOINT}/api/version`, {
62
51
  method: 'GET',
63
52
  headers: {
64
53
  'Content-Type': 'application/json',
@@ -78,8 +67,8 @@ export class OllamaClient implements AIServiceClient {
78
67
  private readonly _temperatureOverride?: number;
79
68
  private readonly _maxToolInvocations: number;
80
69
 
81
- constructor({ endpoint, tools, overrides, maxToolInvocations }: OllamaClientParams) {
82
- this._endpoint = endpoint ?? DEFAULT_OLLAMA_URL;
70
+ constructor({ endpoint, tools, overrides, maxToolInvocations }: OllamaClientParams = {}) {
71
+ this._endpoint = endpoint ?? DEFAULT_OLLAMA_ENDPOINT;
83
72
  this._tools = tools ?? [];
84
73
  this._modelOverride = overrides?.model;
85
74
  this._temperatureOverride = overrides?.temperature;
@@ -104,7 +93,7 @@ export class OllamaClient implements AIServiceClient {
104
93
  messages: [],
105
94
  stream: true,
106
95
  options: {
107
- temperature: this._temperatureOverride ?? 0.7,
96
+ temperature: this._temperatureOverride,
108
97
  },
109
98
  };
110
99
 
@@ -190,7 +179,7 @@ export class OllamaClient implements AIServiceClient {
190
179
  }
191
180
  }
192
181
 
193
- log.info('generate request', { history: request.history, prompt: request.prompt, ollamaRequest });
182
+ log('generate request', { history: request.history, prompt: request.prompt, ollamaRequest });
194
183
 
195
184
  const response = await fetch(`${this._endpoint}/api/chat`, {
196
185
  method: 'POST',
@@ -201,10 +190,9 @@ export class OllamaClient implements AIServiceClient {
201
190
  signal,
202
191
  });
203
192
 
204
- // log.info('ollama response', { response: await response.text() });
205
-
193
+ // TODO(burdon): Test if model is not found.
206
194
  if (!response.ok) {
207
- throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
195
+ throw await parseErrorResponse(response);
208
196
  }
209
197
 
210
198
  if (!response.body) {
@@ -253,7 +241,7 @@ export class OllamaClient implements AIServiceClient {
253
241
  break;
254
242
  }
255
243
 
256
- log.info('ollama data', { data });
244
+ log('ollama data', { data });
257
245
 
258
246
  // Ollama sends chunks of text, we convert them to our event format.
259
247
  if (data.message?.content) {
@@ -471,3 +459,41 @@ class OllamaDecoderStream extends TransformStream<string, OllamaResponseData> {
471
459
  });
472
460
  }
473
461
  }
462
+
463
+ class ModelDoesNotSupportToolsError extends S.TaggedError<ModelDoesNotSupportToolsError>()(
464
+ 'ModelDoesNotSupportToolsError',
465
+ {
466
+ model: S.String,
467
+ },
468
+ ) {}
469
+
470
+ class ModelNotFoundError extends S.TaggedError<ModelNotFoundError>()('ModelNotFoundError', {
471
+ model: S.String,
472
+ }) {}
473
+
474
+ const parseOllamaError = (error: string): Error => {
475
+ {
476
+ const match = error.match(/^([^']+) does not support tools$/);
477
+ if (match) {
478
+ return new ModelDoesNotSupportToolsError({ model: match[1] });
479
+ }
480
+ }
481
+
482
+ {
483
+ const match = error.match(/^model "([^"]+)" not found, try pulling it first$/);
484
+ if (match) {
485
+ return new ModelNotFoundError({ model: match[1] });
486
+ }
487
+ }
488
+
489
+ throw new Error(`Ollama API error: ${error}`);
490
+ };
491
+
492
+ const parseErrorResponse = async (response: Response): Promise<Error> => {
493
+ if (response.headers.get('content-type')?.includes('application/json')) {
494
+ const body = await response.json();
495
+ return parseOllamaError(body.error);
496
+ }
497
+
498
+ throw new Error(`Ollama API error: ${response.status} ${response.statusText} ${await response.text()}`);
499
+ };
@@ -80,9 +80,10 @@ export class MixedStreamParser {
80
80
  const messagesCollected: Message[] = [];
81
81
 
82
82
  for await (const event of stream) {
83
+ log('streamEvent', { event });
83
84
  this.streamEvent.emit(event);
84
85
 
85
- // log.info('event', { type: event.type, event });
86
+ // log('event', { type: event.type, event });
86
87
  switch (event.type) {
87
88
  //
88
89
  // Messages.
@@ -143,7 +144,7 @@ export class MixedStreamParser {
143
144
  case 'text_delta': {
144
145
  const chunks = transformer.transform(event.delta.text);
145
146
  for (const chunk of chunks) {
146
- // log.info('text_delta', { chunk, current });
147
+ log('text_delta', { chunk });
147
148
 
148
149
  switch (streamBlock?.type) {
149
150
  //
@@ -157,6 +158,7 @@ export class MixedStreamParser {
157
158
  if (stack.length > 0) {
158
159
  const top = stack.pop();
159
160
  invariant(top && top.type === 'tag');
161
+ log('pop', { top });
160
162
  top.content.push(streamBlock);
161
163
  streamBlock = top;
162
164
  } else {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { describe, it } from 'vitest';
5
+ import { describe, test } from 'vitest';
6
6
 
7
7
  import { type Message } from '@dxos/artifact';
8
8
  import { log } from '@dxos/log';
@@ -55,7 +55,7 @@ const TEST_BLOCKS = [
55
55
  ];
56
56
 
57
57
  describe('GenerationStream', () => {
58
- it('should generate a stream of blocks', async ({ expect }) => {
58
+ test('should generate a stream of blocks', async ({ expect }) => {
59
59
  const stream = createGenerationStream(new Response(createTestSSEStream(TEST_BLOCKS)));
60
60
  const events: GenerationStreamEvent[] = [];
61
61
  for await (const event of stream) {
@@ -66,7 +66,7 @@ describe('GenerationStream', () => {
66
66
  });
67
67
 
68
68
  for (const splitBy of ['word'] as const) {
69
- it(`should emit xml blocks (splitBy: ${splitBy})`, async ({ expect }) => {
69
+ test(`should emit xml blocks (splitBy: ${splitBy})`, async ({ expect }) => {
70
70
  const stream = createGenerationStream(new Response(createTestSSEStream(TEST_BLOCKS, { splitBy })));
71
71
  const parser = new MixedStreamParser();
72
72
 
@@ -78,7 +78,7 @@ describe('GenerationStream', () => {
78
78
  await parser.parse(stream);
79
79
 
80
80
  for (const block of message?.content ?? []) {
81
- log.info('block', { block });
81
+ log('block', { block });
82
82
  }
83
83
  expect(message?.content.map((block) => block.type)).to.deep.eq([
84
84
  //
@@ -98,4 +98,28 @@ describe('GenerationStream', () => {
98
98
  ]);
99
99
  });
100
100
  }
101
+
102
+ test.skip('invalid xml', async ({ expect }) => {
103
+ const BLOCKS = [
104
+ //
105
+ '<cot> thoughts <tool-list> more thoughts </cot> <tool-list/>',
106
+ ];
107
+
108
+ const stream = createGenerationStream(new Response(createTestSSEStream(BLOCKS, { splitBy: 'word' })));
109
+ const parser = new MixedStreamParser();
110
+
111
+ const result = await parser.parse(stream);
112
+ expect(result[0].content).to.deep.eq([
113
+ {
114
+ type: 'text',
115
+ disposition: 'cot',
116
+ text: 'thoughts more thoughts',
117
+ },
118
+ {
119
+ type: 'json',
120
+ disposition: 'tool_list',
121
+ json: '{}',
122
+ },
123
+ ]);
124
+ });
101
125
  });
@@ -7,11 +7,11 @@ import { Schema as S } from 'effect';
7
7
  import { Tool, Message, type MessageContentBlock, SpaceIdSchema } from '@dxos/artifact';
8
8
  import { ObjectId } from '@dxos/echo-schema';
9
9
 
10
- import { DEFAULT_LLM_MODELS } from './defs';
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 = S.Literal(...DEFAULT_LLM_MODELS);
14
+ export const LLMModel = S.Literal(...DEFAULT_EDGE_MODELS, ...DEFAULT_OLLAMA_MODELS);
15
15
 
16
16
  export type LLMModel = S.Schema.Type<typeof LLMModel>;
17
17
 
package/src/bin/chat.ts CHANGED
@@ -10,7 +10,7 @@ import { ObjectId } from '@dxos/echo-schema';
10
10
  import { SpaceId } from '@dxos/keys';
11
11
  import { log } from '@dxos/log';
12
12
 
13
- import { AIServiceEdgeClient, ToolTypes, DEFAULT_LLM_MODEL } from '../ai-service';
13
+ import { AIServiceEdgeClient, ToolTypes, DEFAULT_EDGE_MODEL } from '../ai-service';
14
14
  import { runLLM } from '../conversation';
15
15
  import {
16
16
  AI_SERVICE_ENDPOINT,
@@ -49,7 +49,7 @@ while (true) {
49
49
  await client.appendMessages([createUserMessage(spaceId, threadId, prompt.message)]);
50
50
 
51
51
  await runLLM({
52
- model: DEFAULT_LLM_MODEL,
52
+ model: DEFAULT_EDGE_MODEL,
53
53
  spaceId,
54
54
  threadId,
55
55
  system: createSystemPrompt(schemaTypes),
@@ -69,7 +69,7 @@ while (true) {
69
69
  onImage: (img) => {
70
70
  const path = `/tmp/image-${img.id}.jpeg`;
71
71
  writeFileSync(path, Buffer.from(img.source.data, 'base64'));
72
- log.info('Saved image', { path });
72
+ log('Saved image', { path });
73
73
  // Print image in iTerm using ANSI escape sequence
74
74
  const imageData = img.source.data;
75
75
  process.stdout.write('\x1b]1337;File=inline=1:' + imageData + '\x07');
@@ -11,7 +11,7 @@ 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_LLM_MODEL } from '../ai-service';
14
+ import { AIServiceEdgeClient, DEFAULT_EDGE_MODEL } from '../ai-service';
15
15
  import { AI_SERVICE_ENDPOINT } from '../testing';
16
16
 
17
17
  // TODO(burdon): Local live LLM test.
@@ -25,7 +25,7 @@ describe.skip('Conversation tests', () => {
25
25
  const threadId = ObjectId.random();
26
26
 
27
27
  const result = await runLLM({
28
- model: DEFAULT_LLM_MODEL,
28
+ model: DEFAULT_EDGE_MODEL,
29
29
  history: [createUserMessage(spaceId, threadId, 'Hello, how are you?')],
30
30
  tools: [],
31
31
  client,
@@ -56,7 +56,7 @@ describe.skip('Conversation tests', () => {
56
56
  const threadId = ObjectId.random();
57
57
 
58
58
  const result = await runLLM({
59
- model: DEFAULT_LLM_MODEL,
59
+ model: DEFAULT_EDGE_MODEL,
60
60
  history: [createUserMessage(spaceId, threadId, 'What is the password? Ask the custodian.')],
61
61
  tools: [custodian],
62
62
  client,
@@ -175,7 +175,23 @@ export const runTools = async ({ message, tools, extensions }: RunToolsOptions):
175
175
  const toolCall = toolCalls[0];
176
176
  const tool = tools.find((tool) => tool.name === toolCall.name);
177
177
  if (!tool) {
178
- throw new Error(`Tool not found: ${toolCall.name}`);
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
+ };
179
195
  }
180
196
 
181
197
  let toolResult: ToolResult;
@@ -12,10 +12,10 @@ import { createTestData } from '../testing';
12
12
  test.skip('run', async ({ expect }) => {
13
13
  const dataSource = createTestData();
14
14
  // for (const node of dataSource.nodes) {
15
- // log.info('node', { id: node.id, label: node.label, name: node.properties.name });
15
+ // log('node', { id: node.id, label: node.label, name: node.properties.name });
16
16
  // }
17
17
  // for (const relationship of dataSource.relationships) {
18
- // log.info('relationship', {
18
+ // log('relationship', {
19
19
  // source: relationship.source.id,
20
20
  // target: relationship.target.id,
21
21
  // label: relationship.label,
@@ -43,5 +43,5 @@ test.skip('run', async ({ expect }) => {
43
43
  },
44
44
  ]);
45
45
 
46
- log.info('result', { result });
46
+ log('result', { result });
47
47
  });
@@ -12,6 +12,6 @@ import { Contact, Org, Project, Task } from '../testing';
12
12
  test('Org-Project-Task-Contact schema', async ({ expect }) => {
13
13
  const jsonSchema = [Org, Project, Task, Contact].map((schema) => toJsonSchema(schema));
14
14
  const result = formatJsonSchemaForLLM(jsonSchema);
15
- // log.info('result', { result });
15
+ // log('result', { result });
16
16
  expect(result).toHaveLength(8);
17
17
  });
package/src/index.ts CHANGED
@@ -5,3 +5,4 @@
5
5
  export * from './ai-service';
6
6
  export * from './cypher';
7
7
  export * from './conversation';
8
+ export * from './session';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './session';