effect-assistant 0.0.0-stage → 0.1.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 (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -2
  3. package/dist/AppEvent.d.ts +111 -0
  4. package/dist/AppEvent.d.ts.map +1 -0
  5. package/dist/AppEvent.js +71 -0
  6. package/dist/AppEvent.js.map +1 -0
  7. package/dist/Assistant.d.ts +403 -0
  8. package/dist/Assistant.d.ts.map +1 -0
  9. package/dist/Assistant.js +126 -0
  10. package/dist/Assistant.js.map +1 -0
  11. package/dist/AssistantActivity.d.ts +85 -0
  12. package/dist/AssistantActivity.d.ts.map +1 -0
  13. package/dist/AssistantActivity.js +105 -0
  14. package/dist/AssistantActivity.js.map +1 -0
  15. package/dist/AssistantEngine.d.ts +116 -0
  16. package/dist/AssistantEngine.d.ts.map +1 -0
  17. package/dist/AssistantEngine.js +255 -0
  18. package/dist/AssistantEngine.js.map +1 -0
  19. package/dist/AssistantEvent.d.ts +614 -0
  20. package/dist/AssistantEvent.d.ts.map +1 -0
  21. package/dist/AssistantEvent.js +523 -0
  22. package/dist/AssistantEvent.js.map +1 -0
  23. package/dist/AssistantLoop.d.ts +171 -0
  24. package/dist/AssistantLoop.d.ts.map +1 -0
  25. package/dist/AssistantLoop.js +108 -0
  26. package/dist/AssistantLoop.js.map +1 -0
  27. package/dist/AssistantToolCall.d.ts +32 -0
  28. package/dist/AssistantToolCall.d.ts.map +1 -0
  29. package/dist/AssistantToolCall.js +37 -0
  30. package/dist/AssistantToolCall.js.map +1 -0
  31. package/dist/Block.d.ts +69 -0
  32. package/dist/Block.d.ts.map +1 -0
  33. package/dist/Block.js +39 -0
  34. package/dist/Block.js.map +1 -0
  35. package/dist/BlockKit.d.ts +184 -0
  36. package/dist/BlockKit.d.ts.map +1 -0
  37. package/dist/BlockKit.js +151 -0
  38. package/dist/BlockKit.js.map +1 -0
  39. package/dist/ChatId.d.ts +31 -0
  40. package/dist/ChatId.d.ts.map +1 -0
  41. package/dist/ChatId.js +36 -0
  42. package/dist/ChatId.js.map +1 -0
  43. package/dist/ChatLog.d.ts +109 -0
  44. package/dist/ChatLog.d.ts.map +1 -0
  45. package/dist/ChatLog.js +177 -0
  46. package/dist/ChatLog.js.map +1 -0
  47. package/dist/ChatView.d.ts +459 -0
  48. package/dist/ChatView.d.ts.map +1 -0
  49. package/dist/ChatView.js +496 -0
  50. package/dist/ChatView.js.map +1 -0
  51. package/dist/Chats.d.ts +110 -0
  52. package/dist/Chats.d.ts.map +1 -0
  53. package/dist/Chats.js +98 -0
  54. package/dist/Chats.js.map +1 -0
  55. package/dist/Compaction.d.ts +105 -0
  56. package/dist/Compaction.d.ts.map +1 -0
  57. package/dist/Compaction.js +133 -0
  58. package/dist/Compaction.js.map +1 -0
  59. package/dist/CurrentChat.d.ts +40 -0
  60. package/dist/CurrentChat.d.ts.map +1 -0
  61. package/dist/CurrentChat.js +33 -0
  62. package/dist/CurrentChat.js.map +1 -0
  63. package/dist/Decision.d.ts +145 -0
  64. package/dist/Decision.d.ts.map +1 -0
  65. package/dist/Decision.js +178 -0
  66. package/dist/Decision.js.map +1 -0
  67. package/dist/DecisionId.d.ts +37 -0
  68. package/dist/DecisionId.d.ts.map +1 -0
  69. package/dist/DecisionId.js +44 -0
  70. package/dist/DecisionId.js.map +1 -0
  71. package/dist/InputId.d.ts +38 -0
  72. package/dist/InputId.d.ts.map +1 -0
  73. package/dist/InputId.js +43 -0
  74. package/dist/InputId.js.map +1 -0
  75. package/dist/Instructions.d.ts +56 -0
  76. package/dist/Instructions.d.ts.map +1 -0
  77. package/dist/Instructions.js +50 -0
  78. package/dist/Instructions.js.map +1 -0
  79. package/dist/Memory.d.ts +190 -0
  80. package/dist/Memory.d.ts.map +1 -0
  81. package/dist/Memory.js +216 -0
  82. package/dist/Memory.js.map +1 -0
  83. package/dist/RunId.d.ts +41 -0
  84. package/dist/RunId.d.ts.map +1 -0
  85. package/dist/RunId.js +46 -0
  86. package/dist/RunId.js.map +1 -0
  87. package/dist/Skill.d.ts +72 -0
  88. package/dist/Skill.d.ts.map +1 -0
  89. package/dist/Skill.js +58 -0
  90. package/dist/Skill.js.map +1 -0
  91. package/dist/Specialist.d.ts +267 -0
  92. package/dist/Specialist.d.ts.map +1 -0
  93. package/dist/Specialist.js +119 -0
  94. package/dist/Specialist.js.map +1 -0
  95. package/dist/ToolDiscovery.d.ts +108 -0
  96. package/dist/ToolDiscovery.d.ts.map +1 -0
  97. package/dist/ToolDiscovery.js +139 -0
  98. package/dist/ToolDiscovery.js.map +1 -0
  99. package/dist/ToolFailure.d.ts +66 -0
  100. package/dist/ToolFailure.d.ts.map +1 -0
  101. package/dist/ToolFailure.js +79 -0
  102. package/dist/ToolFailure.js.map +1 -0
  103. package/dist/_virtual/_rolldown/runtime.js +13 -0
  104. package/dist/index.d.ts +26 -0
  105. package/dist/index.js +26 -0
  106. package/dist/internal/chat.js +202 -0
  107. package/dist/internal/chat.js.map +1 -0
  108. package/dist/internal/decisions.js +66 -0
  109. package/dist/internal/decisions.js.map +1 -0
  110. package/dist/internal/events.d.ts +46 -0
  111. package/dist/internal/events.d.ts.map +1 -0
  112. package/dist/internal/events.js +65 -0
  113. package/dist/internal/events.js.map +1 -0
  114. package/dist/internal/history.js +185 -0
  115. package/dist/internal/history.js.map +1 -0
  116. package/dist/internal/instructions.js +11 -0
  117. package/dist/internal/instructions.js.map +1 -0
  118. package/dist/internal/loop.d.ts +73 -0
  119. package/dist/internal/loop.d.ts.map +1 -0
  120. package/dist/internal/loop.js +166 -0
  121. package/dist/internal/loop.js.map +1 -0
  122. package/dist/internal/schema.js +14 -0
  123. package/dist/internal/schema.js.map +1 -0
  124. package/dist/internal/skills.js +28 -0
  125. package/dist/internal/skills.js.map +1 -0
  126. package/dist/internal/specialists.js +273 -0
  127. package/dist/internal/specialists.js.map +1 -0
  128. package/dist/internal/telemetry.js +85 -0
  129. package/dist/internal/telemetry.js.map +1 -0
  130. package/dist/internal/toolkit.d.ts +76 -0
  131. package/dist/internal/toolkit.d.ts.map +1 -0
  132. package/dist/internal/toolkit.js +129 -0
  133. package/dist/internal/toolkit.js.map +1 -0
  134. package/dist/internal/tools.js +132 -0
  135. package/dist/internal/tools.js.map +1 -0
  136. package/dist/internal/turns.d.ts +64 -0
  137. package/dist/internal/turns.d.ts.map +1 -0
  138. package/dist/internal/turns.js +221 -0
  139. package/dist/internal/turns.js.map +1 -0
  140. package/dist/internal/uuid.js +45 -0
  141. package/dist/internal/uuid.js.map +1 -0
  142. package/dist/internal/writer.d.ts +12 -0
  143. package/dist/internal/writer.d.ts.map +1 -0
  144. package/package.json +51 -3
  145. package/src/AppEvent.ts +134 -0
  146. package/src/Assistant.ts +589 -0
  147. package/src/AssistantActivity.ts +150 -0
  148. package/src/AssistantEngine.ts +343 -0
  149. package/src/AssistantEvent.ts +635 -0
  150. package/src/AssistantLoop.ts +207 -0
  151. package/src/AssistantToolCall.ts +40 -0
  152. package/src/Block.ts +84 -0
  153. package/src/BlockKit.ts +311 -0
  154. package/src/ChatId.ts +36 -0
  155. package/src/ChatLog.ts +247 -0
  156. package/src/ChatView.ts +658 -0
  157. package/src/Chats.ts +122 -0
  158. package/src/Compaction.ts +224 -0
  159. package/src/CurrentChat.ts +47 -0
  160. package/src/Decision.ts +241 -0
  161. package/src/DecisionId.ts +46 -0
  162. package/src/InputId.ts +43 -0
  163. package/src/Instructions.ts +72 -0
  164. package/src/Memory.ts +276 -0
  165. package/src/RunId.ts +46 -0
  166. package/src/Skill.ts +91 -0
  167. package/src/Specialist.ts +355 -0
  168. package/src/ToolDiscovery.ts +187 -0
  169. package/src/ToolFailure.ts +74 -0
  170. package/src/index.ts +130 -0
  171. package/src/internal/chat.ts +372 -0
  172. package/src/internal/decisions.ts +131 -0
  173. package/src/internal/events.ts +100 -0
  174. package/src/internal/history.ts +366 -0
  175. package/src/internal/instructions.ts +9 -0
  176. package/src/internal/loop.ts +344 -0
  177. package/src/internal/schema.ts +25 -0
  178. package/src/internal/skills.ts +37 -0
  179. package/src/internal/specialists.ts +414 -0
  180. package/src/internal/telemetry.ts +125 -0
  181. package/src/internal/toolkit.ts +231 -0
  182. package/src/internal/tools.ts +210 -0
  183. package/src/internal/turns.ts +429 -0
  184. package/src/internal/uuid.ts +50 -0
  185. package/src/internal/writer.ts +12 -0
@@ -0,0 +1,184 @@
1
+ import { AnyEntry, AssistantEvent_d_exports, BlockAnswer } from "./AssistantEvent.js";
2
+ import { Any, Block_d_exports } from "./Block.js";
3
+ import { AssistantMessage, ChatView_d_exports, PendingDecision } from "./ChatView.js";
4
+ import { CurrentChat } from "./CurrentChat.js";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Schema from "effect/Schema";
7
+ declare namespace BlockKit_d_exports {
8
+ export { BlockKit, RenderArgs, RenderOptions, Renderers, State, apply, closeStale, fromEntries, make, staleBlocks, toClient };
9
+ }
10
+ /**
11
+ * Represents a catalog of blocks, with a lookup by kind.
12
+ *
13
+ * @category models
14
+ * @since 0.1.0
15
+ */
16
+ export interface BlockKit<Blocks extends Any = Any> {
17
+ readonly blocks: ReadonlyArray<Blocks>;
18
+ readonly byKind: ReadonlyMap<string, Blocks>;
19
+ }
20
+ /**
21
+ * Creates a kit from blocks.
22
+ *
23
+ * @category constructors
24
+ * @since 0.1.0
25
+ */
26
+ export declare const make: <const Blocks extends ReadonlyArray<Any>>(blocks: Blocks) => BlockKit<Blocks[number]>;
27
+ type KindOf<B> = B extends {
28
+ readonly kind: infer K extends string;
29
+ } ? K : never;
30
+ type BlockOf<Kit extends BlockKit, K> = Extract<Kit["blocks"][number], {
31
+ readonly kind: K;
32
+ }>;
33
+ type PropsOf<B> = B extends {
34
+ readonly props: Schema.Top;
35
+ } ? B["props"]["Type"] : never;
36
+ type AnswerOf<B> = B extends {
37
+ readonly answer: infer A;
38
+ } ? (A extends Schema.Top ? A["Type"] : never) : never;
39
+ /**
40
+ * Represents the state of a message's block.
41
+ *
42
+ * @category models
43
+ * @since 0.1.0
44
+ */
45
+ export interface State {
46
+ readonly open: boolean;
47
+ /** The user's answer, if they used the block. */
48
+ readonly answer: BlockAnswer | undefined;
49
+ }
50
+ /**
51
+ * Returns the block states after one more event.
52
+ *
53
+ * @category utils
54
+ * @since 0.1.0
55
+ */
56
+ export declare const apply: (states: ReadonlyMap<string, State>, event: {
57
+ readonly _tag: string;
58
+ }) => ReadonlyMap<string, State>;
59
+ /**
60
+ * Returns the state of every message's block, by message id.
61
+ *
62
+ * @category utils
63
+ * @since 0.1.0
64
+ */
65
+ export declare const fromEntries: (entries: ReadonlyArray<AnyEntry>) => ReadonlyMap<string, State>;
66
+ /**
67
+ * Returns the ids of the messages whose blocks are still open after the user typed the given
68
+ * number of messages without using them.
69
+ *
70
+ * **Details**
71
+ *
72
+ * These are the blocks {@link closeStale} closes. Decision blocks never go stale, since work waits
73
+ * on them and closing one would leave it waiting.
74
+ *
75
+ * @category utils
76
+ * @since 0.1.0
77
+ */
78
+ export declare const staleBlocks: (entries: ReadonlyArray<AnyEntry>, options: {
79
+ readonly messages: number;
80
+ }) => ReadonlyArray<string>;
81
+ /**
82
+ * Closes the blocks the user moved on from, which are still open after the given number of typed
83
+ * messages.
84
+ *
85
+ * **Details**
86
+ *
87
+ * Run it from a turn, before or after its loop. It records a `BlocksClosed` event, so every
88
+ * client folds the blocks closed.
89
+ *
90
+ * @see {@link staleBlocks} for the blocks it closes
91
+ *
92
+ * @category utils
93
+ * @since 0.1.0
94
+ */
95
+ export declare const closeStale: (options: {
96
+ readonly messages: number;
97
+ }) => Effect.Effect<void, never, CurrentChat>;
98
+ /**
99
+ * Represents what a renderer receives for one block.
100
+ *
101
+ * @category models
102
+ * @since 0.1.0
103
+ */
104
+ export interface RenderArgs<B> {
105
+ readonly text: string;
106
+ readonly props: PropsOf<B>;
107
+ /**
108
+ * Whether the block is open. It is `false` once the block is answered, with the block or in
109
+ * words; show it read-only then.
110
+ */
111
+ readonly open: boolean;
112
+ /** The user's answer, if they used the block. */
113
+ readonly answer: AnswerOf<B> | undefined;
114
+ /**
115
+ * For the decision block, the call each decision is for (its tool and params) and how it was
116
+ * decided. Empty for other blocks.
117
+ */
118
+ readonly decisions: ReadonlyArray<PendingDecision>;
119
+ /** Sends an answer. Only call it while `open`. */
120
+ readonly respond: (answer: AnswerOf<B>) => void;
121
+ }
122
+ /**
123
+ * Represents a renderer for every kind in a kit.
124
+ *
125
+ * @category models
126
+ * @since 0.1.0
127
+ */
128
+ export type Renderers<Kit extends BlockKit, Out> = { readonly [K in KindOf<Kit["blocks"][number]>]: (args: RenderArgs<BlockOf<Kit, K>>) => Out; };
129
+ /**
130
+ * Represents the options for rendering one message's block.
131
+ *
132
+ * @category models
133
+ * @since 0.1.0
134
+ */
135
+ export interface RenderOptions {
136
+ /** Submits an answer, for example over RPC. */
137
+ readonly respond: (answer: BlockAnswer) => void;
138
+ }
139
+ /**
140
+ * Creates a client-side renderer for the blocks on a chat view's messages
141
+ * (`ChatView.AssistantMessage`).
142
+ *
143
+ * **Details**
144
+ *
145
+ * The renderers are type-checked, so every kind in the kit needs one, and each receives typed
146
+ * `props` and `answer`. A renderer's `respond` builds a `BlockAnswer` input, with the answer
147
+ * encoded by the block's answer schema, and passes it to the `respond` option to submit.
148
+ *
149
+ * The returned function returns `undefined` for a message without a block, or with a block it
150
+ * can't decode or has no renderer for.
151
+ *
152
+ * **Example** (Rendering blocks with React)
153
+ *
154
+ * ```tsx
155
+ * import { BlockKit, Decision } from "effect-assistant"
156
+ * import type { ReactNode } from "react"
157
+ *
158
+ * const Blocks = BlockKit.make([PickOne, Decision.block])
159
+ *
160
+ * const renderBlock = BlockKit.toClient<typeof Blocks, ReactNode>(Blocks, {
161
+ * pick_one: ({ props, open, respond }) =>
162
+ * props.options.map((option) => (
163
+ * <button key={option} disabled={!open} onClick={() => respond({ picked: option })}>
164
+ * {option}
165
+ * </button>
166
+ * )),
167
+ * decision: ({ props, open, respond }) => (
168
+ * <button disabled={!open} onClick={() => respond(Decision.answerAll(props, "approved"))}>
169
+ * {props.approveLabel ?? "Approve"}
170
+ * </button>
171
+ * )
172
+ * })
173
+ *
174
+ * // For each AssistantMessage in the chat's view:
175
+ * renderBlock(message, { respond: (answer) => submit(answer) })
176
+ * ```
177
+ *
178
+ * @category constructors
179
+ * @since 0.1.0
180
+ */
181
+ export declare function toClient<Kit extends BlockKit, Out>(kit: Kit, renderers: Renderers<Kit, Out>): (message: AssistantMessage, options: RenderOptions) => Out | undefined;
182
+ //#endregion
183
+ export { BlockKit_d_exports };
184
+ //# sourceMappingURL=BlockKit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockKit.d.ts","names":[],"sources":["../src/BlockKit.ts"],"mappings":";;;;;;;;;;;;;;;iBA2BiB,SAAS,eAAe,MAAY;WAC1C,QAAQ,cAAc;WACtB,QAAQ,oBAAoB;;;;;;;;qBAS1B,aAAc,eAAe,cAAc,MAAU,QAAU,WAAS,SAAS;KAKzF,OAAO,KAAK;WAAqB,YAAY;IAAqB;KAElE,QAAQ,YAAY,UAAU,KAAK,QAAQ;WAAkC,MAAM;;KAEnF,QAAQ,KAAK;WAAqB,OAAO,OAAO;IAAQ;KAExD,SAAS,KAAK;WAAqB,cAAc;KAAO,UAAU,OAAO,MAAM;;;;;;;iBAQnE;WACN;;WAEA,QAAQ;;;;;;;;qBASN,QAAK,QACR,oBAAoB,QAAM;WAChB;MACjB,oBAAoB;;;;;;;qBAkCV,cAAW,SAAa,cAAc,cAA2B,oBAAoB;;;;;;;;;;;;;qBAoBrF,cAAW,SACb,cAAc,WAAwB;WAC3B;MACnB;;;;;;;;;;;;;;;qBAuCU,aAAU;WAAsD;MAK3E,OAAA,oBAAA;;;;;;;iBAQe,WAAW;WACjB;WACA,OAAO,QAAQ;;;;;WAKf;;WAEA,QAAQ,SAAS;;;;;WAKjB,WAAW,cAAc;;WAEzB,UAAU,QAAQ,SAAS;;;;;;;;YAS1B,UAAU,YAAY,UAAU,mBAChC,KAAK,OAAO,0BAA0B,MAAM,WAAW,QAAQ,KAAK,QAAQ;;;;;;;iBASvE;;WAEN,UAAU,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA6Cb,SAAS,YAAY,UAAU,KAC7C,KAAK,KACL,WAAW,UAAU,KAAK,QACxB,SAAS,kBAA2B,SAAS,kBAAkB"}
@@ -0,0 +1,151 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import { fromKey } from "./InputId.js";
3
+ import { BlockAnswer, BlocksClosed, isLibraryEvent } from "./AssistantEvent.js";
4
+ import { CurrentChat } from "./CurrentChat.js";
5
+ import "./Decision.js";
6
+ import * as Predicate from "effect/Predicate";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Schema from "effect/Schema";
9
+ import * as Option from "effect/Option";
10
+ //#region src/BlockKit.ts
11
+ /**
12
+ * Groups the blocks an assistant may put in its messages, and renders them on the client.
13
+ *
14
+ * A kit is created with {@link make} and passed to `Assistant.make`. {@link fromEntries} computes
15
+ * the state of every message's block from a chat's log, {@link closeStale} closes the blocks the
16
+ * user moved on from, and {@link toClient} creates a client-side renderer with one function per
17
+ * block kind.
18
+ *
19
+ * @since 0.1.0
20
+ */
21
+ var BlockKit_exports = /* @__PURE__ */ __exportAll({
22
+ apply: () => apply,
23
+ closeStale: () => closeStale,
24
+ fromEntries: () => fromEntries,
25
+ make: () => make,
26
+ staleBlocks: () => staleBlocks,
27
+ toClient: () => toClient
28
+ });
29
+ /**
30
+ * Creates a kit from blocks.
31
+ *
32
+ * @category constructors
33
+ * @since 0.1.0
34
+ */
35
+ const make = (blocks) => ({
36
+ blocks,
37
+ byKind: new Map(blocks.map((block) => [block.kind, block]))
38
+ });
39
+ /**
40
+ * Returns the block states after one more event.
41
+ *
42
+ * @category utils
43
+ * @since 0.1.0
44
+ */
45
+ const apply = (states, event) => {
46
+ if (!isLibraryEvent(event)) return states;
47
+ const close = (ids) => {
48
+ const open = ids.filter((id) => states.get(id)?.open === true);
49
+ if (open.length === 0) return states;
50
+ const next = new Map(states);
51
+ for (const id of open) next.set(id, {
52
+ open: false,
53
+ answer: void 0
54
+ });
55
+ return next;
56
+ };
57
+ if (Predicate.isTagged(event, "BlocksClosed")) return close(event.messageIds);
58
+ if (Predicate.isTagged(event, "AssistantMessage")) {
59
+ const closed = close(event.closes ?? []);
60
+ return event.block === void 0 ? closed : new Map(closed).set(event.id, {
61
+ open: true,
62
+ answer: void 0
63
+ });
64
+ }
65
+ if (!Predicate.isTagged(event, "BlockAnswer") || states.get(event.messageId)?.open !== true) return states;
66
+ return new Map(states).set(event.messageId, {
67
+ open: false,
68
+ answer: event
69
+ });
70
+ };
71
+ /**
72
+ * Returns the state of every message's block, by message id.
73
+ *
74
+ * @category utils
75
+ * @since 0.1.0
76
+ */
77
+ const fromEntries = (entries) => {
78
+ let states = /* @__PURE__ */ new Map();
79
+ for (const entry of entries) states = apply(states, entry.event);
80
+ return states;
81
+ };
82
+ /**
83
+ * Returns the ids of the messages whose blocks are still open after the user typed the given
84
+ * number of messages without using them.
85
+ *
86
+ * **Details**
87
+ *
88
+ * These are the blocks {@link closeStale} closes. Decision blocks never go stale, since work waits
89
+ * on them and closing one would leave it waiting.
90
+ *
91
+ * @category utils
92
+ * @since 0.1.0
93
+ */
94
+ const staleBlocks = (entries, options) => {
95
+ return [...fromEntries(entries)].flatMap(([id, state]) => state.open ? [id] : []).filter((id) => {
96
+ const shown = entries.findIndex(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") && event.id === id);
97
+ const message = entries[shown]?.event;
98
+ if (message === void 0 || isLibraryEvent(message) && Predicate.isTagged(message, "AssistantMessage") && message.block?.kind === "decision") return false;
99
+ return entries.slice(shown + 1).filter(({ event }) => Predicate.isTagged(event, "UserMessage")).length >= options.messages;
100
+ });
101
+ };
102
+ /**
103
+ * Closes the blocks the user moved on from, which are still open after the given number of typed
104
+ * messages.
105
+ *
106
+ * **Details**
107
+ *
108
+ * Run it from a turn, before or after its loop. It records a `BlocksClosed` event, so every
109
+ * client folds the blocks closed.
110
+ *
111
+ * @see {@link staleBlocks} for the blocks it closes
112
+ *
113
+ * @category utils
114
+ * @since 0.1.0
115
+ */
116
+ const closeStale = Effect.fn(function* (options) {
117
+ const chat = yield* CurrentChat;
118
+ const stale = staleBlocks(yield* chat.entries, options);
119
+ if (stale.length > 0) yield* chat.append(BlocksClosed.make({ messageIds: stale }));
120
+ });
121
+ function toClient(kit, renderers) {
122
+ return (message, options) => {
123
+ if (message.block === void 0) return void 0;
124
+ const { kind } = message.block;
125
+ const block = kit.byKind.get(kind);
126
+ const render = renderers[kind];
127
+ if (block === void 0 || render === void 0) return void 0;
128
+ const props = Schema.decodeUnknownOption(block.props)(message.block.props);
129
+ if (Option.isNone(props)) return void 0;
130
+ const given = message.block.answer;
131
+ const answerCodec = block.answer === void 0 ? void 0 : Schema.toCodecJson(block.answer);
132
+ const answer = given === void 0 || answerCodec === void 0 ? Option.none() : Schema.decodeUnknownOption(answerCodec)(given);
133
+ return render({
134
+ text: message.text,
135
+ props: props.value,
136
+ open: message.block.status === "open",
137
+ answer: Option.getOrUndefined(answer),
138
+ decisions: message.block.decisions ?? [],
139
+ respond: (value) => options.respond(BlockAnswer.make({
140
+ id: fromKey(`${message.id}:answer`),
141
+ messageId: message.id,
142
+ kind,
143
+ value: answerCodec === void 0 ? value : Schema.encodeUnknownSync(answerCodec)(value)
144
+ }))
145
+ });
146
+ };
147
+ }
148
+ //#endregion
149
+ export { BlockKit_exports, apply, closeStale, fromEntries, make, staleBlocks, toClient };
150
+
151
+ //# sourceMappingURL=BlockKit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockKit.js","names":["AssistantEvent.isLibraryEvent","InputId.fromKey"],"sources":["../src/BlockKit.ts"],"sourcesContent":["/**\n * Groups the blocks an assistant may put in its messages, and renders them on the client.\n *\n * A kit is created with {@link make} and passed to `Assistant.make`. {@link fromEntries} computes\n * the state of every message's block from a chat's log, {@link closeStale} closes the blocks the\n * user moved on from, and {@link toClient} creates a client-side renderer with one function per\n * block kind.\n *\n * @since 0.1.0\n */\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport type * as Block from \"./Block.ts\"\nimport type * as ChatView from \"./ChatView.ts\"\nimport { CurrentChat } from \"./CurrentChat.ts\"\nimport * as Decision from \"./Decision.ts\"\nimport * as InputId from \"./InputId.ts\"\n\n/**\n * Represents a catalog of blocks, with a lookup by kind.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface BlockKit<Blocks extends Block.Any = Block.Any> {\n readonly blocks: ReadonlyArray<Blocks>\n readonly byKind: ReadonlyMap<string, Blocks>\n}\n\n/**\n * Creates a kit from blocks.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <const Blocks extends ReadonlyArray<Block.Any>>(blocks: Blocks): BlockKit<Blocks[number]> => ({\n blocks,\n byKind: new Map(blocks.map((block) => [block.kind, block]))\n})\n\ntype KindOf<B> = B extends { readonly kind: infer K extends string } ? K : never\n\ntype BlockOf<Kit extends BlockKit, K> = Extract<Kit[\"blocks\"][number], { readonly kind: K }>\n\ntype PropsOf<B> = B extends { readonly props: Schema.Top } ? B[\"props\"][\"Type\"] : never\n\ntype AnswerOf<B> = B extends { readonly answer: infer A } ? (A extends Schema.Top ? A[\"Type\"] : never) : never\n\n/**\n * Represents the state of a message's block.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface State {\n readonly open: boolean\n /** The user's answer, if they used the block. */\n readonly answer: AssistantEvent.BlockAnswer | undefined\n}\n\n/**\n * Returns the block states after one more event.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const apply = (\n states: ReadonlyMap<string, State>,\n event: { readonly _tag: string }\n): ReadonlyMap<string, State> => {\n if (!AssistantEvent.isLibraryEvent(event)) return states\n\n const close = (ids: ReadonlyArray<string>) => {\n const open = ids.filter((id) => states.get(id)?.open === true)\n\n if (open.length === 0) return states\n\n const next = new Map(states)\n\n for (const id of open) next.set(id, { open: false, answer: undefined })\n\n return next\n }\n\n if (Predicate.isTagged(event, \"BlocksClosed\")) return close(event.messageIds)\n\n if (Predicate.isTagged(event, \"AssistantMessage\")) {\n const closed = close(event.closes ?? [])\n\n return event.block === undefined ? closed : new Map(closed).set(event.id, { open: true, answer: undefined })\n }\n\n if (!Predicate.isTagged(event, \"BlockAnswer\") || states.get(event.messageId)?.open !== true) return states\n\n return new Map(states).set(event.messageId, { open: false, answer: event })\n}\n\n/**\n * Returns the state of every message's block, by message id.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlyMap<string, State> => {\n let states: ReadonlyMap<string, State> = new Map()\n\n for (const entry of entries) states = apply(states, entry.event)\n\n return states\n}\n\n/**\n * Returns the ids of the messages whose blocks are still open after the user typed the given\n * number of messages without using them.\n *\n * **Details**\n *\n * These are the blocks {@link closeStale} closes. Decision blocks never go stale, since work waits\n * on them and closing one would leave it waiting.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const staleBlocks = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n options: { readonly messages: number }\n): ReadonlyArray<string> => {\n const open = [...fromEntries(entries)].flatMap(([id, state]) => (state.open ? [id] : []))\n\n return open.filter((id) => {\n const shown = entries.findIndex(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === id\n )\n\n const message = entries[shown]?.event\n\n if (\n message === undefined ||\n (AssistantEvent.isLibraryEvent(message) &&\n Predicate.isTagged(message, \"AssistantMessage\") &&\n message.block?.kind === Decision.kind)\n )\n return false\n\n const typed = entries.slice(shown + 1).filter(({ event }) => Predicate.isTagged(event, \"UserMessage\")).length\n\n return typed >= options.messages\n })\n}\n\n/**\n * Closes the blocks the user moved on from, which are still open after the given number of typed\n * messages.\n *\n * **Details**\n *\n * Run it from a turn, before or after its loop. It records a `BlocksClosed` event, so every\n * client folds the blocks closed.\n *\n * @see {@link staleBlocks} for the blocks it closes\n *\n * @category utils\n * @since 0.1.0\n */\nexport const closeStale = Effect.fn(function* (options: { readonly messages: number }) {\n const chat = yield* CurrentChat\n const stale = staleBlocks(yield* chat.entries, options)\n\n if (stale.length > 0) yield* chat.append(AssistantEvent.BlocksClosed.make({ messageIds: stale }))\n})\n\n/**\n * Represents what a renderer receives for one block.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RenderArgs<B> {\n readonly text: string\n readonly props: PropsOf<B>\n /**\n * Whether the block is open. It is `false` once the block is answered, with the block or in\n * words; show it read-only then.\n */\n readonly open: boolean\n /** The user's answer, if they used the block. */\n readonly answer: AnswerOf<B> | undefined\n /**\n * For the decision block, the call each decision is for (its tool and params) and how it was\n * decided. Empty for other blocks.\n */\n readonly decisions: ReadonlyArray<ChatView.PendingDecision>\n /** Sends an answer. Only call it while `open`. */\n readonly respond: (answer: AnswerOf<B>) => void\n}\n\n/**\n * Represents a renderer for every kind in a kit.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Renderers<Kit extends BlockKit, Out> = {\n readonly [K in KindOf<Kit[\"blocks\"][number]>]: (args: RenderArgs<BlockOf<Kit, K>>) => Out\n}\n\n/**\n * Represents the options for rendering one message's block.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RenderOptions {\n /** Submits an answer, for example over RPC. */\n readonly respond: (answer: AssistantEvent.BlockAnswer) => void\n}\n\n/**\n * Creates a client-side renderer for the blocks on a chat view's messages\n * (`ChatView.AssistantMessage`).\n *\n * **Details**\n *\n * The renderers are type-checked, so every kind in the kit needs one, and each receives typed\n * `props` and `answer`. A renderer's `respond` builds a `BlockAnswer` input, with the answer\n * encoded by the block's answer schema, and passes it to the `respond` option to submit.\n *\n * The returned function returns `undefined` for a message without a block, or with a block it\n * can't decode or has no renderer for.\n *\n * **Example** (Rendering blocks with React)\n *\n * ```tsx\n * import { BlockKit, Decision } from \"effect-assistant\"\n * import type { ReactNode } from \"react\"\n *\n * const Blocks = BlockKit.make([PickOne, Decision.block])\n *\n * const renderBlock = BlockKit.toClient<typeof Blocks, ReactNode>(Blocks, {\n * pick_one: ({ props, open, respond }) =>\n * props.options.map((option) => (\n * <button key={option} disabled={!open} onClick={() => respond({ picked: option })}>\n * {option}\n * </button>\n * )),\n * decision: ({ props, open, respond }) => (\n * <button disabled={!open} onClick={() => respond(Decision.answerAll(props, \"approved\"))}>\n * {props.approveLabel ?? \"Approve\"}\n * </button>\n * )\n * })\n *\n * // For each AssistantMessage in the chat's view:\n * renderBlock(message, { respond: (answer) => submit(answer) })\n * ```\n *\n * @category constructors\n * @since 0.1.0\n */\nexport function toClient<Kit extends BlockKit, Out>(\n kit: Kit,\n renderers: Renderers<Kit, Out>\n): (message: ChatView.AssistantMessage, options: RenderOptions) => Out | undefined\n// The overload above checks that every kind has a renderer; here each one takes any block's\n// arguments.\nexport function toClient<Out>(\n kit: BlockKit,\n renderers: Readonly<Record<string, (args: RenderArgs<Block.Any>) => Out>>\n): (message: ChatView.AssistantMessage, options: RenderOptions) => Out | undefined {\n return (message, options) => {\n if (message.block === undefined) return undefined\n\n const { kind } = message.block\n const block = kit.byKind.get(kind)\n const render = renderers[kind]\n\n if (block === undefined || render === undefined) return undefined\n\n const props = Schema.decodeUnknownOption(block.props)(message.block.props)\n\n if (Option.isNone(props)) return undefined\n\n const given = message.block.answer\n const answerCodec = block.answer === undefined ? undefined : Schema.toCodecJson(block.answer)\n\n const answer =\n given === undefined || answerCodec === undefined ? Option.none() : Schema.decodeUnknownOption(answerCodec)(given)\n\n return render({\n text: message.text,\n props: props.value,\n open: message.block.status === \"open\",\n answer: Option.getOrUndefined(answer),\n decisions: message.block.decisions ?? [],\n respond: (value) =>\n options.respond(\n AssistantEvent.BlockAnswer.make({\n // A block takes one answer: the same id every time, so a second click is stored once.\n id: InputId.fromKey(`${message.id}:answer`),\n messageId: message.id,\n kind,\n value: answerCodec === undefined ? value : Schema.encodeUnknownSync(answerCodec)(value)\n })\n )\n })\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ declare namespace ChatId_d_exports {
4
+ export { ChatId, make };
5
+ }
6
+ /**
7
+ * Schema for chat ids of the form `` `chat:${uuid}` ``. The schema is branded, so a run id or a
8
+ * plain string can't be passed where a chat id is required.
9
+ *
10
+ * @category schemas
11
+ * @since 0.1.0
12
+ */
13
+ export declare const ChatId: Schema.brand<Schema.TemplateLiteral<readonly ["chat:", Schema.String]>, "~effect-assistant/ChatId">;
14
+ /**
15
+ * Represents a chat id.
16
+ *
17
+ * @category models
18
+ * @since 0.1.0
19
+ */
20
+ export type ChatId = typeof ChatId.Type;
21
+ /**
22
+ * Creates a new random chat id using Effect's `Random`. Seed `Random` in tests to get the same ids
23
+ * on every run.
24
+ *
25
+ * @category constructors
26
+ * @since 0.1.0
27
+ */
28
+ export declare const make: () => Effect.Effect<ChatId>;
29
+ //#endregion
30
+ export { ChatId_d_exports };
31
+ //# sourceMappingURL=ChatId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatId.d.ts","names":[],"sources":["../src/ChatId.ts"],"mappings":";;;;;;;;;;;;qBAkBa,QAAM,OAAA,MAAA,OAAA,mCAAA,OAAA;;;;;;;YAQP,gBAAgB,OAAO;;;;;;;;qBAStB,YAAW,OAAO,OAAO"}
package/dist/ChatId.js ADDED
@@ -0,0 +1,36 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import { Uuid, make as make$1 } from "./internal/uuid.js";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Schema from "effect/Schema";
5
+ //#region src/ChatId.ts
6
+ /**
7
+ * This module provides the id of a chat, which is `chat:` followed by a UUID.
8
+ *
9
+ * A chat id keys everything about a chat: its log, its cluster entity, and its trace session.
10
+ *
11
+ * @since 0.1.0
12
+ */
13
+ var ChatId_exports = /* @__PURE__ */ __exportAll({
14
+ ChatId: () => ChatId,
15
+ make: () => make
16
+ });
17
+ /**
18
+ * Schema for chat ids of the form `` `chat:${uuid}` ``. The schema is branded, so a run id or a
19
+ * plain string can't be passed where a chat id is required.
20
+ *
21
+ * @category schemas
22
+ * @since 0.1.0
23
+ */
24
+ const ChatId = Schema.TemplateLiteral(["chat:", Uuid]).pipe(Schema.brand("~effect-assistant/ChatId"));
25
+ /**
26
+ * Creates a new random chat id using Effect's `Random`. Seed `Random` in tests to get the same ids
27
+ * on every run.
28
+ *
29
+ * @category constructors
30
+ * @since 0.1.0
31
+ */
32
+ const make = () => Effect.map(make$1, (uuid) => ChatId.make(`chat:${uuid}`));
33
+ //#endregion
34
+ export { ChatId, ChatId_exports, make };
35
+
36
+ //# sourceMappingURL=ChatId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatId.js","names":["Uuid.Uuid","Uuid.make"],"sources":["../src/ChatId.ts"],"sourcesContent":["/**\n * This module provides the id of a chat, which is `chat:` followed by a UUID.\n *\n * A chat id keys everything about a chat: its log, its cluster entity, and its trace session.\n *\n * @since 0.1.0\n */\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport * as Uuid from \"./internal/uuid.ts\"\n\n/**\n * Schema for chat ids of the form `` `chat:${uuid}` ``. The schema is branded, so a run id or a\n * plain string can't be passed where a chat id is required.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ChatId = Schema.TemplateLiteral([\"chat:\", Uuid.Uuid]).pipe(Schema.brand(\"~effect-assistant/ChatId\"))\n\n/**\n * Represents a chat id.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ChatId = typeof ChatId.Type\n\n/**\n * Creates a new random chat id using Effect's `Random`. Seed `Random` in tests to get the same ids\n * on every run.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = (): Effect.Effect<ChatId> => Effect.map(Uuid.make, (uuid) => ChatId.make(`chat:${uuid}`))\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkBA,MAAa,SAAS,OAAO,gBAAgB,CAAC,SAASA,IAAS,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM,0BAA0B,CAAC;;;;;;;;AAiBhH,MAAa,aAAoC,OAAO,IAAIC,SAAY,SAAS,OAAO,KAAK,QAAQ,MAAM,CAAC"}
@@ -0,0 +1,109 @@
1
+ import { ChatId } from "./ChatId.js";
2
+ import { AssistantEvent, AssistantEvent_d_exports, Entry, StoredAppEvent } from "./AssistantEvent.js";
3
+ import * as Context from "effect/Context";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+ import * as SqlClient from "effect/sql/SqlClient";
7
+ declare namespace ChatLog_d_exports {
8
+ export { ChatLog, ReadOptions, Storage, layerMemory, layerSql, make };
9
+ }
10
+ declare const ChatLog_base: Context.ServiceClass<ChatLog, "effect-assistant/ChatLog", {
11
+ /** Appends events to a chat and returns them as stored entries. */
12
+ readonly append: (chatId: ChatId, events: ReadonlyArray<AssistantEvent | StoredAppEvent>) => Effect.Effect<ReadonlyArray<Entry>>;
13
+ /** Reads a chat's stored entries in order, either all of them or only those after a `seq`. */
14
+ readonly read: (chatId: ChatId, options?: ReadOptions) => Effect.Effect<ReadonlyArray<Entry>>;
15
+ /**
16
+ * Returns the chats whose last entry ends neither a turn nor the chat. After a restart, these
17
+ * are the chats that may still owe a turn.
18
+ */
19
+ readonly unfinished: Effect.Effect<ReadonlyArray<ChatId>>;
20
+ }>;
21
+ /**
22
+ * Service that stores each chat's events as an ordered list of entries.
23
+ *
24
+ * @category services
25
+ * @since 0.1.0
26
+ */
27
+ export declare class ChatLog extends ChatLog_base {}
28
+ /**
29
+ * Represents the options that select which of a chat's entries to read.
30
+ *
31
+ * @category models
32
+ * @since 0.1.0
33
+ */
34
+ export interface ReadOptions {
35
+ /** Reads only the entries after this `seq`. */
36
+ readonly after?: number | undefined;
37
+ }
38
+ /**
39
+ * Represents the storage that a `ChatLog` is built on.
40
+ *
41
+ * A storage reads a chat's entries, finds a chat's last `seq`, writes new entries, and finds every
42
+ * chat's last entry. Entries are never removed, so a deleted chat keeps its log, which ends in
43
+ * `ChatDeleted`.
44
+ *
45
+ * @category models
46
+ * @since 0.1.0
47
+ */
48
+ export interface Storage {
49
+ readonly read: (chatId: ChatId, options?: ReadOptions) => Effect.Effect<ReadonlyArray<Entry>>;
50
+ readonly lastSeq: (chatId: ChatId) => Effect.Effect<number>;
51
+ readonly write: (chatId: ChatId, entries: ReadonlyArray<Entry>) => Effect.Effect<void>;
52
+ /** Returns the last entry of every chat. */
53
+ readonly lastEntries: Effect.Effect<ReadonlyArray<Entry>>;
54
+ }
55
+ /**
56
+ * Creates a `ChatLog` from a {@link Storage}. `append` numbers new entries after the chat's last
57
+ * `seq` and stamps them with the current time.
58
+ *
59
+ * @category constructors
60
+ * @since 0.1.0
61
+ */
62
+ export declare const make: (storage: Storage) => ChatLog["Service"];
63
+ /**
64
+ * Layer that keeps every chat in memory. The log is lost when the process stops.
65
+ *
66
+ * @category layers
67
+ * @since 0.1.0
68
+ */
69
+ export declare const layerMemory: Layer.Layer<ChatLog>;
70
+ /**
71
+ * Layer that stores chats in a SQL table named `chat_log`.
72
+ *
73
+ * **Details**
74
+ *
75
+ * The table must already exist. It has the columns `chat_id`, `seq`, `at`, and `event`, with the
76
+ * primary key `(chat_id, seq)`. The layer requires a `SqlClient`.
77
+ *
78
+ * **Example** (Storing chats in Postgres)
79
+ *
80
+ * ```ts
81
+ * import * as PgClient from "@effect/sql-pg/PgClient"
82
+ * import { ChatLog } from "effect-assistant"
83
+ * import * as Config from "effect/Config"
84
+ * import * as Layer from "effect/Layer"
85
+ *
86
+ * const ChatLogLive = ChatLog.layerSql.pipe(
87
+ * Layer.provide(PgClient.layerConfig({ url: Config.Redacted("DATABASE_URL") }))
88
+ * )
89
+ * ```
90
+ *
91
+ * The table, in Postgres:
92
+ *
93
+ * ```sql
94
+ * CREATE TABLE chat_log (
95
+ * chat_id TEXT NOT NULL,
96
+ * seq INTEGER NOT NULL,
97
+ * at DOUBLE PRECISION NOT NULL,
98
+ * event TEXT NOT NULL,
99
+ * PRIMARY KEY (chat_id, seq)
100
+ * );
101
+ * ```
102
+ *
103
+ * @category layers
104
+ * @since 0.1.0
105
+ */
106
+ export declare const layerSql: Layer.Layer<ChatLog, never, SqlClient.SqlClient>;
107
+ //#endregion
108
+ export { ChatLog_d_exports };
109
+ //# sourceMappingURL=ChatLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatLog.d.ts","names":[],"sources":["../src/ChatLog.ts"],"mappings":";;;;;;;;;;;WA+BqB,SACf,QAAQ,QACR,QAAQ,cAAc,iBAAgC,oBACnD,OAAO,OAAO,cAAc;;WAElB,OAAC,QAAQ,QAAQ,UAAU,gBAAgB,OAAO,OAAO,cAAc;;;;;WAKjE,YAAA,OAAO,OAAO,cAAc;;;;;;;;qBAdxC,gBAAgB;;;;;;;iBAwBZ;;WAEN;;;;;;;;;;;;iBAaM;WACN,OAAO,QAAQ,QAAQ,UAAU,gBAAgB,OAAO,OAAO,cAAc;WAC7E,UAAU,QAAQ,WAAW,OAAO;WACpC,QAAQ,QAAQ,QAAQ,SAAS,cAAc,WAA0B,OAAO;;WAEhF,aAAa,OAAO,OAAO,cAAc;;;;;;;;;qBAUvC,OAAI,SAAa,YAAU;;;;;;;qBAiC3B,aAAa,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwEzB,UAAU,MAAM,MAAM,gBAAgB,UAAU"}