effect-assistant 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/dist/AssistantEvent.d.ts +3 -66
  3. package/dist/AssistantEvent.d.ts.map +1 -1
  4. package/dist/AssistantEvent.js +1 -51
  5. package/dist/AssistantEvent.js.map +1 -1
  6. package/dist/AssistantLoop.d.ts +7 -5
  7. package/dist/AssistantLoop.d.ts.map +1 -1
  8. package/dist/AssistantLoop.js +6 -3
  9. package/dist/AssistantLoop.js.map +1 -1
  10. package/dist/Block.js.map +1 -1
  11. package/dist/BlockKit.d.ts +11 -15
  12. package/dist/BlockKit.d.ts.map +1 -1
  13. package/dist/BlockKit.js +3 -6
  14. package/dist/BlockKit.js.map +1 -1
  15. package/dist/ChatView.d.ts +7 -58
  16. package/dist/ChatView.d.ts.map +1 -1
  17. package/dist/ChatView.js +15 -68
  18. package/dist/ChatView.js.map +1 -1
  19. package/dist/Compaction.d.ts +2 -2
  20. package/dist/Compaction.d.ts.map +1 -1
  21. package/dist/Compaction.js +6 -13
  22. package/dist/Compaction.js.map +1 -1
  23. package/dist/ToolDiscovery.d.ts +4 -5
  24. package/dist/ToolDiscovery.d.ts.map +1 -1
  25. package/dist/ToolDiscovery.js +5 -4
  26. package/dist/ToolDiscovery.js.map +1 -1
  27. package/dist/ToolFailure.d.ts +1 -1
  28. package/dist/ToolFailure.js +1 -1
  29. package/dist/ToolFailure.js.map +1 -1
  30. package/dist/index.d.ts +1 -3
  31. package/dist/index.js +3 -5
  32. package/dist/internal/chat.js +6 -31
  33. package/dist/internal/chat.js.map +1 -1
  34. package/dist/internal/history.js +2 -40
  35. package/dist/internal/history.js.map +1 -1
  36. package/dist/internal/loop.d.ts +2 -2
  37. package/dist/internal/loop.d.ts.map +1 -1
  38. package/dist/internal/loop.js +20 -55
  39. package/dist/internal/loop.js.map +1 -1
  40. package/dist/internal/specialists.js +2 -3
  41. package/dist/internal/specialists.js.map +1 -1
  42. package/dist/internal/toolkit.d.ts +6 -20
  43. package/dist/internal/toolkit.d.ts.map +1 -1
  44. package/dist/internal/toolkit.js +10 -31
  45. package/dist/internal/toolkit.js.map +1 -1
  46. package/dist/internal/tools.js +1 -20
  47. package/dist/internal/tools.js.map +1 -1
  48. package/dist/internal/turns.d.ts.map +1 -1
  49. package/dist/internal/turns.js +2 -9
  50. package/dist/internal/turns.js.map +1 -1
  51. package/package.json +3 -2
  52. package/src/AssistantEvent.ts +0 -66
  53. package/src/AssistantLoop.ts +7 -5
  54. package/src/Block.ts +1 -1
  55. package/src/BlockKit.ts +12 -25
  56. package/src/ChatView.ts +10 -81
  57. package/src/Compaction.ts +2 -19
  58. package/src/ToolDiscovery.ts +5 -13
  59. package/src/ToolFailure.ts +1 -1
  60. package/src/index.ts +0 -10
  61. package/src/internal/chat.ts +4 -53
  62. package/src/internal/history.ts +2 -74
  63. package/src/internal/loop.ts +31 -101
  64. package/src/internal/specialists.ts +3 -7
  65. package/src/internal/toolkit.ts +17 -60
  66. package/src/internal/tools.ts +2 -53
  67. package/src/internal/turns.ts +7 -26
  68. package/dist/Decision.d.ts +0 -145
  69. package/dist/Decision.d.ts.map +0 -1
  70. package/dist/Decision.js +0 -178
  71. package/dist/Decision.js.map +0 -1
  72. package/dist/DecisionId.d.ts +0 -37
  73. package/dist/DecisionId.d.ts.map +0 -1
  74. package/dist/DecisionId.js +0 -44
  75. package/dist/DecisionId.js.map +0 -1
  76. package/dist/internal/decisions.js +0 -66
  77. package/dist/internal/decisions.js.map +0 -1
  78. package/src/Decision.ts +0 -241
  79. package/src/DecisionId.ts +0 -46
  80. package/src/internal/decisions.ts +0 -131
@@ -2,29 +2,8 @@ import { AnyEntry, AssistantEvent_d_exports } from "./AssistantEvent.js";
2
2
  import * as Schema from "effect/Schema";
3
3
  import * as Stream from "effect/Stream";
4
4
  declare namespace ChatView_d_exports {
5
- export { Activity, AssistantMessage, Block, BlockStatus, Definition, Message, Options, PendingDecision, SpecialistWorking, Update, UpdatesOptions, UserMessage, View, apply, empty, fromEntries, isIdle, updates };
5
+ export { Activity, AssistantMessage, Block, BlockStatus, Definition, Message, Options, SpecialistWorking, Update, UpdatesOptions, UserMessage, View, apply, empty, fromEntries, isIdle, updates };
6
6
  }
