@dxos/assistant 0.8.2 → 0.8.3-main.672df60
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/index.mjs +141 -62
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +150 -68
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +141 -62
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/blueprint/blueprint.d.ts +25 -53
- package/dist/types/src/blueprint/blueprint.d.ts.map +1 -1
- package/dist/types/src/blueprint/logger.d.ts +10 -1
- package/dist/types/src/blueprint/logger.d.ts.map +1 -1
- package/dist/types/src/blueprint/machine.d.ts +19 -10
- package/dist/types/src/blueprint/machine.d.ts.map +1 -1
- package/dist/types/src/research/graph.d.ts +5 -4
- package/dist/types/src/research/graph.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/blueprint/blueprint.test.ts +17 -10
- package/src/blueprint/blueprint.ts +19 -43
- package/src/blueprint/logger.ts +61 -18
- package/src/blueprint/machine.ts +17 -11
- package/src/research/graph.ts +62 -14
package/src/blueprint/logger.ts
CHANGED
|
@@ -2,41 +2,84 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type ReadonlySignal, signal } from '@preact/signals-core';
|
|
5
6
|
import chalk from 'chalk';
|
|
6
7
|
|
|
7
8
|
import { ConsolePrinter } from '@dxos/ai';
|
|
9
|
+
import { type CleanupFn, combine } from '@dxos/async';
|
|
8
10
|
|
|
9
11
|
import { type Blueprint } from './blueprint';
|
|
10
12
|
import { type BlueprintMachine, type BlueprintMachineState } from './machine';
|
|
11
13
|
|
|
12
14
|
/* eslint-disable no-console */
|
|
13
15
|
|
|
14
|
-
// Force chalk colors on for tests
|
|
16
|
+
// Force chalk colors on for tests.
|
|
15
17
|
chalk.level = 2;
|
|
16
18
|
|
|
17
|
-
export
|
|
19
|
+
export class Logger {
|
|
20
|
+
private _messages = signal<string[]>([]);
|
|
21
|
+
|
|
22
|
+
get messages(): ReadonlySignal<string[]> {
|
|
23
|
+
return this._messages;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
clear() {
|
|
27
|
+
this._messages.value = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
log(message: string) {
|
|
31
|
+
this._messages.value = [...this._messages.value, message];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const setLogger = (machine: BlueprintMachine, logger: Logger): CleanupFn => {
|
|
36
|
+
return combine(
|
|
37
|
+
machine.begin.on(() => {
|
|
38
|
+
logger.log('Starting...');
|
|
39
|
+
}),
|
|
40
|
+
machine.stepStart.on((step) => {
|
|
41
|
+
const index = machine.blueprint.steps.indexOf(step);
|
|
42
|
+
logger.log(`Step ${index + 1} of ${machine.blueprint.steps.length}`);
|
|
43
|
+
logger.log(`Instructions: ${step.instructions}`);
|
|
44
|
+
}),
|
|
45
|
+
machine.stepComplete.on((step) => {
|
|
46
|
+
const trace = machine.state.trace.find((t) => t.stepId === step.id);
|
|
47
|
+
logger.log(`Complete: ${trace?.comment}`);
|
|
48
|
+
}),
|
|
49
|
+
machine.end.on(() => {
|
|
50
|
+
logger.log('OK');
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const setConsolePrinter = (machine: BlueprintMachine, verbose = false): CleanupFn => {
|
|
18
56
|
const printer = new ConsolePrinter();
|
|
19
|
-
|
|
57
|
+
return combine(
|
|
58
|
+
machine.begin.on(() => printTrace(machine.blueprint, machine.state)),
|
|
20
59
|
machine.stepStart.on((step) =>
|
|
21
60
|
console.log(
|
|
22
61
|
`\n${chalk.magenta(`${chalk.bold(`STEP ${machine.blueprint.steps.indexOf(step) + 1} of ${machine.blueprint.steps.length}:`)} ${step.instructions}`)}\n`,
|
|
23
62
|
),
|
|
24
|
-
)
|
|
25
|
-
machine.
|
|
26
|
-
machine.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
63
|
+
),
|
|
64
|
+
machine.stepComplete.on(() => printTrace(machine.blueprint, machine.state)),
|
|
65
|
+
machine.end.on(() => console.log('DONE')),
|
|
66
|
+
verbose
|
|
67
|
+
? [
|
|
68
|
+
machine.message.on((msg) => printer.printMessage(msg)),
|
|
69
|
+
machine.block.on((block) => printer.printContentBlock(block)),
|
|
70
|
+
]
|
|
71
|
+
: [],
|
|
72
|
+
);
|
|
30
73
|
};
|
|
31
74
|
|
|
32
|
-
const
|
|
33
|
-
console.log('\n==============================================\n');
|
|
34
|
-
console.log(chalk.bold('\nThe Blueprint:'));
|
|
75
|
+
const BREAK_LINE = `\n${'_'.repeat(80)}\n`;
|
|
35
76
|
|
|
36
|
-
|
|
77
|
+
const printTrace = (blueprint: Blueprint, state: BlueprintMachineState) => {
|
|
78
|
+
console.group(chalk.bold('\nBlueprint'));
|
|
79
|
+
blueprint.steps.forEach((step) => {
|
|
37
80
|
const traceStep = state.trace.find((t) => t.stepId === step.id);
|
|
38
81
|
|
|
39
|
-
let color = chalk.gray; // Not executed
|
|
82
|
+
let color = chalk.gray; // Not executed.
|
|
40
83
|
let bullet = '○';
|
|
41
84
|
if (traceStep) {
|
|
42
85
|
switch (traceStep.status) {
|
|
@@ -56,11 +99,11 @@ const printTrace = (blueprint: Blueprint, state: BlueprintMachineState) => {
|
|
|
56
99
|
}
|
|
57
100
|
|
|
58
101
|
console.log(color(`\n${bullet} ${step.instructions}`));
|
|
59
|
-
|
|
60
102
|
if (traceStep?.comment) {
|
|
61
|
-
console.log(chalk.white(`
|
|
103
|
+
console.log(chalk.white(` ↳ ${traceStep.comment}`));
|
|
62
104
|
}
|
|
63
105
|
});
|
|
64
|
-
|
|
65
|
-
console.
|
|
106
|
+
|
|
107
|
+
console.groupEnd();
|
|
108
|
+
console.log(BREAK_LINE);
|
|
66
109
|
};
|
package/src/blueprint/machine.ts
CHANGED
|
@@ -6,21 +6,28 @@ import { Match, Schema } from 'effect';
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
createTool,
|
|
9
|
-
type ExecutableTool,
|
|
10
|
-
Message,
|
|
11
|
-
ToolResult,
|
|
12
9
|
type AIServiceClient,
|
|
10
|
+
Message,
|
|
13
11
|
type MessageContentBlock,
|
|
12
|
+
ToolResult,
|
|
14
13
|
type ToolUseContentBlock,
|
|
14
|
+
type ToolRegistry,
|
|
15
15
|
} from '@dxos/ai';
|
|
16
16
|
import { Event } from '@dxos/async';
|
|
17
17
|
import { create } from '@dxos/echo-schema';
|
|
18
18
|
import { type ObjectId } from '@dxos/keys';
|
|
19
19
|
import { log } from '@dxos/log';
|
|
20
|
+
import { isNonNullable } from '@dxos/util';
|
|
20
21
|
|
|
21
22
|
import type { Blueprint, BlueprintStep } from './blueprint';
|
|
22
23
|
import { AISession } from '../session';
|
|
23
24
|
|
|
25
|
+
export type BlueprintTraceStep = {
|
|
26
|
+
status: 'done' | 'bailed' | 'skipped';
|
|
27
|
+
stepId: ObjectId;
|
|
28
|
+
comment: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
24
31
|
export type BlueprintMachineState = {
|
|
25
32
|
history: Message[];
|
|
26
33
|
trace: BlueprintTraceStep[];
|
|
@@ -33,12 +40,6 @@ const INITIAL_STATE: BlueprintMachineState = {
|
|
|
33
40
|
trace: [],
|
|
34
41
|
};
|
|
35
42
|
|
|
36
|
-
export type BlueprintTraceStep = {
|
|
37
|
-
status: 'done' | 'bailed' | 'skipped';
|
|
38
|
-
stepId: ObjectId;
|
|
39
|
-
comment: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
43
|
type ExecutionOptions = {
|
|
43
44
|
aiService: AIServiceClient;
|
|
44
45
|
|
|
@@ -61,7 +62,10 @@ export class BlueprintMachine {
|
|
|
61
62
|
|
|
62
63
|
state: BlueprintMachineState = structuredClone(INITIAL_STATE);
|
|
63
64
|
|
|
64
|
-
constructor(
|
|
65
|
+
constructor(
|
|
66
|
+
readonly registry: ToolRegistry,
|
|
67
|
+
readonly blueprint: Blueprint,
|
|
68
|
+
) {}
|
|
65
69
|
|
|
66
70
|
async runToCompletion(options: ExecutionOptions): Promise<void> {
|
|
67
71
|
log.info('runToCompletion', options);
|
|
@@ -132,6 +136,8 @@ export class BlueprintMachine {
|
|
|
132
136
|
: [];
|
|
133
137
|
inputMessages.forEach((message) => this.message.emit(message));
|
|
134
138
|
|
|
139
|
+
// TODO(wittjosiah): Warn if tool is not found.
|
|
140
|
+
const tools = nextStep.tools.map((tool) => this.registry.get(tool)).filter(isNonNullable);
|
|
135
141
|
const messages = await session.run({
|
|
136
142
|
systemPrompt: `
|
|
137
143
|
You are a smart Rule-Following Agent.
|
|
@@ -144,7 +150,7 @@ export class BlueprintMachine {
|
|
|
144
150
|
The Rule-Following Agent precisely follows the instructions.
|
|
145
151
|
`,
|
|
146
152
|
history: [...state.history, ...inputMessages],
|
|
147
|
-
tools: [...
|
|
153
|
+
tools: [...tools, report],
|
|
148
154
|
artifacts: [],
|
|
149
155
|
client: options.aiService,
|
|
150
156
|
prompt: nextStep.instructions,
|
package/src/research/graph.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import { Schema, identity, Option, SchemaAST } from 'effect';
|
|
6
6
|
|
|
7
7
|
import { createTool, ToolResult } from '@dxos/ai';
|
|
8
|
-
import {
|
|
8
|
+
import type { Obj, Relation } from '@dxos/echo';
|
|
9
|
+
import { type EchoDatabase, type Queue } from '@dxos/echo-db';
|
|
9
10
|
import { isEncodedReference } from '@dxos/echo-protocol';
|
|
10
11
|
import {
|
|
11
12
|
EntityKind,
|
|
@@ -20,14 +21,16 @@ import {
|
|
|
20
21
|
ObjectId,
|
|
21
22
|
Query,
|
|
22
23
|
ReferenceAnnotationId,
|
|
24
|
+
type BaseObject,
|
|
25
|
+
RelationSourceDXNId,
|
|
26
|
+
RelationTargetDXNId,
|
|
23
27
|
RelationSourceId,
|
|
24
28
|
RelationTargetId,
|
|
25
|
-
type BaseObject,
|
|
26
29
|
} from '@dxos/echo-schema';
|
|
27
30
|
import { mapAst } from '@dxos/effect';
|
|
28
31
|
import { DXN } from '@dxos/keys';
|
|
29
32
|
import { log } from '@dxos/log';
|
|
30
|
-
import { deepMapValues } from '@dxos/util';
|
|
33
|
+
import { deepMapValues, isNonNullable } from '@dxos/util';
|
|
31
34
|
|
|
32
35
|
// TODO(burdon): Unify with the graph schema.
|
|
33
36
|
export const Subgraph = Schema.Struct({
|
|
@@ -95,7 +98,7 @@ const isSchemaAddressableByDxn = (schema: Schema.Schema.AnyNoContext, dxn: DXN):
|
|
|
95
98
|
/**
|
|
96
99
|
* Perform vector search in the local database.
|
|
97
100
|
*/
|
|
98
|
-
export const createLocalSearchTool = (db: EchoDatabase) => {
|
|
101
|
+
export const createLocalSearchTool = (db: EchoDatabase, queue?: Queue) => {
|
|
99
102
|
return createTool('search', {
|
|
100
103
|
name: 'local_search',
|
|
101
104
|
description: 'Search the local database for information using a vector index',
|
|
@@ -106,9 +109,16 @@ export const createLocalSearchTool = (db: EchoDatabase) => {
|
|
|
106
109
|
}),
|
|
107
110
|
execute: async ({ query }) => {
|
|
108
111
|
const { objects } = await db.query(Query.select(Filter.text(query, { type: 'vector' }))).run();
|
|
112
|
+
const results = [...objects];
|
|
113
|
+
if (queue) {
|
|
114
|
+
const queueObjects = await queue.queryObjects();
|
|
115
|
+
// TODO(dmaretskyi): Text search on the queue.
|
|
116
|
+
results.push(...queueObjects);
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
return ToolResult.Success(`
|
|
110
120
|
<local_context>
|
|
111
|
-
${JSON.stringify(
|
|
121
|
+
${JSON.stringify(results, null, 2)}
|
|
112
122
|
</local_context>
|
|
113
123
|
`);
|
|
114
124
|
},
|
|
@@ -141,6 +151,7 @@ export const sanitizeObjects = async (
|
|
|
141
151
|
types: Schema.Schema.AnyNoContext[],
|
|
142
152
|
data: Record<string, readonly unknown[]>,
|
|
143
153
|
db: EchoDatabase,
|
|
154
|
+
queue?: Queue,
|
|
144
155
|
): Promise<BaseObject[]> => {
|
|
145
156
|
const entries = types
|
|
146
157
|
.map(
|
|
@@ -153,8 +164,8 @@ export const sanitizeObjects = async (
|
|
|
153
164
|
.flat();
|
|
154
165
|
|
|
155
166
|
const idMap = new Map<string, string>();
|
|
156
|
-
|
|
157
167
|
const existingIds = new Set<ObjectId>();
|
|
168
|
+
const enitties = new Map<ObjectId, Obj.Any | Relation.Any>();
|
|
158
169
|
|
|
159
170
|
const resolveId = (id: string): DXN | undefined => {
|
|
160
171
|
if (ObjectId.isValid(id)) {
|
|
@@ -210,23 +221,58 @@ export const sanitizeObjects = async (
|
|
|
210
221
|
}
|
|
211
222
|
delete data.source;
|
|
212
223
|
delete data.target;
|
|
213
|
-
data[
|
|
214
|
-
data[
|
|
224
|
+
data[RelationSourceDXNId] = sourceDxn;
|
|
225
|
+
data[RelationTargetDXNId] = targetDxn;
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
return
|
|
228
|
+
return {
|
|
229
|
+
data,
|
|
230
|
+
schema: entry.schema,
|
|
231
|
+
};
|
|
218
232
|
})
|
|
219
|
-
.filter((object) => !existingIds.has(object.id)); // TODO(dmaretskyi): This dissallows updating existing objects.
|
|
233
|
+
.filter((object) => !existingIds.has(object.data.id)); // TODO(dmaretskyi): This dissallows updating existing objects.
|
|
234
|
+
|
|
235
|
+
// TODO(dmaretskyi): Use ref resolver.
|
|
236
|
+
const { objects: dbObjects } = await db.query(Query.select(Filter.ids(...existingIds))).run();
|
|
237
|
+
const queueObjects = (await queue?.getObjectsById([...existingIds])) ?? [];
|
|
238
|
+
const objects = [...dbObjects, ...queueObjects].filter(isNonNullable);
|
|
220
239
|
|
|
221
|
-
const { objects } = await db.query(Query.select(Filter.ids(...existingIds))).run();
|
|
222
240
|
// TODO(dmaretskyi): Returns everything if IDs are empty!
|
|
223
|
-
log.info('objects', {
|
|
241
|
+
log.info('objects', { dbObjects, queueObjects, existingIds });
|
|
224
242
|
const missing = Array.from(existingIds).filter((id) => !objects.some((object) => object.id === id));
|
|
225
243
|
if (missing.length > 0) {
|
|
226
244
|
throw new Error(`Object IDs do not point to existing objects: ${missing.join(', ')}`);
|
|
227
245
|
}
|
|
228
246
|
|
|
229
|
-
return res
|
|
247
|
+
return res.flatMap(({ data, schema }) => {
|
|
248
|
+
let skip = false;
|
|
249
|
+
if (RelationSourceDXNId in data) {
|
|
250
|
+
const id = (data[RelationSourceDXNId] as DXN).asEchoDXN()?.echoId;
|
|
251
|
+
const obj = objects.find((object) => object.id === id) ?? enitties.get(id!);
|
|
252
|
+
if (obj) {
|
|
253
|
+
delete data[RelationSourceDXNId];
|
|
254
|
+
data[RelationSourceId] = obj;
|
|
255
|
+
} else {
|
|
256
|
+
skip = true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (RelationTargetDXNId in data) {
|
|
260
|
+
const id = (data[RelationTargetDXNId] as DXN).asEchoDXN()?.echoId;
|
|
261
|
+
const obj = objects.find((object) => object.id === id) ?? enitties.get(id!);
|
|
262
|
+
if (obj) {
|
|
263
|
+
delete data[RelationTargetDXNId];
|
|
264
|
+
data[RelationTargetId] = obj;
|
|
265
|
+
} else {
|
|
266
|
+
skip = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!skip) {
|
|
270
|
+
const obj = create(schema, data);
|
|
271
|
+
enitties.set(obj.id, obj);
|
|
272
|
+
return [obj];
|
|
273
|
+
}
|
|
274
|
+
return [];
|
|
275
|
+
});
|
|
230
276
|
};
|
|
231
277
|
|
|
232
278
|
const SoftRef = Schema.Struct({
|
|
@@ -272,10 +318,12 @@ const preprocessSchema = (schema: Schema.Schema.AnyNoContext) => {
|
|
|
272
318
|
|
|
273
319
|
export const createGraphWriterTool = ({
|
|
274
320
|
db,
|
|
321
|
+
queue,
|
|
275
322
|
schemaTypes,
|
|
276
323
|
onDone = async (x) => x,
|
|
277
324
|
}: {
|
|
278
325
|
db: EchoDatabase;
|
|
326
|
+
queue?: Queue;
|
|
279
327
|
schemaTypes: Schema.Schema.AnyNoContext[];
|
|
280
328
|
onDone?: (data: AnyEchoObject[]) => Promise<any>;
|
|
281
329
|
}) => {
|
|
@@ -284,7 +332,7 @@ export const createGraphWriterTool = ({
|
|
|
284
332
|
description: 'Write to the local graph database',
|
|
285
333
|
schema: createExtractionSchema(schemaTypes),
|
|
286
334
|
execute: async (input) => {
|
|
287
|
-
const data = await sanitizeObjects(schemaTypes, input as any, db);
|
|
335
|
+
const data = await sanitizeObjects(schemaTypes, input as any, db, queue);
|
|
288
336
|
return ToolResult.Success(await onDone(data as AnyEchoObject[]));
|
|
289
337
|
},
|
|
290
338
|
});
|