effect-assistant 0.2.0 → 0.4.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/dist/Assistant.d.ts +77 -37
- package/dist/Assistant.d.ts.map +1 -1
- package/dist/Assistant.js +11 -4
- package/dist/Assistant.js.map +1 -1
- package/dist/AssistantActivity.d.ts +10 -16
- package/dist/AssistantActivity.d.ts.map +1 -1
- package/dist/AssistantActivity.js +11 -4
- package/dist/AssistantActivity.js.map +1 -1
- package/dist/AssistantEngine.d.ts +39 -1
- package/dist/AssistantEngine.d.ts.map +1 -1
- package/dist/AssistantEngine.js +81 -28
- package/dist/AssistantEngine.js.map +1 -1
- package/dist/AssistantEvent.d.ts +104 -11
- package/dist/AssistantEvent.d.ts.map +1 -1
- package/dist/AssistantEvent.js +88 -13
- package/dist/AssistantEvent.js.map +1 -1
- package/dist/AssistantLoop.d.ts +4 -2
- package/dist/AssistantLoop.d.ts.map +1 -1
- package/dist/AssistantLoop.js +4 -2
- package/dist/AssistantLoop.js.map +1 -1
- package/dist/BlockKit.d.ts +4 -4
- package/dist/BlockKit.js +1 -1
- package/dist/BlockKit.js.map +1 -1
- package/dist/ChatLog.js +1 -1
- package/dist/Compaction.d.ts +7 -3
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +19 -8
- package/dist/Compaction.js.map +1 -1
- package/dist/CurrentChat.d.ts +2 -1
- package/dist/CurrentChat.d.ts.map +1 -1
- package/dist/CurrentChat.js.map +1 -1
- package/dist/Instructions.js +1 -1
- package/dist/Skill.d.ts +86 -11
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +93 -18
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +7 -7
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +516 -0
- package/dist/Timeline.d.ts.map +1 -0
- package/dist/Timeline.js +631 -0
- package/dist/Timeline.js.map +1 -0
- package/dist/ToolApproval.d.ts +146 -0
- package/dist/ToolApproval.d.ts.map +1 -0
- package/dist/ToolApproval.js +201 -0
- package/dist/ToolApproval.js.map +1 -0
- package/dist/ToolDiscovery.d.ts +6 -4
- package/dist/ToolDiscovery.d.ts.map +1 -1
- package/dist/ToolDiscovery.js +5 -4
- package/dist/ToolDiscovery.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/internal/chat.js +9 -7
- package/dist/internal/chat.js.map +1 -1
- package/dist/internal/events.d.ts +15 -16
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +29 -28
- package/dist/internal/events.js.map +1 -1
- package/dist/internal/history.js +53 -22
- package/dist/internal/history.js.map +1 -1
- package/dist/internal/input.js +7 -0
- package/dist/internal/input.js.map +1 -0
- package/dist/internal/loop.d.ts +10 -5
- package/dist/internal/loop.d.ts.map +1 -1
- package/dist/internal/loop.js +55 -22
- package/dist/internal/loop.js.map +1 -1
- package/dist/internal/specialists.js +10 -16
- package/dist/internal/specialists.js.map +1 -1
- package/dist/internal/toolkit.d.ts +5 -7
- package/dist/internal/toolkit.d.ts.map +1 -1
- package/dist/internal/toolkit.js +28 -18
- package/dist/internal/toolkit.js.map +1 -1
- package/dist/internal/tools.js +1 -1
- package/dist/internal/turns.d.ts.map +1 -1
- package/dist/internal/turns.js +35 -42
- package/dist/internal/turns.js.map +1 -1
- package/package.json +1 -1
- package/src/Assistant.ts +80 -40
- package/src/AssistantActivity.ts +11 -18
- package/src/AssistantEngine.ts +110 -43
- package/src/AssistantEvent.ts +106 -14
- package/src/AssistantLoop.ts +4 -2
- package/src/BlockKit.ts +6 -6
- package/src/Compaction.ts +25 -4
- package/src/CurrentChat.ts +2 -1
- package/src/Skill.ts +133 -23
- package/src/Specialist.ts +9 -9
- package/src/Timeline.ts +947 -0
- package/src/ToolApproval.ts +249 -0
- package/src/ToolDiscovery.ts +8 -3
- package/src/index.ts +10 -10
- package/src/internal/chat.ts +15 -5
- package/src/internal/events.ts +43 -43
- package/src/internal/history.ts +94 -32
- package/src/internal/input.ts +3 -0
- package/src/internal/loop.ts +120 -34
- package/src/internal/specialists.ts +7 -12
- package/src/internal/toolkit.ts +47 -30
- package/src/internal/turns.ts +52 -58
- package/dist/AppEvent.d.ts +0 -111
- package/dist/AppEvent.d.ts.map +0 -1
- package/dist/AppEvent.js +0 -71
- package/dist/AppEvent.js.map +0 -1
- package/dist/ChatView.d.ts +0 -408
- package/dist/ChatView.d.ts.map +0 -1
- package/dist/ChatView.js +0 -443
- package/dist/ChatView.js.map +0 -1
- package/dist/internal/skills.js +0 -28
- package/dist/internal/skills.js.map +0 -1
- package/src/AppEvent.ts +0 -134
- package/src/ChatView.ts +0 -587
- package/src/internal/skills.ts +0 -37
package/dist/AssistantEngine.js
CHANGED
|
@@ -4,10 +4,10 @@ import { Entry, Input, StoredAppEvent } from "./AssistantEvent.js";
|
|
|
4
4
|
import { ChatLog } from "./ChatLog.js";
|
|
5
5
|
import { needsTurn } from "./internal/history.js";
|
|
6
6
|
import { make } from "./internal/chat.js";
|
|
7
|
-
import * as Predicate from "effect/Predicate";
|
|
8
7
|
import * as Context from "effect/Context";
|
|
9
8
|
import * as Effect from "effect/Effect";
|
|
10
9
|
import * as Layer from "effect/Layer";
|
|
10
|
+
import * as Predicate from "effect/Predicate";
|
|
11
11
|
import * as Schema from "effect/Schema";
|
|
12
12
|
import * as Stream from "effect/Stream";
|
|
13
13
|
import * as ClusterSchema from "effect/cluster/ClusterSchema";
|
|
@@ -22,19 +22,21 @@ import * as Rpc from "effect/rpc/Rpc";
|
|
|
22
22
|
import * as Scope from "effect/Scope";
|
|
23
23
|
//#region src/AssistantEngine.ts
|
|
24
24
|
/**
|
|
25
|
-
* This module provides the `AssistantEngine` service, which runs assistants' chats, and its
|
|
25
|
+
* This module provides the `AssistantEngine` service, which runs assistants' chats, and its
|
|
26
26
|
* implementations.
|
|
27
27
|
*
|
|
28
28
|
* An assistant's `toLayer` registers the assistant with the engine, and the assistant's `submit`
|
|
29
29
|
* and `subscribe` go through it. Pick one engine for the app, as with a workflow engine.
|
|
30
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
|
|
31
|
+
* {@link layerCluster} runs each chat as a cluster entity, so a turn cut off by a crash resumes, and
|
|
32
|
+
* {@link layerClusterClient} reaches those chats from a process that runs none.
|
|
32
33
|
*
|
|
33
34
|
* @since 0.1.0
|
|
34
35
|
*/
|
|
35
36
|
var AssistantEngine_exports = /* @__PURE__ */ __exportAll({
|
|
36
37
|
AssistantEngine: () => AssistantEngine,
|
|
37
38
|
layerCluster: () => layerCluster,
|
|
39
|
+
layerClusterClient: () => layerClusterClient,
|
|
38
40
|
layerMemory: () => layerMemory
|
|
39
41
|
});
|
|
40
42
|
/**
|
|
@@ -114,11 +116,11 @@ const layerMemory = Layer.effect(AssistantEngine, Effect.gen(function* () {
|
|
|
114
116
|
})));
|
|
115
117
|
}, Effect.scoped);
|
|
116
118
|
const resumeAll = Effect.fnUntraced(function* (registration) {
|
|
117
|
-
const { name
|
|
119
|
+
const { name } = registration.definition;
|
|
118
120
|
for (const chatId of yield* log.unfinished) {
|
|
119
121
|
const entries = yield* log.read(chatId);
|
|
120
122
|
const lastTurn = entries.findLast(({ event }) => Predicate.isTagged(event, "TurnStarted"))?.event;
|
|
121
|
-
if (Predicate.isTagged(lastTurn, "TurnStarted") && lastTurn.assistant === name && needsTurn(entries
|
|
123
|
+
if (Predicate.isTagged(lastTurn, "TurnStarted") && lastTurn.assistant === name && needsTurn(entries)) yield* withChat(name, chatId, (chat) => chat.resume);
|
|
122
124
|
}
|
|
123
125
|
});
|
|
124
126
|
return AssistantEngine.of({
|
|
@@ -154,6 +156,33 @@ const chatEntity = (assistant) => Entity.make(`Assistant/${assistant}`, [
|
|
|
154
156
|
Subscribe,
|
|
155
157
|
Delete
|
|
156
158
|
]);
|
|
159
|
+
const makeClusterClient = Effect.gen(function* () {
|
|
160
|
+
const context = yield* Effect.context();
|
|
161
|
+
const clients = /* @__PURE__ */ new Map();
|
|
162
|
+
const clientOf = Effect.fnUntraced(function* (assistant) {
|
|
163
|
+
const known = clients.get(assistant);
|
|
164
|
+
if (known !== void 0) return known;
|
|
165
|
+
const makeClient = yield* chatEntity(assistant).client.pipe(Effect.provide(context));
|
|
166
|
+
const client = {
|
|
167
|
+
submit: (chatId, input) => makeClient(chatId).Submit({ input }, { discard: true }).pipe(Effect.orDie),
|
|
168
|
+
subscribe: (chatId, after) => makeClient(chatId).Subscribe({ after }).pipe(Stream.orDie),
|
|
169
|
+
delete: (chatId) => makeClient(chatId).Delete().pipe(Effect.orDie)
|
|
170
|
+
};
|
|
171
|
+
clients.set(assistant, client);
|
|
172
|
+
return client;
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
submit: Effect.fn("AssistantEngine.submit")(function* (assistant, chatId, input) {
|
|
176
|
+
yield* (yield* clientOf(assistant)).submit(chatId, input);
|
|
177
|
+
}),
|
|
178
|
+
subscribe: (assistant, chatId, after) => Stream.unwrap(Effect.gen(function* () {
|
|
179
|
+
return (yield* clientOf(assistant)).subscribe(chatId, after);
|
|
180
|
+
})),
|
|
181
|
+
delete: Effect.fn("AssistantEngine.delete")(function* (assistant, chatId) {
|
|
182
|
+
yield* (yield* clientOf(assistant)).delete(chatId);
|
|
183
|
+
})
|
|
184
|
+
};
|
|
185
|
+
});
|
|
157
186
|
/**
|
|
158
187
|
* Layer that runs chats as cluster entities, one entity per chat, keyed by chat id.
|
|
159
188
|
*
|
|
@@ -191,6 +220,7 @@ const chatEntity = (assistant) => Entity.make(`Assistant/${assistant}`, [
|
|
|
191
220
|
* ```
|
|
192
221
|
*
|
|
193
222
|
* @see {@link layerMemory} for running chats in one process
|
|
223
|
+
* @see {@link layerClusterClient} for a process that only reaches chats the cluster runs
|
|
194
224
|
*
|
|
195
225
|
* @category layers
|
|
196
226
|
* @since 0.1.0
|
|
@@ -198,7 +228,6 @@ const chatEntity = (assistant) => Entity.make(`Assistant/${assistant}`, [
|
|
|
198
228
|
const layerCluster = Layer.effect(AssistantEngine, Effect.gen(function* () {
|
|
199
229
|
const context = yield* Effect.context();
|
|
200
230
|
const sharding = yield* Sharding.Sharding;
|
|
201
|
-
const clients = /* @__PURE__ */ new Map();
|
|
202
231
|
const register = Effect.fnUntraced(function* (registration) {
|
|
203
232
|
const ChatEntity = chatEntity(registration.definition.name);
|
|
204
233
|
const handlers = Effect.gen(function* () {
|
|
@@ -224,32 +253,56 @@ const layerCluster = Layer.effect(AssistantEngine, Effect.gen(function* () {
|
|
|
224
253
|
concurrency: "unbounded"
|
|
225
254
|
});
|
|
226
255
|
});
|
|
227
|
-
const
|
|
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
|
-
});
|
|
256
|
+
const client = yield* makeClusterClient;
|
|
239
257
|
return AssistantEngine.of({
|
|
240
258
|
register: (registration) => register(registration).pipe(Effect.provide(context)),
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
259
|
+
...client
|
|
260
|
+
});
|
|
261
|
+
}));
|
|
262
|
+
/**
|
|
263
|
+
* Layer that reaches chats running as cluster entities elsewhere, and runs none itself.
|
|
264
|
+
*
|
|
265
|
+
* **When to use**
|
|
266
|
+
*
|
|
267
|
+
* Use it in a process that only submits to, subscribes to, and deletes chats that the cluster's
|
|
268
|
+
* runners run with {@link layerCluster}, such as a web server that provides an assistant's
|
|
269
|
+
* `layerClient`. It needs no `ChatLog`, since the runners store the chats.
|
|
270
|
+
*
|
|
271
|
+
* **Details**
|
|
272
|
+
*
|
|
273
|
+
* The layer requires `Sharding`. An assistant's `toLayer` can't register with it: it dies, since
|
|
274
|
+
* no chats run in this process.
|
|
275
|
+
*
|
|
276
|
+
* **Example** (A web server in front of the cluster)
|
|
277
|
+
*
|
|
278
|
+
* ```ts
|
|
279
|
+
* import { Assistant, AssistantEngine } from "effect-assistant"
|
|
280
|
+
* import type * as Sharding from "effect/cluster/Sharding"
|
|
281
|
+
* import * as Layer from "effect/Layer"
|
|
282
|
+
*
|
|
283
|
+
* declare const ShardingLive: Layer.Layer<Sharding.Sharding>
|
|
284
|
+
*
|
|
285
|
+
* export const MyAssistant = Assistant.make("my-assistant")
|
|
286
|
+
*
|
|
287
|
+
* const MyAssistantClient = MyAssistant.layerClient.pipe(
|
|
288
|
+
* Layer.provide(AssistantEngine.layerClusterClient),
|
|
289
|
+
* Layer.provide(ShardingLive)
|
|
290
|
+
* )
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* @see {@link layerCluster} for the runners that run the chats
|
|
294
|
+
*
|
|
295
|
+
* @category layers
|
|
296
|
+
* @since 0.3.0
|
|
297
|
+
*/
|
|
298
|
+
const layerClusterClient = Layer.effect(AssistantEngine, Effect.gen(function* () {
|
|
299
|
+
const client = yield* makeClusterClient;
|
|
300
|
+
return AssistantEngine.of({
|
|
301
|
+
register: (registration) => Effect.die(`The ${registration.definition.name} assistant can't register with AssistantEngine.layerClusterClient, which runs no chats: provide AssistantEngine.layerCluster where its toLayer runs.`),
|
|
302
|
+
...client
|
|
250
303
|
});
|
|
251
304
|
}));
|
|
252
305
|
//#endregion
|
|
253
|
-
export { AssistantEngine, AssistantEngine_exports, layerCluster, layerMemory };
|
|
306
|
+
export { AssistantEngine, AssistantEngine_exports, layerCluster, layerClusterClient, layerMemory };
|
|
254
307
|
|
|
255
308
|
//# sourceMappingURL=AssistantEngine.js.map
|
|
@@ -1 +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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"}
|
|
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\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, and\n * {@link layerClusterClient} reaches those chats from a process that runs none.\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 } = 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)) 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\ntype EngineClient = Omit<AssistantEngine[\"Service\"], \"register\">\n\n// Entity clients are made on first use, so a process that registers no assistant can still reach its chats.\nconst makeClusterClient = Effect.gen(function* () {\n const context = yield* Effect.context<Sharding.Sharding>()\n const clients = new Map<string, EntityClient>()\n\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 const engine: EngineClient = {\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 return engine\n})\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 * @see {@link layerClusterClient} for a process that only reaches chats the cluster runs\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\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 const client = yield* makeClusterClient\n\n return AssistantEngine.of({\n register: (registration) => register(registration).pipe(Effect.provide(context)),\n ...client\n })\n })\n)\n\n/**\n * Layer that reaches chats running as cluster entities elsewhere, and runs none itself.\n *\n * **When to use**\n *\n * Use it in a process that only submits to, subscribes to, and deletes chats that the cluster's\n * runners run with {@link layerCluster}, such as a web server that provides an assistant's\n * `layerClient`. It needs no `ChatLog`, since the runners store the chats.\n *\n * **Details**\n *\n * The layer requires `Sharding`. An assistant's `toLayer` can't register with it: it dies, since\n * no chats run in this process.\n *\n * **Example** (A web server in front of the cluster)\n *\n * ```ts\n * import { Assistant, AssistantEngine } from \"effect-assistant\"\n * import type * as Sharding from \"effect/cluster/Sharding\"\n * import * as Layer from \"effect/Layer\"\n *\n * declare const ShardingLive: Layer.Layer<Sharding.Sharding>\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const MyAssistantClient = MyAssistant.layerClient.pipe(\n * Layer.provide(AssistantEngine.layerClusterClient),\n * Layer.provide(ShardingLive)\n * )\n * ```\n *\n * @see {@link layerCluster} for the runners that run the chats\n *\n * @category layers\n * @since 0.3.0\n */\nexport const layerClusterClient: Layer.Layer<AssistantEngine, never, Sharding.Sharding> = Layer.effect(\n AssistantEngine,\n Effect.gen(function* () {\n const client = yield* makeClusterClient\n\n return AssistantEngine.of({\n register: (registration) =>\n Effect.die(\n `The ${registration.definition.name} assistant can't register with AssistantEngine.layerClusterClient, which runs no chats: provide AssistantEngine.layerCluster where its toLayer runs.`\n ),\n ...client\n })\n })\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,kBAAb,cAAqC,QAAQ,QAsB3C,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC;AAEzC,IAAM,UAAN,cAAsB,KAAK,MAA+D,CAAC;AAE3F,MAAM,iBAAiB,cACrB,OAAO,IAAI,OAAO,UAAU,kDAAkD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmChF,MAAa,cAA4D,MAAM,OAC7E,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,OAAO,QAAiB;CAC/C,MAAM,MAAM,OAAO;CACnB,MAAM,6BAAa,IAAI,IAAgC;CAEvD,MAAM,OAAO,eAAe,MAAgC;CAG5D,MAAM,QAAyC,OAAO,MAAM,KAAK;EAC/D,QAAQ,OAAO,WAAW,WAAW,KAAc;GACjD,MAAM,eAAe,WAAW,IAAI,IAAI,SAAS;GAEjD,IAAI,iBAAiB,KAAA,GAAW,OAAO,OAAO,cAAc,IAAI,SAAS;GAEzE,OAAO,OAAOA,KAAU;IACtB;IACA,QAAQ,IAAI;IACZ,YAAY,YAAY,UAAU,KAAK,OAAO;GAChD,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,OAAO,CAAC;EACjC,CAAC;EACD,gBAAgB;CAClB,CAAC;CAGD,MAAM,YAAY,OAAO,WAAW,WAAW,KAAc,SAAkB;EAC7E,IAAI,SAAS;GACX,MAAM,QAAQ,OAAO,MAAM,KAAK;GAEhC,OAAO,OAAO,eAAe,MAAM,IAAI,OAAO,GAAG,GAAG,MAAM,OAAO,KAAK;GAEtE,OAAO,eAAe,IAAI,MAAM,KAAK,KAAK;EAC5C;EAEA,MAAM,QAAQ,eAAe,IAAI,MAAM,GAAG;EAE1C,eAAe,OAAO,MAAM,GAAG;EAE/B,IAAI,OAAO,OAAO,KAAK,GAAG,OAAO,MAAM,MAAM,MAAM,OAAO,KAAK,IAAI;CACrE,CAAC;CAED,MAAM,WAAW,OAAO,WAAW,WACjC,WACA,QACA,GACA;EAGA,OAAO,OAAO,EAAE,OAFI,MAAM,IAAI,OAAO,IAAI,QAAQ;GAAE;GAAW;EAAO,CAAC,CAAC,CAEnD;CACtB,GAAG,OAAO,MAAM;CAGhB,MAAM,YAAY,OAAO,WAAW,WAAW,cAAkC;EAC/E,MAAM,EAAE,SAAS,aAAa;EAE9B,KAAK,MAAM,UAAU,OAAO,IAAI,YAAY;GAC1C,MAAM,UAAU,OAAO,IAAI,KAAK,MAAM;GAEtC,MAAM,WAAW,QAAQ,UAAU,EAAE,YAAY,UAAU,SAAS,OAAO,aAAa,CAAC,CAAC,EAAE;GAG5F,IAFa,UAAU,SAAS,UAAU,aAAa,KAAK,SAAS,cAAc,QAEvEC,UAAkB,OAAO,GAAG,OAAO,SAAS,MAAM,SAAS,SAAS,KAAK,MAAM;EAC7F;CACF,CAAC;CAED,OAAO,gBAAgB,GAAG;EACxB,UAAU,OAAO,GAAG,0BAA0B,CAAC,CAAC,WAAW,cAAkC;GAC3F,WAAW,IAAI,aAAa,WAAW,MAAM,YAAY;GAEzD,OAAO,OAAO,WAAW,UAAU,YAAY,CAAC;EAClD,CAAC;EACD,SAAS,WAAW,QAAQ,UAAU,SAAS,WAAW,SAAS,SAAS,KAAK,mBAAmB,KAAK,CAAC;EAE1G,QAAQ,OAAO,GAAG,wBAAwB,CAAC,CAAC,WAAW,WAAmB,QAAgB;GACxF,OAAO,SAAS,WAAW,SAAS,SAAS,KAAK,MAAM;GACxD,OAAO,MAAM,WAAW,OAAO,IAAI,QAAQ;IAAE;IAAW;GAAO,CAAC,CAAC;EACnE,CAAC;EAED,YAAY,WAAW,QAAQ,UAC7B,OAAO,OACL,OAAO,IAAI,aAAa;GAGtB,QAAO,OAFa,MAAM,IAAI,OAAO,IAAI,QAAQ;IAAE;IAAW;GAAO,CAAC,CAAC,EAAA,CAE3D,OAAO,KAAK;EAC1B,CAAC,CACH;CACJ,CAAC;AACH,CAAC,CACH;AAQA,MAAM,SAAS,IAAI,KAAK,UAAU,EAChC,SAAS,EAAE,OAAO,OAAO,MAAM,CAACC,OAAsBC,cAA6B,CAAC,EAAE,EACxF,CAAC,CAAC,CAAC,SAAS,cAAc,WAAW,IAAI;AAEzC,MAAM,YAAY,IAAI,KAAK,aAAa;CACtC,SAAS,EAAE,OAAO,OAAO,IAAI;CAC7B,SAASC;CACT,QAAQ;AACV,CAAC;AAED,MAAM,SAAS,IAAI,KAAK,QAAQ;AAEhC,MAAM,cAAc,cAAsB,OAAO,KAAK,aAAa,aAAa;CAAC;CAAQ;CAAW;AAAM,CAAC;AAK3G,MAAM,oBAAoB,OAAO,IAAI,aAAa;CAChD,MAAM,UAAU,OAAO,OAAO,QAA2B;CACzD,MAAM,0BAAU,IAAI,IAA0B;CAE9C,MAAM,WAAW,OAAO,WAAW,WAAW,WAAmB;EAC/D,MAAM,QAAQ,QAAQ,IAAI,SAAS;EAEnC,IAAI,UAAU,KAAA,GAAW,OAAO;EAEhC,MAAM,aAAa,OAAO,WAAW,SAAS,CAAC,CAAC,OAAO,KAAK,OAAO,QAAQ,OAAO,CAAC;EAEnF,MAAM,SAAuB;GAC3B,SAAS,QAAQ,UAAU,WAAW,MAAM,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK;GACpG,YAAY,QAAQ,UAAU,WAAW,MAAM,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK;GACvF,SAAS,WAAW,WAAW,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO,KAAK;EACnE;EAEA,QAAQ,IAAI,WAAW,MAAM;EAE7B,OAAO;CACT,CAAC;CA2BD,OAAO;EAxBL,QAAQ,OAAO,GAAG,wBAAwB,CAAC,CAAC,WAC1C,WACA,QACA,OACA;GAGA,QAAO,OAFe,SAAS,SAAS,EAAA,CAE1B,OAAO,QAAQ,KAAK;EACpC,CAAC;EACD,YAAY,WAAW,QAAQ,UAC7B,OAAO,OACL,OAAO,IAAI,aAAa;GAGtB,QAAO,OAFe,SAAS,SAAS,EAAA,CAE1B,UAAU,QAAQ,KAAK;EACvC,CAAC,CACH;EACF,QAAQ,OAAO,GAAG,wBAAwB,CAAC,CAAC,WAAW,WAAmB,QAAgB;GAGxF,QAAO,OAFe,SAAS,SAAS,EAAA,CAE1B,OAAO,MAAM;EAC7B,CAAC;CAGS;AACd,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CD,MAAa,eAAiF,MAAM,OAClG,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,OAAO,QAAqC;CACnE,MAAM,WAAW,OAAO,SAAS;CAEjC,MAAM,WAAW,OAAO,WAAW,WAAW,cAAkC;EAC9E,MAAM,aAAa,WAAW,aAAa,WAAW,IAAI;EAE1D,MAAM,WAAW,OAAO,IAAI,aAAa;GACvC,MAAM,UAAU,OAAO,OAAO;GAC9B,MAAM,SAAS,OAAO,OAAO,oBAAoB,MAAM,CAAC,CAAC,QAAQ,QAAQ,CAAC,CAAC,KAAK,OAAO,KAAK;GAE5F,MAAM,gBAAgB,OAAO,OAAO,QAAmD;GAEvF,MAAM,aAAa,YACjB,OAAO,UAAU,OAAO,CAAC,CAAC,KACxB,OAAO,QAAQ,aAAa,GAC5B,OAAO,YAAY,UAAU,OAAO,WAAW,oCAAoC,KAAK,CAAC,CAC3F;GAGF,OAAO,UAAU,KAAK;GAEtB,MAAM,OAAO,OAAOJ,KAAU;IAAE;IAAc;IAAQ;GAAU,CAAC;GACjE,OAAO,KAAK;GAEZ,OAAO,WAAW,GAAG;IAEnB,SAAS,YAAY,KAAK,OAAO,QAAQ,QAAQ,KAAK;IACtD,YAAY,YAAY,KAAK,OAAO,QAAQ,QAAQ,KAAK;IACzD,cAAc,KAAK;GACrB,CAAC;EACH,CAAC;EAED,OAAO,SAAS,eAAe,YAAY,SAAS,KAAK,OAAO,QAAQ,OAAO,CAAC,GAAG;GACjF,aAAa;GACb,aAAa;EACf,CAAC;CACH,CAAC;CAED,MAAM,SAAS,OAAO;CAEtB,OAAO,gBAAgB,GAAG;EACxB,WAAW,iBAAiB,SAAS,YAAY,CAAC,CAAC,KAAK,OAAO,QAAQ,OAAO,CAAC;EAC/E,GAAG;CACL,CAAC;AACH,CAAC,CACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAa,qBAA6E,MAAM,OAC9F,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO;CAEtB,OAAO,gBAAgB,GAAG;EACxB,WAAW,iBACT,OAAO,IACL,OAAO,aAAa,WAAW,KAAK,qJACtC;EACF,GAAG;CACL,CAAC;AACH,CAAC,CACH"}
|
package/dist/AssistantEvent.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ChatId } from "./ChatId.js";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
3
|
import * as Response from "effect/ai/Response";
|
|
4
|
+
import * as SchemaAST from "effect/SchemaAST";
|
|
4
5
|
declare namespace AssistantEvent_d_exports {
|
|
5
|
-
export { AnyEntry, AppTag, AssistantEvent, AssistantMessage, Attachment, BlockAnswer, BlocksClosed, ChatDeleted, Entry, Input, MessageBlock, ModelOutput, ModelPart, SpecialistFinished, SpecialistStarted, StoredAppEvent, ToolCallPart, ToolOutcome, ToolResult, ToolStarted, TurnCompleted, TurnFailed, TurnStarted, UserInput, UserMessage, assistantTexts, attachmentLine, formatSize, isInput, isLibraryEvent, isTurnEnd, isUserInput, isWaking, maxAttachments, textWithAttachments };
|
|
6
|
+
export { AnyEntry, AppEventSchema, AppTag, ApprovalDecision, ApprovalRequested, ApprovalsExpired, AssistantEvent, AssistantMessage, Attachment, BlockAnswer, BlocksClosed, ChatDeleted, Entry, Input, MessageBlock, ModelOutput, ModelPart, SpecialistFinished, SpecialistStarted, StoredAppEvent, ToolCallPart, ToolOutcome, ToolResult, ToolStarted, TurnCompleted, TurnFailed, TurnStarted, UserInput, UserMessage, assistantTexts, attachmentLine, formatSize, isAppInput, isInput, isLibraryEvent, isTurnEnd, isUserInput, isWaking, maxAttachments, textWithAttachments };
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Schema for a file the user attached to a message, kept by reference.
|
|
@@ -116,13 +117,30 @@ declare const SpecialistFinished_base: Schema.Class<SpecialistFinished, Schema.T
|
|
|
116
117
|
* @since 0.1.0
|
|
117
118
|
*/
|
|
118
119
|
export declare class SpecialistFinished extends SpecialistFinished_base {}
|
|
120
|
+
declare const ApprovalDecision_base: Schema.Class<ApprovalDecision, Schema.TaggedStruct<"ApprovalDecision", {
|
|
121
|
+
readonly id: Schema.brand<Schema.String, "~effect-assistant/InputId">;
|
|
122
|
+
readonly callId: Schema.String;
|
|
123
|
+
readonly decision: Schema.Literals<readonly ["approved", "rejected"]>;
|
|
124
|
+
readonly reason: Schema.optional<Schema.String>;
|
|
125
|
+
}>, {}>;
|
|
126
|
+
/**
|
|
127
|
+
* Represents the user's decision on a tool call that waits for their approval: `"approved"`, so
|
|
128
|
+
* the call runs, or `"rejected"`, with an optional short reason the model reads.
|
|
129
|
+
*
|
|
130
|
+
* Its id is fixed per call (see `ToolApproval.approve`), so a decision is stored once. A decision on
|
|
131
|
+
* a call that isn't waiting any more (decided, or expired) isn't stored.
|
|
132
|
+
*
|
|
133
|
+
* @category schemas
|
|
134
|
+
* @since 0.4.0
|
|
135
|
+
*/
|
|
136
|
+
export declare class ApprovalDecision extends ApprovalDecision_base {}
|
|
119
137
|
/**
|
|
120
138
|
* Schema for every input `Assistant.submit` accepts.
|
|
121
139
|
*
|
|
122
140
|
* @category schemas
|
|
123
141
|
* @since 0.1.0
|
|
124
142
|
*/
|
|
125
|
-
export declare const Input: Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof SpecialistFinished]>;
|
|
143
|
+
export declare const Input: Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof SpecialistFinished, typeof ApprovalDecision]>;
|
|
126
144
|
/**
|
|
127
145
|
* Represents an input to an assistant.
|
|
128
146
|
*
|
|
@@ -131,7 +149,8 @@ export declare const Input: Schema.Union<readonly [typeof UserMessage, typeof Bl
|
|
|
131
149
|
*/
|
|
132
150
|
export type Input = typeof Input.Type;
|
|
133
151
|
/**
|
|
134
|
-
* Schema for the inputs from the chat's user, which are messages and
|
|
152
|
+
* Schema for the inputs from the chat's user, which are messages, block answers, and decisions on
|
|
153
|
+
* tool calls that wait for their approval.
|
|
135
154
|
*
|
|
136
155
|
* A turn owes each one a reply. It is also what a chat client usually may submit, since the app's
|
|
137
156
|
* events come from the server.
|
|
@@ -139,7 +158,7 @@ export type Input = typeof Input.Type;
|
|
|
139
158
|
* @category schemas
|
|
140
159
|
* @since 0.1.0
|
|
141
160
|
*/
|
|
142
|
-
export declare const UserInput: Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer]>;
|
|
161
|
+
export declare const UserInput: Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof ApprovalDecision]>;
|
|
143
162
|
/**
|
|
144
163
|
* Represents an input from the chat's user.
|
|
145
164
|
*
|
|
@@ -407,13 +426,43 @@ declare const BlocksClosed_base: Schema.Class<BlocksClosed, Schema.TaggedStruct<
|
|
|
407
426
|
* @since 0.1.0
|
|
408
427
|
*/
|
|
409
428
|
export declare class BlocksClosed extends BlocksClosed_base {}
|
|
429
|
+
declare const ApprovalRequested_base: Schema.Class<ApprovalRequested, Schema.TaggedStruct<"ApprovalRequested", {
|
|
430
|
+
readonly callId: Schema.String;
|
|
431
|
+
readonly tool: Schema.String;
|
|
432
|
+
readonly params: Schema.Codec<Schema.Json, Schema.Json, never, never>;
|
|
433
|
+
}>, {}>;
|
|
434
|
+
/**
|
|
435
|
+
* Represents a tool call that waits for the user's approval instead of running: its tool needs
|
|
436
|
+
* approval (Effect's `needsApproval`).
|
|
437
|
+
*
|
|
438
|
+
* **Details**
|
|
439
|
+
*
|
|
440
|
+
* It holds the call's id, the tool, and its params, in JSON, as the model wrote them. The model
|
|
441
|
+
* reads it as the call's result, saying it waits for the user; the user's
|
|
442
|
+
* {@link ApprovalDecision} decides it, and an approved call runs then, with the same id.
|
|
443
|
+
*
|
|
444
|
+
* @category schemas
|
|
445
|
+
* @since 0.4.0
|
|
446
|
+
*/
|
|
447
|
+
export declare class ApprovalRequested extends ApprovalRequested_base {}
|
|
448
|
+
declare const ApprovalsExpired_base: Schema.Class<ApprovalsExpired, Schema.TaggedStruct<"ApprovalsExpired", {
|
|
449
|
+
readonly callIds: Schema.$Array<Schema.String>;
|
|
450
|
+
}>, {}>;
|
|
451
|
+
/**
|
|
452
|
+
* Represents tool calls that waited for the user's approval and won't run: the user moved on, like
|
|
453
|
+
* the ones `ToolApproval.closeStale` expires. The model reads that they weren't approved.
|
|
454
|
+
*
|
|
455
|
+
* @category schemas
|
|
456
|
+
* @since 0.4.0
|
|
457
|
+
*/
|
|
458
|
+
export declare class ApprovalsExpired extends ApprovalsExpired_base {}
|
|
410
459
|
/**
|
|
411
460
|
* Schema for every event the library defines.
|
|
412
461
|
*
|
|
413
462
|
* @category schemas
|
|
414
463
|
* @since 0.1.0
|
|
415
464
|
*/
|
|
416
|
-
export declare const AssistantEvent: Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof AssistantMessage, typeof ModelOutput, typeof ToolStarted, typeof ToolResult, typeof TurnStarted, typeof TurnCompleted, typeof TurnFailed, typeof SpecialistStarted, typeof SpecialistFinished, typeof BlocksClosed, typeof ChatDeleted]>;
|
|
465
|
+
export declare const AssistantEvent: Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof AssistantMessage, typeof ModelOutput, typeof ToolStarted, typeof ToolResult, typeof TurnStarted, typeof TurnCompleted, typeof TurnFailed, typeof SpecialistStarted, typeof SpecialistFinished, typeof BlocksClosed, typeof ApprovalRequested, typeof ApprovalDecision, typeof ApprovalsExpired, typeof ChatDeleted]>;
|
|
417
466
|
/**
|
|
418
467
|
* Represents an event the library defines.
|
|
419
468
|
*
|
|
@@ -432,18 +481,49 @@ export type AssistantEvent = typeof AssistantEvent.Type;
|
|
|
432
481
|
* @since 0.1.0
|
|
433
482
|
*/
|
|
434
483
|
export declare const AppTag: Schema.brand<Schema.String, "~effect-assistant/AppTag">;
|
|
484
|
+
/**
|
|
485
|
+
* Represents the schema of one of the application's own events, as `Assistant.make`'s `events`
|
|
486
|
+
* takes it.
|
|
487
|
+
*
|
|
488
|
+
* **Details**
|
|
489
|
+
*
|
|
490
|
+
* It is a tagged struct or class (`Schema.TaggedStruct`, `Schema.TaggedClass`) whose `_tag` is a
|
|
491
|
+
* string literal other than the library's, and that needs no services. The log stores the event in
|
|
492
|
+
* the schema's JSON form.
|
|
493
|
+
*
|
|
494
|
+
* @category models
|
|
495
|
+
* @since 0.3.0
|
|
496
|
+
*/
|
|
497
|
+
export type AppEventSchema = Schema.Codec<{
|
|
498
|
+
readonly _tag: string;
|
|
499
|
+
}, {
|
|
500
|
+
readonly _tag: string;
|
|
501
|
+
}> & {
|
|
502
|
+
readonly fields: {
|
|
503
|
+
readonly _tag: {
|
|
504
|
+
readonly ast: SchemaAST.AST;
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
};
|
|
435
508
|
/**
|
|
436
509
|
* Schema for one of the application's own events as it is stored, with its `_tag` and its fields
|
|
437
510
|
* in JSON.
|
|
438
511
|
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
512
|
+
* **Details**
|
|
513
|
+
*
|
|
514
|
+
* An assistant made with `events: [Summary]` decodes it to a `Summary` in `CurrentChat`'s
|
|
515
|
+
* `entries` and in its `subscribe`.
|
|
516
|
+
*
|
|
517
|
+
* An event that came in through the assistant's `submit`, from outside a turn, is an input, and the
|
|
518
|
+
* log marks it with a `"~effect-assistant/input": true` key (see {@link isAppInput}); one a turn
|
|
519
|
+
* appended has none. Decoding drops the key, so the app's schemas never see it.
|
|
441
520
|
*
|
|
442
521
|
* @category schemas
|
|
443
522
|
* @since 0.1.0
|
|
444
523
|
*/
|
|
445
524
|
export declare const StoredAppEvent: Schema.StructWithRest<Schema.Struct<{
|
|
446
525
|
readonly _tag: Schema.brand<Schema.String, "~effect-assistant/AppTag">;
|
|
526
|
+
readonly "~effect-assistant/input": Schema.optionalKey<Schema.Literal<true>>;
|
|
447
527
|
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
|
|
448
528
|
/**
|
|
449
529
|
* Represents one of the application's own events, as it is stored.
|
|
@@ -461,6 +541,18 @@ export type StoredAppEvent = typeof StoredAppEvent.Type;
|
|
|
461
541
|
export declare const isLibraryEvent: (event: {
|
|
462
542
|
readonly _tag: string;
|
|
463
543
|
}) => event is AssistantEvent;
|
|
544
|
+
/**
|
|
545
|
+
* Returns whether one of the application's events, as the log stores it, is an input: it came in
|
|
546
|
+
* through the assistant's `submit`, from outside a turn, so storing it woke a turn. An event a turn
|
|
547
|
+
* appended (`CurrentChat`'s `append`) isn't, and neither is a decoded one, which no longer has the
|
|
548
|
+
* mark.
|
|
549
|
+
*
|
|
550
|
+
* @category guards
|
|
551
|
+
* @since 0.3.0
|
|
552
|
+
*/
|
|
553
|
+
export declare const isAppInput: (event: {
|
|
554
|
+
readonly _tag: string;
|
|
555
|
+
}) => boolean;
|
|
464
556
|
/**
|
|
465
557
|
* Represents a chat's entry with any event in it, either the library's or the application's own
|
|
466
558
|
* (decoded or not).
|
|
@@ -491,8 +583,9 @@ export declare const Entry: Schema.Struct<{
|
|
|
491
583
|
readonly chatId: Schema.brand<Schema.TemplateLiteral<readonly ["chat:", Schema.String]>, "~effect-assistant/ChatId">;
|
|
492
584
|
readonly seq: Schema.Int;
|
|
493
585
|
readonly at: Schema.Finite;
|
|
494
|
-
readonly event: Schema.Union<readonly [Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof AssistantMessage, typeof ModelOutput, typeof ToolStarted, typeof ToolResult, typeof TurnStarted, typeof TurnCompleted, typeof TurnFailed, typeof SpecialistStarted, typeof SpecialistFinished, typeof BlocksClosed, typeof ChatDeleted]>, Schema.StructWithRest<Schema.Struct<{
|
|
586
|
+
readonly event: Schema.Union<readonly [Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof AssistantMessage, typeof ModelOutput, typeof ToolStarted, typeof ToolResult, typeof TurnStarted, typeof TurnCompleted, typeof TurnFailed, typeof SpecialistStarted, typeof SpecialistFinished, typeof BlocksClosed, typeof ApprovalRequested, typeof ApprovalDecision, typeof ApprovalsExpired, typeof ChatDeleted]>, Schema.StructWithRest<Schema.Struct<{
|
|
495
587
|
readonly _tag: Schema.brand<Schema.String, "~effect-assistant/AppTag">;
|
|
588
|
+
readonly "~effect-assistant/input": Schema.optionalKey<Schema.Literal<true>>;
|
|
496
589
|
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>]>;
|
|
497
590
|
}>;
|
|
498
591
|
/**
|
|
@@ -504,10 +597,10 @@ export declare const Entry: Schema.Struct<{
|
|
|
504
597
|
export type Entry = typeof Entry.Type;
|
|
505
598
|
/**
|
|
506
599
|
* Returns whether an event is one of the library's inputs, which are a user message, a block
|
|
507
|
-
* answer,
|
|
600
|
+
* answer, a finished specialist run, or a decision on a tool call waiting for approval.
|
|
508
601
|
*
|
|
509
|
-
* The application's events that
|
|
510
|
-
*
|
|
602
|
+
* The application's events that came in through the assistant's `submit` are inputs too (see
|
|
603
|
+
* {@link isAppInput}).
|
|
511
604
|
*
|
|
512
605
|
* @category guards
|
|
513
606
|
* @since 0.1.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantEvent.d.ts","names":[],"sources":["../src/AssistantEvent.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"AssistantEvent.d.ts","names":[],"sources":["../src/AssistantEvent.ts"],"mappings":";;;;;;;;;;;;;;;qBA2Ba,YAAU,OAAA;;;;;;;;;;;;;;;qBAgBV;;;;;;;YAQD,oBAAoB,WAAW;;;;;;;qBAQ9B,aAAU;;;;;;;;;qBAiBV,iBAAc,YAAgB;;;;;;;qBAS9B,sBAAmB,cAAgB,aAAe,cAAc;;;;;;;;;;;;;;;;;;;;;qBAchE,oBAAoB;;;;;;;;;;;;;;;;qBAepB,oBAAoB;;;;;;;;;;;;;;;;;;;;;qBAgBpB,2BAA2B;;;;;;;;;;;;;;;;;qBAsB3B,yBAAyB;;;;;;;qBAazB,OAAK,OAAA,uBAAA,oBAAA,oBAAA,2BAAA;;;;;;;YAQN,eAAe,MAAM;;;;;;;;;;;qBAYpB,WAAS,OAAA,uBAAA,oBAAA,oBAAA;;;;;;;YAQV,mBAAmB,UAAU;;;;;;;;;;;;qBAS5B,qBAAqB;;;;;;;;;;;;;;;qBAYrB,yBAAyB;;;;;;;;;;qBAiBzB,cAAY,OAAA;;;;;;;;;;;;;;;YAWb,sBAAsB,aAAa;;;;;;;;;;qBAWlC,WAAS,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;YAQV,mBAAmB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAU5B,oBAAoB;;;;;;;;;;;;;;qBA2BpB,oBAAoB;;;;;;;;qBAYpB,aAAW,OAAA;;;;;;;YAQZ,qBAAqB,YAAY;;;;;;;;;;;;;;;;;;qBAYhC,mBAAmB;;;;;;;;;;;;;;;qBAcnB,oBAAoB;;;;;;;;;;;;qBAiBpB,sBAAsB;;;;;;;;;;;;;;qBAatB,mBAAmB;;;;;;;;;;;;;;;;;qBAenB,0BAA0B;;;;;;;;;;;qBAgB1B,oBAAoB;;;;;;;;;;;;;qBAWpB,qBAAqB;;;;;;;;;;;;;;;;;;;qBAiBrB,0BAA0B;;;;;;;;;;;qBAa1B,yBAAyB;;;;;;;qBAUzB,gBAAc,OAAA,uBAAA,oBAAA,oBAAA,yBAAA,oBAAA,oBAAA,mBAAA,oBAAA,sBAAA,mBAAA,0BAAA,2BAAA,qBAAA,0BAAA,yBAAA,yBAAA;;;;;;;YAyBf,wBAAwB,eAAe;;;;;;;;;;;qBAgBtC,QAAM,OAAA,MAAA,OAAA;;;;;;;;;;;;;;YAeP,iBAAiB,OAAO;WAAiB;;WAA2B;;WACrE;aAAmB;eAAiB,KAAK,UAAU;;;;;;;;;;;;;;;;;;;;qBAmBjD,gBAAc,OAAA,eAAA,OAAA;;;cAG0F,OAAA,QAAA,OAAA,QAAA,OAAA,MAAA,OAAA,MAAA,OAAA;;;;;;;YAQzG,wBAAwB,eAAe;;;;;;;qBAQtC,iBAAc;WAAsB;MAAiB,SAAS;;;;;;;;;;qBAW9D,aAAU;WAAsB;;;;;;;;;;;iBAY5B;WACN,QAAQ;WACR;WACA;WACA;aAAkB;;;;;;;;;;;;qBAYhB,OAAK,OAAA;;;;;;;;;;;;;;;YAaN,eAAe,MAAM;;;;;;;;;;;qBAYpB,UAAO;WAAsB;MAAiB,SAAS;;;;;;;;qBAavD,WAAQ,OAAW;;;;;;;qBASnB,cAAW,OAAW,UAAQ,SAAS;;;;;;;qBAWvC,YAAS;WAAsB;MAAiB,SAAS,gBAAgB;;;;;;;qBASzE,iBAAc,SAAa,cAAc,cAAY"}
|