effect-assistant 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -2
  3. package/dist/AppEvent.d.ts +111 -0
  4. package/dist/AppEvent.d.ts.map +1 -0
  5. package/dist/AppEvent.js +71 -0
  6. package/dist/AppEvent.js.map +1 -0
  7. package/dist/Assistant.d.ts +403 -0
  8. package/dist/Assistant.d.ts.map +1 -0
  9. package/dist/Assistant.js +126 -0
  10. package/dist/Assistant.js.map +1 -0
  11. package/dist/AssistantActivity.d.ts +85 -0
  12. package/dist/AssistantActivity.d.ts.map +1 -0
  13. package/dist/AssistantActivity.js +105 -0
  14. package/dist/AssistantActivity.js.map +1 -0
  15. package/dist/AssistantEngine.d.ts +116 -0
  16. package/dist/AssistantEngine.d.ts.map +1 -0
  17. package/dist/AssistantEngine.js +255 -0
  18. package/dist/AssistantEngine.js.map +1 -0
  19. package/dist/AssistantEvent.d.ts +551 -0
  20. package/dist/AssistantEvent.d.ts.map +1 -0
  21. package/dist/AssistantEvent.js +473 -0
  22. package/dist/AssistantEvent.js.map +1 -0
  23. package/dist/AssistantLoop.d.ts +173 -0
  24. package/dist/AssistantLoop.d.ts.map +1 -0
  25. package/dist/AssistantLoop.js +111 -0
  26. package/dist/AssistantLoop.js.map +1 -0
  27. package/dist/AssistantToolCall.d.ts +32 -0
  28. package/dist/AssistantToolCall.d.ts.map +1 -0
  29. package/dist/AssistantToolCall.js +37 -0
  30. package/dist/AssistantToolCall.js.map +1 -0
  31. package/dist/Block.d.ts +69 -0
  32. package/dist/Block.d.ts.map +1 -0
  33. package/dist/Block.js +39 -0
  34. package/dist/Block.js.map +1 -0
  35. package/dist/BlockKit.d.ts +180 -0
  36. package/dist/BlockKit.d.ts.map +1 -0
  37. package/dist/BlockKit.js +148 -0
  38. package/dist/BlockKit.js.map +1 -0
  39. package/dist/ChatId.d.ts +31 -0
  40. package/dist/ChatId.d.ts.map +1 -0
  41. package/dist/ChatId.js +36 -0
  42. package/dist/ChatId.js.map +1 -0
  43. package/dist/ChatLog.d.ts +109 -0
  44. package/dist/ChatLog.d.ts.map +1 -0
  45. package/dist/ChatLog.js +177 -0
  46. package/dist/ChatLog.js.map +1 -0
  47. package/dist/ChatView.d.ts +408 -0
  48. package/dist/ChatView.d.ts.map +1 -0
  49. package/dist/ChatView.js +443 -0
  50. package/dist/ChatView.js.map +1 -0
  51. package/dist/Chats.d.ts +110 -0
  52. package/dist/Chats.d.ts.map +1 -0
  53. package/dist/Chats.js +98 -0
  54. package/dist/Chats.js.map +1 -0
  55. package/dist/Compaction.d.ts +105 -0
  56. package/dist/Compaction.d.ts.map +1 -0
  57. package/dist/Compaction.js +126 -0
  58. package/dist/Compaction.js.map +1 -0
  59. package/dist/CurrentChat.d.ts +40 -0
  60. package/dist/CurrentChat.d.ts.map +1 -0
  61. package/dist/CurrentChat.js +33 -0
  62. package/dist/CurrentChat.js.map +1 -0
  63. package/dist/InputId.d.ts +38 -0
  64. package/dist/InputId.d.ts.map +1 -0
  65. package/dist/InputId.js +43 -0
  66. package/dist/InputId.js.map +1 -0
  67. package/dist/Instructions.d.ts +56 -0
  68. package/dist/Instructions.d.ts.map +1 -0
  69. package/dist/Instructions.js +50 -0
  70. package/dist/Instructions.js.map +1 -0
  71. package/dist/Memory.d.ts +190 -0
  72. package/dist/Memory.d.ts.map +1 -0
  73. package/dist/Memory.js +216 -0
  74. package/dist/Memory.js.map +1 -0
  75. package/dist/RunId.d.ts +41 -0
  76. package/dist/RunId.d.ts.map +1 -0
  77. package/dist/RunId.js +46 -0
  78. package/dist/RunId.js.map +1 -0
  79. package/dist/Skill.d.ts +72 -0
  80. package/dist/Skill.d.ts.map +1 -0
  81. package/dist/Skill.js +58 -0
  82. package/dist/Skill.js.map +1 -0
  83. package/dist/Specialist.d.ts +267 -0
  84. package/dist/Specialist.d.ts.map +1 -0
  85. package/dist/Specialist.js +119 -0
  86. package/dist/Specialist.js.map +1 -0
  87. package/dist/ToolDiscovery.d.ts +107 -0
  88. package/dist/ToolDiscovery.d.ts.map +1 -0
  89. package/dist/ToolDiscovery.js +140 -0
  90. package/dist/ToolDiscovery.js.map +1 -0
  91. package/dist/ToolFailure.d.ts +66 -0
  92. package/dist/ToolFailure.d.ts.map +1 -0
  93. package/dist/ToolFailure.js +79 -0
  94. package/dist/ToolFailure.js.map +1 -0
  95. package/dist/_virtual/_rolldown/runtime.js +13 -0
  96. package/dist/index.d.ts +24 -0
  97. package/dist/index.js +24 -0
  98. package/dist/internal/chat.js +177 -0
  99. package/dist/internal/chat.js.map +1 -0
  100. package/dist/internal/events.d.ts +46 -0
  101. package/dist/internal/events.d.ts.map +1 -0
  102. package/dist/internal/events.js +65 -0
  103. package/dist/internal/events.js.map +1 -0
  104. package/dist/internal/history.js +147 -0
  105. package/dist/internal/history.js.map +1 -0
  106. package/dist/internal/instructions.js +11 -0
  107. package/dist/internal/instructions.js.map +1 -0
  108. package/dist/internal/loop.d.ts +73 -0
  109. package/dist/internal/loop.d.ts.map +1 -0
  110. package/dist/internal/loop.js +131 -0
  111. package/dist/internal/loop.js.map +1 -0
  112. package/dist/internal/schema.js +14 -0
  113. package/dist/internal/schema.js.map +1 -0
  114. package/dist/internal/skills.js +28 -0
  115. package/dist/internal/skills.js.map +1 -0
  116. package/dist/internal/specialists.js +272 -0
  117. package/dist/internal/specialists.js.map +1 -0
  118. package/dist/internal/telemetry.js +85 -0
  119. package/dist/internal/telemetry.js.map +1 -0
  120. package/dist/internal/toolkit.d.ts +62 -0
  121. package/dist/internal/toolkit.d.ts.map +1 -0
  122. package/dist/internal/toolkit.js +108 -0
  123. package/dist/internal/toolkit.js.map +1 -0
  124. package/dist/internal/tools.js +113 -0
  125. package/dist/internal/tools.js.map +1 -0
  126. package/dist/internal/turns.d.ts +64 -0
  127. package/dist/internal/turns.d.ts.map +1 -0
  128. package/dist/internal/turns.js +214 -0
  129. package/dist/internal/turns.js.map +1 -0
  130. package/dist/internal/uuid.js +45 -0
  131. package/dist/internal/uuid.js.map +1 -0
  132. package/dist/internal/writer.d.ts +12 -0
  133. package/dist/internal/writer.d.ts.map +1 -0
  134. package/package.json +52 -3
  135. package/src/AppEvent.ts +134 -0
  136. package/src/Assistant.ts +589 -0
  137. package/src/AssistantActivity.ts +150 -0
  138. package/src/AssistantEngine.ts +343 -0
  139. package/src/AssistantEvent.ts +569 -0
  140. package/src/AssistantLoop.ts +209 -0
  141. package/src/AssistantToolCall.ts +40 -0
  142. package/src/Block.ts +84 -0
  143. package/src/BlockKit.ts +298 -0
  144. package/src/ChatId.ts +36 -0
  145. package/src/ChatLog.ts +247 -0
  146. package/src/ChatView.ts +587 -0
  147. package/src/Chats.ts +122 -0
  148. package/src/Compaction.ts +207 -0
  149. package/src/CurrentChat.ts +47 -0
  150. package/src/InputId.ts +43 -0
  151. package/src/Instructions.ts +72 -0
  152. package/src/Memory.ts +276 -0
  153. package/src/RunId.ts +46 -0
  154. package/src/Skill.ts +91 -0
  155. package/src/Specialist.ts +355 -0
  156. package/src/ToolDiscovery.ts +179 -0
  157. package/src/ToolFailure.ts +74 -0
  158. package/src/index.ts +120 -0
  159. package/src/internal/chat.ts +323 -0
  160. package/src/internal/events.ts +100 -0
  161. package/src/internal/history.ts +294 -0
  162. package/src/internal/instructions.ts +9 -0
  163. package/src/internal/loop.ts +274 -0
  164. package/src/internal/schema.ts +25 -0
  165. package/src/internal/skills.ts +37 -0
  166. package/src/internal/specialists.ts +410 -0
  167. package/src/internal/telemetry.ts +125 -0
  168. package/src/internal/toolkit.ts +188 -0
  169. package/src/internal/tools.ts +159 -0
  170. package/src/internal/turns.ts +410 -0
  171. package/src/internal/uuid.ts +50 -0
  172. package/src/internal/writer.ts +12 -0
