effect-assistant 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/Assistant.d.ts +10 -10
  2. package/dist/Assistant.d.ts.map +1 -1
  3. package/dist/Assistant.js +1 -1
  4. package/dist/Assistant.js.map +1 -1
  5. package/dist/AssistantActivity.d.ts +2 -2
  6. package/dist/AssistantActivity.d.ts.map +1 -1
  7. package/dist/AssistantActivity.js +5 -5
  8. package/dist/AssistantActivity.js.map +1 -1
  9. package/dist/AssistantEngine.d.ts +3 -3
  10. package/dist/AssistantEngine.js +2 -2
  11. package/dist/AssistantEngine.js.map +1 -1
  12. package/dist/AssistantEvent.d.ts +8 -8
  13. package/dist/AssistantEvent.js +7 -7
  14. package/dist/AssistantEvent.js.map +1 -1
  15. package/dist/AssistantLoop.d.ts +2 -2
  16. package/dist/AssistantLoop.js.map +1 -1
  17. package/dist/ChatLog.d.ts +9 -9
  18. package/dist/ChatLog.d.ts.map +1 -1
  19. package/dist/ChatLog.js +21 -21
  20. package/dist/ChatLog.js.map +1 -1
  21. package/dist/Compaction.d.ts +10 -10
  22. package/dist/Compaction.d.ts.map +1 -1
  23. package/dist/Compaction.js +16 -16
  24. package/dist/Compaction.js.map +1 -1
  25. package/dist/Memory.d.ts +1 -1
  26. package/dist/Memory.js +1 -1
  27. package/dist/Memory.js.map +1 -1
  28. package/dist/Skill.d.ts +1 -1
  29. package/dist/Skill.js +1 -1
  30. package/dist/Skill.js.map +1 -1
  31. package/dist/Specialist.d.ts +1 -1
  32. package/dist/Specialist.js +1 -1
  33. package/dist/Specialist.js.map +1 -1
  34. package/dist/Timeline.d.ts +9 -9
  35. package/dist/Timeline.js +16 -16
  36. package/dist/Timeline.js.map +1 -1
  37. package/dist/ToolApproval.d.ts +1 -1
  38. package/dist/ToolApproval.js +1 -1
  39. package/dist/ToolApproval.js.map +1 -1
  40. package/dist/ToolDiscovery.d.ts +1 -1
  41. package/dist/ToolDiscovery.js +1 -1
  42. package/dist/ToolDiscovery.js.map +1 -1
  43. package/dist/internal/chat.js +4 -4
  44. package/dist/internal/chat.js.map +1 -1
  45. package/dist/internal/events.js +1 -1
  46. package/dist/internal/events.js.map +1 -1
  47. package/dist/internal/history.js +23 -23
  48. package/dist/internal/history.js.map +1 -1
  49. package/dist/internal/loop.d.ts +1 -1
  50. package/dist/internal/loop.js +4 -4
  51. package/dist/internal/loop.js.map +1 -1
  52. package/dist/internal/turns.d.ts.map +1 -1
  53. package/dist/internal/turns.js +18 -18
  54. package/dist/internal/turns.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/Assistant.ts +11 -11
  57. package/src/AssistantActivity.ts +9 -7
  58. package/src/AssistantEngine.ts +3 -3
  59. package/src/AssistantEvent.ts +8 -8
  60. package/src/AssistantLoop.ts +3 -3
  61. package/src/ChatLog.ts +31 -26
  62. package/src/Compaction.ts +21 -21
  63. package/src/Memory.ts +1 -1
  64. package/src/Skill.ts +1 -1
  65. package/src/Specialist.ts +1 -1
  66. package/src/Timeline.ts +22 -22
  67. package/src/ToolApproval.ts +1 -1
  68. package/src/ToolDiscovery.ts +1 -1
  69. package/src/internal/chat.ts +4 -4
  70. package/src/internal/events.ts +1 -1
  71. package/src/internal/history.ts +32 -28
  72. package/src/internal/loop.ts +5 -5
  73. package/src/internal/turns.ts +28 -26
package/src/Timeline.ts CHANGED
@@ -246,13 +246,13 @@ export type Activity = typeof Activity.Type
246
246
  /**
247
247
  * Represents a chat's timeline: the chat as its user sees it, typed with the app's events it shows.
248
248
  * It holds the chat's items in order, what is working in it, whether the last turn failed, and
249
- * whether the chat was deleted. `seq` is the position in the chat's log that the timeline is up to.
249
+ * whether the chat was deleted. `sequence` is the position in the chat's log that the timeline is up to.
250
250
  *
251
251
  * @category models
252
252
  * @since 0.3.0
253
253
  */
