@dxos/assistant 0.8.4-main.a4bbb77 → 0.8.4-main.abd8ff62ef
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/extraction/index.mjs +202 -0
- package/dist/lib/neutral/extraction/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +1731 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/types/src/conversation/context.d.ts +120 -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/index.d.ts +1 -1
- package/dist/types/src/conversation/index.d.ts.map +1 -1
- package/dist/types/src/conversation/session.d.ts +78 -0
- package/dist/types/src/conversation/session.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 +32 -40
- 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-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-ner-function/named-entity-recognition.d.ts.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +1066 -307
- 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 +3 -3
- package/dist/types/src/extraction/quotes.d.ts.map +1 -1
- package/dist/types/src/functions/services.d.ts +15 -16
- package/dist/types/src/functions/services.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/session/artifact-diff.d.ts +2 -2
- package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
- package/dist/types/src/session/error-handling.d.ts +2 -2
- package/dist/types/src/session/error-handling.d.ts.map +1 -1
- package/dist/types/src/session/format.d.ts +8 -177
- package/dist/types/src/session/format.d.ts.map +1 -1
- package/dist/types/src/session/index.d.ts +2 -1
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/observer.d.ts +7 -6
- package/dist/types/src/session/observer.d.ts.map +1 -1
- package/dist/types/src/session/request.d.ts +104 -0
- package/dist/types/src/session/request.d.ts.map +1 -0
- package/dist/types/src/session/toolkit.d.ts +11 -9
- package/dist/types/src/session/toolkit.d.ts.map +1 -1
- package/dist/types/src/session/version-pin.d.ts +4 -4
- package/dist/types/src/session/version-pin.d.ts.map +1 -1
- package/dist/types/src/templates/system.d.ts +1 -2
- package/dist/types/src/templates/system.d.ts.map +1 -1
- package/dist/types/src/tracing.d.ts +233 -0
- package/dist/types/src/tracing.d.ts.map +1 -0
- package/dist/types/src/types/artifact.d.ts +28 -0
- package/dist/types/src/types/artifact.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -47
- package/src/conversation/context.test.ts +52 -0
- package/src/conversation/context.ts +324 -75
- package/src/conversation/index.ts +1 -1
- package/src/conversation/session.ts +334 -0
- package/src/diff.test.ts +37 -6
- package/src/diff.ts +18 -4
- package/src/errors.ts +2 -2
- package/src/execution-graph.test.ts +47 -45
- package/src/execution-graph.ts +41 -22
- package/src/extraction/extraction-ner-function/index.ts +0 -1
- package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +10 -5
- package/src/extraction/extraction.ts +44 -15
- package/src/extraction/index.ts +0 -1
- package/src/extraction/quotes.ts +5 -6
- package/src/functions/services.ts +126 -65
- package/src/functions.conversations.json +1 -0
- package/src/index.ts +1 -0
- package/src/session/artifact-diff.ts +2 -2
- package/src/session/error-handling.ts +4 -3
- package/src/session/format.ts +34 -26
- package/src/session/index.ts +2 -1
- package/src/session/observer.ts +8 -4
- package/src/session/request.conversations.json +1 -0
- package/src/session/request.ts +342 -0
- package/src/session/toolkit.ts +29 -28
- package/src/session/version-pin.ts +7 -7
- package/src/templates/{format.tpl → instructions/format.tpl} +1 -4
- package/src/templates/{system.tpl → instructions/system.tpl} +50 -2
- package/src/templates/system.ts +9 -8
- package/src/tracing.ts +57 -0
- package/src/{types.ts → types/artifact.ts} +44 -7
- package/src/types/index.ts +5 -0
- package/dist/lib/browser/chunk-ILL2CN3G.mjs +0 -374
- package/dist/lib/browser/chunk-ILL2CN3G.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/extraction/index.mjs +0 -272
- package/dist/lib/browser/extraction/index.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -861
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/named-entity-recognition-QPYHML66.mjs +0 -13
- package/dist/lib/browser/named-entity-recognition-QPYHML66.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/chunk-GVKA7VL3.mjs +0 -376
- package/dist/lib/node-esm/chunk-GVKA7VL3.mjs.map +0 -7
- package/dist/lib/node-esm/extraction/index.mjs +0 -273
- package/dist/lib/node-esm/extraction/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -862
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/named-entity-recognition-2HDF46YX.mjs +0 -14
- package/dist/lib/node-esm/named-entity-recognition-2HDF46YX.mjs.map +0 -7
- package/dist/types/src/conversation/conversation.d.ts +0 -41
- package/dist/types/src/conversation/conversation.d.ts.map +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 -341
- 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/format.test.d.ts +0 -2
- package/dist/types/src/session/format.test.d.ts.map +0 -1
- package/dist/types/src/session/session.d.ts +0 -47
- package/dist/types/src/session/session.d.ts.map +0 -1
- package/dist/types/src/session/session.test.d.ts +0 -2
- package/dist/types/src/session/session.test.d.ts.map +0 -1
- package/dist/types/src/summarization/summarization.test.d.ts +0 -2
- package/dist/types/src/summarization/summarization.test.d.ts.map +0 -1
- package/dist/types/src/types.d.ts +0 -16
- package/dist/types/src/types.d.ts.map +0 -1
- package/src/conversation/conversation.ts +0 -119
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +0 -84
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +0 -72
- package/src/extraction/extraction-llm-function/index.ts +0 -5
- package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +0 -115
- package/src/extraction/extraction-ner-function/extraction-ner-function.ts +0 -64
- package/src/session/format.test.ts +0 -60
- package/src/session/session.conversations.json +0 -1
- package/src/session/session.test.ts +0 -146
- package/src/session/session.ts +0 -206
- package/src/summarization/summarization.test.ts +0 -201
- /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Array from 'effect/Array';
|
|
7
|
+
import * as Context from 'effect/Context';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Either from 'effect/Either';
|
|
10
|
+
import { pipe } from 'effect/Function';
|
|
11
|
+
import * as Layer from 'effect/Layer';
|
|
12
|
+
import * as Record from 'effect/Record';
|
|
13
|
+
import * as Runtime from 'effect/Runtime';
|
|
14
|
+
|
|
15
|
+
import { type OpaqueToolkit, type ToolExecutionService, type ToolResolverService } from '@dxos/ai';
|
|
16
|
+
import { type Blueprint, type OperationRegistry, Operation, Trace } from '@dxos/compute';
|
|
17
|
+
import { Resource } from '@dxos/context';
|
|
18
|
+
import { Database, Feed, Filter, Obj } from '@dxos/echo';
|
|
19
|
+
import { acquireReleaseResource } from '@dxos/effect';
|
|
20
|
+
import { invariant } from '@dxos/invariant';
|
|
21
|
+
import { log } from '@dxos/log';
|
|
22
|
+
import { McpToolkit } from '@dxos/mcp-client';
|
|
23
|
+
import { Message, type ContentBlock } from '@dxos/types';
|
|
24
|
+
|
|
25
|
+
import { ToolExecutionServices } from '../functions';
|
|
26
|
+
import {
|
|
27
|
+
AiRequest,
|
|
28
|
+
type AiRequestRunError,
|
|
29
|
+
type AiRequestRunRequirements,
|
|
30
|
+
type GenerationObserver,
|
|
31
|
+
createToolkit,
|
|
32
|
+
formatSystemPrompt,
|
|
33
|
+
} from '../session';
|
|
34
|
+
import { McpServerError } from '../tracing';
|
|
35
|
+
import { AiContextBinder, AiContextService } from './context';
|
|
36
|
+
|
|
37
|
+
export interface McpServerConfig {
|
|
38
|
+
url: string;
|
|
39
|
+
protocol: 'sse' | 'http';
|
|
40
|
+
apiKey?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AiSessionRunProps<R = never> {
|
|
44
|
+
prompt: string | ContentBlock.Any[];
|
|
45
|
+
system?: string;
|
|
46
|
+
observer?: GenerationObserver;
|
|
47
|
+
toolkit?: OpaqueToolkit.OpaqueToolkit<R>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Space-level MCP servers to connect alongside blueprint-defined ones.
|
|
51
|
+
*/
|
|
52
|
+
mcpServers?: readonly McpServerConfig[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type AiSessionOptions = {
|
|
56
|
+
feed: Feed.Feed;
|
|
57
|
+
runtime: Runtime.Runtime<Feed.FeedService>;
|
|
58
|
+
registry?: Registry.Registry;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// TODO(dmaretskyi): Take from model characteristics. opus has 200k max tokens.
|
|
62
|
+
/**
|
|
63
|
+
* Summarization threshold in tokens.
|
|
64
|
+
*/
|
|
65
|
+
const SUMMARY_THRESHOLD = 80_000;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Durable conversation state (initiated by users and agents) backed by a Feed.
|
|
69
|
+
* Executes tools based on AI responses and supports cancellation of in-progress requests.
|
|
70
|
+
*/
|
|
71
|
+
export class AiSession extends Resource {
|
|
72
|
+
/**
|
|
73
|
+
* Blueprints and objects bound to the session.
|
|
74
|
+
*/
|
|
75
|
+
private readonly _binder: AiContextBinder;
|
|
76
|
+
private readonly _feed: Feed.Feed;
|
|
77
|
+
private readonly _runtime: Runtime.Runtime<Feed.FeedService>;
|
|
78
|
+
|
|
79
|
+
public constructor(options: AiSessionOptions) {
|
|
80
|
+
super();
|
|
81
|
+
this._feed = options.feed;
|
|
82
|
+
this._runtime = options.runtime;
|
|
83
|
+
invariant(this._feed);
|
|
84
|
+
invariant(this._runtime);
|
|
85
|
+
this._binder = new AiContextBinder({ feed: this._feed, runtime: this._runtime, registry: options.registry });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected override async _open(): Promise<void> {
|
|
89
|
+
await this._binder.open(this._ctx);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public get feed() {
|
|
93
|
+
return this._feed;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public get context() {
|
|
97
|
+
return this._binder;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public async getHistory(): Promise<Message.Message[]> {
|
|
101
|
+
const queryResult = await Runtime.runPromise(this._runtime)(Feed.query(this._feed, Filter.type(Message.Message)));
|
|
102
|
+
const items = await queryResult.run();
|
|
103
|
+
return items.filter(Obj.instanceOf(Message.Message));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getTools(): Effect.Effect<
|
|
107
|
+
Record<string, import('@effect/ai/Tool').Any>,
|
|
108
|
+
never,
|
|
109
|
+
ToolExecutionService | ToolResolverService
|
|
110
|
+
> {
|
|
111
|
+
return Effect.gen(this, function* () {
|
|
112
|
+
const toolkit = yield* createToolkit({ blueprints: this.context.getBlueprints() });
|
|
113
|
+
return toolkit.toolkit.tools;
|
|
114
|
+
}).pipe(Effect.orDie);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Not provided by default, since users might want to override them.
|
|
119
|
+
*/
|
|
120
|
+
makeToolExecutionServices(): Layer.Layer<
|
|
121
|
+
ToolExecutionService | ToolResolverService,
|
|
122
|
+
never,
|
|
123
|
+
OpaqueToolkit.OpaqueToolkitProvider | Operation.Service | OperationRegistry.Service
|
|
124
|
+
> {
|
|
125
|
+
return ToolExecutionServices.pipe(
|
|
126
|
+
Layer.provide(Operation.withInvocationOptions({ conversation: Obj.getDXN(this._feed).toString() })),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Creates a new cancelable request effect.
|
|
131
|
+
*/
|
|
132
|
+
public createRequest<R = never>(
|
|
133
|
+
params: AiSessionRunProps<R>,
|
|
134
|
+
): Effect.Effect<Message.Message[], AiRequestRunError, AiRequestRunRequirements | R> {
|
|
135
|
+
return Effect.gen(this, function* () {
|
|
136
|
+
const history = yield* Effect.promise(() => this.getHistory());
|
|
137
|
+
const blueprints = this.context.getBlueprints();
|
|
138
|
+
const objects = this.context.getObjects();
|
|
139
|
+
|
|
140
|
+
log('run', {
|
|
141
|
+
history: history.length,
|
|
142
|
+
blueprints: blueprints.length,
|
|
143
|
+
objects: objects.length,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const request = new AiRequest({
|
|
147
|
+
summarizationThreshold: SUMMARY_THRESHOLD,
|
|
148
|
+
observer: params.observer,
|
|
149
|
+
onOutput: (message) =>
|
|
150
|
+
Effect.promise(() => Runtime.runPromise(this._runtime)(Feed.append(this._feed, [message]))),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
yield* request.begin({
|
|
154
|
+
history,
|
|
155
|
+
blueprints,
|
|
156
|
+
objects,
|
|
157
|
+
prompt: params.prompt,
|
|
158
|
+
system: params.system,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Turn loop: recompute toolkit and system prompt between turns to pick up dynamically enabled blueprints.
|
|
162
|
+
do {
|
|
163
|
+
yield* Effect.promise(() => this.context.sync());
|
|
164
|
+
const currentBlueprints = this.context.getBlueprints();
|
|
165
|
+
const mcps = yield* connectMcpServers(currentBlueprints, params.mcpServers);
|
|
166
|
+
const toolkit = yield* createToolkit({
|
|
167
|
+
toolkit: params.toolkit,
|
|
168
|
+
blueprints: currentBlueprints,
|
|
169
|
+
opaqueToolkits: mcps,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
log('toolkit', { tools: Record.keys(toolkit.toolkit.tools) });
|
|
173
|
+
const system = yield* formatSystemPrompt({
|
|
174
|
+
system: params.system,
|
|
175
|
+
blueprints: currentBlueprints,
|
|
176
|
+
objects: this.context.getObjects(),
|
|
177
|
+
}).pipe(Effect.orDie);
|
|
178
|
+
|
|
179
|
+
const { done } = yield* request.runAgentTurn({ system, toolkit });
|
|
180
|
+
if (done) {
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
yield* request.runTools({ toolkit });
|
|
185
|
+
} while (true);
|
|
186
|
+
|
|
187
|
+
log('result', {
|
|
188
|
+
messages: request.pending.length,
|
|
189
|
+
duration: request.duration,
|
|
190
|
+
toolCalls: request.toolCalls,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return [...request.pending];
|
|
194
|
+
}).pipe(
|
|
195
|
+
Effect.provide(
|
|
196
|
+
Layer.mergeAll(
|
|
197
|
+
Layer.succeed(AiContextService, {
|
|
198
|
+
binder: this.context,
|
|
199
|
+
}),
|
|
200
|
+
Layer.succeed(AiSessionService, this),
|
|
201
|
+
Operation.withInvocationOptions({ conversation: Obj.getDXN(this._feed).toString() }),
|
|
202
|
+
),
|
|
203
|
+
),
|
|
204
|
+
Effect.withSpan('AiSession.createRequest'),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Gives access to the ai session.
|
|
211
|
+
*/
|
|
212
|
+
export class AiSessionService extends Context.Tag('@dxos/assistant/AiSessionService')<AiSessionService, AiSession>() {
|
|
213
|
+
/**
|
|
214
|
+
* Create a new session layer from options.
|
|
215
|
+
*/
|
|
216
|
+
static layer = (options: AiSessionOptions): Layer.Layer<AiSessionService | AiContextService> =>
|
|
217
|
+
aiContextFromSession.pipe(
|
|
218
|
+
Layer.provideMerge(
|
|
219
|
+
Layer.scoped(
|
|
220
|
+
AiSessionService,
|
|
221
|
+
Effect.gen(function* () {
|
|
222
|
+
const session = yield* acquireReleaseResource(() => new AiSession(options));
|
|
223
|
+
return session;
|
|
224
|
+
}),
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create a new session with a new feed.
|
|
231
|
+
*/
|
|
232
|
+
static layerNewFeed = (
|
|
233
|
+
options?: Omit<AiSessionOptions, 'feed' | 'runtime'>,
|
|
234
|
+
): Layer.Layer<AiSessionService | AiContextService, never, Database.Service | Feed.FeedService> =>
|
|
235
|
+
Layer.unwrapScoped(
|
|
236
|
+
Effect.gen(function* () {
|
|
237
|
+
const feed = Feed.make();
|
|
238
|
+
yield* Database.add(feed);
|
|
239
|
+
const runtime = yield* Effect.runtime<Feed.FeedService>();
|
|
240
|
+
return AiSessionService.layer({
|
|
241
|
+
...options,
|
|
242
|
+
feed,
|
|
243
|
+
runtime,
|
|
244
|
+
});
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Run a prompt in the current session.
|
|
250
|
+
*/
|
|
251
|
+
static run = <R = never>(
|
|
252
|
+
params: AiSessionRunProps<R>,
|
|
253
|
+
): Effect.Effect<Message.Message[], AiRequestRunError, AiRequestRunRequirements | AiSessionService | R> =>
|
|
254
|
+
Effect.gen(function* () {
|
|
255
|
+
const session = yield* AiSessionService;
|
|
256
|
+
return yield* session.createRequest(params);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const aiContextFromSession = Layer.effect(
|
|
261
|
+
AiContextService,
|
|
262
|
+
Effect.gen(function* () {
|
|
263
|
+
const session = yield* AiSessionService;
|
|
264
|
+
return {
|
|
265
|
+
binder: session.context,
|
|
266
|
+
};
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const connectMcpServers = (
|
|
271
|
+
blueprints: readonly Blueprint.Blueprint[],
|
|
272
|
+
spaceMcpServers: readonly McpServerConfig[] = [],
|
|
273
|
+
): Effect.Effect<OpaqueToolkit.OpaqueToolkit[], never, Trace.TraceService> => {
|
|
274
|
+
const blueprintServers: McpToolkit.McpToolkitOptions[] = pipe(
|
|
275
|
+
blueprints,
|
|
276
|
+
Array.flatMap((_) => _.mcpServers ?? []),
|
|
277
|
+
Array.map(({ url, protocol }) => ({ url, kind: protocol })),
|
|
278
|
+
);
|
|
279
|
+
const spaceServers: McpToolkit.McpToolkitOptions[] = spaceMcpServers.map(({ url, protocol, apiKey }) => ({
|
|
280
|
+
url,
|
|
281
|
+
kind: protocol,
|
|
282
|
+
apiKey,
|
|
283
|
+
}));
|
|
284
|
+
const allServers = [...blueprintServers, ...spaceServers];
|
|
285
|
+
|
|
286
|
+
return pipe(
|
|
287
|
+
allServers,
|
|
288
|
+
Effect.forEach((options) =>
|
|
289
|
+
McpToolkit.make(options).pipe(
|
|
290
|
+
// NOTE: Type-inference fails here without explicit void return.
|
|
291
|
+
Effect.tap((toolkit): void =>
|
|
292
|
+
log.info('Connected to MCP server', { url: options.url, tools: Object.keys(toolkit.toolkit.tools).length }),
|
|
293
|
+
),
|
|
294
|
+
// Surface typed connection failures via ephemeral trace + warn log, then drop the server.
|
|
295
|
+
Effect.tapError((error) =>
|
|
296
|
+
Effect.gen(function* () {
|
|
297
|
+
log.warn('Failed to connect to MCP server', {
|
|
298
|
+
url: error.url,
|
|
299
|
+
kind: error.kind,
|
|
300
|
+
message: error.message,
|
|
301
|
+
});
|
|
302
|
+
yield* Trace.write(McpServerError, {
|
|
303
|
+
url: error.url,
|
|
304
|
+
kind: error.kind,
|
|
305
|
+
message: error.message,
|
|
306
|
+
});
|
|
307
|
+
}),
|
|
308
|
+
),
|
|
309
|
+
// Catch unexpected defects too (e.g. malformed tool schemas) so a single broken
|
|
310
|
+
// server can never abort the whole turn — surface them through the same channel.
|
|
311
|
+
Effect.catchAllDefect((defect) =>
|
|
312
|
+
Effect.gen(function* () {
|
|
313
|
+
const message = defect instanceof Error ? defect.message : String(defect);
|
|
314
|
+
log.warn('Unexpected MCP defect', { url: options.url, message });
|
|
315
|
+
yield* Trace.write(McpServerError, {
|
|
316
|
+
url: options.url,
|
|
317
|
+
kind: options.kind,
|
|
318
|
+
message: `Unexpected MCP failure: ${message}`,
|
|
319
|
+
});
|
|
320
|
+
return yield* Effect.fail(
|
|
321
|
+
new McpToolkit.McpConnectionError({
|
|
322
|
+
url: options.url,
|
|
323
|
+
kind: options.kind,
|
|
324
|
+
message,
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
}),
|
|
328
|
+
),
|
|
329
|
+
Effect.either,
|
|
330
|
+
),
|
|
331
|
+
),
|
|
332
|
+
Effect.map(Array.filterMap((_) => Either.getRight(_))),
|
|
333
|
+
);
|
|
334
|
+
};
|
package/src/diff.test.ts
CHANGED
|
@@ -2,20 +2,27 @@
|
|
|
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';
|
|
9
|
-
import {
|
|
9
|
+
import { Text } from '@dxos/schema';
|
|
10
10
|
import { trim } from '@dxos/util';
|
|
11
11
|
|
|
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([DataType.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(
|
|
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
|
@@ -11,6 +11,11 @@ import { isNonNullable } from '@dxos/util';
|
|
|
11
11
|
// Relevant info:
|
|
12
12
|
// https://github.com/Minahil-official/GPT-4.1-Promting-Guide?tab=readme-ov-file#-apply-patch-tool-format-v4a-diff
|
|
13
13
|
|
|
14
|
+
// TODO(burdon): Anthropic only.
|
|
15
|
+
// https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/text-editor-tool#example-str-replace-command
|
|
16
|
+
// AI_TOOL_NOT_FOUND: str_replace_based_edit_tool
|
|
17
|
+
// 'str_replace_based_edit_tool',
|
|
18
|
+
|
|
14
19
|
// TOOD(burdon): Support append to end (no "-").
|
|
15
20
|
|
|
16
21
|
export type Diff = {
|
|
@@ -42,9 +47,10 @@ export const reduceDiffs = (diffs: readonly string[]): Diff[] => {
|
|
|
42
47
|
}
|
|
43
48
|
return acc;
|
|
44
49
|
}
|
|
50
|
+
default: {
|
|
51
|
+
throw new Error(`Invalid diff format: each line must start with "- " or "+ ", got: ${JSON.stringify(diff)}`);
|
|
52
|
+
}
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
return acc;
|
|
48
54
|
}, []);
|
|
49
55
|
};
|
|
50
56
|
|
|
@@ -60,7 +66,11 @@ export const computeDiffsWithCursors = <T>(accessor: DocAccessor<T>, diffs: read
|
|
|
60
66
|
.filter(isNonNullable);
|
|
61
67
|
};
|
|
62
68
|
|
|
63
|
-
export const applyDiffs = <T>(
|
|
69
|
+
export const applyDiffs = <T>(
|
|
70
|
+
accessor: DocAccessor<T>,
|
|
71
|
+
diffs: readonly string[],
|
|
72
|
+
{ errorOnNotFound = false }: { errorOnNotFound?: boolean } = {},
|
|
73
|
+
): string => {
|
|
64
74
|
for (const diff of reduceDiffs(diffs)) {
|
|
65
75
|
accessor.handle.change((doc: Doc<T>) => {
|
|
66
76
|
const text = DocAccessor.getValue<string>(accessor);
|
|
@@ -70,7 +80,11 @@ export const applyDiffs = <T>(accessor: DocAccessor<T>, diffs: readonly string[]
|
|
|
70
80
|
// NOTE: This only replaces the first match.
|
|
71
81
|
A.splice(doc, accessor.path as A.Prop[], idx, diff.match.length, diff.replace);
|
|
72
82
|
} else {
|
|
73
|
-
|
|
83
|
+
if (errorOnNotFound) {
|
|
84
|
+
throw new Error(`Diff not found: ${diff.match}`);
|
|
85
|
+
} else {
|
|
86
|
+
log.warn('diff not found', diff);
|
|
87
|
+
}
|
|
74
88
|
}
|
|
75
89
|
});
|
|
76
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') {}
|