effect-assistant 0.5.0 → 0.7.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 (77) 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 +2 -2
  29. package/dist/Skill.js +2 -2
  30. package/dist/Skill.js.map +1 -1
  31. package/dist/Specialist.d.ts +17 -16
  32. package/dist/Specialist.d.ts.map +1 -1
  33. package/dist/Specialist.js +5 -5
  34. package/dist/Specialist.js.map +1 -1
  35. package/dist/Timeline.d.ts +9 -9
  36. package/dist/Timeline.js +16 -16
  37. package/dist/Timeline.js.map +1 -1
  38. package/dist/ToolApproval.d.ts +1 -1
  39. package/dist/ToolApproval.js +1 -1
  40. package/dist/ToolApproval.js.map +1 -1
  41. package/dist/ToolDiscovery.d.ts +1 -1
  42. package/dist/ToolDiscovery.js +1 -1
  43. package/dist/ToolDiscovery.js.map +1 -1
  44. package/dist/internal/chat.js +4 -4
  45. package/dist/internal/chat.js.map +1 -1
  46. package/dist/internal/events.js +1 -1
  47. package/dist/internal/events.js.map +1 -1
  48. package/dist/internal/history.js +23 -23
  49. package/dist/internal/history.js.map +1 -1
  50. package/dist/internal/loop.d.ts +1 -1
  51. package/dist/internal/loop.js +4 -4
  52. package/dist/internal/loop.js.map +1 -1
  53. package/dist/internal/specialists.js +6 -6
  54. package/dist/internal/specialists.js.map +1 -1
  55. package/dist/internal/turns.d.ts.map +1 -1
  56. package/dist/internal/turns.js +18 -18
  57. package/dist/internal/turns.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/Assistant.ts +11 -11
  60. package/src/AssistantActivity.ts +9 -7
  61. package/src/AssistantEngine.ts +3 -3
  62. package/src/AssistantEvent.ts +8 -8
  63. package/src/AssistantLoop.ts +3 -3
  64. package/src/ChatLog.ts +31 -26
  65. package/src/Compaction.ts +21 -21
  66. package/src/Memory.ts +1 -1
  67. package/src/Skill.ts +2 -2
  68. package/src/Specialist.ts +19 -18
  69. package/src/Timeline.ts +22 -22
  70. package/src/ToolApproval.ts +1 -1
  71. package/src/ToolDiscovery.ts +1 -1
  72. package/src/internal/chat.ts +4 -4
  73. package/src/internal/events.ts +1 -1
  74. package/src/internal/history.ts +32 -28
  75. package/src/internal/loop.ts +5 -5
  76. package/src/internal/specialists.ts +7 -7
  77. package/src/internal/turns.ts +28 -26
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
  *
@@ -186,7 +186,7 @@ function toolkitOf(skills: ReadonlyArray<Skill<unknown>>): Toolkit<unknown> {
186
186
  * const skills = Skill.toolkit(allSkills.filter((skill) => allowed(role, skill)))
187
187
  *
188
188
  * return turn
189
- * .loop({ instructions, toolkit: Toolkit.merge(MyTools, skills) })
189
+ * .run({ instructions, toolkit: Toolkit.merge(MyTools, skills) })
190
190
  * .pipe(Stream.provide(skills.layer))
191
191
  * })
192
192
  * )
package/src/Specialist.ts CHANGED
@@ -40,12 +40,12 @@ export interface Specialist<Name extends string = string> {
40
40
  /** The key of the specialist's handler, as `toLayer` provides it. */
41
41
  readonly Handler: Context.Key<SpecialistHandler<Name>, Built>
42
42
  /**
43
- * Provides the specialist's handler. The handler may use the {@link RunServices} and the
43
+ * Provides the specialist's handler. The handler may use the {@link TurnServices} and the
44
44
  * services its build Effect required (see {@link CheckServices}).
45
45
  */
46
46
  readonly toLayer: <R = never, RE = never, E = never, RX = never>(
47
47
  build: (Handler<R, RE> | Effect.Effect<Handler<R, RE>, E, RX>) &
48
- CheckServices<Exclude<R | RE, RunServices | RX | Tool.Handler<string>>>
48
+ CheckServices<Exclude<R | RE, TurnServices | RX | Tool.Handler<string>>>
49
49
  ) => Layer.Layer<SpecialistHandler<Name>, never, Exclude<RX, Scope.Scope | CurrentChat> | ToolHandlers<R | RE>>
50
50
  }
51
51
 
