@dxos/assistant 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0

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 (142) hide show
  1. package/README.md +1 -0
  2. package/dist/lib/neutral/chunk-UAWIXKF6.mjs +1875 -0
  3. package/dist/lib/neutral/chunk-UAWIXKF6.mjs.map +7 -0
  4. package/dist/lib/{browser → neutral}/extraction/index.mjs +29 -35
  5. package/dist/lib/neutral/extraction/index.mjs.map +7 -0
  6. package/dist/lib/neutral/index.mjs +551 -0
  7. package/dist/lib/neutral/index.mjs.map +7 -0
  8. package/dist/lib/neutral/meta.json +1 -0
  9. package/dist/lib/neutral/testing/index.mjs +114 -0
  10. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  11. package/dist/types/src/conversation/context.d.ts +116 -64
  12. package/dist/types/src/conversation/context.d.ts.map +1 -1
  13. package/dist/types/src/conversation/context.test.d.ts +2 -0
  14. package/dist/types/src/conversation/context.test.d.ts.map +1 -0
  15. package/dist/types/src/conversation/conversation.d.ts +45 -18
  16. package/dist/types/src/conversation/conversation.d.ts.map +1 -1
  17. package/dist/types/src/conversation/conversation.test.d.ts +2 -0
  18. package/dist/types/src/conversation/conversation.test.d.ts.map +1 -0
  19. package/dist/types/src/diff.d.ts +3 -1
  20. package/dist/types/src/diff.d.ts.map +1 -1
  21. package/dist/types/src/errors.d.ts +12 -16
  22. package/dist/types/src/errors.d.ts.map +1 -1
  23. package/dist/types/src/execution-graph.d.ts +2 -2
  24. package/dist/types/src/execution-graph.d.ts.map +1 -1
  25. package/dist/types/src/extraction/extraction-ner-function/index.d.ts +0 -1
  26. package/dist/types/src/extraction/extraction-ner-function/index.d.ts.map +1 -1
  27. package/dist/types/src/extraction/extraction.d.ts +1059 -313
  28. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  29. package/dist/types/src/extraction/index.d.ts +0 -1
  30. package/dist/types/src/extraction/index.d.ts.map +1 -1
  31. package/dist/types/src/extraction/quotes.d.ts +2 -2
  32. package/dist/types/src/extraction/quotes.d.ts.map +1 -1
  33. package/dist/types/src/functions/services.d.ts +28 -9
  34. package/dist/types/src/functions/services.d.ts.map +1 -1
  35. package/dist/types/src/functions.test.d.ts +2 -0
  36. package/dist/types/src/functions.test.d.ts.map +1 -0
  37. package/dist/types/src/index.d.ts +2 -0
  38. package/dist/types/src/index.d.ts.map +1 -1
  39. package/dist/types/src/service/AgentService.d.ts +61 -0
  40. package/dist/types/src/service/AgentService.d.ts.map +1 -0
  41. package/dist/types/src/service/AgentService.test.d.ts +2 -0
  42. package/dist/types/src/service/AgentService.test.d.ts.map +1 -0
  43. package/dist/types/src/service/agent-process.d.ts +17 -0
  44. package/dist/types/src/service/agent-process.d.ts.map +1 -0
  45. package/dist/types/src/service/index.d.ts +3 -0
  46. package/dist/types/src/service/index.d.ts.map +1 -0
  47. package/dist/types/src/session/artifact-diff.d.ts +1 -1
  48. package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
  49. package/dist/types/src/session/format.d.ts +3 -3
  50. package/dist/types/src/session/format.d.ts.map +1 -1
  51. package/dist/types/src/session/index.d.ts +1 -1
  52. package/dist/types/src/session/index.d.ts.map +1 -1
  53. package/dist/types/src/session/observer.d.ts +4 -3
  54. package/dist/types/src/session/observer.d.ts.map +1 -1
  55. package/dist/types/src/session/session.d.ts +72 -11
  56. package/dist/types/src/session/session.d.ts.map +1 -1
  57. package/dist/types/src/session/toolkit.d.ts +9 -6
  58. package/dist/types/src/session/toolkit.d.ts.map +1 -1
  59. package/dist/types/src/session/version-pin.d.ts +2 -2
  60. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  61. package/dist/types/src/templates/system.d.ts +0 -1
  62. package/dist/types/src/templates/system.d.ts.map +1 -1
  63. package/dist/types/src/testing/index.d.ts +2 -0
  64. package/dist/types/src/testing/index.d.ts.map +1 -0
  65. package/dist/types/src/testing/layer.d.ts +35 -0
  66. package/dist/types/src/testing/layer.d.ts.map +1 -0
  67. package/dist/types/src/tracing.d.ts +264 -0
  68. package/dist/types/src/tracing.d.ts.map +1 -0
  69. package/dist/types/src/types/artifact.d.ts +13 -5
  70. package/dist/types/src/types/artifact.d.ts.map +1 -1
  71. package/dist/types/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +42 -33
  73. package/src/conversation/context.test.ts +52 -0
  74. package/src/conversation/context.ts +307 -90
  75. package/src/conversation/conversation.test.ts +58 -0
  76. package/src/conversation/conversation.ts +209 -62
  77. package/src/diff.test.ts +36 -5
  78. package/src/diff.ts +13 -4
  79. package/src/errors.ts +2 -2
  80. package/src/execution-graph.test.ts +3 -3
  81. package/src/execution-graph.ts +6 -6
  82. package/src/extraction/extraction-ner-function/index.ts +0 -1
  83. package/src/extraction/extraction.ts +40 -12
  84. package/src/extraction/index.ts +0 -1
  85. package/src/extraction/quotes.ts +4 -5
  86. package/src/functions/services.ts +217 -50
  87. package/src/functions.conversations.json +1 -0
  88. package/src/functions.test.ts +82 -0
  89. package/src/index.ts +2 -0
  90. package/src/service/AgentService.conversations.json +1 -0
  91. package/src/service/AgentService.test.ts +169 -0
  92. package/src/service/AgentService.ts +165 -0
  93. package/src/service/agent-process.ts +359 -0
  94. package/src/service/index.ts +6 -0
  95. package/src/session/artifact-diff.ts +1 -1
  96. package/src/session/format.test.ts +110 -39
  97. package/src/session/format.ts +12 -14
  98. package/src/session/index.ts +1 -2
  99. package/src/session/observer.ts +4 -1
  100. package/src/session/session.conversations.json +1 -1
  101. package/src/session/session.test.ts +58 -17
  102. package/src/session/session.ts +249 -113
  103. package/src/session/toolkit.ts +21 -25
  104. package/src/session/version-pin.ts +4 -3
  105. package/src/templates/instructions/format.tpl +0 -4
  106. package/src/templates/instructions/system.tpl +11 -2
  107. package/src/templates/system.ts +6 -5
  108. package/src/testing/index.ts +5 -0
  109. package/src/testing/layer.ts +258 -0
  110. package/src/tracing.ts +113 -0
  111. package/src/types/artifact.ts +31 -13
  112. package/dist/lib/browser/extraction/index.mjs.map +0 -7
  113. package/dist/lib/browser/index.mjs +0 -1290
  114. package/dist/lib/browser/index.mjs.map +0 -7
  115. package/dist/lib/browser/meta.json +0 -1
  116. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  117. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  118. package/dist/lib/node-esm/extraction/index.mjs +0 -226
  119. package/dist/lib/node-esm/extraction/index.mjs.map +0 -7
  120. package/dist/lib/node-esm/index.mjs +0 -1291
  121. package/dist/lib/node-esm/index.mjs.map +0 -7
  122. package/dist/lib/node-esm/meta.json +0 -1
  123. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +0 -4
  124. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +0 -1
  125. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts +0 -2
  126. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts.map +0 -1
  127. package/dist/types/src/extraction/extraction-llm-function/index.d.ts +0 -2
  128. package/dist/types/src/extraction/extraction-llm-function/index.d.ts.map +0 -1
  129. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +0 -345
  130. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +0 -1
  131. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts +0 -2
  132. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts.map +0 -1
  133. package/dist/types/src/session/GenericToolkit.d.ts +0 -84
  134. package/dist/types/src/session/GenericToolkit.d.ts.map +0 -1
  135. package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +0 -84
  136. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +0 -62
  137. package/src/extraction/extraction-llm-function/index.ts +0 -5
  138. package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +0 -116
  139. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +0 -56
  140. package/src/session/GenericToolkit.ts +0 -130
  141. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  142. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
