clanka 0.3.1 → 0.5.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 (75) hide show
  1. package/dist/Acp.d.ts.map +1 -1
  2. package/dist/Acp.js +3 -0
  3. package/dist/Acp.js.map +1 -1
  4. package/dist/Acp.test.js +58 -0
  5. package/dist/Acp.test.js.map +1 -1
  6. package/dist/Agent.d.ts +2 -2
  7. package/dist/Agent.d.ts.map +1 -1
  8. package/dist/Agent.js +86 -8
  9. package/dist/Agent.js.map +1 -1
  10. package/dist/Agent.test.js +671 -0
  11. package/dist/Agent.test.js.map +1 -1
  12. package/dist/AgentExecutor.d.ts +15 -4
  13. package/dist/AgentExecutor.d.ts.map +1 -1
  14. package/dist/AgentExecutor.js +43 -7
  15. package/dist/AgentExecutor.js.map +1 -1
  16. package/dist/AgentOutput.d.ts +49 -4
  17. package/dist/AgentOutput.d.ts.map +1 -1
  18. package/dist/AgentOutput.js +45 -0
  19. package/dist/AgentOutput.js.map +1 -1
  20. package/dist/AgentSkills.d.ts +56 -0
  21. package/dist/AgentSkills.d.ts.map +1 -0
  22. package/dist/AgentSkills.js +126 -0
  23. package/dist/AgentSkills.js.map +1 -0
  24. package/dist/AgentSkills.test.d.ts +2 -0
  25. package/dist/AgentSkills.test.d.ts.map +1 -0
  26. package/dist/AgentSkills.test.js +356 -0
  27. package/dist/AgentSkills.test.js.map +1 -0
  28. package/dist/AgentTools.d.ts +32 -1
  29. package/dist/AgentTools.d.ts.map +1 -1
  30. package/dist/AgentTools.js +26 -10
  31. package/dist/AgentTools.js.map +1 -1
  32. package/dist/Codex.js +1 -1
  33. package/dist/Codex.js.map +1 -1
  34. package/dist/Compaction.d.ts +342 -0
  35. package/dist/Compaction.d.ts.map +1 -0
  36. package/dist/Compaction.js +566 -0
  37. package/dist/Compaction.js.map +1 -0
  38. package/dist/Compaction.test.d.ts +2 -0
  39. package/dist/Compaction.test.d.ts.map +1 -0
  40. package/dist/Compaction.test.js +576 -0
  41. package/dist/Compaction.test.js.map +1 -0
  42. package/dist/CompactionTransport.test.d.ts +2 -0
  43. package/dist/CompactionTransport.test.d.ts.map +1 -0
  44. package/dist/CompactionTransport.test.js +123 -0
  45. package/dist/CompactionTransport.test.js.map +1 -0
  46. package/dist/Copilot.d.ts.map +1 -1
  47. package/dist/Copilot.js +7 -2
  48. package/dist/Copilot.js.map +1 -1
  49. package/dist/OutputFormatter.d.ts.map +1 -1
  50. package/dist/OutputFormatter.js +9 -0
  51. package/dist/OutputFormatter.js.map +1 -1
  52. package/dist/cli.js +13 -4
  53. package/dist/cli.js.map +1 -1
  54. package/dist/index.d.ts +5 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +5 -0
  57. package/dist/index.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/Acp.test.ts +85 -0
  60. package/src/Acp.ts +2 -0
  61. package/src/Agent.test.ts +994 -0
  62. package/src/Agent.ts +129 -7
  63. package/src/AgentExecutor.ts +76 -32
  64. package/src/AgentOutput.ts +58 -0
  65. package/src/AgentSkills.test.ts +652 -0
  66. package/src/AgentSkills.ts +155 -0
  67. package/src/AgentTools.ts +34 -10
  68. package/src/Codex.ts +3 -1
  69. package/src/Compaction.test.ts +824 -0
  70. package/src/Compaction.ts +788 -0
  71. package/src/CompactionTransport.test.ts +228 -0
  72. package/src/Copilot.ts +8 -1
  73. package/src/OutputFormatter.ts +9 -0
  74. package/src/cli.ts +26 -5
  75. package/src/index.ts +6 -0
package/src/Agent.ts CHANGED
@@ -4,12 +4,14 @@
4
4
  import * as Model from "effect/unstable/ai/Model"
5
5
  import type * as Response from "effect/unstable/ai/Response"
6
6
  import * as AgentExecutor from "./AgentExecutor.ts"