7
- /**
8
- * Schema for a decision as the user sees it on a decision block. It holds the call the decision is
9
- * for (its tool and params, which the app can display as it likes) and, once the user has
10
- * decided, the outcome.
11
- *
12
- * @category schemas
13
- * @since 0.1.0
14
- */
15
- export declare const PendingDecision: Schema.Struct<{
16
- readonly id: Schema.brand<Schema.TemplateLiteral<readonly ["decision:", Schema.String]>, "~effect-assistant/DecisionId">;
17
- readonly tool: Schema.String;
18
- readonly params: Schema.Codec<Schema.Json, Schema.Json, never, never>;
19
- readonly outcome: Schema.optional<Schema.Literals<readonly ["approved", "declined"]>>;
20
- }>;
21
- /**
22
- * Represents a decision as the user sees it.
23
- *
24
- * @category models
25
- * @since 0.1.0
26
- */
27
- export type PendingDecision = typeof PendingDecision.Type;
28
7
  /**
29
8
  * Schema for the status of a block. A block is `"open"` while it accepts an answer, `"answered"`
30
9
  * once the user has used it, and `"closed"` when it closed without an answer because the user
@@ -43,8 +22,8 @@ export declare const BlockStatus: Schema.Literals<readonly ["open", "answered",
43
22
  export type BlockStatus = typeof BlockStatus.Type;
44
23
  /**
45
24
  * Schema for a block on an assistant's message. It holds the block's kind, its props (JSON, as
46
- * the block's props schema encodes them), its status, the user's answer (JSON, as the block's
47
- * answer schema encodes it), and, for a decision block, its decisions.
25
+ * the block's props schema encodes them), its status, and the user's answer (JSON, as the block's
26
+ * answer schema encodes it).
48
27
  *
49
28
  * @category schemas
50
29
  * @since 0.1.0
@@ -54,12 +33,6 @@ export declare const Block: Schema.Struct<{
54
33
  readonly props: Schema.Codec<Schema.Json, Schema.Json, never, never>;
55
34
  readonly status: Schema.Literals<readonly ["open", "answered", "closed"]>;
56
35
  readonly answer: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
57
- readonly decisions: Schema.optional<Schema.$Array<Schema.Struct<{
58
- readonly id: Schema.brand<Schema.TemplateLiteral<readonly ["decision:", Schema.String]>, "~effect-assistant/DecisionId">;
59
- readonly tool: Schema.String;
60
- readonly params: Schema.Codec<Schema.Json, Schema.Json, never, never>;
61
- readonly outcome: Schema.optional<Schema.Literals<readonly ["approved", "declined"]>>;
62
- }>>>;
63
36
  }>;
64
37
  /**
65
38
  * Represents a block on an assistant's message.
@@ -108,12 +81,6 @@ export declare const AssistantMessage: Schema.TaggedStruct<"AssistantMessage", {
108
81
  readonly props: Schema.Codec<Schema.Json, Schema.Json, never, never>;
109
82
  readonly status: Schema.Literals<readonly ["open", "answered", "closed"]>;
110
83
  readonly answer: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
111
- readonly decisions: Schema.optional<Schema.$Array<Schema.Struct<{
112
- readonly id: Schema.brand<Schema.TemplateLiteral<readonly ["decision:", Schema.String]>, "~effect-assistant/DecisionId">;
113
- readonly tool: Schema.String;
114
- readonly params: Schema.Codec<Schema.Json, Schema.Json, never, never>;
115
- readonly outcome: Schema.optional<Schema.Literals<readonly ["approved", "declined"]>>;
116
- }>>>;
117
84
  }>>;
118
85
  }>;
119
86
  /**
@@ -148,12 +115,6 @@ export declare const Message: Schema.Union<readonly [Schema.TaggedStruct<"UserMe
148
115
  readonly props: Schema.Codec<Schema.Json, Schema.Json, never, never>;
149
116
  readonly status: Schema.Literals<readonly ["open", "answered", "closed"]>;
150
117
  readonly answer: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
151
- readonly decisions: Schema.optional<Schema.$Array<Schema.Struct<{
152
- readonly id: Schema.brand<Schema.TemplateLiteral<readonly ["decision:", Schema.String]>, "~effect-assistant/DecisionId">;
153
- readonly tool: Schema.String;
154
- readonly params: Schema.Codec<Schema.Json, Schema.Json, never, never>;
155
- readonly outcome: Schema.optional<Schema.Literals<readonly ["approved", "declined"]>>;
156
- }>>>;
157
118
  }>>;
158
119
  }>]>;
159
120
  /**
@@ -186,7 +147,7 @@ export type SpecialistWorking = typeof SpecialistWorking.Type;
186
147
  *
187
148
  * `working` is true while the assistant runs a turn, or when a turn is about to start for
188
149
  * something that arrived. `specialists` lists the specialists that are working. When the chat is
189
- * idle, nothing is working, even if a workflow is waiting on a timer or a decision.
150
+ * idle, nothing is working, even if a workflow is waiting on a timer or on the user.
190
151
  *
191
152
  * @category schemas
192
153
  * @since 0.1.0
@@ -234,12 +195,6 @@ export declare const View: Schema.Struct<{
234
195
  readonly props: Schema.Codec<Schema.Json, Schema.Json, never, never>;
235
196
  readonly status: Schema.Literals<readonly ["open", "answered", "closed"]>;
236
197
  readonly answer: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
237
- readonly decisions: Schema.optional<Schema.$Array<Schema.Struct<{
238
- readonly id: Schema.brand<Schema.TemplateLiteral<readonly ["decision:", Schema.String]>, "~effect-assistant/DecisionId">;
239
- readonly tool: Schema.String;
240
- readonly params: Schema.Codec<Schema.Json, Schema.Json, never, never>;
241
- readonly outcome: Schema.optional<Schema.Literals<readonly ["approved", "declined"]>>;
242
- }>>>;
243
198
  }>>;
244
199
  }>]>>;
245
200
  readonly activity: Schema.Struct<{
@@ -264,8 +219,8 @@ export type View = typeof View.Type;
264
219
  /**
265
220
  * Schema for a change to a chat's view.
266
221
  *
267
- * An update holds each message that is new or changed (for example, a block answered or closed,
268
- * or a decision made), in full. It also holds the current activity, whether the last turn failed,
222
+ * An update holds each message that is new or changed (for example, a block answered or
223
+ * closed), in full. It also holds the current activity, whether the last turn failed,
269
224
  * and whether the chat was deleted. `seq` is the position in the chat's log that the update is up
270
225
  * to.
271
226
  *
@@ -293,12 +248,6 @@ export declare const Update: Schema.Struct<{
293
248
  readonly props: Schema.Codec<Schema.Json, Schema.Json, never, never>;
294
249
  readonly status: Schema.Literals<readonly ["open", "answered", "closed"]>;
295
250
  readonly answer: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
296
- readonly decisions: Schema.optional<Schema.$Array<Schema.Struct<{
297
- readonly id: Schema.brand<Schema.TemplateLiteral<readonly ["decision:", Schema.String]>, "~effect-assistant/DecisionId">;
298
- readonly tool: Schema.String;
299
- readonly params: Schema.Codec<Schema.Json, Schema.Json, never, never>;
300
- readonly outcome: Schema.optional<Schema.Literals<readonly ["approved", "declined"]>>;
301
- }>>>;
302
251
  }>>;
303
252
  }>]>>;
304
253
  readonly activity: Schema.Struct<{
@@ -420,7 +369,7 @@ export interface UpdatesOptions {
420
369
  *
421
370
  * The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does
422
371
  * without `after`. The fold reads every entry, because a message's view depends on earlier
423
- * entries: a block is answered or closed later, and a decision is made later. `after` only skips
372
+ * entries: a block is answered or closed later. `after` only skips
424
373
  * emitting the updates up to that `seq`, so a client that has the view up to a `seq` receives only
425
374
  * what changed since. Pass `after` here, not to `subscribe`: a subscription that starts after a
426
375
  * `seq` gives the fold an incomplete log, and the updates it produces are wrong.
@@ -1 +1 @@
1
- {"version":3,"file":"ChatView.d.ts","names":[],"sources":["../src/ChatView.ts"],"mappings":";;;;;;;;;;;;;;qBAkCa,iBAAe,OAAA;;;;;;;;;;;;YAahB,yBAAyB,gBAAgB;;;;;;;;;qBAUxC,aAAW,OAAA;;;;;;;YAQZ,qBAAqB,YAAY;;;;;;;;;qBAUhC,OAAK,OAAA;;;;;;;;;;;;;;;;;;YAcN,eAAe,MAAM;;;;;;;;qBASpB,aAAW,OAAA;;;;;;;;;;;;;;;;;YAaZ,qBAAqB,YAAY;;;;;;;qBAQhC,kBAAgB,OAAA;;;;;;;;;;;;;;;;;;;;;;;YAajB,0BAA0B,iBAAiB;;;;;;;qBAQ1C,SAAO,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAQR,iBAAiB,QAAQ;;;;;;;;qBASxB,mBAAiB,OAAA;;;;;;;;;;YAWlB,2BAA2B,kBAAkB;;;;;;;;;;;qBAY5C,UAAQ,OAAA;;;;;;;;;;;;;YAWT,kBAAkB,SAAS;;;;;;;;;qBAU1B,MAAI,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAgBL,cAAc,KAAK;;;;;;;;;;;;qBAalB,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAcP,gBAAgB,OAAO;;;;;;;;qBAStB,OAAO;;;;;;;;;;;;;;qBAqBP,QAAK,MAAU,MAAI,QAAU,WAAS;;;;;;;;qBAuBtC,SAAM,MAAU;;;;;;;iBAQZ;;;;;WAKN,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA4BN,cAAW,SAAa,cAAc,WAAwB,UAAY,YAAU;;;;;;;;iBAgOhF;WACN,QAAQ;aAAyB;aAAsB;;;;;;;;;iBASjD;;;;;WAKN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2CE;GAET,YAAY,YACZ,UAAU,kBACR,GAAG,GAAG,SAAS,OAAO,OAAO,UAAyB,GAAG,OAAO,OAAO,OAAO,QAAQ,GAAG;GAC5F,GAAG,GACF,SAAS,OAAO,OAAO,UAAyB,GAAG,IACnD,YAAY,YACZ,UAAU,iBACT,OAAO,OAAO,QAAQ,GAAG"}
1
+ {"version":3,"file":"ChatView.d.ts","names":[],"sources":["../src/ChatView.ts"],"mappings":";;;;;;;;;;;;;;qBA+Ba,aAAW,OAAA;;;;;;;YAQZ,qBAAqB,YAAY;;;;;;;;;qBAUhC,OAAK,OAAA;;;;;;;;;;;;YAaN,eAAe,MAAM;;;;;;;;qBASpB,aAAW,OAAA;;;;;;;;;;;;;;;;;YAaZ,qBAAqB,YAAY;;;;;;;qBAQhC,kBAAgB,OAAA;;;;;;;;;;;;;;;;;YAajB,0BAA0B,iBAAiB;;;;;;;qBAQ1C,SAAO,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;YAQR,iBAAiB,QAAQ;;;;;;;;qBASxB,mBAAiB,OAAA;;;;;;;;;;YAWlB,2BAA2B,kBAAkB;;;;;;;;;;;qBAY5C,UAAQ,OAAA;;;;;;;;;;;;;YAWT,kBAAkB,SAAS;;;;;;;;;qBAU1B,MAAI,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAgBL,cAAc,KAAK;;;;;;;;;;;;qBAalB,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAcP,gBAAgB,OAAO;;;;;;;;qBAStB,OAAO;;;;;;;;;;;;;;qBAqBP,QAAK,MAAU,MAAI,QAAU,WAAS;;;;;;;;qBAuBtC,SAAM,MAAU;;;;;;;iBAQZ;;;;;WAKN,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA4BN,cAAW,SAAa,cAAc,WAAwB,UAAY,YAAU;;;;;;;;iBAoLhF;WACN,QAAQ;aAAyB;aAAsB;;;;;;;;;iBASjD;;;;;WAKN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2CE;GAET,YAAY,YACZ,UAAU,kBACR,GAAG,GAAG,SAAS,OAAO,OAAO,UAAyB,GAAG,OAAO,OAAO,OAAO,QAAQ,GAAG;GAC5F,GAAG,GACF,SAAS,OAAO,OAAO,UAAyB,GAAG,IACnD,YAAY,YACZ,UAAU,iBACT,OAAO,OAAO,QAAQ,GAAG"}
package/dist/ChatView.js CHANGED
@@ -1,14 +1,11 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
- import { DecisionId } from "./DecisionId.js";
3
2
  import { InputId } from "./InputId.js";
4
- import { Attachment, DecisionOutcome, isLibraryEvent } from "./AssistantEvent.js";
5
- import { Answer, Props, apply as apply$1 } from "./Decision.js";
6
- import { apply as apply$2 } from "./BlockKit.js";
3
+ import { Attachment, isLibraryEvent } from "./AssistantEvent.js";
7
4
  import { toJson } from "./internal/schema.js";
8
- import { apply as apply$3, empty as empty$1 } from "./AssistantActivity.js";
5
+ import { apply as apply$1, empty as empty$1 } from "./AssistantActivity.js";
6
+ import { apply as apply$2 } from "./BlockKit.js";
9
7
  import * as Schema from "effect/Schema";
10
8
  import * as Stream from "effect/Stream";
11
- import * as Option from "effect/Option";
12
9
  import { dual } from "effect/Function";
13
10
  import * as Match from "effect/Match";
14
11
  //#region src/ChatView.ts
@@ -16,7 +13,7 @@ import * as Match from "effect/Match";
16
13
  * This module provides the view of a chat as its user sees it. A chat client receives this view
17
14
  * and nothing else.
18
15
  *
19
- * A {@link View} holds the chat's messages with their blocks and decisions, the chat's
16
+ * A {@link View} holds the chat's messages with their blocks, the chat's
20
17
  * {@link Activity}, and whether the last turn failed or the chat was deleted. It never contains
21
18
  * the model's output, tool calls or results, what the model read, the app's own events, or why a
22
19
  * turn failed. The server streams {@link Update}s by piping the assistant's `subscribe` through
@@ -31,7 +28,6 @@ var ChatView_exports = /* @__PURE__ */ __exportAll({
31
28
  Block: () => Block,
32
29
  BlockStatus: () => BlockStatus,
33
30
  Message: () => Message,
34
- PendingDecision: () => PendingDecision,
35
31
  SpecialistWorking: () => SpecialistWorking,
36
32
  Update: () => Update,
37
33
  UserMessage: () => UserMessage,
@@ -43,20 +39,6 @@ var ChatView_exports = /* @__PURE__ */ __exportAll({
43
39
  updates: () => updates
44
40
  });
45
41
  /**
46
- * Schema for a decision as the user sees it on a decision block. It holds the call the decision is
47
- * for (its tool and params, which the app can display as it likes) and, once the user has
48
- * decided, the outcome.
49
- *
50
- * @category schemas
51
- * @since 0.1.0
52
- */
53
- const PendingDecision = Schema.Struct({
54
- id: DecisionId,
55
- tool: Schema.String,
56
- params: Schema.Json,
57
- outcome: Schema.optional(DecisionOutcome)
58
- });
59
- /**
60
42
  * Schema for the status of a block. A block is `"open"` while it accepts an answer, `"answered"`
61
43
  * once the user has used it, and `"closed"` when it closed without an answer because the user
62
44
  * answered in words or moved on.
@@ -71,8 +53,8 @@ const BlockStatus = Schema.Literals([
71
53
  ]);
72
54
  /**
73
55
  * Schema for a block on an assistant's message. It holds the block's kind, its props (JSON, as
74
- * the block's props schema encodes them), its status, the user's answer (JSON, as the block's
75
- * answer schema encodes it), and, for a decision block, its decisions.
56
+ * the block's props schema encodes them), its status, and the user's answer (JSON, as the block's
57
+ * answer schema encodes it).
76
58
  *
77
59
  * @category schemas
78
60
  * @since 0.1.0
@@ -81,8 +63,7 @@ const Block = Schema.Struct({
81
63
  kind: Schema.String,
82
64
  props: Schema.Json,
83
65
  status: BlockStatus,
84
- answer: Schema.optional(Schema.Json),
85
- decisions: Schema.optional(Schema.Array(PendingDecision))
66
+ answer: Schema.optional(Schema.Json)
86
67
  });
87
68
  /**
88
69
  * Schema for a message the user sent. `at` is when it was stored, in epoch
@@ -132,7 +113,7 @@ const SpecialistWorking = Schema.Struct({
132
113
  *
133
114
  * `working` is true while the assistant runs a turn, or when a turn is about to start for
134
115
  * something that arrived. `specialists` lists the specialists that are working. When the chat is
135
- * idle, nothing is working, even if a workflow is waiting on a timer or a decision.
116
+ * idle, nothing is working, even if a workflow is waiting on a timer or on the user.
136
117
  *
137
118
  * @category schemas
138
119
  * @since 0.1.0
@@ -161,8 +142,8 @@ const View = Schema.Struct({
161
142
  /**
162
143
  * Schema for a change to a chat's view.
163
144
  *
164
- * An update holds each message that is new or changed (for example, a block answered or closed,
165
- * or a decision made), in full. It also holds the current activity, whether the last turn failed,
145
+ * An update holds each message that is new or changed (for example, a block answered or
146
+ * closed), in full. It also holds the current activity, whether the last turn failed,
166
147
  * and whether the chat was deleted. `seq` is the position in the chat's log that the update is up
167
148
  * to.
168
149
  *
@@ -255,8 +236,6 @@ const fromEntries = (entries, options) => {
255
236
  for (const entry of entries) fold.step(entry);
256
237
  return fold.view;
257
238
  };
258
- const decodeProps = Schema.decodeUnknownOption(Props);
259
- const decodeAnswer = Schema.decodeUnknownOption(Answer);
260
239
  const activityOf = (activity) => ({
261
240
  working: activity.turnSince !== void 0 || activity.queued,
262
241
  specialists: [...activity.specialists.values()].map((run) => ({
@@ -268,51 +247,27 @@ const sameActivity = (a, b) => a.working === b.working && a.specialists.length =
268
247
  const other = b.specialists[i];
269
248
  return other !== void 0 && run.specialist === other.specialist && run.since === other.since;
270
249
  });
271
- const decisionIds = (block) => block.kind === "decision" ? Option.match(decodeProps(block.props), {
272
- onNone: () => [],
273
- onSome: (props) => props.decisionIds
274
- }) : [];
275
250
  const folding = (wakes) => {
276
251
  let seq = 0;
277
252
  let failed = false;
278
253
  let deleted = false;
279
254
  let activity = empty$1;
280
255
  let blocks = /* @__PURE__ */ new Map();
281
- let decisions = /* @__PURE__ */ new Map();
282
256
  const messages = /* @__PURE__ */ new Map();
283
257
  const sent = /* @__PURE__ */ new Map();
284
- const asking = /* @__PURE__ */ new Map();
285
258
  const blockOf = (message) => {
286
259
  if (message.block === void 0) return void 0;
287
260
  const state = blocks.get(message.id);
288
261
  const status = state === void 0 || state.open ? "open" : state.answer === void 0 ? "closed" : "answered";
289
- const shown = decisionIds(message.block).flatMap((id) => {
290
- const decision = decisions.get(id);
291
- if (decision === void 0) return [];
292
- const { tool, params } = decision.request.origin;
293
- const shown = {
294
- id: decision.request.id,
295
- tool,
296
- params
297
- };
298
- return [decision.outcome === void 0 ? shown : {
299
- ...shown,
300
- outcome: decision.outcome
301
- }];
302
- });
303
262
  const block = {
304
263
  kind: message.block.kind,
305
264
  props: toJson(message.block.props),
306
265
  status
307
266
  };
308
- const answered = state?.answer === void 0 ? block : {
267
+ return state?.answer === void 0 ? block : {
309
268
  ...block,
310
269
  answer: toJson(state.answer.value)
311
270
  };
312
- return message.block.kind === "decision" ? {
313
- ...answered,
314
- decisions: shown
315
- } : answered;
316
271
  };
317
272
  const viewOf = (message, at) => {
318
273
  const block = blockOf(message);
@@ -346,19 +301,13 @@ const folding = (wakes) => {
346
301
  sent.set(message.id, message);
347
302
  messages.set(message.id, viewOf(message, at));
348
303
  failed = false;
349
- if (message.block !== void 0) for (const id of decisionIds(message.block)) asking.set(id, [...asking.get(id) ?? [], message.id]);
350
304
  return [message.id, ...message.closes ?? []];
351
305
  },
352
306
  BlockAnswer: (answer) => {
353
307
  failed = false;
354
- const decided = answer.kind === "decision" ? Option.match(decodeAnswer(answer.value), {
355
- onNone: () => [],
356
- onSome: ({ decisions }) => Object.keys(decisions).flatMap((id) => asking.get(id) ?? [])
357
- }) : [];
358
- return [answer.messageId, ...decided];
308
+ return [answer.messageId];
359
309
  },
360
310
  BlocksClosed: (closed) => closed.messageIds,
361
- DecisionResolved: (resolved) => asking.get(resolved.id) ?? [],
362
311
  TurnFailed: () => {
363
312
  failed = true;
364
313
  return [];
@@ -370,7 +319,6 @@ const folding = (wakes) => {
370
319
  TurnCompleted: () => [],
371
320
  SpecialistStarted: () => [],
372
321
  SpecialistFinished: () => [],
373
- DecisionRequested: () => [],
374
322
  ChatDeleted: () => {
375
323
  deleted = true;
376
324
  return [];
@@ -384,7 +332,7 @@ const folding = (wakes) => {
384
332
  };
385
333
  const { event } = entry;
386
334
  seq = entry.seq;
387
- activity = apply$3(activity, entry, { wakes });
335
+ activity = apply$1(activity, entry, { wakes });
388
336
  if (!isLibraryEvent(event)) {
389
337
  const now = activityOf(activity);
390
338
  return sameActivity(before.activity, now) ? void 0 : {
@@ -396,7 +344,6 @@ const folding = (wakes) => {
396
344
  };
397
345
  }
398
346
  blocks = apply$2(blocks, event);
399
- decisions = apply$1(decisions, event);
400
347
  const changed = [...new Set(touched(event, entry.at))].flatMap((id) => {
401
348
  const message = sent.get(id);
402
349
  const current = messages.get(id);
@@ -448,7 +395,7 @@ const wakesOf = (definition) => new Set(definition.events.flatMap((event) => eve
448
395
  *
449
396
  * The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does
450
397
  * without `after`. The fold reads every entry, because a message's view depends on earlier
451
- * entries: a block is answered or closed later, and a decision is made later. `after` only skips
398
+ * entries: a block is answered or closed later. `after` only skips
452
399
  * emitting the updates up to that `seq`, so a client that has the view up to a `seq` receives only
453
400
  * what changed since. Pass `after` here, not to `subscribe`: a subscription that starts after a
454
401
  * `seq` gives the fold an incomplete log, and the updates it produces are wrong.
@@ -491,6 +438,6 @@ const updates = dual((args) => Stream.isStream(args[0]), (entries, definition, o
491
438
  }));
492
439
  });
493
440
  //#endregion
494
- export { Activity, AssistantMessage, Block, BlockStatus, ChatView_exports, Message, PendingDecision, SpecialistWorking, Update, UserMessage, View, apply, empty, fromEntries, isIdle, updates };
441
+ export { Activity, AssistantMessage, Block, BlockStatus, ChatView_exports, Message, SpecialistWorking, Update, UserMessage, View, apply, empty, fromEntries, isIdle, updates };
495
442
 
496
443
  //# sourceMappingURL=ChatView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatView.js","names":["AssistantEvent.DecisionOutcome","AssistantEvent.Attachment","Decisions.Props","Decisions.Answer","AssistantActivity.empty","AssistantActivity.apply","AssistantEvent.isLibraryEvent","BlockKit.apply","Decisions.apply"],"sources":["../src/ChatView.ts"],"sourcesContent":["/**\n * This module provides the view of a chat as its user sees it. A chat client receives this view\n * and nothing else.\n *\n * A {@link View} holds the chat's messages with their blocks and decisions, the chat's\n * {@link Activity}, and whether the last turn failed or the chat was deleted. It never contains\n * the model's output, tool calls or results, what the model read, the app's own events, or why a\n * turn failed. The server streams {@link Update}s by piping the assistant's `subscribe` through\n * {@link updates}, and the client folds them with {@link apply}, starting from {@link empty}.\n * On the server, {@link fromEntries} builds a whole view from a chat's log.\n *\n * @since 0.1.0\n */\nimport { dual } from \"effect/Function\"\nimport * as Match from \"effect/Match\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantActivity from \"./AssistantActivity.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport * as BlockKit from \"./BlockKit.ts\"\nimport * as Decisions from \"./Decision.ts\"\nimport { DecisionId } from \"./DecisionId.ts\"\nimport { InputId } from \"./InputId.ts\"\nimport { toJson } from \"./internal/schema.ts\"\n\n/**\n * Schema for a decision as the user sees it on a decision block. It holds the call the decision is\n * for (its tool and params, which the app can display as it likes) and, once the user has\n * decided, the outcome.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const PendingDecision = Schema.Struct({\n id: DecisionId,\n tool: Schema.String,\n params: Schema.Json,\n outcome: Schema.optional(AssistantEvent.DecisionOutcome)\n})\n\n/**\n * Represents a decision as the user sees it.\n *\n * @category models\n * @since 0.1.0\n */\nexport type PendingDecision = typeof PendingDecision.Type\n\n/**\n * Schema for the status of a block. A block is `\"open\"` while it accepts an answer, `\"answered\"`\n * once the user has used it, and `\"closed\"` when it closed without an answer because the user\n * answered in words or moved on.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const BlockStatus = Schema.Literals([\"open\", \"answered\", \"closed\"])\n\n/**\n * The status of a block.\n *\n * @category models\n * @since 0.1.0\n */\nexport type BlockStatus = typeof BlockStatus.Type\n\n/**\n * Schema for a block on an assistant's message. It holds the block's kind, its props (JSON, as\n * the block's props schema encodes them), its status, the user's answer (JSON, as the block's\n * answer schema encodes it), and, for a decision block, its decisions.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Block = Schema.Struct({\n kind: Schema.String,\n props: Schema.Json,\n status: BlockStatus,\n answer: Schema.optional(Schema.Json),\n decisions: Schema.optional(Schema.Array(PendingDecision))\n})\n\n/**\n * Represents a block on an assistant's message.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Block = typeof Block.Type\n\n/**\n * Schema for a message the user sent. `at` is when it was stored, in epoch\n * milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const UserMessage = Schema.TaggedStruct(\"UserMessage\", {\n id: InputId,\n text: Schema.String,\n attachments: Schema.optional(Schema.Array(AssistantEvent.Attachment)),\n at: Schema.Finite\n})\n\n/**\n * Represents a message the user sent.\n *\n * @category models\n * @since 0.1.0\n */\nexport type UserMessage = typeof UserMessage.Type\n\n/**\n * Schema for a message the assistant sent the user, including its block if it has one.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const AssistantMessage = Schema.TaggedStruct(\"AssistantMessage\", {\n id: Schema.String,\n text: Schema.String,\n at: Schema.Finite,\n block: Schema.optional(Block)\n})\n\n/**\n * Represents a message the assistant sent the user.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AssistantMessage = typeof AssistantMessage.Type\n\n/**\n * Schema for a message in the chat, sent by either the user or the assistant.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Message = Schema.Union([UserMessage, AssistantMessage])\n\n/**\n * Represents a message in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Message = typeof Message.Type\n\n/**\n * Schema for a specialist that is working for the assistant. It holds the specialist's name and\n * when it started, in epoch milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const SpecialistWorking = Schema.Struct({\n specialist: Schema.String,\n since: Schema.Finite\n})\n\n/**\n * Represents a specialist that is working for the assistant.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SpecialistWorking = typeof SpecialistWorking.Type\n\n/**\n * Schema for what is working in the chat.\n *\n * `working` is true while the assistant runs a turn, or when a turn is about to start for\n * something that arrived. `specialists` lists the specialists that are working. When the chat is\n * idle, nothing is working, even if a workflow is waiting on a timer or a decision.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Activity = Schema.Struct({\n working: Schema.Boolean,\n specialists: Schema.Array(SpecialistWorking)\n})\n\n/**\n * Represents what is working in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Activity = typeof Activity.Type\n\n/**\n * Schema for a chat as its user sees it. It holds the chat's messages, what is working in it,\n * whether the last turn failed, and whether the chat was deleted. `seq` is the position in the\n * chat's log that the view is up to.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const View = Schema.Struct({\n seq: Schema.Int,\n messages: Schema.Array(Message),\n activity: Activity,\n /** Whether the last turn failed with no message after it. */\n failed: Schema.Boolean,\n /** Whether the chat was deleted. A deleted chat accepts nothing more, and its stream ends. */\n deleted: Schema.Boolean\n})\n\n/**\n * Represents a chat as its user sees it.\n *\n * @category models\n * @since 0.1.0\n */\nexport type View = typeof View.Type\n\n/**\n * Schema for a change to a chat's view.\n *\n * An update holds each message that is new or changed (for example, a block answered or closed,\n * or a decision made), in full. It also holds the current activity, whether the last turn failed,\n * and whether the chat was deleted. `seq` is the position in the chat's log that the update is up\n * to.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Update = Schema.Struct({\n seq: Schema.Int,\n messages: Schema.Array(Message),\n activity: Activity,\n failed: Schema.Boolean,\n deleted: Schema.Boolean\n})\n\n/**\n * Represents a change to a chat's view.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Update = typeof Update.Type\n\n/**\n * The view of a chat before anything has happened in it, with no messages and nothing working.\n * Use it as the starting point when folding updates with {@link apply}.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const empty: View = {\n seq: 0,\n messages: [],\n activity: { working: false, specialists: [] },\n failed: false,\n deleted: false\n}\n\n/**\n * Returns the view after applying an update.\n *\n * **Details**\n *\n * Each message in the update replaces the message with the same id, in place, and new messages\n * are added after the rest. The update's activity, `failed`, and `deleted` replace the view's.\n * Applying an update twice changes nothing, so a client that resubscribes from the start can fold\n * again and get the same view.\n *\n * @category combinators\n * @since 0.1.0\n */\nexport const apply = (view: View, update: Update): View => {\n const changed = new Map(update.messages.map((message) => [message.id, message]))\n const known = new Set(view.messages.map((message) => message.id))\n\n return {\n seq: Math.max(view.seq, update.seq),\n messages: [\n ...view.messages.map((message) => changed.get(message.id) ?? message),\n ...update.messages.filter((message) => !known.has(message.id))\n ],\n activity: update.activity,\n failed: update.failed,\n deleted: update.deleted\n }\n}\n\n/**\n * Returns whether nothing is working in the chat: no turn is running or about to start, and no\n * specialist is working.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isIdle = (view: View): boolean => !view.activity.working && view.activity.specialists.length === 0\n\n/**\n * Represents the options for building a view from a chat's log.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options {\n /**\n * The tags of the app's events that wake a turn. After such an event, the view shows a turn as\n * about to start. Defaults to none.\n */\n readonly wakes?: ReadonlySet<string> | undefined\n}\n\n/**\n * Returns a chat's view, built from the entries of its log.\n *\n * **When to use**\n *\n * Use it in server-side code, for example to return a whole chat at once. A client gets the view\n * as a stream from {@link updates} instead.\n *\n * **Example** (Building a chat's view from its log)\n *\n * ```ts\n * import { type ChatId, ChatLog, ChatView } from \"effect-assistant\"\n * import * as Effect from \"effect/Effect\"\n *\n * const viewOf = (chatId: ChatId.ChatId) =>\n * ChatLog.ChatLog.use((log) => log.read(chatId)).pipe(\n * Effect.map((entries) => ChatView.fromEntries(entries))\n * )\n * ```\n *\n * @see {@link updates} for a stream of changes instead of a whole view\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, options?: Options): View => {\n const fold = folding(options?.wakes ?? new Set())\n\n for (const entry of entries) fold.step(entry)\n\n return fold.view\n}\n\ninterface Folding {\n readonly step: (entry: AssistantEvent.AnyEntry) => Update | undefined\n readonly view: View\n}\n\nconst decodeProps = Schema.decodeUnknownOption(Decisions.Props)\n\nconst decodeAnswer = Schema.decodeUnknownOption(Decisions.Answer)\n\nconst activityOf = (activity: AssistantActivity.State): Activity => ({\n working: activity.turnSince !== undefined || activity.queued,\n specialists: [...activity.specialists.values()].map((run) => ({\n specialist: run.specialist,\n since: run.since\n }))\n})\n\nconst sameActivity = (a: Activity, b: Activity) =>\n a.working === b.working &&\n a.specialists.length === b.specialists.length &&\n a.specialists.every((run, i) => {\n const other = b.specialists[i]\n\n return other !== undefined && run.specialist === other.specialist && run.since === other.since\n })\n\nconst decisionIds = (block: AssistantEvent.MessageBlock): ReadonlyArray<string> =>\n block.kind === Decisions.kind\n ? Option.match(decodeProps(block.props), {\n onNone: () => [],\n onSome: (props) => props.decisionIds\n })\n : []\n\nconst folding = (wakes: ReadonlySet<string>): Folding => {\n let seq = 0\n let failed = false\n let deleted = false\n let activity = AssistantActivity.empty\n let blocks: ReadonlyMap<string, BlockKit.State> = new Map()\n let decisions: ReadonlyMap<string, Decisions.State> = new Map()\n // The messages in order, and the assistant's as stored: their blocks change as the chat goes on.\n const messages = new Map<string, Message>()\n const sent = new Map<string, AssistantEvent.AssistantMessage>()\n // Which messages ask for a decision, so a decision's outcome updates the block that asked for it.\n const asking = new Map<string, Array<string>>()\n\n const blockOf = (message: AssistantEvent.AssistantMessage): Block | undefined => {\n if (message.block === undefined) return undefined\n\n const state = blocks.get(message.id)\n const status = state === undefined || state.open ? \"open\" : state.answer === undefined ? \"closed\" : \"answered\"\n const ids = decisionIds(message.block)\n\n const shown = ids.flatMap((id) => {\n const decision = decisions.get(id)\n\n if (decision === undefined) return []\n\n const { tool, params } = decision.request.origin\n const shown = { id: decision.request.id, tool, params }\n\n return [decision.outcome === undefined ? shown : { ...shown, outcome: decision.outcome }]\n })\n\n const block: Block = { kind: message.block.kind, props: toJson(message.block.props), status }\n const answered = state?.answer === undefined ? block : { ...block, answer: toJson(state.answer.value) }\n\n return message.block.kind === Decisions.kind ? { ...answered, decisions: shown } : answered\n }\n\n const viewOf = (message: AssistantEvent.AssistantMessage, at: number): AssistantMessage => {\n const block = blockOf(message)\n\n return block === undefined\n ? AssistantMessage.make({ id: message.id, text: message.text, at })\n : AssistantMessage.make({ id: message.id, text: message.text, at, block })\n }\n\n const touched = (event: AssistantEvent.AssistantEvent, at: number): ReadonlyArray<string> =>\n Match.value(event).pipe(\n Match.tagsExhaustive({\n UserMessage: (message) => {\n messages.set(\n message.id,\n message.attachments === undefined\n ? UserMessage.make({ id: message.id, text: message.text, at })\n : UserMessage.make({\n id: message.id,\n text: message.text,\n attachments: message.attachments,\n at\n })\n )\n failed = false\n\n return [message.id]\n },\n AssistantMessage: (message) => {\n sent.set(message.id, message)\n messages.set(message.id, viewOf(message, at))\n failed = false\n\n if (message.block !== undefined) {\n for (const id of decisionIds(message.block)) asking.set(id, [...(asking.get(id) ?? []), message.id])\n }\n\n return [message.id, ...(message.closes ?? [])]\n },\n BlockAnswer: (answer) => {\n failed = false\n\n const decided =\n answer.kind === Decisions.kind\n ? Option.match(decodeAnswer(answer.value), {\n onNone: () => [],\n onSome: ({ decisions }) => Object.keys(decisions).flatMap((id) => asking.get(id) ?? [])\n })\n : []\n\n return [answer.messageId, ...decided]\n },\n BlocksClosed: (closed) => closed.messageIds,\n DecisionResolved: (resolved) => asking.get(resolved.id) ?? [],\n TurnFailed: () => {\n failed = true\n\n return []\n },\n ModelOutput: () => [],\n ToolStarted: () => [],\n ToolResult: () => [],\n TurnStarted: () => [],\n TurnCompleted: () => [],\n SpecialistStarted: () => [],\n SpecialistFinished: () => [],\n DecisionRequested: () => [],\n ChatDeleted: () => {\n deleted = true\n\n return []\n }\n })\n )\n\n const step = (entry: AssistantEvent.AnyEntry): Update | undefined => {\n const before = { activity: activityOf(activity), failed, deleted }\n const { event } = entry\n\n seq = entry.seq\n activity = AssistantActivity.apply(activity, entry, { wakes })\n\n if (!AssistantEvent.isLibraryEvent(event)) {\n const now = activityOf(activity)\n\n return sameActivity(before.activity, now) ? undefined : { seq, messages: [], activity: now, failed, deleted }\n }\n\n blocks = BlockKit.apply(blocks, event)\n decisions = Decisions.apply(decisions, event)\n\n const changed = [...new Set(touched(event, entry.at))].flatMap((id) => {\n const message = sent.get(id)\n const current = messages.get(id)\n\n if (message === undefined || current === undefined) return current === undefined ? [] : [current]\n\n const next = viewOf(message, current.at)\n messages.set(id, next)\n\n return [next]\n })\n\n const now = activityOf(activity)\n\n return changed.length === 0 &&\n failed === before.failed &&\n deleted === before.deleted &&\n sameActivity(before.activity, now)\n ? undefined\n : { seq, messages: changed, activity: now, failed, deleted }\n }\n\n return {\n step,\n get view() {\n return { seq, messages: [...messages.values()], activity: activityOf(activity), failed, deleted }\n }\n }\n}\n\nconst merge = (updates: ReadonlyArray<Update>): Update | undefined => {\n const last = updates.at(-1)\n\n if (last === undefined) return undefined\n\n const messages = new Map<string, Message>()\n\n for (const update of updates) for (const message of update.messages) messages.set(message.id, message)\n\n return {\n seq: last.seq,\n messages: [...messages.values()],\n activity: last.activity,\n failed: last.failed,\n deleted: last.deleted\n }\n}\n\n/**\n * Represents an assistant's definition as {@link updates} reads it: the app's events it\n * registered, and whether each one wakes a turn. An assistant made with `Assistant.make` fits it.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Definition {\n readonly events: ReadonlyArray<{ readonly tag: string; readonly wakes: boolean }>\n}\n\n/**\n * Represents the options accepted by {@link updates}.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface UpdatesOptions {\n /**\n * The `seq` up to which the client already has the view. The result contains only the updates\n * after it. Defaults to `0`, every update.\n */\n readonly after?: number | undefined\n}\n\nconst wakesOf = (definition: Definition): ReadonlySet<string> =>\n new Set(definition.events.flatMap((event) => (event.wakes ? [event.tag] : [])))\n\n/**\n * Returns the stream of view updates that a chat's entries produce, for a chat client.\n *\n * **Details**\n *\n * The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does\n * without `after`. The fold reads every entry, because a message's view depends on earlier\n * entries: a block is answered or closed later, and a decision is made later. `after` only skips\n * emitting the updates up to that `seq`, so a client that has the view up to a `seq` receives only\n * what changed since. Pass `after` here, not to `subscribe`: a subscription that starts after a\n * `seq` gives the fold an incomplete log, and the updates it produces are wrong.\n *\n * The definition tells the fold which of the app's events wake a turn, so the view shows a turn as\n * about to start after one. Each chunk of entries becomes one update; the stored entries, for\n * example, arrive as one chunk.\n *\n * **Example** (Streaming a chat's view to a client)\n *\n * ```ts\n * import { Assistant, type ChatId, ChatView } from \"effect-assistant\"\n * import * as Stream from \"effect/Stream\"\n *\n * const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const watch = (chatId: ChatId.ChatId, after: number) =>\n * Stream.unwrap(\n * MyAssistant.useSync((assistant) =>\n * assistant.subscribe(chatId).pipe(ChatView.updates(MyAssistant, { after }))\n * )\n * )\n * ```\n *\n * @see {@link apply} for folding the updates into a view on the client\n *\n * @category utils\n * @since 0.1.0\n */\nexport const updates: {\n (\n definition: Definition,\n options?: UpdatesOptions\n ): <E, R>(entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>) => Stream.Stream<Update, E, R>\n <E, R>(\n entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,\n definition: Definition,\n options?: UpdatesOptions\n ): Stream.Stream<Update, E, R>\n} = dual(\n (args) => Stream.isStream(args[0]),\n <E, R>(\n entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,\n definition: Definition,\n options?: UpdatesOptions\n ): Stream.Stream<Update, E, R> => {\n const after = options?.after ?? 0\n const wakes = wakesOf(definition)\n\n return entries.pipe(\n Stream.mapAccumArray(\n () => folding(wakes),\n (fold, chunk) => {\n const changed = chunk.flatMap((entry) => {\n const update = fold.step(entry)\n\n return update === undefined || entry.seq <= after ? [] : [update]\n })\n\n const merged = merge(changed)\n\n return [fold, merged === undefined ? [] : [merged]]\n }\n )\n )\n }\n)\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChatView.js","names":["AssistantEvent.Attachment","AssistantActivity.empty","AssistantActivity.apply","AssistantEvent.isLibraryEvent","BlockKit.apply"],"sources":["../src/ChatView.ts"],"sourcesContent":["/**\n * This module provides the view of a chat as its user sees it. A chat client receives this view\n * and nothing else.\n *\n * A {@link View} holds the chat's messages with their blocks, the chat's\n * {@link Activity}, and whether the last turn failed or the chat was deleted. It never contains\n * the model's output, tool calls or results, what the model read, the app's own events, or why a\n * turn failed. The server streams {@link Update}s by piping the assistant's `subscribe` through\n * {@link updates}, and the client folds them with {@link apply}, starting from {@link empty}.\n * On the server, {@link fromEntries} builds a whole view from a chat's log.\n *\n * @since 0.1.0\n */\nimport { dual } from \"effect/Function\"\nimport * as Match from \"effect/Match\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantActivity from \"./AssistantActivity.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport * as BlockKit from \"./BlockKit.ts\"\nimport { InputId } from \"./InputId.ts\"\nimport { toJson } from \"./internal/schema.ts\"\n\n/**\n * Schema for the status of a block. A block is `\"open\"` while it accepts an answer, `\"answered\"`\n * once the user has used it, and `\"closed\"` when it closed without an answer because the user\n * answered in words or moved on.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const BlockStatus = Schema.Literals([\"open\", \"answered\", \"closed\"])\n\n/**\n * The status of a block.\n *\n * @category models\n * @since 0.1.0\n */\nexport type BlockStatus = typeof BlockStatus.Type\n\n/**\n * Schema for a block on an assistant's message. It holds the block's kind, its props (JSON, as\n * the block's props schema encodes them), its status, and the user's answer (JSON, as the block's\n * answer schema encodes it).\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Block = Schema.Struct({\n kind: Schema.String,\n props: Schema.Json,\n status: BlockStatus,\n answer: Schema.optional(Schema.Json)\n})\n\n/**\n * Represents a block on an assistant's message.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Block = typeof Block.Type\n\n/**\n * Schema for a message the user sent. `at` is when it was stored, in epoch\n * milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const UserMessage = Schema.TaggedStruct(\"UserMessage\", {\n id: InputId,\n text: Schema.String,\n attachments: Schema.optional(Schema.Array(AssistantEvent.Attachment)),\n at: Schema.Finite\n})\n\n/**\n * Represents a message the user sent.\n *\n * @category models\n * @since 0.1.0\n */\nexport type UserMessage = typeof UserMessage.Type\n\n/**\n * Schema for a message the assistant sent the user, including its block if it has one.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const AssistantMessage = Schema.TaggedStruct(\"AssistantMessage\", {\n id: Schema.String,\n text: Schema.String,\n at: Schema.Finite,\n block: Schema.optional(Block)\n})\n\n/**\n * Represents a message the assistant sent the user.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AssistantMessage = typeof AssistantMessage.Type\n\n/**\n * Schema for a message in the chat, sent by either the user or the assistant.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Message = Schema.Union([UserMessage, AssistantMessage])\n\n/**\n * Represents a message in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Message = typeof Message.Type\n\n/**\n * Schema for a specialist that is working for the assistant. It holds the specialist's name and\n * when it started, in epoch milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const SpecialistWorking = Schema.Struct({\n specialist: Schema.String,\n since: Schema.Finite\n})\n\n/**\n * Represents a specialist that is working for the assistant.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SpecialistWorking = typeof SpecialistWorking.Type\n\n/**\n * Schema for what is working in the chat.\n *\n * `working` is true while the assistant runs a turn, or when a turn is about to start for\n * something that arrived. `specialists` lists the specialists that are working. When the chat is\n * idle, nothing is working, even if a workflow is waiting on a timer or on the user.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Activity = Schema.Struct({\n working: Schema.Boolean,\n specialists: Schema.Array(SpecialistWorking)\n})\n\n/**\n * Represents what is working in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Activity = typeof Activity.Type\n\n/**\n * Schema for a chat as its user sees it. It holds the chat's messages, what is working in it,\n * whether the last turn failed, and whether the chat was deleted. `seq` is the position in the\n * chat's log that the view is up to.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const View = Schema.Struct({\n seq: Schema.Int,\n messages: Schema.Array(Message),\n activity: Activity,\n /** Whether the last turn failed with no message after it. */\n failed: Schema.Boolean,\n /** Whether the chat was deleted. A deleted chat accepts nothing more, and its stream ends. */\n deleted: Schema.Boolean\n})\n\n/**\n * Represents a chat as its user sees it.\n *\n * @category models\n * @since 0.1.0\n */\nexport type View = typeof View.Type\n\n/**\n * Schema for a change to a chat's view.\n *\n * An update holds each message that is new or changed (for example, a block answered or\n * closed), in full. It also holds the current activity, whether the last turn failed,\n * and whether the chat was deleted. `seq` is the position in the chat's log that the update is up\n * to.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Update = Schema.Struct({\n seq: Schema.Int,\n messages: Schema.Array(Message),\n activity: Activity,\n failed: Schema.Boolean,\n deleted: Schema.Boolean\n})\n\n/**\n * Represents a change to a chat's view.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Update = typeof Update.Type\n\n/**\n * The view of a chat before anything has happened in it, with no messages and nothing working.\n * Use it as the starting point when folding updates with {@link apply}.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const empty: View = {\n seq: 0,\n messages: [],\n activity: { working: false, specialists: [] },\n failed: false,\n deleted: false\n}\n\n/**\n * Returns the view after applying an update.\n *\n * **Details**\n *\n * Each message in the update replaces the message with the same id, in place, and new messages\n * are added after the rest. The update's activity, `failed`, and `deleted` replace the view's.\n * Applying an update twice changes nothing, so a client that resubscribes from the start can fold\n * again and get the same view.\n *\n * @category combinators\n * @since 0.1.0\n */\nexport const apply = (view: View, update: Update): View => {\n const changed = new Map(update.messages.map((message) => [message.id, message]))\n const known = new Set(view.messages.map((message) => message.id))\n\n return {\n seq: Math.max(view.seq, update.seq),\n messages: [\n ...view.messages.map((message) => changed.get(message.id) ?? message),\n ...update.messages.filter((message) => !known.has(message.id))\n ],\n activity: update.activity,\n failed: update.failed,\n deleted: update.deleted\n }\n}\n\n/**\n * Returns whether nothing is working in the chat: no turn is running or about to start, and no\n * specialist is working.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isIdle = (view: View): boolean => !view.activity.working && view.activity.specialists.length === 0\n\n/**\n * Represents the options for building a view from a chat's log.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options {\n /**\n * The tags of the app's events that wake a turn. After such an event, the view shows a turn as\n * about to start. Defaults to none.\n */\n readonly wakes?: ReadonlySet<string> | undefined\n}\n\n/**\n * Returns a chat's view, built from the entries of its log.\n *\n * **When to use**\n *\n * Use it in server-side code, for example to return a whole chat at once. A client gets the view\n * as a stream from {@link updates} instead.\n *\n * **Example** (Building a chat's view from its log)\n *\n * ```ts\n * import { type ChatId, ChatLog, ChatView } from \"effect-assistant\"\n * import * as Effect from \"effect/Effect\"\n *\n * const viewOf = (chatId: ChatId.ChatId) =>\n * ChatLog.ChatLog.use((log) => log.read(chatId)).pipe(\n * Effect.map((entries) => ChatView.fromEntries(entries))\n * )\n * ```\n *\n * @see {@link updates} for a stream of changes instead of a whole view\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, options?: Options): View => {\n const fold = folding(options?.wakes ?? new Set())\n\n for (const entry of entries) fold.step(entry)\n\n return fold.view\n}\n\ninterface Folding {\n readonly step: (entry: AssistantEvent.AnyEntry) => Update | undefined\n readonly view: View\n}\n\nconst activityOf = (activity: AssistantActivity.State): Activity => ({\n working: activity.turnSince !== undefined || activity.queued,\n specialists: [...activity.specialists.values()].map((run) => ({\n specialist: run.specialist,\n since: run.since\n }))\n})\n\nconst sameActivity = (a: Activity, b: Activity) =>\n a.working === b.working &&\n a.specialists.length === b.specialists.length &&\n a.specialists.every((run, i) => {\n const other = b.specialists[i]\n\n return other !== undefined && run.specialist === other.specialist && run.since === other.since\n })\n\nconst folding = (wakes: ReadonlySet<string>): Folding => {\n let seq = 0\n let failed = false\n let deleted = false\n let activity = AssistantActivity.empty\n let blocks: ReadonlyMap<string, BlockKit.State> = new Map()\n // The messages in order, and the assistant's as stored: their blocks change as the chat goes on.\n const messages = new Map<string, Message>()\n const sent = new Map<string, AssistantEvent.AssistantMessage>()\n\n const blockOf = (message: AssistantEvent.AssistantMessage): Block | undefined => {\n if (message.block === undefined) return undefined\n\n const state = blocks.get(message.id)\n const status = state === undefined || state.open ? \"open\" : state.answer === undefined ? \"closed\" : \"answered\"\n const block: Block = { kind: message.block.kind, props: toJson(message.block.props), status }\n\n return state?.answer === undefined ? block : { ...block, answer: toJson(state.answer.value) }\n }\n\n const viewOf = (message: AssistantEvent.AssistantMessage, at: number): AssistantMessage => {\n const block = blockOf(message)\n\n return block === undefined\n ? AssistantMessage.make({ id: message.id, text: message.text, at })\n : AssistantMessage.make({ id: message.id, text: message.text, at, block })\n }\n\n const touched = (event: AssistantEvent.AssistantEvent, at: number): ReadonlyArray<string> =>\n Match.value(event).pipe(\n Match.tagsExhaustive({\n UserMessage: (message) => {\n messages.set(\n message.id,\n message.attachments === undefined\n ? UserMessage.make({ id: message.id, text: message.text, at })\n : UserMessage.make({\n id: message.id,\n text: message.text,\n attachments: message.attachments,\n at\n })\n )\n failed = false\n\n return [message.id]\n },\n AssistantMessage: (message) => {\n sent.set(message.id, message)\n messages.set(message.id, viewOf(message, at))\n failed = false\n\n return [message.id, ...(message.closes ?? [])]\n },\n BlockAnswer: (answer) => {\n failed = false\n\n return [answer.messageId]\n },\n BlocksClosed: (closed) => closed.messageIds,\n TurnFailed: () => {\n failed = true\n\n return []\n },\n ModelOutput: () => [],\n ToolStarted: () => [],\n ToolResult: () => [],\n TurnStarted: () => [],\n TurnCompleted: () => [],\n SpecialistStarted: () => [],\n SpecialistFinished: () => [],\n ChatDeleted: () => {\n deleted = true\n\n return []\n }\n })\n )\n\n const step = (entry: AssistantEvent.AnyEntry): Update | undefined => {\n const before = { activity: activityOf(activity), failed, deleted }\n const { event } = entry\n\n seq = entry.seq\n activity = AssistantActivity.apply(activity, entry, { wakes })\n\n if (!AssistantEvent.isLibraryEvent(event)) {\n const now = activityOf(activity)\n\n return sameActivity(before.activity, now) ? undefined : { seq, messages: [], activity: now, failed, deleted }\n }\n\n blocks = BlockKit.apply(blocks, event)\n\n const changed = [...new Set(touched(event, entry.at))].flatMap((id) => {\n const message = sent.get(id)\n const current = messages.get(id)\n\n if (message === undefined || current === undefined) return current === undefined ? [] : [current]\n\n const next = viewOf(message, current.at)\n messages.set(id, next)\n\n return [next]\n })\n\n const now = activityOf(activity)\n\n return changed.length === 0 &&\n failed === before.failed &&\n deleted === before.deleted &&\n sameActivity(before.activity, now)\n ? undefined\n : { seq, messages: changed, activity: now, failed, deleted }\n }\n\n return {\n step,\n get view() {\n return { seq, messages: [...messages.values()], activity: activityOf(activity), failed, deleted }\n }\n }\n}\n\nconst merge = (updates: ReadonlyArray<Update>): Update | undefined => {\n const last = updates.at(-1)\n\n if (last === undefined) return undefined\n\n const messages = new Map<string, Message>()\n\n for (const update of updates) for (const message of update.messages) messages.set(message.id, message)\n\n return {\n seq: last.seq,\n messages: [...messages.values()],\n activity: last.activity,\n failed: last.failed,\n deleted: last.deleted\n }\n}\n\n/**\n * Represents an assistant's definition as {@link updates} reads it: the app's events it\n * registered, and whether each one wakes a turn. An assistant made with `Assistant.make` fits it.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Definition {\n readonly events: ReadonlyArray<{ readonly tag: string; readonly wakes: boolean }>\n}\n\n/**\n * Represents the options accepted by {@link updates}.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface UpdatesOptions {\n /**\n * The `seq` up to which the client already has the view. The result contains only the updates\n * after it. Defaults to `0`, every update.\n */\n readonly after?: number | undefined\n}\n\nconst wakesOf = (definition: Definition): ReadonlySet<string> =>\n new Set(definition.events.flatMap((event) => (event.wakes ? [event.tag] : [])))\n\n/**\n * Returns the stream of view updates that a chat's entries produce, for a chat client.\n *\n * **Details**\n *\n * The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does\n * without `after`. The fold reads every entry, because a message's view depends on earlier\n * entries: a block is answered or closed later. `after` only skips\n * emitting the updates up to that `seq`, so a client that has the view up to a `seq` receives only\n * what changed since. Pass `after` here, not to `subscribe`: a subscription that starts after a\n * `seq` gives the fold an incomplete log, and the updates it produces are wrong.\n *\n * The definition tells the fold which of the app's events wake a turn, so the view shows a turn as\n * about to start after one. Each chunk of entries becomes one update; the stored entries, for\n * example, arrive as one chunk.\n *\n * **Example** (Streaming a chat's view to a client)\n *\n * ```ts\n * import { Assistant, type ChatId, ChatView } from \"effect-assistant\"\n * import * as Stream from \"effect/Stream\"\n *\n * const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const watch = (chatId: ChatId.ChatId, after: number) =>\n * Stream.unwrap(\n * MyAssistant.useSync((assistant) =>\n * assistant.subscribe(chatId).pipe(ChatView.updates(MyAssistant, { after }))\n * )\n * )\n * ```\n *\n * @see {@link apply} for folding the updates into a view on the client\n *\n * @category utils\n * @since 0.1.0\n */\nexport const updates: {\n (\n definition: Definition,\n options?: UpdatesOptions\n ): <E, R>(entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>) => Stream.Stream<Update, E, R>\n <E, R>(\n entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,\n definition: Definition,\n options?: UpdatesOptions\n ): Stream.Stream<Update, E, R>\n} = dual(\n (args) => Stream.isStream(args[0]),\n <E, R>(\n entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,\n definition: Definition,\n options?: UpdatesOptions\n ): Stream.Stream<Update, E, R> => {\n const after = options?.after ?? 0\n const wakes = wakesOf(definition)\n\n return entries.pipe(\n Stream.mapAccumArray(\n () => folding(wakes),\n (fold, chunk) => {\n const changed = chunk.flatMap((entry) => {\n const update = fold.step(entry)\n\n return update === undefined || entry.seq <= after ? [] : [update]\n })\n\n const merged = merge(changed)\n\n return [fold, merged === undefined ? [] : [merged]]\n }\n )\n )\n }\n)\n"],"mappings":"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"}
@@ -28,8 +28,8 @@ export interface Summary {
28
28
  */
29
29
  export declare const inputTokens: (entries: ReadonlyArray<AnyEntry>) => number;
30
30
  /**
31
- * Returns the items that were open at `throughSeq` and are still open: decisions waiting for the
32
- * user, blocks the user hasn't answered, and specialists that are working. The result is text for
31
+ * Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't
32
+ * answered, and specialists that are working. The result is text for
33
33
  * the model, or `None` when nothing is open.
34
34
  *
35
35
  * @category utils
@@ -1 +1 @@
1
- {"version":3,"file":"Compaction.d.ts","names":[],"sources":["../src/Compaction.ts"],"mappings":";;;;;;;;;;;;;;;;iBAgCiB;WACN;WACA;WACA;;;;;;;;;qBAUE,cAAW,SAAa,cAAc;;;;;;;;;qBAmBtC,YAAS,SACX,cAAc,WAAwB,uBAE9C,OAAO;;;;;;;;qBAmEG,gBAAa,SACf,cAAc,WAAwB,uBAE9C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA+DG,QAAK,YACH,mBAAmB,SAAS,aACxC;WAA0B;GAAc,SAC9B,cAAc,MAAgB,aACtC,cAAc,MAAgB,UAAU,OAAO"}
1
+ {"version":3,"file":"Compaction.d.ts","names":[],"sources":["../src/Compaction.ts"],"mappings":";;;;;;;;;;;;;;;;iBA8BiB;WACN;WACA;WACA;;;;;;;;;qBAUE,cAAW,SAAa,cAAc;;;;;;;;;qBAmBtC,YAAS,SACX,cAAc,WAAwB,uBAE9C,OAAO;;;;;;;;qBAoDG,gBAAa,SACf,cAAc,WAAwB,uBAE9C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA+DG,QAAK,YACH,mBAAmB,SAAS,aACxC;WAA0B;GAAc,SAC9B,cAAc,MAAgB,aACtC,cAAc,MAAgB,UAAU,OAAO"}
@@ -1,10 +1,8 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
2
  import { attachmentLine, isLibraryEvent } from "./AssistantEvent.js";
3
- import { fromEntries } from "./Decision.js";
4
- import { fromEntries as fromEntries$1 } from "./BlockKit.js";
5
- import { toJsonText } from "./internal/schema.js";
6
3
  import { userText } from "./internal/history.js";
7
- import { fromEntries as fromEntries$2 } from "./AssistantActivity.js";
4
+ import { fromEntries } from "./AssistantActivity.js";
5
+ import { fromEntries as fromEntries$1 } from "./BlockKit.js";
8
6
  import * as Predicate from "effect/Predicate";
9
7
  import * as Option from "effect/Option";
10
8
  //#region src/Compaction.ts
@@ -26,8 +24,8 @@ const inputTokens = (entries) => {
26
24
  return last !== void 0 && isLibraryEvent(last) && Predicate.isTagged(last, "ModelOutput") ? last.usage?.inputTokens.total ?? 0 : 0;
27
25
  };
28
26
  /**
29
- * Returns the items that were open at `throughSeq` and are still open: decisions waiting for the
30
- * user, blocks the user hasn't answered, and specialists that are working. The result is text for
27
+ * Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't
28
+ * answered, and specialists that are working. The result is text for
31
29
  * the model, or `None` when nothing is open.
32
30
  *
33
31
  * @category utils
@@ -36,18 +34,13 @@ const inputTokens = (entries) => {
36
34
  const openItems = (entries, throughSeq) => {
37
35
  const seqOf = (found) => entries.find(({ event }) => found(event))?.seq ?? Number.POSITIVE_INFINITY;
38
36
  const opened = (seq) => seq <= throughSeq;
39
- const decisions = [...fromEntries(entries).values()].flatMap((state) => state.outcome === void 0 && opened(seqOf((event) => isLibraryEvent(event) && Predicate.isTagged(event, "DecisionRequested") && event.id === state.request.id)) ? [`- decision ${state.request.id}, for ${state.request.origin.tool}: ${toJsonText(state.request.origin.params)}`] : []);
40
37
  const blocks = [...fromEntries$1(entries)].flatMap(([messageId, state]) => {
41
38
  const shown = entries.find(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") && event.id === messageId);
42
39
  const message = shown?.event;
43
40
  return state.open && shown !== void 0 && opened(shown.seq) && message !== void 0 && isLibraryEvent(message) && Predicate.isTagged(message, "AssistantMessage") ? [`- message ${messageId}, a ${message.block?.kind} block: ${message.text}`] : [];
44
41
  });
45
- const specialists = [...fromEntries$2(entries).specialists.values()].flatMap((run) => opened(seqOf((event) => isLibraryEvent(event) && Predicate.isTagged(event, "SpecialistStarted") && event.runId === run.runId)) ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`] : []);
46
- const sections = [
47
- decisions.length > 0 ? `Decisions waiting for the user:\n${decisions.join("\n")}` : "",
48
- blocks.length > 0 ? `Blocks the user hasn't answered:\n${blocks.join("\n")}` : "",
49
- specialists.length > 0 ? `Specialists still working:\n${specialists.join("\n")}` : ""
50
- ].filter((section) => section.length > 0);
42
+ const specialists = [...fromEntries(entries).specialists.values()].flatMap((run) => opened(seqOf((event) => isLibraryEvent(event) && Predicate.isTagged(event, "SpecialistStarted") && event.runId === run.runId)) ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`] : []);
43
+ const sections = [blocks.length > 0 ? `Blocks the user hasn't answered:\n${blocks.join("\n")}` : "", specialists.length > 0 ? `Specialists still working:\n${specialists.join("\n")}` : ""].filter((section) => section.length > 0);
51
44
  return sections.length === 0 ? Option.none() : Option.some(sections.join("\n\n"));
52
45
  };
53
46
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Compaction.js","names":["AssistantEvent.isLibraryEvent","Decision.fromEntries","BlockKit.fromEntries","AssistantActivity.fromEntries","AssistantEvent.attachmentLine"],"sources":["../src/Compaction.ts"],"sourcesContent":["/**\n * This module provides what a long chat needs so that the model can read a summary in place of\n * everything before it.\n *\n * The app decides when to compact, which model writes the summary, and what the summary says.\n * Its handler reads the chat's entries, has a model write a summary, and stores the summary as one\n * of the assistant's own events (see {@link Summary}). Its loop then reads the chat through\n * {@link since}. {@link inputTokens} helps decide when the prompt has grown large enough to\n * compact. The log keeps every entry, so the user still sees the whole chat.\n *\n * @since 0.1.0\n */\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport type * as Assistant from \"./Assistant.ts\"\nimport * as AssistantActivity from \"./AssistantActivity.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport * as BlockKit from \"./BlockKit.ts\"\nimport * as Decision from \"./Decision.ts\"\nimport { userText } from \"./internal/history.ts\"\nimport { toJsonText } from \"./internal/schema.ts\"\n\n/**\n * Represents the fields a summary event must have: its text and the last `seq` it covers.\n *\n * Entries after `throughSeq` are kept as they are, so an input that arrived while the summary was\n * being written isn't lost.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Summary {\n readonly _tag: string\n readonly text: string\n readonly throughSeq: number\n}\n\n/**\n * Returns the number of input tokens used by the chat's last model call, which shows how large\n * the prompt has grown. Returns 0 before the first call.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const inputTokens = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {\n const last = entries.findLast(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ModelOutput\") && event.usage !== undefined\n )?.event\n\n return last !== undefined && AssistantEvent.isLibraryEvent(last) && Predicate.isTagged(last, \"ModelOutput\")\n ? (last.usage?.inputTokens.total ?? 0)\n : 0\n}\n\n/**\n * Returns the items that were open at `throughSeq` and are still open: decisions waiting for the\n * user, blocks the user hasn't answered, and specialists that are working. The result is text for\n * the model, or `None` when nothing is open.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const openItems = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const seqOf = (found: (event: AssistantEvent.AnyEntry[\"event\"]) => boolean) =>\n entries.find(({ event }) => found(event))?.seq ?? Number.POSITIVE_INFINITY\n\n const opened = (seq: number) => seq <= throughSeq\n\n const decisions = [...Decision.fromEntries(entries).values()].flatMap((state) =>\n state.outcome === undefined &&\n opened(\n seqOf(\n (event) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"DecisionRequested\") &&\n event.id === state.request.id\n )\n )\n ? [`- decision ${state.request.id}, for ${state.request.origin.tool}: ${toJsonText(state.request.origin.params)}`]\n : []\n )\n\n const blocks = [...BlockKit.fromEntries(entries)].flatMap(([messageId, state]) => {\n const shown = entries.find(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === messageId\n )\n\n const message = shown?.event\n\n return state.open &&\n shown !== undefined &&\n opened(shown.seq) &&\n message !== undefined &&\n AssistantEvent.isLibraryEvent(message) &&\n Predicate.isTagged(message, \"AssistantMessage\")\n ? [`- message ${messageId}, a ${message.block?.kind} block: ${message.text}`]\n : []\n })\n\n const specialists = [...AssistantActivity.fromEntries(entries).specialists.values()].flatMap((run) =>\n opened(\n seqOf(\n (event) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"SpecialistStarted\") &&\n event.runId === run.runId\n )\n )\n ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`]\n : []\n )\n\n const sections = [\n decisions.length > 0 ? `Decisions waiting for the user:\\n${decisions.join(\"\\n\")}` : \"\",\n blocks.length > 0 ? `Blocks the user hasn't answered:\\n${blocks.join(\"\\n\")}` : \"\",\n specialists.length > 0 ? `Specialists still working:\\n${specialists.join(\"\\n\")}` : \"\"\n ].filter((section) => section.length > 0)\n\n return sections.length === 0 ? Option.none() : Option.some(sections.join(\"\\n\\n\"))\n}\n\n/**\n * Returns the files the user attached up to `throughSeq`, as lines of text for the model, or\n * `None` when there are none. A summary may leave out a file's id, which tools need.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const attachedFiles = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const lines = entries.flatMap(({ seq, event }) =>\n seq <= throughSeq && AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"UserMessage\")\n ? (event.attachments ?? []).map(AssistantEvent.attachmentLine)\n : []\n )\n\n return lines.length === 0 ? Option.none() : Option.some(lines.join(\"\\n\"))\n}\n\n/**\n * Creates a history view that starts at the last summary.\n *\n * **Details**\n *\n * `isSummary` identifies the app's summary events. The model reads the last summary, the files\n * attached earlier ({@link attachedFiles}), and the items that are still open\n * ({@link openItems}), followed by the entries after the summary. When the chat has no summary,\n * the view returns every entry.\n *\n * **Example** (Compacting a long chat after each turn)\n *\n * ```ts\n * import { AppEvent, Assistant, type AssistantEvent, Compaction } from \"effect-assistant\"\n * import * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n * import * as Stream from \"effect/Stream\"\n *\n * class Summary extends Schema.TaggedClass<Summary>()(\"Summary\", {\n * text: Schema.String,\n * throughSeq: Schema.Int\n * }) {}\n *\n * declare const transcript: (entries: ReadonlyArray<AssistantEvent.AnyEntry>) => string\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\", { events: [AppEvent.make(Summary)] })\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) => {\n * const compact = Effect.gen(function* () {\n * const chat = yield* MyAssistant.Chat\n * const entries = yield* chat.entries\n *\n * if (Compaction.inputTokens(entries) < 100_000) return\n *\n * const { text } = yield* LanguageModel.generateText({\n * prompt: `Summarize this chat:\\n\\n${transcript(entries)}`\n * })\n *\n * yield* chat.append(Summary.make({ text, throughSeq: entries.at(-1)?.seq ?? 0 }))\n * })\n *\n * // After the reply, so the user doesn't wait for the summary.\n * return Stream.concat(\n * turn.loop({ instructions: \"Be brief.\", history: Compaction.since(Schema.is(Summary)) }),\n * Stream.drain(Stream.fromEffect(compact))\n * )\n * })\n * ```\n *\n * @category utils\n * @since 0.1.0\n */\nexport const since =\n (isSummary: (event: unknown) => event is Summary) =>\n <Events extends { readonly _tag: string }>(\n entries: ReadonlyArray<Assistant.Entry<Events>>\n ): ReadonlyArray<Assistant.Entry<Events> | Prompt.Message> => {\n const last = entries.findLast(({ event }) => isSummary(event))?.event\n\n if (last === undefined || !isSummary(last)) return entries\n\n const open = Option.match(openItems(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (items) => `\\n\\n<still_open>\\n${items}\\n</still_open>`\n })\n\n const files = Option.match(attachedFiles(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (lines) => `\\n\\n<files_attached_earlier>\\n${lines}\\n</files_attached_earlier>`\n })\n\n return [\n userText(`<earlier_in_this_chat>\\n${last.text}\\n</earlier_in_this_chat>${files}${open}`),\n ...entries.filter(({ seq, event }) => seq > last.throughSeq && !isSummary(event))\n ]\n }\n"],"mappings":"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"}
1
+ {"version":3,"file":"Compaction.js","names":["AssistantEvent.isLibraryEvent","BlockKit.fromEntries","AssistantActivity.fromEntries","AssistantEvent.attachmentLine"],"sources":["../src/Compaction.ts"],"sourcesContent":["/**\n * This module provides what a long chat needs so that the model can read a summary in place of\n * everything before it.\n *\n * The app decides when to compact, which model writes the summary, and what the summary says.\n * Its handler reads the chat's entries, has a model write a summary, and stores the summary as one\n * of the assistant's own events (see {@link Summary}). Its loop then reads the chat through\n * {@link since}. {@link inputTokens} helps decide when the prompt has grown large enough to\n * compact. The log keeps every entry, so the user still sees the whole chat.\n *\n * @since 0.1.0\n */\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport type * as Assistant from \"./Assistant.ts\"\nimport * as AssistantActivity from \"./AssistantActivity.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport * as BlockKit from \"./BlockKit.ts\"\nimport { userText } from \"./internal/history.ts\"\n\n/**\n * Represents the fields a summary event must have: its text and the last `seq` it covers.\n *\n * Entries after `throughSeq` are kept as they are, so an input that arrived while the summary was\n * being written isn't lost.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Summary {\n readonly _tag: string\n readonly text: string\n readonly throughSeq: number\n}\n\n/**\n * Returns the number of input tokens used by the chat's last model call, which shows how large\n * the prompt has grown. Returns 0 before the first call.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const inputTokens = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {\n const last = entries.findLast(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ModelOutput\") && event.usage !== undefined\n )?.event\n\n return last !== undefined && AssistantEvent.isLibraryEvent(last) && Predicate.isTagged(last, \"ModelOutput\")\n ? (last.usage?.inputTokens.total ?? 0)\n : 0\n}\n\n/**\n * Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't\n * answered, and specialists that are working. The result is text for\n * the model, or `None` when nothing is open.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const openItems = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const seqOf = (found: (event: AssistantEvent.AnyEntry[\"event\"]) => boolean) =>\n entries.find(({ event }) => found(event))?.seq ?? Number.POSITIVE_INFINITY\n\n const opened = (seq: number) => seq <= throughSeq\n\n const blocks = [...BlockKit.fromEntries(entries)].flatMap(([messageId, state]) => {\n const shown = entries.find(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === messageId\n )\n\n const message = shown?.event\n\n return state.open &&\n shown !== undefined &&\n opened(shown.seq) &&\n message !== undefined &&\n AssistantEvent.isLibraryEvent(message) &&\n Predicate.isTagged(message, \"AssistantMessage\")\n ? [`- message ${messageId}, a ${message.block?.kind} block: ${message.text}`]\n : []\n })\n\n const specialists = [...AssistantActivity.fromEntries(entries).specialists.values()].flatMap((run) =>\n opened(\n seqOf(\n (event) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"SpecialistStarted\") &&\n event.runId === run.runId\n )\n )\n ? [`- your ${run.specialist} specialist (${run.runId}), working on: ${run.goal}`]\n : []\n )\n\n const sections = [\n blocks.length > 0 ? `Blocks the user hasn't answered:\\n${blocks.join(\"\\n\")}` : \"\",\n specialists.length > 0 ? `Specialists still working:\\n${specialists.join(\"\\n\")}` : \"\"\n ].filter((section) => section.length > 0)\n\n return sections.length === 0 ? Option.none() : Option.some(sections.join(\"\\n\\n\"))\n}\n\n/**\n * Returns the files the user attached up to `throughSeq`, as lines of text for the model, or\n * `None` when there are none. A summary may leave out a file's id, which tools need.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const attachedFiles = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number\n): Option.Option<string> => {\n const lines = entries.flatMap(({ seq, event }) =>\n seq <= throughSeq && AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"UserMessage\")\n ? (event.attachments ?? []).map(AssistantEvent.attachmentLine)\n : []\n )\n\n return lines.length === 0 ? Option.none() : Option.some(lines.join(\"\\n\"))\n}\n\n/**\n * Creates a history view that starts at the last summary.\n *\n * **Details**\n *\n * `isSummary` identifies the app's summary events. The model reads the last summary, the files\n * attached earlier ({@link attachedFiles}), and the items that are still open\n * ({@link openItems}), followed by the entries after the summary. When the chat has no summary,\n * the view returns every entry.\n *\n * **Example** (Compacting a long chat after each turn)\n *\n * ```ts\n * import { AppEvent, Assistant, type AssistantEvent, Compaction } from \"effect-assistant\"\n * import * as LanguageModel from \"effect/ai/LanguageModel\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n * import * as Stream from \"effect/Stream\"\n *\n * class Summary extends Schema.TaggedClass<Summary>()(\"Summary\", {\n * text: Schema.String,\n * throughSeq: Schema.Int\n * }) {}\n *\n * declare const transcript: (entries: ReadonlyArray<AssistantEvent.AnyEntry>) => string\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\", { events: [AppEvent.make(Summary)] })\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) => {\n * const compact = Effect.gen(function* () {\n * const chat = yield* MyAssistant.Chat\n * const entries = yield* chat.entries\n *\n * if (Compaction.inputTokens(entries) < 100_000) return\n *\n * const { text } = yield* LanguageModel.generateText({\n * prompt: `Summarize this chat:\\n\\n${transcript(entries)}`\n * })\n *\n * yield* chat.append(Summary.make({ text, throughSeq: entries.at(-1)?.seq ?? 0 }))\n * })\n *\n * // After the reply, so the user doesn't wait for the summary.\n * return Stream.concat(\n * turn.loop({ instructions: \"Be brief.\", history: Compaction.since(Schema.is(Summary)) }),\n * Stream.drain(Stream.fromEffect(compact))\n * )\n * })\n * ```\n *\n * @category utils\n * @since 0.1.0\n */\nexport const since =\n (isSummary: (event: unknown) => event is Summary) =>\n <Events extends { readonly _tag: string }>(\n entries: ReadonlyArray<Assistant.Entry<Events>>\n ): ReadonlyArray<Assistant.Entry<Events> | Prompt.Message> => {\n const last = entries.findLast(({ event }) => isSummary(event))?.event\n\n if (last === undefined || !isSummary(last)) return entries\n\n const open = Option.match(openItems(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (items) => `\\n\\n<still_open>\\n${items}\\n</still_open>`\n })\n\n const files = Option.match(attachedFiles(entries, last.throughSeq), {\n onNone: () => \"\",\n onSome: (lines) => `\\n\\n<files_attached_earlier>\\n${lines}\\n</files_attached_earlier>`\n })\n\n return [\n userText(`<earlier_in_this_chat>\\n${last.text}\\n</earlier_in_this_chat>${files}${open}`),\n ...entries.filter(({ seq, event }) => seq > last.throughSeq && !isSummary(event))\n ]\n }\n"],"mappings":"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"}