@@ -6,30 +6,32 @@ import type * as AiError from '@effect/ai/AiError';
6
6
  import * as LanguageModel from '@effect/ai/LanguageModel';
7
7
  import type * as Tool from '@effect/ai/Tool';
8
8
  import type * as Toolkit from '@effect/ai/Toolkit';
9
+ import * as Array from 'effect/Array';
9
10
  import * as Chunk from 'effect/Chunk';
10
11
  import * as Effect from 'effect/Effect';
11
- import type * as Schema from 'effect/Schema';
12
+ import { pipe } from 'effect/Function';
13
+ import * as Option from 'effect/Option';
12
14
  import * as Stream from 'effect/Stream';
13
15
 
14
16
  import {
15
17
  AiParser,
16
18
  AiPreprocessor,
19
+ AiSummarizer,
17
20
  type AiToolNotFoundError,
21
+ callTool,
18
22
  type PromptPreprocessingError,
19
23
  type ToolExecutionService,
20
24
  type ToolResolverService,
21
- callTool,
22
- getToolCalls,
25
+ withoutToolCallParising,
23
26
  } from '@dxos/ai';
24
27
  import { type Blueprint } from '@dxos/blueprints';
25
- import { todo } from '@dxos/debug';
26
28
  import { Obj } from '@dxos/echo';
