effect-assistant 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/dist/Assistant.d.ts +74 -28
  3. package/dist/Assistant.d.ts.map +1 -1
  4. package/dist/Assistant.js +11 -4
  5. package/dist/Assistant.js.map +1 -1
  6. package/dist/AssistantActivity.d.ts +10 -16
  7. package/dist/AssistantActivity.d.ts.map +1 -1
  8. package/dist/AssistantActivity.js +11 -4
  9. package/dist/AssistantActivity.js.map +1 -1
  10. package/dist/AssistantEngine.d.ts +39 -1
  11. package/dist/AssistantEngine.d.ts.map +1 -1
  12. package/dist/AssistantEngine.js +81 -28
  13. package/dist/AssistantEngine.js.map +1 -1
  14. package/dist/AssistantEvent.d.ts +52 -70
  15. package/dist/AssistantEvent.d.ts.map +1 -1
  16. package/dist/AssistantEvent.js +28 -57
  17. package/dist/AssistantEvent.js.map +1 -1
  18. package/dist/AssistantLoop.d.ts +7 -5
  19. package/dist/AssistantLoop.d.ts.map +1 -1
  20. package/dist/AssistantLoop.js +6 -3
  21. package/dist/AssistantLoop.js.map +1 -1
  22. package/dist/Block.js.map +1 -1
  23. package/dist/BlockKit.d.ts +14 -18
  24. package/dist/BlockKit.d.ts.map +1 -1
  25. package/dist/BlockKit.js +4 -7
  26. package/dist/BlockKit.js.map +1 -1
  27. package/dist/ChatLog.js +1 -1
  28. package/dist/Compaction.d.ts +8 -4
  29. package/dist/Compaction.d.ts.map +1 -1
  30. package/dist/Compaction.js +12 -15
  31. package/dist/Compaction.js.map +1 -1
  32. package/dist/CurrentChat.d.ts +2 -1
  33. package/dist/CurrentChat.d.ts.map +1 -1
  34. package/dist/CurrentChat.js.map +1 -1
  35. package/dist/Instructions.js +1 -1
  36. package/dist/Skill.d.ts +33 -0
  37. package/dist/Skill.d.ts.map +1 -1
  38. package/dist/Skill.js +27 -1
  39. package/dist/Skill.js.map +1 -1
  40. package/dist/Specialist.d.ts +4 -1
  41. package/dist/Specialist.d.ts.map +1 -1
  42. package/dist/Specialist.js.map +1 -1
  43. package/dist/Timeline.d.ts +482 -0
  44. package/dist/Timeline.d.ts.map +1 -0
  45. package/dist/Timeline.js +572 -0
  46. package/dist/Timeline.js.map +1 -0
  47. package/dist/ToolDiscovery.d.ts +4 -5
  48. package/dist/ToolDiscovery.d.ts.map +1 -1
  49. package/dist/ToolDiscovery.js +5 -4
  50. package/dist/ToolDiscovery.js.map +1 -1
  51. package/dist/ToolFailure.d.ts +1 -1
  52. package/dist/ToolFailure.js +1 -1
  53. package/dist/ToolFailure.js.map +1 -1
  54. package/dist/index.d.ts +2 -5
  55. package/dist/index.js +4 -7
  56. package/dist/internal/chat.js +11 -37
  57. package/dist/internal/chat.js.map +1 -1
  58. package/dist/internal/events.d.ts +15 -16
  59. package/dist/internal/events.d.ts.map +1 -1
  60. package/dist/internal/events.js +29 -28
  61. package/dist/internal/events.js.map +1 -1
  62. package/dist/internal/history.js +26 -58
  63. package/dist/internal/history.js.map +1 -1
  64. package/dist/internal/input.js +7 -0
  65. package/dist/internal/input.js.map +1 -0
  66. package/dist/internal/loop.d.ts +2 -2
  67. package/dist/internal/loop.d.ts.map +1 -1
  68. package/dist/internal/loop.js +21 -56
  69. package/dist/internal/loop.js.map +1 -1
  70. package/dist/internal/skills.js +6 -1
  71. package/dist/internal/skills.js.map +1 -1
  72. package/dist/internal/specialists.js +7 -7
  73. package/dist/internal/specialists.js.map +1 -1
  74. package/dist/internal/toolkit.d.ts +6 -20
  75. package/dist/internal/toolkit.d.ts.map +1 -1
  76. package/dist/internal/toolkit.js +10 -31
  77. package/dist/internal/toolkit.js.map +1 -1
  78. package/dist/internal/tools.js +2 -21
  79. package/dist/internal/tools.js.map +1 -1
  80. package/dist/internal/turns.d.ts.map +1 -1
  81. package/dist/internal/turns.js +24 -43
  82. package/dist/internal/turns.js.map +1 -1
  83. package/package.json +3 -2
  84. package/src/Assistant.ts +75 -29
  85. package/src/AssistantActivity.ts +11 -18
  86. package/src/AssistantEngine.ts +110 -43
  87. package/src/AssistantEvent.ts +46 -74
  88. package/src/AssistantLoop.ts +7 -5
  89. package/src/Block.ts +1 -1
  90. package/src/BlockKit.ts +18 -31
  91. package/src/Compaction.ts +8 -21
  92. package/src/CurrentChat.ts +2 -1
  93. package/src/Skill.ts +34 -1
  94. package/src/Specialist.ts +4 -1
  95. package/src/Timeline.ts +861 -0
  96. package/src/ToolDiscovery.ts +5 -13
  97. package/src/ToolFailure.ts +1 -1
  98. package/src/index.ts +5 -20
  99. package/src/internal/chat.ts +8 -57
  100. package/src/internal/events.ts +43 -43
  101. package/src/internal/history.ts +36 -101
  102. package/src/internal/input.ts +3 -0
  103. package/src/internal/loop.ts +31 -101
  104. package/src/internal/skills.ts +11 -0
  105. package/src/internal/specialists.ts +6 -9
  106. package/src/internal/toolkit.ts +17 -60
  107. package/src/internal/tools.ts +2 -53
  108. package/src/internal/turns.ts +37 -79
  109. package/dist/AppEvent.d.ts +0 -111
  110. package/dist/AppEvent.d.ts.map +0 -1
  111. package/dist/AppEvent.js +0 -71
  112. package/dist/AppEvent.js.map +0 -1
  113. package/dist/ChatView.d.ts +0 -459
  114. package/dist/ChatView.d.ts.map +0 -1
  115. package/dist/ChatView.js +0 -496
  116. package/dist/ChatView.js.map +0 -1
  117. package/dist/Decision.d.ts +0 -145
  118. package/dist/Decision.d.ts.map +0 -1
  119. package/dist/Decision.js +0 -178
  120. package/dist/Decision.js.map +0 -1
  121. package/dist/DecisionId.d.ts +0 -37
  122. package/dist/DecisionId.d.ts.map +0 -1
  123. package/dist/DecisionId.js +0 -44
  124. package/dist/DecisionId.js.map +0 -1
  125. package/dist/internal/decisions.js +0 -66
  126. package/dist/internal/decisions.js.map +0 -1
  127. package/src/AppEvent.ts +0 -134
  128. package/src/ChatView.ts +0 -658
  129. package/src/Decision.ts +0 -241
  130. package/src/DecisionId.ts +0 -46
  131. package/src/internal/decisions.ts +0 -131