@@ -108,21 +108,21 @@ export interface SpecialistHandler<Name extends string> {
108
108
  export type HandlerOf<S> = S extends Specialist<infer Name> ? SpecialistHandler<Name> : never
109
109
 
110
110
  /**
111
- * The services a run provides to its handler, which are its chat, the chat's runner, and the
111
+ * The services a turn provides to its handler, which are its chat, the chat's runner, and the
112
112
  * model.
113
113
  *
114
114
  * @category utility types
115
115
  * @since 0.1.0
116
116
  */
117
- export type RunServices = CurrentChat | Runner | LanguageModel.LanguageModel
117
+ export type TurnServices = CurrentChat | Runner | LanguageModel.LanguageModel
118
118
 
119
119
  /**
120
- * Represents the options for `run.loop`.
120
+ * Represents the options for `turn.run`.
121
121
  *
122
122
  * @category models
123
123
  * @since 0.1.0
124
124
  */
125
- export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never, never>> {
125
+ export interface RunOptions<T extends Record<string, Tool.Any> = Record<never, never>> {
126
126
  readonly instructions: string | Instructions.Resolved
127
127
  /**
128
128
  * The loop's tools, as a native toolkit whose handlers come from the layer. Merge in a
@@ -134,12 +134,13 @@ export interface LoopOptions<T extends Record<string, Tool.Any> = Record<never,
134
134
  }
135
135
 
136
136
  /**
137
- * Represents one run of a specialist on a brief, as its handler sees it.
137
+ * Represents one turn of a specialist's run, as its handler sees it. The first turn works on the
138
+ * brief, and each later one on the messages sent to the run since (follow-ups and steering).
138
139
  *
139
140
  * @category models
140
141
  * @since 0.1.0
141
142
  */
142
- export interface Run {
143
+ export interface Turn {
143
144
  readonly runId: RunId
144
145
  /** The chat the run belongs to, which is the chat of the turn that spawned it. */
145
146
  readonly chatId: ChatId
@@ -150,8 +151,8 @@ export interface Run {
150
151
  */
151
152
  readonly history: Effect.Effect<ReadonlyArray<Prompt.Message>>
152
153
  /** Runs the agent loop over the run's history, with the given instructions and tools. */
153
- readonly loop: <T extends Record<string, Tool.Any> = Record<never, never>>(
154
- options: LoopOptions<T>
154
+ readonly run: <T extends Record<string, Tool.Any> = Record<never, never>>(
155
+ options: RunOptions<T>
155
156
  ) => Stream.Stream<
156
157
  AssistantLoop.Event,
157
158
  AiError.AiError,
@@ -170,7 +171,7 @@ export interface Run {
170
171
  * @since 0.1.0
171
172
  */
172
173
  export type Handler<R = never, RE = R> = (
173
- run: Run
174
+ turn: Turn
174
175
  ) =>
175
176
  | Stream.Stream<AssistantLoop.Event, unknown, R>
176
177
  | Effect.Effect<Stream.Stream<AssistantLoop.Event, unknown, R>, unknown, RE>
@@ -186,7 +187,7 @@ export type Handler<R = never, RE = R> = (
186
187
  */
187
188
  export interface Built {
188
189
  readonly open: Effect.Effect<
189
- (run: Run) => Stream.Stream<AssistantLoop.Event, unknown, RunServices>,
190
+ (turn: Turn) => Stream.Stream<AssistantLoop.Event, unknown, TurnServices>,
190
191
  never,
191
192
  Scope.Scope | CurrentChat
192
193
  >
@@ -203,7 +204,7 @@ const toStream = <R, RE>(
203
204
  *
204
205
  * **Details**
205
206
  *
206
- * The specialist's `toLayer` provides its handler, which is usually `run.loop` with the
207
+ * The specialist's `toLayer` provides its handler, which is usually `turn.run` with the
207
208
  * specialist's own instructions and tools. List the specialist in `Assistant.make`'s
208
209
  * `specialists`, and provide its layer to the assistant's.
209
210
  *
@@ -217,8 +218,8 @@ const toStream = <R, RE>(
217
218
  * description: "Researches past campaigns and orders. Give it the question and any context."
218
219
  * })
219
220
  *
220
- * const ResearchLive = Research.toLayer((run) =>
221
- * run.loop({
221
+ * const ResearchLive = Research.toLayer((turn) =>
222
+ * turn.run({
222
223
  * instructions: "Answer with the findings and numbers the colleague needs, in a few lines.",
223
224
  * toolkit: ResearchTools
224
225
  * })
@@ -226,7 +227,7 @@ const toStream = <R, RE>(
226
227
  *
227
228
  * export const MyAssistant = Assistant.make("my-assistant", { specialists: [Research] })
228
229
  *
229
- * const MyAssistantLive = MyAssistant.toLayer((turn) => turn.loop({ instructions })).pipe(
230
+ * const MyAssistantLive = MyAssistant.toLayer((turn) => turn.run({ instructions })).pipe(
230
231
  * Layer.provide(ResearchLive)
231
232
  * )
232
233
  * ```
@@ -248,7 +249,7 @@ export const make = <const Name extends string>(
248
249
  // services.
249
250
  function toLayer<R = never, RE = never, E = never, RX = never>(
250
251
  build: (Handler<R, RE> | Effect.Effect<Handler<R, RE>, E, RX>) &
251
- CheckServices<Exclude<R | RE, RunServices | RX | Tool.Handler<string>>>
252
+ CheckServices<Exclude<R | RE, TurnServices | RX | Tool.Handler<string>>>
252
253
  ): Layer.Layer<SpecialistHandler<Name>, never, Exclude<RX, Scope.Scope | CurrentChat> | ToolHandlers<R | RE>>
253
254
  function toLayer(
254
255
  build: Handler<any, any> | Effect.Effect<Handler<any, any>, unknown, any>
@@ -264,7 +265,7 @@ export const make = <const Name extends string>(
264
265
  open: Effect.gen(function* () {
265
266
  const handler = Effect.isEffect(build) ? yield* build : build
266
267
 
267
- return (run: Run) => Stream.provideContext(toStream(handler(run)), context)
268
+ return (turn: Turn) => Stream.provideContext(toStream(handler(turn)), context)
268
269
  }).pipe(Effect.provideContext(context), Effect.orDie)
269
270
  }
270
271
  })
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
  * })