effect-assistant 0.5.0 → 0.6.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 (73) hide show
  1. package/dist/Assistant.d.ts +10 -10
  2. package/dist/Assistant.d.ts.map +1 -1
  3. package/dist/Assistant.js +1 -1
  4. package/dist/Assistant.js.map +1 -1
  5. package/dist/AssistantActivity.d.ts +2 -2
  6. package/dist/AssistantActivity.d.ts.map +1 -1
  7. package/dist/AssistantActivity.js +5 -5
  8. package/dist/AssistantActivity.js.map +1 -1
  9. package/dist/AssistantEngine.d.ts +3 -3
  10. package/dist/AssistantEngine.js +2 -2
  11. package/dist/AssistantEngine.js.map +1 -1
  12. package/dist/AssistantEvent.d.ts +8 -8
  13. package/dist/AssistantEvent.js +7 -7
  14. package/dist/AssistantEvent.js.map +1 -1
  15. package/dist/AssistantLoop.d.ts +2 -2
  16. package/dist/AssistantLoop.js.map +1 -1
  17. package/dist/ChatLog.d.ts +9 -9
  18. package/dist/ChatLog.d.ts.map +1 -1
  19. package/dist/ChatLog.js +21 -21
  20. package/dist/ChatLog.js.map +1 -1
  21. package/dist/Compaction.d.ts +10 -10
  22. package/dist/Compaction.d.ts.map +1 -1
  23. package/dist/Compaction.js +16 -16
  24. package/dist/Compaction.js.map +1 -1
  25. package/dist/Memory.d.ts +1 -1
  26. package/dist/Memory.js +1 -1
  27. package/dist/Memory.js.map +1 -1
  28. package/dist/Skill.d.ts +1 -1
  29. package/dist/Skill.js +1 -1
  30. package/dist/Skill.js.map +1 -1
  31. package/dist/Specialist.d.ts +1 -1
  32. package/dist/Specialist.js +1 -1
  33. package/dist/Specialist.js.map +1 -1
  34. package/dist/Timeline.d.ts +9 -9
  35. package/dist/Timeline.js +16 -16
  36. package/dist/Timeline.js.map +1 -1
  37. package/dist/ToolApproval.d.ts +1 -1
  38. package/dist/ToolApproval.js +1 -1
  39. package/dist/ToolApproval.js.map +1 -1
  40. package/dist/ToolDiscovery.d.ts +1 -1
  41. package/dist/ToolDiscovery.js +1 -1
  42. package/dist/ToolDiscovery.js.map +1 -1
  43. package/dist/internal/chat.js +4 -4
  44. package/dist/internal/chat.js.map +1 -1
  45. package/dist/internal/events.js +1 -1
  46. package/dist/internal/events.js.map +1 -1
  47. package/dist/internal/history.js +23 -23
  48. package/dist/internal/history.js.map +1 -1
  49. package/dist/internal/loop.d.ts +1 -1
  50. package/dist/internal/loop.js +4 -4
  51. package/dist/internal/loop.js.map +1 -1
  52. package/dist/internal/turns.d.ts.map +1 -1
  53. package/dist/internal/turns.js +18 -18
  54. package/dist/internal/turns.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/Assistant.ts +11 -11
  57. package/src/AssistantActivity.ts +9 -7
  58. package/src/AssistantEngine.ts +3 -3
  59. package/src/AssistantEvent.ts +8 -8
  60. package/src/AssistantLoop.ts +3 -3
  61. package/src/ChatLog.ts +31 -26
  62. package/src/Compaction.ts +21 -21
  63. package/src/Memory.ts +1 -1
  64. package/src/Skill.ts +1 -1
  65. package/src/Specialist.ts +1 -1
  66. package/src/Timeline.ts +22 -22
  67. package/src/ToolApproval.ts +1 -1
  68. package/src/ToolDiscovery.ts +1 -1
  69. package/src/internal/chat.ts +4 -4
  70. package/src/internal/events.ts +1 -1
  71. package/src/internal/history.ts +32 -28
  72. package/src/internal/loop.ts +5 -5
  73. package/src/internal/turns.ts +28 -26
@@ -19,7 +19,7 @@ import * as Toolkit from "effect/ai/Toolkit";
19
19
  import * as LanguageModel from "effect/ai/LanguageModel";
20
20
  import * as AiError from "effect/ai/AiError";
21
21
  declare namespace Assistant_d_exports {
22
- export { Any, Assistant, Chat, ChatIdentifier, Entry, Handler, HandlerServices, HistoryView, Identifier, LoopOptions, ReplyOptions, Service, Turn, TurnServices, isAssistant, make };
22
+ export { Any, Assistant, Chat, ChatIdentifier, Entry, Handler, HandlerServices, HistoryView, Identifier, ReplyOptions, RunOptions, Service, Turn, TurnServices, isAssistant, make };
23
23
  }
24
24
  declare const TypeId: "~effect-assistant/Assistant";
25
25
  /**
@@ -31,12 +31,12 @@ declare const TypeId: "~effect-assistant/Assistant";
31
31
  */
32
32
  export type TurnServices = CurrentChat | Runner;
33
33
  /**
34
- * Represents the options accepted by `turn.loop`.
34
+ * Represents the options accepted by `turn.run`.
35
35
  *
36
36
  * @category models
37
37
  * @since 0.1.0
38
38
  */
