@dxos/assistant 0.8.4-main.b97322e → 0.8.4-main.bcb3aa67d6
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-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-KVYJFFLF.mjs +1805 -0
- package/dist/lib/neutral/chunk-KVYJFFLF.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 +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 +112 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/conversation/context.d.ts +148 -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/conversation.d.ts +53 -25
- 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/conversation/index.d.ts +1 -0
- package/dist/types/src/conversation/index.d.ts.map +1 -1
- 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 +47 -18
- package/dist/types/src/errors.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/types/src/extraction/extraction.d.ts +1060 -273
- 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 +37 -4
- 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 +5 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/service/AgentService.d.ts +62 -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 +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 +12 -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 +4 -0
- 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/session.d.ts +87 -235
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/toolkit.d.ts +18 -0
- package/dist/types/src/session/toolkit.d.ts.map +1 -0
- 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/index.d.ts +1 -1
- 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 +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 +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 +58 -35
- package/src/conversation/context.test.ts +46 -0
- package/src/conversation/context.ts +369 -0
- package/src/conversation/conversation.test.ts +65 -0
- package/src/conversation/conversation.ts +232 -62
- package/src/conversation/index.ts +1 -0
- package/src/diff.test.ts +74 -0
- package/src/diff.ts +93 -0
- package/src/errors.ts +6 -1
- 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 +2 -0
- 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/services.ts +286 -96
- package/src/functions.conversations.json +1 -0
- package/src/functions.test.ts +82 -0
- package/src/index.ts +5 -3
- package/src/service/AgentService.conversations.json +1 -0
- package/src/service/AgentService.test.ts +169 -0
- package/src/service/AgentService.ts +171 -0
- package/src/service/agent-process.ts +360 -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 +133 -0
- package/src/session/format.ts +143 -0
- package/src/session/index.ts +4 -0
- package/src/session/observer.ts +62 -0
- package/src/session/session.conversations.json +1 -0
- package/src/session/session.test.ts +82 -201
- package/src/session/session.ts +282 -270
- package/src/session/toolkit.ts +44 -0
- package/src/session/version-pin.ts +8 -8
- package/src/templates/index.ts +1 -1
- package/src/templates/instructions/format.tpl +57 -0
- package/src/templates/instructions/system.tpl +57 -0
- package/src/templates/system.ts +42 -0
- package/src/testing/index.ts +2 -8
- package/src/testing/layer.ts +263 -0
- package/src/tracing.ts +112 -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 -1831
- 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 -1833
- 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/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 -56
- 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/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 -302
- 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 -12
- package/dist/types/src/research/exa.d.ts.map +0 -1
- package/dist/types/src/research/graph.d.ts +0 -51
- 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/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/templates/prompt.d.ts +0 -30
- package/dist/types/src/templates/prompt.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 -13
- package/dist/types/src/types.d.ts.map +0 -1
- package/src/context/binder.ts +0 -103
- package/src/context/binding.ts +0 -32
- package/src/context/index.ts +0 -6
- package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +0 -83
- package/src/extraction/extraction-llm-function/extraction-llm-function.ts +0 -70
- 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 -57
- package/src/research/exa.ts +0 -97
- package/src/research/graph.test.ts +0 -64
- package/src/research/graph.ts +0 -369
- package/src/research/index.ts +0 -7
- package/src/research/instructions-research.tpl +0 -98
- package/src/research/research.test.ts +0 -190
- package/src/research/research.ts +0 -87
- package/src/session/__snapshots__/session.test.ts.snap +0 -32
- package/src/session/version-pin.test.ts +0 -7
- package/src/summarization/summarization.test.ts +0 -208
- package/src/templates/assistant.tpl +0 -111
- package/src/templates/prompt.ts +0 -45
- 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 -47
- /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
package/src/session/session.ts
CHANGED
|
@@ -2,42 +2,94 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
5
|
+
import type * as AiError from '@effect/ai/AiError';
|
|
6
|
+
import * as LanguageModel from '@effect/ai/LanguageModel';
|
|
7
|
+
import type * as Tool from '@effect/ai/Tool';
|
|
8
|
+
import type * as Toolkit from '@effect/ai/Toolkit';
|
|
9
|
+
import * as Chunk from 'effect/Chunk';
|
|
10
|
+
import * as Effect from 'effect/Effect';
|
|
11
|
+
import * as Stream from 'effect/Stream';
|
|
12
|
+
import * as Array from 'effect/Array';
|
|
13
|
+
import * as Option from 'effect/Option';
|
|
9
14
|
import {
|
|
10
15
|
AiParser,
|
|
11
16
|
AiPreprocessor,
|
|
12
|
-
|
|
13
|
-
runTool,
|
|
14
|
-
ToolExecutionService,
|
|
15
|
-
ToolResolverService,
|
|
16
|
-
type AgentStatus,
|
|
17
|
-
type AiInputPreprocessingError,
|
|
17
|
+
AiSummarizer,
|
|
18
18
|
type AiToolNotFoundError,
|
|
19
|
-
|
|
19
|
+
callTool,
|
|
20
|
+
type PromptPreprocessingError,
|
|
21
|
+
type ToolExecutionService,
|
|
22
|
+
type ToolResolverService,
|
|
23
|
+
withoutToolCallParising,
|
|
20
24
|
} from '@dxos/ai';
|
|
21
|
-
import { Event } from '@dxos/async';
|
|
22
25
|
import { type Blueprint } from '@dxos/blueprints';
|
|
23
|
-
import { todo } from '@dxos/debug';
|
|
24
26
|
import { Obj } from '@dxos/echo';
|
|
25
|
-
import {
|
|
26
|
-
import { type ObjectId } from '@dxos/echo-schema';
|
|
27
|
-
import { DatabaseService } from '@dxos/functions';
|
|
27
|
+
import { type FunctionInvocationService, Trace, TracingService } from '@dxos/functions';
|
|
28
28
|
import { log } from '@dxos/log';
|
|
29
|
-
import {
|
|
29
|
+
import { ContentBlock, Message } from '@dxos/types';
|
|
30
|
+
|
|
31
|
+
import { type AiAssistantError } from '../errors';
|
|
30
32
|
|
|
31
|
-
import {
|
|
33
|
+
import { formatSystemPrompt, formatUserPrompt } from './format';
|
|
34
|
+
import { GenerationObserver } from './observer';
|
|
35
|
+
import { pipe } from 'effect/Function';
|
|
36
|
+
import { CompleteBlock, PartialBlock } from '../tracing';
|
|
32
37
|
|
|
33
|
-
export type
|
|
38
|
+
export type AiSessionRunError = AiError.AiError | PromptPreprocessingError | AiToolNotFoundError | AiAssistantError;
|
|
34
39
|
|
|
35
|
-
export type
|
|
40
|
+
export type AiSessionRunRequirements =
|
|
41
|
+
| LanguageModel.LanguageModel
|
|
42
|
+
| ToolExecutionService
|
|
43
|
+
| ToolResolverService
|
|
44
|
+
| FunctionInvocationService
|
|
45
|
+
| Trace.TraceService
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Retained for backward compatibility with tool handlers that use TracingService.emitStatus().
|
|
48
|
+
* New code should use Trace.TraceService instead.
|
|
49
|
+
*/
|
|
50
|
+
| TracingService;
|
|
51
|
+
|
|
52
|
+
export type AiSessionOptions = {
|
|
53
|
+
/**
|
|
54
|
+
* Summarize before executing the prompt if the existing history exceeds this threshold.
|
|
55
|
+
*/
|
|
56
|
+
summarizationThreshold?: number;
|
|
57
|
+
|
|
58
|
+
// TODO(dmaretskyi): Plan to phase out in favor of TracingService and the return type being a stream.
|
|
59
|
+
observer?: GenerationObserver;
|
|
60
|
+
/**
|
|
61
|
+
* Callback for when a message is received from the user, model, or tool.
|
|
62
|
+
* This is useful for streaming the output to a queue.
|
|
63
|
+
*/
|
|
64
|
+
onOutput?: (message: Message.Message) => Effect.Effect<void, never, never>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type AiSessionRunProps<Tools extends Record<string, Tool.Any>> = {
|
|
36
68
|
prompt: string;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
69
|
+
// TODO(wittjosiah): Rename to systemPrompt.
|
|
70
|
+
system?: string;
|
|
71
|
+
history?: Message.Message[];
|
|
72
|
+
objects?: Obj.Unknown[];
|
|
73
|
+
blueprints?: readonly Blueprint.Blueprint[];
|
|
74
|
+
toolkit?: Toolkit.WithHandler<Tools>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type AiSessionBeginProps = {
|
|
78
|
+
prompt: string;
|
|
79
|
+
system?: string;
|
|
80
|
+
history?: Message.Message[];
|
|
81
|
+
objects?: Obj.Unknown[];
|
|
82
|
+
blueprints?: readonly Blueprint.Blueprint[];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type AiSessionTurnProps<Tools extends Record<string, Tool.Any>> = {
|
|
86
|
+
system: string;
|
|
87
|
+
toolkit?: Toolkit.WithHandler<Tools>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type AiSessionTurnResult = {
|
|
91
|
+
messages: Message.Message[];
|
|
92
|
+
done: boolean;
|
|
41
93
|
};
|
|
42
94
|
|
|
43
95
|
/**
|
|
@@ -50,287 +102,247 @@ export type SessionRunOptions<Tools extends AiTool.Any> = {
|
|
|
50
102
|
* Could be run locally in the app or remotely.
|
|
51
103
|
* Could be personal or shared.
|
|
52
104
|
*/
|
|
105
|
+
// TODO(dmaretskyi): Rename AiSessionRequest
|
|
53
106
|
export class AiSession {
|
|
54
|
-
|
|
107
|
+
/** Prevents concurrent execution of session. */
|
|
55
108
|
private readonly _semaphore = Effect.runSync(Effect.makeSemaphore(1));
|
|
56
109
|
|
|
57
|
-
|
|
58
|
-
private
|
|
110
|
+
private readonly _observer: GenerationObserver;
|
|
111
|
+
private readonly _onOutput: (message: Message.Message) => Effect.Effect<void, never, never>;
|
|
59
112
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
113
|
+
/** Message history from queue. */
|
|
114
|
+
// TODO(burdon): Evolve into supporting a git-like graph of messages.
|
|
115
|
+
private _history: Message.Message[] = [];
|
|
62
116
|
|
|
63
|
-
/**
|
|
64
|
-
private
|
|
117
|
+
/** Pending messages for this session (incl. the current prompt). */
|
|
118
|
+
private _pending: Message.Message[] = [];
|
|
65
119
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
public readonly message = new Event<DataType.Message>();
|
|
120
|
+
private _started = 0;
|
|
121
|
+
private _ended = 0;
|
|
122
|
+
private _toolCalls = 0;
|
|
70
123
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
124
|
+
constructor(private readonly _options: AiSessionOptions = {}) {
|
|
125
|
+
this._observer = _options.observer ?? GenerationObserver.noop();
|
|
126
|
+
this._onOutput = _options.onOutput ?? (() => Effect.void);
|
|
127
|
+
}
|
|
75
128
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
public readonly update = new Event<ContentBlock.Any>();
|
|
129
|
+
get duration(): number {
|
|
130
|
+
return this._ended - this._started;
|
|
131
|
+
}
|
|
80
132
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
public readonly streamEvent = new Event<AiResponse.Part>();
|
|
133
|
+
get toolCalls(): number {
|
|
134
|
+
return this._toolCalls;
|
|
135
|
+
}
|
|
85
136
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
public readonly userMessage = new Event<DataType.Message>();
|
|
137
|
+
get pending(): readonly Message.Message[] {
|
|
138
|
+
return this._pending;
|
|
139
|
+
}
|
|
90
140
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
141
|
+
private _submitMessage = (message: Message.Message): Effect.Effect<Message.Message, never, Trace.TraceService> =>
|
|
142
|
+
Effect.gen(this, function* () {
|
|
143
|
+
this._pending.push(message);
|
|
144
|
+
yield* this._observer.onMessage(message);
|
|
145
|
+
for (const block of message.blocks) {
|
|
146
|
+
yield* Trace.write(CompleteBlock, {
|
|
147
|
+
messageId: message.id,
|
|
148
|
+
role: message.sender.role!,
|
|
149
|
+
block,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
yield* this._onOutput(message);
|
|
153
|
+
return message;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
getToolCalls = () =>
|
|
157
|
+
pipe(
|
|
158
|
+
[...this._history, ...this._pending],
|
|
159
|
+
Array.reverse,
|
|
160
|
+
Array.takeWhile((_) => _.sender.role === 'assistant'),
|
|
161
|
+
Array.flatMap((_) => _.blocks.filter(ContentBlock.is('toolCall')).map((block) => ({ block, message: _ }))),
|
|
162
|
+
Array.filter((_) => !_.block.providerExecuted),
|
|
163
|
+
);
|
|
96
164
|
|
|
97
165
|
/**
|
|
98
|
-
*
|
|
166
|
+
* Initialize a session: set up history, perform summarization if needed, and submit the user prompt.
|
|
167
|
+
* Must be called before `runTurn()`.
|
|
99
168
|
*/
|
|
100
|
-
|
|
169
|
+
begin = ({
|
|
170
|
+
prompt,
|
|
171
|
+
system,
|
|
172
|
+
history = [],
|
|
173
|
+
blueprints = [],
|
|
174
|
+
objects = [],
|
|
175
|
+
}: AiSessionBeginProps): Effect.Effect<void, AiSessionRunError, AiSessionRunRequirements> =>
|
|
176
|
+
Effect.gen(this, function* () {
|
|
177
|
+
this._started = Date.now();
|
|
178
|
+
this._history = [...history];
|
|
179
|
+
this._pending = [];
|
|
180
|
+
|
|
181
|
+
const systemPrompt = yield* formatSystemPrompt({ system, blueprints, objects }).pipe(Effect.orDie);
|
|
182
|
+
|
|
183
|
+
if (this._options.summarizationThreshold !== undefined) {
|
|
184
|
+
const tokenCount = yield* AiPreprocessor.estimateTokens(
|
|
185
|
+
yield* AiPreprocessor.preprocessPrompt([...this._history], {
|
|
186
|
+
system: systemPrompt,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
if (tokenCount > this._options.summarizationThreshold) {
|
|
190
|
+
const summary = yield* AiSummarizer.summarize([...this._history]);
|
|
191
|
+
yield* this._submitMessage(summary);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
101
194
|
|
|
102
|
-
|
|
195
|
+
yield* this._submitMessage(yield* formatUserPrompt({ prompt, history }));
|
|
196
|
+
});
|
|
103
197
|
|
|
104
198
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* @returns The messages generated by the session, including the user's prompt.
|
|
199
|
+
* Execute a single turn: one LLM generation followed by tool execution.
|
|
200
|
+
* The toolkit and system prompt can be updated between turns to reflect context changes (e.g. dynamically enabled blueprints).
|
|
108
201
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
): Effect.Effect<
|
|
113
|
-
DataType.Message[],
|
|
114
|
-
AiAssistantError | AiInputPreprocessingError | AiToolNotFoundError | AiError.AiError,
|
|
115
|
-
AiLanguageModel.AiLanguageModel | ToolResolverService | ToolExecutionService | AiTool.ToHandler<Tools>
|
|
116
|
-
> =>
|
|
202
|
+
runAgentTurn = <Tools extends Record<string, Tool.Any>>({
|
|
203
|
+
system,
|
|
204
|
+
toolkit,
|
|
205
|
+
}: AiSessionTurnProps<Tools>): Effect.Effect<AiSessionTurnResult, AiSessionRunError, AiSessionRunRequirements> =>
|
|
117
206
|
Effect.gen(this, function* () {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
207
|
+
log('request', {
|
|
208
|
+
system: { snippet: createSnippet(system), length: system.length },
|
|
209
|
+
pending: this._pending.length,
|
|
210
|
+
history: this._history.length,
|
|
211
|
+
});
|
|
123
212
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
history: this._history.length,
|
|
129
|
-
tools: (Object.values(options.toolkit?.tools ?? {}) as AiTool.Any[]).map((tool: AiTool.Any) => tool.name),
|
|
130
|
-
});
|
|
213
|
+
const prompt = yield* AiPreprocessor.preprocessPrompt([...this._history, ...this._pending], {
|
|
214
|
+
system,
|
|
215
|
+
cacheControl: 'ephemeral',
|
|
216
|
+
});
|
|
131
217
|
|
|
132
|
-
|
|
218
|
+
const observer = this._observer;
|
|
219
|
+
let currentMessageId: Obj.ID | null = null; // Consistent IDs for pending blocks preceding the complete one.
|
|
220
|
+
const messages = yield* LanguageModel.streamText({
|
|
221
|
+
prompt,
|
|
222
|
+
toolkit,
|
|
223
|
+
disableToolCallResolution: true,
|
|
224
|
+
}).pipe(
|
|
225
|
+
withoutToolCallParising,
|
|
226
|
+
AiParser.parseResponse({
|
|
227
|
+
emitPartial: true,
|
|
228
|
+
onBegin: () => observer.onBegin(),
|
|
229
|
+
onBlock: (block) => observer.onBlock(block),
|
|
230
|
+
onPart: (part) => observer.onPart(part as any),
|
|
231
|
+
onEnd: (summary) => observer.onEnd(summary),
|
|
232
|
+
}),
|
|
233
|
+
Stream.mapEffect(
|
|
234
|
+
(block) =>
|
|
235
|
+
Effect.gen(this, function* () {
|
|
236
|
+
if (block.pending) {
|
|
237
|
+
currentMessageId ??= Obj.ID.random();
|
|
238
|
+
log('emit ephemeral message', { id: currentMessageId, type: block._tag });
|
|
239
|
+
yield* Trace.write(PartialBlock, {
|
|
240
|
+
messageId: currentMessageId,
|
|
241
|
+
role: 'assistant',
|
|
242
|
+
block,
|
|
243
|
+
});
|
|
244
|
+
return Option.none();
|
|
245
|
+
} else {
|
|
246
|
+
currentMessageId ??= Obj.ID.random();
|
|
247
|
+
const id = currentMessageId;
|
|
248
|
+
currentMessageId = null;
|
|
249
|
+
log('emit complete message', { id, type: block._tag });
|
|
250
|
+
const message = Obj.make(Message.Message, {
|
|
251
|
+
id,
|
|
252
|
+
created: new Date().toISOString(),
|
|
253
|
+
sender: { role: 'assistant' },
|
|
254
|
+
blocks: [block],
|
|
255
|
+
});
|
|
256
|
+
return Option.some(yield* this._submitMessage(message));
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
259
|
+
{ concurrency: 1, unordered: false },
|
|
260
|
+
),
|
|
261
|
+
Stream.filterMap((_) => _),
|
|
262
|
+
Stream.runCollect,
|
|
263
|
+
Effect.map(Chunk.toArray),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const toolCalls = this.getToolCalls();
|
|
267
|
+
|
|
268
|
+
if (toolCalls.length === 0) {
|
|
269
|
+
this._ended = Date.now();
|
|
270
|
+
return { messages, done: true };
|
|
271
|
+
} else if (!toolkit) {
|
|
272
|
+
throw new Error('No toolkit provided');
|
|
273
|
+
}
|
|
133
274
|
|
|
134
|
-
|
|
275
|
+
return { messages, done: false };
|
|
276
|
+
});
|
|
135
277
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
278
|
+
runTools = <Tools extends Record<string, Tool.Any>>({ toolkit }: { toolkit?: Toolkit.WithHandler<Tools> }) =>
|
|
279
|
+
Effect.gen(this, function* () {
|
|
280
|
+
const toolCalls = this.getToolCalls();
|
|
281
|
+
// TODO(burdon): Retry errors? Write result when each completes individually?
|
|
282
|
+
const toolResults = yield* Effect.forEach(toolCalls, ({ block, message }) => {
|
|
283
|
+
if (!toolkit) {
|
|
284
|
+
throw new Error('No toolkit provided');
|
|
285
|
+
}
|
|
286
|
+
return callTool(toolkit, block).pipe(
|
|
287
|
+
Effect.provide(
|
|
288
|
+
TracingService.layerSubframe((context) => ({
|
|
289
|
+
...context,
|
|
290
|
+
parentMessage: message.id,
|
|
291
|
+
toolCallId: block.toolCallId,
|
|
292
|
+
})),
|
|
293
|
+
),
|
|
144
294
|
);
|
|
295
|
+
});
|
|
145
296
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
log.info('run', {
|
|
156
|
-
systemPrompt: [systemPrompt.slice(0, 32), '...', systemPrompt.slice(-32), systemPrompt.length].join(' '),
|
|
157
|
-
tools: Object.keys(blueprintToolkit.tools),
|
|
158
|
-
});
|
|
297
|
+
// TODO(wittjosiah): Sometimes tool error results are added to the queue before the tool agent statuses.
|
|
298
|
+
// TODO(dmaretskyi): Stream tool results one by one.
|
|
299
|
+
yield* this._submitMessage(
|
|
300
|
+
Obj.make(Message.Message, {
|
|
301
|
+
created: new Date().toISOString(),
|
|
302
|
+
sender: { role: 'tool' },
|
|
303
|
+
blocks: toolResults,
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
159
306
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
onPart: (part) =>
|
|
176
|
-
Effect.gen(this, function* () {
|
|
177
|
-
this.streamEvent.emit(part);
|
|
178
|
-
}),
|
|
179
|
-
}),
|
|
180
|
-
Stream.runCollect,
|
|
181
|
-
Effect.map(Chunk.toArray),
|
|
182
|
-
);
|
|
307
|
+
this._toolCalls += toolResults.length;
|
|
308
|
+
});
|
|
309
|
+
/**
|
|
310
|
+
* Run a full conversation turn loop. Equivalent to calling `begin()` then `runTurn()` in a loop.
|
|
311
|
+
*/
|
|
312
|
+
run = <Tools extends Record<string, Tool.Any>>({
|
|
313
|
+
prompt,
|
|
314
|
+
system: systemTemplate,
|
|
315
|
+
history = [],
|
|
316
|
+
objects = [],
|
|
317
|
+
blueprints = [],
|
|
318
|
+
toolkit,
|
|
319
|
+
}: AiSessionRunProps<Tools>): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> =>
|
|
320
|
+
Effect.gen(this, function* () {
|
|
321
|
+
yield* this.begin({ prompt, system: systemTemplate, history, objects, blueprints });
|
|
183
322
|
|
|
184
|
-
|
|
185
|
-
created: new Date().toISOString(),
|
|
186
|
-
sender: { role: 'assistant' },
|
|
187
|
-
blocks,
|
|
188
|
-
});
|
|
189
|
-
this._pending.push(response);
|
|
190
|
-
this.message.emit(response);
|
|
323
|
+
const system = yield* formatSystemPrompt({ system: systemTemplate, blueprints, objects }).pipe(Effect.orDie);
|
|
191
324
|
|
|
192
|
-
|
|
193
|
-
|
|
325
|
+
do {
|
|
326
|
+
const { done } = yield* this.runAgentTurn({ system, toolkit });
|
|
327
|
+
if (done) {
|
|
194
328
|
break;
|
|
195
329
|
}
|
|
196
|
-
|
|
197
|
-
const toolResults = yield* runTools(toolCalls, toolkitWithBlueprintHandlers as any);
|
|
198
|
-
this._pending.push(
|
|
199
|
-
Obj.make(DataType.Message, {
|
|
200
|
-
created: new Date().toISOString(),
|
|
201
|
-
sender: { role: 'user' },
|
|
202
|
-
blocks: toolResults,
|
|
203
|
-
}),
|
|
204
|
-
);
|
|
330
|
+
yield* this.runTools({ toolkit });
|
|
205
331
|
} while (true);
|
|
206
332
|
|
|
333
|
+
log('done', { pending: this._pending.length, duration: this.duration, tools: this._toolCalls });
|
|
207
334
|
return this._pending;
|
|
208
335
|
}).pipe(this._semaphore.withPermits(1), Effect.withSpan('AiSession.run'));
|
|
209
|
-
|
|
210
|
-
async runStructured<S extends Schema.Schema.AnyNoContext>(
|
|
211
|
-
schema: S,
|
|
212
|
-
options: SessionRunOptions<AiTool.Any>,
|
|
213
|
-
): Promise<Schema.Schema.Type<S>> {
|
|
214
|
-
return todo();
|
|
215
|
-
// const parser = structuredOutputParser(schema);
|
|
216
|
-
// const result = await this.run({
|
|
217
|
-
// ...options,
|
|
218
|
-
// executableTools: [...(options.executableTools ?? []), parser.tool],
|
|
219
|
-
// });
|
|
220
|
-
// return parser.getResult(result);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
private _formatUserPrompt = (prompt: string, history: DataType.Message[]) =>
|
|
224
|
-
Effect.gen(function* () {
|
|
225
|
-
const prelude: ContentBlock.Any[] = [];
|
|
226
|
-
|
|
227
|
-
// TODO(dmaretskyi): Evaluate other approaches as `serviceOption` isn't represented in the type system.
|
|
228
|
-
const artifactDiffResolver = yield* Effect.serviceOption(ArtifactDiffResolver);
|
|
229
|
-
if (Option.isSome(artifactDiffResolver)) {
|
|
230
|
-
const versions = gatherObjectVersions(history);
|
|
231
|
-
|
|
232
|
-
const artifactDiff = yield* Effect.tryPromise({
|
|
233
|
-
try: () =>
|
|
234
|
-
artifactDiffResolver.value.resolve(
|
|
235
|
-
[...versions.entries()].map(([id, version]) => ({ id, lastVersion: version })),
|
|
236
|
-
),
|
|
237
|
-
catch: AiAssistantError.wrap('Artifact diff resolution error'),
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
log.info('vision', {
|
|
241
|
-
artifactDiff,
|
|
242
|
-
versions,
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
for (const [id, { version }] of [...artifactDiff.entries()]) {
|
|
246
|
-
if (ObjectVersion.equals(version, versions.get(id)!)) {
|
|
247
|
-
artifactDiff.delete(id);
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
prelude.push({ _tag: 'anchor', objectId: id, version });
|
|
252
|
-
}
|
|
253
|
-
if (artifactDiff.size > 0) {
|
|
254
|
-
prelude.push(createArtifactUpdateBlock(artifactDiff));
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return Obj.make(DataType.Message, {
|
|
259
|
-
created: new Date().toISOString(),
|
|
260
|
-
sender: { role: 'user' },
|
|
261
|
-
blocks: [...prelude, { _tag: 'text', text: prompt }],
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
abort(): void {
|
|
266
|
-
this._stream?.abort();
|
|
267
|
-
}
|
|
268
336
|
}
|
|
269
337
|
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
for (const message of messages) {
|
|
273
|
-
for (const block of message.blocks) {
|
|
274
|
-
if (block._tag === 'anchor') {
|
|
275
|
-
artifactIds.set(block.objectId, block.version as ObjectVersion);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return artifactIds;
|
|
281
|
-
};
|
|
338
|
+
const createSnippet = (text: string, len = 32) =>
|
|
339
|
+
text.length <= len * 2 ? text : [text.slice(0, len), '...', text.slice(-len)].join('');
|
|
282
340
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
text: `
|
|
291
|
-
The following artifacts have been updated since the last message:
|
|
292
|
-
${[...artifactDiff.entries()]
|
|
293
|
-
.map(([id, { diff }]) => `<changed-artifact id="${id}">${diff ? `\n${diff}` : ''}</changed-artifact>`)
|
|
294
|
-
.join('\n')}
|
|
295
|
-
`,
|
|
296
|
-
};
|
|
341
|
+
// TODO(dmaretskyi): Extract as a general util.
|
|
342
|
+
const logDump = (message: string, data: unknown) => {
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
344
|
+
const { writeFileSync } = require('node:fs');
|
|
345
|
+
const path = `/tmp/log-data-${Date.now()}.json`;
|
|
346
|
+
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
347
|
+
log.info(message, { path });
|
|
297
348
|
};
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Resolves artifact ids to their versions.
|
|
301
|
-
* Used to give the model a sense of the changes to the artifacts made by users during the conversation.
|
|
302
|
-
* The artifacts versions are pinned in the history, and whenever the artifact changes in-between assistant's steps,
|
|
303
|
-
* a diff is inserted into the conversation.
|
|
304
|
-
*
|
|
305
|
-
* Can be optionally provided to the session run call.
|
|
306
|
-
*/
|
|
307
|
-
// TODO(dmaretskyi): Convert to Context.Reference
|
|
308
|
-
export class ArtifactDiffResolver extends Context.Tag('ArtifactDiffResolver')<
|
|
309
|
-
ArtifactDiffResolver,
|
|
310
|
-
ArtifactDiffResolver.Service
|
|
311
|
-
>() {}
|
|
312
|
-
|
|
313
|
-
export namespace ArtifactDiffResolver {
|
|
314
|
-
export type Service = {
|
|
315
|
-
resolve: (artifacts: { id: ObjectId; lastVersion: ObjectVersion }[]) => Promise<
|
|
316
|
-
Map<
|
|
317
|
-
ObjectId,
|
|
318
|
-
{
|
|
319
|
-
version: ObjectVersion;
|
|
320
|
-
diff?: string;
|
|
321
|
-
}
|
|
322
|
-
>
|
|
323
|
-
>;
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const runTools: <Tools extends AiTool.Any>(
|
|
328
|
-
toolCalls: ContentBlock.ToolCall[],
|
|
329
|
-
toolkit: AiToolkit.AiToolkit<Tools>,
|
|
330
|
-
) => Effect.Effect<ContentBlock.ToolResult[], AiError.AiError, AiTool.ToHandler<Tools>> = Effect.fn('runTools')(
|
|
331
|
-
function* (toolCalls, toolkit) {
|
|
332
|
-
const toolkitWithHandlers = Effect.isEffect(toolkit) ? yield* toolkit : toolkit;
|
|
333
|
-
|
|
334
|
-
return yield* Effect.forEach(toolCalls, (toolCall) => runTool(toolkitWithHandlers, toolCall));
|
|
335
|
-
},
|
|
336
|
-
);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type * as Tool from '@effect/ai/Tool';
|
|
6
|
+
import * as Toolkit from '@effect/ai/Toolkit';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
|
|
9
|
+
import { type AiToolNotFoundError, GenericToolkit, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
10
|
+
import { type Blueprint } from '@dxos/blueprints';
|
|
11
|
+
import { isTruthy } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
export type CreateToolkitProps = {
|
|
14
|
+
toolkit?: Toolkit.Any;
|
|
15
|
+
blueprints?: readonly Blueprint.Blueprint[];
|
|
16
|
+
/**
|
|
17
|
+
* Self-contained with handlers toolkits.
|
|
18
|
+
*/
|
|
19
|
+
genericToolkits?: readonly GenericToolkit.GenericToolkit[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build a combined toolkit from the blueprint tools and the provided toolkit.
|
|
24
|
+
*/
|
|
25
|
+
export const createToolkit = ({
|
|
26
|
+
toolkit: toolkitProp,
|
|
27
|
+
blueprints = [],
|
|
28
|
+
genericToolkits = [],
|
|
29
|
+
}: CreateToolkitProps): Effect.Effect<
|
|
30
|
+
Toolkit.WithHandler<any>,
|
|
31
|
+
AiToolNotFoundError,
|
|
32
|
+
ToolResolverService | ToolExecutionService | Tool.HandlersFor<any>
|
|
33
|
+
> =>
|
|
34
|
+
Effect.gen(function* () {
|
|
35
|
+
const blueprintToolkit = yield* ToolResolverService.resolveToolkit(blueprints.flatMap(({ tools }) => tools));
|
|
36
|
+
const blueprintToolHandler = yield* blueprintToolkit.toContext(ToolExecutionService.handlersFor(blueprintToolkit));
|
|
37
|
+
const genericToolkit = GenericToolkit.merge(...genericToolkits);
|
|
38
|
+
|
|
39
|
+
const toolkit = Toolkit.merge(...[toolkitProp, blueprintToolkit, genericToolkit.toolkit].filter(isTruthy));
|
|
40
|
+
return yield* toolkit.pipe(
|
|
41
|
+
Effect.provide(blueprintToolHandler),
|
|
42
|
+
Effect.provide(genericToolkit.layer),
|
|
43
|
+
) as any as Effect.Effect<Toolkit.WithHandler<any>, never, Tool.HandlersFor<any>>;
|
|
44
|
+
});
|