effect-assistant 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +22 -2
- package/dist/AppEvent.d.ts +111 -0
- package/dist/AppEvent.d.ts.map +1 -0
- package/dist/AppEvent.js +71 -0
- package/dist/AppEvent.js.map +1 -0
- package/dist/Assistant.d.ts +403 -0
- package/dist/Assistant.d.ts.map +1 -0
- package/dist/Assistant.js +126 -0
- package/dist/Assistant.js.map +1 -0
- package/dist/AssistantActivity.d.ts +85 -0
- package/dist/AssistantActivity.d.ts.map +1 -0
- package/dist/AssistantActivity.js +105 -0
- package/dist/AssistantActivity.js.map +1 -0
- package/dist/AssistantEngine.d.ts +116 -0
- package/dist/AssistantEngine.d.ts.map +1 -0
- package/dist/AssistantEngine.js +255 -0
- package/dist/AssistantEngine.js.map +1 -0
- package/dist/AssistantEvent.d.ts +551 -0
- package/dist/AssistantEvent.d.ts.map +1 -0
- package/dist/AssistantEvent.js +473 -0
- package/dist/AssistantEvent.js.map +1 -0
- package/dist/AssistantLoop.d.ts +173 -0
- package/dist/AssistantLoop.d.ts.map +1 -0
- package/dist/AssistantLoop.js +111 -0
- package/dist/AssistantLoop.js.map +1 -0
- package/dist/AssistantToolCall.d.ts +32 -0
- package/dist/AssistantToolCall.d.ts.map +1 -0
- package/dist/AssistantToolCall.js +37 -0
- package/dist/AssistantToolCall.js.map +1 -0
- package/dist/Block.d.ts +69 -0
- package/dist/Block.d.ts.map +1 -0
- package/dist/Block.js +39 -0
- package/dist/Block.js.map +1 -0
- package/dist/BlockKit.d.ts +180 -0
- package/dist/BlockKit.d.ts.map +1 -0
- package/dist/BlockKit.js +148 -0
- package/dist/BlockKit.js.map +1 -0
- package/dist/ChatId.d.ts +31 -0
- package/dist/ChatId.d.ts.map +1 -0
- package/dist/ChatId.js +36 -0
- package/dist/ChatId.js.map +1 -0
- package/dist/ChatLog.d.ts +109 -0
- package/dist/ChatLog.d.ts.map +1 -0
- package/dist/ChatLog.js +177 -0
- package/dist/ChatLog.js.map +1 -0
- package/dist/ChatView.d.ts +408 -0
- package/dist/ChatView.d.ts.map +1 -0
- package/dist/ChatView.js +443 -0
- package/dist/ChatView.js.map +1 -0
- package/dist/Chats.d.ts +110 -0
- package/dist/Chats.d.ts.map +1 -0
- package/dist/Chats.js +98 -0
- package/dist/Chats.js.map +1 -0
- package/dist/Compaction.d.ts +105 -0
- package/dist/Compaction.d.ts.map +1 -0
- package/dist/Compaction.js +126 -0
- package/dist/Compaction.js.map +1 -0
- package/dist/CurrentChat.d.ts +40 -0
- package/dist/CurrentChat.d.ts.map +1 -0
- package/dist/CurrentChat.js +33 -0
- package/dist/CurrentChat.js.map +1 -0
- package/dist/InputId.d.ts +38 -0
- package/dist/InputId.d.ts.map +1 -0
- package/dist/InputId.js +43 -0
- package/dist/InputId.js.map +1 -0
- package/dist/Instructions.d.ts +56 -0
- package/dist/Instructions.d.ts.map +1 -0
- package/dist/Instructions.js +50 -0
- package/dist/Instructions.js.map +1 -0
- package/dist/Memory.d.ts +190 -0
- package/dist/Memory.d.ts.map +1 -0
- package/dist/Memory.js +216 -0
- package/dist/Memory.js.map +1 -0
- package/dist/RunId.d.ts +41 -0
- package/dist/RunId.d.ts.map +1 -0
- package/dist/RunId.js +46 -0
- package/dist/RunId.js.map +1 -0
- package/dist/Skill.d.ts +72 -0
- package/dist/Skill.d.ts.map +1 -0
- package/dist/Skill.js +58 -0
- package/dist/Skill.js.map +1 -0
- package/dist/Specialist.d.ts +267 -0
- package/dist/Specialist.d.ts.map +1 -0
- package/dist/Specialist.js +119 -0
- package/dist/Specialist.js.map +1 -0
- package/dist/ToolDiscovery.d.ts +107 -0
- package/dist/ToolDiscovery.d.ts.map +1 -0
- package/dist/ToolDiscovery.js +140 -0
- package/dist/ToolDiscovery.js.map +1 -0
- package/dist/ToolFailure.d.ts +66 -0
- package/dist/ToolFailure.d.ts.map +1 -0
- package/dist/ToolFailure.js +79 -0
- package/dist/ToolFailure.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -0
- package/dist/internal/chat.js +177 -0
- package/dist/internal/chat.js.map +1 -0
- package/dist/internal/events.d.ts +46 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +65 -0
- package/dist/internal/events.js.map +1 -0
- package/dist/internal/history.js +147 -0
- package/dist/internal/history.js.map +1 -0
- package/dist/internal/instructions.js +11 -0
- package/dist/internal/instructions.js.map +1 -0
- package/dist/internal/loop.d.ts +73 -0
- package/dist/internal/loop.d.ts.map +1 -0
- package/dist/internal/loop.js +131 -0
- package/dist/internal/loop.js.map +1 -0
- package/dist/internal/schema.js +14 -0
- package/dist/internal/schema.js.map +1 -0
- package/dist/internal/skills.js +28 -0
- package/dist/internal/skills.js.map +1 -0
- package/dist/internal/specialists.js +272 -0
- package/dist/internal/specialists.js.map +1 -0
- package/dist/internal/telemetry.js +85 -0
- package/dist/internal/telemetry.js.map +1 -0
- package/dist/internal/toolkit.d.ts +62 -0
- package/dist/internal/toolkit.d.ts.map +1 -0
- package/dist/internal/toolkit.js +108 -0
- package/dist/internal/toolkit.js.map +1 -0
- package/dist/internal/tools.js +113 -0
- package/dist/internal/tools.js.map +1 -0
- package/dist/internal/turns.d.ts +64 -0
- package/dist/internal/turns.d.ts.map +1 -0
- package/dist/internal/turns.js +214 -0
- package/dist/internal/turns.js.map +1 -0
- package/dist/internal/uuid.js +45 -0
- package/dist/internal/uuid.js.map +1 -0
- package/dist/internal/writer.d.ts +12 -0
- package/dist/internal/writer.d.ts.map +1 -0
- package/package.json +52 -3
- package/src/AppEvent.ts +134 -0
- package/src/Assistant.ts +589 -0
- package/src/AssistantActivity.ts +150 -0
- package/src/AssistantEngine.ts +343 -0
- package/src/AssistantEvent.ts +569 -0
- package/src/AssistantLoop.ts +209 -0
- package/src/AssistantToolCall.ts +40 -0
- package/src/Block.ts +84 -0
- package/src/BlockKit.ts +298 -0
- package/src/ChatId.ts +36 -0
- package/src/ChatLog.ts +247 -0
- package/src/ChatView.ts +587 -0
- package/src/Chats.ts +122 -0
- package/src/Compaction.ts +207 -0
- package/src/CurrentChat.ts +47 -0
- package/src/InputId.ts +43 -0
- package/src/Instructions.ts +72 -0
- package/src/Memory.ts +276 -0
- package/src/RunId.ts +46 -0
- package/src/Skill.ts +91 -0
- package/src/Specialist.ts +355 -0
- package/src/ToolDiscovery.ts +179 -0
- package/src/ToolFailure.ts +74 -0
- package/src/index.ts +120 -0
- package/src/internal/chat.ts +323 -0
- package/src/internal/events.ts +100 -0
- package/src/internal/history.ts +294 -0
- package/src/internal/instructions.ts +9 -0
- package/src/internal/loop.ts +274 -0
- package/src/internal/schema.ts +25 -0
- package/src/internal/skills.ts +37 -0
- package/src/internal/specialists.ts +410 -0
- package/src/internal/telemetry.ts +125 -0
- package/src/internal/toolkit.ts +188 -0
- package/src/internal/tools.ts +159 -0
- package/src/internal/turns.ts +410 -0
- package/src/internal/uuid.ts +50 -0
- package/src/internal/writer.ts +12 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { ChatId } from "./ChatId.js";
|
|
3
|
+
import { Entry, Input, StoredAppEvent } from "./AssistantEvent.js";
|
|
4
|
+
import { ChatLog } from "./ChatLog.js";
|
|
5
|
+
import { needsTurn } from "./internal/history.js";
|
|
6
|
+
import { make } from "./internal/chat.js";
|
|
7
|
+
import * as Predicate from "effect/Predicate";
|
|
8
|
+
import * as Context from "effect/Context";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Layer from "effect/Layer";
|
|
11
|
+
import * as Schema from "effect/Schema";
|
|
12
|
+
import * as Stream from "effect/Stream";
|
|
13
|
+
import * as ClusterSchema from "effect/cluster/ClusterSchema";
|
|
14
|
+
import * as Entity from "effect/cluster/Entity";
|
|
15
|
+
import * as Sharding from "effect/cluster/Sharding";
|
|
16
|
+
import * as Data from "effect/Data";
|
|
17
|
+
import * as Exit from "effect/Exit";
|
|
18
|
+
import * as MutableHashMap from "effect/MutableHashMap";
|
|
19
|
+
import * as Option from "effect/Option";
|
|
20
|
+
import * as RcMap from "effect/RcMap";
|
|
21
|
+
import * as Rpc from "effect/rpc/Rpc";
|
|
22
|
+
import * as Scope from "effect/Scope";
|
|
23
|
+
//#region src/AssistantEngine.ts
|
|
24
|
+
/**
|
|
25
|
+
* This module provides the `AssistantEngine` service, which runs assistants' chats, and its two
|
|
26
|
+
* implementations.
|
|
27
|
+
*
|
|
28
|
+
* An assistant's `toLayer` registers the assistant with the engine, and the assistant's `submit`
|
|
29
|
+
* and `subscribe` go through it. Pick one engine for the app, as with a workflow engine.
|
|
30
|
+
* {@link layerMemory} runs chats in the process, for tests and single-process apps.
|
|
31
|
+
* {@link layerCluster} runs each chat as a cluster entity, so a turn cut off by a crash resumes.
|
|
32
|
+
*
|
|
33
|
+
* @since 0.1.0
|
|
34
|
+
*/
|
|
35
|
+
var AssistantEngine_exports = /* @__PURE__ */ __exportAll({
|
|
36
|
+
AssistantEngine: () => AssistantEngine,
|
|
37
|
+
layerCluster: () => layerCluster,
|
|
38
|
+
layerMemory: () => layerMemory
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Service that runs assistants' chats. An assistant's `toLayer` registers the assistant with it,
|
|
42
|
+
* and the assistant's `submit` and `subscribe` use it.
|
|
43
|
+
*
|
|
44
|
+
* @category services
|
|
45
|
+
* @since 0.1.0
|
|
46
|
+
*/
|
|
47
|
+
var AssistantEngine = class extends Context.Service()("effect-assistant/AssistantEngine") {};
|
|
48
|
+
var ChatKey = class extends Data.Class {};
|
|
49
|
+
const notRegistered = (assistant) => Effect.die(`The ${assistant} assistant isn't registered: provide its toLayer.`);
|
|
50
|
+
/**
|
|
51
|
+
* Layer that runs chats in the current process. Each chat's turns run as fibers, one at a time.
|
|
52
|
+
*
|
|
53
|
+
* **Details**
|
|
54
|
+
*
|
|
55
|
+
* With `ChatLog.layerSql`, the engine resumes chats at startup. When an assistant registers, the
|
|
56
|
+
* engine finds the chats that assistant was working on when the process stopped (a turn cut off
|
|
57
|
+
* partway, or an input that no turn read) and runs a turn for each. Run this engine in one process
|
|
58
|
+
* only, because two processes would run the same chat and write its log at the same time.
|
|
59
|
+
*
|
|
60
|
+
* **Example** (Running chats in memory)
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* import { Assistant, AssistantEngine, ChatLog } from "effect-assistant"
|
|
64
|
+
* import type * as LanguageModel from "effect/ai/LanguageModel"
|
|
65
|
+
* import * as Layer from "effect/Layer"
|
|
66
|
+
*
|
|
67
|
+
* declare const ModelLive: Layer.Layer<LanguageModel.LanguageModel>
|
|
68
|
+
*
|
|
69
|
+
* const MyAssistant = Assistant.make("my-assistant")
|
|
70
|
+
*
|
|
71
|
+
* const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
|
|
72
|
+
* Layer.provide(ModelLive),
|
|
73
|
+
* Layer.provideMerge(AssistantEngine.layerMemory),
|
|
74
|
+
* Layer.provideMerge(ChatLog.layerMemory)
|
|
75
|
+
* )
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @see {@link layerCluster} for running each chat as a cluster entity
|
|
79
|
+
*
|
|
80
|
+
* @category layers
|
|
81
|
+
* @since 0.1.0
|
|
82
|
+
*/
|
|
83
|
+
const layerMemory = Layer.effect(AssistantEngine, Effect.gen(function* () {
|
|
84
|
+
const context = yield* Effect.context();
|
|
85
|
+
const log = yield* ChatLog;
|
|
86
|
+
const registered = /* @__PURE__ */ new Map();
|
|
87
|
+
const held = MutableHashMap.empty();
|
|
88
|
+
const chats = yield* RcMap.make({
|
|
89
|
+
lookup: Effect.fnUntraced(function* (key) {
|
|
90
|
+
const registration = registered.get(key.assistant);
|
|
91
|
+
if (registration === void 0) return yield* notRegistered(key.assistant);
|
|
92
|
+
return yield* make({
|
|
93
|
+
registration,
|
|
94
|
+
chatId: key.chatId,
|
|
95
|
+
keepAlive: (enabled) => keepAlive(key, enabled)
|
|
96
|
+
}).pipe(Effect.provide(context));
|
|
97
|
+
}),
|
|
98
|
+
idleTimeToLive: "5 minutes"
|
|
99
|
+
});
|
|
100
|
+
const keepAlive = Effect.fnUntraced(function* (key, enabled) {
|
|
101
|
+
if (enabled) {
|
|
102
|
+
const scope = yield* Scope.make();
|
|
103
|
+
yield* Effect.provideService(RcMap.get(chats, key), Scope.Scope, scope);
|
|
104
|
+
return MutableHashMap.set(held, key, scope);
|
|
105
|
+
}
|
|
106
|
+
const scope = MutableHashMap.get(held, key);
|
|
107
|
+
MutableHashMap.remove(held, key);
|
|
108
|
+
if (Option.isSome(scope)) yield* Scope.close(scope.value, Exit.void);
|
|
109
|
+
});
|
|
110
|
+
const withChat = Effect.fnUntraced(function* (assistant, chatId, f) {
|
|
111
|
+
return yield* f(yield* RcMap.get(chats, new ChatKey({
|
|
112
|
+
assistant,
|
|
113
|
+
chatId
|
|
114
|
+
})));
|
|
115
|
+
}, Effect.scoped);
|
|
116
|
+
const resumeAll = Effect.fnUntraced(function* (registration) {
|
|
117
|
+
const { name, app } = registration.definition;
|
|
118
|
+
for (const chatId of yield* log.unfinished) {
|
|
119
|
+
const entries = yield* log.read(chatId);
|
|
120
|
+
const lastTurn = entries.findLast(({ event }) => Predicate.isTagged(event, "TurnStarted"))?.event;
|
|
121
|
+
if (Predicate.isTagged(lastTurn, "TurnStarted") && lastTurn.assistant === name && needsTurn(entries, app.wakes)) yield* withChat(name, chatId, (chat) => chat.resume);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return AssistantEngine.of({
|
|
125
|
+
register: Effect.fn("AssistantEngine.register")(function* (registration) {
|
|
126
|
+
registered.set(registration.definition.name, registration);
|
|
127
|
+
yield* Effect.forkScoped(resumeAll(registration));
|
|
128
|
+
}),
|
|
129
|
+
submit: (assistant, chatId, input) => withChat(assistant, chatId, (chat) => chat.submitInBackground(input)),
|
|
130
|
+
delete: Effect.fn("AssistantEngine.delete")(function* (assistant, chatId) {
|
|
131
|
+
yield* withChat(assistant, chatId, (chat) => chat.delete);
|
|
132
|
+
yield* RcMap.invalidate(chats, new ChatKey({
|
|
133
|
+
assistant,
|
|
134
|
+
chatId
|
|
135
|
+
}));
|
|
136
|
+
}),
|
|
137
|
+
subscribe: (assistant, chatId, after) => Stream.unwrap(Effect.gen(function* () {
|
|
138
|
+
return (yield* RcMap.get(chats, new ChatKey({
|
|
139
|
+
assistant,
|
|
140
|
+
chatId
|
|
141
|
+
}))).stream(after);
|
|
142
|
+
}))
|
|
143
|
+
});
|
|
144
|
+
}));
|
|
145
|
+
const Submit = Rpc.make("Submit", { payload: { input: Schema.Union([Input, StoredAppEvent]) } }).annotate(ClusterSchema.Persisted, true);
|
|
146
|
+
const Subscribe = Rpc.make("Subscribe", {
|
|
147
|
+
payload: { after: Schema.Int },
|
|
148
|
+
success: Entry,
|
|
149
|
+
stream: true
|
|
150
|
+
});
|
|
151
|
+
const Delete = Rpc.make("Delete");
|
|
152
|
+
const chatEntity = (assistant) => Entity.make(`Assistant/${assistant}`, [
|
|
153
|
+
Submit,
|
|
154
|
+
Subscribe,
|
|
155
|
+
Delete
|
|
156
|
+
]);
|
|
157
|
+
/**
|
|
158
|
+
* Layer that runs chats as cluster entities, one entity per chat, keyed by chat id.
|
|
159
|
+
*
|
|
160
|
+
* **Details**
|
|
161
|
+
*
|
|
162
|
+
* The layer requires `Sharding`. Use a `SingleRunner` for one process with SQL storage, a runner
|
|
163
|
+
* of the app's cluster, or `TestRunner` in tests.
|
|
164
|
+
*
|
|
165
|
+
* **Example** (Running chats on a single runner with Postgres)
|
|
166
|
+
*
|
|
167
|
+
* ```ts
|
|
168
|
+
* import * as NodeCrypto from "@effect/platform-node/NodeCrypto"
|
|
169
|
+
* import * as PgClient from "@effect/sql-pg/PgClient"
|
|
170
|
+
* import { Assistant, AssistantEngine, ChatLog } from "effect-assistant"
|
|
171
|
+
* import type * as LanguageModel from "effect/ai/LanguageModel"
|
|
172
|
+
* import * as SingleRunner from "effect/cluster/SingleRunner"
|
|
173
|
+
* import * as Config from "effect/Config"
|
|
174
|
+
* import * as Layer from "effect/Layer"
|
|
175
|
+
*
|
|
176
|
+
* declare const ModelLive: Layer.Layer<LanguageModel.LanguageModel>
|
|
177
|
+
*
|
|
178
|
+
* const MyAssistant = Assistant.make("my-assistant")
|
|
179
|
+
*
|
|
180
|
+
* const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
|
|
181
|
+
* Layer.provide(ModelLive),
|
|
182
|
+
* Layer.provideMerge(AssistantEngine.layerCluster),
|
|
183
|
+
* Layer.provideMerge(
|
|
184
|
+
* Layer.merge(
|
|
185
|
+
* ChatLog.layerSql,
|
|
186
|
+
* SingleRunner.layer({ runnerStorage: "sql" }).pipe(Layer.provide(NodeCrypto.layer))
|
|
187
|
+
* )
|
|
188
|
+
* ),
|
|
189
|
+
* Layer.provide(PgClient.layerConfig({ url: Config.Redacted("DATABASE_URL") }))
|
|
190
|
+
* )
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @see {@link layerMemory} for running chats in one process
|
|
194
|
+
*
|
|
195
|
+
* @category layers
|
|
196
|
+
* @since 0.1.0
|
|
197
|
+
*/
|
|
198
|
+
const layerCluster = Layer.effect(AssistantEngine, Effect.gen(function* () {
|
|
199
|
+
const context = yield* Effect.context();
|
|
200
|
+
const sharding = yield* Sharding.Sharding;
|
|
201
|
+
const clients = /* @__PURE__ */ new Map();
|
|
202
|
+
const register = Effect.fnUntraced(function* (registration) {
|
|
203
|
+
const ChatEntity = chatEntity(registration.definition.name);
|
|
204
|
+
const handlers = Effect.gen(function* () {
|
|
205
|
+
const address = yield* Entity.CurrentAddress;
|
|
206
|
+
const chatId = yield* Schema.decodeUnknownEffect(ChatId)(address.entityId).pipe(Effect.orDie);
|
|
207
|
+
const entityContext = yield* Effect.context();
|
|
208
|
+
const keepAlive = (enabled) => Entity.keepAlive(enabled).pipe(Effect.provide(entityContext), Effect.catchCause((cause) => Effect.logWarning("assistant chat keep-alive failed", cause)));
|
|
209
|
+
yield* keepAlive(false);
|
|
210
|
+
const chat = yield* make({
|
|
211
|
+
registration,
|
|
212
|
+
chatId,
|
|
213
|
+
keepAlive
|
|
214
|
+
});
|
|
215
|
+
yield* chat.resume;
|
|
216
|
+
return ChatEntity.of({
|
|
217
|
+
Submit: (request) => chat.submit(request.payload.input),
|
|
218
|
+
Subscribe: (request) => chat.stream(request.payload.after),
|
|
219
|
+
Delete: () => chat.delete
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
yield* sharding.registerEntity(ChatEntity, handlers.pipe(Effect.provide(context)), {
|
|
223
|
+
maxIdleTime: "5 minutes",
|
|
224
|
+
concurrency: "unbounded"
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
const clientOf = Effect.fnUntraced(function* (assistant) {
|
|
228
|
+
const known = clients.get(assistant);
|
|
229
|
+
if (known !== void 0) return known;
|
|
230
|
+
const makeClient = yield* chatEntity(assistant).client.pipe(Effect.provide(context));
|
|
231
|
+
const client = {
|
|
232
|
+
submit: (chatId, input) => makeClient(chatId).Submit({ input }, { discard: true }).pipe(Effect.orDie),
|
|
233
|
+
subscribe: (chatId, after) => makeClient(chatId).Subscribe({ after }).pipe(Stream.orDie),
|
|
234
|
+
delete: (chatId) => makeClient(chatId).Delete().pipe(Effect.orDie)
|
|
235
|
+
};
|
|
236
|
+
clients.set(assistant, client);
|
|
237
|
+
return client;
|
|
238
|
+
});
|
|
239
|
+
return AssistantEngine.of({
|
|
240
|
+
register: (registration) => register(registration).pipe(Effect.provide(context)),
|
|
241
|
+
submit: Effect.fn("AssistantEngine.submit")(function* (assistant, chatId, input) {
|
|
242
|
+
yield* (yield* clientOf(assistant)).submit(chatId, input);
|
|
243
|
+
}),
|
|
244
|
+
subscribe: (assistant, chatId, after) => Stream.unwrap(Effect.gen(function* () {
|
|
245
|
+
return (yield* clientOf(assistant)).subscribe(chatId, after);
|
|
246
|
+
})),
|
|
247
|
+
delete: Effect.fn("AssistantEngine.delete")(function* (assistant, chatId) {
|
|
248
|
+
yield* (yield* clientOf(assistant)).delete(chatId);
|
|
249
|
+
})
|
|
250
|
+
});
|
|
251
|
+
}));
|
|
252
|
+
//#endregion
|
|
253
|
+
export { AssistantEngine, AssistantEngine_exports, layerCluster, layerMemory };
|
|
254
|
+
|
|
255
|
+
//# sourceMappingURL=AssistantEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssistantEngine.js","names":["Chat.make","History.needsTurn","AssistantEvent.Input","AssistantEvent.StoredAppEvent","AssistantEvent.Entry"],"sources":["../src/AssistantEngine.ts"],"sourcesContent":["/**\n * This module provides the `AssistantEngine` service, which runs assistants' chats, and its two\n * implementations.\n *\n * An assistant's `toLayer` registers the assistant with the engine, and the assistant's `submit`\n * and `subscribe` go through it. Pick one engine for the app, as with a workflow engine.\n * {@link layerMemory} runs chats in the process, for tests and single-process apps.\n * {@link layerCluster} runs each chat as a cluster entity, so a turn cut off by a crash resumes.\n *\n * @since 0.1.0\n */\nimport * as ClusterSchema from \"effect/cluster/ClusterSchema\"\nimport * as Entity from \"effect/cluster/Entity\"\nimport * as Sharding from \"effect/cluster/Sharding\"\nimport * as Context from \"effect/Context\"\nimport * as Data from \"effect/Data\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport * as Layer from \"effect/Layer\"\nimport * as MutableHashMap from \"effect/MutableHashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as RcMap from \"effect/RcMap\"\nimport * as Rpc from \"effect/rpc/Rpc\"\nimport * as Schema from \"effect/Schema\"\nimport * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport { ChatId } from \"./ChatId.ts\"\nimport { ChatLog } from \"./ChatLog.ts\"\nimport * as Chat from \"./internal/chat.ts\"\nimport * as History from \"./internal/history.ts\"\nimport type * as Turns from \"./internal/turns.ts\"\n\n/**\n * Service that runs assistants' chats. An assistant's `toLayer` registers the assistant with it,\n * and the assistant's `submit` and `subscribe` use it.\n *\n * @category services\n * @since 0.1.0\n */\nexport class AssistantEngine extends Context.Service<\n AssistantEngine,\n {\n /** Registers an assistant for as long as the scope is open. */\n readonly register: (registration: Turns.Registration) => Effect.Effect<void, never, Scope.Scope>\n /**\n * Stores an input in one of the assistant's chats. The assistant works on it in the\n * background.\n */\n readonly submit: (\n assistant: string,\n chatId: ChatId,\n input: AssistantEvent.Input | AssistantEvent.StoredAppEvent\n ) => Effect.Effect<void>\n /** Streams the chat's stored entries after the `after` seq, then live ones. */\n readonly subscribe: (assistant: string, chatId: ChatId, after: number) => Stream.Stream<AssistantEvent.Entry>\n /**\n * Deletes one of the assistant's chats. The chat's log records the deletion, and everything\n * that works in the chat stops.\n */\n readonly delete: (assistant: string, chatId: ChatId) => Effect.Effect<void>\n }\n>()(\"effect-assistant/AssistantEngine\") {}\n\nclass ChatKey extends Data.Class<{ readonly assistant: string; readonly chatId: ChatId }> {}\n\nconst notRegistered = (assistant: string) =>\n Effect.die(`The ${assistant} assistant isn't registered: provide its toLayer.`)\n\n/**\n * Layer that runs chats in the current process. Each chat's turns run as fibers, one at a time.\n *\n * **Details**\n *\n * With `ChatLog.layerSql`, the engine resumes chats at startup. When an assistant registers, the\n * engine finds the chats that assistant was working on when the process stopped (a turn cut off\n * partway, or an input that no turn read) and runs a turn for each. Run this engine in one process\n * only, because two processes would run the same chat and write its log at the same time.\n *\n * **Example** (Running chats in memory)\n *\n * ```ts\n * import { Assistant, AssistantEngine, ChatLog } from \"effect-assistant\"\n * import type * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as Layer from \"effect/Layer\"\n *\n * declare const ModelLive: Layer.Layer<LanguageModel.LanguageModel>\n *\n * const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: \"Be brief.\" })).pipe(\n * Layer.provide(ModelLive),\n * Layer.provideMerge(AssistantEngine.layerMemory),\n * Layer.provideMerge(ChatLog.layerMemory)\n * )\n * ```\n *\n * @see {@link layerCluster} for running each chat as a cluster entity\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerMemory: Layer.Layer<AssistantEngine, never, ChatLog> = Layer.effect(\n AssistantEngine,\n Effect.gen(function* () {\n const context = yield* Effect.context<ChatLog>()\n const log = yield* ChatLog\n const registered = new Map<string, Turns.Registration>()\n // A chat's keep-alive: a hold on its entry, so it stays open while runs or turns work in the background.\n const held = MutableHashMap.empty<ChatKey, Scope.Closeable>()\n\n // One entry per open chat, like a cluster entity: opened when used, closed five minutes after the last use.\n const chats: RcMap.RcMap<ChatKey, Chat.Chat> = yield* RcMap.make({\n lookup: Effect.fnUntraced(function* (key: ChatKey) {\n const registration = registered.get(key.assistant)\n\n if (registration === undefined) return yield* notRegistered(key.assistant)\n\n return yield* Chat.make({\n registration,\n chatId: key.chatId,\n keepAlive: (enabled) => keepAlive(key, enabled)\n }).pipe(Effect.provide(context))\n }),\n idleTimeToLive: \"5 minutes\"\n })\n\n // On: a hold on the chat's entry, in a scope of its own. Off: closing that scope lets the entry go idle.\n const keepAlive = Effect.fnUntraced(function* (key: ChatKey, enabled: boolean) {\n if (enabled) {\n const scope = yield* Scope.make()\n\n yield* Effect.provideService(RcMap.get(chats, key), Scope.Scope, scope)\n\n return MutableHashMap.set(held, key, scope)\n }\n\n const scope = MutableHashMap.get(held, key)\n\n MutableHashMap.remove(held, key)\n\n if (Option.isSome(scope)) yield* Scope.close(scope.value, Exit.void)\n })\n\n const withChat = Effect.fnUntraced(function* <A>(\n assistant: string,\n chatId: ChatId,\n f: (chat: Chat.Chat) => Effect.Effect<A>\n ) {\n const chat = yield* RcMap.get(chats, new ChatKey({ assistant, chatId }))\n\n return yield* f(chat)\n }, Effect.scoped)\n\n // A chat owes a turn when its last turn was cut off, or an input came after.\n const resumeAll = Effect.fnUntraced(function* (registration: Turns.Registration) {\n const { name, app } = registration.definition\n\n for (const chatId of yield* log.unfinished) {\n const entries = yield* log.read(chatId)\n\n const lastTurn = entries.findLast(({ event }) => Predicate.isTagged(event, \"TurnStarted\"))?.event\n const ours = Predicate.isTagged(lastTurn, \"TurnStarted\") && lastTurn.assistant === name\n\n if (ours && History.needsTurn(entries, app.wakes)) yield* withChat(name, chatId, (chat) => chat.resume)\n }\n })\n\n return AssistantEngine.of({\n register: Effect.fn(\"AssistantEngine.register\")(function* (registration: Turns.Registration) {\n registered.set(registration.definition.name, registration)\n // Chats this assistant was working on when the process stopped: pick them up again.\n yield* Effect.forkScoped(resumeAll(registration))\n }),\n submit: (assistant, chatId, input) => withChat(assistant, chatId, (chat) => chat.submitInBackground(input)),\n // The chat deletes itself, so a chat that's open stops; the entry goes, and the next open finds it deleted.\n delete: Effect.fn(\"AssistantEngine.delete\")(function* (assistant: string, chatId: ChatId) {\n yield* withChat(assistant, chatId, (chat) => chat.delete)\n yield* RcMap.invalidate(chats, new ChatKey({ assistant, chatId }))\n }),\n // The subscriber holds the chat open while it streams.\n subscribe: (assistant, chatId, after) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const chat = yield* RcMap.get(chats, new ChatKey({ assistant, chatId }))\n\n return chat.stream(after)\n })\n )\n })\n })\n)\n\ninterface EntityClient {\n readonly submit: (chatId: ChatId, input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) => Effect.Effect<void>\n readonly subscribe: (chatId: ChatId, after: number) => Stream.Stream<AssistantEvent.Entry>\n readonly delete: (chatId: ChatId) => Effect.Effect<void>\n}\n\nconst Submit = Rpc.make(\"Submit\", {\n payload: { input: Schema.Union([AssistantEvent.Input, AssistantEvent.StoredAppEvent]) }\n}).annotate(ClusterSchema.Persisted, true)\n\nconst Subscribe = Rpc.make(\"Subscribe\", {\n payload: { after: Schema.Int },\n success: AssistantEvent.Entry,\n stream: true\n})\n\nconst Delete = Rpc.make(\"Delete\")\n\nconst chatEntity = (assistant: string) => Entity.make(`Assistant/${assistant}`, [Submit, Subscribe, Delete])\n\n/**\n * Layer that runs chats as cluster entities, one entity per chat, keyed by chat id.\n *\n * **Details**\n *\n * The layer requires `Sharding`. Use a `SingleRunner` for one process with SQL storage, a runner\n * of the app's cluster, or `TestRunner` in tests.\n *\n * **Example** (Running chats on a single runner with Postgres)\n *\n * ```ts\n * import * as NodeCrypto from \"@effect/platform-node/NodeCrypto\"\n * import * as PgClient from \"@effect/sql-pg/PgClient\"\n * import { Assistant, AssistantEngine, ChatLog } from \"effect-assistant\"\n * import type * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as SingleRunner from \"effect/cluster/SingleRunner\"\n * import * as Config from \"effect/Config\"\n * import * as Layer from \"effect/Layer\"\n *\n * declare const ModelLive: Layer.Layer<LanguageModel.LanguageModel>\n *\n * const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: \"Be brief.\" })).pipe(\n * Layer.provide(ModelLive),\n * Layer.provideMerge(AssistantEngine.layerCluster),\n * Layer.provideMerge(\n * Layer.merge(\n * ChatLog.layerSql,\n * SingleRunner.layer({ runnerStorage: \"sql\" }).pipe(Layer.provide(NodeCrypto.layer))\n * )\n * ),\n * Layer.provide(PgClient.layerConfig({ url: Config.Redacted(\"DATABASE_URL\") }))\n * )\n * ```\n *\n * @see {@link layerMemory} for running chats in one process\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerCluster: Layer.Layer<AssistantEngine, never, ChatLog | Sharding.Sharding> = Layer.effect(\n AssistantEngine,\n Effect.gen(function* () {\n const context = yield* Effect.context<ChatLog | Sharding.Sharding>()\n const sharding = yield* Sharding.Sharding\n const clients = new Map<string, EntityClient>()\n\n const register = Effect.fnUntraced(function* (registration: Turns.Registration) {\n const ChatEntity = chatEntity(registration.definition.name)\n\n const handlers = Effect.gen(function* () {\n const address = yield* Entity.CurrentAddress\n const chatId = yield* Schema.decodeUnknownEffect(ChatId)(address.entityId).pipe(Effect.orDie)\n // The entity's own: `Entity.keepAlive` needs its address and keep-alive latch, which is in there too.\n const entityContext = yield* Effect.context<Entity.CurrentAddress | Sharding.Sharding>()\n\n const keepAlive = (enabled: boolean) =>\n Entity.keepAlive(enabled).pipe(\n Effect.provide(entityContext),\n Effect.catchCause((cause) => Effect.logWarning(\"assistant chat keep-alive failed\", cause))\n )\n\n // A keep-alive left on from before a restart (by a run cut off) has done its job: it started the entity.\n yield* keepAlive(false)\n\n const chat = yield* Chat.make({ registration, chatId, keepAlive })\n yield* chat.resume\n\n return ChatEntity.of({\n // The turns run inside the request, so a crash leaves it unprocessed and the cluster redelivers it.\n Submit: (request) => chat.submit(request.payload.input),\n Subscribe: (request) => chat.stream(request.payload.after),\n Delete: () => chat.delete\n })\n })\n\n yield* sharding.registerEntity(ChatEntity, handlers.pipe(Effect.provide(context)), {\n maxIdleTime: \"5 minutes\",\n concurrency: \"unbounded\"\n })\n })\n\n // Made on first use, so a process that registers no assistant can still reach its chats.\n const clientOf = Effect.fnUntraced(function* (assistant: string) {\n const known = clients.get(assistant)\n\n if (known !== undefined) return known\n\n const makeClient = yield* chatEntity(assistant).client.pipe(Effect.provide(context))\n\n const client: EntityClient = {\n submit: (chatId, input) => makeClient(chatId).Submit({ input }, { discard: true }).pipe(Effect.orDie),\n subscribe: (chatId, after) => makeClient(chatId).Subscribe({ after }).pipe(Stream.orDie),\n delete: (chatId) => makeClient(chatId).Delete().pipe(Effect.orDie)\n }\n\n clients.set(assistant, client)\n\n return client\n })\n\n return AssistantEngine.of({\n register: (registration) => register(registration).pipe(Effect.provide(context)),\n submit: Effect.fn(\"AssistantEngine.submit\")(function* (\n assistant: string,\n chatId: ChatId,\n input: AssistantEvent.Input | AssistantEvent.StoredAppEvent\n ) {\n const client = yield* clientOf(assistant)\n\n yield* client.submit(chatId, input)\n }),\n subscribe: (assistant, chatId, after) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* clientOf(assistant)\n\n return client.subscribe(chatId, after)\n })\n ),\n delete: Effect.fn(\"AssistantEngine.delete\")(function* (assistant: string, chatId: ChatId) {\n const client = yield* clientOf(assistant)\n\n yield* client.delete(chatId)\n })\n })\n })\n)\n"],"mappings":"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"}
|