effect-assistant 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/Assistant.d.ts +74 -28
  2. package/dist/Assistant.d.ts.map +1 -1
  3. package/dist/Assistant.js +11 -4
  4. package/dist/Assistant.js.map +1 -1
  5. package/dist/AssistantActivity.d.ts +10 -16
  6. package/dist/AssistantActivity.d.ts.map +1 -1
  7. package/dist/AssistantActivity.js +11 -4
  8. package/dist/AssistantActivity.js.map +1 -1
  9. package/dist/AssistantEngine.d.ts +39 -1
  10. package/dist/AssistantEngine.d.ts.map +1 -1
  11. package/dist/AssistantEngine.js +81 -28
  12. package/dist/AssistantEngine.js.map +1 -1
  13. package/dist/AssistantEvent.d.ts +50 -5
  14. package/dist/AssistantEvent.d.ts.map +1 -1
  15. package/dist/AssistantEvent.js +28 -7
  16. package/dist/AssistantEvent.js.map +1 -1
  17. package/dist/BlockKit.d.ts +4 -4
  18. package/dist/BlockKit.js +1 -1
  19. package/dist/BlockKit.js.map +1 -1
  20. package/dist/ChatLog.js +1 -1
  21. package/dist/Compaction.d.ts +6 -2
  22. package/dist/Compaction.d.ts.map +1 -1
  23. package/dist/Compaction.js +6 -2
  24. package/dist/Compaction.js.map +1 -1
  25. package/dist/CurrentChat.d.ts +2 -1
  26. package/dist/CurrentChat.d.ts.map +1 -1
  27. package/dist/CurrentChat.js.map +1 -1
  28. package/dist/Instructions.js +1 -1
  29. package/dist/Skill.d.ts +33 -0
  30. package/dist/Skill.d.ts.map +1 -1
  31. package/dist/Skill.js +27 -1
  32. package/dist/Skill.js.map +1 -1
  33. package/dist/Specialist.d.ts +4 -1
  34. package/dist/Specialist.d.ts.map +1 -1
  35. package/dist/Specialist.js.map +1 -1
  36. package/dist/Timeline.d.ts +482 -0
  37. package/dist/Timeline.d.ts.map +1 -0
  38. package/dist/Timeline.js +572 -0
  39. package/dist/Timeline.js.map +1 -0
  40. package/dist/index.d.ts +2 -3
  41. package/dist/index.js +2 -3
  42. package/dist/internal/chat.js +5 -6
  43. package/dist/internal/chat.js.map +1 -1
  44. package/dist/internal/events.d.ts +15 -16
  45. package/dist/internal/events.d.ts.map +1 -1
  46. package/dist/internal/events.js +29 -28
  47. package/dist/internal/events.js.map +1 -1
  48. package/dist/internal/history.js +25 -19
  49. package/dist/internal/history.js.map +1 -1
  50. package/dist/internal/input.js +7 -0
  51. package/dist/internal/input.js.map +1 -0
  52. package/dist/internal/loop.js +1 -1
  53. package/dist/internal/skills.js +6 -1
  54. package/dist/internal/skills.js.map +1 -1
  55. package/dist/internal/specialists.js +5 -4
  56. package/dist/internal/specialists.js.map +1 -1
  57. package/dist/internal/tools.js +1 -1
  58. package/dist/internal/turns.d.ts.map +1 -1
  59. package/dist/internal/turns.js +23 -35
  60. package/dist/internal/turns.js.map +1 -1
  61. package/package.json +1 -1
  62. package/src/Assistant.ts +75 -29
  63. package/src/AssistantActivity.ts +11 -18
  64. package/src/AssistantEngine.ts +110 -43
  65. package/src/AssistantEvent.ts +46 -8
  66. package/src/BlockKit.ts +6 -6
  67. package/src/Compaction.ts +6 -2
  68. package/src/CurrentChat.ts +2 -1
  69. package/src/Skill.ts +34 -1
  70. package/src/Specialist.ts +4 -1
  71. package/src/Timeline.ts +861 -0
  72. package/src/index.ts +5 -10
  73. package/src/internal/chat.ts +4 -4
  74. package/src/internal/events.ts +43 -43
  75. package/src/internal/history.ts +36 -29
  76. package/src/internal/input.ts +3 -0
  77. package/src/internal/skills.ts +11 -0
  78. package/src/internal/specialists.ts +3 -2
  79. package/src/internal/turns.ts +30 -53
  80. package/dist/AppEvent.d.ts +0 -111
  81. package/dist/AppEvent.d.ts.map +0 -1
  82. package/dist/AppEvent.js +0 -71
  83. package/dist/AppEvent.js.map +0 -1
  84. package/dist/ChatView.d.ts +0 -408
  85. package/dist/ChatView.d.ts.map +0 -1
  86. package/dist/ChatView.js +0 -443
  87. package/dist/ChatView.js.map +0 -1
  88. package/src/AppEvent.ts +0 -134
  89. package/src/ChatView.ts +0 -587
