@dxos/assistant 0.8.4-main.fd6878d → 0.8.4-main.fffef41

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 (122) hide show
  1. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  2. package/dist/lib/browser/extraction/index.mjs +121 -165
  3. package/dist/lib/browser/extraction/index.mjs.map +4 -4
  4. package/dist/lib/browser/index.mjs +1097 -161
  5. package/dist/lib/browser/index.mjs.map +4 -4
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  8. package/dist/lib/node-esm/extraction/index.mjs +121 -165
  9. package/dist/lib/node-esm/extraction/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/index.mjs +1097 -161
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/types/src/conversation/context.d.ts +23 -10
  14. package/dist/types/src/conversation/context.d.ts.map +1 -1
  15. package/dist/types/src/conversation/conversation.d.ts +22 -31
  16. package/dist/types/src/conversation/conversation.d.ts.map +1 -1
  17. package/dist/types/src/diff.d.ts +15 -0
  18. package/dist/types/src/diff.d.ts.map +1 -0
  19. package/dist/types/src/diff.test.d.ts +2 -0
  20. package/dist/types/src/diff.test.d.ts.map +1 -0
  21. package/dist/types/src/errors.d.ts +45 -13
  22. package/dist/types/src/errors.d.ts.map +1 -1
  23. package/dist/types/src/execution-graph.d.ts +85 -0
  24. package/dist/types/src/execution-graph.d.ts.map +1 -0
  25. package/dist/types/src/execution-graph.test.d.ts +2 -0
  26. package/dist/types/src/execution-graph.test.d.ts.map +1 -0
  27. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +1 -1
  28. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
  29. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +82 -43
  30. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
  31. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
  32. package/dist/types/src/extraction/extraction.d.ts +262 -225
  33. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  34. package/dist/types/src/extraction/quotes.d.ts +1 -1
  35. package/dist/types/src/extraction/quotes.d.ts.map +1 -1
  36. package/dist/types/src/functions/services.d.ts +18 -5
  37. package/dist/types/src/functions/services.d.ts.map +1 -1
  38. package/dist/types/src/index.d.ts +3 -1
  39. package/dist/types/src/index.d.ts.map +1 -1
  40. package/dist/types/src/session/GenericToolkit.d.ts +84 -0
  41. package/dist/types/src/session/GenericToolkit.d.ts.map +1 -0
  42. package/dist/types/src/session/artifact-diff.d.ts +2 -2
  43. package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
  44. package/dist/types/src/session/error-handling.d.ts +4 -0
  45. package/dist/types/src/session/error-handling.d.ts.map +1 -0
  46. package/dist/types/src/session/format.d.ts +5 -161
  47. package/dist/types/src/session/format.d.ts.map +1 -1
  48. package/dist/types/src/session/index.d.ts +3 -0
  49. package/dist/types/src/session/index.d.ts.map +1 -1
  50. package/dist/types/src/session/observer.d.ts +22 -0
  51. package/dist/types/src/session/observer.d.ts.map +1 -0
  52. package/dist/types/src/session/session.d.ts +22 -138
  53. package/dist/types/src/session/session.d.ts.map +1 -1
  54. package/dist/types/src/session/toolkit.d.ts +15 -0
  55. package/dist/types/src/session/toolkit.d.ts.map +1 -0
  56. package/dist/types/src/session/version-pin.d.ts +3 -3
  57. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  58. package/dist/types/src/templates/system.d.ts +8 -12
  59. package/dist/types/src/templates/system.d.ts.map +1 -1
  60. package/dist/types/src/types/artifact.d.ts +20 -0
  61. package/dist/types/src/types/artifact.d.ts.map +1 -0
  62. package/dist/types/src/types/index.d.ts +2 -0
  63. package/dist/types/src/types/index.d.ts.map +1 -0
  64. package/dist/types/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +29 -31
  66. package/src/conversation/context.ts +75 -22
  67. package/src/conversation/conversation.ts +80 -77
  68. package/src/diff.test.ts +43 -0
  69. package/src/diff.ts +84 -0
  70. package/src/errors.ts +6 -1
  71. package/src/execution-graph.test.ts +740 -0
  72. package/src/execution-graph.ts +509 -0
  73. package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +9 -9
  74. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +47 -56
  75. package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +16 -16
  76. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +42 -43
  77. package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +12 -6
  78. package/src/extraction/extraction-ner-function/named-entity-recognition.ts +2 -0
  79. package/src/extraction/extraction.ts +7 -6
  80. package/src/extraction/quotes.test.ts +2 -2
  81. package/src/extraction/quotes.ts +3 -3
  82. package/src/functions/services.ts +104 -56
  83. package/src/index.ts +3 -1
  84. package/src/session/GenericToolkit.ts +130 -0
  85. package/src/session/artifact-diff.ts +8 -4
  86. package/src/session/error-handling.ts +41 -0
  87. package/src/session/format.test.ts +5 -4
  88. package/src/session/format.ts +27 -22
  89. package/src/session/index.ts +4 -0
  90. package/src/session/observer.ts +59 -0
  91. package/src/session/session.conversations.json +1 -0
  92. package/src/session/session.test.ts +35 -191
  93. package/src/session/session.ts +119 -185
  94. package/src/session/toolkit.ts +48 -0
  95. package/src/session/version-pin.ts +4 -5
  96. package/src/templates/{format.tpl → instructions/format.tpl} +8 -2
  97. package/src/templates/{system.tpl → instructions/system.tpl} +15 -14
  98. package/src/templates/system.ts +25 -16
  99. package/src/{types.ts → types/artifact.ts} +22 -3
  100. package/src/types/index.ts +5 -0
  101. package/dist/lib/browser/chunk-6WRGDEMH.mjs +0 -89
  102. package/dist/lib/browser/chunk-6WRGDEMH.mjs.map +0 -7
  103. package/dist/lib/browser/chunk-NZGGCEF3.mjs +0 -341
  104. package/dist/lib/browser/chunk-NZGGCEF3.mjs.map +0 -7
  105. package/dist/lib/browser/named-entity-recognition-O46UTCLR.mjs +0 -13
  106. package/dist/lib/node-esm/chunk-3UU5V4FS.mjs +0 -91
  107. package/dist/lib/node-esm/chunk-3UU5V4FS.mjs.map +0 -7
  108. package/dist/lib/node-esm/chunk-XOOPGSGW.mjs +0 -343
  109. package/dist/lib/node-esm/chunk-XOOPGSGW.mjs.map +0 -7
  110. package/dist/lib/node-esm/named-entity-recognition-W4VMRCH6.mjs +0 -14
  111. package/dist/types/src/session/version-pin.test.d.ts +0 -2
  112. package/dist/types/src/session/version-pin.test.d.ts.map +0 -1
  113. package/dist/types/src/summarization/summarization.test.d.ts +0 -2
  114. package/dist/types/src/summarization/summarization.test.d.ts.map +0 -1
  115. package/dist/types/src/types.d.ts +0 -16
  116. package/dist/types/src/types.d.ts.map +0 -1
  117. package/src/session/__snapshots__/session.test.ts.snap +0 -32
  118. package/src/session/version-pin.test.ts +0 -7
  119. package/src/summarization/summarization.test.ts +0 -197
  120. /package/dist/lib/browser/{named-entity-recognition-O46UTCLR.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
  121. /package/dist/lib/node-esm/{named-entity-recognition-W4VMRCH6.mjs.map → chunk-HSLMI22Q.mjs.map} +0 -0
  122. /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
@@ -2,13 +2,18 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { AiTool, AiToolkit } from '@effect/ai';
5
+ import * as Tool from '@effect/ai/Tool';
6
+ import * as Toolkit from '@effect/ai/Toolkit';
6
7
  import { describe, it } from '@effect/vitest';
7
- import { Effect, Layer, Schema } from 'effect';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Layer from 'effect/Layer';
10
+ import * as Schema from 'effect/Schema';
8
11
 
9
- import { AiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
12
+ import { AiService, MemoizedAiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
10
13
  import { AiServiceTestingPreset } from '@dxos/ai/testing';
11
14
  import { Obj, Type } from '@dxos/echo';
15
+ import { TestHelpers } from '@dxos/effect';
16
+ import { TracingService } from '@dxos/functions';
12
17
  import { log } from '@dxos/log';
13
18
 
14
19
  import { AiSession } from './session';
@@ -28,8 +33,8 @@ const CalendarEventSchema = Schema.Struct({
28
33
 
29
34
  type CalendarEvent = Schema.Schema.Type<typeof CalendarEventSchema>;
30
35
 
31
- class TestToolkit extends AiToolkit.make(
32
- AiTool.make('Calculator', {
36
+ const TestToolkit = Toolkit.make(
37
+ Tool.make('Calculator', {
33
38
  description: 'Basic calculator tool',
34
39
  parameters: {
35
40
  input: Schema.String.annotations({
@@ -41,7 +46,7 @@ class TestToolkit extends AiToolkit.make(
41
46
  }),
42
47
  failure: Schema.Never,
43
48
  }),
44
- ) {}
49
+ );
45
50
 
46
51
  // Tool handlers.
47
52
  const toolkitLayer = TestToolkit.toLayer({
@@ -60,11 +65,23 @@ const toolkitLayer = TestToolkit.toLayer({
60
65
  }),
61
66
  });
62
67
 
63
- describe.runIf(process.env.DX_RUN_SLOW_TESTS)('AiSession', () => {
68
+ const TestLayer = Layer.mergeAll(
69
+ toolkitLayer,
70
+ AiService.model('@anthropic/claude-3-5-sonnet-20241022'),
71
+ TracingService.layerNoop,
72
+ ToolResolverService.layerEmpty,
73
+ ToolExecutionService.layerEmpty,
74
+ ).pipe(
75
+ //
76
+ Layer.provideMerge(MemoizedAiService.layerTest()),
77
+ Layer.provide(AiServiceTestingPreset('direct')),
78
+ );
79
+
80
+ describe('AiSession', () => {
64
81
  it.effect(
65
82
  'no tools',
66
- Effect.fn(
67
- function* ({ expect: _ }) {
83
+ Effect.fnUntraced(
84
+ function* (_) {
68
85
  const session = new AiSession({ operationModel: 'configured' });
69
86
  const response = yield* session.run({
70
87
  prompt: 'Hello world!',
@@ -72,161 +89,28 @@ describe.runIf(process.env.DX_RUN_SLOW_TESTS)('AiSession', () => {
72
89
  });
73
90
  log.info('response', { response });
74
91
  },
75
- Effect.provide(
76
- AiService.model('@anthropic/claude-3-5-sonnet-20241022').pipe(
77
- Layer.provideMerge(ToolResolverService.layerEmpty),
78
- Layer.provideMerge(ToolExecutionService.layerEmpty),
79
- Layer.provideMerge(AiServiceTestingPreset('direct')),
80
- ),
81
- ),
92
+ Effect.provide(TestLayer),
93
+ TestHelpers.provideTestContext,
82
94
  ),
83
95
  );
84
96
 
85
97
  it.effect(
86
98
  'calculator',
87
- Effect.fn(
88
- function* ({ expect: _ }) {
99
+ Effect.fnUntraced(
100
+ function* (_) {
89
101
  const session = new AiSession({ operationModel: 'configured' });
102
+ const toolkit = yield* TestToolkit;
90
103
  const response = yield* session.run({
91
- prompt: 'What is 10 + 20?',
104
+ toolkit,
105
+ prompt: 'What is 10 + 30?',
92
106
  history: [],
93
- toolkit: TestToolkit,
94
107
  });
95
108
  log.info('response', { response });
96
109
  },
97
- Effect.provide(
98
- Layer.mergeAll(
99
- toolkitLayer,
100
- ToolResolverService.layerEmpty,
101
- ToolExecutionService.layerEmpty,
102
- AiService.model('@anthropic/claude-3-5-sonnet-20241022').pipe(
103
- Layer.provideMerge(AiServiceTestingPreset('direct')),
104
- ),
105
- ),
106
- ),
110
+ Effect.provide(TestLayer),
111
+ TestHelpers.provideTestContext,
107
112
  ),
108
113
  );
109
-
110
- // TODO(dmaretskyi): Revive test.
111
- /*
112
- it.skip('create calendar itinerary', { timeout: 60_000 }, async () => {
113
- // overrides: { model: 'llama3.1:8b' },
114
- const session = new AiSession({ operationModel: 'configured' });
115
- const objects = new Set<string>();
116
-
117
- // Define calendar artifact.
118
- const calendarArtifact = defineArtifact({
119
- id: 'artifact:dxos.org/plugin/calendar',
120
- name: 'Calendar',
121
- instructions: 'Use this to create and query calendar events.',
122
- schema: CalendarEventSchema,
123
- tools: [
124
- createTool('calendar', {
125
- name: 'query',
126
- description: 'Query the calendar for events',
127
- schema: Schema.Struct({}),
128
- execute: async () => {
129
- return ToolResult.Success(CALENDAR_EVENTS);
130
- },
131
- }),
132
- ],
133
- });
134
-
135
- const tableArtifact = defineArtifact({
136
- id: 'artifact:dxos.org/plugin/table',
137
- name: 'Table',
138
- instructions: 'Use this to create and manage tables. Each table has a unique id.',
139
- schema: Schema.Struct({}),
140
- tools: [
141
- createTool('table', {
142
- name: 'create',
143
- description: 'Create a table',
144
- schema: Schema.Struct({
145
- data: Schema.Array(Schema.Any).annotations({ description: 'Array of data payloads to add as rows' }),
146
- }),
147
- execute: async ({ data }) => {
148
- log('create table', { data });
149
- const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
150
- objects.add(id);
151
- // TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
152
- return ToolResult.Success(`table @${id}`);
153
- },
154
- }),
155
- ],
156
- });
157
-
158
- const mapArtifact = defineArtifact({
159
- id: 'artifact:dxos.org/plugin/map',
160
- name: 'Map',
161
- instructions:
162
- 'Use this to create and manage maps. Maps source data from tables. Table id is required to create a map.',
163
- schema: Schema.Struct({}),
164
- tools: [
165
- createTool('map', {
166
- name: 'create',
167
- description: 'Create a map',
168
- schema: Schema.Struct({
169
- source: ArtifactId.annotations({
170
- description: 'The table that will be used as the source of the map',
171
- }),
172
- }),
173
- execute: async ({ source }) => {
174
- // TODO(dmaretskyi): Use effect-schema decode instead of manual parsing.
175
- const sourceId = ArtifactId.toDXN(source);
176
- if (!objects.has(sourceId.toString())) {
177
- return ToolResult.Error(`table id=${source} not found`);
178
- }
179
- log('create map', { sourceId });
180
- const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
181
- objects.add(id);
182
- // TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
183
- return ToolResult.Success(`map @${id}`);
184
- },
185
- }),
186
- ],
187
- });
188
-
189
- const scriptArtifact = defineArtifact({
190
- id: 'artifact:dxos.org/plugin/script',
191
- name: 'Script',
192
- instructions: 'Use this to create and manage scripts',
193
- schema: Schema.Struct({}),
194
- tools: [],
195
- });
196
-
197
- // session.streamEvent.on((event) => {
198
- // printStreamEvent(event);
199
- // });
200
-
201
- const printer = new ConsolePrinter();
202
- session.message.on((message) => printer.printMessage(message));
203
- session.userMessage.on((message) => printer.printMessage(message));
204
- session.block.on((block) => printer.printContentBlock(block));
205
-
206
- // session.update.on((update) => {
207
- // log('update', { update });
208
- // });
209
-
210
- // Test creating an itinerary
211
- const response = await session.run({
212
- client: aiClient,
213
- tools: [],
214
- artifacts: [calendarArtifact, tableArtifact, mapArtifact, scriptArtifact],
215
- requiredArtifactIds: [calendarArtifact.id, tableArtifact.id, mapArtifact.id, scriptArtifact.id],
216
- history: [],
217
- generationOptions: {
218
- model: '@anthropic/claude-3-5-haiku-20241022',
219
- },
220
- prompt: 'create a table and map for a travel itinerary based on events in my calendar',
221
- toolResolver: new ToolRegistry([]),
222
- });
223
-
224
- log('result', {
225
- objects,
226
- finalMessage: response.at(-1),
227
- });
228
- });
229
- */
230
114
  });
231
115
 
232
116
  // Travel to rome, florence, livorno, siena, madrid for conferences
@@ -263,43 +147,3 @@ const _CALENDAR_EVENTS: CalendarEvent[] = [
263
147
  description: 'Travel to Madrid',
264
148
  }),
265
149
  ];
266
-
267
- // const printStreamEvent = (event: GenerationStreamEvent) => {
268
- // switch (event.type) {
269
- // case 'message_start': {
270
- // process.stdout.write(`${event.message.role.toUpperCase()}\n\n`);
271
- // for (const content of event.message.content) {
272
- // printContentBlock(content);
273
- // }
274
- // break;
275
- // }
276
- // case 'content_block_start': {
277
- // printContentBlock(event.content);
278
- // break;
279
- // }
280
- // case 'content_block_delta': {
281
- // switch (event.delta.type) {
282
- // case 'text_delta': {
283
- // process.stdout.write(event.delta.text);
284
- // break;
285
- // }
286
- // case 'input_json_delta': {
287
- // process.stdout.write(event.delta.partial_json);
288
- // break;
289
- // }
290
- // }
291
- // break;
292
- // }
293
- // case 'content_block_stop': {
294
- // process.stdout.write('\n');
295
- // break;
296
- // }
297
- // case 'message_delta': {
298
- // break;
299
- // }
300
- // case 'message_stop': {
301
- // process.stdout.write('\n\n');
302
- // break;
303
- // }
304
- // }
305
- // };
@@ -2,106 +2,58 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type AiError, AiLanguageModel, type AiResponse, type AiTool, AiToolkit } from '@effect/ai';
6
- import { Chunk, type Context, Effect, Function, Option, Queue, type Schema, Stream } from 'effect';
5
+ import type * as AiError from '@effect/ai/AiError';
6
+ import * as LanguageModel from '@effect/ai/LanguageModel';
7
+ import type * as Tool from '@effect/ai/Tool';
8
+ import type * as Toolkit from '@effect/ai/Toolkit';
9
+ import * as Chunk from 'effect/Chunk';
10
+ import * as Effect from 'effect/Effect';
11
+ import type * as Schema from 'effect/Schema';
12
+ import * as Stream from 'effect/Stream';
7
13
 
8
14
  import {
9
- type AiInputPreprocessingError,
10
15
  AiParser,
11
16
  AiPreprocessor,
12
17
  type AiToolNotFoundError,
13
- type ConsolePrinter,
14
- ToolExecutionService,
15
- ToolResolverService,
16
- callTools,
18
+ type PromptPreprocessingError,
19
+ type ToolExecutionService,
20
+ type ToolResolverService,
21
+ callTool,
17
22
  getToolCalls,
18
23
  } from '@dxos/ai';
19
24
  import { type Blueprint } from '@dxos/blueprints';
20
25
  import { todo } from '@dxos/debug';
21
26
  import { Obj } from '@dxos/echo';
27
+ import { TracingService } from '@dxos/functions';
22
28
  import { log } from '@dxos/log';
23
- import { type ContentBlock, DataType } from '@dxos/schema';
24
- import { isNotFalsy } from '@dxos/util';
29
+ import { Message } from '@dxos/types';
25
30
 
26
31
  import { type AiAssistantError } from '../errors';
27
32
 
28
33
  import { formatSystemPrompt, formatUserPrompt } from './format';
34
+ import { GenerationObserver } from './observer';
29
35
 
30
- export type AiSessionOptions = {};
36
+ export type AiSessionRunError = AiError.AiError | PromptPreprocessingError | AiToolNotFoundError | AiAssistantError;
31
37
 
32
- /**
33
- * Live observer of the generation process.
34
- */
35
- export interface GenerationObserver {
36
- /**
37
- * Unparsed content block parts from the model.
38
- */
39
- onPart: (part: AiResponse.Part) => Effect.Effect<void>;
38
+ export type AiSessionRunRequirements =
39
+ | LanguageModel.LanguageModel
40
+ | ToolExecutionService
41
+ | ToolResolverService
42
+ | TracingService;
40
43
 
41
- /**
42
- * Parsed content blocks from the model.
43
- * NOTE: Use block.pending to determine if the block was completed.
44
- * For each block this will be called 0..n times with a pending block and then once with the final state of the block.
45
- *
46
- * Example:
47
- * 1. { pending: true, text: "Hello"}
48
- * 2. { pending: true, text: "Hello, I am a"}
49
- * 3. { pending: false, text: "Hello, I am a helpful assistant!"}
50
- */
51
- onBlock: (block: ContentBlock.Any) => Effect.Effect<void>;
52
-
53
- /**
54
- * Complete messages fired during the session, both from the model and from the user.
55
- * This message will contain all message blocks emitted through the `onBlock` callback.
56
- */
57
- onMessage: (message: DataType.Message) => Effect.Effect<void>;
58
- }
59
-
60
- export const GenerationObserver = Object.freeze({
61
- make: ({
62
- onPart = Function.constant(Effect.void),
63
- onBlock = Function.constant(Effect.void),
64
- onMessage = Function.constant(Effect.void),
65
- }: Partial<GenerationObserver> = {}): GenerationObserver => ({
66
- onPart,
67
- onBlock,
68
- onMessage,
69
- }),
70
-
71
- noop: () => GenerationObserver.make(),
72
-
73
- /**
74
- * Debug printer to be used in unit-tests and browser devtools.
75
- */
76
- fromPrinter: (printer: ConsolePrinter) =>
77
- GenerationObserver.make({
78
- onBlock: (block) =>
79
- Effect.sync(() => {
80
- if (block.pending) {
81
- return; // Only prints full blocks (better for unit-tests and browser devtools).
82
- }
83
- printer.printContentBlock(block);
84
- }),
85
- onMessage: (message) =>
86
- Effect.sync(() => {
87
- if (message.sender.role === 'assistant') {
88
- return; // Skip assistant messages since they are printed in the `onBlock` callback.
89
- }
90
- printer.printMessage(message);
91
- }),
92
- }),
93
- });
94
-
95
- export type SessionRunParams<Tools extends AiTool.Any> = {
44
+ export type AiSessionRunParams<Tools extends Record<string, Tool.Any>> = {
96
45
  prompt: string;
46
+ // TODO(wittjosiah): Rename to systemPrompt.
97
47
  system?: string;
98
- history?: DataType.Message[];
99
- objects?: Obj.Any[]; // TODO(burdon): Meta only (typename and id -- write to binder).
48
+ history?: Message.Message[];
49
+ objects?: Obj.Any[];
100
50
  blueprints?: Blueprint.Blueprint[];
101
- toolkit?: AiToolkit.AiToolkit<Tools>;
51
+ toolkit?: Toolkit.WithHandler<Tools>;
102
52
  observer?: GenerationObserver;
103
53
  };
104
54
 
55
+ export type AiSessionOptions = {};
56
+
105
57
  /**
106
58
  * Contains message history, tools, current context.
107
59
  * Current context means the state of the app, time of day, and other contextual information.
@@ -113,142 +65,130 @@ export type SessionRunParams<Tools extends AiTool.Any> = {
113
65
  * Could be personal or shared.
114
66
  */
115
67
  export class AiSession {
116
- /** Complete messages fired during the session, both from the model and from the user. */
117
- public readonly messageQueue = Effect.runSync(Queue.unbounded<DataType.Message>());
118
-
119
- // TODO(dmaretskyi): Remove the queues and convert everything to observer.
120
-
121
- /**
122
- * Blocks streaming from the model during the session.
123
- * @deprecated Use `observer.onBlock` instead.
124
- */
125
- public readonly blockQueue = Effect.runSync(Queue.unbounded<Option.Option<ContentBlock.Any>>());
126
-
127
- /** Unparsed events from the underlying generation stream. */
128
- public readonly eventQueue = Effect.runSync(Queue.unbounded<AiResponse.Part>());
129
-
130
68
  /** Prevents concurrent execution of session. */
131
69
  private readonly _semaphore = Effect.runSync(Effect.makeSemaphore(1));
132
70
 
133
- /** Pending messages (incl. the current user request). */
134
- private _pending: DataType.Message[] = [];
71
+ /** Message history from queue. */
72
+ // TODO(burdon): Evolve into supporting a git-like graph of messages.
73
+ private _history: Message.Message[] = [];
135
74
 
136
- /** Prior history from queue. */
137
- private _history: DataType.Message[] = [];
75
+ /** Pending messages for this session (incl. the current prompt). */
76
+ private _pending: Message.Message[] = [];
138
77
 
139
78
  constructor(private readonly _options: AiSessionOptions = {}) {}
140
79
 
141
- /**
142
- * Runs the AI model loop interacting with tools and artifacts.
143
- * @returns The messages generated by the session, including the user's prompt.
144
- */
145
- // TODO(dmaretskyi): Toolkit context doesn't get added to the effect type.
146
- run = <Tools extends AiTool.Any>(
147
- params: SessionRunParams<Tools>,
148
- ): Effect.Effect<
149
- DataType.Message[],
150
- AiAssistantError | AiInputPreprocessingError | AiToolNotFoundError | AiError.AiError,
151
- AiLanguageModel.AiLanguageModel | ToolResolverService | ToolExecutionService | AiTool.ToHandler<Tools>
152
- > =>
80
+ run = <Tools extends Record<string, Tool.Any>>({
81
+ prompt,
82
+ system: systemTemplate,
83
+ history = [],
84
+ objects = [],
85
+ blueprints = [],
86
+ toolkit,
87
+ observer = GenerationObserver.noop(),
88
+ }: AiSessionRunParams<Tools>): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> =>
153
89
  Effect.gen(this, function* () {
154
- const observer = params.observer ?? GenerationObserver.noop();
155
-
156
- // Create toolkit.
157
- const toolkit: AiToolkit.ToHandler<Tools> = yield* createToolkit(params);
90
+ this._history = [...history];
91
+ this._pending = [];
92
+ const pending = this._pending;
158
93
 
159
- // Generate system prompt.
160
- // TODO(budon): Dynamically resolve template variables.
161
- const system = yield* formatSystemPrompt(params);
162
- console.log(system);
94
+ const submitMessage = Effect.fnUntraced(function* (message: Message.Message) {
95
+ pending.push(message);
96
+ yield* observer.onMessage(message);
97
+ yield* TracingService.emitConverationMessage(message);
98
+ return message;
99
+ });
163
100
 
164
- // Generate user prompt.
165
- const promptMessages = yield* formatUserPrompt(params);
166
- yield* this.messageQueue.offer(promptMessages);
167
- yield* observer.onMessage(promptMessages);
101
+ // Submit the prompt.
102
+ // TODO(burdon): Remove if cancelled?
103
+ const promptMessage = yield* submitMessage(yield* formatUserPrompt({ prompt, history }));
168
104
 
169
- this._history = [...(params.history ?? [])];
170
- this._pending = [promptMessages];
105
+ // Generate system and prompt messages.
106
+ const system = yield* formatSystemPrompt({ system: systemTemplate, blueprints, objects });
171
107
 
172
- // Potential tool-use loop.
108
+ // Tool call loop.
173
109
  do {
174
- log.info('request', {
175
- prompt: promptMessages,
176
- system: { snippet: [system.slice(0, 32), '...', system.slice(-32)].join(''), length: system.length },
110
+ log('request', {
111
+ prompt: promptMessage,
112
+ system: { snippet: createSnippet(system), length: system.length },
177
113
  pending: this._pending.length,
178
114
  history: this._history.length,
179
- objects: params.objects?.length ?? 0,
180
- blueprints: params.blueprints?.length ?? 0,
181
- toolkit: Object.values(toolkit.tools).map((tool: AiTool.Any) => tool.name),
115
+ objects: objects?.length ?? 0,
182
116
  });
183
117
 
184
- // Generate the prompt and make request.
185
- const prompt = yield* AiPreprocessor.preprocessAiInput([...this._history, ...this._pending]);
186
- const blocks = yield* AiLanguageModel.streamText({
118
+ const prompt = yield* AiPreprocessor.preprocessPrompt([...this._history, ...this._pending], { system });
119
+
120
+ // Execute the stream request.
121
+ // logDump('prompt', Prompt.Prompt.pipe(Schema.encodeSync)(prompt));
122
+ const blocks = yield* LanguageModel.streamText({
187
123
  prompt,
188
- system,
189
124
  toolkit,
190
- // TODO(burdon): Despite this flag, the model still calls tools.
191
- // Flag is only used in generateText (not streamText); patch and submit bug.
192
- // https://github.com/Effect-TS/effect/blob/main/packages/ai/ai/src/AiLanguageModel.ts#L401
193
125
  disableToolCallResolution: true,
194
126
  }).pipe(
127
+ // TOOD(dmaretskyi): Error mapping.
195
128
  AiParser.parseResponse({
196
- onBlock: (block) =>
197
- Effect.all([this.blockQueue.offer(Option.some(block)), observer.onBlock(block)], { discard: true }),
198
- onPart: (part) => Effect.all([this.eventQueue.offer(part), observer.onPart(part)], { discard: true }),
129
+ onBegin: () => observer.onBegin(),
130
+ onBlock: (block) => observer.onBlock(block),
131
+ onPart: (part) => observer.onPart(part),
132
+ onEnd: (summary) => observer.onEnd(summary),
199
133
  }),
200
134
  Stream.runCollect,
201
135
  Effect.map(Chunk.toArray),
202
136
  );
203
137
 
204
- // Signal to stream consumers that message blocks are complete.
205
- // Allows for coordination between the block and message queues
206
- // to prevent the streaming blocks from being rendered twice when the message is produced.
207
- // TODO(wittjosiah): The block queue should probably be drained at this point in the case that there is no consumer.
208
- yield* this.blockQueue.offer(Option.none());
209
-
210
- // Create response message.
211
- const response = Obj.make(DataType.Message, {
212
- created: new Date().toISOString(),
213
- sender: { role: 'assistant' },
214
- blocks,
215
- });
216
- this._pending.push(response);
217
- yield* this.messageQueue.offer(response);
218
- yield* observer.onMessage(response);
138
+ // Create the response message.
139
+ const response = yield* submitMessage(
140
+ Obj.make(Message.Message, {
141
+ created: new Date().toISOString(),
142
+ sender: { role: 'assistant' },
143
+ blocks,
144
+ }),
145
+ );
219
146
 
220
- // Parse response for tool calls.
147
+ // Parse the response for tool calls.
221
148
  const toolCalls = getToolCalls(response);
222
149
  if (toolCalls.length === 0) {
223
150
  break;
151
+ } else if (!toolkit) {
152
+ throw new Error('No toolkit provided'); // TODO(burdon): Throw user error?
224
153
  }
225
154
 
226
- // TODO(burdon): Report errors to user; with proposed actions.
227
- const toolResults = yield* callTools(toolkit, toolCalls);
228
- const toolResultsMessage = Obj.make(DataType.Message, {
229
- created: new Date().toISOString(),
230
- sender: { role: 'user' },
231
- blocks: toolResults,
155
+ // Execute the tool calls.
156
+ // TODO(burdon): Retry errors? Write result when each completes individually?
157
+ const toolResults = yield* Effect.forEach(toolCalls, (toolCall) => {
158
+ return callTool(toolkit, toolCall).pipe(
159
+ Effect.provide(
160
+ TracingService.layerSubframe((context) => ({
161
+ ...context,
162
+ parentMessage: response.id,
163
+ toolCallId: toolCall.toolCallId,
164
+ })),
165
+ ),
166
+ );
232
167
  });
233
168
 
234
- this._pending.push(toolResultsMessage);
235
- yield* this.messageQueue.offer(toolResultsMessage);
236
- yield* observer.onMessage(toolResultsMessage);
169
+ // Add to queue and continue loop.
170
+ // TODO(wittjosiah): Sometimes tool error results are added to the queue before the tool agent statuses.
171
+ // This results in a broken execution graph.
172
+ yield* submitMessage(
173
+ Obj.make(Message.Message, {
174
+ created: new Date().toISOString(),
175
+ sender: { role: 'tool' },
176
+ blocks: toolResults,
177
+ }),
178
+ );
237
179
  } while (true);
238
180
 
239
- // Signals to stream consumers that the session has completed and no more messages are coming.
240
- yield* Queue.shutdown(this.messageQueue);
241
- yield* Queue.shutdown(this.blockQueue);
242
- yield* Queue.shutdown(this.eventQueue);
243
-
244
181
  log('done', { pending: this._pending.length });
245
182
  return this._pending;
246
183
  }).pipe(this._semaphore.withPermits(1), Effect.withSpan('AiSession.run'));
247
184
 
248
- // TODO(burdon): Implement.
185
+ /**
186
+ * @deprecated
187
+ */
188
+ // TODO(burdon): Implement or remove.
249
189
  async runStructured<S extends Schema.Schema.AnyNoContext>(
250
190
  _schema: S,
251
- _options: SessionRunParams<AiTool.Any>,
191
+ _options: AiSessionRunParams<any>,
252
192
  ): Promise<Schema.Schema.Type<S>> {
253
193
  return todo();
254
194
  // const parser = structuredOutputParser(schema);
@@ -260,20 +200,14 @@ export class AiSession {
260
200
  }
261
201
  }
262
202
 
263
- /**
264
- * Build a combined toolkit from the blueprint tools and the provided toolkit.
265
- */
266
- const createToolkit = <Tools extends AiTool.Any>({
267
- toolkit,
268
- blueprints = [],
269
- }: Pick<SessionRunParams<Tools>, 'toolkit' | 'blueprints'>) =>
270
- Effect.gen(function* () {
271
- const blueprintToolkit = yield* ToolResolverService.resolveToolkit(blueprints.flatMap(({ tools }) => tools));
272
- const blueprintToolkitHandler: Context.Context<AiTool.ToHandler<AiTool.Any>> = yield* blueprintToolkit.toContext(
273
- ToolExecutionService.handlersFor(blueprintToolkit),
274
- );
203
+ const createSnippet = (text: string, len = 32) =>
204
+ text.length <= len * 2 ? text : [text.slice(0, len), '...', text.slice(-len)].join('');
275
205
 
276
- return yield* AiToolkit.merge(...[toolkit, blueprintToolkit].filter(isNotFalsy)).pipe(
277
- Effect.provide(blueprintToolkitHandler),
278
- ) as Effect.Effect<AiToolkit.ToHandler<any>, never, AiTool.ToHandler<Tools>>;
279
- });
206
+ // TODO(dmaretskyi): Extract as a general util.
207
+ const logDump = (message: string, data: unknown) => {
208
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
209
+ const { writeFileSync } = require('node:fs');
210
+ const path = `/tmp/log-data-${Date.now()}.json`;
211
+ writeFileSync(path, JSON.stringify(data, null, 2));
212
+ log.info(message, { path });
213
+ };