@@ -0,0 +1,569 @@
1
+ /**
2
+ * Defines the events that can happen in a chat.
3
+ *
4
+ * A chat is an append-only log of these events, and the log is the source of truth. A chat client
5
+ * receives the part of the log its user sees (`ChatView`), and the model's history is rebuilt
6
+ * from the log before every model call. Besides the library's events ({@link AssistantEvent}), a
7
+ * log holds the application's own events ({@link StoredAppEvent}).
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+ import * as Response from "effect/ai/Response"
12
+ import * as Predicate from "effect/Predicate"
13
+ import * as Schema from "effect/Schema"
14
+ import { ChatId } from "./ChatId.ts"
15
+ import { InputId, make as makeInputId } from "./InputId.ts"
16
+ import { RunId } from "./RunId.ts"
17
+
18
+ /**
19
+ * Schema for a file the user attached to a message, kept by reference.
20
+ *
21
+ * The file itself is stored elsewhere, so the log never holds its bytes.
22
+ *
23
+ * @category schemas
24
+ * @since 0.1.0
25
+ */
26
+ export const Attachment = Schema.Struct({
27
+ fileId: Schema.String.check(Schema.isMaxLength(64)),
28
+ name: Schema.String.check(Schema.isMaxLength(255)),
29
+ mediaType: Schema.String.check(Schema.isMaxLength(127)),
30
+ /** In bytes. */
31
+ size: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))
32
+ })
33
+
34
+ /**
35
+ * The maximum number of files one message can carry.
36
+ *
37
+ * Each attached file is a line in every prompt after the message.
38
+ *
39
+ * @category utils
40
+ * @since 0.1.0
41
+ */
42
+ export const maxAttachments = 10
43
+
44
+ /**
45
+ * Represents a file the user attached to a message.
46
+ *
47
+ * @category models
48
+ * @since 0.1.0
49
+ */
50
+ export type Attachment = typeof Attachment.Type
51
+
52
+ /**
53
+ * Formats a byte count for people and the model, such as `2.1 MB`.
54
+ *
55
+ * @category utils
56
+ * @since 0.1.0
57
+ */
58
+ export const formatSize = (bytes: number): string =>
59
+ bytes < 1024
60
+ ? `${bytes} B`
61
+ : bytes < 1024 * 1024
62
+ ? `${(bytes / 1024).toFixed(1)} KB`
63
+ : bytes < 1024 * 1024 * 1024
64
+ ? `${(bytes / (1024 * 1024)).toFixed(1)} MB`
65
+ : `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
66
+
67
+ /**
68
+ * Returns the line the model reads for an attachment.
69
+ *
70
+ * The line names the file and never holds its content.
71
+ *
72
+ * @category utils
73
+ * @since 0.1.0
74
+ */
75
+ export const attachmentLine = (attachment: Attachment): string =>
76
+ `[attached: ${attachment.name}, ${attachment.mediaType}, ${formatSize(attachment.size)}, id ${attachment.fileId}]`
77
+
78
+ /**
79
+ * Returns a message's text followed by a line for each attached file.
80
+ *
81
+ * @category utils
82
+ * @since 0.1.0
83
+ */
84
+ export const textWithAttachments = (text: string, attachments: ReadonlyArray<Attachment> | undefined): string =>
85
+ attachments === undefined || attachments.length === 0
86
+ ? text
87
+ : [text, ...attachments.map(attachmentLine)].filter((part) => part.length > 0).join("\n")
88
+
89
+ /**
90
+ * Represents text the user typed, and the files they attached.
91
+ *
92
+ * `id` defaults to a new one. Pass the client's own id so that a retried send stores the message
93
+ * once.
94
+ *
95
+ * @category schemas
96
+ * @since 0.1.0
97
+ */
98
+ export class UserMessage extends Schema.TaggedClass<UserMessage>()("UserMessage", {
99
+ id: InputId.pipe(Schema.withConstructorDefault(makeInputId())),
100
+ text: Schema.String,
101
+ attachments: Schema.optional(Schema.Array(Attachment).check(Schema.isMaxLength(maxAttachments)))
102
+ }) {}
103
+
104
+ /**
105
+ * Represents the user's answer to a block.
106
+ *
107
+ * It holds the id of the message the block was on, and the value in the block's answer schema
108
+ * (in its JSON form). A chat UI shows it inside the block rather than as a message.
109
+ *
110
+ * @category schemas
111
+ * @since 0.1.0
112
+ */
113
+ export class BlockAnswer extends Schema.TaggedClass<BlockAnswer>()("BlockAnswer", {
114
+ id: InputId,
115
+ messageId: Schema.String,
116
+ kind: Schema.String,
117
+ value: Schema.Unknown
118
+ }) {}
119
+
120
+ /**
121
+ * Represents a specialist run that finished working on a brief.
122
+ *
123
+ * Unless the assistant stopped the run, it is an input, and a turn reads its report and decides
124
+ * what to tell the user. Its id is fixed per run and brief, so it is stored once.
125
+ *
126
+ * @category schemas
127
+ * @since 0.1.0
128
+ */
129
+ export class SpecialistFinished extends Schema.TaggedClass<SpecialistFinished>()("SpecialistFinished", {
130
+ id: InputId,
131
+ runId: RunId,
132
+ specialist: Schema.String,
133
+ /** Which brief this answers: 1 for the first, then one more per follow-up. */
134
+ brief: Schema.Int,
135
+ goal: Schema.String,
136
+ status: Schema.Literals(["answered", "failed", "stopped", "cut_off"]),
137
+ report: Schema.optional(Schema.String),
138
+ error: Schema.optional(Schema.String)
139
+ }) {}
140
+
141
+ /**
142
+ * Schema for every input `Assistant.submit` accepts.
143
+ *
144
+ * @category schemas
145
+ * @since 0.1.0
146
+ */
147
+ export const Input = Schema.Union([UserMessage, BlockAnswer, SpecialistFinished])
148
+
149
+ /**
150
+ * Represents an input to an assistant.
151
+ *
152
+ * @category models
153
+ * @since 0.1.0
154
+ */
155
+ export type Input = typeof Input.Type
156
+
157
+ /**
158
+ * Schema for the inputs from the chat's user, which are messages and block answers.
159
+ *
160
+ * A turn owes each one a reply. It is also what a chat client usually may submit, since the app's
161
+ * events come from the server.
162
+ *
163
+ * @category schemas
164
+ * @since 0.1.0
165
+ */
166
+ export const UserInput = Schema.Union([UserMessage, BlockAnswer])
167
+
168
+ /**
169
+ * Represents an input from the chat's user.
170
+ *
171
+ * @category models
172
+ * @since 0.1.0
173
+ */
174
+ export type UserInput = typeof UserInput.Type
175
+
176
+ /**
177
+ * Represents the UI shown with a message, as a block kind from the assistant's `BlockKit` and its
178
+ * props.
179
+ *
180
+ * @category schemas
181
+ * @since 0.1.0
182
+ */
183
+ export class MessageBlock extends Schema.Class<MessageBlock>("effect-assistant/MessageBlock")({
184
+ kind: Schema.String,
185
+ props: Schema.Unknown
186
+ }) {}
187
+
188
+ /**
189
+ * Represents a message the assistant sent the user with `send_user_message`, optionally with a
190
+ * block.
191
+ *
192
+ * @category schemas
193
+ * @since 0.1.0
194
+ */
195
+ export class AssistantMessage extends Schema.TaggedClass<AssistantMessage>()("AssistantMessage", {
196
+ id: Schema.String,
197
+ text: Schema.String,
198
+ block: Schema.optional(MessageBlock),
199
+ /** Earlier messages whose block the user answered by typing instead. Their blocks close. */
200
+ closes: Schema.optional(Schema.Array(Schema.String))
201
+ }) {}
202
+
203
+ /**
204
+ * Schema for a tool call the model made.
205
+ *
206
+ * It is `effect/ai`'s tool call part (`Response.ToolCallPart`) for any tool, with its params in
207
+ * JSON, as the model wrote them.
208
+ *
209
+ * @category schemas
210
+ * @since 0.1.0
211
+ */
212
+ export const ToolCallPart = Schema.Struct({
213
+ ...Response.ToolCallPart("", Schema.Json).fields,
214
+ name: Schema.String
215
+ }).annotate({ identifier: "ToolCallPart" })
216
+
217
+ /**
218
+ * Represents a tool call the model made.
219
+ *
220
+ * @category models
221
+ * @since 0.1.0
222
+ */
223
+ export type ToolCallPart = typeof ToolCallPart.Type
224
+
225
+ /**
226
+ * Schema for what the log keeps of a model call.
227
+ *
228
+ * It uses `effect/ai`'s response parts for text, reasoning (with the provider's metadata, such as
229
+ * a thinking block's signature), and tool calls.
230
+ *
231
+ * @category schemas
232
+ * @since 0.1.0
233
+ */
234
+ export const ModelPart = Schema.Union([Response.TextPart, Response.ReasoningPart, ToolCallPart])
235
+
236
+ /**
237
+ * Represents a part of a model call's output.
238
+ *
239
+ * @category models
240
+ * @since 0.1.0
241
+ */
242
+ export type ModelPart = typeof ModelPart.Type
243
+
244
+ /**
245
+ * Represents what one model call wrote, as response parts for text, reasoning, and tool calls.
246
+ *
247
+ * `Prompt.fromResponseParts` turns the parts into history.
248
+ *
249
+ * @category schemas
250
+ * @since 0.1.0
251
+ */
252
+ export class ModelOutput extends Schema.TaggedClass<ModelOutput>()("ModelOutput", {
253
+ parts: Schema.Array(ModelPart),
254
+ /**
255
+ * The last entry the model had read when it wrote this. Inputs after it
256
+ * arrived while the model was working. A turn has handled what its
257
+ * outputs had read.
258
+ */
259
+ seenThroughSeq: Schema.optional(Schema.Int),
260
+ /** The model that wrote it, as the provider's id for it or the name the context gave it. */
261
+ model: Schema.optional(Schema.String),
262
+ /** The provider's id for the response, when it gives one. */
263
+ responseId: Schema.optional(Schema.String),
264
+ /** The call's token usage, with input (including the cache's reads and writes) and output. */
265
+ usage: Schema.optional(Response.Usage),
266
+ /** How long the call took, in milliseconds. */
267
+ durationMillis: Schema.optional(Schema.Finite)
268
+ }) {}
269
+
270
+ /**
271
+ * Represents a tool call that is about to run.
272
+ *
273
+ * It is stored before the tool runs, so after a crash the log says which calls may have done
274
+ * something. Those calls run again with the same call id (see `AssistantToolCall`).
275
+ *
276
+ * @category schemas
277
+ * @since 0.1.0
278
+ */
279
+ export class ToolStarted extends Schema.TaggedClass<ToolStarted>()("ToolStarted", {
280
+ callId: Schema.String,
281
+ tool: Schema.String
282
+ }) {}
283
+
284
+ /**
285
+ * Schema for how a tool call went, either `"success"` or `"failure"`, which the model reads as an
286
+ * error.
287
+ *
288
+ * @category schemas
289
+ * @since 0.1.0
290
+ */
291
+ export const ToolOutcome = Schema.Literals(["success", "failure"])
292
+
293
+ /**
294
+ * Represents how a tool call went.
295
+ *
296
+ * @category models
297
+ * @since 0.1.0
298
+ */
299
+ export type ToolOutcome = typeof ToolOutcome.Type
300
+
301
+ /**
302
+ * Represents the result of one tool call, as the model reads it.
303
+ *
304
+ * It holds the tool's success or failure in its encoded (JSON) form, and which of the two it is. A
305
+ * failure includes its reason when it has one, which is a `ToolFailure`'s `reason` or the
306
+ * failure's tag.
307
+ *
308
+ * @category schemas
309
+ * @since 0.1.0
310
+ */
311
+ export class ToolResult extends Schema.TaggedClass<ToolResult>()("ToolResult", {
312
+ callId: Schema.String,
313
+ tool: Schema.String,
314
+ outcome: ToolOutcome,
315
+ result: Schema.Json,
316
+ reason: Schema.optional(Schema.String)
317
+ }) {}
318
+
319
+ /**
320
+ * Represents the start of a turn.
321
+ *
322
+ * @category schemas
323
+ * @since 0.1.0
324
+ */
325
+ export class TurnStarted extends Schema.TaggedClass<TurnStarted>()("TurnStarted", {
326
+ /** The assistant that runs it. */
327
+ assistant: Schema.String,
328
+ /**
329
+ * The id of the turn's trace, when a tracer records it (not Effect's own, which exports nothing).
330
+ */
331
+ traceId: Schema.optional(Schema.String)
332
+ }) {}
333
+
334
+ /**
335
+ * Represents a turn that finished.
336
+ *
337
+ * `throughSeq` is the last entry the turn read. Inputs after it still need a turn.
338
+ *
339
+ * @category schemas
340
+ * @since 0.1.0
341
+ */
342
+ export class TurnCompleted extends Schema.TaggedClass<TurnCompleted>()("TurnCompleted", {
343
+ throughSeq: Schema.Int
344
+ }) {}
345
+
346
+ /**
347
+ * Represents a turn that failed.
348
+ *
349
+ * `throughSeq` covers everything stored when the turn failed, so a failing input isn't retried
350
+ * forever.
351
+ *
352
+ * @category schemas
353
+ * @since 0.1.0
354
+ */
355
+ export class TurnFailed extends Schema.TaggedClass<TurnFailed>()("TurnFailed", {
356
+ throughSeq: Schema.Int,
357
+ message: Schema.String
358
+ }) {}
359
+
360
+ /**
361
+ * Represents a specialist run that started working, either spawned or continued by a follow-up
362
+ * message.
363
+ *
364
+ * The model doesn't see it, since the call that started the run says so, and the run's report
365
+ * arrives as a {@link SpecialistFinished}.
366
+ *
367
+ * @category schemas
368
+ * @since 0.1.0
369
+ */
370
+ export class SpecialistStarted extends Schema.TaggedClass<SpecialistStarted>()("SpecialistStarted", {
371
+ runId: RunId,
372
+ specialist: Schema.String,
373
+ /** The goal it's working on. */
374
+ goal: Schema.String
375
+ }) {}
376
+
377
+ /**
378
+ * Represents the deletion of a chat.
379
+ *
380
+ * The chat stores nothing after it, the work running in the chat stopped, and its subscribers'
381
+ * streams end here. Its log stays.
382
+ *
383
+ * @category schemas
384
+ * @since 0.1.0
385
+ */
386
+ export class ChatDeleted extends Schema.TaggedClass<ChatDeleted>()("ChatDeleted", {}) {}
387
+
388
+ /**
389
+ * Represents blocks that closed without an answer, like the ones `BlockKit.closeStale` closes
390
+ * from a turn once the user moved on.
391
+ *
392
+ * The model doesn't see it.
393
+ *
394
+ * @category schemas
395
+ * @since 0.1.0
396
+ */
397
+ export class BlocksClosed extends Schema.TaggedClass<BlocksClosed>()("BlocksClosed", {
398
+ messageIds: Schema.Array(Schema.String)
399
+ }) {}
400
+
401
+ /**
402
+ * Schema for every event the library defines.
403
+ *
404
+ * @category schemas
405
+ * @since 0.1.0
406
+ */
407
+ export const AssistantEvent = Schema.Union([
408
+ UserMessage,
409
+ BlockAnswer,
410
+ AssistantMessage,
411
+ ModelOutput,
412
+ ToolStarted,
413
+ ToolResult,
414
+ TurnStarted,
415
+ TurnCompleted,
416
+ TurnFailed,
417
+ SpecialistStarted,
418
+ SpecialistFinished,
419
+ BlocksClosed,
420
+ ChatDeleted
421
+ ])
422
+
423
+ /**
424
+ * Represents an event the library defines.
425
+ *
426
+ * @category models
427
+ * @since 0.1.0
428
+ */
429
+ export type AssistantEvent = typeof AssistantEvent.Type
430
+
431
+ const libraryTags: ReadonlySet<string> = new Set(
432
+ AssistantEvent.members.map((member) => member.fields._tag.schema.literal)
433
+ )
434
+
435
+ /**
436
+ * Schema for the tag of one of the application's own events.
437
+ *
438
+ * It accepts any tag but the library's, and it is branded, so narrowing an entry's event to one
439
+ * of the library's events (`Predicate.isTagged(event, "UserMessage")`) never leaves an app event
440
+ * in.
441
+ *
442
+ * @category schemas
443
+ * @since 0.1.0
444
+ */
445
+ export const AppTag = Schema.String.pipe(Schema.brand("~effect-assistant/AppTag"))
446
+
447
+ /**
448
+ * Schema for one of the application's own events as it is stored, with its `_tag` and its fields
449
+ * in JSON.
450
+ *
451
+ * An assistant made with `events: [AppEvent.make(Summary)]` decodes it to a `Summary` in
452
+ * `CurrentChat`'s `entries` and in its `subscribe`.
453
+ *
454
+ * @category schemas
455
+ * @since 0.1.0
456
+ */
457
+ export const StoredAppEvent = Schema.StructWithRest(Schema.Struct({ _tag: AppTag }), [
458
+ Schema.Record(Schema.String, Schema.Json)
459
+ ]).check(Schema.makeFilter((event) => !libraryTags.has(event._tag) || `${event._tag} is one of the library's events`))
460
+
461
+ /**
462
+ * Represents one of the application's own events, as it is stored.
463
+ *
464
+ * @category models
465
+ * @since 0.1.0
466
+ */
467
+ export type StoredAppEvent = typeof StoredAppEvent.Type
468
+
469
+ /**
470
+ * Returns whether an event is one of the library's, not one of the app's own.
471
+ *
472
+ * @category guards
473
+ * @since 0.1.0
474
+ */
475
+ export const isLibraryEvent = (event: { readonly _tag: string }): event is AssistantEvent => libraryTags.has(event._tag)
476
+
477
+ /**
478
+ * Represents a chat's entry with any event in it, either the library's or the application's own
479
+ * (decoded or not).
480
+ *
481
+ * The folds over a chat take this type, so a client's typed entries fit too.
482
+ *
483
+ * @category models
484
+ * @since 0.1.0
485
+ */
486
+ export interface AnyEntry {
487
+ readonly chatId: ChatId
488
+ readonly seq: number
489
+ readonly at: number
490
+ readonly event: { readonly _tag: string }
491
+ }
492
+
493
+ /**
494
+ * Schema for a stored event with its position in the chat.
495
+ *
496
+ * `seq` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
497
+ * milliseconds.
498
+ *
499
+ * @category schemas
500
+ * @since 0.1.0
501
+ */
502
+ export const Entry = Schema.Struct({
503
+ chatId: ChatId,
504
+ seq: Schema.Int,
505
+ at: Schema.Finite,
506
+ event: Schema.Union([AssistantEvent, StoredAppEvent])
507
+ })
508
+
509
+ /**
510
+ * Represents a stored event with its position in the chat.
511
+ *
512
+ * @category models
513
+ * @since 0.1.0
514
+ */
515
+ export type Entry = typeof Entry.Type
516
+
517
+ /**
518
+ * Returns whether an event is one of the library's inputs, which are a user message, a block
519
+ * answer, or a finished specialist run.
520
+ *
521
+ * The application's events that wake a turn are inputs too, but only their assistant knows which
522
+ * (see `AppEvent`).
523
+ *
524
+ * @category guards
525
+ * @since 0.1.0
526
+ */
527
+ export const isInput = (event: { readonly _tag: string }): event is Input =>
528
+ Predicate.isTagged(event, "UserMessage") ||
529
+ Predicate.isTagged(event, "BlockAnswer") ||
530
+ Predicate.isTagged(event, "SpecialistFinished")
531
+
532
+ /**
533
+ * Returns whether an input starts a turn, which every input does except a run the assistant
534
+ * stopped itself.
535
+ *
536
+ * @category guards
537
+ * @since 0.1.0
538
+ */
539
+ export const isWaking = (input: Input): boolean =>
540
+ !(Predicate.isTagged(input, "SpecialistFinished") && input.status === "stopped")
541
+
542
+ /**
543
+ * Returns whether an input came from the chat's user, and so is owed a reply.
544
+ *
545
+ * @category guards
546
+ * @since 0.1.0
547
+ */
548
+ export const isUserInput = (input: Input): input is UserInput =>
549
+ Predicate.isTagged(input, "UserMessage") || Predicate.isTagged(input, "BlockAnswer")
550
+
551
+ /**
552
+ * Returns whether an event ends a turn, and so says how far the assistant has read.
553
+ *
554
+ * @category guards
555
+ * @since 0.1.0
556
+ */
557
+ export const isTurnEnd = (event: { readonly _tag: string }): event is TurnCompleted | TurnFailed =>
558
+ Predicate.isTagged(event, "TurnCompleted") || Predicate.isTagged(event, "TurnFailed")
559
+
560
+ /**
561
+ * Returns the text of every message the user was shown, in order.
562
+ *
563
+ * @category utils
564
+ * @since 0.1.0
565
+ */
566
+ export const assistantTexts = (entries: ReadonlyArray<AnyEntry>): ReadonlyArray<string> =>
567
+ entries.flatMap(({ event }) =>
568
+ isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") ? [event.text] : []
569
+ )