package/src/ChatView.ts DELETED
@@ -1,587 +0,0 @@
1
- /**
2
- * This module provides the view of a chat as its user sees it. A chat client receives this view
3
- * and nothing else.
4
- *
5
- * A {@link View} holds the chat's messages with their blocks, the chat's
6
- * {@link Activity}, and whether the last turn failed or the chat was deleted. It never contains
7
- * the model's output, tool calls or results, what the model read, the app's own events, or why a
8
- * turn failed. The server streams {@link Update}s by piping the assistant's `subscribe` through
9
- * {@link updates}, and the client folds them with {@link apply}, starting from {@link empty}.
10
- * On the server, {@link fromEntries} builds a whole view from a chat's log.
11
- *
12
- * @since 0.1.0
13
- */
14
- import { dual } from "effect/Function"
15
- import * as Match from "effect/Match"
16
- import * as Schema from "effect/Schema"
17
- import * as Stream from "effect/Stream"
18
- import * as AssistantActivity from "./AssistantActivity.ts"
19
- import * as AssistantEvent from "./AssistantEvent.ts"
20
- import * as BlockKit from "./BlockKit.ts"
21
- import { InputId } from "./InputId.ts"
22
- import { toJson } from "./internal/schema.ts"
23
-
24
- /**
25
- * Schema for the status of a block. A block is `"open"` while it accepts an answer, `"answered"`
26
- * once the user has used it, and `"closed"` when it closed without an answer because the user
27
- * answered in words or moved on.
28
- *
29
- * @category schemas
30
- * @since 0.1.0
31
- */
32
- export const BlockStatus = Schema.Literals(["open", "answered", "closed"])
33
-
34
- /**
35
- * The status of a block.
36
- *
37
- * @category models
38
- * @since 0.1.0
39
- */
40
- export type BlockStatus = typeof BlockStatus.Type
41
-
42
- /**
43
- * Schema for a block on an assistant's message. It holds the block's kind, its props (JSON, as
44
- * the block's props schema encodes them), its status, and the user's answer (JSON, as the block's
45
- * answer schema encodes it).
46
- *
47
- * @category schemas
48
- * @since 0.1.0
49
- */
50
- export const Block = Schema.Struct({
51
- kind: Schema.String,
52
- props: Schema.Json,
53
- status: BlockStatus,
54
- answer: Schema.optional(Schema.Json)
55
- })
56
-
57
- /**
58
- * Represents a block on an assistant's message.
59
- *
60
- * @category models
61
- * @since 0.1.0
62
- */
63
- export type Block = typeof Block.Type
64
-
65
- /**
66
- * Schema for a message the user sent. `at` is when it was stored, in epoch
67
- * milliseconds.
68
- *
69
- * @category schemas
70
- * @since 0.1.0
71
- */
72
- export const UserMessage = Schema.TaggedStruct("UserMessage", {
73
- id: InputId,
74
- text: Schema.String,
75
- attachments: Schema.optional(Schema.Array(AssistantEvent.Attachment)),
76
- at: Schema.Finite
77
- })
78
-
79
- /**
80
- * Represents a message the user sent.
81
- *
82
- * @category models
83
- * @since 0.1.0
84
- */
85
- export type UserMessage = typeof UserMessage.Type
86
-
87
- /**
88
- * Schema for a message the assistant sent the user, including its block if it has one.
89
- *
90
- * @category schemas
91
- * @since 0.1.0
92
- */
93
- export const AssistantMessage = Schema.TaggedStruct("AssistantMessage", {
94
- id: Schema.String,
95
- text: Schema.String,
96
- at: Schema.Finite,
97
- block: Schema.optional(Block)
98
- })
99
-
100
- /**
101
- * Represents a message the assistant sent the user.
102
- *
103
- * @category models
104
- * @since 0.1.0
105
- */
106
- export type AssistantMessage = typeof AssistantMessage.Type
107
-
108
- /**
109
- * Schema for a message in the chat, sent by either the user or the assistant.
110
- *
111
- * @category schemas
112
- * @since 0.1.0
113
- */
114
- export const Message = Schema.Union([UserMessage, AssistantMessage])
115
-
116
- /**
117
- * Represents a message in the chat.
118
- *
119
- * @category models
120
- * @since 0.1.0
121
- */
122
- export type Message = typeof Message.Type
123
-
124
- /**
125
- * Schema for a specialist that is working for the assistant. It holds the specialist's name and
126
- * when it started, in epoch milliseconds.
127
- *
128
- * @category schemas
129
- * @since 0.1.0
130
- */
131
- export const SpecialistWorking = Schema.Struct({
132
- specialist: Schema.String,
133
- since: Schema.Finite
134
- })
135
-
136
- /**
137
- * Represents a specialist that is working for the assistant.
138
- *
139
- * @category models
140
- * @since 0.1.0
141
- */
142
- export type SpecialistWorking = typeof SpecialistWorking.Type
143
-
144
- /**
145
- * Schema for what is working in the chat.
146
- *
147
- * `working` is true while the assistant runs a turn, or when a turn is about to start for
148
- * something that arrived. `specialists` lists the specialists that are working. When the chat is
149
- * idle, nothing is working, even if a workflow is waiting on a timer or on the user.
150
- *
151
- * @category schemas
152
- * @since 0.1.0
153
- */
154
- export const Activity = Schema.Struct({
155
- working: Schema.Boolean,
156
- specialists: Schema.Array(SpecialistWorking)
157
- })
158
-
159
- /**
160
- * Represents what is working in the chat.
161
- *
162
- * @category models
163
- * @since 0.1.0
164
- */
165
- export type Activity = typeof Activity.Type
166
-
167
- /**
168
- * Schema for a chat as its user sees it. It holds the chat's messages, what is working in it,
169
- * whether the last turn failed, and whether the chat was deleted. `seq` is the position in the
170
- * chat's log that the view is up to.
171
- *
172
- * @category schemas
173
- * @since 0.1.0
174
- */
175
- export const View = Schema.Struct({
176
- seq: Schema.Int,
177
- messages: Schema.Array(Message),
178
- activity: Activity,
179
- /** Whether the last turn failed with no message after it. */
180
- failed: Schema.Boolean,
181
- /** Whether the chat was deleted. A deleted chat accepts nothing more, and its stream ends. */
182
- deleted: Schema.Boolean
183
- })
184
-
185
- /**
186
- * Represents a chat as its user sees it.
187
- *
188
- * @category models
189
- * @since 0.1.0
190
- */
191
- export type View = typeof View.Type
192
-
193
- /**
194
- * Schema for a change to a chat's view.
195
- *
196
- * An update holds each message that is new or changed (for example, a block answered or
197
- * closed), in full. It also holds the current activity, whether the last turn failed,
198
- * and whether the chat was deleted. `seq` is the position in the chat's log that the update is up
199
- * to.
200
- *
201
- * @category schemas
202
- * @since 0.1.0
203
- */
204
- export const Update = Schema.Struct({
205
- seq: Schema.Int,
206
- messages: Schema.Array(Message),
207
- activity: Activity,
208
- failed: Schema.Boolean,
209
- deleted: Schema.Boolean
210
- })
211
-
212
- /**
213
- * Represents a change to a chat's view.
214
- *
215
- * @category models
216
- * @since 0.1.0
217
- */
218
- export type Update = typeof Update.Type
219
-
220
- /**
221
- * The view of a chat before anything has happened in it, with no messages and nothing working.
222
- * Use it as the starting point when folding updates with {@link apply}.
223
- *
224
- * @category constructors
225
- * @since 0.1.0
226
- */
227
- export const empty: View = {
228
- seq: 0,
229
- messages: [],
230
- activity: { working: false, specialists: [] },
231
- failed: false,
232
- deleted: false
233
- }
234
-
235
- /**
236
- * Returns the view after applying an update.
237
- *
238
- * **Details**
239
- *
240
- * Each message in the update replaces the message with the same id, in place, and new messages
241
- * are added after the rest. The update's activity, `failed`, and `deleted` replace the view's.
242
- * Applying an update twice changes nothing, so a client that resubscribes from the start can fold
243
- * again and get the same view.
244
- *
245
- * @category combinators
246
- * @since 0.1.0
247
- */
248
- export const apply = (view: View, update: Update): View => {
249
- const changed = new Map(update.messages.map((message) => [message.id, message]))
250
- const known = new Set(view.messages.map((message) => message.id))
251
-
252
- return {
253
- seq: Math.max(view.seq, update.seq),
254
- messages: [
255
- ...view.messages.map((message) => changed.get(message.id) ?? message),
256
- ...update.messages.filter((message) => !known.has(message.id))
257
- ],
258
- activity: update.activity,
259
- failed: update.failed,
260
- deleted: update.deleted
261
- }
262
- }
263
-
264
- /**
265
- * Returns whether nothing is working in the chat: no turn is running or about to start, and no
266
- * specialist is working.
267
- *
268
- * @category guards
269
- * @since 0.1.0
270
- */
271
- export const isIdle = (view: View): boolean => !view.activity.working && view.activity.specialists.length === 0
272
-
273
- /**
274
- * Represents the options for building a view from a chat's log.
275
- *
276
- * @category models
277
- * @since 0.1.0
278
- */
279
- export interface Options {
280
- /**
281
- * The tags of the app's events that wake a turn. After such an event, the view shows a turn as
282
- * about to start. Defaults to none.
283
- */
284
- readonly wakes?: ReadonlySet<string> | undefined
285
- }
286
-
287
- /**
288
- * Returns a chat's view, built from the entries of its log.
289
- *
290
- * **When to use**
291
- *
292
- * Use it in server-side code, for example to return a whole chat at once. A client gets the view
293
- * as a stream from {@link updates} instead.
294
- *
295
- * **Example** (Building a chat's view from its log)
296
- *
297
- * ```ts
298
- * import { type ChatId, ChatLog, ChatView } from "effect-assistant"
299
- * import * as Effect from "effect/Effect"
300
- *
301
- * const viewOf = (chatId: ChatId.ChatId) =>
302
- * ChatLog.ChatLog.use((log) => log.read(chatId)).pipe(
303
- * Effect.map((entries) => ChatView.fromEntries(entries))
304
- * )
305
- * ```
306
- *
307
- * @see {@link updates} for a stream of changes instead of a whole view
308
- *
309
- * @category constructors
310
- * @since 0.1.0
311
- */
312
- export const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, options?: Options): View => {
313
- const fold = folding(options?.wakes ?? new Set())
314
-
315
- for (const entry of entries) fold.step(entry)
316
-
317
- return fold.view
318
- }
319
-
320
- interface Folding {
321
- readonly step: (entry: AssistantEvent.AnyEntry) => Update | undefined
322
- readonly view: View
323
- }
324
-
325
- const activityOf = (activity: AssistantActivity.State): Activity => ({
326
- working: activity.turnSince !== undefined || activity.queued,
327
- specialists: [...activity.specialists.values()].map((run) => ({
328
- specialist: run.specialist,
329
- since: run.since
330
- }))
331
- })
332
-
333
- const sameActivity = (a: Activity, b: Activity) =>
334
- a.working === b.working &&
335
- a.specialists.length === b.specialists.length &&
336
- a.specialists.every((run, i) => {
337
- const other = b.specialists[i]
338
-
339
- return other !== undefined && run.specialist === other.specialist && run.since === other.since
340
- })
341
-
342
- const folding = (wakes: ReadonlySet<string>): Folding => {
343
- let seq = 0
344
- let failed = false
345
- let deleted = false
346
- let activity = AssistantActivity.empty
347
- let blocks: ReadonlyMap<string, BlockKit.State> = new Map()
348
- // The messages in order, and the assistant's as stored: their blocks change as the chat goes on.
349
- const messages = new Map<string, Message>()
350
- const sent = new Map<string, AssistantEvent.AssistantMessage>()
351
-
352
- const blockOf = (message: AssistantEvent.AssistantMessage): Block | undefined => {
353
- if (message.block === undefined) return undefined
354
-
355
- const state = blocks.get(message.id)
356
- const status = state === undefined || state.open ? "open" : state.answer === undefined ? "closed" : "answered"
357
- const block: Block = { kind: message.block.kind, props: toJson(message.block.props), status }
358
-
359
- return state?.answer === undefined ? block : { ...block, answer: toJson(state.answer.value) }
360
- }
361
-
362
- const viewOf = (message: AssistantEvent.AssistantMessage, at: number): AssistantMessage => {
363
- const block = blockOf(message)
364
-
365
- return block === undefined
366
- ? AssistantMessage.make({ id: message.id, text: message.text, at })
367
- : AssistantMessage.make({ id: message.id, text: message.text, at, block })
368
- }
369
-
370
- const touched = (event: AssistantEvent.AssistantEvent, at: number): ReadonlyArray<string> =>
371
- Match.value(event).pipe(
372
- Match.tagsExhaustive({
373
- UserMessage: (message) => {
374
- messages.set(
375
- message.id,
376
- message.attachments === undefined
377
- ? UserMessage.make({ id: message.id, text: message.text, at })
378
- : UserMessage.make({
379
- id: message.id,
380
- text: message.text,
381
- attachments: message.attachments,
382
- at
383
- })
384
- )
385
- failed = false
386
-
387
- return [message.id]
388
- },
389
- AssistantMessage: (message) => {
390
- sent.set(message.id, message)
391
- messages.set(message.id, viewOf(message, at))
392
- failed = false
393
-
394
- return [message.id, ...(message.closes ?? [])]
395
- },
396
- BlockAnswer: (answer) => {
397
- failed = false
398
-
399
- return [answer.messageId]
400
- },
401
- BlocksClosed: (closed) => closed.messageIds,
402
- TurnFailed: () => {
403
- failed = true
404
-
405
- return []
406
- },
407
- ModelOutput: () => [],
408
- ToolStarted: () => [],
409
- ToolResult: () => [],
410
- TurnStarted: () => [],
411
- TurnCompleted: () => [],
412
- SpecialistStarted: () => [],
413
- SpecialistFinished: () => [],
414
- ChatDeleted: () => {
415
- deleted = true
416
-
417
- return []
418
- }
419
- })
420
- )
421
-
422
- const step = (entry: AssistantEvent.AnyEntry): Update | undefined => {
423
- const before = { activity: activityOf(activity), failed, deleted }
424
- const { event } = entry
425
-
426
- seq = entry.seq
427
- activity = AssistantActivity.apply(activity, entry, { wakes })
428
-
429
- if (!AssistantEvent.isLibraryEvent(event)) {
430
- const now = activityOf(activity)
431
-
432
- return sameActivity(before.activity, now) ? undefined : { seq, messages: [], activity: now, failed, deleted }
433
- }
434
-
435
- blocks = BlockKit.apply(blocks, event)
436
-
437
- const changed = [...new Set(touched(event, entry.at))].flatMap((id) => {
438
- const message = sent.get(id)
439
- const current = messages.get(id)
440
-
441
- if (message === undefined || current === undefined) return current === undefined ? [] : [current]
442
-
443
- const next = viewOf(message, current.at)
444
- messages.set(id, next)
445
-
446
- return [next]
447
- })
448
-
449
- const now = activityOf(activity)
450
-
451
- return changed.length === 0 &&
452
- failed === before.failed &&
453
- deleted === before.deleted &&
454
- sameActivity(before.activity, now)
455
- ? undefined
456
- : { seq, messages: changed, activity: now, failed, deleted }
457
- }
458
-
459
- return {
460
- step,
461
- get view() {
462
- return { seq, messages: [...messages.values()], activity: activityOf(activity), failed, deleted }
463
- }
464
- }
465
- }
466
-
467
- const merge = (updates: ReadonlyArray<Update>): Update | undefined => {
468
- const last = updates.at(-1)
469
-
470
- if (last === undefined) return undefined
471
-
472
- const messages = new Map<string, Message>()
473
-
474
- for (const update of updates) for (const message of update.messages) messages.set(message.id, message)
475
-
476
- return {
477
- seq: last.seq,
478
- messages: [...messages.values()],
479
- activity: last.activity,
480
- failed: last.failed,
481
- deleted: last.deleted
482
- }
483
- }
484
-
485
- /**
486
- * Represents an assistant's definition as {@link updates} reads it: the app's events it
487
- * registered, and whether each one wakes a turn. An assistant made with `Assistant.make` fits it.
488
- *
489
- * @category models
490
- * @since 0.1.0
491
- */
492
- export interface Definition {
493
- readonly events: ReadonlyArray<{ readonly tag: string; readonly wakes: boolean }>
494
- }
495
-
496
- /**
497
- * Represents the options accepted by {@link updates}.
498
- *
499
- * @category models
500
- * @since 0.1.0
501
- */
502
- export interface UpdatesOptions {
503
- /**
504
- * The `seq` up to which the client already has the view. The result contains only the updates
505
- * after it. Defaults to `0`, every update.
506
- */
507
- readonly after?: number | undefined
508
- }
509
-
510
- const wakesOf = (definition: Definition): ReadonlySet<string> =>
511
- new Set(definition.events.flatMap((event) => (event.wakes ? [event.tag] : [])))
512
-
513
- /**
514
- * Returns the stream of view updates that a chat's entries produce, for a chat client.
515
- *
516
- * **Details**
517
- *
518
- * The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does
519
- * without `after`. The fold reads every entry, because a message's view depends on earlier
520
- * entries: a block is answered or closed later. `after` only skips
521
- * emitting the updates up to that `seq`, so a client that has the view up to a `seq` receives only
522
- * what changed since. Pass `after` here, not to `subscribe`: a subscription that starts after a
523
- * `seq` gives the fold an incomplete log, and the updates it produces are wrong.
524
- *
525
- * The definition tells the fold which of the app's events wake a turn, so the view shows a turn as
526
- * about to start after one. Each chunk of entries becomes one update; the stored entries, for
527
- * example, arrive as one chunk.
528
- *
529
- * **Example** (Streaming a chat's view to a client)
530
- *
531
- * ```ts
532
- * import { Assistant, type ChatId, ChatView } from "effect-assistant"
533
- * import * as Stream from "effect/Stream"
534
- *
535
- * const MyAssistant = Assistant.make("my-assistant")
536
- *
537
- * const watch = (chatId: ChatId.ChatId, after: number) =>
538
- * Stream.unwrap(
539
- * MyAssistant.useSync((assistant) =>
540
- * assistant.subscribe(chatId).pipe(ChatView.updates(MyAssistant, { after }))
541
- * )
542
- * )
543
- * ```
544
- *
545
- * @see {@link apply} for folding the updates into a view on the client
546
- *
547
- * @category utils
548
- * @since 0.1.0
549
- */
550
- export const updates: {
551
- (
552
- definition: Definition,
553
- options?: UpdatesOptions
554
- ): <E, R>(entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>) => Stream.Stream<Update, E, R>
555
- <E, R>(
556
- entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,
557
- definition: Definition,
558
- options?: UpdatesOptions
559
- ): Stream.Stream<Update, E, R>
560
- } = dual(
561
- (args) => Stream.isStream(args[0]),
562
- <E, R>(
563
- entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,
564
- definition: Definition,
565
- options?: UpdatesOptions
566
- ): Stream.Stream<Update, E, R> => {
567
- const after = options?.after ?? 0
568
- const wakes = wakesOf(definition)
569
-
570
- return entries.pipe(
571
- Stream.mapAccumArray(
572
- () => folding(wakes),
573
- (fold, chunk) => {
574
- const changed = chunk.flatMap((entry) => {
575
- const update = fold.step(entry)
576
-
577
- return update === undefined || entry.seq <= after ? [] : [update]
578
- })
579
-
580
- const merged = merge(changed)
581
-
582
- return [fold, merged === undefined ? [] : [merged]]
583
- }
584
- )
585
- )
586
- }
587
- )