@dxos/assistant 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
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/README.md +1 -0
- package/dist/lib/neutral/chunk-KVYJFFLF.mjs +1805 -0
- package/dist/lib/neutral/chunk-KVYJFFLF.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/extraction/index.mjs +26 -32
- package/dist/lib/neutral/extraction/index.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/index.mjs +72 -371
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/testing/index.mjs +112 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +24 -5
- package/dist/types/src/conversation/context.d.ts.map +1 -1
- package/dist/types/src/conversation/conversation.d.ts +29 -4
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/diff.d.ts +3 -1
- package/dist/types/src/diff.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/index.d.ts +0 -1
- package/dist/types/src/extraction/extraction-ner-function/index.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +729 -17
- package/dist/types/src/extraction/extraction.d.ts.map +1 -1
- package/dist/types/src/extraction/index.d.ts +0 -1
- package/dist/types/src/extraction/index.d.ts.map +1 -1
- package/dist/types/src/functions/services.d.ts +27 -8
- package/dist/types/src/functions/services.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/service/AgentService.d.ts +62 -0
- package/dist/types/src/service/AgentService.d.ts.map +1 -0
- package/dist/types/src/service/AgentService.test.d.ts +2 -0
- package/dist/types/src/service/AgentService.test.d.ts.map +1 -0
- package/dist/types/src/service/agent-process.d.ts +17 -0
- package/dist/types/src/service/agent-process.d.ts.map +1 -0
- package/dist/types/src/service/index.d.ts +3 -0
- package/dist/types/src/service/index.d.ts.map +1 -0
- package/dist/types/src/session/index.d.ts +1 -1
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/observer.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +64 -8
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +6 -2
- package/dist/types/src/session/toolkit.d.ts.map +1 -1
- package/dist/types/src/templates/system.d.ts +0 -1
- package/dist/types/src/templates/system.d.ts.map +1 -1
- package/dist/types/src/testing/layer.d.ts +23 -9
- package/dist/types/src/testing/layer.d.ts.map +1 -1
- package/dist/types/src/tracing.d.ts +264 -0
- package/dist/types/src/tracing.d.ts.map +1 -0
- package/dist/types/src/types/artifact.d.ts +5 -5
- package/dist/types/src/types/artifact.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -33
- package/src/conversation/context.test.ts +1 -1
- package/src/conversation/context.ts +84 -18
- package/src/conversation/conversation.ts +173 -32
- package/src/diff.test.ts +36 -5
- package/src/diff.ts +13 -4
- package/src/execution-graph.test.ts +2 -2
- package/src/execution-graph.ts +2 -2
- package/src/extraction/extraction-ner-function/index.ts +0 -1
- package/src/extraction/extraction.ts +38 -10
- package/src/extraction/index.ts +0 -1
- package/src/functions/services.ts +199 -50
- package/src/functions.conversations.json +1 -1
- package/src/functions.test.ts +28 -20
- package/src/index.ts +2 -0
- package/src/service/AgentService.conversations.json +1 -0
- package/src/service/AgentService.test.ts +169 -0
- package/src/service/AgentService.ts +171 -0
- package/src/service/agent-process.ts +360 -0
- package/src/service/index.ts +6 -0
- package/src/session/format.test.ts +23 -14
- package/src/session/index.ts +1 -2
- package/src/session/observer.ts +3 -2
- package/src/session/session.conversations.json +1 -1
- package/src/session/session.test.ts +52 -4
- package/src/session/session.ts +231 -117
- package/src/session/toolkit.ts +12 -7
- package/src/templates/instructions/format.tpl +0 -4
- package/src/templates/instructions/system.tpl +11 -2
- package/src/templates/system.ts +6 -5
- package/src/testing/layer.ts +209 -24
- package/src/tracing.ts +112 -0
- package/src/types/artifact.ts +17 -10
- package/dist/lib/browser/chunk-7CDDG6LL.mjs +0 -578
- package/dist/lib/browser/chunk-7CDDG6LL.mjs.map +0 -7
- package/dist/lib/browser/extraction/index.mjs.map +0 -7
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs +0 -32
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CC7ER6Q3.mjs +0 -579
- package/dist/lib/node-esm/chunk-CC7ER6Q3.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/extraction/index.mjs +0 -226
- package/dist/lib/node-esm/extraction/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -851
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -33
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +0 -4
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts +0 -2
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.test.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-llm-function/index.d.ts +0 -2
- package/dist/types/src/extraction/extraction-llm-function/index.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +0 -347
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +0 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts +0 -2
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.test.d.ts.map +0 -1
- package/dist/types/src/session/GenericToolkit.d.ts +0 -85
- package/dist/types/src/session/GenericToolkit.d.ts.map +0 -1
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +0 -84
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +0 -62
- package/src/extraction/extraction-llm-function/index.ts +0 -5
- package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +0 -116
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +0 -56
- package/src/session/GenericToolkit.ts +0 -131
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/assistant",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.bcb3aa67d6",
|
|
4
4
|
"description": "Natural language interface to DXOS",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,20 +16,17 @@
|
|
|
16
16
|
".": {
|
|
17
17
|
"source": "./src/index.ts",
|
|
18
18
|
"types": "./dist/types/src/index.d.ts",
|
|
19
|
-
"
|
|
20
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
19
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
21
20
|
},
|
|
22
21
|
"./extraction": {
|
|
23
22
|
"source": "./src/extraction/index.ts",
|
|
24
23
|
"types": "./dist/types/src/extraction/index.d.ts",
|
|
25
|
-
"
|
|
26
|
-
"node": "./dist/lib/node-esm/extraction/index.mjs"
|
|
24
|
+
"default": "./dist/lib/neutral/extraction/index.mjs"
|
|
27
25
|
},
|
|
28
26
|
"./testing": {
|
|
29
27
|
"source": "./src/testing/index.ts",
|
|
30
28
|
"types": "./dist/types/src/testing/index.d.ts",
|
|
31
|
-
"
|
|
32
|
-
"node": "./dist/lib/node-esm/testing/index.mjs"
|
|
29
|
+
"default": "./dist/lib/neutral/testing/index.mjs"
|
|
33
30
|
}
|
|
34
31
|
},
|
|
35
32
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -49,39 +46,42 @@
|
|
|
49
46
|
],
|
|
50
47
|
"dependencies": {
|
|
51
48
|
"@automerge/automerge": "3.2.3",
|
|
52
|
-
"@effect-atom/atom
|
|
53
|
-
"@effect/
|
|
54
|
-
"@effect/
|
|
55
|
-
"@effect/
|
|
49
|
+
"@effect-atom/atom": "^0.5.1",
|
|
50
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
51
|
+
"@effect/ai": "0.33.2",
|
|
52
|
+
"@effect/experimental": "0.58.0",
|
|
53
|
+
"@effect/platform": "0.94.4",
|
|
56
54
|
"@xenova/transformers": "^2.17.2",
|
|
57
55
|
"lodash.defaultsdeep": "^4.6.1",
|
|
58
|
-
"@dxos/ai": "0.8.4-main.
|
|
59
|
-
"@dxos/
|
|
60
|
-
"@dxos/
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/echo": "0.8.4-main.
|
|
63
|
-
"@dxos/debug": "0.8.4-main.
|
|
64
|
-
"@dxos/echo-
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/effect": "0.8.4-main.
|
|
67
|
-
"@dxos/functions": "0.8.4-main.
|
|
68
|
-
"@dxos/
|
|
69
|
-
"@dxos/functions
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/
|
|
56
|
+
"@dxos/ai": "0.8.4-main.bcb3aa67d6",
|
|
57
|
+
"@dxos/async": "0.8.4-main.bcb3aa67d6",
|
|
58
|
+
"@dxos/blueprints": "0.8.4-main.bcb3aa67d6",
|
|
59
|
+
"@dxos/echo": "0.8.4-main.bcb3aa67d6",
|
|
60
|
+
"@dxos/echo-db": "0.8.4-main.bcb3aa67d6",
|
|
61
|
+
"@dxos/debug": "0.8.4-main.bcb3aa67d6",
|
|
62
|
+
"@dxos/echo-protocol": "0.8.4-main.bcb3aa67d6",
|
|
63
|
+
"@dxos/errors": "0.8.4-main.bcb3aa67d6",
|
|
64
|
+
"@dxos/effect": "0.8.4-main.bcb3aa67d6",
|
|
65
|
+
"@dxos/functions-runtime": "0.8.4-main.bcb3aa67d6",
|
|
66
|
+
"@dxos/graph": "0.8.4-main.bcb3aa67d6",
|
|
67
|
+
"@dxos/functions": "0.8.4-main.bcb3aa67d6",
|
|
68
|
+
"@dxos/invariant": "0.8.4-main.bcb3aa67d6",
|
|
69
|
+
"@dxos/keys": "0.8.4-main.bcb3aa67d6",
|
|
70
|
+
"@dxos/node-std": "0.8.4-main.bcb3aa67d6",
|
|
71
|
+
"@dxos/mcp-client": "0.8.4-main.bcb3aa67d6",
|
|
72
|
+
"@dxos/log": "0.8.4-main.bcb3aa67d6",
|
|
73
|
+
"@dxos/operation": "0.8.4-main.bcb3aa67d6",
|
|
74
|
+
"@dxos/schema": "0.8.4-main.bcb3aa67d6",
|
|
75
|
+
"@dxos/context": "0.8.4-main.bcb3aa67d6",
|
|
76
|
+
"@dxos/types": "0.8.4-main.bcb3aa67d6",
|
|
77
|
+
"@dxos/util": "0.8.4-main.bcb3aa67d6"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
81
|
-
"effect": "3.
|
|
81
|
+
"effect": "3.20.0"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"effect": "3.
|
|
84
|
+
"effect": "3.20.0"
|
|
85
85
|
},
|
|
86
86
|
"publishConfig": {
|
|
87
87
|
"access": "public"
|
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
6
|
import * as EArray from 'effect/Array';
|
|
7
7
|
import * as Context from 'effect/Context';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
8
9
|
import * as Function from 'effect/Function';
|
|
9
10
|
import * as Schema from 'effect/Schema';
|
|
10
11
|
|
|
11
12
|
import { Blueprint } from '@dxos/blueprints';
|
|
12
13
|
import { Resource } from '@dxos/context';
|
|
13
|
-
import { DXN, Obj, Query,
|
|
14
|
+
import { DXN, Obj, Query, Ref, Type } from '@dxos/echo';
|
|
14
15
|
import { type Queue } from '@dxos/echo-db';
|
|
16
|
+
import { assertArgument } from '@dxos/invariant';
|
|
15
17
|
import { log } from '@dxos/log';
|
|
16
|
-
import { ComplexSet,
|
|
18
|
+
import { ComplexSet, isNonNullable } from '@dxos/util';
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Thread message that binds or unbinds contextual objects to a conversation.
|
|
@@ -21,17 +23,17 @@ import { ComplexSet, isTruthy } from '@dxos/util';
|
|
|
21
23
|
// TODO(burdon): Move to @dxos/schema ContentBlock?
|
|
22
24
|
export const ContextBinding = Schema.Struct({
|
|
23
25
|
blueprints: Schema.Struct({
|
|
24
|
-
added: Schema.Array(
|
|
25
|
-
removed: Schema.Array(
|
|
26
|
+
added: Schema.Array(Ref.Ref(Blueprint.Blueprint)),
|
|
27
|
+
removed: Schema.Array(Ref.Ref(Blueprint.Blueprint)),
|
|
26
28
|
}),
|
|
27
29
|
|
|
28
30
|
objects: Schema.Struct({
|
|
29
|
-
added: Schema.Array(
|
|
30
|
-
removed: Schema.Array(
|
|
31
|
+
added: Schema.Array(Ref.Ref(Obj.Unknown)),
|
|
32
|
+
removed: Schema.Array(Ref.Ref(Obj.Unknown)),
|
|
31
33
|
}),
|
|
32
34
|
}).pipe(
|
|
33
35
|
Type.object({
|
|
34
|
-
typename: 'dxos.
|
|
36
|
+
typename: 'org.dxos.type.contextBinding',
|
|
35
37
|
version: '0.1.0',
|
|
36
38
|
}),
|
|
37
39
|
);
|
|
@@ -74,6 +76,7 @@ export class AiContextBinder extends Resource {
|
|
|
74
76
|
|
|
75
77
|
constructor(options: AiContextBinderOptions) {
|
|
76
78
|
super();
|
|
79
|
+
assertArgument(options.queue, 'options.queue', 'Queue is required');
|
|
77
80
|
this._queue = options.queue;
|
|
78
81
|
this._registry = options.registry ?? Registry.make();
|
|
79
82
|
}
|
|
@@ -146,16 +149,30 @@ export class AiContextBinder extends Resource {
|
|
|
146
149
|
// Resolve references (loading them first if needed).
|
|
147
150
|
const currentBlueprints = this._registry.get(this._blueprints);
|
|
148
151
|
const currentObjects = this._registry.get(this._objects);
|
|
149
|
-
const
|
|
150
|
-
const
|
|
152
|
+
const resolvedBlueprints = await this._resolve(bindings.blueprints, currentBlueprints);
|
|
153
|
+
const resolvedObjects = await this._resolve(bindings.objects, currentObjects);
|
|
154
|
+
|
|
155
|
+
// Filter current state to only items still in the reduced binding set,
|
|
156
|
+
// then merge in newly resolved items. This ensures unbind events are respected.
|
|
157
|
+
const reducedBlueprintDxns = new ComplexSet<DXN>(
|
|
158
|
+
DXN.hash,
|
|
159
|
+
[...bindings.blueprints].map((ref) => ref.dxn),
|
|
160
|
+
);
|
|
161
|
+
const reducedObjectDxns = new ComplexSet<DXN>(
|
|
162
|
+
DXN.hash,
|
|
163
|
+
[...bindings.objects].map((ref) => ref.dxn),
|
|
164
|
+
);
|
|
165
|
+
const filteredBlueprints = currentBlueprints.filter((obj) => reducedBlueprintDxns.has(Obj.getDXN(obj)));
|
|
166
|
+
const filteredObjects = currentObjects.filter((obj) => reducedObjectDxns.has(Obj.getDXN(obj)));
|
|
167
|
+
const mergedBlueprints = this._mergeInto(filteredBlueprints, resolvedBlueprints);
|
|
168
|
+
const mergedObjects = this._mergeInto(filteredObjects, resolvedObjects);
|
|
151
169
|
|
|
152
|
-
|
|
153
|
-
this._registry.set(this.
|
|
154
|
-
this._registry.set(this._objects, newObjects);
|
|
170
|
+
this._registry.set(this._blueprints, mergedBlueprints);
|
|
171
|
+
this._registry.set(this._objects, mergedObjects);
|
|
155
172
|
|
|
156
173
|
log('updated bindings', {
|
|
157
|
-
blueprints:
|
|
158
|
-
objects:
|
|
174
|
+
blueprints: mergedBlueprints.length,
|
|
175
|
+
objects: mergedObjects.length,
|
|
159
176
|
});
|
|
160
177
|
}
|
|
161
178
|
|
|
@@ -199,6 +216,24 @@ export class AiContextBinder extends Resource {
|
|
|
199
216
|
return;
|
|
200
217
|
}
|
|
201
218
|
|
|
219
|
+
// Immediately update atom state so removals are reflected before the queue round-trips.
|
|
220
|
+
const removedBlueprintDxns = (blueprints ?? []).map((ref) => ref.dxn);
|
|
221
|
+
const removedObjectDxns = (objects ?? []).map((ref) => ref.dxn);
|
|
222
|
+
if (removedBlueprintDxns.length > 0) {
|
|
223
|
+
const current = this._registry.get(this._blueprints);
|
|
224
|
+
this._registry.set(
|
|
225
|
+
this._blueprints,
|
|
226
|
+
current.filter((obj) => !removedBlueprintDxns.some((dxn) => DXN.equalsEchoId(Obj.getDXN(obj), dxn))),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (removedObjectDxns.length > 0) {
|
|
230
|
+
const current = this._registry.get(this._objects);
|
|
231
|
+
this._registry.set(
|
|
232
|
+
this._objects,
|
|
233
|
+
current.filter((obj) => !removedObjectDxns.some((dxn) => DXN.equalsEchoId(Obj.getDXN(obj), dxn))),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
202
237
|
log('unbind', { blueprints: blueprints?.length, objects: objects?.length });
|
|
203
238
|
await this._queue.append([
|
|
204
239
|
Obj.make(ContextBinding, {
|
|
@@ -271,14 +306,30 @@ export class AiContextBinder extends Resource {
|
|
|
271
306
|
);
|
|
272
307
|
}
|
|
273
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Merge resolved items into the current set, adding only items not already present (by DXN).
|
|
311
|
+
*/
|
|
312
|
+
private _mergeInto<T extends Obj.Unknown>(current: T[], resolved: T[]): T[] {
|
|
313
|
+
const seen = new Set(current.map((obj) => Obj.getDXN(obj).toString()));
|
|
314
|
+
const merged = [...current];
|
|
315
|
+
for (const obj of resolved) {
|
|
316
|
+
const dxn = Obj.getDXN(obj).toString();
|
|
317
|
+
if (!seen.has(dxn)) {
|
|
318
|
+
seen.add(dxn);
|
|
319
|
+
merged.push(obj);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return merged;
|
|
323
|
+
}
|
|
324
|
+
|
|
274
325
|
/**
|
|
275
326
|
* Resolve references to objects, loading them first if needed and falling back to existing objects.
|
|
276
327
|
*/
|
|
277
|
-
private async _resolve<T>(refs: Iterable<Ref.Ref<T>>, current: T[]): Promise<T[]> {
|
|
328
|
+
private async _resolve<T extends Obj.Unknown>(refs: Iterable<Ref.Ref<T>>, current: T[]): Promise<T[]> {
|
|
278
329
|
const refArray = [...refs];
|
|
279
330
|
|
|
280
331
|
// Load all refs that need loading.
|
|
281
|
-
await Promise.all(refArray.map((ref) => ref.
|
|
332
|
+
await Promise.all(refArray.map((ref) => ref.tryLoad()));
|
|
282
333
|
|
|
283
334
|
return refArray
|
|
284
335
|
.map((ref) => {
|
|
@@ -291,7 +342,7 @@ export class AiContextBinder extends Resource {
|
|
|
291
342
|
// Fallback to existing object.
|
|
292
343
|
return target ?? current.find((obj) => Obj.getDXN(obj as any).toString() === ref.dxn.toString());
|
|
293
344
|
})
|
|
294
|
-
.filter(
|
|
345
|
+
.filter(isNonNullable);
|
|
295
346
|
}
|
|
296
347
|
}
|
|
297
348
|
|
|
@@ -300,4 +351,19 @@ export class AiContextService extends Context.Tag('@dxos/assistant/AiContextServ
|
|
|
300
351
|
{
|
|
301
352
|
binder: AiContextBinder;
|
|
302
353
|
}
|
|
303
|
-
>() {
|
|
354
|
+
>() {
|
|
355
|
+
static bindContext = ({ blueprints, objects }: BindingProps): Effect.Effect<void, never, AiContextService> =>
|
|
356
|
+
Effect.gen(function* () {
|
|
357
|
+
const { binder } = yield* AiContextService;
|
|
358
|
+
yield* Effect.promise(() => binder.bind({ blueprints, objects }));
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
static findObjects = <T extends Type.AnyEntity>(
|
|
362
|
+
type: T,
|
|
363
|
+
): Effect.Effect<Schema.Schema.Type<T>[], never, AiContextService> => {
|
|
364
|
+
return Effect.gen(function* () {
|
|
365
|
+
const { binder } = yield* AiContextService;
|
|
366
|
+
return binder.getObjects().filter(Obj.instanceOf(type));
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
}
|
|
@@ -2,16 +2,29 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import type * as Tool from '@effect/ai/Tool';
|
|
5
6
|
import type * as Toolkit from '@effect/ai/Toolkit';
|
|
6
7
|
import type { Registry } from '@effect-atom/atom-react';
|
|
8
|
+
import * as Array from 'effect/Array';
|
|
9
|
+
import * as Context from 'effect/Context';
|
|
7
10
|
import * as Effect from 'effect/Effect';
|
|
11
|
+
import * as Either from 'effect/Either';
|
|
12
|
+
import { pipe } from 'effect/Function';
|
|
13
|
+
import * as Layer from 'effect/Layer';
|
|
8
14
|
|
|
15
|
+
import { type ToolExecutionService, type ToolResolverService } from '@dxos/ai';
|
|
16
|
+
import { type GenericToolkit } from '@dxos/ai';
|
|
17
|
+
import { type Blueprint } from '@dxos/blueprints';
|
|
9
18
|
import { Resource } from '@dxos/context';
|
|
10
19
|
import { Obj } from '@dxos/echo';
|
|
11
20
|
import { type Queue } from '@dxos/echo-db';
|
|
21
|
+
import { acquireReleaseResource } from '@dxos/effect';
|
|
22
|
+
import { QueueService } from '@dxos/functions';
|
|
12
23
|
import { invariant } from '@dxos/invariant';
|
|
13
24
|
import { log } from '@dxos/log';
|
|
25
|
+
import { McpToolkit } from '@dxos/mcp-client';
|
|
14
26
|
import { Message } from '@dxos/types';
|
|
27
|
+
import * as Record from 'effect/Record';
|
|
15
28
|
|
|
16
29
|
import {
|
|
17
30
|
AiSession,
|
|
@@ -19,22 +32,29 @@ import {
|
|
|
19
32
|
type AiSessionRunRequirements,
|
|
20
33
|
type GenerationObserver,
|
|
21
34
|
createToolkit,
|
|
35
|
+
formatSystemPrompt,
|
|
22
36
|
} from '../session';
|
|
23
37
|
|
|
24
38
|
import { AiContextBinder, AiContextService, type ContextBinding } from './context';
|
|
25
39
|
|
|
26
|
-
export interface AiConversationRunProps {
|
|
40
|
+
export interface AiConversationRunProps<Tools extends Record<string, Tool.Any>> {
|
|
27
41
|
prompt: string;
|
|
28
42
|
system?: string;
|
|
29
43
|
observer?: GenerationObserver;
|
|
44
|
+
toolkit?: Toolkit.Toolkit<Tools>;
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
export type AiConversationOptions = {
|
|
33
48
|
queue: Queue<Message.Message | ContextBinding>;
|
|
34
|
-
toolkit?: Toolkit.Any;
|
|
35
49
|
registry?: Registry.Registry;
|
|
36
50
|
};
|
|
37
51
|
|
|
52
|
+
// TODO(dmaretskyi): Take from model characteristics. opus has 200k max tokens.
|
|
53
|
+
/**
|
|
54
|
+
* Summarization threshold in tokens.
|
|
55
|
+
*/
|
|
56
|
+
const SUMMARY_THRESHOLD = 80_000;
|
|
57
|
+
|
|
38
58
|
/**
|
|
39
59
|
* Durable conversation state (initiated by users and agents) backed by a Queue.
|
|
40
60
|
* Executes tools based on AI responses and supports cancellation of in-progress requests.
|
|
@@ -50,7 +70,6 @@ export class AiConversation extends Resource {
|
|
|
50
70
|
public constructor(options: AiConversationOptions) {
|
|
51
71
|
super();
|
|
52
72
|
this._queue = options.queue;
|
|
53
|
-
this._toolkit = options.toolkit;
|
|
54
73
|
invariant(this._queue);
|
|
55
74
|
this._binder = new AiContextBinder({ queue: this._queue, registry: options.registry });
|
|
56
75
|
}
|
|
@@ -76,50 +95,172 @@ export class AiConversation extends Resource {
|
|
|
76
95
|
return queueItems.filter(Obj.instanceOf(Message.Message));
|
|
77
96
|
}
|
|
78
97
|
|
|
98
|
+
getTools(): Effect.Effect<Record<string, Tool.Any>, never, ToolExecutionService | ToolResolverService> {
|
|
99
|
+
return Effect.gen(this, function* () {
|
|
100
|
+
const blueprints = this.context.getBlueprints();
|
|
101
|
+
const tookit = yield* createToolkit({ toolkit: this._toolkit, blueprints });
|
|
102
|
+
return tookit.tools;
|
|
103
|
+
}).pipe(Effect.orDie);
|
|
104
|
+
}
|
|
105
|
+
|
|
79
106
|
/**
|
|
80
107
|
* Creates a new cancelable request effect.
|
|
81
108
|
*/
|
|
82
|
-
public createRequest(
|
|
83
|
-
params: AiConversationRunProps
|
|
84
|
-
): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
// Create toolkit.
|
|
90
|
-
const blueprints = self.context.getBlueprints();
|
|
91
|
-
const toolkit = yield* createToolkit({
|
|
92
|
-
toolkit: self._toolkit,
|
|
93
|
-
blueprints,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Context objects.
|
|
97
|
-
const objects = self.context.getObjects();
|
|
109
|
+
public createRequest<Tools extends Record<string, Tool.Any> = {}>(
|
|
110
|
+
params: AiConversationRunProps<Tools>,
|
|
111
|
+
): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements | Tool.HandlersFor<Tools>> {
|
|
112
|
+
return Effect.gen(this, function* () {
|
|
113
|
+
const history = yield* Effect.promise(() => this.getHistory());
|
|
114
|
+
const blueprints = this.context.getBlueprints();
|
|
115
|
+
const objects = this.context.getObjects();
|
|
98
116
|
|
|
99
117
|
log('run', {
|
|
100
118
|
history: history.length,
|
|
101
119
|
blueprints: blueprints.length,
|
|
102
|
-
tools: Object.keys(toolkit.tools).length,
|
|
103
120
|
objects: objects.length,
|
|
104
121
|
});
|
|
105
122
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Effect.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
const session = new AiSession({
|
|
124
|
+
summarizationThreshold: SUMMARY_THRESHOLD,
|
|
125
|
+
observer: params.observer,
|
|
126
|
+
onOutput: (message) => Effect.promise(() => this._queue.append([message])),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
yield* session.begin({
|
|
130
|
+
history,
|
|
131
|
+
blueprints,
|
|
132
|
+
objects,
|
|
133
|
+
prompt: params.prompt,
|
|
134
|
+
system: params.system,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Turn loop: recompute toolkit and system prompt between turns to pick up dynamically enabled blueprints.
|
|
138
|
+
do {
|
|
139
|
+
const currentBlueprints = this.context.getBlueprints();
|
|
140
|
+
const mcps = yield* connectMcpServers(currentBlueprints);
|
|
141
|
+
const toolkit = yield* createToolkit({
|
|
142
|
+
toolkit: params.toolkit,
|
|
143
|
+
blueprints: currentBlueprints,
|
|
144
|
+
genericToolkits: mcps,
|
|
145
|
+
});
|
|
146
|
+
log('toolkit', { tools: Record.keys(toolkit.tools) });
|
|
147
|
+
const system = yield* formatSystemPrompt({
|
|
148
|
+
system: params.system,
|
|
149
|
+
blueprints: currentBlueprints,
|
|
150
|
+
objects: this.context.getObjects(),
|
|
151
|
+
}).pipe(Effect.orDie);
|
|
152
|
+
|
|
153
|
+
const { done } = yield* session.runAgentTurn({
|
|
154
|
+
system,
|
|
155
|
+
toolkit,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (done) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
yield* session.runTools({ toolkit });
|
|
163
|
+
} while (true);
|
|
113
164
|
|
|
114
165
|
log('result', {
|
|
115
|
-
messages:
|
|
166
|
+
messages: session.pending.length,
|
|
116
167
|
duration: session.duration,
|
|
117
168
|
toolCalls: session.toolCalls,
|
|
118
169
|
});
|
|
119
170
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
171
|
+
return [...session.pending];
|
|
172
|
+
}).pipe(
|
|
173
|
+
Effect.provide(
|
|
174
|
+
Layer.mergeAll(
|
|
175
|
+
Layer.succeed(AiContextService, {
|
|
176
|
+
binder: this.context,
|
|
177
|
+
}),
|
|
178
|
+
Layer.succeed(AiConversationService, this),
|
|
179
|
+
),
|
|
180
|
+
),
|
|
181
|
+
);
|
|
124
182
|
}
|
|
125
183
|
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Gives access to the ai conversation.
|
|
187
|
+
*/
|
|
188
|
+
export class AiConversationService extends Context.Tag('@dxos/assistant/AiConversationService')<
|
|
189
|
+
AiConversationService,
|
|
190
|
+
AiConversation
|
|
191
|
+
>() {
|
|
192
|
+
/**
|
|
193
|
+
* Create a new conversation layer from options.
|
|
194
|
+
*/
|
|
195
|
+
static layer = (options: AiConversationOptions): Layer.Layer<AiConversationService | AiContextService> =>
|
|
196
|
+
aiContextFromConversation.pipe(
|
|
197
|
+
Layer.provideMerge(
|
|
198
|
+
Layer.scoped(
|
|
199
|
+
AiConversationService,
|
|
200
|
+
Effect.gen(function* () {
|
|
201
|
+
const conversation = yield* acquireReleaseResource(() => new AiConversation(options));
|
|
202
|
+
return conversation;
|
|
203
|
+
}),
|
|
204
|
+
),
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Create a new conversation with a new queue.
|
|
210
|
+
*/
|
|
211
|
+
static layerNewQueue = (
|
|
212
|
+
options?: Omit<AiConversationOptions, 'queue'>,
|
|
213
|
+
): Layer.Layer<AiConversationService | AiContextService, never, QueueService> =>
|
|
214
|
+
Layer.unwrapScoped(
|
|
215
|
+
Effect.gen(function* () {
|
|
216
|
+
return AiConversationService.layer({
|
|
217
|
+
...options,
|
|
218
|
+
queue: yield* QueueService.createQueue<Message.Message | ContextBinding>(),
|
|
219
|
+
});
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Run a prompt in the current conversation.
|
|
225
|
+
*/
|
|
226
|
+
static run = <Tools extends Record<string, Tool.Any> = {}>(
|
|
227
|
+
params: AiConversationRunProps<Tools>,
|
|
228
|
+
): Effect.Effect<
|
|
229
|
+
Message.Message[],
|
|
230
|
+
AiSessionRunError,
|
|
231
|
+
AiSessionRunRequirements | Tool.HandlersFor<Tools> | AiConversationService
|
|
232
|
+
> =>
|
|
233
|
+
Effect.gen(function* () {
|
|
234
|
+
const conversation = yield* AiConversationService;
|
|
235
|
+
return yield* conversation.createRequest(params);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const aiContextFromConversation = Layer.effect(
|
|
240
|
+
AiContextService,
|
|
241
|
+
Effect.gen(function* () {
|
|
242
|
+
const conversation = yield* AiConversationService;
|
|
243
|
+
return {
|
|
244
|
+
binder: conversation.context,
|
|
245
|
+
};
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const connectMcpServers = (
|
|
250
|
+
blueprints: readonly Blueprint.Blueprint[],
|
|
251
|
+
): Effect.Effect<GenericToolkit.GenericToolkit[]> =>
|
|
252
|
+
pipe(
|
|
253
|
+
blueprints,
|
|
254
|
+
Array.flatMap((_) => _.mcpServers ?? []),
|
|
255
|
+
Effect.forEach(({ url, protocol }) =>
|
|
256
|
+
McpToolkit.make({ url, kind: protocol }).pipe(
|
|
257
|
+
// NOTE: Type-inference fails here without explicit void return.
|
|
258
|
+
Effect.tap((toolkit): void =>
|
|
259
|
+
log.info('Connected to MCP server', { url, tools: Object.keys(toolkit.toolkit.tools).length }),
|
|
260
|
+
),
|
|
261
|
+
Effect.tapDefect((error) => Effect.sync(() => log.warn('Failed to connect to MCP server', { error }))),
|
|
262
|
+
Effect.either,
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
Effect.map(Array.filterMap((_) => Either.getRight(_))),
|
|
266
|
+
);
|
package/src/diff.test.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { createDocAccessor } from '@dxos/echo-db';
|
|
8
8
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
@@ -12,10 +12,17 @@ import { trim } from '@dxos/util';
|
|
|
12
12
|
import { applyDiffs } from './diff';
|
|
13
13
|
|
|
14
14
|
describe('diff', () => {
|
|
15
|
+
let builder: EchoTestBuilder;
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
builder = await new EchoTestBuilder().open();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
await builder.close();
|
|
22
|
+
});
|
|
23
|
+
|
|
15
24
|
it('should apply diffs', async () => {
|
|
16
|
-
const
|
|
17
|
-
const { db, graph } = await builder.createDatabase();
|
|
18
|
-
await graph.schemaRegistry.register([Text.Text]);
|
|
25
|
+
const { db } = await builder.createDatabase({ types: [Text.Text] });
|
|
19
26
|
|
|
20
27
|
const document = trim`
|
|
21
28
|
# Hello World
|
|
@@ -24,7 +31,7 @@ describe('diff', () => {
|
|
|
24
31
|
But not this one.
|
|
25
32
|
`;
|
|
26
33
|
|
|
27
|
-
const text = db.add(Text.make(document));
|
|
34
|
+
const text = db.add(Text.make({ content: document }));
|
|
28
35
|
const accessor = createDocAccessor(text, ['content']);
|
|
29
36
|
const result = applyDiffs(accessor, [
|
|
30
37
|
`- There'z a typo in this sentence.`,
|
|
@@ -40,4 +47,28 @@ describe('diff', () => {
|
|
|
40
47
|
But not this one.
|
|
41
48
|
`);
|
|
42
49
|
});
|
|
50
|
+
|
|
51
|
+
it('should apply diffs that append content at the end', async () => {
|
|
52
|
+
const { db } = await builder.createDatabase({ types: [Text.Text] });
|
|
53
|
+
|
|
54
|
+
const document = '# Shopping list';
|
|
55
|
+
|
|
56
|
+
const text = db.add(Text.make({ content: document }));
|
|
57
|
+
const accessor = createDocAccessor(text, ['content']);
|
|
58
|
+
const result = applyDiffs(accessor, ['- # Shopping list', '+ # Shopping list\n- Milk']);
|
|
59
|
+
|
|
60
|
+
expect(result).toBe('# Shopping list\n- Milk');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should append text to an empty document', async () => {
|
|
64
|
+
const builder = new EchoTestBuilder();
|
|
65
|
+
const { db, graph } = await builder.createDatabase();
|
|
66
|
+
await graph.schemaRegistry.register([Text.Text]);
|
|
67
|
+
|
|
68
|
+
const text = db.add(Text.make({ content: '' }));
|
|
69
|
+
const accessor = createDocAccessor(text, ['content']);
|
|
70
|
+
const result = applyDiffs(accessor, ['+ # Shopping list']);
|
|
71
|
+
|
|
72
|
+
expect(result).toBe('# Shopping list');
|
|
73
|
+
});
|
|
43
74
|
});
|
package/src/diff.ts
CHANGED
|
@@ -47,9 +47,10 @@ export const reduceDiffs = (diffs: readonly string[]): Diff[] => {
|
|
|
47
47
|
}
|
|
48
48
|
return acc;
|
|
49
49
|
}
|
|
50
|
+
default: {
|
|
51
|
+
throw new Error(`Invalid diff format: each line must start with "- " or "+ ", got: ${JSON.stringify(diff)}`);
|
|
52
|
+
}
|
|
50
53
|
}
|
|
51
|
-
|
|
52
|
-
return acc;
|
|
53
54
|
}, []);
|
|
54
55
|
};
|
|
55
56
|
|
|
@@ -65,7 +66,11 @@ export const computeDiffsWithCursors = <T>(accessor: DocAccessor<T>, diffs: read
|
|
|
65
66
|
.filter(isNonNullable);
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
export const applyDiffs = <T>(
|
|
69
|
+
export const applyDiffs = <T>(
|
|
70
|
+
accessor: DocAccessor<T>,
|
|
71
|
+
diffs: readonly string[],
|
|
72
|
+
{ errorOnNotFound = false }: { errorOnNotFound?: boolean } = {},
|
|
73
|
+
): string => {
|
|
69
74
|
for (const diff of reduceDiffs(diffs)) {
|
|
70
75
|
accessor.handle.change((doc: Doc<T>) => {
|
|
71
76
|
const text = DocAccessor.getValue<string>(accessor);
|
|
@@ -75,7 +80,11 @@ export const applyDiffs = <T>(accessor: DocAccessor<T>, diffs: readonly string[]
|
|
|
75
80
|
// NOTE: This only replaces the first match.
|
|
76
81
|
A.splice(doc, accessor.path as A.Prop[], idx, diff.match.length, diff.replace);
|
|
77
82
|
} else {
|
|
78
|
-
|
|
83
|
+
if (errorOnNotFound) {
|
|
84
|
+
throw new Error(`Diff not found: ${diff.match}`);
|
|
85
|
+
} else {
|
|
86
|
+
log.warn('diff not found', diff);
|
|
87
|
+
}
|
|
79
88
|
}
|
|
80
89
|
});
|
|
81
90
|
}
|