@dxos/assistant 0.8.4-main.ae835ea → 0.8.4-main.bc674ce
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/browser/{chunk-4VIJTKPM.mjs → chunk-7CDDG6LL.mjs} +252 -28
- package/dist/lib/browser/chunk-7CDDG6LL.mjs.map +7 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/extraction/index.mjs +119 -166
- package/dist/lib/browser/extraction/index.mjs.map +4 -4
- package/dist/lib/browser/index.mjs +239 -289
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +32 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-AUUC7II4.mjs → chunk-CC7ER6Q3.mjs} +251 -28
- package/dist/lib/node-esm/chunk-CC7ER6Q3.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/extraction/index.mjs +119 -166
- package/dist/lib/node-esm/extraction/index.mjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +239 -289
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +33 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +88 -62
- package/dist/types/src/conversation/context.d.ts.map +1 -1
- package/dist/types/src/conversation/context.test.d.ts +2 -0
- package/dist/types/src/conversation/context.test.d.ts.map +1 -0
- package/dist/types/src/conversation/conversation.d.ts +16 -16
- package/dist/types/src/conversation/conversation.d.ts.map +1 -1
- package/dist/types/src/conversation/conversation.test.d.ts +2 -0
- package/dist/types/src/conversation/conversation.test.d.ts.map +1 -0
- package/dist/types/src/errors.d.ts +22 -30
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/execution-graph.d.ts +5 -2
- package/dist/types/src/execution-graph.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +254 -252
- package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +344 -309
- package/dist/types/src/extraction/extraction.d.ts.map +1 -1
- package/dist/types/src/extraction/quotes.d.ts +2 -2
- package/dist/types/src/extraction/quotes.d.ts.map +1 -1
- package/dist/types/src/functions/services.d.ts +4 -3
- package/dist/types/src/functions/services.d.ts.map +1 -1
- package/dist/types/src/functions.test.d.ts +2 -0
- package/dist/types/src/functions.test.d.ts.map +1 -0
- package/dist/types/src/session/GenericToolkit.d.ts +85 -0
- package/dist/types/src/session/GenericToolkit.d.ts.map +1 -0
- package/dist/types/src/session/artifact-diff.d.ts +1 -1
- package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
- package/dist/types/src/session/format.d.ts +4 -178
- package/dist/types/src/session/format.d.ts.map +1 -1
- package/dist/types/src/session/index.d.ts +1 -0
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/observer.d.ts +6 -5
- package/dist/types/src/session/observer.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +16 -11
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +5 -6
- package/dist/types/src/session/toolkit.d.ts.map +1 -1
- package/dist/types/src/session/version-pin.d.ts +3 -3
- package/dist/types/src/session/version-pin.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/layer.d.ts +21 -0
- package/dist/types/src/testing/layer.d.ts.map +1 -0
- package/dist/types/src/types/artifact.d.ts +14 -2
- package/dist/types/src/types/artifact.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +38 -27
- package/src/conversation/context.test.ts +46 -0
- package/src/conversation/context.ts +183 -66
- package/src/conversation/conversation.test.ts +65 -0
- package/src/conversation/conversation.ts +51 -54
- package/src/diff.test.ts +3 -3
- package/src/errors.ts +2 -2
- package/src/execution-graph.test.ts +45 -43
- package/src/execution-graph.ts +23 -14
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +2 -2
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +44 -54
- package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +14 -13
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +36 -44
- package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +10 -5
- package/src/extraction/extraction.ts +9 -8
- package/src/extraction/quotes.ts +4 -5
- package/src/functions/services.ts +41 -19
- package/src/functions.conversations.json +1 -0
- package/src/functions.test.ts +74 -0
- package/src/session/GenericToolkit.ts +131 -0
- package/src/session/artifact-diff.ts +1 -1
- package/src/session/format.test.ts +103 -39
- package/src/session/format.ts +17 -16
- package/src/session/index.ts +2 -0
- package/src/session/observer.ts +5 -3
- package/src/session/session.conversations.json +1 -1
- package/src/session/session.test.ts +7 -13
- package/src/session/session.ts +40 -19
- package/src/session/toolkit.ts +13 -22
- package/src/session/version-pin.ts +6 -6
- package/src/templates/{format.tpl → instructions/format.tpl} +1 -0
- package/src/templates/system.ts +2 -2
- package/src/testing/index.ts +5 -0
- package/src/testing/layer.ts +78 -0
- package/src/types/artifact.ts +33 -3
- package/dist/lib/browser/chunk-4VIJTKPM.mjs.map +0 -7
- package/dist/lib/browser/chunk-UB26N5UF.mjs +0 -89
- package/dist/lib/browser/chunk-UB26N5UF.mjs.map +0 -7
- package/dist/lib/browser/named-entity-recognition-QPYHML66.mjs +0 -13
- package/dist/lib/node-esm/chunk-AUUC7II4.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-BCFXTNSK.mjs +0 -91
- package/dist/lib/node-esm/chunk-BCFXTNSK.mjs.map +0 -7
- package/dist/lib/node-esm/named-entity-recognition-2HDF46YX.mjs +0 -14
- package/dist/types/src/summarization/summarization.test.d.ts +0 -2
- package/dist/types/src/summarization/summarization.test.d.ts.map +0 -1
- package/src/summarization/summarization.test.ts +0 -202
- /package/dist/lib/browser/{named-entity-recognition-QPYHML66.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
- /package/dist/lib/node-esm/{named-entity-recognition-2HDF46YX.mjs.map → chunk-HSLMI22Q.mjs.map} +0 -0
- /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
- /package/src/templates/{system.tpl → instructions/system.tpl} +0 -0
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/assistant",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.bc674ce",
|
|
4
4
|
"description": "Natural language interface to DXOS",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "info@dxos.org",
|
|
9
13
|
"sideEffects": true,
|
|
@@ -20,6 +24,12 @@
|
|
|
20
24
|
"types": "./dist/types/src/extraction/index.d.ts",
|
|
21
25
|
"browser": "./dist/lib/browser/extraction/index.mjs",
|
|
22
26
|
"node": "./dist/lib/node-esm/extraction/index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"./testing": {
|
|
29
|
+
"source": "./src/testing/index.ts",
|
|
30
|
+
"types": "./dist/types/src/testing/index.d.ts",
|
|
31
|
+
"browser": "./dist/lib/browser/testing/index.mjs",
|
|
32
|
+
"node": "./dist/lib/node-esm/testing/index.mjs"
|
|
23
33
|
}
|
|
24
34
|
},
|
|
25
35
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -38,39 +48,40 @@
|
|
|
38
48
|
"src"
|
|
39
49
|
],
|
|
40
50
|
"dependencies": {
|
|
41
|
-
"@automerge/automerge": "3.
|
|
42
|
-
"@effect/
|
|
43
|
-
"@effect/
|
|
44
|
-
"@effect/
|
|
45
|
-
"@
|
|
51
|
+
"@automerge/automerge": "3.2.3",
|
|
52
|
+
"@effect-atom/atom-react": "^0.4.6",
|
|
53
|
+
"@effect/ai": "0.32.1",
|
|
54
|
+
"@effect/experimental": "0.57.11",
|
|
55
|
+
"@effect/platform": "0.93.6",
|
|
46
56
|
"@xenova/transformers": "^2.17.2",
|
|
47
57
|
"lodash.defaultsdeep": "^4.6.1",
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/blueprints": "0.8.4-main.
|
|
50
|
-
"@dxos/context": "0.8.4-main.
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/effect": "0.8.4-main.
|
|
57
|
-
"@dxos/functions": "0.8.4-main.
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/
|
|
60
|
-
"@dxos/
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/log": "0.8.4-main.
|
|
64
|
-
"@dxos/schema": "0.8.4-main.
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/
|
|
58
|
+
"@dxos/ai": "0.8.4-main.bc674ce",
|
|
59
|
+
"@dxos/blueprints": "0.8.4-main.bc674ce",
|
|
60
|
+
"@dxos/context": "0.8.4-main.bc674ce",
|
|
61
|
+
"@dxos/async": "0.8.4-main.bc674ce",
|
|
62
|
+
"@dxos/echo": "0.8.4-main.bc674ce",
|
|
63
|
+
"@dxos/debug": "0.8.4-main.bc674ce",
|
|
64
|
+
"@dxos/echo-db": "0.8.4-main.bc674ce",
|
|
65
|
+
"@dxos/echo-protocol": "0.8.4-main.bc674ce",
|
|
66
|
+
"@dxos/effect": "0.8.4-main.bc674ce",
|
|
67
|
+
"@dxos/functions": "0.8.4-main.bc674ce",
|
|
68
|
+
"@dxos/errors": "0.8.4-main.bc674ce",
|
|
69
|
+
"@dxos/functions-runtime": "0.8.4-main.bc674ce",
|
|
70
|
+
"@dxos/graph": "0.8.4-main.bc674ce",
|
|
71
|
+
"@dxos/invariant": "0.8.4-main.bc674ce",
|
|
72
|
+
"@dxos/keys": "0.8.4-main.bc674ce",
|
|
73
|
+
"@dxos/log": "0.8.4-main.bc674ce",
|
|
74
|
+
"@dxos/schema": "0.8.4-main.bc674ce",
|
|
75
|
+
"@dxos/node-std": "0.8.4-main.bc674ce",
|
|
76
|
+
"@dxos/types": "0.8.4-main.bc674ce",
|
|
77
|
+
"@dxos/util": "0.8.4-main.bc674ce"
|
|
67
78
|
},
|
|
68
79
|
"devDependencies": {
|
|
69
80
|
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
70
|
-
"effect": "3.
|
|
81
|
+
"effect": "3.19.11"
|
|
71
82
|
},
|
|
72
83
|
"peerDependencies": {
|
|
73
|
-
"effect": "
|
|
84
|
+
"effect": "3.19.11"
|
|
74
85
|
},
|
|
75
86
|
"publishConfig": {
|
|
76
87
|
"access": "public"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import { describe, it } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { Obj, Ref, Type } from '@dxos/echo';
|
|
9
|
+
import { type Queue } from '@dxos/echo-db';
|
|
10
|
+
|
|
11
|
+
import { AiContextBinder } from './context';
|
|
12
|
+
|
|
13
|
+
// TODO(burdon): Create mock queue?
|
|
14
|
+
const createMockQueue = (): Queue =>
|
|
15
|
+
({
|
|
16
|
+
query: () =>
|
|
17
|
+
({
|
|
18
|
+
subscribe: () => ({ unsubscribe: () => {} }),
|
|
19
|
+
results: [],
|
|
20
|
+
}) as any,
|
|
21
|
+
append: async () => {
|
|
22
|
+
return [] as any;
|
|
23
|
+
},
|
|
24
|
+
}) as any;
|
|
25
|
+
|
|
26
|
+
describe('AiContextBinder', () => {
|
|
27
|
+
it('should handle bind with Ref', async () => {
|
|
28
|
+
const queue = createMockQueue();
|
|
29
|
+
const binder = new AiContextBinder({ queue });
|
|
30
|
+
|
|
31
|
+
const TestSchema = Schema.Struct({}).pipe(
|
|
32
|
+
Type.object({
|
|
33
|
+
typename: 'dxos.org/type/Example',
|
|
34
|
+
version: '0.1.0',
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const obj = Obj.make(TestSchema, {});
|
|
39
|
+
const ref = Ref.make(obj);
|
|
40
|
+
|
|
41
|
+
await binder.bind({
|
|
42
|
+
blueprints: [],
|
|
43
|
+
objects: [ref],
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import * as
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import * as EArray from 'effect/Array';
|
|
7
7
|
import * as Context from 'effect/Context';
|
|
8
8
|
import * as Function from 'effect/Function';
|
|
9
9
|
import * as Schema from 'effect/Schema';
|
|
10
10
|
|
|
11
11
|
import { Blueprint } from '@dxos/blueprints';
|
|
12
12
|
import { Resource } from '@dxos/context';
|
|
13
|
-
import { DXN,
|
|
13
|
+
import { DXN, Obj, Query, type Ref, Type } from '@dxos/echo';
|
|
14
14
|
import { type Queue } from '@dxos/echo-db';
|
|
15
|
-
import {
|
|
16
|
-
import { ComplexSet } from '@dxos/util';
|
|
15
|
+
import { log } from '@dxos/log';
|
|
16
|
+
import { ComplexSet, isTruthy } from '@dxos/util';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Thread message that binds or unbinds contextual objects to a conversation.
|
|
@@ -25,13 +25,12 @@ export const ContextBinding = Schema.Struct({
|
|
|
25
25
|
removed: Schema.Array(Type.Ref(Blueprint.Blueprint)),
|
|
26
26
|
}),
|
|
27
27
|
|
|
28
|
-
// TODO(burdon): Type.Expando => Type.Obj (or Obj.Any?)
|
|
29
28
|
objects: Schema.Struct({
|
|
30
|
-
added: Schema.Array(Type.Ref(Type.
|
|
31
|
-
removed: Schema.Array(Type.Ref(Type.
|
|
29
|
+
added: Schema.Array(Type.Ref(Type.Obj)),
|
|
30
|
+
removed: Schema.Array(Type.Ref(Type.Obj)),
|
|
32
31
|
}),
|
|
33
32
|
}).pipe(
|
|
34
|
-
Type.
|
|
33
|
+
Type.object({
|
|
35
34
|
typename: 'dxos.org/type/ContextBinding',
|
|
36
35
|
version: '0.1.0',
|
|
37
36
|
}),
|
|
@@ -41,141 +40,259 @@ export interface ContextBinding extends Schema.Schema.Type<typeof ContextBinding
|
|
|
41
40
|
|
|
42
41
|
export type BindingProps = Partial<{
|
|
43
42
|
blueprints: Ref.Ref<Blueprint.Blueprint>[];
|
|
44
|
-
objects: Ref.Ref<
|
|
43
|
+
objects: Ref.Ref<Obj.Unknown>[];
|
|
45
44
|
}>;
|
|
46
45
|
|
|
47
46
|
export class Bindings {
|
|
48
47
|
readonly blueprints = new ComplexSet<Ref.Ref<Blueprint.Blueprint>>((ref) => ref.dxn.toString());
|
|
48
|
+
|
|
49
49
|
// TODO(burdon): Some DXNs have the Space prefix so only compare the object ID.
|
|
50
|
-
readonly objects = new ComplexSet<Ref.Ref<
|
|
50
|
+
readonly objects = new ComplexSet<Ref.Ref<Obj.Unknown>>((ref) => ref.dxn.asEchoDXN()?.echoId);
|
|
51
51
|
|
|
52
52
|
toJSON() {
|
|
53
53
|
return {
|
|
54
|
-
blueprints:
|
|
55
|
-
objects:
|
|
54
|
+
blueprints: EArray.fromIterable(this.blueprints).map((ref) => ref.dxn.toString()),
|
|
55
|
+
objects: EArray.fromIterable(this.objects).map((ref) => ref.dxn.toString()),
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export type AiContextBinderOptions = {
|
|
61
|
+
queue: Queue;
|
|
62
|
+
registry?: Registry.Registry;
|
|
63
|
+
};
|
|
64
|
+
|
|
60
65
|
/**
|
|
61
66
|
* Manages bindings of blueprints and objects to a conversation.
|
|
62
67
|
*/
|
|
63
68
|
// TODO(burdon): Context should manage ephemeral state of bindings until prompt is issued?
|
|
64
69
|
export class AiContextBinder extends Resource {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
private _bindings?: ReadonlySignal<Bindings>;
|
|
70
|
-
private _blueprints?: ReadonlySignal<Ref.Ref<Blueprint.Blueprint>[]>;
|
|
71
|
-
private _objects?: ReadonlySignal<Ref.Ref<Type.Expando>[]>;
|
|
70
|
+
private readonly _blueprints = Atom.make<Blueprint.Blueprint[]>([]).pipe(Atom.keepAlive);
|
|
71
|
+
private readonly _objects = Atom.make<Obj.Unknown[]>([]).pipe(Atom.keepAlive);
|
|
72
|
+
private readonly _registry: Registry.Registry;
|
|
73
|
+
private readonly _queue: Queue;
|
|
72
74
|
|
|
73
|
-
constructor(
|
|
75
|
+
constructor(options: AiContextBinderOptions) {
|
|
74
76
|
super();
|
|
77
|
+
this._queue = options.queue;
|
|
78
|
+
this._registry = options.registry ?? Registry.make();
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
get blueprints() {
|
|
83
|
-
invariant(this._blueprints, 'AiContextBinder not open');
|
|
81
|
+
/**
|
|
82
|
+
* Returns the blueprints atom for subscription.
|
|
83
|
+
*/
|
|
84
|
+
get blueprints(): Atom.Atom<Blueprint.Blueprint[]> {
|
|
84
85
|
return this._blueprints;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Returns the objects atom for subscription.
|
|
90
|
+
*/
|
|
91
|
+
get objects(): Atom.Atom<Obj.Unknown[]> {
|
|
89
92
|
return this._objects;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.
|
|
97
|
-
this._blueprints = computed(() => [...this.bindings.value.blueprints]);
|
|
98
|
-
this._objects = computed(() => [...this.bindings.value.objects]);
|
|
95
|
+
/**
|
|
96
|
+
* Gets the current blueprints value.
|
|
97
|
+
*/
|
|
98
|
+
getBlueprints(): Blueprint.Blueprint[] {
|
|
99
|
+
return this._registry.get(this._blueprints);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Gets the current objects value.
|
|
104
|
+
*/
|
|
105
|
+
getObjects(): Obj.Unknown[] {
|
|
106
|
+
return this._registry.get(this._objects);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe to changes in blueprints.
|
|
111
|
+
*/
|
|
112
|
+
subscribeBlueprints(cb: (blueprints: Blueprint.Blueprint[]) => void): () => void {
|
|
113
|
+
return this._registry.subscribe(this._blueprints, () => cb(this._registry.get(this._blueprints)));
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
/**
|
|
108
|
-
*
|
|
117
|
+
* Subscribe to changes in objects.
|
|
109
118
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
subscribeObjects(cb: (objects: Obj.Unknown[]) => void): () => void {
|
|
120
|
+
return this._registry.subscribe(this._objects, () => cb(this._registry.get(this._objects)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected override async _open(): Promise<void> {
|
|
124
|
+
const query = this._queue.query(Query.type(ContextBinding));
|
|
125
|
+
|
|
126
|
+
// Process initial state before returning.
|
|
127
|
+
const initialResults = await query.run();
|
|
128
|
+
await this._updateBindings(initialResults);
|
|
129
|
+
|
|
130
|
+
// Subscribe to future changes.
|
|
131
|
+
this._ctx.onDispose(
|
|
132
|
+
query.subscribe(async () => {
|
|
133
|
+
await this._updateBindings(query.results);
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async _updateBindings(items: ContextBinding[]): Promise<void> {
|
|
139
|
+
// Skip update if no items - preserve existing state set by bind().
|
|
140
|
+
if (items.length === 0) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const bindings = this._reduce(items);
|
|
145
|
+
|
|
146
|
+
// Resolve references (loading them first if needed).
|
|
147
|
+
const currentBlueprints = this._registry.get(this._blueprints);
|
|
148
|
+
const currentObjects = this._registry.get(this._objects);
|
|
149
|
+
const newBlueprints = await this._resolve(bindings.blueprints, currentBlueprints);
|
|
150
|
+
const newObjects = await this._resolve(bindings.objects, currentObjects);
|
|
151
|
+
|
|
152
|
+
// Atomic updates - subscribers notified automatically.
|
|
153
|
+
this._registry.set(this._blueprints, newBlueprints);
|
|
154
|
+
this._registry.set(this._objects, newObjects);
|
|
155
|
+
|
|
156
|
+
log('updated bindings', {
|
|
157
|
+
blueprints: newBlueprints.length,
|
|
158
|
+
objects: newObjects.length,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected override async _close(): Promise<void> {
|
|
163
|
+
// Reset atoms to empty state.
|
|
164
|
+
this._registry.set(this._blueprints, []);
|
|
165
|
+
this._registry.set(this._objects, []);
|
|
113
166
|
}
|
|
114
167
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
props.blueprints?.filter((ref) => !this.blueprints.peek().find((b) => b.dxn.toString() === ref.dxn.toString())) ??
|
|
119
|
-
[];
|
|
120
|
-
const objects =
|
|
121
|
-
props.objects?.filter((ref) => !this.objects.peek().find((o) => o.dxn.toString() === ref.dxn.toString())) ?? [];
|
|
168
|
+
async bind({ blueprints, objects }: BindingProps): Promise<void> {
|
|
169
|
+
const currentBlueprints = this._registry.get(this._blueprints);
|
|
170
|
+
const currentObjects = this._registry.get(this._objects);
|
|
122
171
|
|
|
123
|
-
|
|
172
|
+
const { added: addedBlueprints, next: nextBlueprints } = this._processBindings(blueprints, currentBlueprints);
|
|
173
|
+
const { added: addedObjects, next: nextObjects } = this._processBindings(objects, currentObjects);
|
|
174
|
+
if (!addedBlueprints.length && !addedObjects.length) {
|
|
124
175
|
return;
|
|
125
176
|
}
|
|
126
177
|
|
|
178
|
+
// Atomic updates - subscribers notified automatically.
|
|
179
|
+
this._registry.set(this._blueprints, nextBlueprints);
|
|
180
|
+
this._registry.set(this._objects, nextObjects);
|
|
181
|
+
|
|
182
|
+
log('bind', { blueprints: addedBlueprints.length, objects: addedObjects.length });
|
|
127
183
|
await this._queue.append([
|
|
128
184
|
Obj.make(ContextBinding, {
|
|
129
185
|
blueprints: {
|
|
130
|
-
added:
|
|
186
|
+
added: addedBlueprints,
|
|
131
187
|
removed: [],
|
|
132
188
|
},
|
|
133
189
|
objects: {
|
|
134
|
-
added:
|
|
190
|
+
added: addedObjects,
|
|
135
191
|
removed: [],
|
|
136
192
|
},
|
|
137
193
|
}),
|
|
138
194
|
]);
|
|
139
195
|
}
|
|
140
196
|
|
|
141
|
-
async unbind(
|
|
142
|
-
if (!
|
|
197
|
+
async unbind({ blueprints, objects }: BindingProps): Promise<void> {
|
|
198
|
+
if (!blueprints?.length && !objects?.length) {
|
|
143
199
|
return;
|
|
144
200
|
}
|
|
145
201
|
|
|
202
|
+
log('unbind', { blueprints: blueprints?.length, objects: objects?.length });
|
|
146
203
|
await this._queue.append([
|
|
147
204
|
Obj.make(ContextBinding, {
|
|
148
205
|
blueprints: {
|
|
149
206
|
added: [],
|
|
150
|
-
removed:
|
|
207
|
+
removed: blueprints ?? [],
|
|
151
208
|
},
|
|
152
209
|
objects: {
|
|
153
210
|
added: [],
|
|
154
|
-
removed:
|
|
211
|
+
removed: objects ?? [],
|
|
155
212
|
},
|
|
156
213
|
}),
|
|
157
214
|
]);
|
|
158
215
|
}
|
|
159
216
|
|
|
160
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Process bindings to filter duplicates and determine next state.
|
|
219
|
+
*/
|
|
220
|
+
private _processBindings<T extends Obj.Unknown>(
|
|
221
|
+
refs: Ref.Ref<T>[] | undefined,
|
|
222
|
+
current: T[],
|
|
223
|
+
): { added: Ref.Ref<T>[]; next: T[] } {
|
|
224
|
+
const next = [...current];
|
|
225
|
+
const added: Ref.Ref<T>[] = [];
|
|
226
|
+
if (!refs?.length) {
|
|
227
|
+
return { added, next };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const seen = new Set(current.map((obj) => Obj.getDXN(obj).toString()));
|
|
231
|
+
for (const ref of refs) {
|
|
232
|
+
const dxn = ref.dxn.toString();
|
|
233
|
+
if (!seen.has(dxn)) {
|
|
234
|
+
seen.add(dxn);
|
|
235
|
+
added.push(ref);
|
|
236
|
+
|
|
237
|
+
// Only resolve target if available (has target or resolver).
|
|
238
|
+
if (ref.isAvailable) {
|
|
239
|
+
const target = ref.target;
|
|
240
|
+
if (target) {
|
|
241
|
+
next.push(target);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { added, next };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Reduce results into sets of blueprints and objects.
|
|
252
|
+
*/
|
|
253
|
+
private _reduce(items: ContextBinding[]): Bindings {
|
|
161
254
|
return Function.pipe(
|
|
162
255
|
items,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
256
|
+
EArray.reduce(new Bindings(), (context, { blueprints, objects }) => {
|
|
257
|
+
blueprints.added.forEach((ref) => context.blueprints.add(ref));
|
|
258
|
+
blueprints.removed.forEach((ref) => context.blueprints.delete(ref));
|
|
259
|
+
|
|
260
|
+
objects.added.forEach((ref) => context.objects.add(ref));
|
|
261
|
+
objects.removed.forEach((ref) => {
|
|
168
262
|
for (const obj of context.objects) {
|
|
169
263
|
if (DXN.equalsEchoId(obj.dxn, ref.dxn)) {
|
|
170
264
|
context.objects.delete(obj);
|
|
171
265
|
}
|
|
172
266
|
}
|
|
173
267
|
});
|
|
174
|
-
|
|
268
|
+
|
|
175
269
|
return context;
|
|
176
270
|
}),
|
|
177
271
|
);
|
|
178
272
|
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Resolve references to objects, loading them first if needed and falling back to existing objects.
|
|
276
|
+
*/
|
|
277
|
+
private async _resolve<T>(refs: Iterable<Ref.Ref<T>>, current: T[]): Promise<T[]> {
|
|
278
|
+
const refArray = [...refs];
|
|
279
|
+
|
|
280
|
+
// Load all refs that need loading.
|
|
281
|
+
await Promise.all(refArray.map((ref) => ref.load()));
|
|
282
|
+
|
|
283
|
+
return refArray
|
|
284
|
+
.map((ref) => {
|
|
285
|
+
let target: T | undefined;
|
|
286
|
+
// Only resolve target if available (has target or resolver).
|
|
287
|
+
if (ref.isAvailable) {
|
|
288
|
+
target = ref.target;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Fallback to existing object.
|
|
292
|
+
return target ?? current.find((obj) => Obj.getDXN(obj as any).toString() === ref.dxn.toString());
|
|
293
|
+
})
|
|
294
|
+
.filter(isTruthy);
|
|
295
|
+
}
|
|
179
296
|
}
|
|
180
297
|
|
|
181
298
|
export class AiContextService extends Context.Tag('@dxos/assistant/AiContextService')<
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { it } from '@effect/vitest';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import { afterEach, beforeEach, describe, expect } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { Blueprint } from '@dxos/blueprints';
|
|
10
|
+
import { Obj, Ref } from '@dxos/echo';
|
|
11
|
+
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
12
|
+
|
|
13
|
+
import { ContextBinding } from './context';
|
|
14
|
+
import { AiConversation } from './conversation';
|
|
15
|
+
|
|
16
|
+
describe('AiConversation', () => {
|
|
17
|
+
let builder: EchoTestBuilder;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
builder = await new EchoTestBuilder().open();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await builder.close();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it.effect('loads blueprints on open', () =>
|
|
28
|
+
Effect.gen(function* () {
|
|
29
|
+
const peer = yield* Effect.promise(() => builder.createPeer({ types: [Blueprint.Blueprint, ContextBinding] }));
|
|
30
|
+
const db = yield* Effect.promise(() => peer.createDatabase());
|
|
31
|
+
const queues = peer.client.constructQueueFactory(db.spaceId);
|
|
32
|
+
const queue = queues.create<ContextBinding>();
|
|
33
|
+
|
|
34
|
+
// Create blueprint.
|
|
35
|
+
const blueprint = db.add(
|
|
36
|
+
Blueprint.make({
|
|
37
|
+
key: 'example.com/blueprint/Test',
|
|
38
|
+
name: 'Test Blueprint',
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Add blueprint to queue via binding.
|
|
43
|
+
yield* Effect.promise(() =>
|
|
44
|
+
queue.append([
|
|
45
|
+
Obj.make(ContextBinding, {
|
|
46
|
+
blueprints: {
|
|
47
|
+
added: [Ref.make(blueprint)],
|
|
48
|
+
removed: [],
|
|
49
|
+
},
|
|
50
|
+
objects: {
|
|
51
|
+
added: [],
|
|
52
|
+
removed: [],
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
]),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const conversation = new AiConversation({ queue });
|
|
59
|
+
yield* Effect.promise(() => conversation.open());
|
|
60
|
+
|
|
61
|
+
expect(conversation.context.getBlueprints()).toHaveLength(1);
|
|
62
|
+
expect(conversation.context.getObjects()).toHaveLength(0);
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
});
|