@dxos/assistant 0.8.4-main.f9ba587 → 0.8.4-main.fcfe5033a5
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-2TBLG3XY.mjs +1793 -0
- package/dist/lib/neutral/chunk-2TBLG3XY.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/extraction/index.mjs +219 -0
- package/dist/lib/neutral/extraction/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +537 -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 +111 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +155 -0
- package/dist/types/src/conversation/context.d.ts.map +1 -0
- 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 +2 -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/conversation/session.test.d.ts.map +1 -0
- package/dist/types/src/diff.d.ts +17 -0
- package/dist/types/src/diff.d.ts.map +1 -0
- package/dist/types/src/diff.test.d.ts +2 -0
- package/dist/types/src/diff.test.d.ts.map +1 -0
- package/dist/types/src/errors.d.ts +67 -0
- package/dist/types/src/errors.d.ts.map +1 -0
- package/dist/types/src/execution-graph.d.ts +85 -0
- package/dist/types/src/execution-graph.d.ts.map +1 -0
- package/dist/types/src/execution-graph.test.d.ts +2 -0
- package/dist/types/src/execution-graph.test.d.ts.map +1 -0
- 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 +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 -215
- 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/index.d.ts +2 -0
- package/dist/types/src/functions/index.d.ts.map +1 -0
- package/dist/types/src/functions/services.d.ts +17 -0
- package/dist/types/src/functions/services.d.ts.map +1 -0
- 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 +7 -5
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/service/AgentService.d.ts +75 -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 +21 -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 +27 -0
- package/dist/types/src/session/artifact-diff.d.ts.map +1 -0
- package/dist/types/src/session/error-handling.d.ts +4 -0
- package/dist/types/src/session/error-handling.d.ts.map +1 -0
- package/dist/types/src/session/format.d.ts +16 -0
- package/dist/types/src/session/format.d.ts.map +1 -0
- package/dist/types/src/session/format.test.d.ts +2 -0
- package/dist/types/src/session/format.test.d.ts.map +1 -0
- package/dist/types/src/session/index.d.ts +5 -1
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/observer.d.ts +23 -0
- package/dist/types/src/session/observer.d.ts.map +1 -0
- package/dist/types/src/session/request.d.ts +106 -0
- package/dist/types/src/session/request.d.ts.map +1 -0
- package/dist/types/src/session/request.test.d.ts +2 -0
- package/dist/types/src/session/request.test.d.ts.map +1 -0
- package/dist/types/src/session/session-ollama.test.d.ts +2 -0
- package/dist/types/src/session/session-ollama.test.d.ts.map +1 -0
- package/dist/types/src/session/toolkit.d.ts +16 -0
- package/dist/types/src/session/toolkit.d.ts.map +1 -0
- package/dist/types/src/session/version-pin.d.ts +5 -5
- package/dist/types/src/session/version-pin.d.ts.map +1 -1
- package/dist/types/src/templates/index.d.ts +2 -0
- package/dist/types/src/templates/index.d.ts.map +1 -0
- package/dist/types/src/templates/system.d.ts +17 -0
- package/dist/types/src/templates/system.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -2
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/layer.d.ts +40 -0
- package/dist/types/src/testing/layer.d.ts.map +1 -0
- package/dist/types/src/tracing.d.ts +223 -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 +60 -29
- package/src/conversation/context.test.ts +52 -0
- package/src/conversation/context.ts +403 -0
- package/src/conversation/index.ts +2 -1
- package/src/conversation/session.test.ts +58 -0
- package/src/conversation/session.ts +300 -0
- package/src/diff.test.ts +74 -0
- package/src/diff.ts +93 -0
- package/src/errors.ts +15 -0
- package/src/execution-graph.test.ts +740 -0
- package/src/execution-graph.ts +509 -0
- package/src/extraction/extraction-ner-function/index.ts +0 -1
- package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +12 -6
- package/src/extraction/extraction-ner-function/named-entity-recognition.ts +3 -1
- package/src/extraction/extraction.ts +44 -15
- package/src/extraction/index.ts +0 -1
- package/src/extraction/quotes.test.ts +3 -3
- package/src/extraction/quotes.ts +6 -7
- package/src/functions/index.ts +5 -0
- package/src/functions/services.ts +214 -0
- package/src/functions.conversations.json +1 -0
- package/src/functions.test.ts +88 -0
- package/src/index.ts +7 -5
- package/src/service/AgentService.conversations.json +1 -0
- package/src/service/AgentService.test.ts +169 -0
- package/src/service/AgentService.ts +184 -0
- package/src/service/agent-process.ts +364 -0
- package/src/service/index.ts +6 -0
- package/src/session/artifact-diff.ts +41 -0
- package/src/session/error-handling.ts +41 -0
- package/src/session/format.test.ts +132 -0
- package/src/session/format.ts +149 -0
- package/src/session/index.ts +5 -1
- package/src/session/observer.ts +62 -0
- package/src/session/request.conversations.json +1 -0
- package/src/session/request.test.ts +195 -0
- package/src/session/request.ts +344 -0
- package/src/session/session-ollama.test.ts +81 -0
- package/src/session/toolkit.ts +47 -0
- package/src/session/version-pin.ts +12 -12
- package/src/templates/index.ts +5 -0
- package/src/templates/instructions/format.tpl +57 -0
- package/src/templates/instructions/system.tpl +85 -0
- package/src/templates/system.ts +42 -0
- package/src/testing/index.ts +2 -8
- package/src/testing/layer.ts +236 -0
- package/src/tracing.ts +43 -0
- package/src/types/artifact.ts +89 -0
- package/src/types/index.ts +5 -0
- package/src/typings.d.ts +5 -0
- package/dist/lib/browser/index.mjs +0 -2019
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -2021
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/types/bin/chat.d.ts +0 -2
- package/dist/types/bin/chat.d.ts.map +0 -1
- package/dist/types/src/blueprint/blueprint.d.ts +0 -41
- package/dist/types/src/blueprint/blueprint.d.ts.map +0 -1
- package/dist/types/src/blueprint/index.d.ts +0 -3
- package/dist/types/src/blueprint/index.d.ts.map +0 -1
- package/dist/types/src/blueprint/registry.d.ts +0 -7
- package/dist/types/src/blueprint/registry.d.ts.map +0 -1
- package/dist/types/src/context/binder.d.ts +0 -34
- package/dist/types/src/context/binder.d.ts.map +0 -1
- package/dist/types/src/context/binding.d.ts +0 -38
- package/dist/types/src/context/binding.d.ts.map +0 -1
- package/dist/types/src/context/index.d.ts +0 -3
- package/dist/types/src/context/index.d.ts.map +0 -1
- package/dist/types/src/conversation/conversation.d.ts +0 -44
- 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 -208
- 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/research/exa.d.ts +0 -7
- package/dist/types/src/research/exa.d.ts.map +0 -1
- package/dist/types/src/research/graph.d.ts +0 -41
- package/dist/types/src/research/graph.d.ts.map +0 -1
- package/dist/types/src/research/graph.test.d.ts +0 -2
- package/dist/types/src/research/graph.test.d.ts.map +0 -1
- package/dist/types/src/research/index.d.ts +0 -4
- package/dist/types/src/research/index.d.ts.map +0 -1
- package/dist/types/src/research/research.d.ts +0 -10
- package/dist/types/src/research/research.d.ts.map +0 -1
- package/dist/types/src/research/research.test.d.ts +0 -2
- package/dist/types/src/research/research.test.d.ts.map +0 -1
- package/dist/types/src/session/session.d.ts +0 -283
- package/dist/types/src/session/session.d.ts.map +0 -1
- package/dist/types/src/session/session.test.d.ts.map +0 -1
- package/dist/types/src/session/version-pin.test.d.ts +0 -2
- package/dist/types/src/session/version-pin.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/testing/exa-search-1748337321991.d.ts +0 -38
- package/dist/types/src/testing/exa-search-1748337321991.d.ts.map +0 -1
- package/dist/types/src/testing/exa-search-1748337331526.d.ts +0 -37
- package/dist/types/src/testing/exa-search-1748337331526.d.ts.map +0 -1
- package/dist/types/src/testing/exa-search-1748337344119.d.ts +0 -58
- package/dist/types/src/testing/exa-search-1748337344119.d.ts.map +0 -1
- package/dist/types/src/types.d.ts +0 -3
- package/dist/types/src/types.d.ts.map +0 -1
- package/src/blueprint/blueprint.ts +0 -75
- package/src/blueprint/index.ts +0 -6
- package/src/blueprint/registry.ts +0 -15
- package/src/context/binder.ts +0 -101
- package/src/context/binding.ts +0 -31
- package/src/context/index.ts +0 -6
- package/src/conversation/conversation.ts +0 -103
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +0 -95
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +0 -66
- 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 -54
- package/src/research/exa.ts +0 -100
- package/src/research/graph.test.ts +0 -15
- package/src/research/graph.ts +0 -337
- package/src/research/index.ts +0 -7
- package/src/research/instructions-research.tpl +0 -97
- package/src/research/research.test.ts +0 -190
- package/src/research/research.ts +0 -90
- package/src/session/session.test.ts +0 -275
- package/src/session/session.ts +0 -582
- package/src/session/version-pin.test.ts +0 -26
- package/src/summarization/summarization.test.ts +0 -221
- package/src/testing/exa-search-1748337321991.ts +0 -131
- package/src/testing/exa-search-1748337331526.ts +0 -144
- package/src/testing/exa-search-1748337344119.ts +0 -133
- package/src/typedefs.d.ts +0 -8
- package/src/types.ts +0 -8
- /package/dist/types/src/{session → conversation}/session.test.d.ts +0 -0
- /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Tool from '@effect/ai/Tool';
|
|
6
|
+
import * as Toolkit from '@effect/ai/Toolkit';
|
|
7
|
+
import * as Cause from 'effect/Cause';
|
|
8
|
+
import * as Duration from 'effect/Duration';
|
|
9
|
+
import * as Effect from 'effect/Effect';
|
|
10
|
+
import * as Exit from 'effect/Exit';
|
|
11
|
+
import * as Layer from 'effect/Layer';
|
|
12
|
+
import * as Match from 'effect/Match';
|
|
13
|
+
import * as Option from 'effect/Option';
|
|
14
|
+
import * as Schema from 'effect/Schema';
|
|
15
|
+
|
|
16
|
+
import { AiService, OpaqueToolkit, type ModelName } from '@dxos/ai';
|
|
17
|
+
import { Database, DXN, Feed, Obj } from '@dxos/echo';
|
|
18
|
+
import { acquireReleaseResource } from '@dxos/effect';
|
|
19
|
+
import { Trace } from '@dxos/functions';
|
|
20
|
+
import { Process, ProcessManager, StorageService } from '@dxos/functions-runtime';
|
|
21
|
+
import { log } from '@dxos/log';
|
|
22
|
+
import { Operation, OperationRegistry } from '@dxos/operation';
|
|
23
|
+
import { trim } from '@dxos/util';
|
|
24
|
+
|
|
25
|
+
import { type McpServerConfig, AiSession } from '../conversation';
|
|
26
|
+
import { getOperationFromTool, makeToolExecutionService, makeToolResolverFromOperations } from '../functions';
|
|
27
|
+
import { AgentRequestBegin, AgentRequestEnd } from '../tracing';
|
|
28
|
+
|
|
29
|
+
interface AgentProcessOptions {
|
|
30
|
+
systemPrompt?: string;
|
|
31
|
+
model?: ModelName;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Provider for space-level MCP server configs, called on each turn.
|
|
35
|
+
*/
|
|
36
|
+
getMcpServers?: () => McpServerConfig[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const AGENT_PROCESS_KEY = 'org.dxos.testing.process.agent';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Hosts a persistent, suspendible AiAgent that can process a number of prompts.
|
|
43
|
+
* The process target is a queue DXN string.
|
|
44
|
+
*/
|
|
45
|
+
export const AgentProcess = (options: AgentProcessOptions) =>
|
|
46
|
+
Process.make(
|
|
47
|
+
{
|
|
48
|
+
key: AGENT_PROCESS_KEY,
|
|
49
|
+
input: Schema.String,
|
|
50
|
+
output: Schema.Void,
|
|
51
|
+
services: [
|
|
52
|
+
Database.Service,
|
|
53
|
+
OpaqueToolkit.OpaqueToolkitProvider,
|
|
54
|
+
Operation.Service,
|
|
55
|
+
OperationRegistry.Service,
|
|
56
|
+
StorageService.StorageService,
|
|
57
|
+
Feed.FeedService,
|
|
58
|
+
ProcessManager.ProcessOperationInvoker.Service,
|
|
59
|
+
AiService.AiService,
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
(ctx) =>
|
|
63
|
+
Effect.gen(function* () {
|
|
64
|
+
const feedDxn = ctx.params.target;
|
|
65
|
+
if (feedDxn == null) {
|
|
66
|
+
return yield* Effect.die(new Error('Agent executable requires spawn options.target set to a queue DXN.'));
|
|
67
|
+
}
|
|
68
|
+
const feed = yield* Database.resolve(DXN.parse(feedDxn), Feed.Feed).pipe(Effect.orDie);
|
|
69
|
+
const runtime = yield* Effect.runtime<Feed.FeedService>();
|
|
70
|
+
const session = yield* acquireReleaseResource(() => new AiSession({ feed, runtime }));
|
|
71
|
+
let inputQueue: AgentEvent[] = [...(yield* AgentEventsKey.get)];
|
|
72
|
+
const storageService = yield* StorageService.StorageService;
|
|
73
|
+
const toolCallManager = new ToolCallManager(storageService);
|
|
74
|
+
yield* toolCallManager.load();
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
onInput: Effect.fnUntraced(function* (prompt: string) {
|
|
78
|
+
log('agent onInput received', { promptLength: prompt.length, backlog: inputQueue.length });
|
|
79
|
+
inputQueue.push({ _tag: 'prompt', content: prompt });
|
|
80
|
+
log('agent onInput persisting queue', { depth: inputQueue.length });
|
|
81
|
+
yield* AgentEventsKey.set(inputQueue);
|
|
82
|
+
log('agent onInput persisted', { depth: inputQueue.length });
|
|
83
|
+
ctx.setAlarm();
|
|
84
|
+
log('agent onInput alarm scheduled');
|
|
85
|
+
}),
|
|
86
|
+
onAlarm: Effect.fnUntraced(
|
|
87
|
+
function* () {
|
|
88
|
+
log('agent onAlarm fired', { pending: inputQueue.length });
|
|
89
|
+
const item = inputQueue.shift();
|
|
90
|
+
if (!item) {
|
|
91
|
+
log('agent onAlarm empty queue', {});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (item._tag === 'tool_result' && toolCallManager.isReported(item.pid)) {
|
|
96
|
+
log.info('skip tool result that was reported synchronously', { pid: item.pid });
|
|
97
|
+
// Ignore tool results that were reported synchronously.
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
log('agent onAlarm handling', { tag: item._tag });
|
|
102
|
+
|
|
103
|
+
const prompt = Match.value(item).pipe(
|
|
104
|
+
Match.tag('prompt', (item) => item.content),
|
|
105
|
+
Match.tag('tool_result', (item) =>
|
|
106
|
+
item.isError
|
|
107
|
+
? toolErrorResponse(item.pid, item.result as string)
|
|
108
|
+
: toolResultResponse(item.pid, item.result),
|
|
109
|
+
),
|
|
110
|
+
Match.exhaustive,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
log('begin request', { prompt });
|
|
114
|
+
yield* Trace.write(AgentRequestBegin, {});
|
|
115
|
+
yield* session
|
|
116
|
+
.createRequest({
|
|
117
|
+
prompt,
|
|
118
|
+
// TODO(dmaretskyi): Polling currently broken, agent relies on completion notifications being delivered.
|
|
119
|
+
// toolkit: AsynchronousExectionToolkit,
|
|
120
|
+
system: options.systemPrompt,
|
|
121
|
+
mcpServers: options.getMcpServers?.(),
|
|
122
|
+
})
|
|
123
|
+
.pipe(Effect.ensuring(Trace.write(AgentRequestEnd, {})));
|
|
124
|
+
log('end request');
|
|
125
|
+
yield* AgentEventsKey.set(inputQueue);
|
|
126
|
+
if (inputQueue.length > 0) {
|
|
127
|
+
ctx.setAlarm();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
Effect.orDie,
|
|
131
|
+
Effect.provide(
|
|
132
|
+
Layer.mergeAll(
|
|
133
|
+
makeToolResolverFromOperations(),
|
|
134
|
+
ToolExecutionService({
|
|
135
|
+
toolCallManager,
|
|
136
|
+
feed,
|
|
137
|
+
}),
|
|
138
|
+
AsynchronousExectionToolkitLayer,
|
|
139
|
+
AiService.model(options.model ?? '@anthropic/claude-opus-4-6'),
|
|
140
|
+
).pipe(Layer.orDie),
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
onChildEvent: Effect.fnUntraced(function* (event) {
|
|
144
|
+
log('childEvent', { event });
|
|
145
|
+
if (event._tag === 'exited') {
|
|
146
|
+
if (!toolCallManager.isToolCall(event.pid)) {
|
|
147
|
+
log.verbose('childEvent ignored non-tool call', { pid: event.pid });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const operationInvoker = yield* ProcessManager.ProcessOperationInvoker.Service;
|
|
152
|
+
const fiber = yield* operationInvoker.attachFiber(event.pid).pipe(Effect.orDie);
|
|
153
|
+
const result = yield* fiber.await.pipe(Effect.orDie).pipe(
|
|
154
|
+
Effect.map(
|
|
155
|
+
Exit.match({
|
|
156
|
+
onSuccess: (value): AgentEvent => ({
|
|
157
|
+
_tag: 'tool_result',
|
|
158
|
+
pid: event.pid,
|
|
159
|
+
result: value,
|
|
160
|
+
isError: false,
|
|
161
|
+
}),
|
|
162
|
+
onFailure: (cause): AgentEvent => ({
|
|
163
|
+
_tag: 'tool_result',
|
|
164
|
+
pid: event.pid,
|
|
165
|
+
result: Cause.pretty(cause),
|
|
166
|
+
isError: true,
|
|
167
|
+
}),
|
|
168
|
+
}),
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
inputQueue.push(result);
|
|
172
|
+
log('agent onChildEvent persisted tool result', { depth: inputQueue.length, childPid: event.pid });
|
|
173
|
+
yield* AgentEventsKey.set(inputQueue);
|
|
174
|
+
ctx.setAlarm();
|
|
175
|
+
log('agent onChildEvent alarm scheduled', { depth: inputQueue.length });
|
|
176
|
+
}
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
interface ToolExecutionServiceOptions {
|
|
183
|
+
/**
|
|
184
|
+
* Threshold after which the tool execution is placed in the background.
|
|
185
|
+
*/
|
|
186
|
+
// TODO(dmaretskyi): Tool annotation to never run in background.
|
|
187
|
+
backgroundThreshold?: Duration.Duration;
|
|
188
|
+
|
|
189
|
+
toolCallManager: ToolCallManager;
|
|
190
|
+
feed: Feed.Feed;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const AgentEvent = Schema.Union(
|
|
194
|
+
Schema.TaggedStruct('prompt', {
|
|
195
|
+
content: Schema.String,
|
|
196
|
+
}),
|
|
197
|
+
Schema.TaggedStruct('tool_result', {
|
|
198
|
+
pid: Process.ID,
|
|
199
|
+
result: Schema.Unknown,
|
|
200
|
+
isError: Schema.Boolean,
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
type AgentEvent = Schema.Schema.Type<typeof AgentEvent>;
|
|
204
|
+
|
|
205
|
+
const AgentEventsKey = StorageService.key(
|
|
206
|
+
Schema.parseJson(Schema.Array(AgentEvent).pipe(Schema.mutable)),
|
|
207
|
+
'inputQueue',
|
|
208
|
+
).pipe(StorageService.withDefault(() => []));
|
|
209
|
+
|
|
210
|
+
const ToolCallState = Schema.Struct({
|
|
211
|
+
activeCalls: Schema.Array(
|
|
212
|
+
Schema.Struct({
|
|
213
|
+
pid: Process.ID,
|
|
214
|
+
// Whether the result was reported to the agent.
|
|
215
|
+
reported: Schema.Boolean,
|
|
216
|
+
}).pipe(Schema.mutable),
|
|
217
|
+
).pipe(Schema.mutable),
|
|
218
|
+
});
|
|
219
|
+
interface ToolCallState extends Schema.Schema.Type<typeof ToolCallState> {}
|
|
220
|
+
|
|
221
|
+
// Id's of processes who's results were already submitted to the agent.
|
|
222
|
+
const ToolCallStateKey = StorageService.key(Schema.parseJson(ToolCallState.pipe(Schema.mutable)), 'toolCallState').pipe(
|
|
223
|
+
StorageService.withDefault(() => ({ activeCalls: [] })),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
class ToolCallManager {
|
|
227
|
+
#storageService: StorageService.Service;
|
|
228
|
+
#state: ToolCallState = { activeCalls: [] };
|
|
229
|
+
|
|
230
|
+
constructor(storageService: StorageService.Service) {
|
|
231
|
+
this.#storageService = storageService;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
load() {
|
|
235
|
+
return Effect.gen(this, function* () {
|
|
236
|
+
this.#state = yield* ToolCallStateKey.get;
|
|
237
|
+
}).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
beginCall(pid: Process.ID) {
|
|
241
|
+
return Effect.gen(this, function* () {
|
|
242
|
+
this.#state.activeCalls.push({ pid, reported: false });
|
|
243
|
+
yield* ToolCallStateKey.set(this.#state);
|
|
244
|
+
}).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
markAsReported(pid: Process.ID) {
|
|
248
|
+
return Effect.gen(this, function* () {
|
|
249
|
+
const call = this.#state.activeCalls.find((call) => call.pid === pid);
|
|
250
|
+
if (!call) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
call.reported = true;
|
|
254
|
+
yield* ToolCallStateKey.set(this.#state);
|
|
255
|
+
}).pipe(Effect.provideService(StorageService.StorageService, this.#storageService));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
isToolCall(pid: Process.ID): boolean {
|
|
259
|
+
return this.#state.activeCalls.some((call) => call.pid === pid);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
isReported(pid: Process.ID) {
|
|
263
|
+
return this.#state.activeCalls.some((call) => call.pid === pid && call.reported);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ToolExecutionService = ({
|
|
268
|
+
backgroundThreshold = Duration.seconds(1),
|
|
269
|
+
toolCallManager,
|
|
270
|
+
feed,
|
|
271
|
+
}: ToolExecutionServiceOptions) =>
|
|
272
|
+
Layer.unwrapEffect(
|
|
273
|
+
Effect.gen(function* () {
|
|
274
|
+
const operationInvoker = yield* ProcessManager.ProcessOperationInvoker.Service;
|
|
275
|
+
return makeToolExecutionService({
|
|
276
|
+
invoke: (tool, input) =>
|
|
277
|
+
Effect.gen(function* () {
|
|
278
|
+
const operationDef = getOperationFromTool(tool).pipe(Option.getOrThrow);
|
|
279
|
+
log('invoking operation', { operationDef, input });
|
|
280
|
+
const fiber = yield* operationInvoker.invokeFiber(operationDef, input, {
|
|
281
|
+
environment: {
|
|
282
|
+
conversation: Obj.getDXN(feed).toString(),
|
|
283
|
+
},
|
|
284
|
+
traceMeta: {
|
|
285
|
+
conversationId: feed.id,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
toolCallManager.beginCall(fiber.pid);
|
|
289
|
+
log('invoked operation', { operationDef, input, fiber });
|
|
290
|
+
|
|
291
|
+
const result = yield* fiber.await.pipe(
|
|
292
|
+
Effect.tap(() => toolCallManager.markAsReported(fiber.pid)),
|
|
293
|
+
Effect.timeout(backgroundThreshold),
|
|
294
|
+
Effect.catchTag('TimeoutException', () => Effect.succeed(toolIsRunningInBackgroundResponse(fiber.pid))),
|
|
295
|
+
);
|
|
296
|
+
log('result', { result });
|
|
297
|
+
return result;
|
|
298
|
+
}),
|
|
299
|
+
});
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
class AsynchronousExectionToolkit extends Toolkit.make(
|
|
304
|
+
Tool.make('poll-tools', {
|
|
305
|
+
description: trim`
|
|
306
|
+
Poll tool calls running in the background.
|
|
307
|
+
Set wait to true to wait for the tool call to complete before returning.
|
|
308
|
+
Only set wait to true if you dont have other tasks to perform in parallel.
|
|
309
|
+
Set an appropriate timeout to avoid waiting forever.
|
|
310
|
+
You will also be notified about the job completion separatelly, so you do not always need to inspect the job if you dont need the result right now.
|
|
311
|
+
`,
|
|
312
|
+
parameters: {
|
|
313
|
+
ids: Schema.Array(Schema.String).annotations({
|
|
314
|
+
description: 'The IDs of the jobs to inspect.',
|
|
315
|
+
}),
|
|
316
|
+
wait: Schema.optional(Schema.Boolean).annotations({
|
|
317
|
+
description: 'Whether to wait for the job to complete before returning.',
|
|
318
|
+
default: false,
|
|
319
|
+
}),
|
|
320
|
+
timeout: Schema.optional(Schema.Number).annotations({
|
|
321
|
+
description:
|
|
322
|
+
'Maximum time to wait for the job to complete. If the job does not complete within the timeout, the current state is returned.',
|
|
323
|
+
default: 10_000,
|
|
324
|
+
}),
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
) {}
|
|
328
|
+
|
|
329
|
+
// TODO(dmaretskyi): Currently broken: polling a completed process returns interruped error.
|
|
330
|
+
const AsynchronousExectionToolkitLayer = AsynchronousExectionToolkit.toLayer(
|
|
331
|
+
Effect.gen(function* () {
|
|
332
|
+
const invoker = yield* ProcessManager.ProcessOperationInvoker.Service;
|
|
333
|
+
return {
|
|
334
|
+
'poll-tools': ({ ids, wait, timeout = 10_000 }) =>
|
|
335
|
+
Effect.gen(function* () {
|
|
336
|
+
return yield* Effect.forEach(ids, (pid) =>
|
|
337
|
+
invoker.attachFiber<unknown>(Process.ID.make(pid)).pipe(
|
|
338
|
+
Effect.flatMap((_) => _.await),
|
|
339
|
+
Effect.timeout(Duration.millis(timeout)),
|
|
340
|
+
Effect.flatMap(
|
|
341
|
+
Exit.match({
|
|
342
|
+
onSuccess: (value) => Effect.succeed(toolResultResponse(pid, value)),
|
|
343
|
+
onFailure: (cause) => Effect.succeed(toolErrorResponse(pid, Cause.pretty(cause))),
|
|
344
|
+
}),
|
|
345
|
+
),
|
|
346
|
+
Effect.catchTag('ProcessNotFoundError', () => Effect.succeed(`Process not found: ${pid}`)),
|
|
347
|
+
Effect.catchTag('TimeoutException', () => Effect.succeed(`Process still running: ${pid}`)),
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
}),
|
|
351
|
+
};
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Instructs model that the tool is running in the background.
|
|
357
|
+
*/
|
|
358
|
+
const toolIsRunningInBackgroundResponse = (pid: Process.ID) =>
|
|
359
|
+
`Tool is running in the background id=${pid}; wait for the completion notification to get the result.`;
|
|
360
|
+
// `Tool is running in the background id=${pid}; use ${AsynchronousExectionToolkit.tools['poll-tools'].name} to get the result.`;
|
|
361
|
+
|
|
362
|
+
const toolResultResponse = (pid: string, value: unknown) => `<result pid=${pid}>${JSON.stringify(value)}</result>`;
|
|
363
|
+
|
|
364
|
+
const toolErrorResponse = (pid: string, cause: string) => `<error pid=${pid}>${cause}</error>`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
|
|
7
|
+
import { type ObjectVersion } from '@dxos/echo-db';
|
|
8
|
+
import { type ObjectId } from '@dxos/keys';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves artifact ids to their versions.
|
|
12
|
+
* Used to give the model a sense of the changes to the artifacts made by users during the conversation.
|
|
13
|
+
* The artifacts versions are pinned in the history, and whenever the artifact changes in-between assistant's steps,
|
|
14
|
+
* a diff is inserted into the conversation.
|
|
15
|
+
*
|
|
16
|
+
* Can be optionally provided to the session run call.
|
|
17
|
+
*/
|
|
18
|
+
// TODO(dmaretskyi): Convert to Context.Reference
|
|
19
|
+
export class ArtifactDiffResolver extends Context.Tag('@dxos/assistant/ArtifactDiffResolver')<
|
|
20
|
+
ArtifactDiffResolver,
|
|
21
|
+
ArtifactDiffResolver.Service
|
|
22
|
+
>() {}
|
|
23
|
+
|
|
24
|
+
export namespace ArtifactDiffResolver {
|
|
25
|
+
export type Service = {
|
|
26
|
+
resolve: (
|
|
27
|
+
artifacts: {
|
|
28
|
+
id: ObjectId;
|
|
29
|
+
lastVersion: ObjectVersion;
|
|
30
|
+
}[],
|
|
31
|
+
) => Promise<
|
|
32
|
+
Map<
|
|
33
|
+
ObjectId,
|
|
34
|
+
{
|
|
35
|
+
version: ObjectVersion;
|
|
36
|
+
diff?: string;
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
>;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as AiError from '@effect/ai/AiError';
|
|
6
|
+
import * as HttpClientError from '@effect/platform/HttpClientError';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
|
+
|
|
10
|
+
import { AiModelError } from '../errors';
|
|
11
|
+
|
|
12
|
+
const AnthropicErrorResponse = Schema.Struct({
|
|
13
|
+
type: Schema.Literal('error'),
|
|
14
|
+
error: Schema.Struct({
|
|
15
|
+
type: Schema.String,
|
|
16
|
+
message: Schema.String,
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// TODO(dmaretskyi): Needs rework
|
|
21
|
+
export const mapAiError = (err: AiError.AiError): Effect.Effect<AiError.AiError> =>
|
|
22
|
+
Effect.gen(function* () {
|
|
23
|
+
const cause = err.cause;
|
|
24
|
+
if (HttpClientError.isHttpClientError(cause) && cause.reason === 'StatusCode') {
|
|
25
|
+
const body = yield* cause.response.json.pipe(
|
|
26
|
+
Effect.flatMap(Schema.decodeUnknown(AnthropicErrorResponse, { exact: false })),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const parsedCause = new AiModelError({
|
|
30
|
+
message: body.error.message,
|
|
31
|
+
context: { model: 'anthropic', type: body.error.type },
|
|
32
|
+
});
|
|
33
|
+
return AiError.UnknownError.make({
|
|
34
|
+
description: body.error.message,
|
|
35
|
+
module: err.module,
|
|
36
|
+
method: err.method,
|
|
37
|
+
cause: parsedCause,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return err;
|
|
41
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(err)));
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from '@effect/vitest';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
|
|
9
|
+
import { Blueprint, Template } from '@dxos/blueprints';
|
|
10
|
+
import { Database, Obj, Query } from '@dxos/echo';
|
|
11
|
+
import { TestHelpers } from '@dxos/effect/testing';
|
|
12
|
+
import { Operation, OperationHandlerSet } from '@dxos/operation';
|
|
13
|
+
import { Text } from '@dxos/schema';
|
|
14
|
+
import { Organization } from '@dxos/types';
|
|
15
|
+
import { trim } from '@dxos/util';
|
|
16
|
+
|
|
17
|
+
import { createSystemPrompt } from '../templates/system';
|
|
18
|
+
import { AssistantTestLayer } from '../testing';
|
|
19
|
+
import { formatSystemPrompt } from './format';
|
|
20
|
+
|
|
21
|
+
const OrganizationList = Operation.make({
|
|
22
|
+
meta: {
|
|
23
|
+
key: 'org.dxos.function.organization-list',
|
|
24
|
+
name: 'Organization List',
|
|
25
|
+
description: 'List organizations',
|
|
26
|
+
},
|
|
27
|
+
input: Schema.Struct({}),
|
|
28
|
+
output: Schema.Array(Schema.String),
|
|
29
|
+
services: [Database.Service],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const Handlers = OperationHandlerSet.make(
|
|
33
|
+
Operation.withHandler(
|
|
34
|
+
OrganizationList,
|
|
35
|
+
Effect.fnUntraced(function* () {
|
|
36
|
+
const organizations = yield* Database.runQuery(Query.type(Organization.Organization));
|
|
37
|
+
return organizations.map((organization) => organization.name ?? '<no org>');
|
|
38
|
+
}),
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const TestLayer = AssistantTestLayer({
|
|
43
|
+
types: [Text.Text, Organization.Organization, Blueprint.Blueprint],
|
|
44
|
+
operationHandlers: Handlers,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('format', () => {
|
|
48
|
+
it.effect(
|
|
49
|
+
'should format',
|
|
50
|
+
Effect.fnUntraced(
|
|
51
|
+
function* (_) {
|
|
52
|
+
const { db } = yield* Database.Service;
|
|
53
|
+
const object = db.add(
|
|
54
|
+
Obj.make(Organization.Organization, {
|
|
55
|
+
name: 'Test',
|
|
56
|
+
website: 'https://www.test.com',
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const blueprint = db.add(
|
|
61
|
+
Blueprint.make({
|
|
62
|
+
key: 'example.com/blueprint/test',
|
|
63
|
+
name: 'Test',
|
|
64
|
+
instructions: Template.make({
|
|
65
|
+
source: trim`
|
|
66
|
+
Test
|
|
67
|
+
This is the test blueprint.
|
|
68
|
+
`,
|
|
69
|
+
}),
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const output = yield* formatSystemPrompt({
|
|
74
|
+
system: createSystemPrompt({}),
|
|
75
|
+
blueprints: [blueprint],
|
|
76
|
+
objects: [object],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
console.log(output);
|
|
80
|
+
},
|
|
81
|
+
Effect.provide(TestLayer),
|
|
82
|
+
TestHelpers.provideTestContext,
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
it.effect(
|
|
87
|
+
'should format with function input',
|
|
88
|
+
Effect.fnUntraced(
|
|
89
|
+
function* (_) {
|
|
90
|
+
const { db } = yield* Database.Service;
|
|
91
|
+
const object = db.add(
|
|
92
|
+
Obj.make(Organization.Organization, {
|
|
93
|
+
name: 'Test Org',
|
|
94
|
+
website: 'https://www.test.com',
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const blueprint = db.add(
|
|
99
|
+
Blueprint.make({
|
|
100
|
+
key: 'example.com/blueprint/test',
|
|
101
|
+
name: 'Test',
|
|
102
|
+
instructions: Template.make({
|
|
103
|
+
source: trim`
|
|
104
|
+
Organization List:
|
|
105
|
+
{{#each organizations}}
|
|
106
|
+
- {{this}}
|
|
107
|
+
{{/each}}
|
|
108
|
+
`,
|
|
109
|
+
inputs: [
|
|
110
|
+
{
|
|
111
|
+
name: 'organizations',
|
|
112
|
+
kind: 'function',
|
|
113
|
+
function: OrganizationList.meta.key,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
}),
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const output = yield* formatSystemPrompt({
|
|
121
|
+
system: createSystemPrompt({}),
|
|
122
|
+
blueprints: [blueprint],
|
|
123
|
+
objects: [object],
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(output).to.include('Test Org');
|
|
127
|
+
},
|
|
128
|
+
Effect.provide(TestLayer),
|
|
129
|
+
TestHelpers.provideTestContext,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
});
|