@dxos/assistant 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/lib/neutral/chunk-UAWIXKF6.mjs +1875 -0
- package/dist/lib/neutral/chunk-UAWIXKF6.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/extraction/index.mjs +29 -35
- package/dist/lib/neutral/extraction/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +551 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/testing/index.mjs +114 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +116 -64
- 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 +45 -18
- 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/diff.d.ts +3 -1
- package/dist/types/src/diff.d.ts.map +1 -1
- package/dist/types/src/errors.d.ts +12 -16
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/execution-graph.d.ts +2 -2
- package/dist/types/src/execution-graph.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 +1059 -313
- 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/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 +28 -9
- 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/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/service/AgentService.d.ts +61 -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/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 +3 -3
- package/dist/types/src/session/format.d.ts.map +1 -1
- 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 +4 -3
- package/dist/types/src/session/observer.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +72 -11
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +9 -6
- package/dist/types/src/session/toolkit.d.ts.map +1 -1
- package/dist/types/src/session/version-pin.d.ts +2 -2
- package/dist/types/src/session/version-pin.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/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/layer.d.ts +35 -0
- package/dist/types/src/testing/layer.d.ts.map +1 -0
- 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 +13 -5
- package/dist/types/src/types/artifact.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +42 -33
- package/src/conversation/context.test.ts +52 -0
- package/src/conversation/context.ts +307 -90
- package/src/conversation/conversation.test.ts +58 -0
- package/src/conversation/conversation.ts +209 -62
- package/src/diff.test.ts +36 -5
- package/src/diff.ts +13 -4
- package/src/errors.ts +2 -2
- package/src/execution-graph.test.ts +3 -3
- package/src/execution-graph.ts +6 -6
- package/src/extraction/extraction-ner-function/index.ts +0 -1
- package/src/extraction/extraction.ts +40 -12
- package/src/extraction/index.ts +0 -1
- package/src/extraction/quotes.ts +4 -5
- package/src/functions/services.ts +217 -50
- package/src/functions.conversations.json +1 -0
- package/src/functions.test.ts +82 -0
- 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 +165 -0
- package/src/service/agent-process.ts +359 -0
- package/src/service/index.ts +6 -0
- package/src/session/artifact-diff.ts +1 -1
- package/src/session/format.test.ts +110 -39
- package/src/session/format.ts +12 -14
- package/src/session/index.ts +1 -2
- package/src/session/observer.ts +4 -1
- package/src/session/session.conversations.json +1 -1
- package/src/session/session.test.ts +58 -17
- package/src/session/session.ts +249 -113
- package/src/session/toolkit.ts +21 -25
- package/src/session/version-pin.ts +4 -3
- 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/index.ts +5 -0
- package/src/testing/layer.ts +258 -0
- package/src/tracing.ts +113 -0
- package/src/types/artifact.ts +31 -13
- package/dist/lib/browser/extraction/index.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -1290
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- 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 -1291
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- 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 -345
- 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 -84
- 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 -130
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { it } from '@effect/vitest';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import { describe, expect } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { Blueprint } from '@dxos/blueprints';
|
|
10
|
+
import { Database, Feed, Obj, Ref } from '@dxos/echo';
|
|
11
|
+
import { TestDatabaseLayer } from '@dxos/functions-runtime/testing';
|
|
12
|
+
|
|
13
|
+
import { ContextBinding } from './context';
|
|
14
|
+
import { AiConversation } from './conversation';
|
|
15
|
+
|
|
16
|
+
describe('AiConversation', () => {
|
|
17
|
+
const TestLayer = TestDatabaseLayer({
|
|
18
|
+
types: [Blueprint.Blueprint, ContextBinding],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it.effect('loads blueprints on open', () =>
|
|
22
|
+
Effect.gen(function* () {
|
|
23
|
+
const { db } = yield* Database.Service;
|
|
24
|
+
|
|
25
|
+
// Create feed.
|
|
26
|
+
const feed = db.add(Feed.make());
|
|
27
|
+
|
|
28
|
+
// Create blueprint.
|
|
29
|
+
const blueprint = db.add(
|
|
30
|
+
Blueprint.make({
|
|
31
|
+
key: 'example.com/blueprint/Test',
|
|
32
|
+
name: 'Test Blueprint',
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Add blueprint to feed via binding.
|
|
37
|
+
yield* Feed.append(feed, [
|
|
38
|
+
Obj.make(ContextBinding, {
|
|
39
|
+
blueprints: {
|
|
40
|
+
added: [Ref.make(blueprint)],
|
|
41
|
+
removed: [],
|
|
42
|
+
},
|
|
43
|
+
objects: {
|
|
44
|
+
added: [],
|
|
45
|
+
removed: [],
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const runtime = yield* Effect.runtime<Feed.FeedService>();
|
|
51
|
+
const conversation = new AiConversation({ feed, runtime });
|
|
52
|
+
yield* Effect.promise(() => conversation.open());
|
|
53
|
+
|
|
54
|
+
expect(conversation.context.getBlueprints()).toHaveLength(1);
|
|
55
|
+
expect(conversation.context.getObjects()).toHaveLength(0);
|
|
56
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import type { Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import type * as Tool from '@effect/ai/Tool';
|
|
5
7
|
import type * as Toolkit from '@effect/ai/Toolkit';
|
|
6
8
|
import * as Array from 'effect/Array';
|
|
9
|
+
import * as Context from 'effect/Context';
|
|
7
10
|
import * as Effect from 'effect/Effect';
|
|
8
|
-
import * as
|
|
11
|
+
import * as Either from 'effect/Either';
|
|
12
|
+
import { pipe } from 'effect/Function';
|
|
13
|
+
import * as Layer from 'effect/Layer';
|
|
14
|
+
import * as Record from 'effect/Record';
|
|
15
|
+
import * as Runtime from 'effect/Runtime';
|
|
9
16
|
|
|
17
|
+
import { type ToolExecutionService, type ToolResolverService } from '@dxos/ai';
|
|
18
|
+
import { type GenericToolkit } from '@dxos/ai';
|
|
19
|
+
import { type Blueprint } from '@dxos/blueprints';
|
|
10
20
|
import { Resource } from '@dxos/context';
|
|
11
|
-
import { Obj } from '@dxos/echo';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
21
|
+
import { Database, Feed, Filter, Obj } from '@dxos/echo';
|
|
22
|
+
import { acquireReleaseResource } from '@dxos/effect';
|
|
23
|
+
import { invariant } from '@dxos/invariant';
|
|
14
24
|
import { log } from '@dxos/log';
|
|
25
|
+
import { McpToolkit } from '@dxos/mcp-client';
|
|
15
26
|
import { Message } from '@dxos/types';
|
|
16
27
|
|
|
17
28
|
import {
|
|
@@ -20,53 +31,61 @@ import {
|
|
|
20
31
|
type AiSessionRunRequirements,
|
|
21
32
|
type GenerationObserver,
|
|
22
33
|
createToolkit,
|
|
34
|
+
formatSystemPrompt,
|
|
23
35
|
} from '../session';
|
|
36
|
+
import { AiContextBinder, AiContextService } from './context';
|
|
24
37
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export interface AiConversationRunParams {
|
|
38
|
+
export interface AiConversationRunProps<Tools extends Record<string, Tool.Any>> {
|
|
28
39
|
prompt: string;
|
|
29
40
|
system?: string;
|
|
30
41
|
observer?: GenerationObserver;
|
|
42
|
+
toolkit?: Toolkit.Toolkit<Tools>;
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
export type AiConversationOptions = {
|
|
46
|
+
feed: Feed.Feed;
|
|
47
|
+
runtime: Runtime.Runtime<Feed.FeedService>;
|
|
48
|
+
registry?: Registry.Registry;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// TODO(dmaretskyi): Take from model characteristics. opus has 200k max tokens.
|
|
52
|
+
/**
|
|
53
|
+
* Summarization threshold in tokens.
|
|
54
|
+
*/
|
|
55
|
+
const SUMMARY_THRESHOLD = 80_000;
|
|
56
|
+
|
|
33
57
|
/**
|
|
34
|
-
* Durable conversation state (initiated by users and agents) backed by a
|
|
58
|
+
* Durable conversation state (initiated by users and agents) backed by a Feed.
|
|
35
59
|
* Executes tools based on AI responses and supports cancellation of in-progress requests.
|
|
36
60
|
*/
|
|
37
61
|
export class AiConversation extends Resource {
|
|
38
62
|
/**
|
|
39
|
-
*
|
|
40
|
-
*/
|
|
41
|
-
private readonly _queue: Queue<Message.Message | ContextBinding>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Blueprints bound to the conversation.
|
|
45
|
-
*/
|
|
46
|
-
private readonly _context: AiContextBinder;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Toolkit from the current session request.
|
|
63
|
+
* Blueprints and objects bound to the conversation.
|
|
50
64
|
*/
|
|
51
|
-
private
|
|
65
|
+
private readonly _binder: AiContextBinder;
|
|
66
|
+
private readonly _feed: Feed.Feed;
|
|
67
|
+
private readonly _runtime: Runtime.Runtime<Feed.FeedService>;
|
|
68
|
+
private readonly _toolkit?: Toolkit.Any;
|
|
52
69
|
|
|
53
|
-
public constructor(
|
|
70
|
+
public constructor(options: AiConversationOptions) {
|
|
54
71
|
super();
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
72
|
+
this._feed = options.feed;
|
|
73
|
+
this._runtime = options.runtime;
|
|
74
|
+
invariant(this._feed);
|
|
75
|
+
invariant(this._runtime);
|
|
76
|
+
this._binder = new AiContextBinder({ feed: this._feed, runtime: this._runtime, registry: options.registry });
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
protected override async _open(): Promise<void> {
|
|
60
|
-
|
|
61
|
-
await this._context.open();
|
|
80
|
+
await this._binder.open(this._ctx);
|
|
62
81
|
}
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
public get feed() {
|
|
84
|
+
return this._feed;
|
|
66
85
|
}
|
|
67
86
|
|
|
68
87
|
public get context() {
|
|
69
|
-
return this.
|
|
88
|
+
return this._binder;
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
public get toolkit() {
|
|
@@ -74,55 +93,183 @@ export class AiConversation extends Resource {
|
|
|
74
93
|
}
|
|
75
94
|
|
|
76
95
|
public async getHistory(): Promise<Message.Message[]> {
|
|
77
|
-
const
|
|
78
|
-
|
|
96
|
+
const queryResult = await Runtime.runPromise(this._runtime)(Feed.query(this._feed, Filter.type(Message.Message)));
|
|
97
|
+
const items = await queryResult.run();
|
|
98
|
+
return items.filter(Obj.instanceOf(Message.Message));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getTools(): Effect.Effect<Record<string, Tool.Any>, never, ToolExecutionService | ToolResolverService> {
|
|
102
|
+
return Effect.gen(this, function* () {
|
|
103
|
+
const blueprints = this.context.getBlueprints();
|
|
104
|
+
const tookit = yield* createToolkit({ toolkit: this._toolkit, blueprints });
|
|
105
|
+
return tookit.tools;
|
|
106
|
+
}).pipe(Effect.orDie);
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
/**
|
|
82
110
|
* Creates a new cancelable request effect.
|
|
83
111
|
*/
|
|
84
|
-
createRequest(
|
|
85
|
-
params:
|
|
86
|
-
): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements
|
|
112
|
+
public createRequest<Tools extends Record<string, Tool.Any> = {}>(
|
|
113
|
+
params: AiConversationRunProps<Tools>,
|
|
114
|
+
): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements | Tool.HandlersFor<Tools>> {
|
|
87
115
|
return Effect.gen(this, function* () {
|
|
88
|
-
const session = new AiSession();
|
|
89
116
|
const history = yield* Effect.promise(() => this.getHistory());
|
|
117
|
+
const blueprints = this.context.getBlueprints();
|
|
118
|
+
const objects = this.context.getObjects();
|
|
90
119
|
|
|
91
|
-
// Get context objects.
|
|
92
|
-
const context = yield* Effect.promise(() => this.context.query());
|
|
93
|
-
const blueprints = yield* Effect.forEach(context.blueprints.values(), DatabaseService.loadOption).pipe(
|
|
94
|
-
Effect.map(Array.filter(Option.isSome)),
|
|
95
|
-
Effect.map(Array.map((option) => option.value)),
|
|
96
|
-
);
|
|
97
|
-
const objects = yield* Effect.forEach(context.objects.values(), DatabaseService.loadOption).pipe(
|
|
98
|
-
Effect.map(Array.filter(Option.isSome)),
|
|
99
|
-
Effect.map(Array.map((option) => option.value)),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// Create toolkit.
|
|
103
|
-
const toolkit = yield* createToolkit({ blueprints });
|
|
104
|
-
this._toolkit = toolkit;
|
|
105
|
-
|
|
106
|
-
const start = Date.now();
|
|
107
120
|
log('run', {
|
|
108
121
|
history: history.length,
|
|
109
122
|
blueprints: blueprints.length,
|
|
110
123
|
objects: objects.length,
|
|
111
|
-
tools: this._toolkit?.tools.length ?? 0,
|
|
112
124
|
});
|
|
113
125
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
126
|
+
const session = new AiSession({
|
|
127
|
+
summarizationThreshold: SUMMARY_THRESHOLD,
|
|
128
|
+
observer: params.observer,
|
|
129
|
+
onOutput: (message) =>
|
|
130
|
+
Effect.promise(() => Runtime.runPromise(this._runtime)(Feed.append(this._feed, [message]))),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
yield* session.begin({
|
|
134
|
+
history,
|
|
135
|
+
blueprints,
|
|
136
|
+
objects,
|
|
137
|
+
prompt: params.prompt,
|
|
138
|
+
system: params.system,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Turn loop: recompute toolkit and system prompt between turns to pick up dynamically enabled blueprints.
|
|
142
|
+
do {
|
|
143
|
+
yield* Effect.promise(() => this.context.sync());
|
|
144
|
+
const currentBlueprints = this.context.getBlueprints();
|
|
145
|
+
const mcps = yield* connectMcpServers(currentBlueprints);
|
|
146
|
+
const toolkit = yield* createToolkit({
|
|
147
|
+
toolkit: params.toolkit,
|
|
148
|
+
blueprints: currentBlueprints,
|
|
149
|
+
genericToolkits: mcps,
|
|
150
|
+
});
|
|
151
|
+
log('toolkit', { tools: Record.keys(toolkit.tools) });
|
|
152
|
+
const system = yield* formatSystemPrompt({
|
|
153
|
+
system: params.system,
|
|
154
|
+
blueprints: currentBlueprints,
|
|
155
|
+
objects: this.context.getObjects(),
|
|
156
|
+
}).pipe(Effect.orDie);
|
|
120
157
|
|
|
121
|
-
|
|
158
|
+
const { done } = yield* session.runAgentTurn({
|
|
159
|
+
system,
|
|
160
|
+
toolkit,
|
|
161
|
+
});
|
|
122
162
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
163
|
+
if (done) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
yield* session.runTools({ toolkit });
|
|
168
|
+
} while (true);
|
|
169
|
+
|
|
170
|
+
log('result', {
|
|
171
|
+
messages: session.pending.length,
|
|
172
|
+
duration: session.duration,
|
|
173
|
+
toolCalls: session.toolCalls,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return [...session.pending];
|
|
177
|
+
}).pipe(
|
|
178
|
+
Effect.provide(
|
|
179
|
+
Layer.mergeAll(
|
|
180
|
+
Layer.succeed(AiContextService, {
|
|
181
|
+
binder: this.context,
|
|
182
|
+
}),
|
|
183
|
+
Layer.succeed(AiConversationService, this),
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
);
|
|
127
187
|
}
|
|
128
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Gives access to the ai conversation.
|
|
192
|
+
*/
|
|
193
|
+
export class AiConversationService extends Context.Tag('@dxos/assistant/AiConversationService')<
|
|
194
|
+
AiConversationService,
|
|
195
|
+
AiConversation
|
|
196
|
+
>() {
|
|
197
|
+
/**
|
|
198
|
+
* Create a new conversation layer from options.
|
|
199
|
+
*/
|
|
200
|
+
static layer = (options: AiConversationOptions): Layer.Layer<AiConversationService | AiContextService> =>
|
|
201
|
+
aiContextFromConversation.pipe(
|
|
202
|
+
Layer.provideMerge(
|
|
203
|
+
Layer.scoped(
|
|
204
|
+
AiConversationService,
|
|
205
|
+
Effect.gen(function* () {
|
|
206
|
+
const conversation = yield* acquireReleaseResource(() => new AiConversation(options));
|
|
207
|
+
return conversation;
|
|
208
|
+
}),
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create a new conversation with a new feed.
|
|
215
|
+
*/
|
|
216
|
+
static layerNewFeed = (
|
|
217
|
+
options?: Omit<AiConversationOptions, 'feed' | 'runtime'>,
|
|
218
|
+
): Layer.Layer<AiConversationService | AiContextService, never, Database.Service | Feed.FeedService> =>
|
|
219
|
+
Layer.unwrapScoped(
|
|
220
|
+
Effect.gen(function* () {
|
|
221
|
+
const feed = Feed.make();
|
|
222
|
+
yield* Database.add(feed);
|
|
223
|
+
const runtime = yield* Effect.runtime<Feed.FeedService>();
|
|
224
|
+
return AiConversationService.layer({
|
|
225
|
+
...options,
|
|
226
|
+
feed,
|
|
227
|
+
runtime,
|
|
228
|
+
});
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Run a prompt in the current conversation.
|
|
234
|
+
*/
|
|
235
|
+
static run = <Tools extends Record<string, Tool.Any> = {}>(
|
|
236
|
+
params: AiConversationRunProps<Tools>,
|
|
237
|
+
): Effect.Effect<
|
|
238
|
+
Message.Message[],
|
|
239
|
+
AiSessionRunError,
|
|
240
|
+
AiSessionRunRequirements | Tool.HandlersFor<Tools> | AiConversationService
|
|
241
|
+
> =>
|
|
242
|
+
Effect.gen(function* () {
|
|
243
|
+
const conversation = yield* AiConversationService;
|
|
244
|
+
return yield* conversation.createRequest(params);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const aiContextFromConversation = Layer.effect(
|
|
249
|
+
AiContextService,
|
|
250
|
+
Effect.gen(function* () {
|
|
251
|
+
const conversation = yield* AiConversationService;
|
|
252
|
+
return {
|
|
253
|
+
binder: conversation.context,
|
|
254
|
+
};
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const connectMcpServers = (
|
|
259
|
+
blueprints: readonly Blueprint.Blueprint[],
|
|
260
|
+
): Effect.Effect<GenericToolkit.GenericToolkit[]> =>
|
|
261
|
+
pipe(
|
|
262
|
+
blueprints,
|
|
263
|
+
Array.flatMap((_) => _.mcpServers ?? []),
|
|
264
|
+
Effect.forEach(({ url, protocol }) =>
|
|
265
|
+
McpToolkit.make({ url, kind: protocol }).pipe(
|
|
266
|
+
// NOTE: Type-inference fails here without explicit void return.
|
|
267
|
+
Effect.tap((toolkit): void =>
|
|
268
|
+
log.info('Connected to MCP server', { url, tools: Object.keys(toolkit.toolkit.tools).length }),
|
|
269
|
+
),
|
|
270
|
+
Effect.tapDefect((error) => Effect.sync(() => log.warn('Failed to connect to MCP server', { error }))),
|
|
271
|
+
Effect.either,
|
|
272
|
+
),
|
|
273
|
+
),
|
|
274
|
+
Effect.map(Array.filterMap((_) => Either.getRight(_))),
|
|
275
|
+
);
|
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
|
-
graph.schemaRegistry.addSchema([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
|
}
|
package/src/errors.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { BaseError } from '@dxos/errors';
|
|
|
7
7
|
/**
|
|
8
8
|
* Generic error from AI model.
|
|
9
9
|
*/
|
|
10
|
-
export class AiModelError extends BaseError.extend('
|
|
10
|
+
export class AiModelError extends BaseError.extend('AiModelError', 'AI model error') {}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Generic error for AI agent execution.
|
|
14
14
|
*/
|
|
15
|
-
export class AiAssistantError extends BaseError.extend('
|
|
15
|
+
export class AiAssistantError extends BaseError.extend('AiAssistantError', 'AI assistant error') {}
|
|
@@ -6,7 +6,7 @@ import { describe, expect, it } from '@effect/vitest';
|
|
|
6
6
|
|
|
7
7
|
import { AgentStatus } from '@dxos/ai';
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { ObjectId } from '@dxos/
|
|
9
|
+
import { ObjectId } from '@dxos/keys';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { type ContentBlock, Message } from '@dxos/types';
|
|
12
12
|
|
|
@@ -71,7 +71,7 @@ describe('ExecutionGraph', () => {
|
|
|
71
71
|
blocks: [
|
|
72
72
|
{
|
|
73
73
|
_tag: 'text',
|
|
74
|
-
text: '{"id":"01K6AXZKK79H4E54RKP1YZDARK","@type":"dxn:type:dxos.
|
|
74
|
+
text: '{"id":"01K6AXZKK79H4E54RKP1YZDARK","@type":"dxn:type:org.dxos.type.organization:0.1.0","@meta":{"keys":[]},"name":"Blue Yard","website":"https://blueyard.com"}',
|
|
75
75
|
},
|
|
76
76
|
],
|
|
77
77
|
properties: {},
|
|
@@ -619,7 +619,7 @@ describe('ExecutionGraph', () => {
|
|
|
619
619
|
{ _tag: 'reasoning', reasoningText: 'I need to think about this' },
|
|
620
620
|
{ _tag: 'toolCall', toolCallId: 'tool1', name: 'analyze', input: '{}', providerExecuted: false },
|
|
621
621
|
{
|
|
622
|
-
_tag: '
|
|
622
|
+
_tag: 'stats',
|
|
623
623
|
message: 'OK',
|
|
624
624
|
duration: 1000,
|
|
625
625
|
toolCalls: 1,
|
package/src/execution-graph.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { AgentStatus } from '@dxos/ai';
|
|
6
|
-
import { Obj, type Ref } from '@dxos/echo';
|
|
6
|
+
import { type Entity, Obj, type Ref } from '@dxos/echo';
|
|
7
7
|
import { MESSAGE_PROPERTY_TOOL_CALL_ID } from '@dxos/functions';
|
|
8
8
|
import { type ObjectId } from '@dxos/keys';
|
|
9
9
|
import { LogLevel } from '@dxos/log';
|
|
@@ -65,7 +65,7 @@ export class ExecutionGraph {
|
|
|
65
65
|
/**
|
|
66
66
|
* Adds events to the graph.
|
|
67
67
|
*/
|
|
68
|
-
addEvents(events:
|
|
68
|
+
addEvents(events: Entity.Unknown[]) {
|
|
69
69
|
const sortedEvents = this.sortObjectsByCreated(events);
|
|
70
70
|
for (const event of sortedEvents) {
|
|
71
71
|
if (Obj.instanceOf(Message.Message, event)) {
|
|
@@ -412,7 +412,7 @@ const createBlockCommit = (
|
|
|
412
412
|
message: block.error ? 'Tool error: ' + block.error : 'Tool call succeeded',
|
|
413
413
|
} satisfies Commit;
|
|
414
414
|
|
|
415
|
-
case '
|
|
415
|
+
case 'stats':
|
|
416
416
|
return {
|
|
417
417
|
id: getGenericBlockId(message.id, idx),
|
|
418
418
|
branch,
|
|
@@ -420,7 +420,7 @@ const createBlockCommit = (
|
|
|
420
420
|
timestamp,
|
|
421
421
|
icon: IconType.ROCKET,
|
|
422
422
|
level: LogLevel.INFO,
|
|
423
|
-
message: ContentBlock.
|
|
423
|
+
message: ContentBlock.createStatsMessage(block),
|
|
424
424
|
} satisfies Commit;
|
|
425
425
|
|
|
426
426
|
case 'status':
|
|
@@ -496,7 +496,7 @@ const getParentId = (message: Message.Message) => {
|
|
|
496
496
|
}
|
|
497
497
|
};
|
|
498
498
|
|
|
499
|
-
const stringifyRef = (ref: Ref.
|
|
499
|
+
const stringifyRef = (ref: Ref.Unknown) => {
|
|
500
500
|
if (ref.target) {
|
|
501
501
|
return stringifyObject(ref.target);
|
|
502
502
|
}
|
|
@@ -504,6 +504,6 @@ const stringifyRef = (ref: Ref.Any) => {
|
|
|
504
504
|
return ref.dxn.asEchoDXN()?.echoId ?? ref.dxn.asQueueDXN()?.objectId ?? '';
|
|
505
505
|
};
|
|
506
506
|
|
|
507
|
-
const stringifyObject = (obj: Obj.
|
|
507
|
+
const stringifyObject = (obj: Obj.Unknown) => {
|
|
508
508
|
return Obj.getLabel(obj) ?? Obj.getTypename(obj) ?? obj.id;
|
|
509
509
|
};
|