254
254
  export interface Timeline<Events = never> {
255
- readonly seq: number
255
+ readonly sequence: number
256
256
  readonly items: ReadonlyArray<Item<Events>>
257
257
  readonly activity: Activity
258
258
  /** Whether the last turn failed with no message after it. */
@@ -268,14 +268,14 @@ export interface Timeline<Events = never> {
268
268
  *
269
269
  * A change holds each item that is new or changed (for example, a message whose block was
270
270
  * answered or closed, or a call the user approved), in full. It also holds the current activity, whether the last turn failed,
271
- * and whether the chat was deleted. `seq` is the position in the chat's log that the change is up
271
+ * and whether the chat was deleted. `sequence` is the position in the chat's log that the change is up
272
272
  * to.
273
273
  *
274
274
  * @category models
275
275
  * @since 0.3.0
276
276
  */
277
277
  export interface Change<Events = never> {
278
- readonly seq: number
278
+ readonly sequence: number
279
279
  readonly items: ReadonlyArray<Item<Events>>
280
280
  readonly activity: Activity
281
281
  readonly failed: boolean
@@ -298,7 +298,7 @@ function makeChange(events: ReadonlyArray<AssistantEvent.AppEventSchema> = []):
298
298
  const encodeItem = Schema.encodeUnknownSync(AnyItem)
299
299
 
300
300
  return Schema.Struct({
301
- seq: Schema.Int,
301
+ sequence: Schema.Int,
302
302
  // An item for an event this schema doesn't name, or that doesn't decode (from a newer server, say), is left out
303
303
  // rather than failing the whole change.
304
304
  items: Schema.Array(Schema.Json).pipe(
@@ -361,7 +361,7 @@ export const Change: <const Events extends ReadonlyArray<AssistantEvent.AppEvent
361
361
  * @since 0.1.0
362
362
  */
363
363
  export const empty: Timeline = {
364
- seq: 0,
364
+ sequence: 0,
365
365
  items: [],
366
366
  activity: { working: false, specialists: [] },
367
367
  failed: false,
@@ -407,7 +407,7 @@ export const apply = <Events>(timeline: Timeline<Events>, change: Change<Events>
407
407
  const known = new Set(timeline.items.map((item) => item.id))
408
408
 
409
409
  return {
410
- seq: Math.max(timeline.seq, change.seq),
410
+ sequence: Math.max(timeline.sequence, change.sequence),
411
411
  items: [
412
412
  ...timeline.items.map((item) => changed.get(item.id) ?? item),
413
413
  ...change.items.filter((item) => !known.has(item.id))
@@ -465,7 +465,7 @@ type AnyItem = UserMessage | AssistantMessage | AnyEventItem | ApprovalItem
465
465
 
466
466
  // What the fold below builds: the overloads that use it type its items with the events shown.
467
467
  interface AnyChange {
468
- readonly seq: number
468
+ readonly sequence: number
469
469
  readonly items: ReadonlyArray<AnyItem>
470
470
  readonly activity: Activity
471
471
  readonly failed: boolean
@@ -622,7 +622,7 @@ const sameActivity = (a: Activity, b: Activity) =>
622
622
  const folding = (show: ReadonlyArray<AssistantEvent.AppEventSchema>): Folding => {
623
623
  const shown = new Map(show.map((schema) => [AppEvents.tagOf(schema), schema]))
624
624
  let approvals: ReadonlyMap<string, ToolApproval.State> = new Map()
625
- let seq = 0
625
+ let sequence = 0
626
626
  let failed = false
627
627
  let deleted = false
628
628
  let activity = AssistantActivity.empty
@@ -729,7 +729,7 @@ const folding = (show: ReadonlyArray<AssistantEvent.AppEventSchema>): Folding =>
729
729
 
730
730
  return Option.map(event, (decoded) => ({
731
731
  _tag: decoded._tag,
732
- id: `app-event:${entry.seq}`,
732
+ id: `app-event:${entry.sequence}`,
733
733
  at: entry.at,
734
734
  event: decoded
735
735
  }))
@@ -769,7 +769,7 @@ const folding = (show: ReadonlyArray<AssistantEvent.AppEventSchema>): Folding =>
769
769
  const previous = { activity: activityOf(activity), failed, deleted }
770
770
  const { event } = entry
771
771
 
772
- seq = entry.seq
772
+ sequence = entry.sequence
773
773
  activity = AssistantActivity.apply(activity, entry)
774
774
 
775
775
  if (!AssistantEvent.isLibraryEvent(event)) {
@@ -777,11 +777,11 @@ const folding = (show: ReadonlyArray<AssistantEvent.AppEventSchema>): Folding =>
777
777
 
778
778
  return Option.match(eventItemOf(entry), {
779
779
  onNone: () =>
780
- sameActivity(previous.activity, now) ? undefined : { seq, items: [], activity: now, failed, deleted },
780
+ sameActivity(previous.activity, now) ? undefined : { sequence, items: [], activity: now, failed, deleted },
781
781
  onSome: (item) => {
782
782
  items.set(item.id, item)
783
783
 
784
- return { seq, items: [item], activity: now, failed, deleted }
784
+ return { sequence, items: [item], activity: now, failed, deleted }
785
785
  }
786
786
  })
787
787
  }
@@ -811,13 +811,13 @@ const folding = (show: ReadonlyArray<AssistantEvent.AppEventSchema>): Folding =>
811
811
  deleted === previous.deleted &&
812
812
  sameActivity(previous.activity, now)
813
813
  ? undefined
814
- : { seq, items: all, activity: now, failed, deleted }
814
+ : { sequence, items: all, activity: now, failed, deleted }
815
815
  }
816
816
 
817
817
  return {
818
818
  step,
819
819
  get snapshot() {
820
- return { seq, items: [...items.values()], activity: activityOf(activity), failed, deleted }
820
+ return { sequence, items: [...items.values()], activity: activityOf(activity), failed, deleted }
821
821
  }
822
822
  }
823
823
  }
@@ -832,7 +832,7 @@ const merge = (changes: ReadonlyArray<AnyChange>): AnyChange | undefined => {
832
832
  for (const change of changes) for (const item of change.items) items.set(item.id, item)
833
833
 
834
834
  return {
835
- seq: last.seq,
835
+ sequence: last.sequence,
836
836
  items: [...items.values()],
837
837
  activity: last.activity,
838
838
  failed: last.failed,
@@ -864,7 +864,7 @@ export interface ChangesOptions<
864
864
  Show extends ReadonlyArray<AssistantEvent.AppEventSchema> = readonly []
865
865
  > extends ShowOptions<Show> {
866
866
  /**
867
- * The `seq` up to which the client already has the timeline. The result contains only the
867
+ * The `sequence` up to which the client already has the timeline. The result contains only the
868
868
  * changes after it. Defaults to `0`, every change.
869
869
  */
870
870
  readonly after?: number | undefined
@@ -889,7 +889,7 @@ function changesWith<E, R>(
889
889
  const changed = chunk.flatMap((entry) => {
890
890
  const change = timeline.step(entry)
891
891
 
892
- return change === undefined || entry.seq <= after ? [] : [change]
892
+ return change === undefined || entry.sequence <= after ? [] : [change]
893
893
  })
894
894
 
895
895
  const merged = merge(changed)
@@ -907,9 +907,9 @@ function changesWith<E, R>(
907
907
  *
908
908
  * The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does
909
909
  * without `after`. The fold reads every entry, because a message depends on earlier entries: its
910
- * block is answered or closed later. `after` only skips emitting the changes up to that `seq`, so a
911
- * client that has the timeline up to a `seq` receives only what changed since. Pass `after` here,
912
- * not to `subscribe`: a subscription that starts after a `seq` gives the fold an incomplete log,
910
+ * block is answered or closed later. `after` only skips emitting the changes up to that `sequence`, so a
911
+ * client that has the timeline up to a `sequence` receives only what changed since. Pass `after` here,
912
+ * not to `subscribe`: a subscription that starts after a `sequence` gives the fold an incomplete log,
913
913
  * and the changes it produces are wrong.
914
914
  *
915
915
  * Each chunk of entries becomes one change; the stored entries, for example, arrive as one chunk.
@@ -928,7 +928,7 @@ function changesWith<E, R>(
928
928
  * import * as Schema from "effect/Schema"
929
929
  * import * as Stream from "effect/Stream"
930
930
  *
931
- * class Summary extends Schema.TaggedClass<Summary>()("Summary", { text: Schema.String, throughSeq: Schema.Int }) {}
931
+ * class Summary extends Schema.TaggedClass<Summary>()("Summary", { text: Schema.String, throughSequence: Schema.Int }) {}
932
932
  * class DailyBudgetReached extends Schema.TaggedClass<DailyBudgetReached>()("DailyBudgetReached", {}) {}
933
933
  *
934
934
  * const MyAssistant = Assistant.make("my-assistant", { events: [Summary, DailyBudgetReached] })
@@ -231,7 +231,7 @@ export const staleApprovals = (
231
231
  * yield* BlockKit.closeStale({ messages: 10 })
232
232
  * yield* ToolApproval.closeStale({ messages: 10 })
233
233
  *
234
- * return turn.loop({ instructions, toolkit: MyTools })
234
+ * return turn.run({ instructions, toolkit: MyTools })
235
235
  * })
236
236
  * )
237
237
  * ```
@@ -110,7 +110,7 @@ const requiring =
110
110
  * const OfficeDiscoveryLive = ToolDiscovery.layer(OfficeTools).pipe(Layer.provide(OfficeToolsLive))
111
111
  *
112
112
  * const MyAssistantLive = MyAssistant.toLayer((turn) =>
113
- * turn.loop({
113
+ * turn.run({
114
114
  * instructions,
115
115
  * toolkit: Toolkit.merge(CommonTools, OfficeDiscovery)
116
116
  * })
@@ -87,7 +87,7 @@ const deletedChat = (entries: ReadonlyArray<AssistantEvent.Entry>): Chat => ({
87
87
  submit: () => Effect.void,
88
88
  submitInBackground: () => Effect.void,
89
89
  resume: Effect.void,
90
- stream: (after) => Stream.fromIterable(entries.filter((entry) => entry.seq > after)),
90
+ stream: (after) => Stream.fromIterable(entries.filter((entry) => entry.sequence > after)),
91
91
  delete: Effect.void
92
92
  })
93
93
 
@@ -314,15 +314,15 @@ export const make = Effect.fnUntraced(function* (options: {
314
314
  // Subscribes before it reads, so nothing appended in between is missed.
315
315
  const subscription = yield* PubSub.subscribe(channel)
316
316
  const all = yield* chat.entries
317
- const stored = all.filter((entry) => entry.seq > after)
318
- const last = stored.at(-1)?.seq ?? after
317
+ const stored = all.filter((entry) => entry.sequence > after)
318
+ const last = stored.at(-1)?.sequence ?? after
319
319
 
320
320
  // Deleted: what the reader hasn't seen, then the end. Otherwise the stream ends with the `ChatDeleted`.
321
321
  if (all.some(isChatDeleted)) return Stream.fromIterable(stored)
322
322
 
323
323
  return Stream.concat(
324
324
  Stream.fromIterable(stored),
325
- Stream.fromSubscription(subscription).pipe(Stream.filter((entry) => entry.seq > last))
325
+ Stream.fromSubscription(subscription).pipe(Stream.filter((entry) => entry.sequence > last))
326
326
  ).pipe(Stream.takeUntil(isChatDeleted))
327
327
  })
328
328
  ),
@@ -95,6 +95,6 @@ export type DecodedEntries = ReturnType<typeof decodeEntries>
95
95
 
96
96
  const toEntry = (entry: AssistantEvent.AnyEntry) => ({
97
97
  chatId: entry.chatId,
98
- seq: entry.seq,
98
+ sequence: entry.sequence,
99
99
  at: entry.at
100
100
  })
@@ -140,7 +140,7 @@ const awaitingMessage = (request: AssistantEvent.ApprovalRequested): Prompt.Tool
140
140
  })
141
141
 
142
142
  const isEntry = (item: AssistantEvent.AnyEntry | Prompt.Message): item is AssistantEvent.AnyEntry =>
143
- Predicate.hasProperty(item, "seq")
143
+ Predicate.hasProperty(item, "sequence")
144
144
 
145
145
  // In the order the model saw the entries, not the order they were stored. A message a history view put in stands
146
146
  // where it is, right after the entry before it, and waits like an input for a call's result or for the model's next
@@ -166,15 +166,15 @@ export const fromEntries = (
166
166
  const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, "ModelOutput"))?.event
167
167
 
168
168
  return next !== undefined && AssistantEvent.isLibraryEvent(next) && Predicate.isTagged(next, "ModelOutput")
169
- ? next.seenThroughSeq
169
+ ? next.seenThroughSequence
170
170
  : undefined
171
171
  }
172
172
 
173
- // `index` is the last entry at or before the item, and `seq` where the item stands.
174
- const input = (index: number, seq: number, item: Prompt.Message) => {
173
+ // `index` is the last entry at or before the item, and `sequence` where the item stands.
174
+ const input = (index: number, sequence: number, item: Prompt.Message) => {
175
175
  const seen = nextSeen(index)
176
176
 
177
- if (open.size > 0 || (seen !== undefined && seen < seq)) held.push(item)
177
+ if (open.size > 0 || (seen !== undefined && seen < sequence)) held.push(item)
178
178
  else history.push(item)
179
179
  }
180
180
 
@@ -182,14 +182,14 @@ export const fromEntries = (
182
182
 
183
183
  for (const item of items) {
184
184
  if (!isEntry(item)) {
185
- input(index, (entries[index]?.seq ?? 0) + 0.5, item)
185
+ input(index, (entries[index]?.sequence ?? 0) + 0.5, item)
186
186
 
187
187
  continue
188
188
  }
189
189
 
190
190
  index += 1
191
191
 
192
- const { event, seq } = item
192
+ const { event, sequence } = item
193
193
  const at = index
194
194
 
195
195
  if (!AssistantEvent.isLibraryEvent(event)) continue
@@ -197,8 +197,8 @@ export const fromEntries = (
197
197
  Match.value(event).pipe(
198
198
  Match.tagsExhaustive({
199
199
  UserMessage: (message) =>
200
- input(at, seq, userText(AssistantEvent.textWithAttachments(message.text, message.attachments))),
201
- BlockAnswer: (answer) => input(at, seq, userText(answerText(answer))),
200
+ input(at, sequence, userText(AssistantEvent.textWithAttachments(message.text, message.attachments))),
201
+ BlockAnswer: (answer) => input(at, sequence, userText(answerText(answer))),
202
202
  ModelOutput: (output) => {
203
203
  history.push(...Prompt.fromResponseParts(output.parts).content)
204
204
 
@@ -212,11 +212,11 @@ export const fromEntries = (
212
212
  flushIfClosed()
213
213
  },
214
214
  ToolResult: (result) => {
215
- if (requests.has(result.callId)) return input(at, seq, userText(approvedText(result)))
215
+ if (requests.has(result.callId)) return input(at, sequence, userText(approvedText(result)))
216
216
 
217
217
  // A view that starts after the call (since a summary, say) keeps its result as text: a tool result needs its
218
218
  // call right before it.
219
- if (!called.has(result.callId)) return input(at, seq, userText(resultText(result)))
219
+ if (!called.has(result.callId)) return input(at, sequence, userText(resultText(result)))
220
220
 
221
221
  history.push(toolMessage(result))
222
222
  open.delete(result.callId)
@@ -227,7 +227,7 @@ export const fromEntries = (
227
227
  TurnStarted: () => undefined,
228
228
  TurnCompleted: () => undefined,
229
229
  TurnFailed: () => undefined,
230
- SpecialistFinished: (finished) => input(at, seq, userText(finishedText(entries, at, finished))),
230
+ SpecialistFinished: (finished) => input(at, sequence, userText(finishedText(entries, at, finished))),
231
231
  // Every run starts in a call of the model's own (a spawn or a follow-up), whose result says so.
232
232
  SpecialistStarted: () => undefined,
233
233
  BlocksClosed: () => undefined,
@@ -235,7 +235,7 @@ export const fromEntries = (
235
235
  ApprovalRequested: (request) => {
236
236
  requests.set(request.callId, request)
237
237
 
238
- if (!called.has(request.callId)) return input(at, seq, userText(awaitingText(request)))
238
+ if (!called.has(request.callId)) return input(at, sequence, userText(awaitingText(request)))
239
239
 
240
240
  history.push(awaitingMessage(request))
241
241
  open.delete(request.callId)
@@ -244,10 +244,10 @@ export const fromEntries = (
244
244
  // An approved call's result says so.
245
245
  ApprovalDecision: (decision) =>
246
246
  decision.decision === "rejected"
247
- ? input(at, seq, userText(rejectedText(decision, toolOf(decision.callId))))
247
+ ? input(at, sequence, userText(rejectedText(decision, toolOf(decision.callId))))
248
248
  : undefined,
249
249
  ApprovalsExpired: (expired) =>
250
- expired.callIds.forEach((callId) => input(at, seq, userText(expiredText(callId, toolOf(callId))))),
250
+ expired.callIds.forEach((callId) => input(at, sequence, userText(expiredText(callId, toolOf(callId))))),
251
251
  ChatDeleted: () => undefined
252
252
  })
253
253
  )
@@ -264,7 +264,7 @@ export const isInput = (event: { readonly _tag: string }): boolean =>
264
264
  export const handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {
265
265
  const last = entries.findLast(({ event }) => AssistantEvent.isTurnEnd(event))?.event
266
266
 
267
- return last !== undefined && AssistantEvent.isTurnEnd(last) ? last.throughSeq : 0
267
+ return last !== undefined && AssistantEvent.isTurnEnd(last) ? last.throughSequence : 0
268
268
  }
269
269
 
270
270
  // A run the assistant stopped isn't an input here: it wakes nothing.
@@ -274,8 +274,10 @@ export const unhandledInputs = (
274
274
  ): ReadonlyArray<{ readonly _tag: string }> => {
275
275
  const seen = handledThrough(entries)
276
276
 
277
- return entries.flatMap(({ event, seq }) =>
278
- seq > seen && isInput(event) && !(AssistantEvent.isInput(event) && !AssistantEvent.isWaking(event)) ? [event] : []
277
+ return entries.flatMap(({ event, sequence }) =>
278
+ sequence > seen && isInput(event) && !(AssistantEvent.isInput(event) && !AssistantEvent.isWaking(event))
279
+ ? [event]
280
+ : []
279
281
  )
280
282
  }
281
283
 
@@ -285,16 +287,17 @@ export const needsTurn = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): bool
285
287
 
286
288
  // A failure the user was shown counts as a reply, and so does a call they're asked to approve.
287
289
  /** @internal */
288
- export const owesReply = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {
290
+ export const owesReply = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSequence: number): boolean => {
289
291
  const latest = entries.findLast(
290
- ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)
292
+ ({ event, sequence }) =>
293
+ sequence <= throughSequence && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)
291
294
  )
292
295
 
293
296
  return (
294
297
  latest !== undefined &&
295
298
  !entries.some(
296
- ({ event, seq }) =>
297
- seq > latest.seq &&
299
+ ({ event, sequence }) =>
300
+ sequence > latest.sequence &&
298
301
  (Predicate.isTagged(event, "AssistantMessage") ||
299
302
  Predicate.isTagged(event, "TurnFailed") ||
300
303
  Predicate.isTagged(event, "ApprovalRequested"))
@@ -321,15 +324,16 @@ const progressMessages = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): Read
321
324
  // The model said it was on it and stopped without the result: only progress messages since the user's latest input.
322
325
  // A specialist still working doesn't count, since its report is what comes next.
323
326
  /** @internal */
324
- export const owesDelivery = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {
327
+ export const owesDelivery = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSequence: number): boolean => {
325
328
  const latest = entries.findLast(
326
- ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)
329
+ ({ event, sequence }) =>
330
+ sequence <= throughSequence && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)
327
331
  )
328
332
 
329
333
  if (latest === undefined) return false
330
334
 
331
- const after = entries.flatMap(({ seq, event }) =>
332
- seq > latest.seq && AssistantEvent.isLibraryEvent(event) ? [event] : []
335
+ const after = entries.flatMap(({ sequence, event }) =>
336
+ sequence > latest.sequence && AssistantEvent.isLibraryEvent(event) ? [event] : []
333
337
  )
334
338
 
335
339
  const messages = after.flatMap((event) => (Predicate.isTagged(event, "AssistantMessage") ? [event] : []))
@@ -352,5 +356,5 @@ export const owesDelivery = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, th
352
356
  }
353
357
 
354
358
  /** @internal */
355
- export const unreadAfter = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean =>
356
- entries.some(({ event, seq }) => seq > throughSeq && isInput(event))
359
+ export const unreadAfter = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSequence: number): boolean =>
360
+ entries.some(({ event, sequence }) => sequence > throughSequence && isInput(event))
@@ -44,13 +44,13 @@ export const TurnTools = Context.Reference<Option.Option<Toolkits.Tools>>("effec
44
44
  /** @internal */
45
45
  export interface ChatHistory {
46
46
  readonly messages: ReadonlyArray<Prompt.Message>
47
- readonly throughSeq: number
47
+ readonly throughSequence: number
48
48
  }
49
49
 
50
50
  const readHistory = (read: ReadonlyArray<Prompt.Message> | ChatHistory) =>
51
51
  "messages" in read
52
- ? { messages: read.messages, throughSeq: read.throughSeq }
53
- : { messages: read, throughSeq: undefined }
52
+ ? { messages: read.messages, throughSequence: read.throughSequence }
53
+ : { messages: read, throughSequence: undefined }
54
54
 
55
55
  // `AssistantLoop` takes native toolkits; turns and specialist runs come here with their tools already merged.
56
56
  /** @internal */
@@ -281,7 +281,7 @@ export const step = <R, S, E2 = never, R2 = never>(
281
281
 
282
282
  return Stream.unwrap(
283
283
  Effect.gen(function* () {
284
- const { messages: history, throughSeq } = readHistory(yield* options.history)
284
+ const { messages: history, throughSequence } = readHistory(yield* options.history)
285
285
  // Calls a crash cut off, with no result: they run again, with the same ids, before the model goes on.
286
286
  const cutOff = History.unansweredCalls(history)
287
287
  const approvals = yield* TurnApprovals
@@ -318,7 +318,7 @@ export const step = <R, S, E2 = never, R2 = never>(
318
318
 
319
319
  const output = AssistantEvent.ModelOutput.make({
320
320
  parts: modelParts(response.content),
321
- seenThroughSeq: throughSeq,
321
+ seenThroughSequence: throughSequence,
322
322
  model: metadata?.modelId ?? Option.getOrUndefined(name),
323
323
  responseId: metadata?.id,
324
324
  usage: response.usage,
@@ -140,12 +140,13 @@ const approvedCalls = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): Readonl
140
140
  : []
141
141
  )
142
142
 
143
- const lastSeq = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) => entries.at(-1)?.seq ?? otherwise
143
+ const lastSequence = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) =>
144
+ entries.at(-1)?.sequence ?? otherwise
144
145
 
145
146
  // The handler's stream can end between a step's output and its tools, or while they run. An interrupted turn (a
146
147
  // shutdown) leaves its calls open instead: the next turn runs them again.
147
- const closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSeq: number) {
148
- const entries = (yield* chat.entries).filter(({ seq }) => seq > startSeq)
148
+ const closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSequence: number) {
149
+ const entries = (yield* chat.entries).filter(({ sequence }) => sequence > startSequence)
149
150
 
150
151
  const started = new Set(
151
152
  entries.flatMap(({ event }) => (Predicate.isTagged(event, "ToolStarted") ? [event.callId] : []))
@@ -169,18 +170,18 @@ const closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSeq: numb
169
170
  )
170
171
  })
171
172
 
172
- // The inputs before the turn's `TurnStarted` (at `startSeq`) woke it; past that, what its model outputs record
173
+ // The inputs before the turn's `TurnStarted` (at `startSequence`) woke it; past that, what its model outputs record
173
174
  // reading.
174
- const handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, startSeq: number) =>
175
+ const handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, startSequence: number) =>
175
176
  entries.reduce(
176
- (through, { seq, event }) =>
177
- seq > startSeq &&
177
+ (through, { sequence, event }) =>
178
+ sequence > startSequence &&
178
179
  AssistantEvent.isLibraryEvent(event) &&
179
180
  Predicate.isTagged(event, "ModelOutput") &&
180
- event.seenThroughSeq !== undefined
181
- ? Math.max(through, event.seenThroughSeq)
181
+ event.seenThroughSequence !== undefined
182
+ ? Math.max(through, event.seenThroughSequence)
182
183
  : through,
183
- startSeq
184
+ startSequence
184
185
  )
185
186
 
186
187
  // A turn that ran the loop fails visibly when the user's latest input got no reply; an input that arrived after what
@@ -190,8 +191,8 @@ const finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, ranL
190
191
 
191
192
  const end =
192
193
  ranLoop && History.owesReply(entries, read) && !History.unreadAfter(entries, read)
193
- ? AssistantEvent.TurnFailed.make({ throughSeq: read, message: silentTurnMessage })
194
- : AssistantEvent.TurnCompleted.make({ throughSeq: read })
194
+ ? AssistantEvent.TurnFailed.make({ throughSequence: read, message: silentTurnMessage })
195
+ : AssistantEvent.TurnCompleted.make({ throughSequence: read })
195
196
 
196
197
  yield* chat.append([end])
197
198
  })
@@ -200,15 +201,15 @@ const finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, ranL
200
201
  const failTurn = Effect.fnUntraced(function* (chat: Writer, cause: Cause.Cause<unknown>) {
201
202
  yield* Effect.logError("assistant turn failed", cause)
202
203
 
203
- const last = lastSeq(yield* chat.entries, 0)
204
+ const last = lastSequence(yield* chat.entries, 0)
204
205
 
205
- yield* chat.append([AssistantEvent.TurnFailed.make({ throughSeq: last, message: failureMessage(cause) })])
206
+ yield* chat.append([AssistantEvent.TurnFailed.make({ throughSequence: last, message: failureMessage(cause) })])
206
207
  })
207
208
 
208
209
  const makeTurnApi = (
209
210
  registration: Registration,
210
211
  chat: Writer,
211
- startSeq: number,
212
+ startSequence: number,
212
213
  checkOpen: Effect.Effect<void>,
213
214
  ranLoop: Ref.Ref<boolean>
214
215
  ): Assistant.Turn<any> => {
@@ -228,7 +229,7 @@ const makeTurnApi = (
228
229
 
229
230
  const read: AssistantLoop.ChatHistory = {
230
231
  messages: History.fromEntries(view(AppEvents.decodeEntries(app, all))),
231
- throughSeq: lastSeq(all, startSeq)
232
+ throughSequence: lastSequence(all, startSequence)
232
233
  }
233
234
 
234
235
  return read
@@ -247,7 +248,7 @@ const makeTurnApi = (
247
248
  Stream.unwrap(
248
249
  Effect.gen(function* () {
249
250
  const all = yield* chat.entries
250
- const through = handledThrough(all, startSeq)
251
+ const through = handledThrough(all, startSequence)
251
252
 
252
253
  // Something arrived after what the loop's model read: the next turn reads it with the full loop, and replies.
253
254
  if (History.unreadAfter(all, through)) return Stream.empty
@@ -277,7 +278,7 @@ const makeTurnApi = (
277
278
  ...History.fromEntries(options.view(AppEvents.decodeEntries(app, entries))),
278
279
  History.userText(nudge)
279
280
  ],
280
- throughSeq: lastSeq(entries, startSeq)
281
+ throughSequence: lastSequence(entries, startSequence)
281
282
  }
282
283
  }),
283
284
  // A second step, for a reply after a call it was refused.
@@ -300,7 +301,7 @@ const makeTurnApi = (
300
301
  })
301
302
  )
302
303
 
303
- const prepare = <T extends Record<string, Tool.Any>>(options: Assistant.LoopOptions<T, any>) => {
304
+ const prepare = <T extends Record<string, Tool.Any>>(options: Assistant.RunOptions<T, any>) => {
304
305
  const own = Toolkits.showing(Toolkits.fromToolkitOrEmpty(options.toolkit), shownTools)
305
306
 
306
307
  return {
@@ -315,7 +316,7 @@ const makeTurnApi = (
315
316
  history,
316
317
  instructions,
317
318
  replyIfOwed,
318
- loop: (options) => {
319
+ run: (options) => {
319
320
  const { system, toolkit, view } = prepare(options)
320
321
  const twice = Toolkits.duplicateNames(toolkit)
321
322
 
@@ -362,15 +363,15 @@ export const run = Effect.fnUntraced(
362
363
  )
363
364
  ])
364
365
 
365
- const startSeq = lastSeq(started, 0)
366
+ const startSequence = lastSequence(started, 0)
366
367
  const ranLoop = yield* Ref.make(false)
367
- const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen, ranLoop)
368
+ const turn = makeTurnApi(options.registration, chat, startSequence, options.checkOpen, ranLoop)
368
369
 
369
370
  const record = (event: AssistantLoop.Event) => chat.append([event])
370
371
 
371
372
  yield* options.handler(turn).pipe(
372
373
  Stream.runForEach(record),
373
- Effect.onExit((exit) => (Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSeq))),
374
+ Effect.onExit((exit) => (Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSequence))),
374
375
  // A loop the app writes gets the assistant's own tools too, with this turn's services.
375
376
  Effect.provideService(
376
377
  Loop.TurnTools,
@@ -383,15 +384,16 @@ export const run = Effect.fnUntraced(
383
384
  onSuccess: Effect.fnUntraced(function* () {
384
385
  const all = yield* chat.entries
385
386
 
386
- yield* finishTurn(chat, handledThrough(all, startSeq), yield* Ref.get(ranLoop))
387
+ yield* finishTurn(chat, handledThrough(all, startSequence), yield* Ref.get(ranLoop))
387
388
  }),
388
389
  onFailure: (cause) => failTurn(chat, cause)
389
390
  })
390
391
  )
391
392
 
392
393
  // The trace's output is what the user saw this turn, and a failure if any.
393
- const shown = (yield* chat.entries).flatMap(({ event, seq }) =>
394
- seq > startSeq && (Predicate.isTagged(event, "AssistantMessage") || Predicate.isTagged(event, "TurnFailed"))
394
+ const shown = (yield* chat.entries).flatMap(({ event, sequence }) =>
395
+ sequence > startSequence &&
396
+ (Predicate.isTagged(event, "AssistantMessage") || Predicate.isTagged(event, "TurnFailed"))
395
397
  ? [event]
396
398
  : []
397
399
  )