27
- import { TracingService } from '@dxos/functions';
29
+ import { type FunctionInvocationService, Trace, TracingService } from '@dxos/functions';
28
30
  import { log } from '@dxos/log';
29
- import { Message } from '@dxos/types';
31
+ import { ContentBlock, Message } from '@dxos/types';
30
32
 
31
33
  import { type AiAssistantError } from '../errors';
32
-
34
+ import { CompleteBlock, PartialBlock } from '../tracing';
33
35
  import { formatSystemPrompt, formatUserPrompt } from './format';
34
36
  import { GenerationObserver } from './observer';
35
37
 
@@ -39,20 +41,56 @@ export type AiSessionRunRequirements =
39
41
  | LanguageModel.LanguageModel
40
42
  | ToolExecutionService
41
43
  | ToolResolverService
44
+ | FunctionInvocationService
45
+ | Trace.TraceService
46
+ /**
47
+ * @deprecated Retained for backward compatibility with tool handlers that use TracingService.emitStatus().
48
+ * New code should use Trace.TraceService instead.
49
+ */
42
50
  | TracingService;
43
51
 
44
- export type AiSessionRunParams<Tools extends Record<string, Tool.Any>> = {
52
+ export type AiSessionOptions = {
53
+ /**
54
+ * Summarize before executing the prompt if the existing history exceeds this threshold.
55
+ */
56
+ summarizationThreshold?: number;
57
+
58
+ // TODO(dmaretskyi): Plan to phase out in favor of TracingService and the return type being a stream.
59
+ observer?: GenerationObserver;
60
+ /**
61
+ * Callback for when a message is received from the user, model, or tool.
62
+ * This is useful for streaming the output to a queue.
63
+ */
64
+ onOutput?: (message: Message.Message) => Effect.Effect<void, never, never>;
65
+ };
66
+
67
+ export type AiSessionRunProps<Tools extends Record<string, Tool.Any>> = {
45
68
  prompt: string;
46
69
  // TODO(wittjosiah): Rename to systemPrompt.
47
70
  system?: string;
48
71
  history?: Message.Message[];
49
- objects?: Obj.Any[];
50
- blueprints?: Blueprint.Blueprint[];
72
+ objects?: Obj.Unknown[];
73
+ blueprints?: readonly Blueprint.Blueprint[];
74
+ toolkit?: Toolkit.WithHandler<Tools>;
75
+ };
76
+
77
+ export type AiSessionBeginProps = {
78
+ prompt: string;
79
+ system?: string;
80
+ history?: Message.Message[];
81
+ objects?: Obj.Unknown[];
82
+ blueprints?: readonly Blueprint.Blueprint[];
83
+ };
84
+
85
+ export type AiSessionTurnProps<Tools extends Record<string, Tool.Any>> = {
86
+ system: string;
51
87
  toolkit?: Toolkit.WithHandler<Tools>;
52
- observer?: GenerationObserver;
53
88
  };
54
89
 
55
- export type AiSessionOptions = {};
90
+ export type AiSessionTurnResult = {
91
+ messages: Message.Message[];
92
+ done: boolean;
93
+ };
56
94
 
57
95
  /**
58
96
  * Contains message history, tools, current context.
@@ -64,10 +102,14 @@ export type AiSessionOptions = {};
64
102
  * Could be run locally in the app or remotely.
65
103
  * Could be personal or shared.
66
104
  */
105
+ // TODO(dmaretskyi): Rename AiSessionRequest
67
106
  export class AiSession {
68
107
  /** Prevents concurrent execution of session. */
69
108
  private readonly _semaphore = Effect.runSync(Effect.makeSemaphore(1));
70
109
 
110
+ private readonly _observer: GenerationObserver;
111
+ private readonly _onOutput: (message: Message.Message) => Effect.Effect<void, never, never>;
112
+
71
113
  /** Message history from queue. */
72
114
  // TODO(burdon): Evolve into supporting a git-like graph of messages.
73
115
  private _history: Message.Message[] = [];
@@ -75,129 +117,223 @@ export class AiSession {
75
117
  /** Pending messages for this session (incl. the current prompt). */
76
118
  private _pending: Message.Message[] = [];
77
119
 
78
- constructor(private readonly _options: AiSessionOptions = {}) {}
120
+ private _started = 0;
121
+ private _ended = 0;
122
+ private _toolCalls = 0;
79
123
 
80
- run = <Tools extends Record<string, Tool.Any>>({
124
+ constructor(private readonly _options: AiSessionOptions = {}) {
125
+ this._observer = _options.observer ?? GenerationObserver.noop();
126
+ this._onOutput = _options.onOutput ?? (() => Effect.void);
127
+ }
128
+
129
+ get duration(): number {
130
+ return this._ended - this._started;
131
+ }
132
+
133
+ get toolCalls(): number {
134
+ return this._toolCalls;
135
+ }
136
+
137
+ get pending(): readonly Message.Message[] {
138
+ return this._pending;
139
+ }
140
+
141
+ private _submitMessage = (message: Message.Message): Effect.Effect<Message.Message, never, Trace.TraceService> =>
142
+ Effect.gen(this, function* () {
143
+ this._pending.push(message);
144
+ yield* this._observer.onMessage(message);
145
+ for (const block of message.blocks) {
146
+ yield* Trace.write(CompleteBlock, {
147
+ messageId: message.id,
148
+ role: message.sender.role!,
149
+ block,
150
+ });
151
+ }
152
+ yield* this._onOutput(message);
153
+ return message;
154
+ });
155
+
156
+ getToolCalls = () =>
157
+ pipe(
158
+ [...this._history, ...this._pending],
159
+ Array.reverse,
160
+ Array.takeWhile((_) => _.sender.role === 'assistant'),
161
+ Array.flatMap((_) => _.blocks.filter(ContentBlock.is('toolCall')).map((block) => ({ block, message: _ }))),
162
+ Array.filter((_) => !_.block.providerExecuted),
163
+ );
164
+
165
+ /**
166
+ * Initialize a session: set up history, perform summarization if needed, and submit the user prompt.
167
+ * Must be called before `runTurn()`.
168
+ */
169
+ begin = ({
81
170
  prompt,
82
- system: systemTemplate,
171
+ system,
83
172
  history = [],
84
- objects = [],
85
173
  blueprints = [],
86
- toolkit,
87
- observer = GenerationObserver.noop(),
88
- }: AiSessionRunParams<Tools>): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> =>
174
+ objects = [],
175
+ }: AiSessionBeginProps): Effect.Effect<void, AiSessionRunError, AiSessionRunRequirements> =>
89
176
  Effect.gen(this, function* () {
177
+ this._started = Date.now();
90
178
  this._history = [...history];
91
179
  this._pending = [];
92
- const pending = this._pending;
93
180
 
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;
181
+ const systemPrompt = yield* formatSystemPrompt({ system, blueprints, objects }).pipe(Effect.orDie);
182
+
183
+ if (this._options.summarizationThreshold !== undefined) {
184
+ const tokenCount = yield* AiPreprocessor.estimateTokens(
185
+ yield* AiPreprocessor.preprocessPrompt([...this._history], {
186
+ system: systemPrompt,
187
+ }),
188
+ );
189
+ if (tokenCount > this._options.summarizationThreshold) {
190
+ const summary = yield* AiSummarizer.summarize([...this._history]);
191
+ yield* this._submitMessage(summary);
192
+ }
193
+ }
194
+
195
+ yield* this._submitMessage(yield* formatUserPrompt({ prompt, history }));
196
+ });
197
+
198
+ /**
199
+ * Execute a single turn: one LLM generation followed by tool execution.
200
+ * The toolkit and system prompt can be updated between turns to reflect context changes (e.g. dynamically enabled blueprints).
201
+ */
202
+ runAgentTurn = <Tools extends Record<string, Tool.Any>>({
203
+ system,
204
+ toolkit,
205
+ }: AiSessionTurnProps<Tools>): Effect.Effect<AiSessionTurnResult, AiSessionRunError, AiSessionRunRequirements> =>
206
+ Effect.gen(this, function* () {
207
+ log('request', {
208
+ system: { snippet: createSnippet(system), length: system.length },
209
+ pending: this._pending.length,
210
+ history: this._history.length,
99
211
  });
100
212
 
101
- // Submit the prompt.
102
- // TODO(burdon): Remove if cancelled?
103
- const promptMessage = yield* submitMessage(yield* formatUserPrompt({ prompt, history }));
213
+ const prompt = yield* AiPreprocessor.preprocessPrompt([...this._history, ...this._pending], {
214
+ system,
215
+ cacheControl: 'ephemeral',
216
+ });
104
217
 
105
- // Generate system and prompt messages.
106
- const system = yield* formatSystemPrompt({ system: systemTemplate, blueprints, objects });
218
+ const observer = this._observer;
219
+ let currentMessageId: Obj.ID | null = null; // Consistent IDs for pending blocks preceding the complete one.
107
220
 
108
- // Tool call loop.
109
- do {
110
- log('request', {
111
- prompt: promptMessage,
112
- system: { snippet: createSnippet(system), length: system.length },
113
- pending: this._pending.length,
114
- history: this._history.length,
115
- objects: objects?.length ?? 0,
116
- });
221
+ const messages = yield* LanguageModel.streamText({
222
+ prompt,
223
+ toolkit,
224
+ disableToolCallResolution: true,
225
+ }).pipe(
226
+ withoutToolCallParising,
227
+ AiParser.parseResponse({
228
+ emitPartial: true,
229
+ onBegin: () => observer.onBegin(),
230
+ onBlock: (block) => observer.onBlock(block),
231
+ onPart: (part) => observer.onPart(part as any),
232
+ onEnd: (summary) => observer.onEnd(summary),
233
+ }),
234
+ Stream.mapEffect(
235
+ (block) =>
236
+ Effect.gen(this, function* () {
237
+ if (block.pending) {
238
+ currentMessageId ??= Obj.ID.random();
239
+ log('emit ephemeral message', { id: currentMessageId, type: block._tag });
240
+ yield* Trace.write(PartialBlock, {
241
+ messageId: currentMessageId,
242
+ role: 'assistant',
243
+ block,
244
+ });
245
+ return Option.none();
246
+ } else {
247
+ currentMessageId ??= Obj.ID.random();
248
+ const id = currentMessageId;
249
+ currentMessageId = null;
250
+ log('emit complete message', { id, type: block._tag });
251
+ const message = Obj.make(Message.Message, {
252
+ id,
253
+ created: new Date().toISOString(),
254
+ sender: { role: 'assistant' },
255
+ blocks: [block],
256
+ });
257
+ return Option.some(yield* this._submitMessage(message));
258
+ }
259
+ }),
260
+ { concurrency: 1, unordered: false },
261
+ ),
262
+ Stream.filterMap((_) => _),
263
+ Stream.runCollect,
264
+ Effect.map(Chunk.toArray),
265
+ );
117
266
 
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({
123
- prompt,
124
- toolkit,
125
- disableToolCallResolution: true,
126
- }).pipe(
127
- // TOOD(dmaretskyi): Error mapping.
128
- AiParser.parseResponse({
129
- onBegin: () => observer.onBegin(),
130
- onBlock: (block) => observer.onBlock(block),
131
- onPart: (part) => observer.onPart(part),
132
- onEnd: (summary) => observer.onEnd(summary),
133
- }),
134
- Stream.runCollect,
135
- Effect.map(Chunk.toArray),
136
- );
267
+ const toolCalls = this.getToolCalls();
137
268
 
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
- );
269
+ if (toolCalls.length === 0) {
270
+ this._ended = Date.now();
271
+ return { messages, done: true };
272
+ } else if (!toolkit) {
273
+ throw new Error('No toolkit provided');
274
+ }
146
275
 
147
- // Parse the response for tool calls.
148
- const toolCalls = getToolCalls(response);
149
- if (toolCalls.length === 0) {
150
- break;
151
- } else if (!toolkit) {
152
- throw new Error('No toolkit provided'); // TODO(burdon): Throw user error?
276
+ return { messages, done: false };
277
+ });
278
+
279
+ runTools = <Tools extends Record<string, Tool.Any>>({ toolkit }: { toolkit?: Toolkit.WithHandler<Tools> }) =>
280
+ Effect.gen(this, function* () {
281
+ const toolCalls = this.getToolCalls();
282
+ // TODO(burdon): Retry errors? Write result when each completes individually?
283
+ const toolResults = yield* Effect.forEach(toolCalls, ({ block, message }) => {
284
+ if (!toolkit) {
285
+ throw new Error('No toolkit provided');
153
286
  }
287
+ return callTool(toolkit, block).pipe(
288
+ Effect.provide(
289
+ TracingService.layerSubframe((context) => ({
290
+ ...context,
291
+ parentMessage: message.id,
292
+ toolCallId: block.toolCallId,
293
+ })),
294
+ ),
295
+ );
296
+ });
154
297
 
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
- );
167
- });
298
+ // TODO(wittjosiah): Sometimes tool error results are added to the queue before the tool agent statuses.
299
+ // TODO(dmaretskyi): Stream tool results one by one.
300
+ yield* this._submitMessage(
301
+ Obj.make(Message.Message, {
302
+ created: new Date().toISOString(),
303
+ sender: { role: 'tool' },
304
+ blocks: toolResults,
305
+ }),
306
+ );
168
307
 
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
- );
308
+ this._toolCalls += toolResults.length;
309
+ });
310
+ /**
311
+ * Run a full conversation turn loop. Equivalent to calling `begin()` then `runTurn()` in a loop.
312
+ */
313
+ run = <Tools extends Record<string, Tool.Any>>({
314
+ prompt,
315
+ system: systemTemplate,
316
+ history = [],
317
+ objects = [],
318
+ blueprints = [],
319
+ toolkit,
320
+ }: AiSessionRunProps<Tools>): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> =>
321
+ Effect.gen(this, function* () {
322
+ yield* this.begin({ prompt, system: systemTemplate, history, objects, blueprints });
323
+
324
+ const system = yield* formatSystemPrompt({ system: systemTemplate, blueprints, objects }).pipe(Effect.orDie);
325
+
326
+ do {
327
+ const { done } = yield* this.runAgentTurn({ system, toolkit });
328
+ if (done) {
329
+ break;
330
+ }
331
+ yield* this.runTools({ toolkit });
179
332
  } while (true);
180
333
 
181
- log('done', { pending: this._pending.length });
334
+ log('done', { pending: this._pending.length, duration: this.duration, tools: this._toolCalls });
182
335
  return this._pending;
183
336
  }).pipe(this._semaphore.withPermits(1), Effect.withSpan('AiSession.run'));