@@ -1,6 +1,5 @@
1
1
  import type * as LanguageModel from "effect/ai/LanguageModel"
2
2
  import type * as Tool from "effect/ai/Tool"
3
- import type * as Prompt from "effect/ai/Prompt"
4
3
  import * as Cause from "effect/Cause"
5
4
  import * as Context from "effect/Context"
6
5
  import * as Effect from "effect/Effect"
@@ -8,6 +7,7 @@ import * as Exit from "effect/Exit"
8
7
  import { identity } from "effect/Function"
9
8
  import * as Option from "effect/Option"
10
9
  import * as Predicate from "effect/Predicate"
10
+ import * as Ref from "effect/Ref"
11
11
  import * as Schema from "effect/Schema"
12
12
  import type * as Scope from "effect/Scope"
13
13
  import * as Stream from "effect/Stream"
@@ -18,12 +18,10 @@ import type * as AssistantLoop from "../AssistantLoop.ts"
18
18
  import * as Loop from "./loop.ts"
19
19
  import type * as BlockKit from "../BlockKit.ts"
20
20
  import type { CurrentChat } from "../CurrentChat.ts"
21
- import * as Decision from "../Decision.ts"
22
21
  import * as Instructions from "../Instructions.ts"
23
22
  import * as Skill from "../Skill.ts"
