@dxos/assistant 0.9.1-main.c7dcc2e112 → 0.10.0
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.
- package/dist/lib/neutral/extraction/index.mjs +1 -1
- package/dist/lib/neutral/extraction/index.mjs.map +1 -1
- package/dist/lib/neutral/index.mjs +387 -230
- package/dist/lib/neutral/index.mjs.map +4 -4
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/types/src/extraction/extraction-llm-function/proper-noun-extraction.d.ts +1 -202
- package/dist/types/src/extraction/extraction-llm-function/proper-noun-extraction.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/request/AiRequest.d.ts +13 -208
- package/dist/types/src/request/AiRequest.d.ts.map +1 -1
- package/dist/types/src/request/format.d.ts +1 -1
- package/dist/types/src/request/format.d.ts.map +1 -1
- package/dist/types/src/session/AiContext.d.ts +27 -105
- package/dist/types/src/session/AiContext.d.ts.map +1 -1
- package/dist/types/src/session/AiSession.d.ts +5 -29
- package/dist/types/src/session/AiSession.d.ts.map +1 -1
- package/dist/types/src/session/Harness.d.ts +122 -0
- package/dist/types/src/session/Harness.d.ts.map +1 -0
- package/dist/types/src/session/SessionLink.d.ts +8 -11
- package/dist/types/src/session/SessionLink.d.ts.map +1 -1
- package/dist/types/src/session/SessionLoader.d.ts +2 -2
- package/dist/types/src/session/SessionLoader.d.ts.map +1 -1
- package/dist/types/src/session/SkillHooks.d.ts +28 -0
- package/dist/types/src/session/SkillHooks.d.ts.map +1 -0
- package/dist/types/src/session/harness-control.d.ts +177 -0
- package/dist/types/src/session/harness-control.d.ts.map +1 -0
- package/dist/types/src/session/index.d.ts +3 -0
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +4 -4
- package/dist/types/src/session/toolkit.d.ts.map +1 -1
- package/dist/types/src/templates/system.d.ts +4 -6
- package/dist/types/src/templates/system.d.ts.map +1 -1
- package/dist/types/src/tool-runtime/index.d.ts.map +1 -0
- package/dist/types/src/tool-runtime/services.d.ts.map +1 -0
- package/dist/types/src/util/artifact.d.ts.map +1 -1
- package/dist/types/src/util/diff.d.ts +9 -3
- package/dist/types/src/util/diff.d.ts.map +1 -1
- package/dist/types/src/util/tracing.d.ts +40 -0
- package/dist/types/src/util/tracing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -25
- package/src/extraction/extraction-llm-function/proper-noun-extraction.ts +1 -1
- package/src/index.ts +1 -1
- package/src/request/AiRequest.ts +35 -14
- package/src/request/format.ts +9 -11
- package/src/session/AiContext.test.ts +52 -32
- package/src/session/AiContext.ts +73 -95
- package/src/session/AiSession.test.ts +4 -4
- package/src/session/AiSession.ts +43 -83
- package/src/session/Harness.ts +241 -0
- package/src/session/SessionLink.ts +8 -8
- package/src/session/SessionLoader.ts +4 -4
- package/src/session/SkillHooks.ts +71 -0
- package/src/session/harness-control.ts +30 -0
- package/src/session/index.ts +3 -0
- package/src/session/toolkit.ts +8 -8
- package/src/templates/instructions/format.tpl +3 -3
- package/src/templates/instructions/system.tpl +10 -12
- package/src/templates/system.ts +6 -2
- package/src/util/artifact.ts +9 -2
- package/src/util/diff.test.ts +4 -4
- package/src/util/diff.ts +15 -8
- package/src/util/tracing.ts +1 -1
- package/dist/types/src/functions/index.d.ts.map +0 -1
- package/dist/types/src/functions/services.d.ts.map +0 -1
- /package/dist/types/src/{functions → tool-runtime}/index.d.ts +0 -0
- /package/dist/types/src/{functions → tool-runtime}/services.d.ts +0 -0
- /package/src/{functions → tool-runtime}/index.ts +0 -0
- /package/src/{functions → tool-runtime}/services.ts +0 -0
package/src/session/AiContext.ts
CHANGED
|
@@ -6,15 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import { Atom, Registry as AtomRegistry } from '@effect-atom/atom-react';
|
|
8
8
|
import * as EArray from 'effect/Array';
|
|
9
|
-
import * as Context from 'effect/Context';
|
|
10
|
-
import * as Effect from 'effect/Effect';
|
|
11
9
|
import * as Function from 'effect/Function';
|
|
12
10
|
import * as Runtime from 'effect/Runtime';
|
|
13
11
|
import * as Schema from 'effect/Schema';
|
|
14
12
|
|
|
15
|
-
import {
|
|
13
|
+
import { Skill } from '@dxos/compute';
|
|
16
14
|
import { Resource } from '@dxos/context';
|
|
17
|
-
import { Annotation, DXN, Feed, Obj, type QueryResult,
|
|
15
|
+
import { Annotation, Database, DXN, Feed, Obj, Query, type QueryResult, Ref, Type } from '@dxos/echo';
|
|
18
16
|
import { assertArgument } from '@dxos/invariant';
|
|
19
17
|
import { EID, type URI } from '@dxos/keys';
|
|
20
18
|
import { log } from '@dxos/log';
|
|
@@ -23,26 +21,26 @@ import { ComplexSet, isNonNullable } from '@dxos/util';
|
|
|
23
21
|
/**
|
|
24
22
|
* Thread message that binds or unbinds contextual objects to a conversation.
|
|
25
23
|
*/
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
export class Binding extends Type.makeObject<Binding>(DXN.make('org.dxos.type.contextBinding', '0.1.0'))(
|
|
25
|
+
Schema.Struct({
|
|
26
|
+
skills: Schema.Struct({
|
|
27
|
+
added: Schema.Array(Ref.Ref(Skill.Skill)),
|
|
28
|
+
removed: Schema.Array(Ref.Ref(Skill.Skill)),
|
|
29
|
+
}),
|
|
30
|
+
|
|
31
|
+
objects: Schema.Struct({
|
|
32
|
+
added: Schema.Array(Ref.Ref(Obj.Unknown)),
|
|
33
|
+
removed: Schema.Array(Ref.Ref(Obj.Unknown)),
|
|
34
|
+
}),
|
|
35
|
+
}).pipe(Annotation.HiddenAnnotation.set(true)),
|
|
36
|
+
) {}
|
|
39
37
|
export type BindingProps = Partial<{
|
|
40
|
-
|
|
38
|
+
skills: Ref.Ref<Skill.Skill>[];
|
|
41
39
|
objects: Ref.Ref<Obj.Unknown>[];
|
|
42
40
|
}>;
|
|
43
41
|
|
|
44
42
|
export class Bindings {
|
|
45
|
-
readonly
|
|
43
|
+
readonly skills = new ComplexSet<Ref.Ref<Skill.Skill>>((ref) => ref.uri);
|
|
46
44
|
|
|
47
45
|
// TODO(burdon): Some DXNs have the Space prefix so only compare the object ID.
|
|
48
46
|
readonly objects = new ComplexSet<Ref.Ref<Obj.Unknown>>((ref) => {
|
|
@@ -50,9 +48,9 @@ export class Bindings {
|
|
|
50
48
|
return echoUri ? EID.getEntityId(echoUri) : undefined;
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
toJSON(): {
|
|
51
|
+
toJSON(): { skills: URI.URI[]; objects: URI.URI[] } {
|
|
54
52
|
return {
|
|
55
|
-
|
|
53
|
+
skills: EArray.fromIterable(this.skills).map((ref) => ref.uri),
|
|
56
54
|
objects: EArray.fromIterable(this.objects).map((ref) => ref.uri),
|
|
57
55
|
};
|
|
58
56
|
}
|
|
@@ -60,21 +58,21 @@ export class Bindings {
|
|
|
60
58
|
|
|
61
59
|
export type BinderOptions = {
|
|
62
60
|
feed: Feed.Feed;
|
|
63
|
-
runtime: Runtime.Runtime<
|
|
61
|
+
runtime: Runtime.Runtime<Database.Service>;
|
|
64
62
|
/** @effect-atom/atom-react Registry for reactive state management. */
|
|
65
63
|
registry?: AtomRegistry.Registry;
|
|
66
64
|
};
|
|
67
65
|
|
|
68
66
|
/**
|
|
69
|
-
* Manages bindings of
|
|
67
|
+
* Manages bindings of skills and objects to a conversation.
|
|
70
68
|
*/
|
|
71
69
|
// TODO(burdon): Context should manage ephemeral state of bindings until prompt is issued?
|
|
72
70
|
export class Binder extends Resource {
|
|
73
|
-
private readonly
|
|
71
|
+
private readonly _skills = Atom.make<Skill.Skill[]>([]).pipe(Atom.keepAlive);
|
|
74
72
|
private readonly _objects = Atom.make<Obj.Unknown[]>([]).pipe(Atom.keepAlive);
|
|
75
73
|
private readonly _registry: AtomRegistry.Registry;
|
|
76
74
|
private readonly _feed: Feed.Feed;
|
|
77
|
-
private readonly _runtime: Runtime.Runtime<
|
|
75
|
+
private readonly _runtime: Runtime.Runtime<Database.Service>;
|
|
78
76
|
|
|
79
77
|
#bindingsQuery: QueryResult.QueryResult<Binding> | undefined;
|
|
80
78
|
|
|
@@ -88,10 +86,10 @@ export class Binder extends Resource {
|
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
/**
|
|
91
|
-
* Returns the
|
|
89
|
+
* Returns the skills atom for subscription.
|
|
92
90
|
*/
|
|
93
|
-
get
|
|
94
|
-
return this.
|
|
91
|
+
get skills(): Atom.Atom<Skill.Skill[]> {
|
|
92
|
+
return this._skills;
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
/**
|
|
@@ -102,10 +100,10 @@ export class Binder extends Resource {
|
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
/**
|
|
105
|
-
* Gets the current
|
|
103
|
+
* Gets the current skills value.
|
|
106
104
|
*/
|
|
107
|
-
|
|
108
|
-
return this._registry.get(this.
|
|
105
|
+
getSkills(): Skill.Skill[] {
|
|
106
|
+
return this._registry.get(this._skills);
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
/**
|
|
@@ -116,10 +114,10 @@ export class Binder extends Resource {
|
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
/**
|
|
119
|
-
* Subscribe to changes in
|
|
117
|
+
* Subscribe to changes in skills.
|
|
120
118
|
*/
|
|
121
|
-
|
|
122
|
-
return this._registry.subscribe(this.
|
|
119
|
+
subscribeSkills(cb: (skills: Skill.Skill[]) => void): () => void {
|
|
120
|
+
return this._registry.subscribe(this._skills, () => cb(this._registry.get(this._skills)));
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
/**
|
|
@@ -153,8 +151,8 @@ export class Binder extends Resource {
|
|
|
153
151
|
log('sync', { bindingItems: results.length });
|
|
154
152
|
await this._updateBindings(results);
|
|
155
153
|
log('sync complete', {
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
skills: this._registry.get(this._skills).length,
|
|
155
|
+
skillKeys: this._registry.get(this._skills).map((bp) => Skill.getKey(bp)),
|
|
158
156
|
});
|
|
159
157
|
}
|
|
160
158
|
}
|
|
@@ -169,25 +167,25 @@ export class Binder extends Resource {
|
|
|
169
167
|
|
|
170
168
|
log('_updateBindings', {
|
|
171
169
|
items: items.length,
|
|
172
|
-
|
|
170
|
+
skillRefs: [...bindings.skills].map((ref) => ({ uri: ref.uri, available: ref.isAvailable })),
|
|
173
171
|
});
|
|
174
172
|
|
|
175
173
|
// Resolve references (loading them first if needed).
|
|
176
|
-
const
|
|
174
|
+
const currentSkills = this._registry.get(this._skills);
|
|
177
175
|
const currentObjects = this._registry.get(this._objects);
|
|
178
|
-
const
|
|
176
|
+
const resolvedSkills = await this._resolve(bindings.skills, currentSkills);
|
|
179
177
|
const resolvedObjects = await this._resolve(bindings.objects, currentObjects);
|
|
180
178
|
|
|
181
179
|
log('_updateBindings resolved', {
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
resolvedSkills: resolvedSkills.length,
|
|
181
|
+
resolvedSkillKeys: resolvedSkills.map((bp) => Obj.getMeta(bp).key ?? '<missing>'),
|
|
184
182
|
});
|
|
185
183
|
|
|
186
|
-
// Drop
|
|
187
|
-
// (e.g. tool/operation registration calls
|
|
188
|
-
const
|
|
184
|
+
// Drop skills that have no registry key — they cannot be used downstream
|
|
185
|
+
// (e.g. tool/operation registration calls Skill.getKey which throws).
|
|
186
|
+
const keyedSkills = resolvedSkills.filter((bp) => {
|
|
189
187
|
if (Obj.getMeta(bp).key === undefined) {
|
|
190
|
-
log.warn('dropping
|
|
188
|
+
log.warn('dropping skill with no meta key', { uri: Obj.getURI(bp) });
|
|
191
189
|
return false;
|
|
192
190
|
}
|
|
193
191
|
return true;
|
|
@@ -195,54 +193,54 @@ export class Binder extends Resource {
|
|
|
195
193
|
|
|
196
194
|
// Filter current state to only items still in the reduced binding set,
|
|
197
195
|
// then merge in newly resolved items. This ensures unbind events are respected.
|
|
198
|
-
const
|
|
196
|
+
const reducedSkillDxns = new Set<URI.URI>([...bindings.skills].map((ref) => ref.uri));
|
|
199
197
|
const reducedObjectDxns = new Set<URI.URI>([...bindings.objects].map((ref) => ref.uri));
|
|
200
|
-
const
|
|
198
|
+
const filteredSkills = currentSkills.filter((obj) => {
|
|
201
199
|
const uri = Obj.getURI(obj);
|
|
202
|
-
return uri != null &&
|
|
200
|
+
return uri != null && reducedSkillDxns.has(uri) && Obj.getMeta(obj).key !== undefined;
|
|
203
201
|
});
|
|
204
202
|
const filteredObjects = currentObjects.filter((obj) => {
|
|
205
203
|
const uri = Obj.getURI(obj);
|
|
206
204
|
return uri != null && reducedObjectDxns.has(uri);
|
|
207
205
|
});
|
|
208
|
-
const
|
|
206
|
+
const mergedSkills = this._mergeInto(filteredSkills, keyedSkills);
|
|
209
207
|
const mergedObjects = this._mergeInto(filteredObjects, resolvedObjects);
|
|
210
208
|
|
|
211
|
-
this._registry.set(this.
|
|
209
|
+
this._registry.set(this._skills, mergedSkills);
|
|
212
210
|
this._registry.set(this._objects, mergedObjects);
|
|
213
211
|
|
|
214
212
|
log('updated bindings', {
|
|
215
|
-
|
|
213
|
+
skills: mergedSkills.length,
|
|
216
214
|
objects: mergedObjects.length,
|
|
217
215
|
});
|
|
218
216
|
}
|
|
219
217
|
|
|
220
218
|
protected override async _close(): Promise<void> {
|
|
221
219
|
// Reset atoms to empty state.
|
|
222
|
-
this._registry.set(this.
|
|
220
|
+
this._registry.set(this._skills, []);
|
|
223
221
|
this._registry.set(this._objects, []);
|
|
224
222
|
}
|
|
225
223
|
|
|
226
|
-
async bind({
|
|
227
|
-
const
|
|
224
|
+
async bind({ skills, objects }: BindingProps): Promise<void> {
|
|
225
|
+
const currentSkills = this._registry.get(this._skills);
|
|
228
226
|
const currentObjects = this._registry.get(this._objects);
|
|
229
227
|
|
|
230
|
-
const { added:
|
|
228
|
+
const { added: addedSkills, next: nextSkills } = this._processBindings(skills, currentSkills);
|
|
231
229
|
const { added: addedObjects, next: nextObjects } = this._processBindings(objects, currentObjects);
|
|
232
|
-
if (!
|
|
230
|
+
if (!addedSkills.length && !addedObjects.length) {
|
|
233
231
|
return;
|
|
234
232
|
}
|
|
235
233
|
|
|
236
234
|
// Atomic updates - subscribers notified automatically.
|
|
237
|
-
this._registry.set(this.
|
|
235
|
+
this._registry.set(this._skills, nextSkills);
|
|
238
236
|
this._registry.set(this._objects, nextObjects);
|
|
239
237
|
|
|
240
|
-
log('bind', {
|
|
238
|
+
log('bind', { skills: addedSkills.length, objects: addedObjects.length });
|
|
241
239
|
await Runtime.runPromise(this._runtime)(
|
|
242
240
|
Feed.append(this._feed, [
|
|
243
241
|
Obj.make(Binding, {
|
|
244
|
-
|
|
245
|
-
added:
|
|
242
|
+
skills: {
|
|
243
|
+
added: addedSkills,
|
|
246
244
|
removed: [],
|
|
247
245
|
},
|
|
248
246
|
objects: {
|
|
@@ -254,19 +252,19 @@ export class Binder extends Resource {
|
|
|
254
252
|
);
|
|
255
253
|
}
|
|
256
254
|
|
|
257
|
-
async unbind({
|
|
258
|
-
if (!
|
|
255
|
+
async unbind({ skills, objects }: BindingProps): Promise<void> {
|
|
256
|
+
if (!skills?.length && !objects?.length) {
|
|
259
257
|
return;
|
|
260
258
|
}
|
|
261
259
|
|
|
262
260
|
// Immediately update atom state so removals are reflected before the queue round-trips.
|
|
263
|
-
const
|
|
261
|
+
const removedSkillDxns = (skills ?? []).map((ref) => ref.uri);
|
|
264
262
|
const removedObjectDxns = (objects ?? []).map((ref) => ref.uri);
|
|
265
|
-
if (
|
|
266
|
-
const current = this._registry.get(this.
|
|
263
|
+
if (removedSkillDxns.length > 0) {
|
|
264
|
+
const current = this._registry.get(this._skills);
|
|
267
265
|
this._registry.set(
|
|
268
|
-
this.
|
|
269
|
-
current.filter((obj) => !
|
|
266
|
+
this._skills,
|
|
267
|
+
current.filter((obj) => !removedSkillDxns.some((uri) => Obj.getURI(obj) === uri)),
|
|
270
268
|
);
|
|
271
269
|
}
|
|
272
270
|
if (removedObjectDxns.length > 0) {
|
|
@@ -277,13 +275,13 @@ export class Binder extends Resource {
|
|
|
277
275
|
);
|
|
278
276
|
}
|
|
279
277
|
|
|
280
|
-
log('unbind', {
|
|
278
|
+
log('unbind', { skills: skills?.length, objects: objects?.length });
|
|
281
279
|
await Runtime.runPromise(this._runtime)(
|
|
282
280
|
Feed.append(this._feed, [
|
|
283
281
|
Obj.make(Binding, {
|
|
284
|
-
|
|
282
|
+
skills: {
|
|
285
283
|
added: [],
|
|
286
|
-
removed:
|
|
284
|
+
removed: skills ?? [],
|
|
287
285
|
},
|
|
288
286
|
objects: {
|
|
289
287
|
added: [],
|
|
@@ -329,14 +327,14 @@ export class Binder extends Resource {
|
|
|
329
327
|
}
|
|
330
328
|
|
|
331
329
|
/**
|
|
332
|
-
* Reduce results into sets of
|
|
330
|
+
* Reduce results into sets of skills and objects.
|
|
333
331
|
*/
|
|
334
332
|
private _reduce(items: Binding[]): Bindings {
|
|
335
333
|
return Function.pipe(
|
|
336
334
|
items,
|
|
337
|
-
EArray.reduce(new Bindings(), (context, {
|
|
338
|
-
|
|
339
|
-
|
|
335
|
+
EArray.reduce(new Bindings(), (context, { skills, objects }) => {
|
|
336
|
+
skills.added.forEach((ref) => context.skills.add(ref));
|
|
337
|
+
skills.removed.forEach((ref) => context.skills.delete(ref));
|
|
340
338
|
|
|
341
339
|
objects.added.forEach((ref) => context.objects.add(ref));
|
|
342
340
|
objects.removed.forEach((ref) => {
|
|
@@ -375,7 +373,7 @@ export class Binder extends Resource {
|
|
|
375
373
|
|
|
376
374
|
/**
|
|
377
375
|
* Resolve references to objects, loading them first if needed and falling back to existing objects.
|
|
378
|
-
* DXN refs (e.g. `dxn:org.dxos.
|
|
376
|
+
* DXN refs (e.g. `dxn:org.dxos.skill.database`) resolve via the wired-up ECHO ref resolver
|
|
379
377
|
* which already spans both the space DB and the hypergraph registry.
|
|
380
378
|
*/
|
|
381
379
|
private async _resolve<T extends Obj.Unknown>(refs: Iterable<Ref.Ref<T>>, current: T[]): Promise<T[]> {
|
|
@@ -398,23 +396,3 @@ export class Binder extends Resource {
|
|
|
398
396
|
.filter(isNonNullable);
|
|
399
397
|
}
|
|
400
398
|
}
|
|
401
|
-
|
|
402
|
-
export class Service extends Context.Tag('@dxos/assistant/AiContextService')<
|
|
403
|
-
Service,
|
|
404
|
-
{
|
|
405
|
-
binder: Binder;
|
|
406
|
-
}
|
|
407
|
-
>() {
|
|
408
|
-
static bindContext = ({ blueprints, objects }: BindingProps): Effect.Effect<void, never, Service> =>
|
|
409
|
-
Effect.gen(function* () {
|
|
410
|
-
const { binder } = yield* Service;
|
|
411
|
-
yield* Effect.promise(() => binder.bind({ blueprints, objects }));
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
static findObjects = <T extends Type.AnyObj>(type: T): Effect.Effect<Type.InstanceType<T>[], never, Service> => {
|
|
415
|
-
return Effect.gen(function* () {
|
|
416
|
-
const { binder } = yield* Service;
|
|
417
|
-
return binder.getObjects().filter(Obj.instanceOf(type));
|
|
418
|
-
});
|
|
419
|
-
};
|
|
420
|
-
}
|
|
@@ -27,7 +27,7 @@ describe('AiSession.Session.getHistory', () => {
|
|
|
27
27
|
const message = makeMessage('hello');
|
|
28
28
|
yield* Feed.append(feed, [message]);
|
|
29
29
|
|
|
30
|
-
const runtime = yield* Effect.runtime<
|
|
30
|
+
const runtime = yield* Effect.runtime<Database.Service>();
|
|
31
31
|
const session = new AiSession.Session({ feed, runtime });
|
|
32
32
|
|
|
33
33
|
const result = yield* Effect.promise(() => session.getHistory());
|
|
@@ -51,7 +51,7 @@ describe('AiSession.Session.getHistory', () => {
|
|
|
51
51
|
forkMsg,
|
|
52
52
|
]);
|
|
53
53
|
|
|
54
|
-
const runtime = yield* Effect.runtime<
|
|
54
|
+
const runtime = yield* Effect.runtime<Database.Service>();
|
|
55
55
|
const session = new AiSession.Session({ feed: forkFeed, runtime });
|
|
56
56
|
|
|
57
57
|
const result = yield* Effect.promise(() => session.getHistory());
|
|
@@ -76,7 +76,7 @@ describe('AiSession.Session.getHistory', () => {
|
|
|
76
76
|
Obj.make(SessionLink.SessionLink, { feedRef: Ref.make(sourceFeed), messageId: msg2.id }),
|
|
77
77
|
]);
|
|
78
78
|
|
|
79
|
-
const runtime = yield* Effect.runtime<
|
|
79
|
+
const runtime = yield* Effect.runtime<Database.Service>();
|
|
80
80
|
const session = new AiSession.Session({ feed: forkFeed, runtime });
|
|
81
81
|
|
|
82
82
|
const result = yield* Effect.promise(() => session.getHistory());
|
|
@@ -100,7 +100,7 @@ describe('AiSession.Session.getHistory', () => {
|
|
|
100
100
|
forkMsg,
|
|
101
101
|
]);
|
|
102
102
|
|
|
103
|
-
const runtime = yield* Effect.runtime<
|
|
103
|
+
const runtime = yield* Effect.runtime<Database.Service>();
|
|
104
104
|
const session = new AiSession.Session({ feed: forkFeed, runtime });
|
|
105
105
|
|
|
106
106
|
const result = yield* Effect.promise(() => session.getHistory());
|
package/src/session/AiSession.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import { type Registry as AtomRegistry } from '@effect-atom/atom-react';
|
|
8
8
|
import * as Array from 'effect/Array';
|
|
9
|
-
import * as Context from 'effect/Context';
|
|
10
9
|
import * as Effect from 'effect/Effect';
|
|
11
10
|
import * as Either from 'effect/Either';
|
|
12
11
|
import { pipe } from 'effect/Function';
|
|
@@ -15,20 +14,21 @@ import * as Record from 'effect/Record';
|
|
|
15
14
|
import * as Runtime from 'effect/Runtime';
|
|
16
15
|
|
|
17
16
|
import { type OpaqueToolkit, type ToolExecutionService, type ToolResolverService } from '@dxos/ai';
|
|
18
|
-
import {
|
|
17
|
+
import { McpServer, Operation, type Skill, Trace } from '@dxos/compute';
|
|
19
18
|
import { Resource } from '@dxos/context';
|
|
20
19
|
import { Database, Feed, Filter, Obj, Registry } from '@dxos/echo';
|
|
21
|
-
import { EffectEx } from '@dxos/effect';
|
|
22
20
|
import { invariant } from '@dxos/invariant';
|
|
23
21
|
import { log } from '@dxos/log';
|
|
24
22
|
import { McpToolkit } from '@dxos/mcp-client';
|
|
25
|
-
import {
|
|
23
|
+
import { type ContentBlock, Message } from '@dxos/types';
|
|
26
24
|
|
|
27
|
-
import { ToolExecutionServices } from '../functions';
|
|
28
25
|
import { AiRequest, type GenerationObserver, formatSystemPrompt } from '../request';
|
|
26
|
+
import { ToolExecutionServices } from '../tool-runtime';
|
|
29
27
|
import { McpServerError } from '../util';
|
|
30
28
|
import * as AiContext from './AiContext';
|
|
29
|
+
import * as Harness from './Harness';
|
|
31
30
|
import { SessionLoader } from './SessionLoader';
|
|
31
|
+
import * as SkillHooks from './SkillHooks';
|
|
32
32
|
import { createToolkit } from './toolkit';
|
|
33
33
|
|
|
34
34
|
export type RunProps<R = never> = {
|
|
@@ -38,7 +38,7 @@ export type RunProps<R = never> = {
|
|
|
38
38
|
toolkit?: OpaqueToolkit.OpaqueToolkit<R>;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Space-level MCP servers to connect alongside
|
|
41
|
+
* Space-level MCP servers to connect alongside skill-defined ones.
|
|
42
42
|
*/
|
|
43
43
|
mcpServers?: readonly McpServer.McpServer[];
|
|
44
44
|
|
|
@@ -52,7 +52,7 @@ export type RunProps<R = never> = {
|
|
|
52
52
|
|
|
53
53
|
export type Options = {
|
|
54
54
|
feed: Feed.Feed;
|
|
55
|
-
runtime: Runtime.Runtime<
|
|
55
|
+
runtime: Runtime.Runtime<Database.Service>;
|
|
56
56
|
/** @effect-atom/atom-react Registry for reactive state. */
|
|
57
57
|
registry?: AtomRegistry.Registry;
|
|
58
58
|
};
|
|
@@ -69,11 +69,11 @@ const SUMMARY_THRESHOLD = 80_000;
|
|
|
69
69
|
*/
|
|
70
70
|
export class Session extends Resource {
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* Skills and objects bound to the session.
|
|
73
73
|
*/
|
|
74
74
|
private readonly _binder: AiContext.Binder;
|
|
75
75
|
private readonly _feed: Feed.Feed;
|
|
76
|
-
private readonly _runtime: Runtime.Runtime<
|
|
76
|
+
private readonly _runtime: Runtime.Runtime<Database.Service>;
|
|
77
77
|
private readonly _sessionLoader = new SessionLoader();
|
|
78
78
|
|
|
79
79
|
public constructor(options: Options) {
|
|
@@ -114,7 +114,7 @@ export class Session extends Resource {
|
|
|
114
114
|
ToolExecutionService | ToolResolverService
|
|
115
115
|
> {
|
|
116
116
|
return Effect.gen(this, function* () {
|
|
117
|
-
const toolkit = yield* createToolkit({
|
|
117
|
+
const toolkit = yield* createToolkit({ skills: this.context.getSkills() });
|
|
118
118
|
return toolkit.toolkit.tools;
|
|
119
119
|
}).pipe(Effect.orDie);
|
|
120
120
|
}
|
|
@@ -140,12 +140,12 @@ export class Session extends Resource {
|
|
|
140
140
|
): Effect.Effect<Message.Message[], AiRequest.RunError, AiRequest.RunRequirements | R> {
|
|
141
141
|
return Effect.gen(this, function* () {
|
|
142
142
|
const history = yield* Effect.promise(() => this.getHistory());
|
|
143
|
-
const
|
|
143
|
+
const skills = this.context.getSkills();
|
|
144
144
|
const objects = this.context.getObjects();
|
|
145
145
|
|
|
146
146
|
log('run', {
|
|
147
147
|
history: history.length,
|
|
148
|
-
|
|
148
|
+
skills: skills.length,
|
|
149
149
|
objects: objects.length,
|
|
150
150
|
});
|
|
151
151
|
|
|
@@ -159,34 +159,47 @@ export class Session extends Resource {
|
|
|
159
159
|
|
|
160
160
|
yield* request.begin({
|
|
161
161
|
history,
|
|
162
|
-
|
|
162
|
+
skills,
|
|
163
163
|
objects,
|
|
164
164
|
prompt: params.prompt,
|
|
165
165
|
system: params.system,
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
//
|
|
168
|
+
// Fire begin-request hooks declared by the bound skills. These run in the agent's turn
|
|
169
|
+
// fiber (Tier A only), so they cannot reach the live host (Tier B) — that is the end hook's job.
|
|
170
|
+
yield* SkillHooks.runHooks({
|
|
171
|
+
skills,
|
|
172
|
+
phase: 'begin-request',
|
|
173
|
+
invoke: (operation, input) => Operation.invoke(operation, input).pipe(Effect.asVoid, Effect.orDie),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Turn loop: recompute toolkit and system prompt between turns to pick up dynamically enabled skills.
|
|
169
177
|
do {
|
|
170
178
|
yield* Effect.promise(() => this.context.sync());
|
|
171
|
-
const
|
|
172
|
-
const mcps = yield* connectMcpServers(
|
|
179
|
+
const currentSkills = this.context.getSkills();
|
|
180
|
+
const mcps = yield* connectMcpServers(currentSkills, params.mcpServers);
|
|
173
181
|
const toolkit = yield* createToolkit({
|
|
174
182
|
toolkit: params.toolkit,
|
|
175
|
-
|
|
183
|
+
skills: currentSkills,
|
|
176
184
|
opaqueToolkits: mcps,
|
|
177
185
|
});
|
|
178
186
|
|
|
179
187
|
log('toolkit', { tools: Record.keys(toolkit.toolkit.tools) });
|
|
180
188
|
const system = yield* formatSystemPrompt({
|
|
181
189
|
system: params.system,
|
|
182
|
-
|
|
190
|
+
skills: currentSkills,
|
|
183
191
|
objects: this.context.getObjects(),
|
|
184
192
|
}).pipe(Effect.orDie);
|
|
185
193
|
|
|
186
|
-
const { done } = yield* request.runAgentTurn({ system, toolkit });
|
|
194
|
+
const { done, finishReason } = yield* request.runAgentTurn({ system, toolkit });
|
|
187
195
|
if (done) {
|
|
188
196
|
break;
|
|
189
197
|
}
|
|
198
|
+
// A paused server-tool turn (e.g. Anthropic `pause_turn`) resumes with another request and
|
|
199
|
+
// no local tool execution; the trailing server tool call must be left intact for the provider.
|
|
200
|
+
if (finishReason === 'pause') {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
190
203
|
|
|
191
204
|
yield* request.runTools({ toolkit });
|
|
192
205
|
} while (true);
|
|
@@ -201,10 +214,13 @@ export class Session extends Resource {
|
|
|
201
214
|
}).pipe(
|
|
202
215
|
Effect.provide(
|
|
203
216
|
Layer.mergeAll(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
Layer.succeed(
|
|
217
|
+
// Tier A only: the agent's own turn fiber has no `ProcessManager.Service` in scope, so
|
|
218
|
+
// Tier B (setAlarm/enqueueMessage) is reachable only by child operations through the
|
|
219
|
+
// process-affinity HarnessService LayerSpec — not from here.
|
|
220
|
+
Layer.succeed(
|
|
221
|
+
Harness.HarnessService,
|
|
222
|
+
Harness.fromBinder({ feed: this._feed, runtime: this._runtime, binder: this.context }),
|
|
223
|
+
),
|
|
208
224
|
Operation.withInvocationOptions({ conversation: Obj.getURI(this._feed) }),
|
|
209
225
|
),
|
|
210
226
|
),
|
|
@@ -213,68 +229,12 @@ export class Session extends Resource {
|
|
|
213
229
|
}
|
|
214
230
|
}
|
|
215
231
|
|
|
216
|
-
/**
|
|
217
|
-
* Gives access to the ai session.
|
|
218
|
-
*/
|
|
219
|
-
export class Service extends Context.Tag('@dxos/assistant/AiSessionService')<Service, Session>() {
|
|
220
|
-
/**
|
|
221
|
-
* Create a new session layer from options.
|
|
222
|
-
*/
|
|
223
|
-
static layer = (options: Options): Layer.Layer<Service | AiContext.Service> =>
|
|
224
|
-
aiContextFromSession.pipe(
|
|
225
|
-
Layer.provideMerge(
|
|
226
|
-
Layer.scoped(
|
|
227
|
-
Service,
|
|
228
|
-
Effect.gen(function* () {
|
|
229
|
-
const session = yield* EffectEx.acquireReleaseResource(() => new Session(options));
|
|
230
|
-
return session;
|
|
231
|
-
}),
|
|
232
|
-
),
|
|
233
|
-
),
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Create a new session with a new feed.
|
|
238
|
-
*/
|
|
239
|
-
static layerNewFeed = (
|
|
240
|
-
options?: Omit<Options, 'feed' | 'runtime'>,
|
|
241
|
-
): Layer.Layer<Service | AiContext.Service, never, Database.Service | Feed.FeedService> =>
|
|
242
|
-
Layer.unwrapScoped(
|
|
243
|
-
Effect.gen(function* () {
|
|
244
|
-
const feed = yield* Database.add(Feed.make());
|
|
245
|
-
const runtime = yield* Effect.runtime<Feed.FeedService>();
|
|
246
|
-
return Service.layer({ ...options, feed, runtime });
|
|
247
|
-
}),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Run a prompt in the current session.
|
|
252
|
-
*/
|
|
253
|
-
static run = <R = never>(
|
|
254
|
-
params: RunProps<R>,
|
|
255
|
-
): Effect.Effect<Message.Message[], AiRequest.RunError, AiRequest.RunRequirements | Service | R> =>
|
|
256
|
-
Effect.gen(function* () {
|
|
257
|
-
const session = yield* Service;
|
|
258
|
-
return yield* session.createRequest(params);
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const aiContextFromSession = Layer.effect(
|
|
263
|
-
AiContext.Service,
|
|
264
|
-
Effect.gen(function* () {
|
|
265
|
-
const session = yield* Service;
|
|
266
|
-
return {
|
|
267
|
-
binder: session.context,
|
|
268
|
-
};
|
|
269
|
-
}),
|
|
270
|
-
);
|
|
271
|
-
|
|
272
232
|
const connectMcpServers = (
|
|
273
|
-
|
|
233
|
+
skills: readonly Skill.Skill[],
|
|
274
234
|
spaceMcpServers: readonly McpServer.McpServer[] = [],
|
|
275
235
|
): Effect.Effect<OpaqueToolkit.OpaqueToolkit[], never, Trace.TraceService> => {
|
|
276
|
-
const
|
|
277
|
-
|
|
236
|
+
const skillServers: McpToolkit.McpToolkitOptions[] = pipe(
|
|
237
|
+
skills,
|
|
278
238
|
Array.flatMap((_) => _.mcpServers ?? []),
|
|
279
239
|
Array.map(({ url, protocol, apiKey }) => ({ url, protocol, apiKey })),
|
|
280
240
|
);
|
|
@@ -283,7 +243,7 @@ const connectMcpServers = (
|
|
|
283
243
|
protocol,
|
|
284
244
|
apiKey,
|
|
285
245
|
}));
|
|
286
|
-
const allServers = [...
|
|
246
|
+
const allServers = [...skillServers, ...spaceServers];
|
|
287
247
|
|
|
288
248
|
return pipe(
|
|
289
249
|
allServers,
|