184
-
185
- /**
186
- * @deprecated
187
- */
188
- // TODO(burdon): Implement or remove.
189
- async runStructured<S extends Schema.Schema.AnyNoContext>(
190
- _schema: S,
191
- _options: AiSessionRunParams<any>,
192
- ): Promise<Schema.Schema.Type<S>> {
193
- return todo();
194
- // const parser = structuredOutputParser(schema);
195
- // const result = await this.run({
196
- // ...options,
197
- // executableTools: [...(options.executableTools ?? []), parser.tool],
198
- // });
199
- // return parser.getResult(result);
200
- }
201
337
  }
202
338
 
203
339
  const createSnippet = (text: string, len = 32) =>
@@ -4,45 +4,41 @@
4
4
 
5
5
  import type * as Tool from '@effect/ai/Tool';
6
6
  import * as Toolkit from '@effect/ai/Toolkit';
7
- import type * as Context from 'effect/Context';
8
7
  import * as Effect from 'effect/Effect';
9
8
 
10
- import { type AiToolNotFoundError, ToolExecutionService, type ToolId, ToolResolverService } from '@dxos/ai';
9
+ import { type AiToolNotFoundError, GenericToolkit, ToolExecutionService, ToolResolverService } from '@dxos/ai';
11
10
  import { type Blueprint } from '@dxos/blueprints';
