@dxos/assistant 0.8.1-main.ba2dec9 → 0.8.1-staging.5be625a

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 (70) hide show
  1. package/dist/lib/browser/chunk-BDMTBYIM.mjs +2285 -0
  2. package/dist/lib/browser/chunk-BDMTBYIM.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 +15 -1
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/chunk-A44NGSCM.cjs +2296 -0
  9. package/dist/lib/node/chunk-A44NGSCM.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 +18 -7
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/lib/node-esm/chunk-J6DMSFSZ.mjs +2287 -0
  16. package/dist/lib/node-esm/chunk-J6DMSFSZ.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 +15 -1
  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 +215 -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 +10 -10
  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 +49 -23
  52. package/src/ai-service/parser.ts +1 -0
  53. package/src/ai-service/types.ts +2 -2
  54. package/src/bin/chat.ts +2 -2
  55. package/src/conversation/conversation.test.ts +3 -3
  56. package/src/index.ts +1 -0
  57. package/src/session/index.ts +5 -0
  58. package/src/session/session.test.ts +256 -0
  59. package/src/session/session.ts +369 -0
  60. package/src/testing/artifacts.test.ts +2 -2
  61. package/src/testing/index.ts +1 -0
  62. package/src/testing/object-extraction.test.ts +1 -1
  63. package/src/testing/ollama.ts +15 -0
  64. package/src/testing/query.test.ts +3 -3
  65. package/dist/lib/browser/chunk-AXUUNA7O.mjs +0 -565
  66. package/dist/lib/browser/chunk-AXUUNA7O.mjs.map +0 -7
  67. package/dist/lib/node/chunk-QPHGQINW.cjs +0 -577
  68. package/dist/lib/node/chunk-QPHGQINW.cjs.map +0 -7
  69. package/dist/lib/node-esm/chunk-GOVFOBIB.mjs +0 -567
  70. 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.5be625a",
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.5be625a",
44
+ "@dxos/async": "0.8.1-staging.5be625a",
45
+ "@dxos/debug": "0.8.1-staging.5be625a",
46
+ "@dxos/echo-db": "0.8.1-staging.5be625a",
47
+ "@dxos/echo-schema": "0.8.1-staging.5be625a",
48
+ "@dxos/context": "0.8.1-staging.5be625a",
49
+ "@dxos/keys": "0.8.1-staging.5be625a",
50
+ "@dxos/invariant": "0.8.1-staging.5be625a",
51
+ "@dxos/live-object": "0.8.1-staging.5be625a",
52
+ "@dxos/util": "0.8.1-staging.5be625a",
53
+ "@dxos/log": "0.8.1-staging.5be625a",
54
+ "@dxos/node-std": "0.8.1-staging.5be625a"
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(
@@ -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',
@@ -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(
@@ -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
 
@@ -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,6 +80,7 @@ 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
86
  // log.info('event', { type: event.type, event });
@@ -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),
@@ -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,
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';
@@ -0,0 +1,256 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema as S } from 'effect';
6
+ import { inspect } from 'node:util';
7
+ import { describe, test } from 'vitest';
8
+
9
+ import {
10
+ ArtifactId,
11
+ defineArtifact,
12
+ defineTool,
13
+ ToolResult,
14
+ type Message,
15
+ type MessageContentBlock,
16
+ } from '@dxos/artifact';
17
+ import { AIServiceEdgeClient } from '@dxos/assistant';
18
+ import { createStatic, EchoObject, ObjectId } from '@dxos/echo-schema';
19
+ import { DXN } from '@dxos/keys';
20
+ import { log } from '@dxos/log';
21
+
22
+ import { AISession } from './session';
23
+ import { AI_SERVICE_ENDPOINT } from '../testing';
24
+
25
+ // 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('example.com/type/CalendarEvent', '0.1.0'));
32
+
33
+ type CalendarEvent = S.Schema.Type<typeof CalendarEventSchema>;
34
+
35
+ describe('AISession with Ollama', () => {
36
+ test('create calendar itinerary', { timeout: 60_000 }, async () => {
37
+ const aiClient = new AIServiceEdgeClient({ endpoint: AI_SERVICE_ENDPOINT.REMOTE });
38
+ // const aiClient = new OllamaClient({
39
+ // overrides: { model: 'llama3.1:8b' },
40
+ // });
41
+ const session = new AISession({ operationModel: 'immediate' });
42
+
43
+ const objects = new Set<string>();
44
+
45
+ // Define calendar artifact
46
+ const calendarArtifact = defineArtifact({
47
+ id: 'artifact:dxos.org/plugin/calendar',
48
+ name: 'Calendar',
49
+ instructions: 'Use this to create and query calendar events.',
50
+ schema: CalendarEventSchema,
51
+ tools: [
52
+ defineTool('calendar', {
53
+ name: 'query',
54
+ description: 'Query the calendar for events',
55
+ schema: S.Struct({}),
56
+ execute: async () => {
57
+ return ToolResult.Success(CALENDAR_EVENTS);
58
+ },
59
+ }),
60
+ ],
61
+ });
62
+
63
+ const tableArtifact = defineArtifact({
64
+ id: 'artifact:dxos.org/plugin/table',
65
+ name: 'Table',
66
+ instructions: 'Use this to create and manage tables. Each table has a unique id.',
67
+ schema: S.Struct({}),
68
+ tools: [
69
+ defineTool('table', {
70
+ name: 'create',
71
+ description: 'Create a table',
72
+ schema: S.Struct({
73
+ data: S.Array(S.Any).annotations({ description: 'Array of data payloads to add as rows' }),
74
+ }),
75
+ execute: async ({ data }) => {
76
+ log.info('create table', { data });
77
+ const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
78
+ objects.add(id);
79
+ // TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
80
+ return ToolResult.Success(`table @${id}`);
81
+ },
82
+ }),
83
+ ],
84
+ });
85
+
86
+ const mapArtifact = defineArtifact({
87
+ id: 'artifact:dxos.org/plugin/map',
88
+ name: 'Map',
89
+ instructions:
90
+ 'Use this to create and manage maps. Maps source data from tables. Table id is required to create a map.',
91
+ schema: S.Struct({}),
92
+ tools: [
93
+ defineTool('map', {
94
+ name: 'create',
95
+ description: 'Create a map',
96
+ schema: S.Struct({
97
+ source: ArtifactId.annotations({
98
+ description: 'The table that will be used as the source of the map',
99
+ }),
100
+ }),
101
+ execute: async ({ source }) => {
102
+ // TODO(dmaretskyi): Use effect-schema decode instead of manual parsing.
103
+ const sourceId = ArtifactId.toDXN(source);
104
+ if (!objects.has(sourceId.toString())) {
105
+ return ToolResult.Error(`table id=${source} not found`);
106
+ }
107
+ log.info('create map', { sourceId });
108
+ const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
109
+ objects.add(id);
110
+ // TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
111
+ return ToolResult.Success(`map @${id}`);
112
+ },
113
+ }),
114
+ ],
115
+ });
116
+
117
+ const scriptArtifact = defineArtifact({
118
+ id: 'artifact:dxos.org/plugin/script',
119
+ name: 'Script',
120
+ instructions: 'Use this to create and manage scripts',
121
+ schema: S.Struct({}),
122
+ tools: [],
123
+ });
124
+
125
+ // session.streamEvent.on((event) => {
126
+ // printStreamEvent(event);
127
+ // });
128
+
129
+ session.message.on(printMessage);
130
+ session.userMessage.on(printMessage);
131
+ session.block.on(printContentBlock);
132
+
133
+ // session.update.on((update) => {
134
+ // log.info('update', { update });
135
+ // });
136
+
137
+ // Test creating an itinerary
138
+ const response = await session.run({
139
+ client: aiClient,
140
+ tools: [],
141
+ artifacts: [calendarArtifact, tableArtifact, mapArtifact, scriptArtifact],
142
+ history: [],
143
+ generationOptions: {
144
+ model: '@anthropic/claude-3-5-haiku-20241022',
145
+ },
146
+ prompt: 'create a table and map for a travel itinerary based on events in my calendar',
147
+ });
148
+
149
+ log.info('result', {
150
+ objects,
151
+ finalMessage: response.at(-1),
152
+ });
153
+ });
154
+ });
155
+
156
+ // Travel to rome, florence, livorno, siena, madrid for conferences
157
+ const CALENDAR_EVENTS: CalendarEvent[] = [
158
+ createStatic(CalendarEventSchema, {
159
+ title: 'Exploring Ancient Ruins in Rome',
160
+ startTime: '2024-01-01T10:00:00Z',
161
+ endTime: '2024-01-01T11:00:00Z',
162
+ description: 'Tech conference at the historic Colosseum with networking opportunities',
163
+ }),
164
+ createStatic(CalendarEventSchema, {
165
+ title: 'Renaissance Tech Summit in Florence',
166
+ startTime: '2024-01-01T11:00:00Z',
167
+ endTime: '2024-01-01T12:00:00Z',
168
+ description: 'Discussing AI innovations surrounded by Renaissance art',
169
+ }),
170
+ createStatic(CalendarEventSchema, {
171
+ title: 'Travel to Livorno',
172
+ startTime: '2024-01-01T12:00:00Z',
173
+ endTime: '2024-01-01T13:00:00Z',
174
+ description: 'Travel to Livorno',
175
+ }),
176
+ createStatic(CalendarEventSchema, {
177
+ title: 'Travel to Siena',
178
+ startTime: '2024-01-01T13:00:00Z',
179
+ endTime: '2024-01-01T14:00:00Z',
180
+ description: 'Travel to Siena',
181
+ }),
182
+ createStatic(CalendarEventSchema, {
183
+ title: 'Travel to Madrid',
184
+ startTime: '2024-01-01T14:00:00Z',
185
+ endTime: '2024-01-01T15:00:00Z',
186
+ description: 'Travel to Madrid',
187
+ }),
188
+ ];
189
+
190
+ const printMessage = (message: Message) => {
191
+ console.log(`${message.role.toUpperCase()}\n`);
192
+ for (const content of message.content) {
193
+ printContentBlock(content);
194
+ }
195
+ };
196
+
197
+ const printContentBlock = (content: MessageContentBlock) => {
198
+ switch (content.type) {
199
+ case 'text':
200
+ console.log(content.text);
201
+ break;
202
+ case 'tool_use':
203
+ console.log(`⚙️ [Tool Use] ${content.name} ${inspect(content.input, { depth: null, colors: true })}`);
204
+ break;
205
+ case 'tool_result': {
206
+ let data: any;
207
+ try {
208
+ data = JSON.parse(content.content);
209
+ } catch {
210
+ data = content.content;
211
+ }
212
+ console.log(`⚙️ [Tool Result] ${content.toolUseId} ${inspect(data, { depth: null, colors: true })}`);
213
+ break;
214
+ }
215
+ }
216
+ };
217
+
218
+ // const printStreamEvent = (event: GenerationStreamEvent) => {
219
+ // switch (event.type) {
220
+ // case 'message_start': {
221
+ // process.stdout.write(`${event.message.role.toUpperCase()}\n\n`);
222
+ // for (const content of event.message.content) {
223
+ // printContentBlock(content);
224
+ // }
225
+ // break;
226
+ // }
227
+ // case 'content_block_start': {
228
+ // printContentBlock(event.content);
229
+ // break;
230
+ // }
231
+ // case 'content_block_delta': {
232
+ // switch (event.delta.type) {
233
+ // case 'text_delta': {
234
+ // process.stdout.write(event.delta.text);
235
+ // break;
236
+ // }
237
+ // case 'input_json_delta': {
238
+ // process.stdout.write(event.delta.partial_json);
239
+ // break;
240
+ // }
241
+ // }
242
+ // break;
243
+ // }
244
+ // case 'content_block_stop': {
245
+ // process.stdout.write('\n');
246
+ // break;
247
+ // }
248
+ // case 'message_delta': {
249
+ // break;
250
+ // }
251
+ // case 'message_stop': {
252
+ // process.stdout.write('\n\n');
253
+ // break;
254
+ // }
255
+ // }
256
+ // };