7
+ import * as AgentSkills from "./AgentSkills.ts"
7
8
  import { stripWrappingCodeFence } from "./ScriptExtraction.ts"
8
9
  import type * as Path from "effect/Path"
9
10
  import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
10
11
  import type * as HttpClient from "effect/unstable/http/HttpClient"
11
12
  import type {
12
13
  CurrentDirectory,
14
+ DirectoryChanger,
13
15
  SubagentExecutor,
14
16
  TaskCompleter,
15
17
  } from "./AgentTools.ts"
@@ -36,6 +38,8 @@ import * as Duration from "effect/Duration"
36
38
  import * as Cause from "effect/Cause"
37
39
  import * as Latch from "effect/Latch"
38
40
  import * as Clock from "effect/Clock"
41
+ import * as Exit from "effect/Exit"
42
+ import * as Compaction from "./Compaction.ts"
39
43
  import {
40
44
  ScriptEnd,
41
45
  ScriptOutput,
@@ -52,6 +56,9 @@ import {
52
56
  ScriptStart,
53
57
  ScriptDelta,
54
58
  AgentStart,
59
+ ExecuteOutputCapped,
60
+ CompactionStarted,
61
+ CompactionEnded,
55
62
  } from "./AgentOutput.ts"
56
63
 
57
64
  /**
@@ -187,6 +194,9 @@ ${content}
187
194
  const conversationMode = yield* ConversationMode
188
195
  const turnTimeout = yield* TurnTimeout
189
196
  let finalSummary = Option.none<string>()
197
+ // `inputTokens.total` from the most recent finish part; undefined until
198
+ // the first response and after every compaction.
199
+ let lastContextTokens: number | undefined = undefined
190
200
 
191
201
  const output = yield* Queue.make<Output, AgentFinished | AiError.AiError>()
192
202
  let inputTokens = 0
@@ -319,6 +329,50 @@ ${content}
319
329
  }
320
330
  })
321
331
 
332
+ // Compaction. `compactWithEvents` applies a successful rewrite and closes
333
+ // the progress event on every exit: success, failure, timeout, interrupt.
334
+ let compactionStarted = false
335
+ const onCompactionStart = (reason: Compaction.CompactionReason) =>
336
+ Effect.sync(() => {
337
+ compactionStarted = true
338
+ maybeSend({ agentId, part: new CompactionStarted({ reason }) })
339
+ })
340
+ const compactWithEvents = <E, R>(
341
+ reason: Compaction.CompactionReason,
342
+ compaction: Effect.Effect<
343
+ Option.Option<Compaction.CompactionResult>,
344
+ E,
345
+ R
346
+ >,
347
+ ) =>
348
+ Effect.suspend(() => {
349
+ compactionStarted = false
350
+ return compaction
351
+ }).pipe(
352
+ Effect.onExit((exit) =>
353
+ Effect.sync(() => {
354
+ if (!compactionStarted) return
355
+ const result = Exit.isSuccess(exit) ? exit.value : Option.none()
356
+ let tokensBefore: number
357
+ let tokensAfter: number
358
+ if (Option.isSome(result)) {
359
+ MutableRef.set(prompt, result.value.prompt)
360
+ lastContextTokens = undefined
361
+ tokensBefore = result.value.tokensBefore
362
+ tokensAfter = result.value.tokensAfter
363
+ } else {
364
+ tokensBefore = tokensAfter = Compaction.estimateTokens(
365
+ prompt.current,
366
+ )
367
+ }
368
+ maybeSend({
369
+ agentId,
370
+ part: new CompactionEnded({ reason, tokensBefore, tokensAfter }),
371
+ })
372
+ }),
373
+ ),
374
+ )
375
+
322
376
  // Script execution
323
377
  const executeScript = Effect.fnUntraced(function* (script: string) {
324
378
  yield* toolLatchAcquire
@@ -335,8 +389,20 @@ ${content}
335
389
  }),
336
390
  Stream.mkString,
337
391
  )
392
+ // Always-on cap on what enters the Prompt. The ScriptOutput event keeps
393
+ // the full output for display; the formatter applies its own truncation.
394
+ const capped = Compaction.capOutput(output)
395
+ if (capped.capped) {
396
+ maybeSend({
397
+ agentId,
398
+ part: new ExecuteOutputCapped({
399
+ charsBefore: capped.charsBefore,
400
+ charsAfter: capped.charsAfter,
401
+ }),
402
+ })
403
+ }
338
404
  maybeSend({ agentId, part: new ScriptOutput({ output }) })
339
- return output
405
+ return capped.output
340
406
  }, Effect.ensuring(toolLatchRelease))
341
407
 
342
408
  if (!modelConfig.systemPromptTransform) {
@@ -369,12 +435,29 @@ ${content}
369
435
  pendingMessages.clear()
370
436
  }
371
437
 
438
+ // Threshold compaction before the next model call
439
+ yield* compactWithEvents(
440
+ "threshold",
441
+ Compaction.compactIfNeeded({
442
+ prompt: prompt.current,
443
+ contextTokens: lastContextTokens,
444
+ onStart: onCompactionStart,
445
+ }).pipe(Effect.timeout(turnTimeout)),
446
+ ).pipe(
447
+ Effect.catchTag("TimeoutError", (error) =>
448
+ Effect.logWarning(
449
+ "Compaction timed out, continuing with the uncompacted prompt",
450
+ error,
451
+ ),
452
+ ),
453
+ )
454
+
372
455
  // oxlint-disable-next-line typescript/no-explicit-any
373
456
  let response = Array.empty<Response.StreamPart<any>>()
374
457
  let reasoningStarted = false
375
458
  let hadReasoningDelta = false
376
459
  let hadToolCall = false
377
- yield* pipe(
460
+ const runModel = pipe(
378
461
  Stream.suspend(() =>
379
462
  ai.streamText({ prompt: prompt.current, toolkit: singleTool }),
380
463
  ),
@@ -431,6 +514,7 @@ ${content}
431
514
  outputTokens += usage.outputTokens.total
432
515
  }
433
516
  if (usage.inputTokens.total !== undefined) {
517
+ lastContextTokens = usage.inputTokens.total
434
518
  inputTokens += usage.inputTokens.total
435
519
  maybeSend({
436
520
  agentId,
@@ -449,6 +533,12 @@ ${content}
449
533
  }
450
534
  return Effect.void
451
535
  }),
536
+ modelConfig.systemPromptTransform
537
+ ? (effect) => modelConfig.systemPromptTransform!(system, effect)
538
+ : identity,
539
+ )
540
+ const attempt = pipe(
541
+ runModel,
452
542
  Effect.raceFirst(turnTimeoutEffect),
453
543
  Effect.retry({
454
544
  while: (err) => {
@@ -468,6 +558,9 @@ ${content}
468
558
  response = []
469
559
  return true
470
560
  }
561
+ // Never retry a context-length error as is: the overflow
562
+ // handler below compacts first.
563
+ if (Compaction.isContextLengthError(err)) return false
471
564
  if (err.isRetryable) {
472
565
  maybeSend({ agentId, part: new ErrorRetry({ error: err }) })
473
566
  switch (err.reason._tag) {
@@ -488,9 +581,33 @@ ${content}
488
581
  schedule: retryPolicy,
489
582
  }),
490
583
  Effect.catchTag("TimeoutError", Effect.die),
491
- modelConfig.systemPromptTransform
492
- ? (effect) => modelConfig.systemPromptTransform!(system, effect)
493
- : identity,
584
+ )
585
+ // Overflow: compact once, retry the attempt once, then fail.
586
+ yield* attempt.pipe(
587
+ Effect.catchIf(
588
+ (err): err is AiError.AiError =>
589
+ Compaction.isContextLengthError(err),
590
+ (err) =>
591
+ Effect.gen(function* () {
592
+ response = []
593
+ const compacted = yield* compactWithEvents(
594
+ "overflow",
595
+ Compaction.compact({
596
+ prompt: prompt.current,
597
+ reason: "overflow",
598
+ onStart: onCompactionStart,
599
+ }).pipe(
600
+ Effect.timeout(turnTimeout),
601
+ Effect.retry({
602
+ while: (error) => error._tag === "TimeoutError",
603
+ times: 1,
604
+ }),
605
+ Effect.catchTag("TimeoutError", Effect.die),
606
+ ),
607
+ )
608
+ return Option.isSome(compacted) ? yield* attempt : yield* err
609
+ }),
610
+ ),
494
611
  )
495
612
  MutableRef.update(
496
613
  prompt,
@@ -620,7 +737,12 @@ ${capabilities.toolsDts}
620
737
 
621
738
  /** The global Fetch API available for making HTTP requests. */
622
739
  declare const fetch: typeof globalThis.fetch
623
- \`\`\``
740
+ \`\`\`${Option.match(AgentSkills.renderCatalog(capabilities.skills), {
741
+ onNone: () => "",
742
+ onSome: (catalog) => `
743
+
744
+ ${catalog}`,
745
+ })}`
624
746
 
625
747
  class ScriptExecutor extends Context.Service<
626
748
  ScriptExecutor,
@@ -671,7 +793,7 @@ export const layerLocal = <Toolkit extends Toolkit.Any = never>(options: {
671
793
  Toolkit extends Toolkit.Toolkit<infer T>
672
794
  ? Tool.HandlersFor<T> | Tool.HandlerServices<T[keyof T]>
673
795
  : never,
674
- CurrentDirectory | SubagentExecutor | TaskCompleter
796
+ CurrentDirectory | DirectoryChanger | SubagentExecutor | TaskCompleter
675
797
  >
676
798
  > => layer.pipe(Layer.provide(AgentExecutor.layerLocal(options)))
677
799
 
@@ -2,6 +2,7 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
  import * as NodeConsole from "node:console"
5
+ import * as NodeOs from "node:os"
5
6
  import * as NodeVm from "node:vm"
6
7
  import { Writable } from "node:stream"
7
8
  import {
@@ -9,10 +10,12 @@ import {
9
10
  AgentTools,
10
11
  AgentToolsWithSearch,
11
12
  CurrentDirectory,
13
+ DirectoryChanger,
12
14
  SubagentExecutor,
13
15
  TaskCompleter,
14
16
  } from "./AgentTools.ts"
15
17
  import { ToolkitRenderer } from "./ToolkitRenderer.ts"
18
+ import * as AgentSkills from "./AgentSkills.ts"
16
19
  import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
17
20
  import type * as HttpClient from "effect/unstable/http/HttpClient"
18
21
  import * as Context from "effect/Context"
@@ -69,6 +72,13 @@ export class Capabilities extends Schema.Class<Capabilities>("Capabilities")({
69
72
  toolsDts: Schema.String,
70
73
  agentsMd: Schema.Option(Schema.String),
71
74
  supportsSearch: Schema.Boolean,
75
+ /**
76
+ * Skills available on the executor filesystem.
77
+ */
78
+ skills: Schema.Array(AgentSkills.Skill).pipe(
79
+ Schema.withDecodingDefaultKey(Effect.succeed([])),
80
+ Schema.withConstructorDefault(Effect.succeed([])),
81
+ ),
72
82
  }) {}
73
83
 
74
84
  /**
@@ -91,12 +101,22 @@ export const makeLocal = Effect.fnUntraced(function* <
91
101
  Toolkit extends Toolkit.Toolkit<infer T>
92
102
  ? Tool.HandlersFor<T> | Tool.HandlerServices<T[keyof T]>
93
103
  : never,
94
- CurrentDirectory | SubagentExecutor | TaskCompleter
104
+ CurrentDirectory | DirectoryChanger | SubagentExecutor | TaskCompleter
95
105
  >
96
106
  > {
97
107
  const fs = yield* FileSystem.FileSystem
98
108
  const pathService = yield* Path.Path
99
109
  const renderer = yield* ToolkitRenderer
110
+ let currentDirectory = pathService.resolve(options.directory)
111
+ const changeDirectory = Effect.fnUntraced(function* (directory: string) {
112
+ const target = pathService.resolve(currentDirectory, directory)
113
+ const info = yield* fs.stat(target)
114
+ if (info.type !== "Directory") {
115
+ return yield* Effect.die(new Error(`Not a directory: ${target}`))
116
+ }
117
+ currentDirectory = target
118
+ return target
119
+ }, Effect.orDie)
100
120
  const search = yield* Effect.serviceOption(SemanticSearch)
101
121
  const hasSearch = Option.isSome(search)
102
122
  const AllTools = Toolkit.merge(
@@ -133,7 +153,7 @@ export const makeLocal = Effect.fnUntraced(function* <
133
153
 
134
154
  const taskServices = Context.empty().pipe(
135
155
  Context.add(TaskCompleter, opts.onTaskComplete),
136
- Context.add(CurrentDirectory, options.directory),
156
+ Context.add(DirectoryChanger, changeDirectory),
137
157
  Context.add(SubagentExecutor, opts.onSubagent),
138
158
  Context.add(Console.Console, console),
139
159
  Context.add(References.CurrentLogAnnotations, {}),
@@ -156,12 +176,13 @@ export const makeLocal = Effect.fnUntraced(function* <
156
176
 
157
177
  for (let i = 0; i < toolEntries.length; i++) {
158
178
  const { name, handler, services } = toolEntries[i]!
159
- const runFork = Effect.runForkWith(
160
- Context.merge(services, taskServices),
161
- )
179
+ const toolServices = Context.merge(services, taskServices)
162
180
 
163
181
  // oxlint-disable-next-line typescript/no-explicit-any
164
182
  sandbox[name] = function (params: any) {
183
+ const runFork = Effect.runForkWith(
184
+ Context.add(toolServices, CurrentDirectory, currentDirectory),
185
+ )
165
186
  running++
166
187
  const fiber = trackFiber(runFork(handler(params, {})))
167
188
  return new Promise((resolve, reject) => {
@@ -201,40 +222,53 @@ export const makeLocal = Effect.fnUntraced(function* <
201
222
  const agentsMd = yield* Effect.option(
202
223
  fs.readFileString(pathService.join(options.directory, "AGENTS.md")),
203
224
  )
225
+ const skills = yield* AgentSkills.discover({
226
+ directory: options.directory,
227
+ homeDirectory: homeDirectory(),
228
+ }).pipe(
229
+ Effect.provideService(FileSystem.FileSystem, fs),
230
+ Effect.provideService(Path.Path, pathService),
231
+ )
204
232
  return new Capabilities({
205
233
  toolsDts,
206
234
  agentsMd,
207
235
  supportsSearch: hasSearch,
236
+ skills,
208
237
  })
209
238
  }),
210
239
  execute,
211
- executeUnsafe: (opts) => {
212
- const tool = tools.tools[opts.tool as keyof typeof tools.tools]
213
- const handler = services.mapUnsafe.get(tool.id) as Tool.Handler<string>
214
- if (!handler || !tool) {
215
- return Effect.die(new Error(`Unknown tool: ${opts.tool}`))
216
- }
240
+ executeUnsafe: (opts) =>
241
+ Effect.suspend(() => {
242
+ const tool = tools.tools[opts.tool as keyof typeof tools.tools]
243
+ if (!tool) {
244
+ return Effect.die(new Error(`Unknown tool: ${opts.tool}`))
245
+ }
246
+ const handler = services.mapUnsafe.get(tool.id) as Tool.Handler<string>
247
+ if (!handler) {
248
+ return Effect.die(new Error(`Unknown tool: ${opts.tool}`))
249
+ }
217
250
 
218
- const taskServices = handler.context.pipe(
219
- Context.add(TaskCompleter, () => Effect.void),
220
- Context.add(CurrentDirectory, options.directory),
221
- Context.add(SubagentExecutor, () => Effect.succeed("")),
222
- Context.add(References.CurrentLogAnnotations, {}),
223
- Option.isSome(search)
224
- ? Context.add(SemanticSearch, search.value)
225
- : identity,
226
- )
251
+ const taskServices = handler.context.pipe(
252
+ Context.add(TaskCompleter, () => Effect.void),
253
+ Context.add(CurrentDirectory, currentDirectory),
254
+ Context.add(DirectoryChanger, changeDirectory),
255
+ Context.add(SubagentExecutor, () => Effect.succeed("")),
256
+ Context.add(References.CurrentLogAnnotations, {}),
257
+ Option.isSome(search)
258
+ ? Context.add(SemanticSearch, search.value)
259
+ : identity,
260
+ )
227
261
 
228
- const encodeSuccess = Schema.encodeUnknownEffect(
229
- Schema.toCodecJson(tool.successSchema as Schema.Top),
230
- )
231
- return pipe(
232
- handler.handler(opts.params, {}),
233
- Effect.flatMap(encodeSuccess),
234
- Effect.provideContext(taskServices),
235
- Effect.orDie,
236
- ) as Effect.Effect<Schema.Json>
237
- },
262
+ const encodeSuccess = Schema.encodeUnknownEffect(
263
+ Schema.toCodecJson(tool.successSchema as Schema.Top),
264
+ )
265
+ return pipe(
266
+ handler.handler(opts.params, {}),
267
+ Effect.flatMap(encodeSuccess),
268
+ Effect.provideContext(taskServices),
269
+ Effect.orDie,
270
+ ) as Effect.Effect<Schema.Json>
271
+ }),
238
272
  })
239
273
  })
240
274
 
@@ -302,7 +336,7 @@ export const layerLocal = <Toolkit extends Toolkit.Any = never>(options: {
302
336
  Toolkit extends Toolkit.Toolkit<infer T>
303
337
  ? Tool.HandlersFor<T> | Tool.HandlerServices<T[keyof T]>
304
338
  : never,
305
- CurrentDirectory | SubagentExecutor | TaskCompleter
339
+ CurrentDirectory | DirectoryChanger | SubagentExecutor | TaskCompleter
306
340
  >
307
341
  > =>
308
342
  Layer.effect(AgentExecutor, makeLocal(options)).pipe(
@@ -342,7 +376,7 @@ export const layerRpcServer = <Toolkit extends Toolkit.Any = never>(options: {
342
376
  Toolkit extends Toolkit.Toolkit<infer T>
343
377
  ? Tool.HandlersFor<T> | Tool.HandlerServices<T[keyof T]>
344
378
  : never,
345
- CurrentDirectory | SubagentExecutor | TaskCompleter
379
+ CurrentDirectory | DirectoryChanger | SubagentExecutor | TaskCompleter
346
380
  >
347
381
  > =>
348
382
  RpcServer.layer(Rpcs, {
@@ -500,6 +534,16 @@ const compileScript = (script: string): NodeVm.Script => {
500
534
 
501
535
  const defaultMain = () => Promise.resolve()
502
536
 
537
+ const homeDirectory = (): Option.Option<string> => {
538
+ const fromEnv = process.env.HOME
539
+ if (fromEnv) return Option.some(fromEnv)
540
+ try {
541
+ return Option.some(NodeOs.homedir())
542
+ } catch {
543
+ return Option.none()
544
+ }
545
+ }
546
+
503
547
  const makeConsole = Effect.fn(function* (
504
548
  queue: Queue.Queue<string, Cause.Done>,
505
549
  ) {
@@ -138,6 +138,58 @@ export class SubagentComplete extends Schema.TaggedClass<SubagentComplete>()(
138
138
  },
139
139
  ) {}
140
140
 
141
+ /**
142
+ * Emitted when an `execute` result exceeded the always-on output cap and was
143
+ * shortened before being appended to the Prompt.
144
+ *
145
+ * @since 1.0.0
146
+ * @category Output
147
+ */
148
+ export class ExecuteOutputCapped extends Schema.TaggedClass<ExecuteOutputCapped>()(
149
+ "ExecuteOutputCapped",
150
+ {
151
+ charsBefore: Schema.Number,
152
+ charsAfter: Schema.Number,
153
+ },
154
+ ) {}
155
+
156
+ /**
157
+ * @since 1.0.0
158
+ * @category Output
159
+ */
160
+ export const CompactionReason = Schema.Literals(["threshold", "overflow"])
161
+
162
+ /**
163
+ * Emitted when auto-compaction begins. `reason` is `threshold` when the
164
+ * context token count crossed the configured limit before a model call, and
165
+ * `overflow` when a context-length error from the provider triggered it.
166
+ *
167
+ * @since 1.0.0
168
+ * @category Output
169
+ */
170
+ export class CompactionStarted extends Schema.TaggedClass<CompactionStarted>()(
171
+ "CompactionStarted",
172
+ {
173
+ reason: CompactionReason,
174
+ },
175
+ ) {}
176
+
177
+ /**
178
+ * Emitted when auto-compaction finished and the Prompt was rewritten.
179
+ * `tokensBefore` / `tokensAfter` are estimates.
180
+ *
181
+ * @since 1.0.0
182
+ * @category Output
183
+ */
184
+ export class CompactionEnded extends Schema.TaggedClass<CompactionEnded>()(
185
+ "CompactionEnded",
186
+ {
187
+ reason: CompactionReason,
188
+ tokensBefore: Schema.Number,
189
+ tokensAfter: Schema.Number,
190
+ },
191
+ ) {}
192
+
141
193
  export type ContentPart =
142
194
  | ReasoningStart
143
195
  | ReasoningDelta
@@ -148,6 +200,9 @@ export type ContentPart =
148
200
  | ScriptOutput
149
201
  | Usage
150
202
  | ErrorRetry
203
+ | ExecuteOutputCapped
204
+ | CompactionStarted
205
+ | CompactionEnded
151
206
 
152
207
  export const ContentPart = Schema.Union([
153
208
  ReasoningStart,
@@ -159,6 +214,9 @@ export const ContentPart = Schema.Union([
159
214
  ScriptOutput,
160
215
  Usage,
161
216
  ErrorRetry,
217
+ ExecuteOutputCapped,
218
+ CompactionStarted,
219
+ CompactionEnded,
162
220
  ])
163
221
 
164
222
  /**