24
23
  import * as Skills from "./skills.ts"
25
24
  import type * as Specialist from "../Specialist.ts"
26
- import * as Decisions from "./decisions.ts"
27
25
  import * as AppEvents from "./events.ts"
28
26
  import * as History from "./history.ts"
29
27
  import { mapText } from "./instructions.ts"
@@ -87,12 +85,6 @@ export const register = Effect.fnUntraced(function* (
87
85
  ) {
88
86
  const model = yield* Effect.context<LanguageModel.LanguageModel>()
89
87
 
90
- const decisionBlock = definition.blocks?.byKind.get(Decision.kind)
91
-
92
- if (decisionBlock !== undefined && decisionBlock !== Decision.block) {
93
- return yield* Effect.die(`The ${Decision.kind} block kind is the library's: use Decision.block.`)
94
- }
95
-
96
88
  const specialists = yield* Effect.forEach(
97
89
  definition.specialists,
98
90
  Effect.fnUntraced(function* (specialist) {
@@ -135,34 +127,6 @@ const failureMessage = (cause: Cause.Cause<unknown>): string =>
135
127
 
136
128
  const lastSeq = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) => entries.at(-1)?.seq ?? otherwise
137
129
 
138
- const toHistory = (
139
- viewed: ReadonlyArray<Assistant.Entry<any> | Prompt.Message>,
140
- app: AppEvents.AppEvents
141
- ): ReadonlyArray<Prompt.Message> => {
142
- const out: Array<Prompt.Message> = []
143
- let run: Array<AssistantEvent.AnyEntry> = []
144
-
145
- const flush = () => {
146
- out.push(...History.fromEntries(run, (event) => AppEvents.read(app, event)))
147
- run = []
148
- }
149
-
150
- for (const item of viewed) {
151
- if (isEntry(item)) run.push(item)
152
- else {
153
- flush()
154
- out.push(item)
155
- }
156
- }
157
-
158
- flush()
159
-
160
- return out
161
- }
162
-
163
- const isEntry = (item: Assistant.Entry<any> | Prompt.Message): item is Assistant.Entry<any> =>
164
- Predicate.hasProperty(item, "seq")
165
-
166
130
  // The handler's stream can end between a step's output and its tools, or while they run. An interrupted turn (a
167
131
  // shutdown) leaves its calls open instead: the next turn runs them again.
168
132
  const closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSeq: number) {
@@ -172,10 +136,7 @@ const closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSeq: numb
172
136
  entries.flatMap(({ event }) => (Predicate.isTagged(event, "ToolStarted") ? [event.callId] : []))
173
137
  )
174
138
 
175
- // Approved calls this turn started that have no result are cut off like any other; one not started yet waits.
176
- const all = yield* chat.entries
177
- const approved = Decisions.approvedCalls(all).filter((call) => started.has(call.id))
178
- const open = [...History.unansweredCalls(History.fromEntries(entries, () => undefined)), ...approved]
139
+ const open = History.unansweredCalls(History.fromEntries(entries))
179
140
 
180
141
  if (open.length === 0) return
181
142
 
@@ -207,13 +168,13 @@ const handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, startSe
207
168
  startSeq
208
169
  )
209
170
 
210
- // Fails visibly when the user's latest input got no reply; an input that arrived after what its model read gets the
211
- // next turn, which replies.
212
- const finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, wakes: ReadonlySet<string>) {
171
+ // A turn that ran the loop fails visibly when the user's latest input got no reply; an input that arrived after what
172
+ // its model read gets the next turn, which replies. A handler that ended without the loop decided that itself.
173
+ const finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, ranLoop: boolean) {
213
174
  const entries = yield* chat.entries
214
175
 
215
176
  const end =
216
- History.owesReply(entries, read) && !History.unreadAfter(entries, read, wakes)
177
+ ranLoop && History.owesReply(entries, read) && !History.unreadAfter(entries, read)
217
178
  ? AssistantEvent.TurnFailed.make({ throughSeq: read, message: silentTurnMessage })
218
179
  : AssistantEvent.TurnCompleted.make({ throughSeq: read })
219
180
 
@@ -233,11 +194,15 @@ const makeTurnApi = (
233
194
  registration: Registration,
234
195
  chat: Writer,
235
196
  startSeq: number,
236
- checkOpen: Effect.Effect<void>
197
+ checkOpen: Effect.Effect<void>,
198
+ ranLoop: Ref.Ref<boolean>
237
199
  ): Assistant.Turn<any> => {
238
200
  const { definition, builtins } = registration
239
201
  const { app } = definition
240
202
 
203
+ const running = <A, E, R>(stream: Stream.Stream<A, E, R>) =>
204
+ Stream.unwrap(Ref.set(ranLoop, true).pipe(Effect.as(stream)))
205
+
241
206
  // With how far into the chat it goes: the step records it on the model's output.
242
207
  // Read before every model call: a chat the app deleted meanwhile stops here.
243
208
  const history = Effect.fnUntraced(function* (view: Assistant.HistoryView<any> = identity) {
@@ -246,7 +211,7 @@ const makeTurnApi = (
246
211
  const all = yield* chat.entries
247
212
 
248
213
  const read: AssistantLoop.ChatHistory = {
249
- messages: toHistory(view(AppEvents.decodeEntries(app, all)), app),
214
+ messages: History.fromEntries(view(AppEvents.decodeEntries(app, all))),
250
215
  throughSeq: lastSeq(all, startSeq)
251
216
  }
252
217
 
@@ -269,7 +234,7 @@ const makeTurnApi = (
269
234
  const through = handledThrough(all, startSeq)
270
235
 
271
236
  // Something arrived after what the loop's model read: the next turn reads it with the full loop, and replies.
272
- if (History.unreadAfter(all, through, app.wakes)) return Stream.empty
237
+ if (History.unreadAfter(all, through)) return Stream.empty
273
238
 
274
239
  const owed = History.owesReply(all, through)
275
240
  const undelivered = !owed && History.owesDelivery(all, through)
@@ -293,7 +258,7 @@ const makeTurnApi = (
293
258
 
294
259
  return {
295
260
  messages: [
296
- ...toHistory(options.view(AppEvents.decodeEntries(app, entries)), app),
261
+ ...History.fromEntries(options.view(AppEvents.decodeEntries(app, entries))),
297
262
  History.userText(nudge)
298
263
  ],
299
264
  throughSeq: lastSeq(entries, startSeq)
@@ -306,22 +271,22 @@ const makeTurnApi = (
306
271
  )
307
272
 
308
273
  const replyIfOwed = (options: Assistant.ReplyOptions) =>
309
- replyPass({
310
- instructions: instructions(options.instructions),
311
- toolkit: builtins,
312
- view: identity,
313
- nudge: options.nudge
314
- })
274
+ running(
275
+ replyPass({
276
+ instructions: instructions(options.instructions),
277
+ toolkit: builtins,
278
+ view: identity,
279
+ nudge: options.nudge
280
+ })
281
+ )
315
282
 
316
283
  const prepare = <T extends Record<string, Tool.Any>, RS>(options: Assistant.LoopOptions<T, RS, any>) => {
317
- const skills = options.skills ?? []
284
+ const own = Toolkits.fromToolkitOrEmpty(options.toolkit)
285
+ const skills = Skills.available(options.skills ?? [], [...builtins.definitions, ...own.definitions])
318
286
 
319
287
  return {
320
288
  system: mapText(instructions(options.instructions), (text) => Skill.withCatalog(text, skills)),
321
- toolkit: Toolkits.merge(
322
- Toolkits.merge(builtins, Skills.toTools(skills)),
323
- Toolkits.fromToolkitOrEmpty(options.toolkit)
324
- ),
289
+ toolkit: Toolkits.merge(Toolkits.merge(builtins, Skills.toTools(skills)), own),
325
290
  view: options.history ?? identity
326
291
  }
327
292
  }
@@ -335,25 +300,17 @@ const makeTurnApi = (
335
300
  const { system, toolkit, view } = prepare(options)
336
301
  const twice = Toolkits.duplicateNames(toolkit)
337
302
 
338
- const deciding = toolkit.definitions.filter(
339
- (tool) => tool.needsApproval !== undefined && tool.needsApproval !== false
340
- )
341
-
342
303
  const loop =
343
304
  twice.length > 0
344
305
  ? Stream.die(`Tool names used twice, like one named after a built-in: ${twice.join(", ")}`)
345
- : deciding.length > 0 && definition.blocks?.byKind.get(Decision.kind) === undefined
346
- ? Stream.die(
347
- `${deciding.map((tool) => tool.name).join(", ")} can wait for the user's decision: add Decision.block to the assistant's blocks.`
348
- )
349
- : Loop.run({
350
- instructions: system,
351
- toolkit,
352
- history: history(view),
353
- maxSteps: options.maxSteps ?? 16
354
- })
355
-
356
- return Stream.concat(loop, replyPass({ instructions: system, toolkit, view, nudge: options.replyNudge }))
306
+ : Loop.run({
307
+ instructions: system,
308
+ toolkit,
309
+ history: history(view),
310
+ maxSteps: options.maxSteps ?? 16
311
+ })
312
+
313
+ return running(Stream.concat(loop, replyPass({ instructions: system, toolkit, view, nudge: options.replyNudge })))
357
314
  }
358
315
  }
359
316
  }
@@ -372,7 +329,7 @@ export const run = Effect.fnUntraced(
372
329
  const { chat } = options
373
330
  const before = yield* chat.entries
374
331
 
375
- yield* Telemetry.agentInput(History.unhandledInputs(before, options.registration.definition.app.wakes))
332
+ yield* Telemetry.agentInput(History.unhandledInputs(before))
376
333
 
377
334
  const assistant = options.registration.definition.name
378
335
  const traceId = yield* Telemetry.currentTraceId
@@ -387,7 +344,8 @@ export const run = Effect.fnUntraced(
387
344
  ])
388
345
 
389
346
  const startSeq = lastSeq(started, 0)
390
- const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen)
347
+ const ranLoop = yield* Ref.make(false)
348
+ const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen, ranLoop)
391
349
 
392
350
  const record = (event: AssistantLoop.Event) => chat.append([event])
393
351
 
@@ -404,7 +362,7 @@ export const run = Effect.fnUntraced(
404
362
  onSuccess: Effect.fnUntraced(function* () {
405
363
  const all = yield* chat.entries
406
364
 
407
- yield* finishTurn(chat, handledThrough(all, startSeq), options.registration.definition.app.wakes)
365
+ yield* finishTurn(chat, handledThrough(all, startSeq), yield* Ref.get(ranLoop))
408
366
  }),
409
367
  onFailure: (cause) => failTurn(chat, cause)
410
368
  })
@@ -1,111 +0,0 @@
1
- import * as SchemaAST from "effect/SchemaAST";
2
- import * as Schema from "effect/Schema";
3
- declare namespace AppEvent_d_exports {
4
- export { Any, AppEvent, EventSchema, Render, Type, make };
5
- }
6
- /**
7
- * Schema for one of the application's own events.
8
- *
9
- * It is a tagged struct or class (`Schema.TaggedStruct`, `Schema.TaggedClass`) that needs no
10
- * services.
11
- *
12
- * @category models
13
- * @since 0.1.0
14
- */
15
- export type EventSchema = Schema.Codec<{
16
- readonly _tag: string;
17
- }, {
18
- readonly _tag: string;
19
- }> & {
20
- readonly fields: {
21
- readonly _tag: {
22
- readonly ast: SchemaAST.AST;
23
- };
24
- };
25
- };
26
- /**
27
- * Describes how the model reads an event.
28
- *
29
- * `"json"` shows the event's tag and fields as JSON. A function shows the text it returns for the
30
- * event.
31
- *
32
- * @category models
33
- * @since 0.1.0
34
- */
35
- export type Render<A> = "json" | ((event: A) => string);
36
- /**
37
- * Represents one of the application's events, registered with an assistant.
38
- *
39
- * It holds the event's schema and tag, whether storing it wakes a turn, and how the model reads
40
- * it. A `render` of `undefined` means the model doesn't read it.
41
- *
42
- * @category models
43
- * @since 0.1.0
44
- */
45
- export interface AppEvent<S extends EventSchema = EventSchema> {
46
- readonly schema: S;
47
- readonly tag: string;
48
- readonly wakes: boolean;
49
- readonly render: Render<S["Type"]> | undefined;
50
- }
51
- /**
52
- * Represents any registered event, for generic constraints.
53
- *
54
- * @category utility types
55
- * @since 0.1.0
56
- */
57
- export type Any = AppEvent<any>;
58
- /**
59
- * Extracts the type of a registered event's values.
60
- *
61
- * @category utility types
62
- * @since 0.1.0
63
- */
64
- export type Type<E extends Any> = E["schema"]["Type"];
65
- /**
66
- * Registers one of the application's events with an assistant.
67
- *
68
- * **Details**
69
- *
70
- * Storing the event wakes a turn only when `wakes` is `true`; it defaults to `false`. `render`
71
- * defaults to `"json"` for an event that wakes a turn. An event that doesn't wake a turn and has
72
- * no `render` isn't read by the model.
73
- *
74
- * The schema must be a tagged struct or class whose `_tag` is a string literal; `make` throws
75
- * otherwise.
76
- *
77
- * **Example** (Registering an event that wakes a turn)
78
- *
79
- * ```ts
80
- * import { AppEvent, Assistant, InputId } from "effect-assistant"
81
- * import * as Schema from "effect/Schema"
82
- *
83
- * class OrderShipped extends Schema.TaggedClass<OrderShipped>()("OrderShipped", {
84
- * id: InputId.InputId,
85
- * orderId: Schema.String,
86
- * carrier: Schema.String
87
- * }) {}
88
- *
89
- * export const MyAssistant = Assistant.make("my-assistant", {
90
- * events: [
91
- * AppEvent.make(OrderShipped, {
92
- * wakes: true,
93
- * render: (shipped) =>
94
- * `Order ${shipped.orderId} shipped with ${shipped.carrier}. Tell the user if it matters.`
95
- * })
96
- * ]
97
- * })
98
- * ```
99
- *
100
- * @category constructors
101
- * @since 0.1.0
102
- */
103
- export declare const make: <S extends EventSchema>(schema: S, options?: {
104
- /** Whether storing it starts a turn, like a user's message. Default false. */
105
- readonly wakes?: boolean | undefined;
106
- /** How the model reads it in its history. */
107
- readonly render?: Render<S["Type"]> | undefined;
108
- }) => AppEvent<S>;
109
- //#endregion
110
- export { AppEvent_d_exports };
111
- //# sourceMappingURL=AppEvent.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppEvent.d.ts","names":[],"sources":["../src/AppEvent.ts"],"mappings":";;;;;;;;;;;;;;YAuBY,cAAc,OAAO;WAAiB;;WAA2B;;WAClE;aAAmB;eAAiB,KAAK,UAAU;;;;;;;;;;;;;YAYlD,OAAO,gBAAgB,OAAO;;;;;;;;;;iBAWzB,SAAS,UAAU,cAAc;WACvC,QAAQ;WACR;WACA;WACA,QAAQ,OAAO;;;;;;;;YASd,MAAM;;;;;;;YAQN,KAAK,UAAU,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgDrB,OAAQ,UAAU,aAAW,QAChC,GAAC;;WAGE;;WAEA,SAAS,OAAO;MAE1B,SAAS"}
package/dist/AppEvent.js DELETED
@@ -1,71 +0,0 @@
1
- import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
- import * as Predicate from "effect/Predicate";
3
- import * as SchemaAST from "effect/SchemaAST";
4
- //#region src/AppEvent.ts
5
- /**
6
- * Registers an application's own events with an assistant.
7
- *
8
- * An app event is a tagged schema for something the application reports into a chat, such as a
9
- * workflow's progress, a webhook, a summary for compaction, or the draft the user opened.
10
- * {@link make} registers one with two choices: whether storing it wakes a turn (`wakes`), and
11
- * how the model reads it (`render`).
12
- *
13
- * @since 0.1.0
14
- */
15
- var AppEvent_exports = /* @__PURE__ */ __exportAll({ make: () => make });
16
- const tagOf = (schema) => {
17
- const ast = schema.fields._tag.ast;
18
- if (SchemaAST.isLiteral(ast) && Predicate.isString(ast.literal)) return ast.literal;
19
- throw new Error("An app event is a tagged struct or class.");
20
- };
21
- /**
22
- * Registers one of the application's events with an assistant.
23
- *
24
- * **Details**
25
- *
26
- * Storing the event wakes a turn only when `wakes` is `true`; it defaults to `false`. `render`
27
- * defaults to `"json"` for an event that wakes a turn. An event that doesn't wake a turn and has
28
- * no `render` isn't read by the model.
29
- *
30
- * The schema must be a tagged struct or class whose `_tag` is a string literal; `make` throws
31
- * otherwise.
32
- *
33
- * **Example** (Registering an event that wakes a turn)
34
- *
35
- * ```ts
36
- * import { AppEvent, Assistant, InputId } from "effect-assistant"
37
- * import * as Schema from "effect/Schema"
38
- *
39
- * class OrderShipped extends Schema.TaggedClass<OrderShipped>()("OrderShipped", {
40
- * id: InputId.InputId,
41
- * orderId: Schema.String,
42
- * carrier: Schema.String
43
- * }) {}
44
- *
45
- * export const MyAssistant = Assistant.make("my-assistant", {
46
- * events: [
47
- * AppEvent.make(OrderShipped, {
48
- * wakes: true,
49
- * render: (shipped) =>
50
- * `Order ${shipped.orderId} shipped with ${shipped.carrier}. Tell the user if it matters.`
51
- * })
52
- * ]
53
- * })
54
- * ```
55
- *
56
- * @category constructors
57
- * @since 0.1.0
58
- */
59
- const make = (schema, options) => {
60
- const wakes = options?.wakes ?? false;
61
- return {
62
- schema,
63
- tag: tagOf(schema),
64
- wakes,
65
- render: options?.render ?? (wakes ? "json" : void 0)
66
- };
67
- };
68
- //#endregion
69
- export { AppEvent_exports, make };
70
-
71
- //# sourceMappingURL=AppEvent.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppEvent.js","names":[],"sources":["../src/AppEvent.ts"],"sourcesContent":["/**\n * Registers an application's own events with an assistant.\n *\n * An app event is a tagged schema for something the application reports into a chat, such as a\n * workflow's progress, a webhook, a summary for compaction, or the draft the user opened.\n * {@link make} registers one with two choices: whether storing it wakes a turn (`wakes`), and\n * how the model reads it (`render`).\n *\n * @since 0.1.0\n */\nimport * as Predicate from \"effect/Predicate\"\nimport type * as Schema from \"effect/Schema\"\nimport * as SchemaAST from \"effect/SchemaAST\"\n\n/**\n * Schema for one of the application's own events.\n *\n * It is a tagged struct or class (`Schema.TaggedStruct`, `Schema.TaggedClass`) that needs no\n * services.\n *\n * @category models\n * @since 0.1.0\n */\nexport type EventSchema = Schema.Codec<{ readonly _tag: string }, { readonly _tag: string }> & {\n readonly fields: { readonly _tag: { readonly ast: SchemaAST.AST } }\n}\n\n/**\n * Describes how the model reads an event.\n *\n * `\"json\"` shows the event's tag and fields as JSON. A function shows the text it returns for the\n * event.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Render<A> = \"json\" | ((event: A) => string)\n\n/**\n * Represents one of the application's events, registered with an assistant.\n *\n * It holds the event's schema and tag, whether storing it wakes a turn, and how the model reads\n * it. A `render` of `undefined` means the model doesn't read it.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface AppEvent<S extends EventSchema = EventSchema> {\n readonly schema: S\n readonly tag: string\n readonly wakes: boolean\n readonly render: Render<S[\"Type\"]> | undefined\n}\n\n/**\n * Represents any registered event, for generic constraints.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type Any = AppEvent<any>\n\n/**\n * Extracts the type of a registered event's values.\n *\n * @category utility types\n * @since 0.1.0\n */\nexport type Type<E extends Any> = E[\"schema\"][\"Type\"]\n\nconst tagOf = (schema: EventSchema): string => {\n const ast = schema.fields._tag.ast\n\n if (SchemaAST.isLiteral(ast) && Predicate.isString(ast.literal)) return ast.literal\n\n throw new Error(\"An app event is a tagged struct or class.\")\n}\n\n/**\n * Registers one of the application's events with an assistant.\n *\n * **Details**\n *\n * Storing the event wakes a turn only when `wakes` is `true`; it defaults to `false`. `render`\n * defaults to `\"json\"` for an event that wakes a turn. An event that doesn't wake a turn and has\n * no `render` isn't read by the model.\n *\n * The schema must be a tagged struct or class whose `_tag` is a string literal; `make` throws\n * otherwise.\n *\n * **Example** (Registering an event that wakes a turn)\n *\n * ```ts\n * import { AppEvent, Assistant, InputId } from \"effect-assistant\"\n * import * as Schema from \"effect/Schema\"\n *\n * class OrderShipped extends Schema.TaggedClass<OrderShipped>()(\"OrderShipped\", {\n * id: InputId.InputId,\n * orderId: Schema.String,\n * carrier: Schema.String\n * }) {}\n *\n * export const MyAssistant = Assistant.make(\"my-assistant\", {\n * events: [\n * AppEvent.make(OrderShipped, {\n * wakes: true,\n * render: (shipped) =>\n * `Order ${shipped.orderId} shipped with ${shipped.carrier}. Tell the user if it matters.`\n * })\n * ]\n * })\n * ```\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <S extends EventSchema>(\n schema: S,\n options?: {\n /** Whether storing it starts a turn, like a user's message. Default false. */\n readonly wakes?: boolean | undefined\n /** How the model reads it in its history. */\n readonly render?: Render<S[\"Type\"]> | undefined\n }\n): AppEvent<S> => {\n const wakes = options?.wakes ?? false\n\n return {\n schema,\n tag: tagOf(schema),\n wakes,\n render: options?.render ?? (wakes ? \"json\" : undefined)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsEA,MAAM,SAAS,WAAgC;CAC7C,MAAM,MAAM,OAAO,OAAO,KAAK;CAE/B,IAAI,UAAU,UAAU,GAAG,KAAK,UAAU,SAAS,IAAI,OAAO,GAAG,OAAO,IAAI;CAE5E,MAAM,IAAI,MAAM,2CAA2C;AAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,QACX,QACA,YAMgB;CAChB,MAAM,QAAQ,SAAS,SAAS;CAEhC,OAAO;EACL;EACA,KAAK,MAAM,MAAM;EACjB;EACA,QAAQ,SAAS,WAAW,QAAQ,SAAS,KAAA;CAC/C;AACF"}