39
- export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never, never>, Events = never> {
39
+ export interface RunOptions<T extends Record<string, Tool.Any> = Record<never, never>, Events = never> {
40
40
  /** The assistant's role and rules. The loop places `Instructions.protocol` before them. */
41
41
  readonly instructions: string | Resolved;
42
42
  /**
@@ -97,7 +97,7 @@ export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never,
97
97
  *
98
98
  * // An `OrderPlaced` that the API submits wakes a turn, which reads it as this note.
99
99
  * const MyAssistantLive = MyAssistant.toLayer((turn) =>
100
- * turn.loop({ instructions: "Be brief.", history: withOrders })
100
+ * turn.run({ instructions: "Be brief.", history: withOrders })
101
101
  * )
102
102
  * ```
103
103
  *
@@ -113,7 +113,7 @@ export type HistoryView<Events = never> = (entries: ReadonlyArray<Entry<Events>>
113
113
  */
114
114
  export interface Entry<Events = never> {
115
115
  readonly chatId: ChatId;
116
- readonly seq: number;
116
+ readonly sequence: number;
117
117
  readonly at: number;
118
118
  readonly event: AssistantEvent | Events;
119
119
  }
@@ -124,7 +124,7 @@ export interface Entry<Events = never> {
124
124
  * @since 0.1.0
125
125
  */
126
126
  export interface ReplyOptions {
127
- /** The assistant's instructions, as passed to `turn.loop`. `Instructions.protocol` is added. */
127
+ /** The assistant's instructions, as passed to `turn.run`. `Instructions.protocol` is added. */
128
128
  readonly instructions: string | Resolved;
129
129
  /**
130
130
  * The message given to the model after the history. Defaults to a message saying that it
@@ -162,13 +162,13 @@ export interface Turn<Events = never> {
162
162
  * tools, and the instructions and tools in `options`. Every step is a tool call, and
163
163
  * the loop ends with `replyIfOwed`.
164
164
  */
165
- readonly loop: <T extends Record<string, Tool.Any> = Record<never, never>>(options: LoopOptions<T, Events>) => Stream.Stream<Event, AiError.AiError, LanguageModel.LanguageModel | TurnServices | ToolkitServices<T>>;
165
+ readonly run: <T extends Record<string, Tool.Any> = Record<never, never>>(options: RunOptions<T, Events>) => Stream.Stream<Event, AiError.AiError, LanguageModel.LanguageModel | TurnServices | ToolkitServices<T>>;
166
166
  /**
167
167
  * Runs one more pass when the turn would otherwise end without a reply to the user's latest
168
168
  * message. In this pass the model can only reply.
169
169
  *
170
170
  * The stream is empty when no reply is owed, or when an input arrived that the next turn will
171
- * read. `loop` runs this pass with its own prompt and tools, so the pass reuses the cached
171
+ * read. `run` runs this pass with its own prompt and tools, so the pass reuses the cached
172
172
  * prompt.
173
173
  */
174
174
  readonly replyIfOwed: (options: ReplyOptions) => Stream.Stream<Event, AiError.AiError, LanguageModel.LanguageModel | TurnServices>;
@@ -261,7 +261,7 @@ export interface Service<Events = never> {
261
261
  /**
262
262
  * Streams the chat's stored entries, then live ones, with the assistant's own events decoded.
263
263
  * A reader that already has the earlier entries passes `after` and receives only the entries
264
- * after that `seq`.
264
+ * after that `sequence`.
265
265
  *
266
266
  * For a chat client, pipe the whole log into `Timeline.changes(MyAssistant, { after })`, which
267
267
  * streams the chat's timeline, as its user sees it. Its `after` goes there, not here: a message
@@ -328,7 +328,7 @@ export interface Assistant<Name extends string = string, Specs extends Any$1 = n
328
328
  * export const MyAssistant = Assistant.make("my-assistant")
329
329
  *
330
330
  * const MyAssistantLive = MyAssistant.toLayer((turn) =>
331
- * turn.loop({ instructions: "You help a small team. Be brief." })
331
+ * turn.run({ instructions: "You help a small team. Be brief." })
332
332
  * )
333
333
  *
334
334
  * const AppLive = MyAssistantLive.pipe(
@@ -1 +1 @@
1
- {"version":3,"file":"Assistant.d.ts","names":[],"sources":["../src/Assistant.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;cAsCM;;;;;;;;YASM,eAAe,cAAc;;;;;;;iBAQxB,YAAY,UAAU,eAAe,KAAK,OAAO,sBAAsB;;WAE7E,uBAAuB;;;;;;;WAOvB,UAAU,QAAQ,QAAQ;;WAE1B;;WAEA;;;;;;WAMA,UAAU,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkDrB,YAAY,mBACtB,SAAS,cAAc,MAAM,aAC1B,cAAc,MAAM,UAAU,OAAO;;;;;;;iBAQzB,MAAM;WACZ,QAAQ;WACR;WACA;WACA,OAAO,iBAAgC;;;;;;;;iBASjC;;WAEN,uBAAuB;;;;;WAKvB;;;;;;;;;;;iBAYM,KAAK;WACX,QAAQ;;;;;;;;;WASR,UAAU,OAAO,YAAY,YAAY,OAAO,OAAO;;;;;WAKvD,eAAe,uBAAuB,aAA0B;;;;;;WAMhE,OAAO,UAAU,eAAe,KAAK,OAAO,sBACnD,SAAS,YAAY,GAAG,YACrB,OAAO,OACV,OACA,QAAQ,SACR,cAAc,gBAAgB,eAAe,gBAA8B;;;;;;;;;WAUpE,cACP,SAAS,iBACN,OAAO,OAAO,OAAqB,QAAQ,SAAS,cAAc,gBAAgB;;;;;;;;;;;;YAa7E,QAAQ,WAAW,KAAK,GAAG,mBACrC,MAAM,KAAK,YAET,OAAO,OAAO,gBAA8B,KAC5C,OAAO,OAAO,OAAO,OAAO,gBAA8B,aAAa;;;;;;;;;YAU/D,gBAAgB,KAAK,QAAQ,GAAG,eAAe,cAAc,gBAAgB,KAAK;;;;;;;;iBAe7E,WAAW;WACjB,+BAA+B;;;;;;;;iBASzB,eAAe;WACrB,oCAAoC;;;;;;;;;;;;;iBAc9B,KAAK;WACX,QAAQ;;;;;WAKR,SAAS,OAAO,OAAO,cAAc,MAAM;;;;;;EAMpD,UAAU,QAAQ,cAAc,iBAAgC,UAAU,OAAO;;;;;;;;;;iBAWlE,QAAQ;;;;;;;;;;;;EAavB,OAAO,QAAQ,QAAQ,OAAO,QAAuB,SAAS,OAAO,aAAa;;;;;;;;;;;WAWzE,YACP,QAAQ,QACR;aAAqB;QAClB,OAAO,OAAO,MAAM;;;;;;;;;;;;;;WAchB,SAAS,QAAQ,WAAW,OAAO;;;;;;;;;;;;iBAa7B,UACf,8BACA,cAAc,eACd,wBACQ,QAAQ,QAAQ,WAAW,OAAO,QAAQ;YACxC,gBAAgB;WACjB,MAAM;WACN,QAAQ;;WAER,aAAa,cAAc;;WAE3B,QAAQ,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmCtB,UAAU,WAAW,YAAY,WAAW,YACnD,QAAQ,QAAQ,GAAG,IAAI,UAAU,OAAO,OAAO,QAAQ,GAAG,IAAI,SAAS,GAAG,OACxE,cAAyB,QAAQ,gBAAgB,IAAI,KAAK,KAAK,eAAe,YAC7E,MAAM,MACT,WAAW,cAET,kBACA,cAAc,gBACd,UAAqB,SACrB,QAAQ,IAAI,MAAM,QAAQ,cAAc,eAAe,SACvD,aAAwB,IAAI;;;;;;;;;;;;;;;;;;;;;;;WAwBvB,aAAa,MAAM,MAAM,WAAW,cAAc;;;;;WAKlD,MAAM,QAAQ,QAAQ,eAAe,OAAO,KAAK;;WAEjD,OAAO,OAAO,MAAM,MAAM,SAAS,OAAO;;;;;;;;YASzC,MAAM;;;;;;;qBAQL,cAAW,eAAiB,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAqDjC,aACL,2BACA,cAAc,cAAc,4BAC5B,eAAe,cAAc,+BAA4C,MAEzE,MAAI;;WAGC,SAAS;;;;;WAKT,cAAc;;;;;;;WAOd,SAAS;MAEnB,UAAU,MAAM,eAAe"}
1
+ {"version":3,"file":"Assistant.d.ts","names":[],"sources":["../src/Assistant.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;cAsCM;;;;;;;;YASM,eAAe,cAAc;;;;;;;iBAQxB,WAAW,UAAU,eAAe,KAAK,OAAO,sBAAsB;;WAE5E,uBAAuB;;;;;;;WAOvB,UAAU,QAAQ,QAAQ;;WAE1B;;WAEA;;;;;;WAMA,UAAU,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAkDrB,YAAY,mBACtB,SAAS,cAAc,MAAM,aAC1B,cAAc,MAAM,UAAU,OAAO;;;;;;;iBAQzB,MAAM;WACZ,QAAQ;WACR;WACA;WACA,OAAO,iBAAgC;;;;;;;;iBASjC;;WAEN,uBAAuB;;;;;WAKvB;;;;;;;;;;;iBAYM,KAAK;WACX,QAAQ;;;;;;;;;WASR,UAAU,OAAO,YAAY,YAAY,OAAO,OAAO;;;;;WAKvD,eAAe,uBAAuB,aAA0B;;;;;;WAMhE,MAAM,UAAU,eAAe,KAAK,OAAO,sBAClD,SAAS,WAAW,GAAG,YACpB,OAAO,OACV,OACA,QAAQ,SACR,cAAc,gBAAgB,eAAe,gBAA8B;;;;;;;;;WAUpE,cACP,SAAS,iBACN,OAAO,OAAO,OAAqB,QAAQ,SAAS,cAAc,gBAAgB;;;;;;;;;;;;YAa7E,QAAQ,WAAW,KAAK,GAAG,mBACrC,MAAM,KAAK,YAET,OAAO,OAAO,gBAA8B,KAC5C,OAAO,OAAO,OAAO,OAAO,gBAA8B,aAAa;;;;;;;;;YAU/D,gBAAgB,KAAK,QAAQ,GAAG,eAAe,cAAc,gBAAgB,KAAK;;;;;;;;iBAe7E,WAAW;WACjB,+BAA+B;;;;;;;;iBASzB,eAAe;WACrB,oCAAoC;;;;;;;;;;;;;iBAc9B,KAAK;WACX,QAAQ;;;;;WAKR,SAAS,OAAO,OAAO,cAAc,MAAM;;;;;;EAMpD,UAAU,QAAQ,cAAc,iBAAgC,UAAU,OAAO;;;;;;;;;;iBAWlE,QAAQ;;;;;;;;;;;;EAavB,OAAO,QAAQ,QAAQ,OAAO,QAAuB,SAAS,OAAO,aAAa;;;;;;;;;;;WAWzE,YACP,QAAQ,QACR;aAAqB;QAClB,OAAO,OAAO,MAAM;;;;;;;;;;;;;;WAchB,SAAS,QAAQ,WAAW,OAAO;;;;;;;;;;;;iBAa7B,UACf,8BACA,cAAc,eACd,wBACQ,QAAQ,QAAQ,WAAW,OAAO,QAAQ;YACxC,gBAAgB;WACjB,MAAM;WACN,QAAQ;;WAER,aAAa,cAAc;;WAE3B,QAAQ,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmCtB,UAAU,WAAW,YAAY,WAAW,YACnD,QAAQ,QAAQ,GAAG,IAAI,UAAU,OAAO,OAAO,QAAQ,GAAG,IAAI,SAAS,GAAG,OACxE,cAAyB,QAAQ,gBAAgB,IAAI,KAAK,KAAK,eAAe,YAC7E,MAAM,MACT,WAAW,cAET,kBACA,cAAc,gBACd,UAAqB,SACrB,QAAQ,IAAI,MAAM,QAAQ,cAAc,eAAe,SACvD,aAAwB,IAAI;;;;;;;;;;;;;;;;;;;;;;;WAwBvB,aAAa,MAAM,MAAM,WAAW,cAAc;;;;;WAKlD,MAAM,QAAQ,QAAQ,eAAe,OAAO,KAAK;;WAEjD,OAAO,OAAO,MAAM,MAAM,SAAS,OAAO;;;;;;;;YASzC,MAAM;;;;;;;qBAQL,cAAW,eAAiB,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAqDjC,aACL,2BACA,cAAc,cAAc,4BAC5B,eAAe,cAAc,+BAA4C,MAEzE,MAAI;;WAGC,SAAS;;;;;WAKT,cAAc;;;;;;;WAOd,SAAS;MAEnB,UAAU,MAAM,eAAe"}
package/dist/Assistant.js CHANGED
@@ -29,7 +29,7 @@ const isAssistant = (u) => Predicate.hasProperty(u, TypeId);
29
29
  function entrySchema(events) {
30
30
  return Schema.toCodecJson(Schema.Struct({
31
31
  chatId: ChatId,
32
- seq: Schema.Int,
32
+ sequence: Schema.Int,
33
33
  at: Schema.Finite,
34
34
  event: Schema.Union([AssistantEvent, ...events])
35
35
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"Assistant.js","names":["AssistantEvent.AssistantEvent","AppEvents.make","AssistantEvent.isInput","AppEvents.encode","Chats.Chats","AppEvents.decodeEntry","Turns.register"],"sources":["../src/Assistant.ts"],"sourcesContent":["/**\n * This module provides `Assistant.make`, which defines an assistant that works in a chat the way a\n * colleague does: the user messages it, it works in the background with the app's tools, and it\n * messages the user back.\n *\n * A definition is what the rest of an app imports. Yielding it (`yield* MyAssistant`) returns the\n * assistant's {@link Service}, which submits inputs to chats and streams them back. Its `toLayer`\n * registers the handler that runs each {@link Turn} with the `AssistantEngine`, and its\n * `layerClient` provides the service without a handler. Code that runs inside a chat reads and\n * extends that chat through the definition's {@link Chat} key.\n *\n * @since 0.1.0\n */\nimport type * as AiError from \"effect/ai/AiError\"\nimport type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Filter from \"effect/Filter\"\nimport * as Layer from \"effect/Layer\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport { AssistantEngine } from \"./AssistantEngine.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport type * as AssistantLoop from \"./AssistantLoop.ts\"\nimport type * as BlockKit from \"./BlockKit.ts\"\nimport { ChatId } from \"./ChatId.ts\"\nimport * as Chats from \"./Chats.ts\"\nimport type { CurrentChat } from \"./CurrentChat.ts\"\nimport type * as Instructions from \"./Instructions.ts\"\nimport * as AppEvents from \"./internal/events.ts\"\nimport * as Turns from \"./internal/turns.ts\"\nimport type * as Specialist from \"./Specialist.ts\"\n\nconst TypeId = \"~effect-assistant/Assistant\" as const\n\n/**\n * Represents the services that a turn provides to its tools, its loop, and its specialists. These\n * are the turn's chat (`CurrentChat`) and the chat's specialist runner (`Specialist.Runner`).\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type TurnServices = CurrentChat | Specialist.Runner\n\n/**\n * Represents the options accepted by `turn.loop`.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface LoopOptions<T extends Record<string, Tool.Any> = Record<never, never>, Events = never> {\n /** The assistant's role and rules. The loop places `Instructions.protocol` before them. */\n readonly instructions: string | Instructions.Resolved\n /**\n * Tools the model can use in addition to the assistant's own. This is an `effect/ai` toolkit,\n * and its handlers come from the layer. Combine several toolkits with `Toolkit.merge`, for\n * example `Memory.Tools`, a `Skill.toolkit(...)`, or a `ToolDiscovery.make(...)` toolkit together\n * with your own.\n */\n readonly toolkit?: Toolkit.Toolkit<T> | undefined\n /** The maximum number of model calls per turn. Defaults to 16. */\n readonly maxSteps?: number | undefined\n /** The message the reply pass gives the model when the turn would end without a reply. */\n readonly replyNudge?: string | undefined\n /**\n * Selects which of the chat's entries the model reads, for example only those since the last\n * summary, and maps the app's own events it should read into prompt messages. Defaults to all\n * entries, without the app's events.\n */\n readonly history?: HistoryView<Events> | undefined\n}\n\n/**\n * Represents a function that selects which of a chat's entries the model reads, and what else it\n * reads with them.\n *\n * **Details**\n *\n * The function receives the chat's entries, the app's own events among them, and returns, in\n * order, a mix of entries and `Prompt.Message`s. The model reads the returned entries the same way\n * it reads the full history, and reads each prompt message unchanged, where the view put it. The\n * model reads none of the app's events by itself: map those it should read to a message, and an\n * app event's entry left in the result is dropped.\n *\n * A message stands right after the entry before it: one that came after the model's last read, or\n * between a tool call and its result, moves after them, as an input does.\n *\n * **Example** (Telling the model about an order the user placed)\n *\n * ```ts\n * import { Assistant, InputId } from \"effect-assistant\"\n * import * as Prompt from \"effect/ai/Prompt\"\n * import * as Schema from \"effect/Schema\"\n *\n * class OrderPlaced extends Schema.TaggedClass<OrderPlaced>()(\"OrderPlaced\", {\n * id: InputId.InputId,\n * reference: Schema.String\n * }) {}\n *\n * const MyAssistant = Assistant.make(\"my-assistant\", { events: [OrderPlaced] })\n *\n * const withOrders: Assistant.HistoryView<OrderPlaced> = (entries) =>\n * entries.map((entry) =>\n * entry.event instanceof OrderPlaced\n * ? Prompt.userMessage({\n * content: [Prompt.textPart({ text: `The user placed order ${entry.event.reference}.` })]\n * })\n * : entry\n * )\n *\n * // An `OrderPlaced` that the API submits wakes a turn, which reads it as this note.\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * turn.loop({ instructions: \"Be brief.\", history: withOrders })\n * )\n * ```\n *\n * @category models\n * @since 0.1.0\n */\nexport type HistoryView<Events = never> = (\n entries: ReadonlyArray<Entry<Events>>\n) => ReadonlyArray<Entry<Events> | Prompt.Message>\n\n/**\n * Represents an entry in a chat's log, with the assistant's own events decoded to their types.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Entry<Events = never> {\n readonly chatId: ChatId\n readonly seq: number\n readonly at: number\n readonly event: AssistantEvent.AssistantEvent | Events\n}\n\n/**\n * Represents the options accepted by `turn.replyIfOwed`.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface ReplyOptions {\n /** The assistant's instructions, as passed to `turn.loop`. `Instructions.protocol` is added. */\n readonly instructions: string | Instructions.Resolved\n /**\n * The message given to the model after the history. Defaults to a message saying that it\n * hasn't replied yet and should reply now.\n */\n readonly nudge?: string | undefined\n}\n\n/**\n * Represents one turn, as the assistant's handler receives it.\n *\n * `Events` is the type of the app's own events that the assistant registered with its `events`\n * option.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Turn<Events = never> {\n readonly chatId: ChatId\n /**\n * Returns the model's history, built from the chat's entries (all of them, or those a view\n * keeps), together with how far into the chat it reaches.\n *\n * Read it before every model call, as the loop does. Each step records how far its model call\n * read, and the turn ends once it has handled what the model saw. An input that arrived after\n * that is handled by the next turn.\n */\n readonly history: (view?: HistoryView<Events>) => Effect.Effect<AssistantLoop.ChatHistory>\n /**\n * Returns the full system prompt for the given instructions. It contains\n * `Instructions.protocol`, the instructions, and the specialist runs that are working now.\n */\n readonly instructions: (instructions: string | Instructions.Resolved) => Instructions.Resolved\n /**\n * Runs the agent loop over the chat. The loop uses `Instructions.protocol`, the assistant's own\n * tools, and the instructions and tools in `options`. Every step is a tool call, and\n * the loop ends with `replyIfOwed`.\n */\n readonly loop: <T extends Record<string, Tool.Any> = Record<never, never>>(\n options: LoopOptions<T, Events>\n ) => Stream.Stream<\n AssistantLoop.Event,\n AiError.AiError,\n LanguageModel.LanguageModel | TurnServices | AssistantLoop.ToolkitServices<T>\n >\n /**\n * Runs one more pass when the turn would otherwise end without a reply to the user's latest\n * message. In this pass the model can only reply.\n *\n * The stream is empty when no reply is owed, or when an input arrived that the next turn will\n * read. `loop` runs this pass with its own prompt and tools, so the pass reuses the cached\n * prompt.\n */\n readonly replyIfOwed: (\n options: ReplyOptions\n ) => Stream.Stream<AssistantLoop.Event, AiError.AiError, LanguageModel.LanguageModel | TurnServices>\n}\n\n/**\n * Represents an assistant's handler, which runs a turn and returns it as a stream of the loop's\n * events.\n *\n * The handler may return the stream directly or from an Effect. `R` is the services the stream\n * requires, and `RE` is the services the Effect requires.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Handler<R = never, RE = R, Events = never> = (\n turn: Turn<Events>\n) =>\n | Stream.Stream<AssistantLoop.Event, unknown, R>\n | Effect.Effect<Stream.Stream<AssistantLoop.Event, unknown, R>, unknown, RE>\n\n/**\n * Computes the services an assistant's handler needs beyond those its turns provide (the turn\n * services and `LanguageModel`) and its tools' handlers (which its layer requires). These are the\n * services that its build Effect must require.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type HandlerServices<R> = Exclude<R, TurnServices | LanguageModel.LanguageModel | Tool.Handler<string>>\n\nconst toStream = <R, RE>(\n result:\n | Stream.Stream<AssistantLoop.Event, unknown, R>\n | Effect.Effect<Stream.Stream<AssistantLoop.Event, unknown, R>, unknown, RE>\n): Stream.Stream<AssistantLoop.Event, unknown, R | RE> => (Effect.isEffect(result) ? Stream.unwrap(result) : result)\n\n/**\n * Identifies an assistant's service in a context. Each assistant has its own identifier, keyed by\n * its name.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Identifier<Name extends string> {\n readonly \"~effect-assistant/Assistant\": Name\n}\n\n/**\n * Identifies, in a context, the chat that an assistant's turns run in.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface ChatIdentifier<Name extends string> {\n readonly \"~effect-assistant/Assistant/Chat\": Name\n}\n\n/**\n * Represents the chat that a turn of this assistant runs in, typed with the assistant's events.\n *\n * Use `yield* MyAssistant.Chat` in the handler, in the handler's build, or in a tool that its\n * turns run. Code that doesn't know the assistant uses `CurrentChat` instead.\n *\n * @see {@link CurrentChat} for the same chat without the assistant's event types\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Chat<Events = never> {\n readonly chatId: ChatId\n /**\n * The chat's entries so far, in order. They include the library's events and the assistant's\n * own events, typed.\n */\n readonly entries: Effect.Effect<ReadonlyArray<Entry<Events>>>\n /**\n * Appends events after the chat's last entry and sends them to the chat's subscribers. The\n * events can be the library's or the assistant's own, such as a summary. None of them wakes a\n * turn: only what comes in through the assistant's `submit` does.\n */\n append(...events: ReadonlyArray<AssistantEvent.AssistantEvent | Events>): Effect.Effect<void>\n}\n\n/**\n * Represents the service that `yield* MyAssistant` returns. It gives code outside a chat, such as\n * a server or a workflow, access to the assistant's chats. Code that runs inside a chat uses\n * `CurrentChat` instead.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service<Events = never> {\n /**\n * Stores an input or one of the assistant's own events, then returns. Examples are a user's\n * message or block answer, a workflow's progress, or the order the user placed. Each one is an\n * input: storing it wakes a turn, which the assistant runs in the background. The handler decides\n * what the turn does with it, and an app event the model should read needs a history view that\n * maps it (see {@link HistoryView}). To store an app event without waking a turn, append it from\n * within the chat instead (`yield* MyAssistant.Chat`).\n *\n * Fails with `GetChatError` when the app's `Chats` service reports that the chat can't be used,\n * because the app doesn't know the chat or has deleted it.\n */\n // Method syntax: a service for these events is one for any (`Service<any>`), for helpers over assistants.\n submit(chatId: ChatId, input: AssistantEvent.Input | Events): Effect.Effect<void, Chats.GetChatError>\n /**\n * Streams the chat's stored entries, then live ones, with the assistant's own events decoded.\n * A reader that already has the earlier entries passes `after` and receives only the entries\n * after that `seq`.\n *\n * For a chat client, pipe the whole log into `Timeline.changes(MyAssistant, { after })`, which\n * streams the chat's timeline, as its user sees it. Its `after` goes there, not here: a message\n * on the timeline depends on earlier entries. The timeline shows only the app events named in its\n * `show`, none by default (see `Timeline.changes`).\n */\n readonly subscribe: (\n chatId: ChatId,\n options?: { readonly after?: number | undefined }\n ) => Stream.Stream<Entry<Events>>\n /**\n * Deletes a chat by storing a `ChatDeleted` event and stopping everything that works in it.\n *\n * **Details**\n *\n * A turn or specialist run working in the chat stops, and subscribers' streams end with the\n * `ChatDeleted` event. After that the chat stores nothing more: a later input, a run's report,\n * or a workflow's event for it is dropped without an error. The log is kept, and removing it is\n * up to the app. Deleting a chat a second time does nothing.\n *\n * When the app's `Chats` service reports a chat as deleted, the assistant deletes it in the same\n * way the next time it checks. It checks before an input is stored and before each model call.\n */\n readonly delete: (chatId: ChatId) => Effect.Effect<void>\n}\n\n/**\n * Represents an assistant's definition.\n *\n * The definition holds what callers and the UI need to know about the assistant, builds its\n * handler's layer, and is the key of its service (`yield* MyAssistant`). `Events` is the type of\n * the app's own events that the assistant registered.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Assistant<\n Name extends string = string,\n Specs extends Specialist.Any = never,\n Events = never\n> extends Context.Service<Identifier<Name>, Service<Events>> {\n readonly [TypeId]: typeof TypeId\n readonly name: Name\n readonly blocks: BlockKit.BlockKit | undefined\n /** The specialists the assistant can hand work to. */\n readonly specialists: ReadonlyArray<Specs>\n /** The schemas of the app's own events the assistant stores. */\n readonly events: ReadonlyArray<AssistantEvent.AppEventSchema>\n /**\n * Creates a layer that registers the assistant's handler with the engine and provides the\n * assistant's service.\n *\n * **Details**\n *\n * `build` is the handler, or an Effect that returns it. The handler may use the turn services,\n * `LanguageModel`, its tools' handlers (which the layer then requires), and services that its\n * build Effect required (see `Specialist.CheckServices`). The layer requires the\n * `AssistantEngine`, the `LanguageModel`, and each specialist's handler\n * (`Research.toLayer(...)`).\n *\n * **Example** (Providing an assistant's handler)\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 * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * turn.loop({ instructions: \"You help a small team. Be brief.\" })\n * )\n *\n * const AppLive = MyAssistantLive.pipe(\n * Layer.provide(ModelLive),\n * Layer.provideMerge(AssistantEngine.layerMemory),\n * Layer.provideMerge(ChatLog.layerMemory)\n * )\n * ```\n */\n readonly toLayer: <R = never, RE = never, E = never, RX = never>(\n build: (Handler<R, RE, Events> | Effect.Effect<Handler<R, RE, Events>, E, RX>) &\n Specialist.CheckServices<Exclude<HandlerServices<R | RE>, RX | ChatIdentifier<Name>>>\n ) => Layer.Layer<\n Identifier<Name>,\n never,\n | AssistantEngine\n | LanguageModel.LanguageModel\n | Specialist.HandlerOf<Specs>\n | Exclude<RX, Scope.Scope | CurrentChat | ChatIdentifier<Name>>\n | Specialist.ToolHandlers<R | RE>\n >\n /**\n * A layer that provides the assistant's service without its handler.\n *\n * **When to use**\n *\n * Use it in a process that submits to chats that another process runs, such as a web server in\n * front of a cluster.\n *\n * **Example** (Serving clients in front of a cluster)\n *\n * ```ts\n * import { Assistant, AssistantEngine } from \"effect-assistant\"\n * import * as Layer from \"effect/Layer\"\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * // Requires `Sharding`. The cluster's runners run the chats, with `AssistantEngine.layerCluster`.\n * const MyAssistantClient = MyAssistant.layerClient.pipe(\n * Layer.provide(AssistantEngine.layerClusterClient)\n * )\n * ```\n */\n readonly layerClient: Layer.Layer<Identifier<Name>, never, AssistantEngine>\n /**\n * The key of the chat that the assistant's turns run in, typed with its events. See\n * {@link Chat}.\n */\n readonly Chat: Context.Service<ChatIdentifier<Name>, Chat<Events>>\n /** A schema that encodes the chat's entries to JSON, including the assistant's own events. */\n readonly Entry: Schema.Codec<Entry<Events>, Schema.Json>\n}\n\n/**\n * Represents any assistant, for use in generic constraints.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type Any = Assistant<any, any, any>\n\n/**\n * Returns `true` when the value is an assistant's definition, created with {@link make}.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isAssistant = (u: unknown): u is Any => Predicate.hasProperty(u, TypeId)\n\nfunction entrySchema<E>(events: ReadonlyArray<AssistantEvent.AppEventSchema>): Schema.Codec<Entry<E>, Schema.Json>\n// The overload states the decoded type: the library's events, or one of these schemas' types.\nfunction entrySchema(events: ReadonlyArray<AssistantEvent.AppEventSchema>): Schema.Top {\n return Schema.toCodecJson(\n Schema.Struct({\n chatId: ChatId,\n seq: Schema.Int,\n at: Schema.Finite,\n event: Schema.Union([AssistantEvent.AssistantEvent, ...events])\n })\n )\n}\n\n/**\n * Creates an assistant definition from its name, its UI blocks, its specialists, and the schemas of\n * the app's own events it stores.\n *\n * **Details**\n *\n * The definition holds no code that runs turns. Provide a handler with its `toLayer`, then yield\n * the definition to get the assistant's {@link Service}.\n *\n * The definition only says which of the app's events a chat stores. What happens with one is up\n * to the places that use it. One that comes in through the service's `submit`, from outside a\n * turn, is an input and wakes a turn, like a user's message; one a turn appends to its chat never\n * does. The handler decides what a turn does, a history view says how the model reads an event\n * ({@link HistoryView}), and the chat's timeline says which its user sees\n * (`Timeline.changes`).\n *\n * **Example** (Defining an assistant and submitting a message)\n *\n * ```ts\n * import { Assistant, AssistantEvent, ChatId } from \"effect-assistant\"\n * import * as Effect from \"effect/Effect\"\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const program = Effect.gen(function* () {\n * const assistant = yield* MyAssistant\n * const chatId = yield* ChatId.make()\n *\n * yield* assistant.submit(chatId, AssistantEvent.UserMessage.make({ text: \"Weather in Oslo?\" }))\n * })\n * ```\n *\n * @see {@link Assistant} for the definition's members\n * @see {@link Service} for what `yield* MyAssistant` returns\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <\n const Name extends string,\n const Specs extends ReadonlyArray<Specialist.Any> = readonly [],\n const Events extends ReadonlyArray<AssistantEvent.AppEventSchema> = readonly []\n>(\n name: Name,\n options?: {\n /** UI blocks the assistant can add to its messages. */\n readonly blocks?: BlockKit.BlockKit | undefined\n /**\n * The specialists the assistant can hand work to. Each specialist's `toLayer` must be\n * provided to the assistant's layer.\n */\n readonly specialists?: Specs | undefined\n /**\n * The schemas of the app's own events, each a tagged struct or class. `submit` and the chat's\n * `append` store them, in their JSON form, and the chat's `entries` and `subscribe` read them\n * back, decoded. Each needs a tag of its own that isn't one of the library's: `make` throws\n * otherwise.\n */\n readonly events?: Events | undefined\n }\n): Assistant<Name, Specs[number], Events[number][\"Type\"]> => {\n const events = options?.events ?? []\n\n const definition: Turns.Definition = {\n name,\n blocks: options?.blocks,\n specialists: options?.specialists ?? [],\n app: AppEvents.make(events)\n }\n\n type OwnEvents = Events[number][\"Type\"]\n\n const Key = Context.Service<Identifier<Name>, Service<OwnEvents>>(`effect-assistant/Assistant/${name}`)\n\n // The app's own events go to the engine encoded, as they're stored; the library's go unchanged.\n const stored = (\n input: AssistantEvent.Input | OwnEvents\n ): Effect.Effect<AssistantEvent.Input | AssistantEvent.StoredAppEvent> =>\n AssistantEvent.isInput(input) ? Effect.succeed(input) : AppEvents.encode(definition.app, input)\n\n const ChatKey = Context.Service<ChatIdentifier<Name>, Chat<OwnEvents>>(`effect-assistant/Assistant/${name}/Chat`)\n\n // On the engine and the app's `Chats` the layer was given, so every caller sees the same ones.\n const makeService = Effect.gen(function* () {\n const engine = yield* AssistantEngine\n const chats = yield* Chats.Chats\n\n return Key.of({\n submit: Effect.fn(\"Assistant.submit\")(function* (chatId: ChatId, input: AssistantEvent.Input | OwnEvents) {\n // Checked here, where the caller can handle it: the chat checks again when it stores the input.\n yield* chats.get(chatId)\n yield* engine.submit(name, chatId, yield* stored(input))\n }),\n subscribe: (chatId, options) =>\n engine\n .subscribe(name, chatId, options?.after ?? 0)\n .pipe(\n Stream.filterMap(\n Filter.fromPredicateOption((entry: AssistantEvent.Entry) => AppEvents.decodeEntry(definition.app, entry))\n )\n ),\n delete: (chatId) => engine.delete(name, chatId)\n })\n })\n\n // The signature checks what the handler uses (`CheckServices`); the layer's services, which the build\n // required, are provided to it and to every turn's stream, so what's left is the turn services.\n function toLayer<R = never, RE = never, E = never, RX = never>(\n build: (Handler<R, RE, OwnEvents> | Effect.Effect<Handler<R, RE, OwnEvents>, E, RX>) &\n Specialist.CheckServices<Exclude<HandlerServices<R | RE>, RX | ChatIdentifier<Name>>>\n ): Layer.Layer<\n Identifier<Name>,\n never,\n | AssistantEngine\n | LanguageModel.LanguageModel\n | Specialist.HandlerOf<Specs[number]>\n | Exclude<RX, Scope.Scope | CurrentChat | ChatIdentifier<Name>>\n | Specialist.ToolHandlers<R | RE>\n >\n function toLayer(\n build: Handler<any, any, any> | Effect.Effect<Handler<any, any, any>, unknown, any>\n ): Layer.Layer<Identifier<Name>, never, any> {\n return Layer.effect(\n Key,\n Effect.gen(function* () {\n const context = yield* Effect.context()\n\n const registration = yield* Turns.register(definition, {\n // The build runs per chat activation, in the chat; here it only keeps the layer's services for it.\n open: (typed) =>\n Effect.gen(function* () {\n // The chat's entries are already decoded with this assistant's events.\n const chat = ChatKey.of({ chatId: typed.chatId, entries: typed.entries, append: typed.append })\n const handler = Effect.isEffect(build) ? yield* Effect.provideService(build, ChatKey, chat) : build\n\n return (turn: Turn<any>) =>\n Stream.provideContext(toStream(handler(turn)), context).pipe(Stream.provideService(ChatKey, chat))\n }).pipe(Effect.provideContext(context), Effect.orDie)\n })\n\n yield* (yield* AssistantEngine).register(registration)\n\n return yield* makeService\n })\n )\n }\n\n // The key is a function, whose own `name` is read-only: the assistant's name replaces it.\n const named = Object.defineProperty(Key, \"name\", { value: name, writable: true })\n\n return Object.assign(named, {\n [TypeId]: TypeId,\n name,\n blocks: options?.blocks,\n specialists: options?.specialists ?? [],\n events,\n toLayer,\n layerClient: Layer.effect(Key, makeService),\n Chat: ChatKey,\n Entry: entrySchema<OwnEvents>(events)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsCA,MAAM,SAAS;AAmMf,MAAM,YACJ,WAGyD,OAAO,SAAS,MAAM,IAAI,OAAO,OAAO,MAAM,IAAI;;;;;;;AAuN7G,MAAa,eAAe,MAAyB,UAAU,YAAY,GAAG,MAAM;AAIpF,SAAS,YAAY,QAAkE;CACrF,OAAO,OAAO,YACZ,OAAO,OAAO;EACZ,QAAQ;EACR,KAAK,OAAO;EACZ,IAAI,OAAO;EACX,OAAO,OAAO,MAAM,CAACA,gBAA+B,GAAG,MAAM,CAAC;CAChE,CAAC,CACH;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,QAKX,MACA,YAgB2D;CAC3D,MAAM,SAAS,SAAS,UAAU,CAAC;CAEnC,MAAM,aAA+B;EACnC;EACA,QAAQ,SAAS;EACjB,aAAa,SAAS,eAAe,CAAC;EACtC,KAAKC,OAAe,MAAM;CAC5B;CAIA,MAAM,MAAM,QAAQ,QAA8C,8BAA8B,MAAM;CAGtG,MAAM,UACJ,UAEAC,QAAuB,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAIC,OAAiB,WAAW,KAAK,KAAK;CAEhG,MAAM,UAAU,QAAQ,QAA+C,8BAA8B,KAAK,MAAM;CAGhH,MAAM,cAAc,OAAO,IAAI,aAAa;EAC1C,MAAM,SAAS,OAAO;EACtB,MAAM,QAAQ,OAAOC;EAErB,OAAO,IAAI,GAAG;GACZ,QAAQ,OAAO,GAAG,kBAAkB,CAAC,CAAC,WAAW,QAAgB,OAAyC;IAExG,OAAO,MAAM,IAAI,MAAM;IACvB,OAAO,OAAO,OAAO,MAAM,QAAQ,OAAO,OAAO,KAAK,CAAC;GACzD,CAAC;GACD,YAAY,QAAQ,YAClB,OACG,UAAU,MAAM,QAAQ,SAAS,SAAS,CAAC,CAAC,CAC5C,KACC,OAAO,UACL,OAAO,qBAAqB,UAAgCC,YAAsB,WAAW,KAAK,KAAK,CAAC,CAC1G,CACF;GACJ,SAAS,WAAW,OAAO,OAAO,MAAM,MAAM;EAChD,CAAC;CACH,CAAC;CAgBD,SAAS,QACP,OAC2C;EAC3C,OAAO,MAAM,OACX,KACA,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,OAAO,QAAQ;GAEtC,MAAM,eAAe,OAAOC,SAAe,YAAY,EAErD,OAAO,UACL,OAAO,IAAI,aAAa;IAEtB,MAAM,OAAO,QAAQ,GAAG;KAAE,QAAQ,MAAM;KAAQ,SAAS,MAAM;KAAS,QAAQ,MAAM;IAAO,CAAC;IAC9F,MAAM,UAAU,OAAO,SAAS,KAAK,IAAI,OAAO,OAAO,eAAe,OAAO,SAAS,IAAI,IAAI;IAE9F,QAAQ,SACN,OAAO,eAAe,SAAS,QAAQ,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,OAAO,eAAe,SAAS,IAAI,CAAC;GACrG,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,OAAO,GAAG,OAAO,KAAK,EACxD,CAAC;GAED,QAAQ,OAAO,gBAAA,CAAiB,SAAS,YAAY;GAErD,OAAO,OAAO;EAChB,CAAC,CACH;CACF;CAGA,MAAM,QAAQ,OAAO,eAAe,KAAK,QAAQ;EAAE,OAAO;EAAM,UAAU;CAAK,CAAC;CAEhF,OAAO,OAAO,OAAO,OAAO;GACzB,SAAS;EACV;EACA,QAAQ,SAAS;EACjB,aAAa,SAAS,eAAe,CAAC;EACtC;EACA;EACA,aAAa,MAAM,OAAO,KAAK,WAAW;EAC1C,MAAM;EACN,OAAO,YAAuB,MAAM;CACtC,CAAC;AACH"}
1
+ {"version":3,"file":"Assistant.js","names":["AssistantEvent.AssistantEvent","AppEvents.make","AssistantEvent.isInput","AppEvents.encode","Chats.Chats","AppEvents.decodeEntry","Turns.register"],"sources":["../src/Assistant.ts"],"sourcesContent":["/**\n * This module provides `Assistant.make`, which defines an assistant that works in a chat the way a\n * colleague does: the user messages it, it works in the background with the app's tools, and it\n * messages the user back.\n *\n * A definition is what the rest of an app imports. Yielding it (`yield* MyAssistant`) returns the\n * assistant's {@link Service}, which submits inputs to chats and streams them back. Its `toLayer`\n * registers the handler that runs each {@link Turn} with the `AssistantEngine`, and its\n * `layerClient` provides the service without a handler. Code that runs inside a chat reads and\n * extends that chat through the definition's {@link Chat} key.\n *\n * @since 0.1.0\n */\nimport type * as AiError from \"effect/ai/AiError\"\nimport type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Filter from \"effect/Filter\"\nimport * as Layer from \"effect/Layer\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport { AssistantEngine } from \"./AssistantEngine.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport type * as AssistantLoop from \"./AssistantLoop.ts\"\nimport type * as BlockKit from \"./BlockKit.ts\"\nimport { ChatId } from \"./ChatId.ts\"\nimport * as Chats from \"./Chats.ts\"\nimport type { CurrentChat } from \"./CurrentChat.ts\"\nimport type * as Instructions from \"./Instructions.ts\"\nimport * as AppEvents from \"./internal/events.ts\"\nimport * as Turns from \"./internal/turns.ts\"\nimport type * as Specialist from \"./Specialist.ts\"\n\nconst TypeId = \"~effect-assistant/Assistant\" as const\n\n/**\n * Represents the services that a turn provides to its tools, its loop, and its specialists. These\n * are the turn's chat (`CurrentChat`) and the chat's specialist runner (`Specialist.Runner`).\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type TurnServices = CurrentChat | Specialist.Runner\n\n/**\n * Represents the options accepted by `turn.run`.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RunOptions<T extends Record<string, Tool.Any> = Record<never, never>, Events = never> {\n /** The assistant's role and rules. The loop places `Instructions.protocol` before them. */\n readonly instructions: string | Instructions.Resolved\n /**\n * Tools the model can use in addition to the assistant's own. This is an `effect/ai` toolkit,\n * and its handlers come from the layer. Combine several toolkits with `Toolkit.merge`, for\n * example `Memory.Tools`, a `Skill.toolkit(...)`, or a `ToolDiscovery.make(...)` toolkit together\n * with your own.\n */\n readonly toolkit?: Toolkit.Toolkit<T> | undefined\n /** The maximum number of model calls per turn. Defaults to 16. */\n readonly maxSteps?: number | undefined\n /** The message the reply pass gives the model when the turn would end without a reply. */\n readonly replyNudge?: string | undefined\n /**\n * Selects which of the chat's entries the model reads, for example only those since the last\n * summary, and maps the app's own events it should read into prompt messages. Defaults to all\n * entries, without the app's events.\n */\n readonly history?: HistoryView<Events> | undefined\n}\n\n/**\n * Represents a function that selects which of a chat's entries the model reads, and what else it\n * reads with them.\n *\n * **Details**\n *\n * The function receives the chat's entries, the app's own events among them, and returns, in\n * order, a mix of entries and `Prompt.Message`s. The model reads the returned entries the same way\n * it reads the full history, and reads each prompt message unchanged, where the view put it. The\n * model reads none of the app's events by itself: map those it should read to a message, and an\n * app event's entry left in the result is dropped.\n *\n * A message stands right after the entry before it: one that came after the model's last read, or\n * between a tool call and its result, moves after them, as an input does.\n *\n * **Example** (Telling the model about an order the user placed)\n *\n * ```ts\n * import { Assistant, InputId } from \"effect-assistant\"\n * import * as Prompt from \"effect/ai/Prompt\"\n * import * as Schema from \"effect/Schema\"\n *\n * class OrderPlaced extends Schema.TaggedClass<OrderPlaced>()(\"OrderPlaced\", {\n * id: InputId.InputId,\n * reference: Schema.String\n * }) {}\n *\n * const MyAssistant = Assistant.make(\"my-assistant\", { events: [OrderPlaced] })\n *\n * const withOrders: Assistant.HistoryView<OrderPlaced> = (entries) =>\n * entries.map((entry) =>\n * entry.event instanceof OrderPlaced\n * ? Prompt.userMessage({\n * content: [Prompt.textPart({ text: `The user placed order ${entry.event.reference}.` })]\n * })\n * : entry\n * )\n *\n * // An `OrderPlaced` that the API submits wakes a turn, which reads it as this note.\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * turn.run({ instructions: \"Be brief.\", history: withOrders })\n * )\n * ```\n *\n * @category models\n * @since 0.1.0\n */\nexport type HistoryView<Events = never> = (\n entries: ReadonlyArray<Entry<Events>>\n) => ReadonlyArray<Entry<Events> | Prompt.Message>\n\n/**\n * Represents an entry in a chat's log, with the assistant's own events decoded to their types.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Entry<Events = never> {\n readonly chatId: ChatId\n readonly sequence: number\n readonly at: number\n readonly event: AssistantEvent.AssistantEvent | Events\n}\n\n/**\n * Represents the options accepted by `turn.replyIfOwed`.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface ReplyOptions {\n /** The assistant's instructions, as passed to `turn.run`. `Instructions.protocol` is added. */\n readonly instructions: string | Instructions.Resolved\n /**\n * The message given to the model after the history. Defaults to a message saying that it\n * hasn't replied yet and should reply now.\n */\n readonly nudge?: string | undefined\n}\n\n/**\n * Represents one turn, as the assistant's handler receives it.\n *\n * `Events` is the type of the app's own events that the assistant registered with its `events`\n * option.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Turn<Events = never> {\n readonly chatId: ChatId\n /**\n * Returns the model's history, built from the chat's entries (all of them, or those a view\n * keeps), together with how far into the chat it reaches.\n *\n * Read it before every model call, as the loop does. Each step records how far its model call\n * read, and the turn ends once it has handled what the model saw. An input that arrived after\n * that is handled by the next turn.\n */\n readonly history: (view?: HistoryView<Events>) => Effect.Effect<AssistantLoop.ChatHistory>\n /**\n * Returns the full system prompt for the given instructions. It contains\n * `Instructions.protocol`, the instructions, and the specialist runs that are working now.\n */\n readonly instructions: (instructions: string | Instructions.Resolved) => Instructions.Resolved\n /**\n * Runs the agent loop over the chat. The loop uses `Instructions.protocol`, the assistant's own\n * tools, and the instructions and tools in `options`. Every step is a tool call, and\n * the loop ends with `replyIfOwed`.\n */\n readonly run: <T extends Record<string, Tool.Any> = Record<never, never>>(\n options: RunOptions<T, Events>\n ) => Stream.Stream<\n AssistantLoop.Event,\n AiError.AiError,\n LanguageModel.LanguageModel | TurnServices | AssistantLoop.ToolkitServices<T>\n >\n /**\n * Runs one more pass when the turn would otherwise end without a reply to the user's latest\n * message. In this pass the model can only reply.\n *\n * The stream is empty when no reply is owed, or when an input arrived that the next turn will\n * read. `run` runs this pass with its own prompt and tools, so the pass reuses the cached\n * prompt.\n */\n readonly replyIfOwed: (\n options: ReplyOptions\n ) => Stream.Stream<AssistantLoop.Event, AiError.AiError, LanguageModel.LanguageModel | TurnServices>\n}\n\n/**\n * Represents an assistant's handler, which runs a turn and returns it as a stream of the loop's\n * events.\n *\n * The handler may return the stream directly or from an Effect. `R` is the services the stream\n * requires, and `RE` is the services the Effect requires.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Handler<R = never, RE = R, Events = never> = (\n turn: Turn<Events>\n) =>\n | Stream.Stream<AssistantLoop.Event, unknown, R>\n | Effect.Effect<Stream.Stream<AssistantLoop.Event, unknown, R>, unknown, RE>\n\n/**\n * Computes the services an assistant's handler needs beyond those its turns provide (the turn\n * services and `LanguageModel`) and its tools' handlers (which its layer requires). These are the\n * services that its build Effect must require.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type HandlerServices<R> = Exclude<R, TurnServices | LanguageModel.LanguageModel | Tool.Handler<string>>\n\nconst toStream = <R, RE>(\n result:\n | Stream.Stream<AssistantLoop.Event, unknown, R>\n | Effect.Effect<Stream.Stream<AssistantLoop.Event, unknown, R>, unknown, RE>\n): Stream.Stream<AssistantLoop.Event, unknown, R | RE> => (Effect.isEffect(result) ? Stream.unwrap(result) : result)\n\n/**\n * Identifies an assistant's service in a context. Each assistant has its own identifier, keyed by\n * its name.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Identifier<Name extends string> {\n readonly \"~effect-assistant/Assistant\": Name\n}\n\n/**\n * Identifies, in a context, the chat that an assistant's turns run in.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface ChatIdentifier<Name extends string> {\n readonly \"~effect-assistant/Assistant/Chat\": Name\n}\n\n/**\n * Represents the chat that a turn of this assistant runs in, typed with the assistant's events.\n *\n * Use `yield* MyAssistant.Chat` in the handler, in the handler's build, or in a tool that its\n * turns run. Code that doesn't know the assistant uses `CurrentChat` instead.\n *\n * @see {@link CurrentChat} for the same chat without the assistant's event types\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Chat<Events = never> {\n readonly chatId: ChatId\n /**\n * The chat's entries so far, in order. They include the library's events and the assistant's\n * own events, typed.\n */\n readonly entries: Effect.Effect<ReadonlyArray<Entry<Events>>>\n /**\n * Appends events after the chat's last entry and sends them to the chat's subscribers. The\n * events can be the library's or the assistant's own, such as a summary. None of them wakes a\n * turn: only what comes in through the assistant's `submit` does.\n */\n append(...events: ReadonlyArray<AssistantEvent.AssistantEvent | Events>): Effect.Effect<void>\n}\n\n/**\n * Represents the service that `yield* MyAssistant` returns. It gives code outside a chat, such as\n * a server or a workflow, access to the assistant's chats. Code that runs inside a chat uses\n * `CurrentChat` instead.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Service<Events = never> {\n /**\n * Stores an input or one of the assistant's own events, then returns. Examples are a user's\n * message or block answer, a workflow's progress, or the order the user placed. Each one is an\n * input: storing it wakes a turn, which the assistant runs in the background. The handler decides\n * what the turn does with it, and an app event the model should read needs a history view that\n * maps it (see {@link HistoryView}). To store an app event without waking a turn, append it from\n * within the chat instead (`yield* MyAssistant.Chat`).\n *\n * Fails with `GetChatError` when the app's `Chats` service reports that the chat can't be used,\n * because the app doesn't know the chat or has deleted it.\n */\n // Method syntax: a service for these events is one for any (`Service<any>`), for helpers over assistants.\n submit(chatId: ChatId, input: AssistantEvent.Input | Events): Effect.Effect<void, Chats.GetChatError>\n /**\n * Streams the chat's stored entries, then live ones, with the assistant's own events decoded.\n * A reader that already has the earlier entries passes `after` and receives only the entries\n * after that `sequence`.\n *\n * For a chat client, pipe the whole log into `Timeline.changes(MyAssistant, { after })`, which\n * streams the chat's timeline, as its user sees it. Its `after` goes there, not here: a message\n * on the timeline depends on earlier entries. The timeline shows only the app events named in its\n * `show`, none by default (see `Timeline.changes`).\n */\n readonly subscribe: (\n chatId: ChatId,\n options?: { readonly after?: number | undefined }\n ) => Stream.Stream<Entry<Events>>\n /**\n * Deletes a chat by storing a `ChatDeleted` event and stopping everything that works in it.\n *\n * **Details**\n *\n * A turn or specialist run working in the chat stops, and subscribers' streams end with the\n * `ChatDeleted` event. After that the chat stores nothing more: a later input, a run's report,\n * or a workflow's event for it is dropped without an error. The log is kept, and removing it is\n * up to the app. Deleting a chat a second time does nothing.\n *\n * When the app's `Chats` service reports a chat as deleted, the assistant deletes it in the same\n * way the next time it checks. It checks before an input is stored and before each model call.\n */\n readonly delete: (chatId: ChatId) => Effect.Effect<void>\n}\n\n/**\n * Represents an assistant's definition.\n *\n * The definition holds what callers and the UI need to know about the assistant, builds its\n * handler's layer, and is the key of its service (`yield* MyAssistant`). `Events` is the type of\n * the app's own events that the assistant registered.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Assistant<\n Name extends string = string,\n Specs extends Specialist.Any = never,\n Events = never\n> extends Context.Service<Identifier<Name>, Service<Events>> {\n readonly [TypeId]: typeof TypeId\n readonly name: Name\n readonly blocks: BlockKit.BlockKit | undefined\n /** The specialists the assistant can hand work to. */\n readonly specialists: ReadonlyArray<Specs>\n /** The schemas of the app's own events the assistant stores. */\n readonly events: ReadonlyArray<AssistantEvent.AppEventSchema>\n /**\n * Creates a layer that registers the assistant's handler with the engine and provides the\n * assistant's service.\n *\n * **Details**\n *\n * `build` is the handler, or an Effect that returns it. The handler may use the turn services,\n * `LanguageModel`, its tools' handlers (which the layer then requires), and services that its\n * build Effect required (see `Specialist.CheckServices`). The layer requires the\n * `AssistantEngine`, the `LanguageModel`, and each specialist's handler\n * (`Research.toLayer(...)`).\n *\n * **Example** (Providing an assistant's handler)\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 * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * turn.run({ instructions: \"You help a small team. Be brief.\" })\n * )\n *\n * const AppLive = MyAssistantLive.pipe(\n * Layer.provide(ModelLive),\n * Layer.provideMerge(AssistantEngine.layerMemory),\n * Layer.provideMerge(ChatLog.layerMemory)\n * )\n * ```\n */\n readonly toLayer: <R = never, RE = never, E = never, RX = never>(\n build: (Handler<R, RE, Events> | Effect.Effect<Handler<R, RE, Events>, E, RX>) &\n Specialist.CheckServices<Exclude<HandlerServices<R | RE>, RX | ChatIdentifier<Name>>>\n ) => Layer.Layer<\n Identifier<Name>,\n never,\n | AssistantEngine\n | LanguageModel.LanguageModel\n | Specialist.HandlerOf<Specs>\n | Exclude<RX, Scope.Scope | CurrentChat | ChatIdentifier<Name>>\n | Specialist.ToolHandlers<R | RE>\n >\n /**\n * A layer that provides the assistant's service without its handler.\n *\n * **When to use**\n *\n * Use it in a process that submits to chats that another process runs, such as a web server in\n * front of a cluster.\n *\n * **Example** (Serving clients in front of a cluster)\n *\n * ```ts\n * import { Assistant, AssistantEngine } from \"effect-assistant\"\n * import * as Layer from \"effect/Layer\"\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * // Requires `Sharding`. The cluster's runners run the chats, with `AssistantEngine.layerCluster`.\n * const MyAssistantClient = MyAssistant.layerClient.pipe(\n * Layer.provide(AssistantEngine.layerClusterClient)\n * )\n * ```\n */\n readonly layerClient: Layer.Layer<Identifier<Name>, never, AssistantEngine>\n /**\n * The key of the chat that the assistant's turns run in, typed with its events. See\n * {@link Chat}.\n */\n readonly Chat: Context.Service<ChatIdentifier<Name>, Chat<Events>>\n /** A schema that encodes the chat's entries to JSON, including the assistant's own events. */\n readonly Entry: Schema.Codec<Entry<Events>, Schema.Json>\n}\n\n/**\n * Represents any assistant, for use in generic constraints.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type Any = Assistant<any, any, any>\n\n/**\n * Returns `true` when the value is an assistant's definition, created with {@link make}.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isAssistant = (u: unknown): u is Any => Predicate.hasProperty(u, TypeId)\n\nfunction entrySchema<E>(events: ReadonlyArray<AssistantEvent.AppEventSchema>): Schema.Codec<Entry<E>, Schema.Json>\n// The overload states the decoded type: the library's events, or one of these schemas' types.\nfunction entrySchema(events: ReadonlyArray<AssistantEvent.AppEventSchema>): Schema.Top {\n return Schema.toCodecJson(\n Schema.Struct({\n chatId: ChatId,\n sequence: Schema.Int,\n at: Schema.Finite,\n event: Schema.Union([AssistantEvent.AssistantEvent, ...events])\n })\n )\n}\n\n/**\n * Creates an assistant definition from its name, its UI blocks, its specialists, and the schemas of\n * the app's own events it stores.\n *\n * **Details**\n *\n * The definition holds no code that runs turns. Provide a handler with its `toLayer`, then yield\n * the definition to get the assistant's {@link Service}.\n *\n * The definition only says which of the app's events a chat stores. What happens with one is up\n * to the places that use it. One that comes in through the service's `submit`, from outside a\n * turn, is an input and wakes a turn, like a user's message; one a turn appends to its chat never\n * does. The handler decides what a turn does, a history view says how the model reads an event\n * ({@link HistoryView}), and the chat's timeline says which its user sees\n * (`Timeline.changes`).\n *\n * **Example** (Defining an assistant and submitting a message)\n *\n * ```ts\n * import { Assistant, AssistantEvent, ChatId } from \"effect-assistant\"\n * import * as Effect from \"effect/Effect\"\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const program = Effect.gen(function* () {\n * const assistant = yield* MyAssistant\n * const chatId = yield* ChatId.make()\n *\n * yield* assistant.submit(chatId, AssistantEvent.UserMessage.make({ text: \"Weather in Oslo?\" }))\n * })\n * ```\n *\n * @see {@link Assistant} for the definition's members\n * @see {@link Service} for what `yield* MyAssistant` returns\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <\n const Name extends string,\n const Specs extends ReadonlyArray<Specialist.Any> = readonly [],\n const Events extends ReadonlyArray<AssistantEvent.AppEventSchema> = readonly []\n>(\n name: Name,\n options?: {\n /** UI blocks the assistant can add to its messages. */\n readonly blocks?: BlockKit.BlockKit | undefined\n /**\n * The specialists the assistant can hand work to. Each specialist's `toLayer` must be\n * provided to the assistant's layer.\n */\n readonly specialists?: Specs | undefined\n /**\n * The schemas of the app's own events, each a tagged struct or class. `submit` and the chat's\n * `append` store them, in their JSON form, and the chat's `entries` and `subscribe` read them\n * back, decoded. Each needs a tag of its own that isn't one of the library's: `make` throws\n * otherwise.\n */\n readonly events?: Events | undefined\n }\n): Assistant<Name, Specs[number], Events[number][\"Type\"]> => {\n const events = options?.events ?? []\n\n const definition: Turns.Definition = {\n name,\n blocks: options?.blocks,\n specialists: options?.specialists ?? [],\n app: AppEvents.make(events)\n }\n\n type OwnEvents = Events[number][\"Type\"]\n\n const Key = Context.Service<Identifier<Name>, Service<OwnEvents>>(`effect-assistant/Assistant/${name}`)\n\n // The app's own events go to the engine encoded, as they're stored; the library's go unchanged.\n const stored = (\n input: AssistantEvent.Input | OwnEvents\n ): Effect.Effect<AssistantEvent.Input | AssistantEvent.StoredAppEvent> =>\n AssistantEvent.isInput(input) ? Effect.succeed(input) : AppEvents.encode(definition.app, input)\n\n const ChatKey = Context.Service<ChatIdentifier<Name>, Chat<OwnEvents>>(`effect-assistant/Assistant/${name}/Chat`)\n\n // On the engine and the app's `Chats` the layer was given, so every caller sees the same ones.\n const makeService = Effect.gen(function* () {\n const engine = yield* AssistantEngine\n const chats = yield* Chats.Chats\n\n return Key.of({\n submit: Effect.fn(\"Assistant.submit\")(function* (chatId: ChatId, input: AssistantEvent.Input | OwnEvents) {\n // Checked here, where the caller can handle it: the chat checks again when it stores the input.\n yield* chats.get(chatId)\n yield* engine.submit(name, chatId, yield* stored(input))\n }),\n subscribe: (chatId, options) =>\n engine\n .subscribe(name, chatId, options?.after ?? 0)\n .pipe(\n Stream.filterMap(\n Filter.fromPredicateOption((entry: AssistantEvent.Entry) => AppEvents.decodeEntry(definition.app, entry))\n )\n ),\n delete: (chatId) => engine.delete(name, chatId)\n })\n })\n\n // The signature checks what the handler uses (`CheckServices`); the layer's services, which the build\n // required, are provided to it and to every turn's stream, so what's left is the turn services.\n function toLayer<R = never, RE = never, E = never, RX = never>(\n build: (Handler<R, RE, OwnEvents> | Effect.Effect<Handler<R, RE, OwnEvents>, E, RX>) &\n Specialist.CheckServices<Exclude<HandlerServices<R | RE>, RX | ChatIdentifier<Name>>>\n ): Layer.Layer<\n Identifier<Name>,\n never,\n | AssistantEngine\n | LanguageModel.LanguageModel\n | Specialist.HandlerOf<Specs[number]>\n | Exclude<RX, Scope.Scope | CurrentChat | ChatIdentifier<Name>>\n | Specialist.ToolHandlers<R | RE>\n >\n function toLayer(\n build: Handler<any, any, any> | Effect.Effect<Handler<any, any, any>, unknown, any>\n ): Layer.Layer<Identifier<Name>, never, any> {\n return Layer.effect(\n Key,\n Effect.gen(function* () {\n const context = yield* Effect.context()\n\n const registration = yield* Turns.register(definition, {\n // The build runs per chat activation, in the chat; here it only keeps the layer's services for it.\n open: (typed) =>\n Effect.gen(function* () {\n // The chat's entries are already decoded with this assistant's events.\n const chat = ChatKey.of({ chatId: typed.chatId, entries: typed.entries, append: typed.append })\n const handler = Effect.isEffect(build) ? yield* Effect.provideService(build, ChatKey, chat) : build\n\n return (turn: Turn<any>) =>\n Stream.provideContext(toStream(handler(turn)), context).pipe(Stream.provideService(ChatKey, chat))\n }).pipe(Effect.provideContext(context), Effect.orDie)\n })\n\n yield* (yield* AssistantEngine).register(registration)\n\n return yield* makeService\n })\n )\n }\n\n // The key is a function, whose own `name` is read-only: the assistant's name replaces it.\n const named = Object.defineProperty(Key, \"name\", { value: name, writable: true })\n\n return Object.assign(named, {\n [TypeId]: TypeId,\n name,\n blocks: options?.blocks,\n specialists: options?.specialists ?? [],\n events,\n toLayer,\n layerClient: Layer.effect(Key, makeService),\n Chat: ChatKey,\n Entry: entrySchema<OwnEvents>(events)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsCA,MAAM,SAAS;AAmMf,MAAM,YACJ,WAGyD,OAAO,SAAS,MAAM,IAAI,OAAO,OAAO,MAAM,IAAI;;;;;;;AAuN7G,MAAa,eAAe,MAAyB,UAAU,YAAY,GAAG,MAAM;AAIpF,SAAS,YAAY,QAAkE;CACrF,OAAO,OAAO,YACZ,OAAO,OAAO;EACZ,QAAQ;EACR,UAAU,OAAO;EACjB,IAAI,OAAO;EACX,OAAO,OAAO,MAAM,CAACA,gBAA+B,GAAG,MAAM,CAAC;CAChE,CAAC,CACH;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,QAKX,MACA,YAgB2D;CAC3D,MAAM,SAAS,SAAS,UAAU,CAAC;CAEnC,MAAM,aAA+B;EACnC;EACA,QAAQ,SAAS;EACjB,aAAa,SAAS,eAAe,CAAC;EACtC,KAAKC,OAAe,MAAM;CAC5B;CAIA,MAAM,MAAM,QAAQ,QAA8C,8BAA8B,MAAM;CAGtG,MAAM,UACJ,UAEAC,QAAuB,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAIC,OAAiB,WAAW,KAAK,KAAK;CAEhG,MAAM,UAAU,QAAQ,QAA+C,8BAA8B,KAAK,MAAM;CAGhH,MAAM,cAAc,OAAO,IAAI,aAAa;EAC1C,MAAM,SAAS,OAAO;EACtB,MAAM,QAAQ,OAAOC;EAErB,OAAO,IAAI,GAAG;GACZ,QAAQ,OAAO,GAAG,kBAAkB,CAAC,CAAC,WAAW,QAAgB,OAAyC;IAExG,OAAO,MAAM,IAAI,MAAM;IACvB,OAAO,OAAO,OAAO,MAAM,QAAQ,OAAO,OAAO,KAAK,CAAC;GACzD,CAAC;GACD,YAAY,QAAQ,YAClB,OACG,UAAU,MAAM,QAAQ,SAAS,SAAS,CAAC,CAAC,CAC5C,KACC,OAAO,UACL,OAAO,qBAAqB,UAAgCC,YAAsB,WAAW,KAAK,KAAK,CAAC,CAC1G,CACF;GACJ,SAAS,WAAW,OAAO,OAAO,MAAM,MAAM;EAChD,CAAC;CACH,CAAC;CAgBD,SAAS,QACP,OAC2C;EAC3C,OAAO,MAAM,OACX,KACA,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,OAAO,QAAQ;GAEtC,MAAM,eAAe,OAAOC,SAAe,YAAY,EAErD,OAAO,UACL,OAAO,IAAI,aAAa;IAEtB,MAAM,OAAO,QAAQ,GAAG;KAAE,QAAQ,MAAM;KAAQ,SAAS,MAAM;KAAS,QAAQ,MAAM;IAAO,CAAC;IAC9F,MAAM,UAAU,OAAO,SAAS,KAAK,IAAI,OAAO,OAAO,eAAe,OAAO,SAAS,IAAI,IAAI;IAE9F,QAAQ,SACN,OAAO,eAAe,SAAS,QAAQ,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,OAAO,eAAe,SAAS,IAAI,CAAC;GACrG,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,OAAO,GAAG,OAAO,KAAK,EACxD,CAAC;GAED,QAAQ,OAAO,gBAAA,CAAiB,SAAS,YAAY;GAErD,OAAO,OAAO;EAChB,CAAC,CACH;CACF;CAGA,MAAM,QAAQ,OAAO,eAAe,KAAK,QAAQ;EAAE,OAAO;EAAM,UAAU;CAAK,CAAC;CAEhF,OAAO,OAAO,OAAO,OAAO;GACzB,SAAS;EACV;EACA,QAAQ,SAAS;EACjB,aAAa,SAAS,eAAe,CAAC;EACtC;EACA;EACA,aAAa,MAAM,OAAO,KAAK,WAAW;EAC1C,MAAM;EACN,OAAO,YAAuB,MAAM;CACtC,CAAC;AACH"}
@@ -31,8 +31,8 @@ export interface State {
31
31
  * to start.
32
32
  */
33
33
  readonly queued: boolean;
34
- /** The `seq` of the newest input. */
35
- readonly lastInputSeq: number;
34
+ /** The `sequence` of the newest input. */
35
+ readonly lastInputSequence: number;
36
36
  /** Specialist runs that are working, by run id. */
37
37
  readonly specialists: ReadonlyMap<RunId, SpecialistRun>;
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantActivity.d.ts","names":[],"sources":["../src/AssistantActivity.ts"],"mappings":";;;;;;;;;;;iBAsBiB;WACN,OAAO;WACP;;WAEA;;WAEA;;;;;;;;iBASM;;WAEN;;;;;WAKA;;WAEA;;WAEA,aAAa,YAAY,OAAO;;;;;;;;qBAS9B,OAAO;;;;;;;;;;;;;;;qBA8BP,QAAK,UAAc,OAAK,OAAS,aAA0B;;;;;;;;qBA4C3D,cAAW,SAAa,cAAc,cAA2B;;;;;;;;qBAUjE,YAAS,UAAc"}
1
+ {"version":3,"file":"AssistantActivity.d.ts","names":[],"sources":["../src/AssistantActivity.ts"],"mappings":";;;;;;;;;;;iBAsBiB;WACN,OAAO;WACP;;WAEA;;WAEA;;;;;;;;iBASM;;WAEN;;;;;WAKA;;WAEA;;WAEA,aAAa,YAAY,OAAO;;;;;;;;qBAS9B,OAAO;;;;;;;;;;;;;;;qBA8BP,QAAK,UAAc,OAAK,OAAS,aAA0B;;;;;;;;qBA8C3D,cAAW,SAAa,cAAc,cAA2B;;;;;;;;qBAUjE,YAAS,UAAc"}
@@ -29,7 +29,7 @@ var AssistantActivity_exports = /* @__PURE__ */ __exportAll({
29
29
  const empty = {
30
30
  turnSince: void 0,
31
31
  queued: false,
32
- lastInputSeq: 0,
32
+ lastInputSequence: 0,
33
33
  specialists: /* @__PURE__ */ new Map()
34
34
  };
35
35
  const withoutRun = (activity, runId) => {
@@ -60,14 +60,14 @@ const apply = (activity, entry) => {
60
60
  if (!isLibraryEvent(event)) return isAppInput(event) ? {
61
61
  ...activity,
62
62
  queued: true,
63
- lastInputSeq: entry.seq
63
+ lastInputSequence: entry.sequence
64
64
  } : activity;
65
65
  if (isInput(event)) {
66
66
  const current = Predicate.isTagged(event, "SpecialistFinished") ? withoutRun(activity, event.runId) : activity;
67
67
  return isWaking(event) ? {
68
68
  ...current,
69
69
  queued: true,
70
- lastInputSeq: entry.seq
70
+ lastInputSequence: entry.sequence
71
71
  } : current;
72
72
  }
73
73
  return Match.value(event).pipe(Match.tag("TurnStarted", () => ({
@@ -76,7 +76,7 @@ const apply = (activity, entry) => {
76
76
  })), Match.tag("TurnCompleted", "TurnFailed", (ended) => ({
77
77
  ...activity,
78
78
  turnSince: void 0,
79
- queued: activity.lastInputSeq > ended.throughSeq
79
+ queued: activity.lastInputSequence > ended.throughSequence
80
80
  })), Match.tag("SpecialistStarted", (started) => ({
81
81
  ...activity,
82
82
  specialists: new Map(activity.specialists).set(started.runId, {
@@ -87,7 +87,7 @@ const apply = (activity, entry) => {
87
87
  })
88
88
  })), Match.tag("ChatDeleted", () => ({
89
89
  ...empty,
90
- lastInputSeq: activity.lastInputSeq
90
+ lastInputSequence: activity.lastInputSequence
91
91
  })), Match.orElse(() => activity));
92
92
  };
93
93
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantActivity.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.isAppInput","AssistantEvent.isInput","AssistantEvent.isWaking"],"sources":["../src/AssistantActivity.ts"],"sourcesContent":["/**\n * This module provides functions that determine what is working in a chat right now, read from\n * the chat's log.\n *\n * A {@link State} records whether the assistant is running a turn or is about to start one,\n * and which specialist runs are working. Fold a chat's entries into it with {@link apply}, or\n * build it from a list of entries with {@link fromEntries}, then check it with\n * {@link isWorking}.\n *\n * @since 0.1.0\n */\nimport * as Match from \"effect/Match\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport type { RunId } from \"./RunId.ts\"\n\n/**\n * Represents a specialist run that is working.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface SpecialistRun {\n readonly runId: RunId\n readonly specialist: string\n /** The goal the run is working on. */\n readonly goal: string\n /** When the run started, in epoch milliseconds. */\n readonly since: number\n}\n\n/**\n * Represents what is working in a chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface State {\n /** When the running turn started, in epoch milliseconds, or `undefined` when no turn runs. */\n readonly turnSince: number | undefined\n /**\n * Whether an input arrived that no finished turn has read. When this is true, a turn is about\n * to start.\n */\n readonly queued: boolean\n /** The `seq` of the newest input. */\n readonly lastInputSeq: number\n /** Specialist runs that are working, by run id. */\n readonly specialists: ReadonlyMap<RunId, SpecialistRun>\n}\n\n/**\n * The activity of a chat with an empty log, where nothing is working.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const empty: State = {\n turnSince: undefined,\n queued: false,\n lastInputSeq: 0,\n specialists: new Map()\n}\n\nconst withoutRun = (activity: State, runId: RunId): State => {\n if (!activity.specialists.has(runId)) return activity\n\n const specialists = new Map(activity.specialists)\n specialists.delete(runId)\n\n return { ...activity, specialists }\n}\n\n/**\n * Returns the activity after one more entry. Fold a chat's entries in order, or use\n * {@link fromEntries}.\n *\n * **Details**\n *\n * One of the app's events counts as an input, and a turn as about to start, when it came in\n * through the assistant's `submit`. The log marks it so (`AssistantEvent.isAppInput`), as\n * `ChatLog.read` returns it; `subscribe` streams it decoded, without the mark, so there the turn\n * shows from its `TurnStarted`.\n *\n * @category combinators\n * @since 0.1.0\n */\nexport const apply = (activity: State, entry: AssistantEvent.AnyEntry): State => {\n const event = entry.event\n\n if (!AssistantEvent.isLibraryEvent(event)) {\n return AssistantEvent.isAppInput(event) ? { ...activity, queued: true, lastInputSeq: entry.seq } : activity\n }\n\n if (AssistantEvent.isInput(event)) {\n // A finished run's report is an input, and ends the run.\n const current = Predicate.isTagged(event, \"SpecialistFinished\") ? withoutRun(activity, event.runId) : activity\n\n return AssistantEvent.isWaking(event) ? { ...current, queued: true, lastInputSeq: entry.seq } : current\n }\n\n return Match.value(event).pipe(\n Match.tag(\"TurnStarted\", (): State => ({ ...activity, turnSince: entry.at })),\n // Turns run one at a time, so an end closes whichever turn is open, also one a restart cut off.\n Match.tag(\"TurnCompleted\", \"TurnFailed\", (ended): State => ({\n ...activity,\n turnSince: undefined,\n queued: activity.lastInputSeq > ended.throughSeq\n })),\n Match.tag(\"SpecialistStarted\", (started): State => ({\n ...activity,\n specialists: new Map(activity.specialists).set(started.runId, {\n runId: started.runId,\n specialist: started.specialist,\n goal: started.goal,\n since: entry.at\n })\n })),\n // Deleting a chat stops everything in it, and nothing starts again.\n Match.tag(\"ChatDeleted\", (): State => ({ ...empty, lastInputSeq: activity.lastInputSeq })),\n Match.orElse(() => activity)\n )\n}\n\n/**\n * Returns what is working in a chat after the given entries, folded in order from\n * {@link empty}.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): State =>\n entries.reduce((activity, entry) => apply(activity, entry), empty)\n\n/**\n * Returns whether anything is working: a turn that is running or about to start, or a specialist\n * run.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isWorking = (activity: State): boolean =>\n activity.turnSince !== undefined || activity.queued || activity.specialists.size > 0\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,MAAa,QAAe;CAC1B,WAAW,KAAA;CACX,QAAQ;CACR,cAAc;CACd,6BAAa,IAAI,IAAI;AACvB;AAEA,MAAM,cAAc,UAAiB,UAAwB;CAC3D,IAAI,CAAC,SAAS,YAAY,IAAI,KAAK,GAAG,OAAO;CAE7C,MAAM,cAAc,IAAI,IAAI,SAAS,WAAW;CAChD,YAAY,OAAO,KAAK;CAExB,OAAO;EAAE,GAAG;EAAU;CAAY;AACpC;;;;;;;;;;;;;;;AAgBA,MAAa,SAAS,UAAiB,UAA0C;CAC/E,MAAM,QAAQ,MAAM;CAEpB,IAAI,CAACA,eAA8B,KAAK,GACtC,OAAOC,WAA0B,KAAK,IAAI;EAAE,GAAG;EAAU,QAAQ;EAAM,cAAc,MAAM;CAAI,IAAI;CAGrG,IAAIC,QAAuB,KAAK,GAAG;EAEjC,MAAM,UAAU,UAAU,SAAS,OAAO,oBAAoB,IAAI,WAAW,UAAU,MAAM,KAAK,IAAI;EAEtG,OAAOC,SAAwB,KAAK,IAAI;GAAE,GAAG;GAAS,QAAQ;GAAM,cAAc,MAAM;EAAI,IAAI;CAClG;CAEA,OAAO,MAAM,MAAM,KAAK,CAAC,CAAC,KACxB,MAAM,IAAI,sBAA6B;EAAE,GAAG;EAAU,WAAW,MAAM;CAAG,EAAE,GAE5E,MAAM,IAAI,iBAAiB,eAAe,WAAkB;EAC1D,GAAG;EACH,WAAW,KAAA;EACX,QAAQ,SAAS,eAAe,MAAM;CACxC,EAAE,GACF,MAAM,IAAI,sBAAsB,aAAoB;EAClD,GAAG;EACH,aAAa,IAAI,IAAI,SAAS,WAAW,CAAC,CAAC,IAAI,QAAQ,OAAO;GAC5D,OAAO,QAAQ;GACf,YAAY,QAAQ;GACpB,MAAM,QAAQ;GACd,OAAO,MAAM;EACf,CAAC;CACH,EAAE,GAEF,MAAM,IAAI,sBAA6B;EAAE,GAAG;EAAO,cAAc,SAAS;CAAa,EAAE,GACzF,MAAM,aAAa,QAAQ,CAC7B;AACF;;;;;;;;AASA,MAAa,eAAe,YAC1B,QAAQ,QAAQ,UAAU,UAAU,MAAM,UAAU,KAAK,GAAG,KAAK;;;;;;;;AASnE,MAAa,aAAa,aACxB,SAAS,cAAc,KAAA,KAAa,SAAS,UAAU,SAAS,YAAY,OAAO"}
1
+ {"version":3,"file":"AssistantActivity.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.isAppInput","AssistantEvent.isInput","AssistantEvent.isWaking"],"sources":["../src/AssistantActivity.ts"],"sourcesContent":["/**\n * This module provides functions that determine what is working in a chat right now, read from\n * the chat's log.\n *\n * A {@link State} records whether the assistant is running a turn or is about to start one,\n * and which specialist runs are working. Fold a chat's entries into it with {@link apply}, or\n * build it from a list of entries with {@link fromEntries}, then check it with\n * {@link isWorking}.\n *\n * @since 0.1.0\n */\nimport * as Match from \"effect/Match\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport type { RunId } from \"./RunId.ts\"\n\n/**\n * Represents a specialist run that is working.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface SpecialistRun {\n readonly runId: RunId\n readonly specialist: string\n /** The goal the run is working on. */\n readonly goal: string\n /** When the run started, in epoch milliseconds. */\n readonly since: number\n}\n\n/**\n * Represents what is working in a chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface State {\n /** When the running turn started, in epoch milliseconds, or `undefined` when no turn runs. */\n readonly turnSince: number | undefined\n /**\n * Whether an input arrived that no finished turn has read. When this is true, a turn is about\n * to start.\n */\n readonly queued: boolean\n /** The `sequence` of the newest input. */\n readonly lastInputSequence: number\n /** Specialist runs that are working, by run id. */\n readonly specialists: ReadonlyMap<RunId, SpecialistRun>\n}\n\n/**\n * The activity of a chat with an empty log, where nothing is working.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const empty: State = {\n turnSince: undefined,\n queued: false,\n lastInputSequence: 0,\n specialists: new Map()\n}\n\nconst withoutRun = (activity: State, runId: RunId): State => {\n if (!activity.specialists.has(runId)) return activity\n\n const specialists = new Map(activity.specialists)\n specialists.delete(runId)\n\n return { ...activity, specialists }\n}\n\n/**\n * Returns the activity after one more entry. Fold a chat's entries in order, or use\n * {@link fromEntries}.\n *\n * **Details**\n *\n * One of the app's events counts as an input, and a turn as about to start, when it came in\n * through the assistant's `submit`. The log marks it so (`AssistantEvent.isAppInput`), as\n * `ChatLog.read` returns it; `subscribe` streams it decoded, without the mark, so there the turn\n * shows from its `TurnStarted`.\n *\n * @category combinators\n * @since 0.1.0\n */\nexport const apply = (activity: State, entry: AssistantEvent.AnyEntry): State => {\n const event = entry.event\n\n if (!AssistantEvent.isLibraryEvent(event)) {\n return AssistantEvent.isAppInput(event)\n ? { ...activity, queued: true, lastInputSequence: entry.sequence }\n : activity\n }\n\n if (AssistantEvent.isInput(event)) {\n // A finished run's report is an input, and ends the run.\n const current = Predicate.isTagged(event, \"SpecialistFinished\") ? withoutRun(activity, event.runId) : activity\n\n return AssistantEvent.isWaking(event) ? { ...current, queued: true, lastInputSequence: entry.sequence } : current\n }\n\n return Match.value(event).pipe(\n Match.tag(\"TurnStarted\", (): State => ({ ...activity, turnSince: entry.at })),\n // Turns run one at a time, so an end closes whichever turn is open, also one a restart cut off.\n Match.tag(\"TurnCompleted\", \"TurnFailed\", (ended): State => ({\n ...activity,\n turnSince: undefined,\n queued: activity.lastInputSequence > ended.throughSequence\n })),\n Match.tag(\"SpecialistStarted\", (started): State => ({\n ...activity,\n specialists: new Map(activity.specialists).set(started.runId, {\n runId: started.runId,\n specialist: started.specialist,\n goal: started.goal,\n since: entry.at\n })\n })),\n // Deleting a chat stops everything in it, and nothing starts again.\n Match.tag(\"ChatDeleted\", (): State => ({ ...empty, lastInputSequence: activity.lastInputSequence })),\n Match.orElse(() => activity)\n )\n}\n\n/**\n * Returns what is working in a chat after the given entries, folded in order from\n * {@link empty}.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): State =>\n entries.reduce((activity, entry) => apply(activity, entry), empty)\n\n/**\n * Returns whether anything is working: a turn that is running or about to start, or a specialist\n * run.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isWorking = (activity: State): boolean =>\n activity.turnSince !== undefined || activity.queued || activity.specialists.size > 0\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,MAAa,QAAe;CAC1B,WAAW,KAAA;CACX,QAAQ;CACR,mBAAmB;CACnB,6BAAa,IAAI,IAAI;AACvB;AAEA,MAAM,cAAc,UAAiB,UAAwB;CAC3D,IAAI,CAAC,SAAS,YAAY,IAAI,KAAK,GAAG,OAAO;CAE7C,MAAM,cAAc,IAAI,IAAI,SAAS,WAAW;CAChD,YAAY,OAAO,KAAK;CAExB,OAAO;EAAE,GAAG;EAAU;CAAY;AACpC;;;;;;;;;;;;;;;AAgBA,MAAa,SAAS,UAAiB,UAA0C;CAC/E,MAAM,QAAQ,MAAM;CAEpB,IAAI,CAACA,eAA8B,KAAK,GACtC,OAAOC,WAA0B,KAAK,IAClC;EAAE,GAAG;EAAU,QAAQ;EAAM,mBAAmB,MAAM;CAAS,IAC/D;CAGN,IAAIC,QAAuB,KAAK,GAAG;EAEjC,MAAM,UAAU,UAAU,SAAS,OAAO,oBAAoB,IAAI,WAAW,UAAU,MAAM,KAAK,IAAI;EAEtG,OAAOC,SAAwB,KAAK,IAAI;GAAE,GAAG;GAAS,QAAQ;GAAM,mBAAmB,MAAM;EAAS,IAAI;CAC5G;CAEA,OAAO,MAAM,MAAM,KAAK,CAAC,CAAC,KACxB,MAAM,IAAI,sBAA6B;EAAE,GAAG;EAAU,WAAW,MAAM;CAAG,EAAE,GAE5E,MAAM,IAAI,iBAAiB,eAAe,WAAkB;EAC1D,GAAG;EACH,WAAW,KAAA;EACX,QAAQ,SAAS,oBAAoB,MAAM;CAC7C,EAAE,GACF,MAAM,IAAI,sBAAsB,aAAoB;EAClD,GAAG;EACH,aAAa,IAAI,IAAI,SAAS,WAAW,CAAC,CAAC,IAAI,QAAQ,OAAO;GAC5D,OAAO,QAAQ;GACf,YAAY,QAAQ;GACpB,MAAM,QAAQ;GACd,OAAO,MAAM;EACf,CAAC;CACH,EAAE,GAEF,MAAM,IAAI,sBAA6B;EAAE,GAAG;EAAO,mBAAmB,SAAS;CAAkB,EAAE,GACnG,MAAM,aAAa,QAAQ,CAC7B;AACF;;;;;;;;AASA,MAAa,eAAe,YAC1B,QAAQ,QAAQ,UAAU,UAAU,MAAM,UAAU,KAAK,GAAG,KAAK;;;;;;;;AASnE,MAAa,aAAa,aACxB,SAAS,cAAc,KAAA,KAAa,SAAS,UAAU,SAAS,YAAY,OAAO"}
@@ -19,7 +19,7 @@ declare const AssistantEngine_base: Context.ServiceClass<AssistantEngine, "effec
19
19
  * background.
20
20
  */
21
21
  readonly submit: (assistant: string, chatId: ChatId, input: Input | StoredAppEvent) => Effect.Effect<void>;
22
- /** Streams the chat's stored entries after the `after` seq, then live ones. */
22
+ /** Streams the chat's stored entries after the `after` sequence, then live ones. */
23
23
  readonly subscribe: (assistant: string, chatId: ChatId, after: number) => Stream.Stream<Entry>;
24
24
  /**
25
25
  * Deletes one of the assistant's chats. The chat's log records the deletion, and everything
@@ -56,7 +56,7 @@ export declare class AssistantEngine extends AssistantEngine_base {}
56
56
  *
57
57
  * const MyAssistant = Assistant.make("my-assistant")
58
58
  *
59
- * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
59
+ * const AppLive = MyAssistant.toLayer((turn) => turn.run({ instructions: "Be brief." })).pipe(
60
60
  * Layer.provide(ModelLive),
61
61
  * Layer.provideMerge(AssistantEngine.layerMemory),
62
62
  * Layer.provideMerge(ChatLog.layerMemory)
@@ -92,7 +92,7 @@ export declare const layerMemory: Layer.Layer<AssistantEngine, never, ChatLog>;
92
92
  *
93
93
  * const MyAssistant = Assistant.make("my-assistant")
94
94
  *
95
- * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
95
+ * const AppLive = MyAssistant.toLayer((turn) => turn.run({ instructions: "Be brief." })).pipe(
96
96
  * Layer.provide(ModelLive),
97
97
  * Layer.provideMerge(AssistantEngine.layerCluster),
98
98
  * Layer.provideMerge(
@@ -70,7 +70,7 @@ const notRegistered = (assistant) => Effect.die(`The ${assistant} assistant isn'
70
70
  *
71
71
  * const MyAssistant = Assistant.make("my-assistant")
72
72
  *
73
- * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
73
+ * const AppLive = MyAssistant.toLayer((turn) => turn.run({ instructions: "Be brief." })).pipe(
74
74
  * Layer.provide(ModelLive),
75
75
  * Layer.provideMerge(AssistantEngine.layerMemory),
76
76
  * Layer.provideMerge(ChatLog.layerMemory)
@@ -206,7 +206,7 @@ const makeClusterClient = Effect.gen(function* () {
206
206
  *
207
207
  * const MyAssistant = Assistant.make("my-assistant")
208
208
  *
209
- * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
209
+ * const AppLive = MyAssistant.toLayer((turn) => turn.run({ instructions: "Be brief." })).pipe(
210
210
  * Layer.provide(ModelLive),
211
211
  * Layer.provideMerge(AssistantEngine.layerCluster),
212
212
  * Layer.provideMerge(
@@ -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\n * implementations.\n *\n * An assistant's `toLayer` registers the assistant with the engine, and the assistant's `submit`\n * and `subscribe` go through it. Pick one engine for the app, as with a workflow engine.\n * {@link layerMemory} runs chats in the process, for tests and single-process apps.\n * {@link layerCluster} runs each chat as a cluster entity, so a turn cut off by a crash resumes, and\n * {@link layerClusterClient} reaches those chats from a process that runs none.\n *\n * @since 0.1.0\n */\nimport * as ClusterSchema from \"effect/cluster/ClusterSchema\"\nimport * as Entity from \"effect/cluster/Entity\"\nimport * as Sharding from \"effect/cluster/Sharding\"\nimport * as Context from \"effect/Context\"\nimport * as Data from \"effect/Data\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport * as Layer from \"effect/Layer\"\nimport * as MutableHashMap from \"effect/MutableHashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as RcMap from \"effect/RcMap\"\nimport * as Rpc from \"effect/rpc/Rpc\"\nimport * as Schema from \"effect/Schema\"\nimport * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport { ChatId } from \"./ChatId.ts\"\nimport { ChatLog } from \"./ChatLog.ts\"\nimport * as Chat from \"./internal/chat.ts\"\nimport * as History from \"./internal/history.ts\"\nimport type * as Turns from \"./internal/turns.ts\"\n\n/**\n * Service that runs assistants' chats. An assistant's `toLayer` registers the assistant with it,\n * and the assistant's `submit` and `subscribe` use it.\n *\n * @category services\n * @since 0.1.0\n */\nexport class AssistantEngine extends Context.Service<\n AssistantEngine,\n {\n /** Registers an assistant for as long as the scope is open. */\n readonly register: (registration: Turns.Registration) => Effect.Effect<void, never, Scope.Scope>\n /**\n * Stores an input in one of the assistant's chats. The assistant works on it in the\n * background.\n */\n readonly submit: (\n assistant: string,\n chatId: ChatId,\n input: AssistantEvent.Input | AssistantEvent.StoredAppEvent\n ) => Effect.Effect<void>\n /** Streams the chat's stored entries after the `after` seq, then live ones. */\n readonly subscribe: (assistant: string, chatId: ChatId, after: number) => Stream.Stream<AssistantEvent.Entry>\n /**\n * Deletes one of the assistant's chats. The chat's log records the deletion, and everything\n * that works in the chat stops.\n */\n readonly delete: (assistant: string, chatId: ChatId) => Effect.Effect<void>\n }\n>()(\"effect-assistant/AssistantEngine\") {}\n\nclass ChatKey extends Data.Class<{ readonly assistant: string; readonly chatId: ChatId }> {}\n\nconst notRegistered = (assistant: string) =>\n Effect.die(`The ${assistant} assistant isn't registered: provide its toLayer.`)\n\n/**\n * Layer that runs chats in the current process. Each chat's turns run as fibers, one at a time.\n *\n * **Details**\n *\n * With `ChatLog.layerSql`, the engine resumes chats at startup. When an assistant registers, the\n * engine finds the chats that assistant was working on when the process stopped (a turn cut off\n * partway, or an input that no turn read) and runs a turn for each. Run this engine in one process\n * only, because two processes would run the same chat and write its log at the same time.\n *\n * **Example** (Running chats in memory)\n *\n * ```ts\n * import { Assistant, AssistantEngine, ChatLog } from \"effect-assistant\"\n * import type * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as Layer from \"effect/Layer\"\n *\n * declare const ModelLive: Layer.Layer<LanguageModel.LanguageModel>\n *\n * const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: \"Be brief.\" })).pipe(\n * Layer.provide(ModelLive),\n * Layer.provideMerge(AssistantEngine.layerMemory),\n * Layer.provideMerge(ChatLog.layerMemory)\n * )\n * ```\n *\n * @see {@link layerCluster} for running each chat as a cluster entity\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerMemory: Layer.Layer<AssistantEngine, never, ChatLog> = Layer.effect(\n AssistantEngine,\n Effect.gen(function* () {\n const context = yield* Effect.context<ChatLog>()\n const log = yield* ChatLog\n const registered = new Map<string, Turns.Registration>()\n // A chat's keep-alive: a hold on its entry, so it stays open while runs or turns work in the background.\n const held = MutableHashMap.empty<ChatKey, Scope.Closeable>()\n\n // One entry per open chat, like a cluster entity: opened when used, closed five minutes after the last use.\n const chats: RcMap.RcMap<ChatKey, Chat.Chat> = yield* RcMap.make({\n lookup: Effect.fnUntraced(function* (key: ChatKey) {\n const registration = registered.get(key.assistant)\n\n if (registration === undefined) return yield* notRegistered(key.assistant)\n\n return yield* Chat.make({\n registration,\n chatId: key.chatId,\n keepAlive: (enabled) => keepAlive(key, enabled)\n }).pipe(Effect.provide(context))\n }),\n idleTimeToLive: \"5 minutes\"\n })\n\n // On: a hold on the chat's entry, in a scope of its own. Off: closing that scope lets the entry go idle.\n const keepAlive = Effect.fnUntraced(function* (key: ChatKey, enabled: boolean) {\n if (enabled) {\n const scope = yield* Scope.make()\n\n yield* Effect.provideService(RcMap.get(chats, key), Scope.Scope, scope)\n\n return MutableHashMap.set(held, key, scope)\n }\n\n const scope = MutableHashMap.get(held, key)\n\n MutableHashMap.remove(held, key)\n\n if (Option.isSome(scope)) yield* Scope.close(scope.value, Exit.void)\n })\n\n const withChat = Effect.fnUntraced(function* <A>(\n assistant: string,\n chatId: ChatId,\n f: (chat: Chat.Chat) => Effect.Effect<A>\n ) {\n const chat = yield* RcMap.get(chats, new ChatKey({ assistant, chatId }))\n\n return yield* f(chat)\n }, Effect.scoped)\n\n // A chat owes a turn when its last turn was cut off, or an input came after.\n const resumeAll = Effect.fnUntraced(function* (registration: Turns.Registration) {\n const { name } = registration.definition\n\n for (const chatId of yield* log.unfinished) {\n const entries = yield* log.read(chatId)\n\n const lastTurn = entries.findLast(({ event }) => Predicate.isTagged(event, \"TurnStarted\"))?.event\n const ours = Predicate.isTagged(lastTurn, \"TurnStarted\") && lastTurn.assistant === name\n\n if (ours && History.needsTurn(entries)) yield* withChat(name, chatId, (chat) => chat.resume)\n }\n })\n\n return AssistantEngine.of({\n register: Effect.fn(\"AssistantEngine.register\")(function* (registration: Turns.Registration) {\n registered.set(registration.definition.name, registration)\n // Chats this assistant was working on when the process stopped: pick them up again.\n yield* Effect.forkScoped(resumeAll(registration))\n }),\n submit: (assistant, chatId, input) => withChat(assistant, chatId, (chat) => chat.submitInBackground(input)),\n // The chat deletes itself, so a chat that's open stops; the entry goes, and the next open finds it deleted.\n delete: Effect.fn(\"AssistantEngine.delete\")(function* (assistant: string, chatId: ChatId) {\n yield* withChat(assistant, chatId, (chat) => chat.delete)\n yield* RcMap.invalidate(chats, new ChatKey({ assistant, chatId }))\n }),\n // The subscriber holds the chat open while it streams.\n subscribe: (assistant, chatId, after) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const chat = yield* RcMap.get(chats, new ChatKey({ assistant, chatId }))\n\n return chat.stream(after)\n })\n )\n })\n })\n)\n\ninterface EntityClient {\n readonly submit: (chatId: ChatId, input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) => Effect.Effect<void>\n readonly subscribe: (chatId: ChatId, after: number) => Stream.Stream<AssistantEvent.Entry>\n readonly delete: (chatId: ChatId) => Effect.Effect<void>\n}\n\nconst Submit = Rpc.make(\"Submit\", {\n payload: { input: Schema.Union([AssistantEvent.Input, AssistantEvent.StoredAppEvent]) }\n}).annotate(ClusterSchema.Persisted, true)\n\nconst Subscribe = Rpc.make(\"Subscribe\", {\n payload: { after: Schema.Int },\n success: AssistantEvent.Entry,\n stream: true\n})\n\nconst Delete = Rpc.make(\"Delete\")\n\nconst chatEntity = (assistant: string) => Entity.make(`Assistant/${assistant}`, [Submit, Subscribe, Delete])\n\ntype EngineClient = Omit<AssistantEngine[\"Service\"], \"register\">\n\n// Entity clients are made on first use, so a process that registers no assistant can still reach its chats.\nconst makeClusterClient = Effect.gen(function* () {\n const context = yield* Effect.context<Sharding.Sharding>()\n const clients = new Map<string, EntityClient>()\n\n const clientOf = Effect.fnUntraced(function* (assistant: string) {\n const known = clients.get(assistant)\n\n if (known !== undefined) return known\n\n const makeClient = yield* chatEntity(assistant).client.pipe(Effect.provide(context))\n\n const client: EntityClient = {\n submit: (chatId, input) => makeClient(chatId).Submit({ input }, { discard: true }).pipe(Effect.orDie),\n subscribe: (chatId, after) => makeClient(chatId).Subscribe({ after }).pipe(Stream.orDie),\n delete: (chatId) => makeClient(chatId).Delete().pipe(Effect.orDie)\n }\n\n clients.set(assistant, client)\n\n return client\n })\n\n const engine: EngineClient = {\n submit: Effect.fn(\"AssistantEngine.submit\")(function* (\n assistant: string,\n chatId: ChatId,\n input: AssistantEvent.Input | AssistantEvent.StoredAppEvent\n ) {\n const client = yield* clientOf(assistant)\n\n yield* client.submit(chatId, input)\n }),\n subscribe: (assistant, chatId, after) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* clientOf(assistant)\n\n return client.subscribe(chatId, after)\n })\n ),\n delete: Effect.fn(\"AssistantEngine.delete\")(function* (assistant: string, chatId: ChatId) {\n const client = yield* clientOf(assistant)\n\n yield* client.delete(chatId)\n })\n }\n\n return engine\n})\n\n/**\n * Layer that runs chats as cluster entities, one entity per chat, keyed by chat id.\n *\n * **Details**\n *\n * The layer requires `Sharding`. Use a `SingleRunner` for one process with SQL storage, a runner\n * of the app's cluster, or `TestRunner` in tests.\n *\n * **Example** (Running chats on a single runner with Postgres)\n *\n * ```ts\n * import * as NodeCrypto from \"@effect/platform-node/NodeCrypto\"\n * import * as PgClient from \"@effect/sql-pg/PgClient\"\n * import { Assistant, AssistantEngine, ChatLog } from \"effect-assistant\"\n * import type * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as SingleRunner from \"effect/cluster/SingleRunner\"\n * import * as Config from \"effect/Config\"\n * import * as Layer from \"effect/Layer\"\n *\n * declare const ModelLive: Layer.Layer<LanguageModel.LanguageModel>\n *\n * const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: \"Be brief.\" })).pipe(\n * Layer.provide(ModelLive),\n * Layer.provideMerge(AssistantEngine.layerCluster),\n * Layer.provideMerge(\n * Layer.merge(\n * ChatLog.layerSql,\n * SingleRunner.layer({ runnerStorage: \"sql\" }).pipe(Layer.provide(NodeCrypto.layer))\n * )\n * ),\n * Layer.provide(PgClient.layerConfig({ url: Config.Redacted(\"DATABASE_URL\") }))\n * )\n * ```\n *\n * @see {@link layerMemory} for running chats in one process\n * @see {@link layerClusterClient} for a process that only reaches chats the cluster runs\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layerCluster: Layer.Layer<AssistantEngine, never, ChatLog | Sharding.Sharding> = Layer.effect(\n AssistantEngine,\n Effect.gen(function* () {\n const context = yield* Effect.context<ChatLog | Sharding.Sharding>()\n const sharding = yield* Sharding.Sharding\n\n const register = Effect.fnUntraced(function* (registration: Turns.Registration) {\n const ChatEntity = chatEntity(registration.definition.name)\n\n const handlers = Effect.gen(function* () {\n const address = yield* Entity.CurrentAddress\n const chatId = yield* Schema.decodeUnknownEffect(ChatId)(address.entityId).pipe(Effect.orDie)\n // The entity's own: `Entity.keepAlive` needs its address and keep-alive latch, which is in there too.\n const entityContext = yield* Effect.context<Entity.CurrentAddress | Sharding.Sharding>()\n\n const keepAlive = (enabled: boolean) =>\n Entity.keepAlive(enabled).pipe(\n Effect.provide(entityContext),\n Effect.catchCause((cause) => Effect.logWarning(\"assistant chat keep-alive failed\", cause))\n )\n\n // A keep-alive left on from before a restart (by a run cut off) has done its job: it started the entity.\n yield* keepAlive(false)\n\n const chat = yield* Chat.make({ registration, chatId, keepAlive })\n yield* chat.resume\n\n return ChatEntity.of({\n // The turns run inside the request, so a crash leaves it unprocessed and the cluster redelivers it.\n Submit: (request) => chat.submit(request.payload.input),\n Subscribe: (request) => chat.stream(request.payload.after),\n Delete: () => chat.delete\n })\n })\n\n yield* sharding.registerEntity(ChatEntity, handlers.pipe(Effect.provide(context)), {\n maxIdleTime: \"5 minutes\",\n concurrency: \"unbounded\"\n })\n })\n\n const client = yield* makeClusterClient\n\n return AssistantEngine.of({\n register: (registration) => register(registration).pipe(Effect.provide(context)),\n ...client\n })\n })\n)\n\n/**\n * Layer that reaches chats running as cluster entities elsewhere, and runs none itself.\n *\n * **When to use**\n *\n * Use it in a process that only submits to, subscribes to, and deletes chats that the cluster's\n * runners run with {@link layerCluster}, such as a web server that provides an assistant's\n * `layerClient`. It needs no `ChatLog`, since the runners store the chats.\n *\n * **Details**\n *\n * The layer requires `Sharding`. An assistant's `toLayer` can't register with it: it dies, since\n * no chats run in this process.\n *\n * **Example** (A web server in front of the cluster)\n *\n * ```ts\n * import { Assistant, AssistantEngine } from \"effect-assistant\"\n * import type * as Sharding from \"effect/cluster/Sharding\"\n * import * as Layer from \"effect/Layer\"\n *\n * declare const ShardingLive: Layer.Layer<Sharding.Sharding>\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const MyAssistantClient = MyAssistant.layerClient.pipe(\n * Layer.provide(AssistantEngine.layerClusterClient),\n * Layer.provide(ShardingLive)\n * )\n * ```\n *\n * @see {@link layerCluster} for the runners that run the chats\n *\n * @category layers\n * @since 0.3.0\n */\nexport const layerClusterClient: Layer.Layer<AssistantEngine, never, Sharding.Sharding> = Layer.effect(\n AssistantEngine,\n Effect.gen(function* () {\n const client = yield* makeClusterClient\n\n return AssistantEngine.of({\n register: (registration) =>\n Effect.die(\n `The ${registration.definition.name} assistant can't register with AssistantEngine.layerClusterClient, which runs no chats: provide AssistantEngine.layerCluster where its toLayer runs.`\n ),\n ...client\n })\n })\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,kBAAb,cAAqC,QAAQ,QAsB3C,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC;AAEzC,IAAM,UAAN,cAAsB,KAAK,MAA+D,CAAC;AAE3F,MAAM,iBAAiB,cACrB,OAAO,IAAI,OAAO,UAAU,kDAAkD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmChF,MAAa,cAA4D,MAAM,OAC7E,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,OAAO,QAAiB;CAC/C,MAAM,MAAM,OAAO;CACnB,MAAM,6BAAa,IAAI,IAAgC;CAEvD,MAAM,OAAO,eAAe,MAAgC;CAG5D,MAAM,QAAyC,OAAO,MAAM,KAAK;EAC/D,QAAQ,OAAO,WAAW,WAAW,KAAc;GACjD,MAAM,eAAe,WAAW,IAAI,IAAI,SAAS;GAEjD,IAAI,iBAAiB,KAAA,GAAW,OAAO,OAAO,cAAc,IAAI,SAAS;GAEzE,OAAO,OAAOA,KAAU;IACtB;IACA,QAAQ,IAAI;IACZ,YAAY,YAAY,UAAU,KAAK,OAAO;GAChD,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,OAAO,CAAC;EACjC,CAAC;EACD,gBAAgB;CAClB,CAAC;CAGD,MAAM,YAAY,OAAO,WAAW,WAAW,KAAc,SAAkB;EAC7E,IAAI,SAAS;GACX,MAAM,QAAQ,OAAO,MAAM,KAAK;GAEhC,OAAO,OAAO,eAAe,MAAM,IAAI,OAAO,GAAG,GAAG,MAAM,OAAO,KAAK;GAEtE,OAAO,eAAe,IAAI,MAAM,KAAK,KAAK;EAC5C;EAEA,MAAM,QAAQ,eAAe,IAAI,MAAM,GAAG;EAE1C,eAAe,OAAO,MAAM,GAAG;EAE/B,IAAI,OAAO,OAAO,KAAK,GAAG,OAAO,MAAM,MAAM,MAAM,OAAO,KAAK,IAAI;CACrE,CAAC;CAED,MAAM,WAAW,OAAO,WAAW,WACjC,WACA,QACA,GACA;EAGA,OAAO,OAAO,EAAE,OAFI,MAAM,IAAI,OAAO,IAAI,QAAQ;GAAE;GAAW;EAAO,CAAC,CAAC,CAEnD;CACtB,GAAG,OAAO,MAAM;CAGhB,MAAM,YAAY,OAAO,WAAW,WAAW,cAAkC;EAC/E,MAAM,EAAE,SAAS,aAAa;EAE9B,KAAK,MAAM,UAAU,OAAO,IAAI,YAAY;GAC1C,MAAM,UAAU,OAAO,IAAI,KAAK,MAAM;GAEtC,MAAM,WAAW,QAAQ,UAAU,EAAE,YAAY,UAAU,SAAS,OAAO,aAAa,CAAC,CAAC,EAAE;GAG5F,IAFa,UAAU,SAAS,UAAU,aAAa,KAAK,SAAS,cAAc,QAEvEC,UAAkB,OAAO,GAAG,OAAO,SAAS,MAAM,SAAS,SAAS,KAAK,MAAM;EAC7F;CACF,CAAC;CAED,OAAO,gBAAgB,GAAG;EACxB,UAAU,OAAO,GAAG,0BAA0B,CAAC,CAAC,WAAW,cAAkC;GAC3F,WAAW,IAAI,aAAa,WAAW,MAAM,YAAY;GAEzD,OAAO,OAAO,WAAW,UAAU,YAAY,CAAC;EAClD,CAAC;EACD,SAAS,WAAW,QAAQ,UAAU,SAAS,WAAW,SAAS,SAAS,KAAK,mBAAmB,KAAK,CAAC;EAE1G,QAAQ,OAAO,GAAG,wBAAwB,CAAC,CAAC,WAAW,WAAmB,QAAgB;GACxF,OAAO,SAAS,WAAW,SAAS,SAAS,KAAK,MAAM;GACxD,OAAO,MAAM,WAAW,OAAO,IAAI,QAAQ;IAAE;IAAW;GAAO,CAAC,CAAC;EACnE,CAAC;EAED,YAAY,WAAW,QAAQ,UAC7B,OAAO,OACL,OAAO,IAAI,aAAa;GAGtB,QAAO,OAFa,MAAM,IAAI,OAAO,IAAI,QAAQ;IAAE;IAAW;GAAO,CAAC,CAAC,EAAA,CAE3D,OAAO,KAAK;EAC1B,CAAC,CACH;CACJ,CAAC;AACH,CAAC,CACH;AAQA,MAAM,SAAS,IAAI,KAAK,UAAU,EAChC,SAAS,EAAE,OAAO,OAAO,MAAM,CAACC,OAAsBC,cAA6B,CAAC,EAAE,EACxF,CAAC,CAAC,CAAC,SAAS,cAAc,WAAW,IAAI;AAEzC,MAAM,YAAY,IAAI,KAAK,aAAa;CACtC,SAAS,EAAE,OAAO,OAAO,IAAI;CAC7B,SAASC;CACT,QAAQ;AACV,CAAC;AAED,MAAM,SAAS,IAAI,KAAK,QAAQ;AAEhC,MAAM,cAAc,cAAsB,OAAO,KAAK,aAAa,aAAa;CAAC;CAAQ;CAAW;AAAM,CAAC;AAK3G,MAAM,oBAAoB,OAAO,IAAI,aAAa;CAChD,MAAM,UAAU,OAAO,OAAO,QAA2B;CACzD,MAAM,0BAAU,IAAI,IAA0B;CAE9C,MAAM,WAAW,OAAO,WAAW,WAAW,WAAmB;EAC/D,MAAM,QAAQ,QAAQ,IAAI,SAAS;EAEnC,IAAI,UAAU,KAAA,GAAW,OAAO;EAEhC,MAAM,aAAa,OAAO,WAAW,SAAS,CAAC,CAAC,OAAO,KAAK,OAAO,QAAQ,OAAO,CAAC;EAEnF,MAAM,SAAuB;GAC3B,SAAS,QAAQ,UAAU,WAAW,MAAM,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK;GACpG,YAAY,QAAQ,UAAU,WAAW,MAAM,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK;GACvF,SAAS,WAAW,WAAW,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO,KAAK;EACnE;EAEA,QAAQ,IAAI,WAAW,MAAM;EAE7B,OAAO;CACT,CAAC;CA2BD,OAAO;EAxBL,QAAQ,OAAO,GAAG,wBAAwB,CAAC,CAAC,WAC1C,WACA,QACA,OACA;GAGA,QAAO,OAFe,SAAS,SAAS,EAAA,CAE1B,OAAO,QAAQ,KAAK;EACpC,CAAC;EACD,YAAY,WAAW,QAAQ,UAC7B,OAAO,OACL,OAAO,IAAI,aAAa;GAGtB,QAAO,OAFe,SAAS,SAAS,EAAA,CAE1B,UAAU,QAAQ,KAAK;EACvC,CAAC,CACH;EACF,QAAQ,OAAO,GAAG,wBAAwB,CAAC,CAAC,WAAW,WAAmB,QAAgB;GAGxF,QAAO,OAFe,SAAS,SAAS,EAAA,CAE1B,OAAO,MAAM;EAC7B,CAAC;CAGS;AACd,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CD,MAAa,eAAiF,MAAM,OAClG,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,OAAO,QAAqC;CACnE,MAAM,WAAW,OAAO,SAAS;CAEjC,MAAM,WAAW,OAAO,WAAW,WAAW,cAAkC;EAC9E,MAAM,aAAa,WAAW,aAAa,WAAW,IAAI;EAE1D,MAAM,WAAW,OAAO,IAAI,aAAa;GACvC,MAAM,UAAU,OAAO,OAAO;GAC9B,MAAM,SAAS,OAAO,OAAO,oBAAoB,MAAM,CAAC,CAAC,QAAQ,QAAQ,CAAC,CAAC,KAAK,OAAO,KAAK;GAE5F,MAAM,gBAAgB,OAAO,OAAO,QAAmD;GAEvF,MAAM,aAAa,YACjB,OAAO,UAAU,OAAO,CAAC,CAAC,KACxB,OAAO,QAAQ,aAAa,GAC5B,OAAO,YAAY,UAAU,OAAO,WAAW,oCAAoC,KAAK,CAAC,CAC3F;GAGF,OAAO,UAAU,KAAK;GAEtB,MAAM,OAAO,OAAOJ,KAAU;IAAE;IAAc;IAAQ;GAAU,CAAC;GACjE,OAAO,KAAK;GAEZ,OAAO,WAAW,GAAG;IAEnB,SAAS,YAAY,KAAK,OAAO,QAAQ,QAAQ,KAAK;IACtD,YAAY,YAAY,KAAK,OAAO,QAAQ,QAAQ,KAAK;IACzD,cAAc,KAAK;GACrB,CAAC;EACH,CAAC;EAED,OAAO,SAAS,eAAe,YAAY,SAAS,KAAK,OAAO,QAAQ,OAAO,CAAC,GAAG;GACjF,aAAa;GACb,aAAa;EACf,CAAC;CACH,CAAC;CAED,MAAM,SAAS,OAAO;CAEtB,OAAO,gBAAgB,GAAG;EACxB,WAAW,iBAAiB,SAAS,YAAY,CAAC,CAAC,KAAK,OAAO,QAAQ,OAAO,CAAC;EAC/E,GAAG;CACL,CAAC;AACH,CAAC,CACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAa,qBAA6E,MAAM,OAC9F,iBACA,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO;CAEtB,OAAO,gBAAgB,GAAG;EACxB,WAAW,iBACT,OAAO,IACL,OAAO,aAAa,WAAW,KAAK,qJACtC;EACF,GAAG;CACL,CAAC;AACH,CAAC,CACH"}
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` sequence, 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.run({ 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.run({ 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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"}
@@ -282,7 +282,7 @@ declare const ModelOutput_base: Schema.Class<ModelOutput, Schema.TaggedStruct<"M
282
282
  * arrived while the model was working. A turn has handled what its
283
283
  * outputs had read.
284
284
  */
285
- readonly seenThroughSeq: Schema.optional<Schema.Int>;
285
+ readonly seenThroughSequence: Schema.optional<Schema.Int>;
286
286
  /** The model that wrote it, as the provider's id for it or the name the context gave it. */
287
287
  readonly model: Schema.optional<Schema.String>;
288
288
  /** The provider's id for the response, when it gives one. */
@@ -371,25 +371,25 @@ declare const TurnStarted_base: Schema.Class<TurnStarted, Schema.TaggedStruct<"T
371
371
  */
372
372
  export declare class TurnStarted extends TurnStarted_base {}
373
373
  declare const TurnCompleted_base: Schema.Class<TurnCompleted, Schema.TaggedStruct<"TurnCompleted", {
374
- readonly throughSeq: Schema.Int;
374
+ readonly throughSequence: Schema.Int;
375
375
  }>, {}>;
376
376
  /**
377
377
  * Represents a turn that finished.
378
378
  *
379
- * `throughSeq` is the last entry the turn read. Inputs after it still need a turn.
379
+ * `throughSequence` is the last entry the turn read. Inputs after it still need a turn.
380
380
  *
381
381
  * @category schemas
382
382
  * @since 0.1.0
383
383
  */
384
384
  export declare class TurnCompleted extends TurnCompleted_base {}
385
385
  declare const TurnFailed_base: Schema.Class<TurnFailed, Schema.TaggedStruct<"TurnFailed", {
386
- readonly throughSeq: Schema.Int;
386
+ readonly throughSequence: Schema.Int;
387
387
  readonly message: Schema.String;
388
388
  }>, {}>;
389
389
  /**
390
390
  * Represents a turn that failed.
391
391
  *
392
- * `throughSeq` covers everything stored when the turn failed, so a failing input isn't retried
392
+ * `throughSequence` covers everything stored when the turn failed, so a failing input isn't retried
393
393
  * forever.
394
394
  *
395
395
  * @category schemas
@@ -575,7 +575,7 @@ export declare const isAppInput: (event: {
575
575
  */
576
576
  export interface AnyEntry {
577
577
  readonly chatId: ChatId;
578
- readonly seq: number;
578
+ readonly sequence: number;
579
579
  readonly at: number;
580
580
  readonly event: {
581
581
  readonly _tag: string;
@@ -584,7 +584,7 @@ export interface AnyEntry {
584
584
  /**
585
585
  * Schema for a stored event with its position in the chat.
586
586
  *
587
- * `seq` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
587
+ * `sequence` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
588
588
  * milliseconds.
589
589
  *
590
590
  * @category schemas
@@ -592,7 +592,7 @@ export interface AnyEntry {
592
592
  */
593
593
  export declare const Entry: Schema.Struct<{
594
594
  readonly chatId: Schema.brand<Schema.TemplateLiteral<readonly ["chat:", Schema.String]>, "~effect-assistant/ChatId">;
595
- readonly seq: Schema.Int;
595
+ readonly sequence: Schema.Int;
596
596
  readonly at: Schema.Finite;
597
597
  readonly event: Schema.Union<readonly [Schema.Union<readonly [typeof UserMessage, typeof BlockAnswer, typeof AssistantMessage, typeof ModelOutput, typeof ToolStarted, typeof ToolResult, typeof TurnStarted, typeof TurnCompleted, typeof TurnFailed, typeof SpecialistStarted, typeof SpecialistFinished, typeof BlocksClosed, typeof ApprovalRequested, typeof ApprovalDecision, typeof ApprovalsExpired, typeof ChatDeleted]>, Schema.StructWithRest<Schema.Struct<{
598
598
  readonly _tag: Schema.brand<Schema.String, "~effect-assistant/AppTag">;
@@ -272,7 +272,7 @@ var ModelOutput = class extends Schema.TaggedClass()("ModelOutput", {
272
272
  * arrived while the model was working. A turn has handled what its
273
273
  * outputs had read.
274
274
  */
275
- seenThroughSeq: Schema.optional(Schema.Int),
275
+ seenThroughSequence: Schema.optional(Schema.Int),
276
276
  /** The model that wrote it, as the provider's id for it or the name the context gave it. */
277
277
  model: Schema.optional(Schema.String),
278
278
  /** The provider's id for the response, when it gives one. */
@@ -344,23 +344,23 @@ var TurnStarted = class extends Schema.TaggedClass()("TurnStarted", {
344
344
  /**
345
345
  * Represents a turn that finished.
346
346
  *
347
- * `throughSeq` is the last entry the turn read. Inputs after it still need a turn.
347
+ * `throughSequence` is the last entry the turn read. Inputs after it still need a turn.
348
348
  *
349
349
  * @category schemas
350
350
  * @since 0.1.0
351
351
  */
352
- var TurnCompleted = class extends Schema.TaggedClass()("TurnCompleted", { throughSeq: Schema.Int }) {};
352
+ var TurnCompleted = class extends Schema.TaggedClass()("TurnCompleted", { throughSequence: Schema.Int }) {};
353
353
  /**
354
354
  * Represents a turn that failed.
355
355
  *
356
- * `throughSeq` covers everything stored when the turn failed, so a failing input isn't retried
356
+ * `throughSequence` covers everything stored when the turn failed, so a failing input isn't retried
357
357
  * forever.
358
358
  *
359
359
  * @category schemas
360
360
  * @since 0.1.0
361
361
  */
362
362
  var TurnFailed = class extends Schema.TaggedClass()("TurnFailed", {
363
- throughSeq: Schema.Int,
363
+ throughSequence: Schema.Int,
364
364
  message: Schema.String
365
365
  }) {};
366
366
  /**
@@ -501,7 +501,7 @@ const isAppInput = (event) => !isLibraryEvent(event) && Predicate.hasProperty(ev
501
501
  /**
502
502
  * Schema for a stored event with its position in the chat.
503
503
  *
504
- * `seq` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
504
+ * `sequence` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
505
505
  * milliseconds.
506
506
  *
507
507
  * @category schemas
@@ -509,7 +509,7 @@ const isAppInput = (event) => !isLibraryEvent(event) && Predicate.hasProperty(ev
509
509
  */
510
510
  const Entry = Schema.Struct({
511
511
  chatId: ChatId,
512
- seq: Schema.Int,
512
+ sequence: Schema.Int,
513
513
  at: Schema.Finite,
514
514
  event: Schema.Union([AssistantEvent, StoredAppEvent])
515
515
  });