effect-assistant 0.2.0 → 0.3.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 +74 -28
- 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 +50 -5
- package/dist/AssistantEvent.d.ts.map +1 -1
- package/dist/AssistantEvent.js +28 -7
- package/dist/AssistantEvent.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 +6 -2
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +6 -2
- 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 +33 -0
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +27 -1
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +4 -1
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +482 -0
- package/dist/Timeline.d.ts.map +1 -0
- package/dist/Timeline.js +572 -0
- package/dist/Timeline.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/internal/chat.js +5 -6
- 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 +25 -19
- 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.js +1 -1
- package/dist/internal/skills.js +6 -1
- package/dist/internal/skills.js.map +1 -1
- package/dist/internal/specialists.js +5 -4
- package/dist/internal/specialists.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 +23 -35
- package/dist/internal/turns.js.map +1 -1
- package/package.json +1 -1
- package/src/Assistant.ts +75 -29
- package/src/AssistantActivity.ts +11 -18
- package/src/AssistantEngine.ts +110 -43
- package/src/AssistantEvent.ts +46 -8
- package/src/BlockKit.ts +6 -6
- package/src/Compaction.ts +6 -2
- package/src/CurrentChat.ts +2 -1
- package/src/Skill.ts +34 -1
- package/src/Specialist.ts +4 -1
- package/src/Timeline.ts +861 -0
- package/src/index.ts +5 -10
- package/src/internal/chat.ts +4 -4
- package/src/internal/events.ts +43 -43
- package/src/internal/history.ts +36 -29
- package/src/internal/input.ts +3 -0
- package/src/internal/skills.ts +11 -0
- package/src/internal/specialists.ts +3 -2
- package/src/internal/turns.ts +30 -53
- 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/src/AppEvent.ts +0 -134
- package/src/ChatView.ts +0 -587
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":"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"}
|
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, 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.
|
|
@@ -432,18 +433,49 @@ export type AssistantEvent = typeof AssistantEvent.Type;
|
|
|
432
433
|
* @since 0.1.0
|
|
433
434
|
*/
|
|
434
435
|
export declare const AppTag: Schema.brand<Schema.String, "~effect-assistant/AppTag">;
|
|
436
|
+
/**
|
|
437
|
+
* Represents the schema of one of the application's own events, as `Assistant.make`'s `events`
|
|
438
|
+
* takes it.
|
|
439
|
+
*
|
|
440
|
+
* **Details**
|
|
441
|
+
*
|
|
442
|
+
* It is a tagged struct or class (`Schema.TaggedStruct`, `Schema.TaggedClass`) whose `_tag` is a
|
|
443
|
+
* string literal other than the library's, and that needs no services. The log stores the event in
|
|
444
|
+
* the schema's JSON form.
|
|
445
|
+
*
|
|
446
|
+
* @category models
|
|
447
|
+
* @since 0.3.0
|
|
448
|
+
*/
|
|
449
|
+
export type AppEventSchema = Schema.Codec<{
|
|
450
|
+
readonly _tag: string;
|
|
451
|
+
}, {
|
|
452
|
+
readonly _tag: string;
|
|
453
|
+
}> & {
|
|
454
|
+
readonly fields: {
|
|
455
|
+
readonly _tag: {
|
|
456
|
+
readonly ast: SchemaAST.AST;
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
};
|
|
435
460
|
/**
|
|
436
461
|
* Schema for one of the application's own events as it is stored, with its `_tag` and its fields
|
|
437
462
|
* in JSON.
|
|
438
463
|
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
464
|
+
* **Details**
|
|
465
|
+
*
|
|
466
|
+
* An assistant made with `events: [Summary]` decodes it to a `Summary` in `CurrentChat`'s
|
|
467
|
+
* `entries` and in its `subscribe`.
|
|
468
|
+
*
|
|
469
|
+
* An event that came in through the assistant's `submit`, from outside a turn, is an input, and the
|
|
470
|
+
* log marks it with a `"~effect-assistant/input": true` key (see {@link isAppInput}); one a turn
|
|
471
|
+
* appended has none. Decoding drops the key, so the app's schemas never see it.
|
|
441
472
|
*
|
|
442
473
|
* @category schemas
|
|
443
474
|
* @since 0.1.0
|
|
444
475
|
*/
|
|
445
476
|
export declare const StoredAppEvent: Schema.StructWithRest<Schema.Struct<{
|
|
446
477
|
readonly _tag: Schema.brand<Schema.String, "~effect-assistant/AppTag">;
|
|
478
|
+
readonly "~effect-assistant/input": Schema.optionalKey<Schema.Literal<true>>;
|
|
447
479
|
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>;
|
|
448
480
|
/**
|
|
449
481
|
* Represents one of the application's own events, as it is stored.
|
|
@@ -461,6 +493,18 @@ export type StoredAppEvent = typeof StoredAppEvent.Type;
|
|
|
461
493
|
export declare const isLibraryEvent: (event: {
|
|
462
494
|
readonly _tag: string;
|
|
463
495
|
}) => event is AssistantEvent;
|
|
496
|
+
/**
|
|
497
|
+
* Returns whether one of the application's events, as the log stores it, is an input: it came in
|
|
498
|
+
* through the assistant's `submit`, from outside a turn, so storing it woke a turn. An event a turn
|
|
499
|
+
* appended (`CurrentChat`'s `append`) isn't, and neither is a decoded one, which no longer has the
|
|
500
|
+
* mark.
|
|
501
|
+
*
|
|
502
|
+
* @category guards
|
|
503
|
+
* @since 0.3.0
|
|
504
|
+
*/
|
|
505
|
+
export declare const isAppInput: (event: {
|
|
506
|
+
readonly _tag: string;
|
|
507
|
+
}) => boolean;
|
|
464
508
|
/**
|
|
465
509
|
* Represents a chat's entry with any event in it, either the library's or the application's own
|
|
466
510
|
* (decoded or not).
|
|
@@ -493,6 +537,7 @@ export declare const Entry: Schema.Struct<{
|
|
|
493
537
|
readonly at: Schema.Finite;
|
|
494
538
|
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<{
|
|
495
539
|
readonly _tag: Schema.brand<Schema.String, "~effect-assistant/AppTag">;
|
|
540
|
+
readonly "~effect-assistant/input": Schema.optionalKey<Schema.Literal<true>>;
|
|
496
541
|
}>, readonly [Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>]>]>;
|
|
497
542
|
}>;
|
|
498
543
|
/**
|
|
@@ -506,8 +551,8 @@ export type Entry = typeof Entry.Type;
|
|
|
506
551
|
* Returns whether an event is one of the library's inputs, which are a user message, a block
|
|
507
552
|
* answer, or a finished specialist run.
|
|
508
553
|
*
|
|
509
|
-
* The application's events that
|
|
510
|
-
*
|
|
554
|
+
* The application's events that came in through the assistant's `submit` are inputs too (see
|
|
555
|
+
* {@link isAppInput}).
|
|
511
556
|
*
|
|
512
557
|
* @category guards
|
|
513
558
|
* @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;;;;;;;qBAkB3B,OAAK,OAAA,uBAAA,oBAAA,oBAAA;;;;;;;YAQN,eAAe,MAAM;;;;;;;;;;qBAWpB,WAAS,OAAA,uBAAA,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;;;;;;;qBAUrB,gBAAc,OAAA,uBAAA,oBAAA,oBAAA,yBAAA,oBAAA,oBAAA,mBAAA,oBAAA,sBAAA,mBAAA,0BAAA,2BAAA,qBAAA;;;;;;;YAsBf,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;;;;;;;;qBAYvD,WAAQ,OAAW;;;;;;;qBASnB,cAAW,OAAW,UAAQ,SAAS;;;;;;;qBASvC,YAAS;WAAsB;MAAiB,SAAS,gBAAgB;;;;;;;qBASzE,iBAAc,SAAa,cAAc,cAAY"}
|
package/dist/AssistantEvent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { ChatId } from "./ChatId.js";
|
|
3
3
|
import { InputId, make } from "./InputId.js";
|
|
4
|
+
import { inputKey } from "./internal/input.js";
|
|
4
5
|
import { RunId } from "./RunId.js";
|
|
5
6
|
import * as Predicate from "effect/Predicate";
|
|
6
7
|
import * as Schema from "effect/Schema";
|
|
@@ -10,7 +11,7 @@ import * as Response from "effect/ai/Response";
|
|
|
10
11
|
* Defines the events that can happen in a chat.
|
|
11
12
|
*
|
|
12
13
|
* A chat is an append-only log of these events, and the log is the source of truth. A chat client
|
|
13
|
-
* receives the part of the log its user sees (`
|
|
14
|
+
* receives the part of the log its user sees (its `Timeline`), and the model's history is rebuilt
|
|
14
15
|
* from the log before every model call. Besides the library's events ({@link AssistantEvent}), a
|
|
15
16
|
* log holds the application's own events ({@link StoredAppEvent}).
|
|
16
17
|
*
|
|
@@ -44,6 +45,7 @@ var AssistantEvent_exports = /* @__PURE__ */ __exportAll({
|
|
|
44
45
|
assistantTexts: () => assistantTexts,
|
|
45
46
|
attachmentLine: () => attachmentLine,
|
|
46
47
|
formatSize: () => formatSize,
|
|
48
|
+
isAppInput: () => isAppInput,
|
|
47
49
|
isInput: () => isInput,
|
|
48
50
|
isLibraryEvent: () => isLibraryEvent,
|
|
49
51
|
isTurnEnd: () => isTurnEnd,
|
|
@@ -398,13 +400,22 @@ const AppTag = Schema.String.pipe(Schema.brand("~effect-assistant/AppTag"));
|
|
|
398
400
|
* Schema for one of the application's own events as it is stored, with its `_tag` and its fields
|
|
399
401
|
* in JSON.
|
|
400
402
|
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
+
* **Details**
|
|
404
|
+
*
|
|
405
|
+
* An assistant made with `events: [Summary]` decodes it to a `Summary` in `CurrentChat`'s
|
|
406
|
+
* `entries` and in its `subscribe`.
|
|
407
|
+
*
|
|
408
|
+
* An event that came in through the assistant's `submit`, from outside a turn, is an input, and the
|
|
409
|
+
* log marks it with a `"~effect-assistant/input": true` key (see {@link isAppInput}); one a turn
|
|
410
|
+
* appended has none. Decoding drops the key, so the app's schemas never see it.
|
|
403
411
|
*
|
|
404
412
|
* @category schemas
|
|
405
413
|
* @since 0.1.0
|
|
406
414
|
*/
|
|
407
|
-
const StoredAppEvent = Schema.StructWithRest(Schema.Struct({
|
|
415
|
+
const StoredAppEvent = Schema.StructWithRest(Schema.Struct({
|
|
416
|
+
_tag: AppTag,
|
|
417
|
+
[inputKey]: Schema.optionalKey(Schema.Literal(true))
|
|
418
|
+
}), [Schema.Record(Schema.String, Schema.Json)]).check(Schema.makeFilter((event) => !libraryTags.has(event._tag) || `${event._tag} is one of the library's events`));
|
|
408
419
|
/**
|
|
409
420
|
* Returns whether an event is one of the library's, not one of the app's own.
|
|
410
421
|
*
|
|
@@ -413,6 +424,16 @@ const StoredAppEvent = Schema.StructWithRest(Schema.Struct({ _tag: AppTag }), [S
|
|
|
413
424
|
*/
|
|
414
425
|
const isLibraryEvent = (event) => libraryTags.has(event._tag);
|
|
415
426
|
/**
|
|
427
|
+
* Returns whether one of the application's events, as the log stores it, is an input: it came in
|
|
428
|
+
* through the assistant's `submit`, from outside a turn, so storing it woke a turn. An event a turn
|
|
429
|
+
* appended (`CurrentChat`'s `append`) isn't, and neither is a decoded one, which no longer has the
|
|
430
|
+
* mark.
|
|
431
|
+
*
|
|
432
|
+
* @category guards
|
|
433
|
+
* @since 0.3.0
|
|
434
|
+
*/
|
|
435
|
+
const isAppInput = (event) => !isLibraryEvent(event) && Predicate.hasProperty(event, "~effect-assistant/input") && event["~effect-assistant/input"] === true;
|
|
436
|
+
/**
|
|
416
437
|
* Schema for a stored event with its position in the chat.
|
|
417
438
|
*
|
|
418
439
|
* `seq` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
|
|
@@ -431,8 +452,8 @@ const Entry = Schema.Struct({
|
|
|
431
452
|
* Returns whether an event is one of the library's inputs, which are a user message, a block
|
|
432
453
|
* answer, or a finished specialist run.
|
|
433
454
|
*
|
|
434
|
-
* The application's events that
|
|
435
|
-
*
|
|
455
|
+
* The application's events that came in through the assistant's `submit` are inputs too (see
|
|
456
|
+
* {@link isAppInput}).
|
|
436
457
|
*
|
|
437
458
|
* @category guards
|
|
438
459
|
* @since 0.1.0
|
|
@@ -468,6 +489,6 @@ const isTurnEnd = (event) => Predicate.isTagged(event, "TurnCompleted") || Predi
|
|
|
468
489
|
*/
|
|
469
490
|
const assistantTexts = (entries) => entries.flatMap(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") ? [event.text] : []);
|
|
470
491
|
//#endregion
|
|
471
|
-
export { AppTag, AssistantEvent, AssistantEvent_exports, 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 };
|
|
492
|
+
export { AppTag, AssistantEvent, AssistantEvent_exports, 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 };
|
|
472
493
|
|
|
473
494
|
//# sourceMappingURL=AssistantEvent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantEvent.js","names":["makeInputId"],"sources":["../src/AssistantEvent.ts"],"sourcesContent":["/**\n * Defines the events that can happen in a chat.\n *\n * A chat is an append-only log of these events, and the log is the source of truth. A chat client\n * receives the part of the log its user sees (`ChatView`), and the model's history is rebuilt\n * from the log before every model call. Besides the library's events ({@link AssistantEvent}), a\n * log holds the application's own events ({@link StoredAppEvent}).\n *\n * @since 0.1.0\n */\nimport * as Response from \"effect/ai/Response\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport { ChatId } from \"./ChatId.ts\"\nimport { InputId, make as makeInputId } from \"./InputId.ts\"\nimport { RunId } from \"./RunId.ts\"\n\n/**\n * Schema for a file the user attached to a message, kept by reference.\n *\n * The file itself is stored elsewhere, so the log never holds its bytes.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Attachment = Schema.Struct({\n fileId: Schema.String.check(Schema.isMaxLength(64)),\n name: Schema.String.check(Schema.isMaxLength(255)),\n mediaType: Schema.String.check(Schema.isMaxLength(127)),\n /** In bytes. */\n size: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))\n})\n\n/**\n * The maximum number of files one message can carry.\n *\n * Each attached file is a line in every prompt after the message.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const maxAttachments = 10\n\n/**\n * Represents a file the user attached to a message.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Attachment = typeof Attachment.Type\n\n/**\n * Formats a byte count for people and the model, such as `2.1 MB`.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const formatSize = (bytes: number): string =>\n bytes < 1024\n ? `${bytes} B`\n : bytes < 1024 * 1024\n ? `${(bytes / 1024).toFixed(1)} KB`\n : bytes < 1024 * 1024 * 1024\n ? `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n : `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n\n/**\n * Returns the line the model reads for an attachment.\n *\n * The line names the file and never holds its content.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const attachmentLine = (attachment: Attachment): string =>\n `[attached: ${attachment.name}, ${attachment.mediaType}, ${formatSize(attachment.size)}, id ${attachment.fileId}]`\n\n/**\n * Returns a message's text followed by a line for each attached file.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const textWithAttachments = (text: string, attachments: ReadonlyArray<Attachment> | undefined): string =>\n attachments === undefined || attachments.length === 0\n ? text\n : [text, ...attachments.map(attachmentLine)].filter((part) => part.length > 0).join(\"\\n\")\n\n/**\n * Represents text the user typed, and the files they attached.\n *\n * `id` defaults to a new one. Pass the client's own id so that a retried send stores the message\n * once.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class UserMessage extends Schema.TaggedClass<UserMessage>()(\"UserMessage\", {\n id: InputId.pipe(Schema.withConstructorDefault(makeInputId())),\n text: Schema.String,\n attachments: Schema.optional(Schema.Array(Attachment).check(Schema.isMaxLength(maxAttachments)))\n}) {}\n\n/**\n * Represents the user's answer to a block.\n *\n * It holds the id of the message the block was on, and the value in the block's answer schema\n * (in its JSON form). A chat UI shows it inside the block rather than as a message.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class BlockAnswer extends Schema.TaggedClass<BlockAnswer>()(\"BlockAnswer\", {\n id: InputId,\n messageId: Schema.String,\n kind: Schema.String,\n value: Schema.Unknown\n}) {}\n\n/**\n * Represents a specialist run that finished working on a brief.\n *\n * Unless the assistant stopped the run, it is an input, and a turn reads its report and decides\n * what to tell the user. Its id is fixed per run and brief, so it is stored once.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class SpecialistFinished extends Schema.TaggedClass<SpecialistFinished>()(\"SpecialistFinished\", {\n id: InputId,\n runId: RunId,\n specialist: Schema.String,\n /** Which brief this answers: 1 for the first, then one more per follow-up. */\n brief: Schema.Int,\n goal: Schema.String,\n status: Schema.Literals([\"answered\", \"failed\", \"stopped\", \"cut_off\"]),\n report: Schema.optional(Schema.String),\n error: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Schema for every input `Assistant.submit` accepts.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Input = Schema.Union([UserMessage, BlockAnswer, SpecialistFinished])\n\n/**\n * Represents an input to an assistant.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Input = typeof Input.Type\n\n/**\n * Schema for the inputs from the chat's user, which are messages and block answers.\n *\n * A turn owes each one a reply. It is also what a chat client usually may submit, since the app's\n * events come from the server.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const UserInput = Schema.Union([UserMessage, BlockAnswer])\n\n/**\n * Represents an input from the chat's user.\n *\n * @category models\n * @since 0.1.0\n */\nexport type UserInput = typeof UserInput.Type\n\n/**\n * Represents the UI shown with a message, as a block kind from the assistant's `BlockKit` and its\n * props.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class MessageBlock extends Schema.Class<MessageBlock>(\"effect-assistant/MessageBlock\")({\n kind: Schema.String,\n props: Schema.Unknown\n}) {}\n\n/**\n * Represents a message the assistant sent the user with `send_user_message`, optionally with a\n * block.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class AssistantMessage extends Schema.TaggedClass<AssistantMessage>()(\"AssistantMessage\", {\n id: Schema.String,\n text: Schema.String,\n block: Schema.optional(MessageBlock),\n /** Earlier messages whose block the user answered by typing instead. Their blocks close. */\n closes: Schema.optional(Schema.Array(Schema.String))\n}) {}\n\n/**\n * Schema for a tool call the model made.\n *\n * It is `effect/ai`'s tool call part (`Response.ToolCallPart`) for any tool, with its params in\n * JSON, as the model wrote them.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ToolCallPart = Schema.Struct({\n ...Response.ToolCallPart(\"\", Schema.Json).fields,\n name: Schema.String\n}).annotate({ identifier: \"ToolCallPart\" })\n\n/**\n * Represents a tool call the model made.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ToolCallPart = typeof ToolCallPart.Type\n\n/**\n * Schema for what the log keeps of a model call.\n *\n * It uses `effect/ai`'s response parts for text, reasoning (with the provider's metadata, such as\n * a thinking block's signature), and tool calls.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ModelPart = Schema.Union([Response.TextPart, Response.ReasoningPart, ToolCallPart])\n\n/**\n * Represents a part of a model call's output.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ModelPart = typeof ModelPart.Type\n\n/**\n * Represents what one model call wrote, as response parts for text, reasoning, and tool calls.\n *\n * `Prompt.fromResponseParts` turns the parts into history.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ModelOutput extends Schema.TaggedClass<ModelOutput>()(\"ModelOutput\", {\n parts: Schema.Array(ModelPart),\n /**\n * The last entry the model had read when it wrote this. Inputs after it\n * arrived while the model was working. A turn has handled what its\n * outputs had read.\n */\n seenThroughSeq: Schema.optional(Schema.Int),\n /** The model that wrote it, as the provider's id for it or the name the context gave it. */\n model: Schema.optional(Schema.String),\n /** The provider's id for the response, when it gives one. */\n responseId: Schema.optional(Schema.String),\n /** The call's token usage, with input (including the cache's reads and writes) and output. */\n usage: Schema.optional(Response.Usage),\n /** How long the call took, in milliseconds. */\n durationMillis: Schema.optional(Schema.Finite)\n}) {}\n\n/**\n * Represents a tool call that is about to run.\n *\n * It is stored before the tool runs, so after a crash the log says which calls may have done\n * something. Those calls run again with the same call id (see `AssistantToolCall`).\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ToolStarted extends Schema.TaggedClass<ToolStarted>()(\"ToolStarted\", {\n callId: Schema.String,\n tool: Schema.String\n}) {}\n\n/**\n * Schema for how a tool call went, either `\"success\"` or `\"failure\"`, which the model reads as an\n * error.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ToolOutcome = Schema.Literals([\"success\", \"failure\"])\n\n/**\n * Represents how a tool call went.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ToolOutcome = typeof ToolOutcome.Type\n\n/**\n * Represents the result of one tool call, as the model reads it.\n *\n * It holds the tool's success or failure in its encoded (JSON) form, and which of the two it is. A\n * failure includes its reason when it has one, which is a `ToolFailure`'s `reason` or the\n * failure's tag.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ToolResult extends Schema.TaggedClass<ToolResult>()(\"ToolResult\", {\n callId: Schema.String,\n tool: Schema.String,\n outcome: ToolOutcome,\n result: Schema.Json,\n reason: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Represents the start of a turn.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class TurnStarted extends Schema.TaggedClass<TurnStarted>()(\"TurnStarted\", {\n /** The assistant that runs it. */\n assistant: Schema.String,\n /**\n * The id of the turn's trace, when a tracer records it (not Effect's own, which exports nothing).\n */\n traceId: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Represents a turn that finished.\n *\n * `throughSeq` is the last entry the turn read. Inputs after it still need a turn.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class TurnCompleted extends Schema.TaggedClass<TurnCompleted>()(\"TurnCompleted\", {\n throughSeq: Schema.Int\n}) {}\n\n/**\n * Represents a turn that failed.\n *\n * `throughSeq` covers everything stored when the turn failed, so a failing input isn't retried\n * forever.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class TurnFailed extends Schema.TaggedClass<TurnFailed>()(\"TurnFailed\", {\n throughSeq: Schema.Int,\n message: Schema.String\n}) {}\n\n/**\n * Represents a specialist run that started working, either spawned or continued by a follow-up\n * message.\n *\n * The model doesn't see it, since the call that started the run says so, and the run's report\n * arrives as a {@link SpecialistFinished}.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class SpecialistStarted extends Schema.TaggedClass<SpecialistStarted>()(\"SpecialistStarted\", {\n runId: RunId,\n specialist: Schema.String,\n /** The goal it's working on. */\n goal: Schema.String\n}) {}\n\n/**\n * Represents the deletion of a chat.\n *\n * The chat stores nothing after it, the work running in the chat stopped, and its subscribers'\n * streams end here. Its log stays.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ChatDeleted extends Schema.TaggedClass<ChatDeleted>()(\"ChatDeleted\", {}) {}\n\n/**\n * Represents blocks that closed without an answer, like the ones `BlockKit.closeStale` closes\n * from a turn once the user moved on.\n *\n * The model doesn't see it.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class BlocksClosed extends Schema.TaggedClass<BlocksClosed>()(\"BlocksClosed\", {\n messageIds: Schema.Array(Schema.String)\n}) {}\n\n/**\n * Schema for every event the library defines.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const AssistantEvent = Schema.Union([\n UserMessage,\n BlockAnswer,\n AssistantMessage,\n ModelOutput,\n ToolStarted,\n ToolResult,\n TurnStarted,\n TurnCompleted,\n TurnFailed,\n SpecialistStarted,\n SpecialistFinished,\n BlocksClosed,\n ChatDeleted\n])\n\n/**\n * Represents an event the library defines.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AssistantEvent = typeof AssistantEvent.Type\n\nconst libraryTags: ReadonlySet<string> = new Set(\n AssistantEvent.members.map((member) => member.fields._tag.schema.literal)\n)\n\n/**\n * Schema for the tag of one of the application's own events.\n *\n * It accepts any tag but the library's, and it is branded, so narrowing an entry's event to one\n * of the library's events (`Predicate.isTagged(event, \"UserMessage\")`) never leaves an app event\n * in.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const AppTag = Schema.String.pipe(Schema.brand(\"~effect-assistant/AppTag\"))\n\n/**\n * Schema for one of the application's own events as it is stored, with its `_tag` and its fields\n * in JSON.\n *\n * An assistant made with `events: [AppEvent.make(Summary)]` decodes it to a `Summary` in\n * `CurrentChat`'s `entries` and in its `subscribe`.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const StoredAppEvent = Schema.StructWithRest(Schema.Struct({ _tag: AppTag }), [\n Schema.Record(Schema.String, Schema.Json)\n]).check(Schema.makeFilter((event) => !libraryTags.has(event._tag) || `${event._tag} is one of the library's events`))\n\n/**\n * Represents one of the application's own events, as it is stored.\n *\n * @category models\n * @since 0.1.0\n */\nexport type StoredAppEvent = typeof StoredAppEvent.Type\n\n/**\n * Returns whether an event is one of the library's, not one of the app's own.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isLibraryEvent = (event: { readonly _tag: string }): event is AssistantEvent => libraryTags.has(event._tag)\n\n/**\n * Represents a chat's entry with any event in it, either the library's or the application's own\n * (decoded or not).\n *\n * The folds over a chat take this type, so a client's typed entries fit too.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface AnyEntry {\n readonly chatId: ChatId\n readonly seq: number\n readonly at: number\n readonly event: { readonly _tag: string }\n}\n\n/**\n * Schema for a stored event with its position in the chat.\n *\n * `seq` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch\n * milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Entry = Schema.Struct({\n chatId: ChatId,\n seq: Schema.Int,\n at: Schema.Finite,\n event: Schema.Union([AssistantEvent, StoredAppEvent])\n})\n\n/**\n * Represents a stored event with its position in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Entry = typeof Entry.Type\n\n/**\n * Returns whether an event is one of the library's inputs, which are a user message, a block\n * answer, or a finished specialist run.\n *\n * The application's events that wake a turn are inputs too, but only their assistant knows which\n * (see `AppEvent`).\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isInput = (event: { readonly _tag: string }): event is Input =>\n Predicate.isTagged(event, \"UserMessage\") ||\n Predicate.isTagged(event, \"BlockAnswer\") ||\n Predicate.isTagged(event, \"SpecialistFinished\")\n\n/**\n * Returns whether an input starts a turn, which every input does except a run the assistant\n * stopped itself.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isWaking = (input: Input): boolean =>\n !(Predicate.isTagged(input, \"SpecialistFinished\") && input.status === \"stopped\")\n\n/**\n * Returns whether an input came from the chat's user, and so is owed a reply.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isUserInput = (input: Input): input is UserInput =>\n Predicate.isTagged(input, \"UserMessage\") || Predicate.isTagged(input, \"BlockAnswer\")\n\n/**\n * Returns whether an event ends a turn, and so says how far the assistant has read.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isTurnEnd = (event: { readonly _tag: string }): event is TurnCompleted | TurnFailed =>\n Predicate.isTagged(event, \"TurnCompleted\") || Predicate.isTagged(event, \"TurnFailed\")\n\n/**\n * Returns the text of every message the user was shown, in order.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const assistantTexts = (entries: ReadonlyArray<AnyEntry>): ReadonlyArray<string> =>\n entries.flatMap(({ event }) =>\n isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") ? [event.text] : []\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AssistantEvent.js","names":["makeInputId"],"sources":["../src/AssistantEvent.ts"],"sourcesContent":["/**\n * Defines the events that can happen in a chat.\n *\n * A chat is an append-only log of these events, and the log is the source of truth. A chat client\n * receives the part of the log its user sees (its `Timeline`), and the model's history is rebuilt\n * from the log before every model call. Besides the library's events ({@link AssistantEvent}), a\n * log holds the application's own events ({@link StoredAppEvent}).\n *\n * @since 0.1.0\n */\nimport * as Response from \"effect/ai/Response\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport type * as SchemaAST from \"effect/SchemaAST\"\nimport { ChatId } from \"./ChatId.ts\"\nimport { InputId, make as makeInputId } from \"./InputId.ts\"\nimport { inputKey } from \"./internal/input.ts\"\nimport { RunId } from \"./RunId.ts\"\n\n/**\n * Schema for a file the user attached to a message, kept by reference.\n *\n * The file itself is stored elsewhere, so the log never holds its bytes.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Attachment = Schema.Struct({\n fileId: Schema.String.check(Schema.isMaxLength(64)),\n name: Schema.String.check(Schema.isMaxLength(255)),\n mediaType: Schema.String.check(Schema.isMaxLength(127)),\n /** In bytes. */\n size: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))\n})\n\n/**\n * The maximum number of files one message can carry.\n *\n * Each attached file is a line in every prompt after the message.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const maxAttachments = 10\n\n/**\n * Represents a file the user attached to a message.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Attachment = typeof Attachment.Type\n\n/**\n * Formats a byte count for people and the model, such as `2.1 MB`.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const formatSize = (bytes: number): string =>\n bytes < 1024\n ? `${bytes} B`\n : bytes < 1024 * 1024\n ? `${(bytes / 1024).toFixed(1)} KB`\n : bytes < 1024 * 1024 * 1024\n ? `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n : `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n\n/**\n * Returns the line the model reads for an attachment.\n *\n * The line names the file and never holds its content.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const attachmentLine = (attachment: Attachment): string =>\n `[attached: ${attachment.name}, ${attachment.mediaType}, ${formatSize(attachment.size)}, id ${attachment.fileId}]`\n\n/**\n * Returns a message's text followed by a line for each attached file.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const textWithAttachments = (text: string, attachments: ReadonlyArray<Attachment> | undefined): string =>\n attachments === undefined || attachments.length === 0\n ? text\n : [text, ...attachments.map(attachmentLine)].filter((part) => part.length > 0).join(\"\\n\")\n\n/**\n * Represents text the user typed, and the files they attached.\n *\n * `id` defaults to a new one. Pass the client's own id so that a retried send stores the message\n * once.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class UserMessage extends Schema.TaggedClass<UserMessage>()(\"UserMessage\", {\n id: InputId.pipe(Schema.withConstructorDefault(makeInputId())),\n text: Schema.String,\n attachments: Schema.optional(Schema.Array(Attachment).check(Schema.isMaxLength(maxAttachments)))\n}) {}\n\n/**\n * Represents the user's answer to a block.\n *\n * It holds the id of the message the block was on, and the value in the block's answer schema\n * (in its JSON form). A chat UI shows it inside the block rather than as a message.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class BlockAnswer extends Schema.TaggedClass<BlockAnswer>()(\"BlockAnswer\", {\n id: InputId,\n messageId: Schema.String,\n kind: Schema.String,\n value: Schema.Unknown\n}) {}\n\n/**\n * Represents a specialist run that finished working on a brief.\n *\n * Unless the assistant stopped the run, it is an input, and a turn reads its report and decides\n * what to tell the user. Its id is fixed per run and brief, so it is stored once.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class SpecialistFinished extends Schema.TaggedClass<SpecialistFinished>()(\"SpecialistFinished\", {\n id: InputId,\n runId: RunId,\n specialist: Schema.String,\n /** Which brief this answers: 1 for the first, then one more per follow-up. */\n brief: Schema.Int,\n goal: Schema.String,\n status: Schema.Literals([\"answered\", \"failed\", \"stopped\", \"cut_off\"]),\n report: Schema.optional(Schema.String),\n error: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Schema for every input `Assistant.submit` accepts.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Input = Schema.Union([UserMessage, BlockAnswer, SpecialistFinished])\n\n/**\n * Represents an input to an assistant.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Input = typeof Input.Type\n\n/**\n * Schema for the inputs from the chat's user, which are messages and block answers.\n *\n * A turn owes each one a reply. It is also what a chat client usually may submit, since the app's\n * events come from the server.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const UserInput = Schema.Union([UserMessage, BlockAnswer])\n\n/**\n * Represents an input from the chat's user.\n *\n * @category models\n * @since 0.1.0\n */\nexport type UserInput = typeof UserInput.Type\n\n/**\n * Represents the UI shown with a message, as a block kind from the assistant's `BlockKit` and its\n * props.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class MessageBlock extends Schema.Class<MessageBlock>(\"effect-assistant/MessageBlock\")({\n kind: Schema.String,\n props: Schema.Unknown\n}) {}\n\n/**\n * Represents a message the assistant sent the user with `send_user_message`, optionally with a\n * block.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class AssistantMessage extends Schema.TaggedClass<AssistantMessage>()(\"AssistantMessage\", {\n id: Schema.String,\n text: Schema.String,\n block: Schema.optional(MessageBlock),\n /** Earlier messages whose block the user answered by typing instead. Their blocks close. */\n closes: Schema.optional(Schema.Array(Schema.String))\n}) {}\n\n/**\n * Schema for a tool call the model made.\n *\n * It is `effect/ai`'s tool call part (`Response.ToolCallPart`) for any tool, with its params in\n * JSON, as the model wrote them.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ToolCallPart = Schema.Struct({\n ...Response.ToolCallPart(\"\", Schema.Json).fields,\n name: Schema.String\n}).annotate({ identifier: \"ToolCallPart\" })\n\n/**\n * Represents a tool call the model made.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ToolCallPart = typeof ToolCallPart.Type\n\n/**\n * Schema for what the log keeps of a model call.\n *\n * It uses `effect/ai`'s response parts for text, reasoning (with the provider's metadata, such as\n * a thinking block's signature), and tool calls.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ModelPart = Schema.Union([Response.TextPart, Response.ReasoningPart, ToolCallPart])\n\n/**\n * Represents a part of a model call's output.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ModelPart = typeof ModelPart.Type\n\n/**\n * Represents what one model call wrote, as response parts for text, reasoning, and tool calls.\n *\n * `Prompt.fromResponseParts` turns the parts into history.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ModelOutput extends Schema.TaggedClass<ModelOutput>()(\"ModelOutput\", {\n parts: Schema.Array(ModelPart),\n /**\n * The last entry the model had read when it wrote this. Inputs after it\n * arrived while the model was working. A turn has handled what its\n * outputs had read.\n */\n seenThroughSeq: Schema.optional(Schema.Int),\n /** The model that wrote it, as the provider's id for it or the name the context gave it. */\n model: Schema.optional(Schema.String),\n /** The provider's id for the response, when it gives one. */\n responseId: Schema.optional(Schema.String),\n /** The call's token usage, with input (including the cache's reads and writes) and output. */\n usage: Schema.optional(Response.Usage),\n /** How long the call took, in milliseconds. */\n durationMillis: Schema.optional(Schema.Finite)\n}) {}\n\n/**\n * Represents a tool call that is about to run.\n *\n * It is stored before the tool runs, so after a crash the log says which calls may have done\n * something. Those calls run again with the same call id (see `AssistantToolCall`).\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ToolStarted extends Schema.TaggedClass<ToolStarted>()(\"ToolStarted\", {\n callId: Schema.String,\n tool: Schema.String\n}) {}\n\n/**\n * Schema for how a tool call went, either `\"success\"` or `\"failure\"`, which the model reads as an\n * error.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ToolOutcome = Schema.Literals([\"success\", \"failure\"])\n\n/**\n * Represents how a tool call went.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ToolOutcome = typeof ToolOutcome.Type\n\n/**\n * Represents the result of one tool call, as the model reads it.\n *\n * It holds the tool's success or failure in its encoded (JSON) form, and which of the two it is. A\n * failure includes its reason when it has one, which is a `ToolFailure`'s `reason` or the\n * failure's tag.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ToolResult extends Schema.TaggedClass<ToolResult>()(\"ToolResult\", {\n callId: Schema.String,\n tool: Schema.String,\n outcome: ToolOutcome,\n result: Schema.Json,\n reason: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Represents the start of a turn.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class TurnStarted extends Schema.TaggedClass<TurnStarted>()(\"TurnStarted\", {\n /** The assistant that runs it. */\n assistant: Schema.String,\n /**\n * The id of the turn's trace, when a tracer records it (not Effect's own, which exports nothing).\n */\n traceId: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Represents a turn that finished.\n *\n * `throughSeq` is the last entry the turn read. Inputs after it still need a turn.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class TurnCompleted extends Schema.TaggedClass<TurnCompleted>()(\"TurnCompleted\", {\n throughSeq: Schema.Int\n}) {}\n\n/**\n * Represents a turn that failed.\n *\n * `throughSeq` covers everything stored when the turn failed, so a failing input isn't retried\n * forever.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class TurnFailed extends Schema.TaggedClass<TurnFailed>()(\"TurnFailed\", {\n throughSeq: Schema.Int,\n message: Schema.String\n}) {}\n\n/**\n * Represents a specialist run that started working, either spawned or continued by a follow-up\n * message.\n *\n * The model doesn't see it, since the call that started the run says so, and the run's report\n * arrives as a {@link SpecialistFinished}.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class SpecialistStarted extends Schema.TaggedClass<SpecialistStarted>()(\"SpecialistStarted\", {\n runId: RunId,\n specialist: Schema.String,\n /** The goal it's working on. */\n goal: Schema.String\n}) {}\n\n/**\n * Represents the deletion of a chat.\n *\n * The chat stores nothing after it, the work running in the chat stopped, and its subscribers'\n * streams end here. Its log stays.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ChatDeleted extends Schema.TaggedClass<ChatDeleted>()(\"ChatDeleted\", {}) {}\n\n/**\n * Represents blocks that closed without an answer, like the ones `BlockKit.closeStale` closes\n * from a turn once the user moved on.\n *\n * The model doesn't see it.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class BlocksClosed extends Schema.TaggedClass<BlocksClosed>()(\"BlocksClosed\", {\n messageIds: Schema.Array(Schema.String)\n}) {}\n\n/**\n * Schema for every event the library defines.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const AssistantEvent = Schema.Union([\n UserMessage,\n BlockAnswer,\n AssistantMessage,\n ModelOutput,\n ToolStarted,\n ToolResult,\n TurnStarted,\n TurnCompleted,\n TurnFailed,\n SpecialistStarted,\n SpecialistFinished,\n BlocksClosed,\n ChatDeleted\n])\n\n/**\n * Represents an event the library defines.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AssistantEvent = typeof AssistantEvent.Type\n\nconst libraryTags: ReadonlySet<string> = new Set(\n AssistantEvent.members.map((member) => member.fields._tag.schema.literal)\n)\n\n/**\n * Schema for the tag of one of the application's own events.\n *\n * It accepts any tag but the library's, and it is branded, so narrowing an entry's event to one\n * of the library's events (`Predicate.isTagged(event, \"UserMessage\")`) never leaves an app event\n * in.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const AppTag = Schema.String.pipe(Schema.brand(\"~effect-assistant/AppTag\"))\n\n/**\n * Represents the schema of one of the application's own events, as `Assistant.make`'s `events`\n * takes it.\n *\n * **Details**\n *\n * It is a tagged struct or class (`Schema.TaggedStruct`, `Schema.TaggedClass`) whose `_tag` is a\n * string literal other than the library's, and that needs no services. The log stores the event in\n * the schema's JSON form.\n *\n * @category models\n * @since 0.3.0\n */\nexport type AppEventSchema = Schema.Codec<{ readonly _tag: string }, { readonly _tag: string }> & {\n readonly fields: { readonly _tag: { readonly ast: SchemaAST.AST } }\n}\n\n/**\n * Schema for one of the application's own events as it is stored, with its `_tag` and its fields\n * in JSON.\n *\n * **Details**\n *\n * An assistant made with `events: [Summary]` decodes it to a `Summary` in `CurrentChat`'s\n * `entries` and in its `subscribe`.\n *\n * An event that came in through the assistant's `submit`, from outside a turn, is an input, and the\n * log marks it with a `\"~effect-assistant/input\": true` key (see {@link isAppInput}); one a turn\n * appended has none. Decoding drops the key, so the app's schemas never see it.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const StoredAppEvent = Schema.StructWithRest(\n Schema.Struct({ _tag: AppTag, [inputKey]: Schema.optionalKey(Schema.Literal(true)) }),\n [Schema.Record(Schema.String, Schema.Json)]\n).check(Schema.makeFilter((event) => !libraryTags.has(event._tag) || `${event._tag} is one of the library's events`))\n\n/**\n * Represents one of the application's own events, as it is stored.\n *\n * @category models\n * @since 0.1.0\n */\nexport type StoredAppEvent = typeof StoredAppEvent.Type\n\n/**\n * Returns whether an event is one of the library's, not one of the app's own.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isLibraryEvent = (event: { readonly _tag: string }): event is AssistantEvent => libraryTags.has(event._tag)\n\n/**\n * Returns whether one of the application's events, as the log stores it, is an input: it came in\n * through the assistant's `submit`, from outside a turn, so storing it woke a turn. An event a turn\n * appended (`CurrentChat`'s `append`) isn't, and neither is a decoded one, which no longer has the\n * mark.\n *\n * @category guards\n * @since 0.3.0\n */\nexport const isAppInput = (event: { readonly _tag: string }): boolean =>\n !isLibraryEvent(event) && Predicate.hasProperty(event, inputKey) && event[inputKey] === true\n\n/**\n * Represents a chat's entry with any event in it, either the library's or the application's own\n * (decoded or not).\n *\n * The folds over a chat take this type, so a client's typed entries fit too.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface AnyEntry {\n readonly chatId: ChatId\n readonly seq: number\n readonly at: number\n readonly event: { readonly _tag: string }\n}\n\n/**\n * Schema for a stored event with its position in the chat.\n *\n * `seq` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch\n * milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Entry = Schema.Struct({\n chatId: ChatId,\n seq: Schema.Int,\n at: Schema.Finite,\n event: Schema.Union([AssistantEvent, StoredAppEvent])\n})\n\n/**\n * Represents a stored event with its position in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Entry = typeof Entry.Type\n\n/**\n * Returns whether an event is one of the library's inputs, which are a user message, a block\n * answer, or a finished specialist run.\n *\n * The application's events that came in through the assistant's `submit` are inputs too (see\n * {@link isAppInput}).\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isInput = (event: { readonly _tag: string }): event is Input =>\n Predicate.isTagged(event, \"UserMessage\") ||\n Predicate.isTagged(event, \"BlockAnswer\") ||\n Predicate.isTagged(event, \"SpecialistFinished\")\n\n/**\n * Returns whether an input starts a turn, which every input does except a run the assistant\n * stopped itself.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isWaking = (input: Input): boolean =>\n !(Predicate.isTagged(input, \"SpecialistFinished\") && input.status === \"stopped\")\n\n/**\n * Returns whether an input came from the chat's user, and so is owed a reply.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isUserInput = (input: Input): input is UserInput =>\n Predicate.isTagged(input, \"UserMessage\") || Predicate.isTagged(input, \"BlockAnswer\")\n\n/**\n * Returns whether an event ends a turn, and so says how far the assistant has read.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isTurnEnd = (event: { readonly _tag: string }): event is TurnCompleted | TurnFailed =>\n Predicate.isTagged(event, \"TurnCompleted\") || Predicate.isTagged(event, \"TurnFailed\")\n\n/**\n * Returns the text of every message the user was shown, in order.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const assistantTexts = (entries: ReadonlyArray<AnyEntry>): ReadonlyArray<string> =>\n entries.flatMap(({ event }) =>\n isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") ? [event.text] : []\n )\n"],"mappings":"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"}
|
package/dist/BlockKit.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnyEntry, AssistantEvent_d_exports, BlockAnswer } from "./AssistantEvent.js";
|
|
2
2
|
import { Any, Block_d_exports } from "./Block.js";
|
|
3
|
-
import { AssistantMessage,
|
|
3
|
+
import { AssistantMessage, Timeline_d_exports } from "./Timeline.js";
|
|
4
4
|
import { CurrentChat } from "./CurrentChat.js";
|
|
5
5
|
import * as Effect from "effect/Effect";
|
|
6
6
|
import * as Schema from "effect/Schema";
|
|
@@ -136,8 +136,8 @@ export interface RenderOptions {
|
|
|
136
136
|
readonly respond: (answer: BlockAnswer) => void;
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
|
-
* Creates a client-side renderer for the blocks on a chat
|
|
140
|
-
* (`
|
|
139
|
+
* Creates a client-side renderer for the blocks on the assistant's messages in a chat's timeline
|
|
140
|
+
* (`Timeline.AssistantMessage`).
|
|
141
141
|
*
|
|
142
142
|
* **Details**
|
|
143
143
|
*
|
|
@@ -167,7 +167,7 @@ export interface RenderOptions {
|
|
|
167
167
|
* order_draft: ({ props }) => <button onClick={() => placeOrder(props)}>Place order</button>
|
|
168
168
|
* })
|
|
169
169
|
*
|
|
170
|
-
* // For each AssistantMessage in the chat's
|
|
170
|
+
* // For each AssistantMessage in the chat's timeline:
|
|
171
171
|
* renderBlock(message, { respond: (answer) => submit(answer) })
|
|
172
172
|
* ```
|
|
173
173
|
*
|
package/dist/BlockKit.js
CHANGED
|
@@ -2,8 +2,8 @@ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
|
2
2
|
import { fromKey } from "./InputId.js";
|
|
3
3
|
import { BlockAnswer, BlocksClosed, isLibraryEvent } from "./AssistantEvent.js";
|
|
4
4
|
import { CurrentChat } from "./CurrentChat.js";
|
|
5
|
-
import * as Predicate from "effect/Predicate";
|
|
6
5
|
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Predicate from "effect/Predicate";
|
|
7
7
|
import * as Schema from "effect/Schema";
|
|
8
8
|
import * as Option from "effect/Option";
|
|
9
9
|
//#region src/BlockKit.ts
|