12
11
  import { isTruthy } from '@dxos/util';
13
12
 
14
- export type ToolkitParams<Tools extends Record<string, Tool.Any>> = {
15
- toolkit?: Toolkit.Toolkit<Tools>;
16
- toolIds?: ToolId[];
17
- blueprints?: Blueprint.Blueprint[];
13
+ export type CreateToolkitProps = {
14
+ toolkit?: Toolkit.Any;
15
+ blueprints?: readonly Blueprint.Blueprint[];
16
+ /**
17
+ * Self-contained with handlers toolkits.
18
+ */
19
+ genericToolkits?: readonly GenericToolkit.GenericToolkit[];
18
20
  };
19
21
 
20
22
  /**
21
23
  * Build a combined toolkit from the blueprint tools and the provided toolkit.
22
24
  */
23
- export const createToolkit = <Tools extends Record<string, Tool.Any> = {}>({
24
- toolkit: toolkitParam,
25
- toolIds = [],
25
+ export const createToolkit = ({
26
+ toolkit: toolkitProp,
26
27
  blueprints = [],
27
- }: ToolkitParams<Tools>): Effect.Effect<
28
+ genericToolkits = [],
29
+ }: CreateToolkitProps): Effect.Effect<
28
30
  Toolkit.WithHandler<any>,
29
31
  AiToolNotFoundError,
30
- ToolResolverService | ToolExecutionService | Tool.HandlersFor<Tools>
32
+ ToolResolverService | ToolExecutionService | Tool.HandlersFor<any>
31
33
  > =>
32
34
  Effect.gen(function* () {
33
- const blueprintToolkit = yield* ToolResolverService.resolveToolkit([
34
- ...blueprints.flatMap(({ tools }) => tools),
35
- ...toolIds,
36
- ]);
35
+ const blueprintToolkit = yield* ToolResolverService.resolveToolkit(blueprints.flatMap(({ tools }) => tools));
36
+ const blueprintToolHandler = yield* blueprintToolkit.toContext(ToolExecutionService.handlersFor(blueprintToolkit));
37
+ const genericToolkit = GenericToolkit.merge(...genericToolkits);
37
38
 
38
- const blueprintToolkitHandler: Context.Context<Tool.HandlersFor<any>> = yield* blueprintToolkit.toContext(
39
- ToolExecutionService.handlersFor(blueprintToolkit),
40
- );
41
-
42
- const toolkit = Toolkit.merge(...[toolkitParam, blueprintToolkit].filter(isTruthy));
43
- return yield* toolkit.pipe(Effect.provide(blueprintToolkitHandler)) as any as Effect.Effect<
44
- Toolkit.WithHandler<any>,
45
- never,
46
- Tool.HandlersFor<Tools>
47
- >;
39
+ const toolkit = Toolkit.merge(...[toolkitProp, blueprintToolkit, genericToolkit.toolkit].filter(isTruthy));
40
+ return yield* toolkit.pipe(
41
+ Effect.provide(blueprintToolHandler),
42
+ Effect.provide(genericToolkit.layer),
43
+ ) as any as Effect.Effect<Toolkit.WithHandler<any>, never, Tool.HandlersFor<any>>;
48
44
  });
@@ -4,8 +4,9 @@
4
4
 
5
5
  import * as Schema from 'effect/Schema';
6
6
 
7
- import { type AnyEchoObject, ObjectId } from '@dxos/echo/internal';
7
+ import { type Obj } from '@dxos/echo';
8
8
  import { type ObjectVersion, getVersion } from '@dxos/echo-db';
9
+ import { ObjectId } from '@dxos/keys';
9
10
  import { type ContentBlock } from '@dxos/types';
10
11
 
11
12
  // TODO(dmaretskyi): Extract.
@@ -26,11 +27,11 @@ export interface VersionPin extends Schema.Schema.Type<typeof VersionPinSchema>
26
27
 
27
28
  export const VersionPin: typeof VersionPinSchema & {
28
29
  DISPOSITION: 'version-pin';
29
- fromObject: (object: AnyEchoObject) => VersionPin;
30
+ fromObject: (object: Obj.Unknown) => VersionPin;
30
31
  createBlock: (pin: VersionPin) => ContentBlock.Any;
31
32
  } = class extends VersionPinSchema {
32
33
  static readonly DISPOSITION = 'version-pin';
33
- static fromObject(object: AnyEchoObject): VersionPin {
34
+ static fromObject(object: Obj.Unknown): VersionPin {
34
35
  return VersionPin.make({
35
36
  objectId: object.id,
36
37
  version: getVersion(object),
@@ -52,10 +52,6 @@ Do not use bullets when listing suggestions.
52
52
  You may ask multiple choice questions by wrapping consise options inside <option> tags inside a well formed <select> tag.
53
53
  Example: <select><option>Yes</option><option>No</option></select>
54
54
 
55
- ## Toolkit
56
-
57
- If the user asks for a list of tools, then just emit a single self-closing <toolkit/> tag instead of listing the tools.
58
-
59
55
  ## Context
60
56
 
61
57
  The current date and time is {{DATETIME}}.
@@ -43,6 +43,15 @@
43
43
  - Suggested actions could include actions that create artifacts.
44
44
  - After creating an artifact use a tool to add it to the chat context.
45
45
 
46
- ## Context
46
+ ## Navigation
47
+
48
+ - When the user asks to open, go to, navigate to, or show a specific document, page, or object, use the navigation tools instead of loading content inline.
49
+ - First, call the Resolve navigation targets tool to find the navigation path for the requested item.
50
+ - Without arguments, it returns available pages that can be navigated to.
51
+ - With a DXN in the query, it resolves that specific object to a navigation path.
52
+ - If you know the object's DXN (e.g. from a database query or context objects), pass it in the query to get the exact navigation path.
53
+ - Then, call the Open tool with the resolved navigation path(s) in the subject array to navigate the user to that item.
54
+ - Do not respond with the document's content when the user asks to open it — open it in the main content area instead.
55
+ - If multiple targets match, present the options to the user and let them choose which to open.
47
56
 
48
- Today is {{DATETIME}}.
57
+ ## Context
@@ -29,13 +29,14 @@ export const createSystemPrompt = (options: Partial<SystemPromptOptions> = {}) =
29
29
  }),
30
30
  );
31
31
 
32
+ /**
33
+ * NOTE: We are forbidden from accessing randomness at model level in cloudflare, so we have to have pre-generated ids.
34
+ */
35
+ const SYSTEM_ID = '01K2J2FZWH39F55CS7B26GP1Q3';
36
+
32
37
  export const templates = {
33
- /** Editable system prompt.*/
34
38
  system: Template.make({
35
- /**
36
- * NOTE: We are forbidden from accessing randomness at model level in cloudflare, so we have to have pre-generated ids.
37
- */
38
- id: '01K2J2FZWH39F55CS7B26GP1Q3',
39
+ id: SYSTEM_ID,
39
40
  source: SYSTEM,
40
41
  }),
41
42
  };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './layer';