@lite-agent/core 0.1.0 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -250,6 +250,7 @@ interface CreateAgentConfig {
250
250
  maxTokens?: number;
251
251
  sandbox?: Sandbox;
252
252
  input?: InputHandler;
253
+ store?: Store;
253
254
  }
254
255
  type RunOptions = {
255
256
  signal?: AbortSignal;
@@ -275,6 +276,108 @@ declare function fakeProvider(turns: FakeTurn[]): ModelProvider;
275
276
 
276
277
  declare function noopSandbox(): Sandbox;
277
278
 
279
+ declare function memoryStore(): Store;
280
+
281
+ interface RetryOptions {
282
+ /** Retries after the first attempt. Default 2 (→ up to 3 total attempts). */
283
+ maxRetries?: number;
284
+ /** ms to wait before retry N (1-based). Default exponential 250ms→8s. */
285
+ backoff?: (attempt: number) => number;
286
+ /** Whether an error is retryable. Default: transient ProviderError. */
287
+ retryOn?: (err: unknown) => boolean;
288
+ }
289
+ declare function retry(opts?: RetryOptions): Middleware;
290
+
291
+ declare const SPILL_PREFIX = "[spilled:";
292
+ interface CompactPass {
293
+ readonly name: string;
294
+ apply(messages: Message[]): Message[];
295
+ }
296
+ declare function runPipeline(passes: CompactPass[], messages: Message[]): Message[];
297
+ declare function estimateTokens(messages: Message[]): number;
298
+
299
+ interface MicroPassOptions {
300
+ /** How many of the most recent tool_results keep their full body. Default 3. */
301
+ keepRecent?: number;
302
+ placeholder?: string;
303
+ }
304
+ declare function microPass(opts?: MicroPassOptions): CompactPass;
305
+
306
+ interface SnipPassOptions {
307
+ /** Only snip when the transcript exceeds this many messages. Default 50. */
308
+ maxMessages?: number;
309
+ /** How many leading turns to always keep. Default 1. */
310
+ headTurns?: number;
311
+ /** Keep trailing turns until at least this many messages are retained. Default 20. */
312
+ tailKeep?: number;
313
+ }
314
+ declare function splitTurns(messages: Message[]): Message[][];
315
+ declare function snipPass(opts?: SnipPassOptions): CompactPass;
316
+
317
+ interface SpillStore {
318
+ put(content: string): string;
319
+ get(ref: string): string | null;
320
+ }
321
+ declare function memorySpillStore(): SpillStore;
322
+ interface ToolResultBudgetOptions {
323
+ store: SpillStore;
324
+ /** Spill the largest tool_results until total body bytes ≤ this. Default 200_000. */
325
+ budgetBytes?: number;
326
+ }
327
+ declare function toolResultBudgetPass(opts: ToolResultBudgetOptions): CompactPass;
328
+
329
+ interface DefaultCompactorOptions {
330
+ /** snip: only snip beyond this many messages. Default 50. */
331
+ maxMessages?: number;
332
+ /** snip: leading turns always kept. Default 1. */
333
+ headTurns?: number;
334
+ /** snip: keep trailing turns until this many messages retained. Default 20. */
335
+ tailKeep?: number;
336
+ /** micro: how many recent tool_results keep full bodies. Default 3. */
337
+ keepRecentToolResults?: number;
338
+ /** L3: when set, spill oversized tool_results to this store (runs first). */
339
+ spillStore?: SpillStore;
340
+ /** L3: spill largest tool_results until total body bytes ≤ this. Default 200_000. */
341
+ budgetBytes?: number;
342
+ /** Replace the whole pass pipeline (hot-swap). Defaults to [spill?] → snip → micro. */
343
+ passes?: CompactPass[];
344
+ }
345
+ declare function defaultCompactor(opts?: DefaultCompactorOptions): Compactor;
346
+
347
+ declare function compaction(compactor: Compactor): Middleware;
348
+
349
+ interface ReactiveTrimOptions {
350
+ /** Max recent turns to keep. Default 2. */
351
+ keepTurns?: number;
352
+ /** Approx token cap on the kept tail. Default 4000. */
353
+ tokenBudget?: number;
354
+ }
355
+ declare function reactiveTrim(messages: Message[], opts?: ReactiveTrimOptions): Message[];
356
+ interface ReactiveCompactionOptions {
357
+ /** How to trim on overflow. Default reactiveTrim with its defaults. */
358
+ trim?: (messages: Message[]) => Message[];
359
+ /** Classify an error as a context-overflow. Default: ProviderError 413 / prompt_too_long. */
360
+ isOverflow?: (err: unknown) => boolean;
361
+ /** Reactive retries after the first failure. Default 1. */
362
+ maxAttempts?: number;
363
+ }
364
+ declare function reactiveCompaction(opts?: ReactiveCompactionOptions): Middleware;
365
+
366
+ interface LlmCompactorOptions {
367
+ provider: ModelProvider;
368
+ model: string;
369
+ /** Deterministic compactor run first. Default defaultCompactor(). */
370
+ base?: Compactor;
371
+ /** Summarize via the LLM once the estimate exceeds this. Default 120_000. */
372
+ tokenThreshold?: number;
373
+ /** Recent turns kept verbatim; older ones get summarized. Default 3. */
374
+ keepRecentTurns?: number;
375
+ summaryPrompt?: string;
376
+ /** Consecutive LLM failures before the circuit opens (falls back to base). Default 2. */
377
+ maxFailures?: number;
378
+ }
379
+ declare function llmCompactor(opts: LlmCompactorOptions): Compactor;
380
+
278
381
  interface PolicyOptions {
279
382
  allow?: string[];
280
383
  ask?: string[];
@@ -284,4 +387,4 @@ interface PolicyOptions {
284
387
  declare function policy(opts?: PolicyOptions): PermissionPolicy;
285
388
  declare function permission(pol: PermissionPolicy, approval?: ApprovalHandler): Middleware;
286
389
 
287
- export { AbortError, type Agent, type AgentContext, AgentError, type AgentEvent, type ApprovalHandler, type AssistantMessage, CodecError, type CompactResult, type Compactor, type ContentBlock, type CreateAgentConfig, type Decision, type FakeTurn, type InputHandler, MaxTurnsError, type Message, type Middleware, type ModelCall, type ModelChunk, type ModelProvider, type ModelRequest, type PermissionPolicy, type PolicyContext, type PolicyOptions, ProviderError, type Role, type RunOptions, type RunResult, type Sandbox, type SandboxWrapOptions, type StopReason, type Store, type TextBlock, type Tool, type ToolCall, type ToolCallBlock, type ToolCallCodec, type ToolCallContext, type ToolContext, ToolError, type ToolExec, type ToolResult, type ToolResultBlock, type ToolSpec, type Usage, type UserAnswer, type UserQuestion, composeModelCall, composeToolCall, createAgent, defineTool, fakeProvider, isTextBlock, isToolCallBlock, nativeCodec, noopSandbox, permission, policy, runLifecycle, textBlock, toToolSpec, toolResultBlock };
390
+ export { AbortError, type Agent, type AgentContext, AgentError, type AgentEvent, type ApprovalHandler, type AssistantMessage, CodecError, type CompactPass, type CompactResult, type Compactor, type ContentBlock, type CreateAgentConfig, type Decision, type DefaultCompactorOptions, type FakeTurn, type InputHandler, type LlmCompactorOptions, MaxTurnsError, type Message, type MicroPassOptions, type Middleware, type ModelCall, type ModelChunk, type ModelProvider, type ModelRequest, type PermissionPolicy, type PolicyContext, type PolicyOptions, ProviderError, type ReactiveCompactionOptions, type ReactiveTrimOptions, type RetryOptions, type Role, type RunOptions, type RunResult, SPILL_PREFIX, type Sandbox, type SandboxWrapOptions, type SnipPassOptions, type SpillStore, type StopReason, type Store, type TextBlock, type Tool, type ToolCall, type ToolCallBlock, type ToolCallCodec, type ToolCallContext, type ToolContext, ToolError, type ToolExec, type ToolResult, type ToolResultBlock, type ToolResultBudgetOptions, type ToolSpec, type Usage, type UserAnswer, type UserQuestion, compaction, composeModelCall, composeToolCall, createAgent, defaultCompactor, defineTool, estimateTokens, fakeProvider, isTextBlock, isToolCallBlock, llmCompactor, memorySpillStore, memoryStore, microPass, nativeCodec, noopSandbox, permission, policy, reactiveCompaction, reactiveTrim, retry, runLifecycle, runPipeline, snipPass, splitTurns, textBlock, toToolSpec, toolResultBlock, toolResultBudgetPass };
package/dist/index.js CHANGED
@@ -74,6 +74,13 @@ function toToolSpec(tool) {
74
74
  // src/kernel.ts
75
75
  async function* runKernel(cfg, input, signal, sessionId) {
76
76
  let messages = typeof input === "string" ? [{ role: "user", content: input }] : [...input];
77
+ if (cfg.store) {
78
+ const saved = await cfg.store.load(sessionId);
79
+ if (saved) messages = [...saved, ...messages];
80
+ }
81
+ const persist = async () => {
82
+ if (cfg.store) await cfg.store.save(sessionId, messages);
83
+ };
77
84
  const queue = [];
78
85
  const emit = (ev) => {
79
86
  queue.push(ev);
@@ -99,11 +106,17 @@ async function* runKernel(cfg, input, signal, sessionId) {
99
106
  await runLifecycle(cfg.middleware, "beforeModel", ctx);
100
107
  yield* drain();
101
108
  messages = ctx.messages;
102
- const req = cfg.codec.encode(
103
- { model: cfg.model, system: cfg.system, messages: ctx.messages, maxTokens: cfg.maxTokens },
104
- toolSpecs
109
+ const modelCall = composeModelCall(
110
+ cfg.middleware,
111
+ ctx,
112
+ () => cfg.provider.stream(
113
+ cfg.codec.encode(
114
+ { model: cfg.model, system: cfg.system, messages: ctx.messages, maxTokens: cfg.maxTokens },
115
+ toolSpecs
116
+ ),
117
+ signal
118
+ )
105
119
  );
106
- const modelCall = composeModelCall(cfg.middleware, ctx, () => cfg.provider.stream(req, signal));
107
120
  let assistant;
108
121
  for await (const chunk of modelCall()) {
109
122
  if (chunk.type === "text_delta") yield { type: "text_delta", text: chunk.text };
@@ -115,6 +128,7 @@ async function* runKernel(cfg, input, signal, sessionId) {
115
128
  };
116
129
  }
117
130
  }
131
+ messages = ctx.messages;
118
132
  yield* drain();
119
133
  if (!assistant) throw new ProviderError("provider produced no message_done chunk");
120
134
  ctx.messages.push(assistant);
@@ -146,10 +160,12 @@ async function* runKernel(cfg, input, signal, sessionId) {
146
160
  yield { type: "tool_result", result: result2 };
147
161
  }
148
162
  ctx.messages.push({ role: "user", content: resultBlocks });
163
+ await persist();
149
164
  yield { type: "turn_end", turn, stopReason: "tool_use" };
150
165
  }
151
166
  await runLifecycle(cfg.middleware, "afterAgent", mkCtx(0));
152
167
  yield* drain();
168
+ await persist();
153
169
  const result = { messages, text: lastAssistantText(messages), usage, stopReason };
154
170
  yield { type: "done", reason: stopReason, result };
155
171
  return result;
@@ -177,7 +193,8 @@ function createAgent(cfg) {
177
193
  maxTurns: cfg.maxTurns ?? 50,
178
194
  maxTokens: cfg.maxTokens,
179
195
  sandbox: cfg.sandbox ?? noopSandbox(),
180
- input: cfg.input
196
+ input: cfg.input,
197
+ store: cfg.store
181
198
  };
182
199
  const agent = {
183
200
  run(input, opts) {
@@ -232,6 +249,343 @@ function fakeProvider(turns) {
232
249
  };
233
250
  }
234
251
 
252
+ // src/store.ts
253
+ function memoryStore() {
254
+ const sessions = /* @__PURE__ */ new Map();
255
+ return {
256
+ async load(id) {
257
+ return sessions.get(id) ?? null;
258
+ },
259
+ async save(id, messages) {
260
+ sessions.set(id, structuredClone(messages));
261
+ }
262
+ };
263
+ }
264
+
265
+ // src/retry.ts
266
+ var TRANSIENT = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
267
+ var defaultRetryOn = (err) => err instanceof ProviderError && (err.status === void 0 || TRANSIENT.has(err.status));
268
+ var defaultBackoff = (attempt) => Math.min(250 * 2 ** (attempt - 1), 8e3);
269
+ function retry(opts = {}) {
270
+ const maxRetries = opts.maxRetries ?? 2;
271
+ const backoff = opts.backoff ?? defaultBackoff;
272
+ const retryOn = opts.retryOn ?? defaultRetryOn;
273
+ return {
274
+ name: "retry",
275
+ async *wrapModelCall(ctx, next) {
276
+ let attempt = 0;
277
+ while (true) {
278
+ let started = false;
279
+ try {
280
+ for await (const chunk of next()) {
281
+ started = true;
282
+ yield chunk;
283
+ }
284
+ return;
285
+ } catch (err) {
286
+ if (started || attempt >= maxRetries || !retryOn(err)) throw err;
287
+ attempt++;
288
+ const ms = backoff(attempt);
289
+ if (ms > 0) await new Promise((r) => setTimeout(r, ms));
290
+ }
291
+ }
292
+ }
293
+ };
294
+ }
295
+
296
+ // src/compaction/types.ts
297
+ var SPILL_PREFIX = "[spilled:";
298
+ function runPipeline(passes, messages) {
299
+ return passes.reduce((msgs, pass) => pass.apply(msgs), messages);
300
+ }
301
+ function estimateTokens(messages) {
302
+ let chars = 0;
303
+ for (const m of messages) {
304
+ if (typeof m.content === "string") {
305
+ chars += m.content.length;
306
+ continue;
307
+ }
308
+ for (const b of m.content) {
309
+ if (b.type === "text") chars += b.text.length;
310
+ else if (b.type === "tool_result") chars += b.content.length;
311
+ else if (b.type === "tool_call") chars += JSON.stringify(b.input).length;
312
+ }
313
+ }
314
+ return Math.ceil(chars / 4);
315
+ }
316
+
317
+ // src/compaction/micro.ts
318
+ function microPass(opts = {}) {
319
+ const keepRecent = opts.keepRecent ?? 3;
320
+ const placeholder = opts.placeholder ?? "[tool result omitted to save context]";
321
+ return {
322
+ name: "micro",
323
+ apply(messages) {
324
+ const positions = [];
325
+ messages.forEach((m, mi) => {
326
+ if (Array.isArray(m.content)) {
327
+ m.content.forEach((b, bi) => {
328
+ if (b.type === "tool_result") positions.push([mi, bi]);
329
+ });
330
+ }
331
+ });
332
+ if (positions.length <= keepRecent) return messages;
333
+ const omit = new Set(positions.slice(0, positions.length - keepRecent).map(([mi, bi]) => `${mi}:${bi}`));
334
+ let anyChanged = false;
335
+ const out = messages.map((m, mi) => {
336
+ if (!Array.isArray(m.content)) return m;
337
+ let changed = false;
338
+ const content = m.content.map((b, bi) => {
339
+ if (b.type === "tool_result" && omit.has(`${mi}:${bi}`) && b.content !== placeholder && !b.content.startsWith(SPILL_PREFIX)) {
340
+ changed = true;
341
+ return { ...b, content: placeholder };
342
+ }
343
+ return b;
344
+ });
345
+ if (!changed) return m;
346
+ anyChanged = true;
347
+ return { ...m, content };
348
+ });
349
+ return anyChanged ? out : messages;
350
+ }
351
+ };
352
+ }
353
+
354
+ // src/compaction/snip.ts
355
+ function splitTurns(messages) {
356
+ const turns = [];
357
+ let cur = [];
358
+ for (const m of messages) {
359
+ if (m.role === "user" && typeof m.content === "string" && cur.length > 0) {
360
+ turns.push(cur);
361
+ cur = [];
362
+ }
363
+ cur.push(m);
364
+ }
365
+ if (cur.length) turns.push(cur);
366
+ return turns;
367
+ }
368
+ function snipPass(opts = {}) {
369
+ const maxMessages = opts.maxMessages ?? 50;
370
+ const headTurns = opts.headTurns ?? 1;
371
+ const tailKeep = opts.tailKeep ?? 20;
372
+ return {
373
+ name: "snip",
374
+ apply(messages) {
375
+ if (messages.length <= maxMessages) return messages;
376
+ const turns = splitTurns(messages);
377
+ let tailTurnCount = 0;
378
+ let tailMsgCount = 0;
379
+ for (let i = turns.length - 1; i >= 0 && tailMsgCount < tailKeep; i--) {
380
+ tailMsgCount += turns[i].length;
381
+ tailTurnCount++;
382
+ }
383
+ if (headTurns + tailTurnCount >= turns.length) return messages;
384
+ const head = turns.slice(0, headTurns).flat();
385
+ const tail = turns.slice(turns.length - tailTurnCount).flat();
386
+ const omitted = turns.length - headTurns - tailTurnCount;
387
+ const placeholder = { role: "user", content: `[${omitted} earlier turn(s) omitted to save context]` };
388
+ return [...head, placeholder, ...tail];
389
+ }
390
+ };
391
+ }
392
+
393
+ // src/compaction/budget.ts
394
+ function memorySpillStore() {
395
+ const blobs = /* @__PURE__ */ new Map();
396
+ let n = 0;
397
+ return {
398
+ put(content) {
399
+ const ref = `m${++n}`;
400
+ blobs.set(ref, content);
401
+ return ref;
402
+ },
403
+ get(ref) {
404
+ return blobs.get(ref) ?? null;
405
+ }
406
+ };
407
+ }
408
+ var isSpilled = (s) => s.startsWith(SPILL_PREFIX);
409
+ var marker = (ref, bytes) => `${SPILL_PREFIX}${ref}] ${bytes} bytes moved off-context \u2014 call read_spilled({ ref: "${ref}" }) to view the full content.`;
410
+ function toolResultBudgetPass(opts) {
411
+ const budget = opts.budgetBytes ?? 2e5;
412
+ return {
413
+ name: "toolResultBudget",
414
+ apply(messages) {
415
+ const results = [];
416
+ let total = 0;
417
+ messages.forEach((m, mi) => {
418
+ if (Array.isArray(m.content)) {
419
+ m.content.forEach((b, bi) => {
420
+ if (b.type === "tool_result" && !isSpilled(b.content)) {
421
+ results.push({ mi, bi, bytes: b.content.length });
422
+ total += b.content.length;
423
+ }
424
+ });
425
+ }
426
+ });
427
+ if (total <= budget) return messages;
428
+ results.sort((a, b) => b.bytes - a.bytes);
429
+ const spill = /* @__PURE__ */ new Set();
430
+ for (const r of results) {
431
+ if (total <= budget) break;
432
+ spill.add(`${r.mi}:${r.bi}`);
433
+ total -= r.bytes;
434
+ }
435
+ if (spill.size === 0) return messages;
436
+ return messages.map((m, mi) => {
437
+ if (!Array.isArray(m.content)) return m;
438
+ let changed = false;
439
+ const content = m.content.map((b, bi) => {
440
+ if (b.type === "tool_result" && spill.has(`${mi}:${bi}`)) {
441
+ changed = true;
442
+ const ref = opts.store.put(b.content);
443
+ return { ...b, content: marker(ref, b.content.length) };
444
+ }
445
+ return b;
446
+ });
447
+ return changed ? { ...m, content } : m;
448
+ });
449
+ }
450
+ };
451
+ }
452
+
453
+ // src/compaction/defaultCompactor.ts
454
+ function defaultCompactor(opts = {}) {
455
+ const passes = opts.passes ?? [
456
+ ...opts.spillStore ? [toolResultBudgetPass({ store: opts.spillStore, budgetBytes: opts.budgetBytes })] : [],
457
+ snipPass({ maxMessages: opts.maxMessages, headTurns: opts.headTurns, tailKeep: opts.tailKeep }),
458
+ microPass({ keepRecent: opts.keepRecentToolResults })
459
+ ];
460
+ return {
461
+ async maybeCompact(messages) {
462
+ const before = estimateTokens(messages);
463
+ const out = runPipeline(passes, messages);
464
+ if (out === messages) return { messages, before, after: before };
465
+ return { messages: out, kind: "micro", before, after: estimateTokens(out) };
466
+ }
467
+ };
468
+ }
469
+
470
+ // src/compaction/middleware.ts
471
+ var ZERO_USAGE = { inputTokens: 0, outputTokens: 0 };
472
+ function compaction(compactor) {
473
+ return {
474
+ name: "compaction",
475
+ async beforeModel(ctx) {
476
+ const r = await compactor.maybeCompact(ctx.messages, ZERO_USAGE);
477
+ if (r.messages !== ctx.messages) {
478
+ ctx.emit({ type: "compaction", kind: r.kind ?? "micro", before: r.before ?? 0, after: r.after ?? 0 });
479
+ ctx.messages = r.messages;
480
+ }
481
+ }
482
+ };
483
+ }
484
+
485
+ // src/compaction/reactive.ts
486
+ function reactiveTrim(messages, opts = {}) {
487
+ const keepTurns = opts.keepTurns ?? 2;
488
+ const budget = opts.tokenBudget ?? 4e3;
489
+ const turns = splitTurns(messages);
490
+ const kept = [];
491
+ let toks = 0;
492
+ for (let i = turns.length - 1; i >= 0; i--) {
493
+ const t = turns[i];
494
+ if (kept.length >= 1 && (kept.length >= keepTurns || toks + estimateTokens(t) > budget)) break;
495
+ kept.unshift(t);
496
+ toks += estimateTokens(t);
497
+ }
498
+ const dropped = turns.length - kept.length;
499
+ if (dropped <= 0) return messages;
500
+ const placeholder = { role: "user", content: `[${dropped} earlier turn(s) dropped \u2014 context overflow]` };
501
+ return [placeholder, ...kept.flat()];
502
+ }
503
+ function defaultIsOverflow(err) {
504
+ if (!(err instanceof ProviderError)) return false;
505
+ if (err.status === 413) return true;
506
+ return /prompt[\s_]?too[\s_]?long|context[\s_]?length|too many tokens|maximum context/i.test(err.message);
507
+ }
508
+ function reactiveCompaction(opts = {}) {
509
+ const trim = opts.trim ?? ((m) => reactiveTrim(m));
510
+ const isOverflow = opts.isOverflow ?? defaultIsOverflow;
511
+ const maxAttempts = opts.maxAttempts ?? 1;
512
+ return {
513
+ name: "reactive-compaction",
514
+ async *wrapModelCall(ctx, next) {
515
+ let attempts = 0;
516
+ while (true) {
517
+ let started = false;
518
+ try {
519
+ for await (const chunk of next()) {
520
+ started = true;
521
+ yield chunk;
522
+ }
523
+ return;
524
+ } catch (err) {
525
+ if (started || !isOverflow(err) || attempts >= maxAttempts) throw err;
526
+ attempts++;
527
+ const before = estimateTokens(ctx.messages);
528
+ ctx.messages = trim(ctx.messages);
529
+ ctx.emit({ type: "compaction", kind: "auto", before, after: estimateTokens(ctx.messages) });
530
+ }
531
+ }
532
+ }
533
+ };
534
+ }
535
+
536
+ // src/compaction/llm.ts
537
+ var DEFAULT_SUMMARY_PROMPT = "You are a context-compaction assistant. Summarize the conversation so far into a concise note that preserves key facts, decisions, file paths, and the current task state. Output only the summary.";
538
+ function llmCompactor(opts) {
539
+ const base = opts.base ?? defaultCompactor();
540
+ const threshold = opts.tokenThreshold ?? 12e4;
541
+ const keepRecentTurns = opts.keepRecentTurns ?? 3;
542
+ const summaryPrompt = opts.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
543
+ const maxFailures = opts.maxFailures ?? 2;
544
+ let failures = 0;
545
+ let circuitOpen = false;
546
+ async function summarize(older) {
547
+ const req = {
548
+ model: opts.model,
549
+ system: summaryPrompt,
550
+ messages: [...older, { role: "user", content: "Summarize the conversation above as instructed." }]
551
+ };
552
+ let assistant;
553
+ for await (const chunk of opts.provider.stream(req)) {
554
+ if (chunk.type === "message_done") assistant = chunk.message;
555
+ }
556
+ if (!assistant || !Array.isArray(assistant.content)) return "";
557
+ return assistant.content.filter(isTextBlock).map((b) => b.text).join("").trim();
558
+ }
559
+ return {
560
+ async maybeCompact(messages, usage) {
561
+ const before = estimateTokens(messages);
562
+ const baseResult = await base.maybeCompact(messages, usage);
563
+ const msgs = baseResult.messages;
564
+ if (circuitOpen || estimateTokens(msgs) <= threshold) {
565
+ return { ...baseResult, before, after: estimateTokens(msgs) };
566
+ }
567
+ const turns = splitTurns(msgs);
568
+ if (turns.length <= keepRecentTurns + 1) {
569
+ return { messages: msgs, kind: baseResult.kind, before, after: estimateTokens(msgs) };
570
+ }
571
+ const recent = turns.slice(turns.length - keepRecentTurns).flat();
572
+ const older = turns.slice(0, turns.length - keepRecentTurns).flat();
573
+ try {
574
+ const summary = await summarize(older);
575
+ failures = 0;
576
+ const summaryMsg = { role: "user", content: `[Summary of earlier conversation]
577
+ ${summary}` };
578
+ const out = [summaryMsg, ...recent];
579
+ return { messages: out, kind: "auto", before, after: estimateTokens(out) };
580
+ } catch {
581
+ failures++;
582
+ if (failures >= maxFailures) circuitOpen = true;
583
+ return { messages: msgs, kind: baseResult.kind, before, after: estimateTokens(msgs) };
584
+ }
585
+ }
586
+ };
587
+ }
588
+
235
589
  // src/permission.ts
236
590
  function globToRegExp(pattern) {
237
591
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
@@ -276,20 +630,35 @@ export {
276
630
  CodecError,
277
631
  MaxTurnsError,
278
632
  ProviderError,
633
+ SPILL_PREFIX,
279
634
  ToolError,
635
+ compaction,
280
636
  composeModelCall,
281
637
  composeToolCall,
282
638
  createAgent,
639
+ defaultCompactor,
283
640
  defineTool,
641
+ estimateTokens,
284
642
  fakeProvider,
285
643
  isTextBlock,
286
644
  isToolCallBlock,
645
+ llmCompactor,
646
+ memorySpillStore,
647
+ memoryStore,
648
+ microPass,
287
649
  nativeCodec,
288
650
  noopSandbox,
289
651
  permission,
290
652
  policy,
653
+ reactiveCompaction,
654
+ reactiveTrim,
655
+ retry,
291
656
  runLifecycle,
657
+ runPipeline,
658
+ snipPass,
659
+ splitTurns,
292
660
  textBlock,
293
661
  toToolSpec,
294
- toolResultBlock
662
+ toolResultBlock,
663
+ toolResultBudgetPass
295
664
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lite-agent/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Pluggable, event-driven agent core: kernel, strategy interfaces, middleware pipeline, and normalized types.",
5
5
  "license": "ISC",
6
6
  "type": "module",
@@ -34,11 +34,6 @@
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },
37
- "scripts": {
38
- "build": "tsup src/index.ts --format esm --dts --clean --tsconfig tsconfig.build.json",
39
- "test": "vitest run",
40
- "typecheck": "tsc --noEmit"
41
- },
42
37
  "dependencies": {
43
38
  "zod": "^4.3.6"
44
39
  },
@@ -47,5 +42,10 @@
47
42
  "tsup": "^8.3.0",
48
43
  "typescript": "^6.0.2",
49
44
  "vitest": "^2.1.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup src/index.ts --format esm --dts --clean --tsconfig tsconfig.build.json",
48
+ "test": "vitest run",
49
+ "typecheck": "tsc --noEmit"
50
50
  }
51
- }
51
+ }