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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-assistant",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A colleague assistant on Effect: an append-only event log per chat, an interruptible agent loop, specialists, skills, and UI blocks",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/Assistant.ts CHANGED
@@ -48,12 +48,12 @@ const TypeId = "~effect-assistant/Assistant" as const
48
48
  export type TurnServices = CurrentChat | Specialist.Runner
49
49
 
50
50
  /**
51
- * Represents the options accepted by `turn.loop`.
51
+ * Represents the options accepted by `turn.run`.
52
52
  *
53
53
  * @category models
54
54
  * @since 0.1.0
55
55
  */
56
- export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never, never>, Events = never> {
56
+ export interface RunOptions<T extends Record<string, Tool.Any> = Record<never, never>, Events = never> {
57
57
  /** The assistant's role and rules. The loop places `Instructions.protocol` before them. */
58
58
  readonly instructions: string | Instructions.Resolved
59
59
  /**
@@ -115,7 +115,7 @@ export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never,
115
115
  *
116
116
  * // An `OrderPlaced` that the API submits wakes a turn, which reads it as this note.
117
117
  * const MyAssistantLive = MyAssistant.toLayer((turn) =>
118
- * turn.loop({ instructions: "Be brief.", history: withOrders })
118
+ * turn.run({ instructions: "Be brief.", history: withOrders })
119
119
  * )
120
120
  * ```
121
121
  *
@@ -134,7 +134,7 @@ export type HistoryView<Events = never> = (
134
134
  */
135
135
  export interface Entry<Events = never> {
136
136
  readonly chatId: ChatId
137
- readonly seq: number
137
+ readonly sequence: number
138
138
  readonly at: number
139
139
  readonly event: AssistantEvent.AssistantEvent | Events
140
140
  }
@@ -146,7 +146,7 @@ export interface Entry<Events = never> {
146
146
  * @since 0.1.0
147
147
  */
148
148
  export interface ReplyOptions {
149
- /** The assistant's instructions, as passed to `turn.loop`. `Instructions.protocol` is added. */
149
+ /** The assistant's instructions, as passed to `turn.run`. `Instructions.protocol` is added. */
150
150
  readonly instructions: string | Instructions.Resolved
151
151
  /**
152
152
  * The message given to the model after the history. Defaults to a message saying that it
@@ -185,8 +185,8 @@ export interface Turn<Events = never> {
185
185
  * tools, and the instructions and tools in `options`. Every step is a tool call, and
186
186
  * the loop ends with `replyIfOwed`.
187
187
  */
188
- readonly loop: <T extends Record<string, Tool.Any> = Record<never, never>>(
189
- options: LoopOptions<T, Events>
188
+ readonly run: <T extends Record<string, Tool.Any> = Record<never, never>>(
189
+ options: RunOptions<T, Events>
190
190
  ) => Stream.Stream<
191
191
  AssistantLoop.Event,
192
192
  AiError.AiError,
@@ -197,7 +197,7 @@ export interface Turn<Events = never> {
197
197
  * message. In this pass the model can only reply.
198
198
  *
199
199
  * The stream is empty when no reply is owed, or when an input arrived that the next turn will
200
- * read. `loop` runs this pass with its own prompt and tools, so the pass reuses the cached
200
+ * read. `run` runs this pass with its own prompt and tools, so the pass reuses the cached
201
201
  * prompt.
202
202
  */
203
203
  readonly replyIfOwed: (
@@ -309,7 +309,7 @@ export interface Service<Events = never> {
309
309
  /**
310
310
  * Streams the chat's stored entries, then live ones, with the assistant's own events decoded.
311
311
  * A reader that already has the earlier entries passes `after` and receives only the entries
312
- * after that `seq`.
312
+ * after that `sequence`.
313
313
  *
314
314
  * For a chat client, pipe the whole log into `Timeline.changes(MyAssistant, { after })`, which
315
315
  * streams the chat's timeline, as its user sees it. Its `after` goes there, not here: a message
@@ -382,7 +382,7 @@ export interface Assistant<
382
382
  * export const MyAssistant = Assistant.make("my-assistant")
383
383
  *
384
384
  * const MyAssistantLive = MyAssistant.toLayer((turn) =>
385
- * turn.loop({ instructions: "You help a small team. Be brief." })
385
+ * turn.run({ instructions: "You help a small team. Be brief." })
386
386
  * )
387
387
  *
388
388
  * const AppLive = MyAssistantLive.pipe(
@@ -458,7 +458,7 @@ function entrySchema(events: ReadonlyArray<AssistantEvent.AppEventSchema>): Sche
458
458
  return Schema.toCodecJson(
459
459
  Schema.Struct({
460
460
  chatId: ChatId,
461
- seq: Schema.Int,
461
+ sequence: Schema.Int,
462
462
  at: Schema.Finite,
463
463
  event: Schema.Union([AssistantEvent.AssistantEvent, ...events])
464
464
  })
@@ -43,8 +43,8 @@ export interface State {
43
43
  * to start.
44
44
  */
45
45
  readonly queued: boolean
46
- /** The `seq` of the newest input. */
47
- readonly lastInputSeq: number
46
+ /** The `sequence` of the newest input. */
47
+ readonly lastInputSequence: number
48
48
  /** Specialist runs that are working, by run id. */
49
49
  readonly specialists: ReadonlyMap<RunId, SpecialistRun>
50
50
  }
@@ -58,7 +58,7 @@ export interface State {
58
58
  export const empty: State = {
59
59
  turnSince: undefined,
60
60
  queued: false,
61
- lastInputSeq: 0,
61
+ lastInputSequence: 0,
62
62
  specialists: new Map()
63
63
  }
64
64
 
@@ -89,14 +89,16 @@ export const apply = (activity: State, entry: AssistantEvent.AnyEntry): State =>
89
89
  const event = entry.event
90
90
 
91
91
  if (!AssistantEvent.isLibraryEvent(event)) {
92
- return AssistantEvent.isAppInput(event) ? { ...activity, queued: true, lastInputSeq: entry.seq } : activity
92
+ return AssistantEvent.isAppInput(event)
93
+ ? { ...activity, queued: true, lastInputSequence: entry.sequence }
94
+ : activity
93
95
  }
94
96
 
95
97
  if (AssistantEvent.isInput(event)) {
96
98
  // A finished run's report is an input, and ends the run.
97
99
  const current = Predicate.isTagged(event, "SpecialistFinished") ? withoutRun(activity, event.runId) : activity
98
100
 
99
- return AssistantEvent.isWaking(event) ? { ...current, queued: true, lastInputSeq: entry.seq } : current
101
+ return AssistantEvent.isWaking(event) ? { ...current, queued: true, lastInputSequence: entry.sequence } : current
100
102
  }
101
103
 
102
104
  return Match.value(event).pipe(
@@ -105,7 +107,7 @@ export const apply = (activity: State, entry: AssistantEvent.AnyEntry): State =>
105
107
  Match.tag("TurnCompleted", "TurnFailed", (ended): State => ({
106
108
  ...activity,
107
109
  turnSince: undefined,
108
- queued: activity.lastInputSeq > ended.throughSeq
110
+ queued: activity.lastInputSequence > ended.throughSequence
109
111
  })),
110
112
  Match.tag("SpecialistStarted", (started): State => ({
111
113
  ...activity,
@@ -117,7 +119,7 @@ export const apply = (activity: State, entry: AssistantEvent.AnyEntry): State =>
117
119
  })
118
120
  })),
119
121
  // Deleting a chat stops everything in it, and nothing starts again.
120
- Match.tag("ChatDeleted", (): State => ({ ...empty, lastInputSeq: activity.lastInputSeq })),
122
+ Match.tag("ChatDeleted", (): State => ({ ...empty, lastInputSequence: activity.lastInputSequence })),
121
123
  Match.orElse(() => activity)
122
124
  )
123
125
  }
@@ -54,7 +54,7 @@ export class AssistantEngine extends Context.Service<
54
54
  chatId: ChatId,
55
55
  input: AssistantEvent.Input | AssistantEvent.StoredAppEvent
56
56
  ) => Effect.Effect<void>
57
- /** Streams the chat's stored entries after the `after` seq, then live ones. */
57
+ /** Streams the chat's stored entries after the `after` sequence, then live ones. */
58
58
  readonly subscribe: (assistant: string, chatId: ChatId, after: number) => Stream.Stream<AssistantEvent.Entry>
59
59
  /**
60
60
  * Deletes one of the assistant's chats. The chat's log records the deletion, and everything
@@ -90,7 +90,7 @@ const notRegistered = (assistant: string) =>
90
90
  *
91
91
  * const MyAssistant = Assistant.make("my-assistant")
92
92
  *
93
- * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
93
+ * const AppLive = MyAssistant.toLayer((turn) => turn.run({ instructions: "Be brief." })).pipe(
94
94
  * Layer.provide(ModelLive),
95
95
  * Layer.provideMerge(AssistantEngine.layerMemory),
96
96
  * Layer.provideMerge(ChatLog.layerMemory)
@@ -289,7 +289,7 @@ const makeClusterClient = Effect.gen(function* () {
289
289
  *
290
290
  * const MyAssistant = Assistant.make("my-assistant")
291
291
  *
292
- * const AppLive = MyAssistant.toLayer((turn) => turn.loop({ instructions: "Be brief." })).pipe(
292
+ * const AppLive = MyAssistant.toLayer((turn) => turn.run({ instructions: "Be brief." })).pipe(
293
293
  * Layer.provide(ModelLive),
294
294
  * Layer.provideMerge(AssistantEngine.layerCluster),
295
295
  * Layer.provideMerge(
@@ -280,7 +280,7 @@ export class ModelOutput extends Schema.TaggedClass<ModelOutput>()("ModelOutput"
280
280
  * arrived while the model was working. A turn has handled what its
281
281
  * outputs had read.
282
282
  */
283
- seenThroughSeq: Schema.optional(Schema.Int),
283
+ seenThroughSequence: Schema.optional(Schema.Int),
284
284
  /** The model that wrote it, as the provider's id for it or the name the context gave it. */
285
285
  model: Schema.optional(Schema.String),
286
286
  /** The provider's id for the response, when it gives one. */
@@ -365,26 +365,26 @@ export class TurnStarted extends Schema.TaggedClass<TurnStarted>()("TurnStarted"
365
365
  /**
366
366
  * Represents a turn that finished.
367
367
  *
368
- * `throughSeq` is the last entry the turn read. Inputs after it still need a turn.
368
+ * `throughSequence` is the last entry the turn read. Inputs after it still need a turn.
369
369
  *
370
370
  * @category schemas
371
371
  * @since 0.1.0
372
372
  */
373
373
  export class TurnCompleted extends Schema.TaggedClass<TurnCompleted>()("TurnCompleted", {
374
- throughSeq: Schema.Int
374
+ throughSequence: Schema.Int
375
375
  }) {}
376
376
 
377
377
  /**
378
378
  * Represents a turn that failed.
379
379
  *
380
- * `throughSeq` covers everything stored when the turn failed, so a failing input isn't retried
380
+ * `throughSequence` covers everything stored when the turn failed, so a failing input isn't retried
381
381
  * forever.
382
382
  *
383
383
  * @category schemas
384
384
  * @since 0.1.0
385
385
  */
386
386
  export class TurnFailed extends Schema.TaggedClass<TurnFailed>()("TurnFailed", {
387
- throughSeq: Schema.Int,
387
+ throughSequence: Schema.Int,
388
388
  message: Schema.String
389
389
  }) {}
390
390
 
@@ -585,7 +585,7 @@ export const isAppInput = (event: { readonly _tag: string }): boolean =>
585
585
  */
586
586
  export interface AnyEntry {
587
587
  readonly chatId: ChatId
588
- readonly seq: number
588
+ readonly sequence: number
589
589
  readonly at: number
590
590
  readonly event: { readonly _tag: string }
591
591
  }
@@ -593,7 +593,7 @@ export interface AnyEntry {
593
593
  /**
594
594
  * Schema for a stored event with its position in the chat.
595
595
  *
596
- * `seq` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
596
+ * `sequence` starts at 1 and has no gaps. `at` is the time the event was stored, in epoch
597
597
  * milliseconds.
598
598
  *
599
599
  * @category schemas
@@ -601,7 +601,7 @@ export interface AnyEntry {
601
601
  */
602
602
  export const Entry = Schema.Struct({
603
603
  chatId: ChatId,
604
- seq: Schema.Int,
604
+ sequence: Schema.Int,
605
605
  at: Schema.Finite,
606
606
  event: Schema.Union([AssistantEvent, StoredAppEvent])
607
607
  })
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Runs the agent loop, which calls the model, runs its tool calls, and repeats.
3
3
  *
4
- * Most handlers use `turn.loop` or `run.loop`. This module is for a loop of your own: {@link run}
4
+ * Most handlers use `turn.run` or `run.loop`. This module is for a loop of your own: {@link run}
5
5
  * runs the loop until the model is done, and {@link step} is one step, as a body for
6
6
  * {@link loop}.
7
7
  *
@@ -37,7 +37,7 @@ export type Event = Loop.Event
37
37
  /**
38
38
  * Represents a chat's history as the model reads it.
39
39
  *
40
- * It holds the messages, and the last `seq` of the chat they cover.
40
+ * It holds the messages, and the last `sequence` of the chat they cover.
41
41
  *
42
42
  * @category models
43
43
  * @since 0.1.0
@@ -122,7 +122,7 @@ export interface StepOptions<T extends Record<string, Tool.Any> = NoTools> {
122
122
  readonly toolkit?: Toolkit.Toolkit<T> | undefined
123
123
  /**
124
124
  * The current history, read at the start of the step. It is either messages, or a chat's
125
- * history (`turn.history()`), whose `throughSeq` the step's `ModelOutput` records as how far
125
+ * history (`turn.history()`), whose `throughSequence` the step's `ModelOutput` records as how far
126
126
  * the model had read.
127
127
  */
128
128
  readonly history: Effect.Effect<ReadonlyArray<Prompt.Message> | ChatHistory>
package/src/ChatLog.ts CHANGED
@@ -33,7 +33,7 @@ export class ChatLog extends Context.Service<
33
33
  chatId: ChatId,
34
34
  events: ReadonlyArray<AssistantEvent.AssistantEvent | AssistantEvent.StoredAppEvent>
35
35
  ) => Effect.Effect<ReadonlyArray<AssistantEvent.Entry>>
36
- /** Reads a chat's stored entries in order, either all of them or only those after a `seq`. */
36
+ /** Reads a chat's stored entries in order, either all of them or only those after a `sequence`. */
37
37
  readonly read: (chatId: ChatId, options?: ReadOptions) => Effect.Effect<ReadonlyArray<AssistantEvent.Entry>>
38
38
  /**
39
39
  * Returns the chats whose last entry ends neither a turn nor the chat. After a restart, these
@@ -50,14 +50,14 @@ export class ChatLog extends Context.Service<
50
50
  * @since 0.1.0
51
51
  */
52
52
  export interface ReadOptions {
53
- /** Reads only the entries after this `seq`. */
53
+ /** Reads only the entries after this `sequence`. */
54
54
  readonly after?: number | undefined
55
55
  }
56
56
 
57
57
  /**
58
58
  * Represents the storage that a `ChatLog` is built on.
59
59
  *
60
- * A storage reads a chat's entries, finds a chat's last `seq`, writes new entries, and finds every
60
+ * A storage reads a chat's entries, finds a chat's last `sequence`, writes new entries, and finds every
61
61
  * chat's last entry. Entries are never removed, so a deleted chat keeps its log, which ends in
62
62
  * `ChatDeleted`.
63
63
  *
@@ -66,7 +66,7 @@ export interface ReadOptions {
66
66
  */
67
67
  export interface Storage {
68
68
  readonly read: (chatId: ChatId, options?: ReadOptions) => Effect.Effect<ReadonlyArray<AssistantEvent.Entry>>
69
- readonly lastSeq: (chatId: ChatId) => Effect.Effect<number>
69
+ readonly lastSequence: (chatId: ChatId) => Effect.Effect<number>
70
70
  readonly write: (chatId: ChatId, entries: ReadonlyArray<AssistantEvent.Entry>) => Effect.Effect<void>
71
71
  /** Returns the last entry of every chat. */
72
72
  readonly lastEntries: Effect.Effect<ReadonlyArray<AssistantEvent.Entry>>
@@ -74,7 +74,7 @@ export interface Storage {
74
74
 
75
75
  /**
76
76
  * Creates a `ChatLog` from a {@link Storage}. `append` numbers new entries after the chat's last
77
- * `seq` and stamps them with the current time.
77
+ * `sequence` and stamps them with the current time.
78
78
  *
79
79
  * @category constructors
80
80
  * @since 0.1.0
@@ -88,9 +88,12 @@ export const make = (storage: Storage): ChatLog["Service"] =>
88
88
  ) {
89
89
  if (events.length === 0) return []
90
90
 
91
- const seq = yield* storage.lastSeq(chatId)
91
+ const sequence = yield* storage.lastSequence(chatId)
92
92
  const at = yield* Clock.currentTimeMillis
93
- const entries = events.map((event, n) => AssistantEvent.Entry.make({ chatId, seq: seq + n + 1, at, event }))
93
+
94
+ const entries = events.map((event, n) =>
95
+ AssistantEvent.Entry.make({ chatId, sequence: sequence + n + 1, at, event })
96
+ )
94
97
 
95
98
  yield* storage.write(chatId, entries)
96
99
 
@@ -121,12 +124,12 @@ export const layerMemory: Layer.Layer<ChatLog> = Layer.effect(
121
124
  read: Effect.fnUntraced(function* (chatId: ChatId, options?: ReadOptions) {
122
125
  const all = yield* Ref.get(chats)
123
126
 
124
- return (all.get(chatId) ?? []).filter((entry) => entry.seq > (options?.after ?? 0))
127
+ return (all.get(chatId) ?? []).filter((entry) => entry.sequence > (options?.after ?? 0))
125
128
  }),
126
- lastSeq: Effect.fnUntraced(function* (chatId: ChatId) {
129
+ lastSequence: Effect.fnUntraced(function* (chatId: ChatId) {
127
130
  const all = yield* Ref.get(chats)
128
131
 
129
- return all.get(chatId)?.at(-1)?.seq ?? 0
132
+ return all.get(chatId)?.at(-1)?.sequence ?? 0
130
133
  }),
131
134
  write: (chatId, entries) =>
132
135
  Ref.update(chats, (all) => new Map(all).set(chatId, [...(all.get(chatId) ?? []), ...entries])),
@@ -144,17 +147,19 @@ const EventJson = Schema.fromJsonString(Schema.Union([AssistantEvent.AssistantEv
144
147
  // Postgres returns BIGINT and DOUBLE PRECISION columns as strings; other databases as numbers.
145
148
  const SqlNumber = Schema.Union([Schema.Finite, Schema.FiniteFromString])
146
149
 
147
- const Row = Schema.Struct({ chat_id: ChatId, seq: SqlNumber, at: SqlNumber, event: EventJson })
150
+ const Row = Schema.Struct({ chat_id: ChatId, sequence: SqlNumber, at: SqlNumber, event: EventJson })
148
151
 
149
- const decodeLastSeq = Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ seq: Schema.NullOr(SqlNumber) })))
152
+ const decodeLastSequence = Schema.decodeUnknownEffect(
153
+ Schema.Array(Schema.Struct({ sequence: Schema.NullOr(SqlNumber) }))
154
+ )
150
155
 
151
156
  /**
152
157
  * Layer that stores chats in a SQL table named `chat_log`.
153
158
  *
154
159
  * **Details**
155
160
  *
156
- * The table must already exist. It has the columns `chat_id`, `seq`, `at`, and `event`, with the
157
- * primary key `(chat_id, seq)`. The layer requires a `SqlClient`.
161
+ * The table must already exist. It has the columns `chat_id`, `sequence`, `at`, and `event`, with the
162
+ * primary key `(chat_id, sequence)`. The layer requires a `SqlClient`.
158
163
  *
159
164
  * **Example** (Storing chats in Postgres)
160
165
  *
@@ -174,10 +179,10 @@ const decodeLastSeq = Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ se
174
179
  * ```sql
175
180
  * CREATE TABLE chat_log (
176
181
  * chat_id TEXT NOT NULL,
177
- * seq INTEGER NOT NULL,
182
+ * sequence INTEGER NOT NULL,
178
183
  * at DOUBLE PRECISION NOT NULL,
179
184
  * event TEXT NOT NULL,
180
- * PRIMARY KEY (chat_id, seq)
185
+ * PRIMARY KEY (chat_id, sequence)
181
186
  * );
182
187
  * ```
183
188
  *
@@ -195,7 +200,7 @@ export const layerSql: Layer.Layer<ChatLog, never, SqlClient.SqlClient> = Layer.
195
200
  return decoded.map((row) =>
196
201
  AssistantEvent.Entry.make({
197
202
  chatId: row.chat_id,
198
- seq: row.seq,
203
+ sequence: row.sequence,
199
204
  at: row.at,
200
205
  event: row.event
201
206
  })
@@ -207,19 +212,19 @@ export const layerSql: Layer.Layer<ChatLog, never, SqlClient.SqlClient> = Layer.
207
212
  return make({
208
213
  read: Effect.fnUntraced(function* (chatId: ChatId, options?: ReadOptions) {
209
214
  const rows = yield* sql`
210
- SELECT chat_id, seq, at, event FROM chat_log WHERE chat_id = ${chatId} AND seq > ${options?.after ?? 0} ORDER BY seq
215
+ SELECT chat_id, sequence, at, event FROM chat_log WHERE chat_id = ${chatId} AND sequence > ${options?.after ?? 0} ORDER BY sequence
211
216
  `
212
217
 
213
218
  return yield* toEntries(rows)
214
219
  }, Effect.orDie),
215
- lastSeq: Effect.fnUntraced(function* (chatId: ChatId) {
220
+ lastSequence: Effect.fnUntraced(function* (chatId: ChatId) {
216
221
  const result = yield* sql`
217
- SELECT MAX(seq) AS seq FROM chat_log WHERE chat_id = ${chatId}
222
+ SELECT MAX(sequence) AS sequence FROM chat_log WHERE chat_id = ${chatId}
218
223
  `
219
224
 
220
- const rows = yield* decodeLastSeq(result)
225
+ const rows = yield* decodeLastSequence(result)
221
226
 
222
- return rows[0]?.seq ?? 0
227
+ return rows[0]?.sequence ?? 0
223
228
  }, Effect.orDie),
224
229
  write: Effect.fnUntraced(function* (chatId: ChatId, entries: ReadonlyArray<AssistantEvent.Entry>) {
225
230
  const rows = yield* Effect.forEach(
@@ -227,7 +232,7 @@ export const layerSql: Layer.Layer<ChatLog, never, SqlClient.SqlClient> = Layer.
227
232
  Effect.fnUntraced(function* (entry: AssistantEvent.Entry) {
228
233
  const event = yield* encodeEvent(entry.event)
229
234
 
230
- return { chat_id: chatId, seq: entry.seq, at: entry.at, event }
235
+ return { chat_id: chatId, sequence: entry.sequence, at: entry.at, event }
231
236
  })
232
237
  )
233
238
 
@@ -235,9 +240,9 @@ export const layerSql: Layer.Layer<ChatLog, never, SqlClient.SqlClient> = Layer.
235
240
  }, Effect.orDie),
236
241
  lastEntries: Effect.gen(function* () {
237
242
  const rows = yield* sql`
238
- SELECT e.chat_id, e.seq, e.at, e.event FROM chat_log e
239
- JOIN (SELECT chat_id, MAX(seq) AS seq FROM chat_log GROUP BY chat_id) last
240
- ON last.chat_id = e.chat_id AND last.seq = e.seq
243
+ SELECT e.chat_id, e.sequence, e.at, e.event FROM chat_log e
244
+ JOIN (SELECT chat_id, MAX(sequence) AS sequence FROM chat_log GROUP BY chat_id) last
245
+ ON last.chat_id = e.chat_id AND last.sequence = e.sequence
241
246
  `
242
247
 
243
248
  return yield* toEntries(rows)
package/src/Compaction.ts CHANGED
@@ -22,9 +22,9 @@ import { toJsonText } from "./internal/schema.ts"
22
22
  import * as ToolApproval from "./ToolApproval.ts"
23
23
 
24
24
  /**
25
- * Represents the fields a summary event must have: its text and the last `seq` it covers.
25
+ * Represents the fields a summary event must have: its text and the last `sequence` it covers.
26
26
  *
27
- * Entries after `throughSeq` are kept as they are, so an input that arrived while the summary was
27
+ * Entries after `throughSequence` are kept as they are, so an input that arrived while the summary was
28
28
  * being written isn't lost.
29
29
  *
30
30
  * @category models
@@ -33,7 +33,7 @@ import * as ToolApproval from "./ToolApproval.ts"
33
33
  export interface Summary {
34
34
  readonly _tag: string
35
35
  readonly text: string
36
- readonly throughSeq: number
36
+ readonly throughSequence: number
37
37
  }
38
38
 
39
39
  /**
@@ -55,7 +55,7 @@ export const inputTokens = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): nu
55
55
  }
56
56
 
57
57
  /**
58
- * Returns the items that were open at `throughSeq` and are still open: blocks the user hasn't
58
+ * Returns the items that were open at `throughSequence` and are still open: blocks the user hasn't
59
59
  * answered, specialists that are working, and tool calls waiting for the user's approval. The result is text for
60
60
  * the model, or `None` when nothing is open.
61
61
  *
@@ -64,12 +64,12 @@ export const inputTokens = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): nu
64
64
  */
65
65
  export const openItems = (
66
66
  entries: ReadonlyArray<AssistantEvent.AnyEntry>,
67
- throughSeq: number
67
+ throughSequence: number
68
68
  ): Option.Option<string> => {
69
- const seqOf = (found: (event: AssistantEvent.AnyEntry["event"]) => boolean) =>
70
- entries.find(({ event }) => found(event))?.seq ?? Number.POSITIVE_INFINITY
69
+ const sequenceOf = (found: (event: AssistantEvent.AnyEntry["event"]) => boolean) =>
70
+ entries.find(({ event }) => found(event))?.sequence ?? Number.POSITIVE_INFINITY
71
71
 
72
- const opened = (seq: number) => seq <= throughSeq
72
+ const opened = (sequence: number) => sequence <= throughSequence
73
73
 
74
74
  const blocks = [...BlockKit.fromEntries(entries)].flatMap(([messageId, state]) => {
75
75
  const shown = entries.find(
@@ -81,7 +81,7 @@ export const openItems = (
81
81
 
82
82
  return state.open &&
83
83
  shown !== undefined &&
84
- opened(shown.seq) &&
84
+ opened(shown.sequence) &&
85
85
  message !== undefined &&
86
86
  AssistantEvent.isLibraryEvent(message) &&
87
87
  Predicate.isTagged(message, "AssistantMessage")
@@ -91,7 +91,7 @@ export const openItems = (
91
91
 
92
92
  const specialists = [...AssistantActivity.fromEntries(entries).specialists.values()].flatMap((run) =>
93
93
  opened(
94
- seqOf(
94
+ sequenceOf(
95
95
  (event) =>
96
96
  AssistantEvent.isLibraryEvent(event) &&
97
97
  Predicate.isTagged(event, "SpecialistStarted") &&
@@ -105,7 +105,7 @@ export const openItems = (
105
105
  const approvals = [...ToolApproval.fromEntries(entries).values()].flatMap((approval) =>
106
106
  approval.status === "pending" &&
107
107
  opened(
108
- seqOf(
108
+ sequenceOf(
109
109
  (event) =>
110
110
  AssistantEvent.isLibraryEvent(event) &&
111
111
  Predicate.isTagged(event, "ApprovalRequested") &&
@@ -126,7 +126,7 @@ export const openItems = (
126
126
  }
127
127
 
128
128
  /**
129
- * Returns the files the user attached up to `throughSeq`, as lines of text for the model, or
129
+ * Returns the files the user attached up to `throughSequence`, as lines of text for the model, or
130
130
  * `None` when there are none. A summary may leave out a file's id, which tools need.
131
131
  *
132
132
  * @category utils
@@ -134,10 +134,10 @@ export const openItems = (
134
134
  */
135
135
  export const attachedFiles = (
136
136
  entries: ReadonlyArray<AssistantEvent.AnyEntry>,
137
- throughSeq: number
137
+ throughSequence: number
138
138
  ): Option.Option<string> => {
139
- const lines = entries.flatMap(({ seq, event }) =>
140
- seq <= throughSeq && AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, "UserMessage")
139
+ const lines = entries.flatMap(({ sequence, event }) =>
140
+ sequence <= throughSequence && AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, "UserMessage")
141
141
  ? (event.attachments ?? []).map(AssistantEvent.attachmentLine)
142
142
  : []
143
143
  )
@@ -170,7 +170,7 @@ export const attachedFiles = (
170
170
  *
171
171
  * class Summary extends Schema.TaggedClass<Summary>()("Summary", {
172
172
  * text: Schema.String,
173
- * throughSeq: Schema.Int
173
+ * throughSequence: Schema.Int
174
174
  * }) {}
175
175
  *
176
176
  * declare const transcript: (entries: ReadonlyArray<AssistantEvent.AnyEntry>) => string
@@ -188,12 +188,12 @@ export const attachedFiles = (
188
188
  * prompt: `Summarize this chat:\n\n${transcript(entries)}`
189
189
  * })
190
190
  *
191
- * yield* chat.append(Summary.make({ text, throughSeq: entries.at(-1)?.seq ?? 0 }))
191
+ * yield* chat.append(Summary.make({ text, throughSequence: entries.at(-1)?.sequence ?? 0 }))
192
192
  * })
193
193
  *
194
194
  * // After the reply, so the user doesn't wait for the summary.
195
195
  * return Stream.concat(
196
- * turn.loop({ instructions: "Be brief.", history: Compaction.since(Schema.is(Summary)) }),
196
+ * turn.run({ instructions: "Be brief.", history: Compaction.since(Schema.is(Summary)) }),
197
197
  * Stream.drain(Stream.fromEffect(compact))
198
198
  * )
199
199
  * })
@@ -211,18 +211,18 @@ export const since =
211
211
 
212
212
  if (last === undefined || !isSummary(last)) return entries
213
213
 
214
- const open = Option.match(openItems(entries, last.throughSeq), {
214
+ const open = Option.match(openItems(entries, last.throughSequence), {
215
215
  onNone: () => "",
216
216
  onSome: (items) => `\n\n<still_open>\n${items}\n</still_open>`
217
217
  })
218
218
 
219
- const files = Option.match(attachedFiles(entries, last.throughSeq), {
219
+ const files = Option.match(attachedFiles(entries, last.throughSequence), {
220
220
  onNone: () => "",
221
221
  onSome: (lines) => `\n\n<files_attached_earlier>\n${lines}\n</files_attached_earlier>`
222
222
  })
223
223
 
224
224
  return [
225
225
  userText(`<earlier_in_this_chat>\n${last.text}\n</earlier_in_this_chat>${files}${open}`),
226
- ...entries.filter(({ seq, event }) => seq > last.throughSeq && !isSummary(event))
226
+ ...entries.filter(({ sequence, event }) => sequence > last.throughSequence && !isSummary(event))
227
227
  ]
228
228
  }
package/src/Memory.ts CHANGED
@@ -218,7 +218,7 @@ const Recall = Tool.make("recall", {
218
218
  * return (turn) =>
219
219
  * Stream.unwrap(
220
220
  * Effect.map(memory.facts(turn.chatId), (facts) =>
221
- * turn.loop({
221
+ * turn.run({
222
222
  * instructions: `You help a small team.\n\n${Memory.render(facts)}`,
223
223
  * toolkit: Memory.Tools
224
224
  * })
package/src/Skill.ts CHANGED
@@ -168,7 +168,7 @@ function toolkitOf(skills: ReadonlyArray<Skill<unknown>>): Toolkit<unknown> {
168
168
  * const AssistantSkills = Skill.toolkit([orderingCampaigns, sharingAssets])
169
169
  *
170
170
  * const MyAssistantLive = MyAssistant.toLayer((turn) =>
171
- * turn.loop({ instructions, toolkit: Toolkit.merge(MyTools, AssistantSkills) })
171
+ * turn.run({ instructions, toolkit: Toolkit.merge(MyTools, AssistantSkills) })
172
172
  * ).pipe(Layer.provide([MyToolsLive, AssistantSkills.layer]))
173
173
  * ```
174
174
  *
package/src/Specialist.ts CHANGED
@@ -226,7 +226,7 @@ const toStream = <R, RE>(
226
226
  *
227
227
  * export const MyAssistant = Assistant.make("my-assistant", { specialists: [Research] })
228
228
  *
229
- * const MyAssistantLive = MyAssistant.toLayer((turn) => turn.loop({ instructions })).pipe(
229
+ * const MyAssistantLive = MyAssistant.toLayer((turn) => turn.run({ instructions })).pipe(
230
230
  * Layer.provide(ResearchLive)
231
231
  * )
232
232
  * ```