@aparte/plugin-compaction 0.25.0 → 0.27.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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @aparte/plugin-compaction
2
2
 
3
- Conversation **compaction** for [aparté](https://github.com/apartejs/aparte): summarise the turns
4
- that no longer fit the model's window, keep the recent ones verbatim, and answer the context
5
- gauge's `aparte-compact`. Nothing in core compacts by itself — no UI kit does, and every agent
6
- SDK ships it as an opt-in module — so this is where it lives.
3
+ Conversation **compaction** for [aparté](https://github.com/apartejs/aparte): when a conversation
4
+ outgrows the model's window, summarise the older turns for the model and keep the recent ones
5
+ verbatim — without deleting anything. Nothing in core compacts by itself — no UI kit does, and
6
+ every agent SDK ships it as an opt-in module — so this is where it lives.
7
7
 
8
8
  ```bash
9
9
  npm install @aparte/plugin-compaction @aparte/core
@@ -12,47 +12,66 @@ npm install @aparte/plugin-compaction @aparte/core
12
12
  ```ts
13
13
  import { setupCompaction } from '@aparte/plugin-compaction';
14
14
 
15
- const compaction = setupCompaction(); // the global config, the current model's budget
15
+ const compaction = setupCompaction(); // the global config, the current model's window
16
16
  ```
17
17
 
18
- ```html
19
- <aparte-composer-toolbar>
20
- <!-- asks for a compaction on reaching 90 % of the window; the plugin answers -->
21
- <aparte-context auto-compact style="flex: 1"></aparte-context>
22
- </aparte-composer-toolbar>
23
- ```
18
+ That is the whole wiring. The plugin registers itself as the config's compactor and decides on
19
+ its own: after a turn that leaves the conversation past 80 % of the window (`threshold`), and
20
+ before a send whose request would not fit (the turn waits, Stop cancels both), and when the
21
+ model refuses a request as over its window (it compacts and sends the turn once more).
22
+
23
+ A compaction aims **below** the threshold, not at it: the history the model reads afterwards
24
+ lands at about `threshold − 0.2` of the window, less the fixed costs (system prompt, tools, the
25
+ room for the reply, the summary's slot), so one summary buys several turns instead of being asked
26
+ for again after the next. The selection is made in the model's own tokens (the character
27
+ heuristic's undercount is calibrated from the last reply, as the context gauge does) and leaves
28
+ room for the turn being prepared, so a long paste is made room for. A `selector` of your own is
29
+ called with the messages alone and is left as it is. Each chat compacts on its own: a summary
30
+ being written for one does not hold up another. A Restore cancels a summary still being written
31
+ for that chat (with `listen: false`, which hears no window event, the summary is refused when it
32
+ comes back instead), and Stop cancels it in every case.
24
33
 
25
- That is the whole wiring. A compaction resolves the chat, selects what to summarise —
26
- by default the budget-aware selector over the current model's `contextWindow`, the system
27
- prompt and the tools, keeping the newest turns that still fit; the last two exchanges when
28
- the model declares no window — summarises it through the config's transport (the request
29
- carries `_meta: { compaction: true }`, so a backend can route it to a cheaper model), then
30
- puts back the summary as a **notice** (`compaction: true` — centred, no avatar, no actions;
31
- sent to the model under a preamble saying what it is) followed by the kept turns verbatim.
34
+ A compaction **writes a checkpoint, it deletes nothing**. The summary goes on the first turn it
35
+ keeps — `contextSummary: { summary, count, createdAt }`, saved with the conversation — and the
36
+ history the model receives starts there, the summary first. Every message stays on screen and
37
+ in storage; the bubble draws a separator above that turn ("12 messages summarised for the
38
+ model", Show summary, Restore), and Restore sends the original messages to the model again.
32
39
 
33
40
  ```ts
34
- await compaction.compact(); // or from a button; returns the outcome, never throws
35
- compaction.abort(); // the summarisation in flight; the transcript is untouched
36
- compaction.running; // true meanwhile
37
- compaction.dispose(); // remove the listeners
41
+ await compaction.compact('main-chat'); // on demand; returns the outcome, never throws
42
+ compaction.abort(); // the summary in flight; the conversation is untouched
43
+ compaction.running; // true meanwhile
44
+ compaction.dispose(); // remove the listeners and the compactor
38
45
  ```
39
46
 
40
- **Options** — `selector` (your own `(messages) => { keep, drop }`; `createCompactionSelector`
41
- builds the budget-aware one over a window you choose), `prompt` (the summariser's
42
- instruction), `keyResolver` (the resolver you gave `AparteClient`, when the key is not on the
43
- config), `summarize` (replace the model call entirely: your endpoint, a cheaper model),
44
- `resolveTarget` (a transcript that lives in a store rather than in the DOM),
45
- `scopeToTargetId` (one setup per chat on a multi-chat page), `keepWithoutWindow`, `listen`.
46
- The config comes last, like every `setup*`: `setupCompaction(options, config)`.
47
+ **The summary** is asked of the current model through the config's transport: streamed, with a
48
+ `maxTokens` that keeps the answer inside the window and `_meta: { compaction: true }`, so a
49
+ backend can route it to a cheaper model. The default prompt asks for fixed sections in the
50
+ conversation's language; a second compaction updates the summary rather than summarising a
51
+ summary; the first request is kept word for word. A summary cut off at the length limit, ending
52
+ in an error, or no shorter than what it replaces is refused and the conversation stays as it
53
+ was. With **no** window on the model (and no `contextWindow` option) nothing says what fits, so
54
+ the default skips with `'no-window'`.
55
+
56
+ **Options** — `auto` (default `true`; `false` is **manual only**: only a person's request compacts —
57
+ Compact now, Retry, `compact()`, `aparte-compact` — and neither the after-turn, the before-send nor
58
+ the overflow attempt is made, so a model's overflow error stands as it did before the plugin; so
59
+ does `listen: false`), `threshold` (default `0.8`), `contextWindow` (the window when the model declares none),
60
+ `selector` (your own `(messages) => { keep, drop }`; `createCompactionSelector` builds the
61
+ budget-aware one), `prompt`, `keyResolver`, `summarize` (replace the model call: your endpoint, a
62
+ cheaper model), `resolveTarget` (a transcript in a store: `{ getMessages, updateMessage }`),
63
+ `scopeToTargetId` (one setup per chat on a multi-chat page), `keepWithoutWindow`, `listen`. The
64
+ config comes last, like every `setup*`: `setupCompaction(options, config)`.
47
65
 
48
- **Events**, on `window`, each naming the chat: `aparte-compact-start`, `aparte-compact-done`
49
- (`{ summary, kept, dropped }`, or `{ skipped: true, reason }` — `empty`, `nothing-to-drop`,
50
- `running`, `streaming`), `aparte-compact-error` (`{ error }`).
66
+ **Events**, on `window`, each naming the chat and the message the checkpoint sits on:
67
+ `aparte-compact-start`, `aparte-compact-done` (`{ summary, kept, dropped, count }`, `{ skipped:
68
+ true, reason }` — `empty`, `nothing-to-drop`, `running`, `streaming`, `no-window` — or `{ restored:
69
+ true }`), `aparte-compact-error` (`{ error }`).
51
70
 
52
71
  **The budget**, exported for a host that wants the numbers: `computeHistoryBudget`,
53
- `splitHistoryBudget`, `estimateTokens`, `estimateTokensJson`, `DEFAULT_COMPACTION_CONFIG`;
54
- and `transcriptForSummary` / `messageText` / `DEFAULT_COMPACTION_PROMPT` for a `summarize`
55
- of your own that wants the same transcript.
72
+ `splitHistoryBudget`, `estimateTokens`, `estimateTokensJson`, `DEFAULT_COMPACTION_CONFIG`; and
73
+ `transcriptForSummary` / `messageText` / `DEFAULT_COMPACTION_PROMPT` for a `summarize` of your
74
+ own that wants the same transcript.
56
75
 
57
76
  `@aparte/core` is the only **peer dependency**. No element, no DOM at import: the same entry
58
77
  serves the browser and Node.
package/dist/budget.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  * reports usage and lets the caller decide — so it moved here, next to the one thing
17
17
  * that does: the compaction this plugin performs. Zero deps, a char-count heuristic.
18
18
  */
19
+ import { estimateTokens } from '@aparte/core';
19
20
  export interface CompactionConfig {
20
21
  /** Total context window of the active model, in tokens. */
21
22
  contextWindow: number;
@@ -35,8 +36,10 @@ export interface CompactionConfig {
35
36
  */
36
37
  summaryRatio: number;
37
38
  /**
38
- * Ceiling on that reservation, in tokens. It sizes the WINDOW — it does not bound
39
- * the summary your `summarize` returns, and nothing here truncates one.
39
+ * Ceiling on that reservation, in tokens. It sizes the WINDOW: the verbatim turns
40
+ * leave this much room for the summary. The summary request's own `maxTokens` is a
41
+ * looser ceiling that only keeps the answer inside the window (see `setupCompaction`),
42
+ * and a summary no shorter than the turns it replaces is refused.
40
43
  */
41
44
  summaryMaxTokens: number;
42
45
  }
@@ -59,9 +62,9 @@ export interface BudgetResult {
59
62
  * The history budget, split: the room held back for the running summary, and the
60
63
  * verbatim window that is what remains.
61
64
  *
62
- * `summary` is a RESERVATION, read only to shrink `window`. Nothing measures the
63
- * summary against it, and nothing clips one that comes back longer — a summariser that
64
- * overruns simply costs the turn more than the split assumed.
65
+ * `summary` is a RESERVATION, read to shrink `window`. The summary request carries a
66
+ * looser `maxTokens` of its own (it keeps the answer inside the window), and a summary
67
+ * that is cut off at that limit, or no shorter than the turns it replaces, is refused.
65
68
  */
66
69
  export interface SplitBudget {
67
70
  summary: number;
@@ -69,11 +72,13 @@ export interface SplitBudget {
69
72
  }
70
73
  export declare const DEFAULT_COMPACTION_CONFIG: CompactionConfig;
71
74
  /**
72
- * Token heuristic (FR ~3.5 chars/tok, EN ~4 chars/tok).
73
- * Accurate to ±10% — enough for budgeting, and it avoids `tokenizer.encode()`,
74
- * which costs ~5ms per message × N (too slow to run on every turn).
75
+ * Token heuristic — core's, re-exported under the name this module always had.
76
+ *
77
+ * It lives in `@aparte/core` (`estimateTokens`) because the context gauge and the count
78
+ * the client records on each reply read the SAME estimate: a plugin that kept a private copy
79
+ * would decide on figures the page does not show.
75
80
  */
76
- export declare function estimateTokens(text: string | null | undefined): number;
81
+ export { estimateTokens };
77
82
  /**
78
83
  * Estimate tokens for a JSON-serializable structure (e.g. tools array).
79
84
  */
@@ -1 +1 @@
1
- {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,gBAAgB;IAC7B,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oEAAoE;IACpE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAClB;AAID,eAAO,MAAM,yBAAyB,EAAE,gBAYvC,CAAC;AAIF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGtE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOvD;AAID;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACtC,GAAG,YAAY,CA2Bf;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAC9B,aAAa,EAAE,MAAM,EACrB,GAAG,GAAE,gBAA4C,GAClD,WAAW,CAGb"}
1
+ {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C,MAAM,WAAW,gBAAgB;IAC7B,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oEAAoE;IACpE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAClB;AAID,eAAO,MAAM,yBAAyB,EAAE,gBAYvC,CAAC;AAIF;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOvD;AAID;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACtC,GAAG,YAAY,CA2Bf;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAC9B,aAAa,EAAE,MAAM,EACrB,GAAG,GAAE,gBAA4C,GAClD,WAAW,CAGb"}
@@ -2,103 +2,132 @@
2
2
  * compaction.ts — `setupCompaction`: the controller that answers `aparte-compact`.
3
3
  *
4
4
  * One compaction is: resolve the chat, select what to summarise (the budget-aware
5
- * selector over the current model by default), summarise it through the config's
6
- * transport — or a summariser of the host's — then replace the transcript with the
7
- * summary as a notice followed by the kept turns verbatim. Every step that can fail
8
- * reports on `window` (`aparte-compact-start` / `-done` / `-error`, each naming the
9
- * chat) AND in the returned outcome, so a host that called `compact()` itself never
10
- * has to listen for the answer to its own call.
5
+ * selector over the model's view of the conversation by default), summarise it through
6
+ * the config's transport — or a summariser of the host's — then write the summary as a
7
+ * CHECKPOINT on the first message that stays verbatim. Nothing is deleted: the messages
8
+ * above the checkpoint stay on the screen and in storage, and the history the client
9
+ * builds resumes from the checkpoint, so the model reads the summary in their place.
10
+ * Every step that can fail reports on `window` (`aparte-compact-start` / `-done` /
11
+ * `-error`, each naming the chat) AND in the returned outcome, so a host that called
12
+ * `compact()` itself never has to listen for the answer to its own call.
11
13
  *
12
14
  * What this file is careful about, each learned on the client's version of it:
13
- * - one compaction at a time per setup — a second request while one runs is
14
- * reported skipped, not started (two paid summaries, both replacing the transcript);
15
- * - a transcript with a turn in flight is left alone — summarising under a streaming
16
- * reply would drop it;
17
- * - the summarisation has its OWN abort controller, reached by `abort()` and by an
18
- * `aparte-abort` addressed to the chat, so a summary the user cancelled stops there;
19
- * - and a summary nobody cancelled is refused too when the transcript it was written
20
- * from is gone: if not one selected turn is left on the target when the model
21
- * answers, the conversation was switched (or reset) underneath, and the summary
22
- * would land over a transcript it never read;
23
- * - what arrived while the summary was being written is kept: the replacement is
24
- * summary, then the kept turns, then anything newer than the selection;
15
+ * - one compaction at a time per CHAT — a second request for a chat whose summary is
16
+ * being written is reported skipped, not started (two paid summaries, both written),
17
+ * while another chat on the same setup compacts on its own;
18
+ * - a transcript with a turn in flight is left alone — except the turn being PREPARED
19
+ * (a send or retry the client is about to make), which the caller names in
20
+ * `exclude`: it is neither summarised nor counted as in flight;
21
+ * - the summarisation has its OWN abort controller, reached by `abort()`, by an
22
+ * `aparte-abort` addressed to the chat and by the abort signal of the request that
23
+ * asked for it (the client's Stop), so a summary the user cancelled stops there, and a
24
+ * Restore of the same chat cancels it too;
25
+ * - and a summary nobody cancelled is refused too when the message it was to sit on
26
+ * is no longer on the chat's path, or the checkpoint it updates was taken off: the
27
+ * conversation was switched (or reset) or restored underneath, and a success the host
28
+ * dropped would be reported as one;
29
+ * - the checkpoint is the model's own update of the one before it: a second compaction
30
+ * sends "Summary so far:" and only the messages since, not the whole history again;
25
31
  * - a chat is addressed by id through the same rule the gauge and the client use, and
26
32
  * `scopeToTargetId` makes a setup answer one chat on a page that has several.
27
33
  */
28
- import { type AparteConfig, type AparteMessage, type AparteChatRequest } from '@aparte/core';
34
+ import { type AparteConfig, type AparteMessage, type AparteChatRequest, type AparteCompactor, type AparteCompactorOutcome, type AparteCompactorRequest } from '@aparte/core';
29
35
  import { type CompactionSelection } from './selector.js';
30
36
  /**
31
- * The three things a chat must expose to be compacted: read the active path, empty
32
- * it, append to it. `<aparte-chat-viewport>` does; an `<aparte-chat>` shell hands over
33
- * its viewport. A host whose transcript lives elsewhere (a framework store) gives
34
- * `resolveTarget` an object of its own.
37
+ * What a chat must expose to be compacted: read the active path and update one message
38
+ * of it. `<aparte-chat-viewport>` does; an `<aparte-chat>` shell hands over its viewport.
39
+ * A host whose transcript lives elsewhere (a framework store) gives `resolveTarget` an
40
+ * object of its own.
35
41
  */
36
42
  export interface CompactionTarget {
37
43
  getMessages(): AparteMessage[];
38
44
  /**
39
- * Empty the transcript. A compaction passes `{ revokeAttachments: false }`,
40
- * because it puts the kept turns straight back: `<aparte-chat-viewport>` releases
41
- * every message's object URLs on the way out, which killed the images and files of
42
- * the turns the compaction was keeping. It revokes the summarised-away ones itself
43
- * afterwards. A target that ignores the argument keeps working — it just leaks the
44
- * URLs of what it drops, which is what it did before this existed.
45
+ * Merge `updates` into one message of the path — the one write a compaction makes: the
46
+ * checkpoint (`contextSummary`) on the first message that stays. A target without it
47
+ * cannot hold a checkpoint, and the compaction fails with an error saying so rather
48
+ * than falling back to rewriting the transcript.
45
49
  */
46
- clearAll(options?: {
50
+ updateMessage?(messageId: string, updates: Partial<AparteMessage>): void;
51
+ /**
52
+ * Not called any more. A compaction used to empty the transcript and put the kept turns
53
+ * back; it now writes one checkpoint and removes nothing. Still accepted, so a target
54
+ * written for the earlier contract keeps type-checking.
55
+ */
56
+ clearAll?(options?: {
47
57
  revokeAttachments?: boolean;
48
58
  }): void;
49
- appendMessage(message: AparteMessage): void;
59
+ /** Not called any more; see `clearAll`. */
60
+ appendMessage?(message: AparteMessage): void;
50
61
  }
51
62
  /** Which messages are summarised (`drop`) and which stay verbatim (`keep`). Pure — no model call. */
52
63
  export type CompactionMessageSelector = (messages: AparteMessage[]) => CompactionSelection<AparteMessage>;
53
64
  /** The key (or record of settings) for a provider — the same shape `AparteClientOptions.keyResolver` takes. */
54
65
  export type CompactionKeyResolver = (providerId: string) => string | Record<string, string> | undefined | null | Promise<string | Record<string, string> | undefined | null>;
55
- /** Replaces the model call: given the summarisation request, return the summary text. */
66
+ /**
67
+ * Replaces the model call: given the summarisation request, return the summary text.
68
+ * The request asks for a stream (`stream: true`) and carries `maxTokens`, a ceiling on the
69
+ * answer that keeps it inside the window — a summariser that talks to a model should pass both on.
70
+ */
56
71
  export type CompactionSummarizer = (request: AparteChatRequest, signal: AbortSignal) => Promise<string>;
57
72
  /** Why a compaction did nothing. */
58
73
  export type CompactionSkipReason =
59
74
  /** The transcript is empty. */
60
75
  'empty'
61
- /** The selector kept everything — within budget, or nothing older than what is kept. */
76
+ /**
77
+ * The selector kept everything — within budget, or nothing older than what is kept —
78
+ * or no user message could carry the checkpoint (the first message kept must be the
79
+ * user's). Also what an automatic request is told when the back-off, a Restore or the
80
+ * hysteresis says not to try now, and what an automatic or overflow request is told
81
+ * when `auto: false` (or `listen: false`) keeps the setup manual.
82
+ */
62
83
  | 'nothing-to-drop'
63
- /** A compaction is already running for this setup. */
84
+ /** A compaction is already running for this chat. */
64
85
  | 'running'
65
- /** A turn is in flight in the transcript; compacting under it would drop it. */
66
- | 'streaming';
67
- export type CompactionOutcome = {
68
- ok: true;
69
- skipped: true;
70
- reason: CompactionSkipReason;
71
- targetId?: string;
72
- } | {
73
- ok: true;
74
- skipped: false;
75
- summary: string;
76
- kept: number;
77
- dropped: number;
78
- targetId?: string;
79
- } | {
80
- ok: false;
81
- error: string;
82
- targetId?: string;
83
- };
86
+ /** A turn is in flight in the transcript; compacting under it would summarise a half-written reply. */
87
+ | 'streaming'
88
+ /**
89
+ * The current model declares no `contextWindow` and nothing of the host's sizes the
90
+ * request (no `selector`, no `summarize`, no `keepWithoutWindow`). Without a window the
91
+ * default would send everything but the last few messages in one request, which a small
92
+ * model refuses as over its context. Declare the window, or pass one of the three.
93
+ */
94
+ | 'no-window';
95
+ /** How a compaction ended — core's outcome, the same type the compactor seam returns. */
96
+ export type CompactionOutcome = AparteCompactorOutcome;
84
97
  export interface CompactionSetupOptions {
85
98
  /**
86
- * Which messages are summarised away and which stay verbatim.
99
+ * Which messages are summarised and which stay verbatim, over what the model reads:
100
+ * the messages from the latest checkpoint on, minus the turn being prepared.
87
101
  *
88
102
  * Default: `createCompactionSelector` over the current model — its `contextWindow`,
89
103
  * the resolved system prompt and the registered tools set the budget, and the
90
- * newest turns that fit the window stay. When the current model declares no
91
- * window there is no budget to walk, so the last `keepWithoutWindow` messages
92
- * stay and the rest is summarised.
104
+ * newest turns that fit stay. Each compaction sizes it: to land below the `threshold`
105
+ * (about `threshold − 0.2` of the window once the fixed costs are paid, so one
106
+ * compaction buys several turns), with room for the turn being prepared, and in the
107
+ * model's own tokens (the heuristic's undercount, as the context gauge calibrates it).
108
+ * A `selector` of your own is called with the messages alone and keeps what it keeps.
109
+ * Whatever it chooses, the cut is moved so the
110
+ * first message kept is a user message (the checkpoint sits on it). When the current
111
+ * model declares no window there is no budget to walk, and the compaction skips
112
+ * (`'no-window'`) unless the host sizes the request itself: a `selector`, a
113
+ * `summarize` or `keepWithoutWindow`.
93
114
  */
94
115
  selector?: CompactionMessageSelector;
95
- /** How many of the newest messages stay when the model declares no window. Default 4 — the last two exchanges. */
116
+ /**
117
+ * How many of the newest messages stay when the model declares no window; everything
118
+ * older goes to the summariser in ONE request, so the number is the host's bet that it
119
+ * fits the model. Passing it is the opt-in: left out, a model without a window makes the
120
+ * default compaction skip with `'no-window'`, since nothing says the request would fit.
121
+ * Without a `selector` of your own, `4` (the last two exchanges) is what it takes when
122
+ * you opt in through `summarize` alone. Ignored when the model declares a window.
123
+ */
96
124
  keepWithoutWindow?: number;
97
125
  /**
98
126
  * The summariser's instruction — carried by the ask itself, not by a `system`
99
127
  * message, so a provider that imposes its own system prompt cannot drop it.
100
128
  * English by default — it is addressed to the model, not to the user — asking for
101
- * the decisions, the open tasks and the tool results that still matter. Replace it
129
+ * fixed sections (the goal, the decisions, the open tasks, the facts and tool results
130
+ * still needed, the next step) in the language of the conversation. Replace it
102
131
  * to steer the summary (a language, a domain, a length).
103
132
  */
104
133
  prompt?: string;
@@ -110,51 +139,118 @@ export interface CompactionSetupOptions {
110
139
  keyResolver?: CompactionKeyResolver;
111
140
  /**
112
141
  * Replace the model call entirely. The request carries the transcript of the
113
- * dropped turns, then a final `user` message holding the summariser's instruction
114
- * and the ask, and `_meta: { compaction: true }`; return the summary text. For a
142
+ * turns being summarised, then a final `user` message holding the summariser's
143
+ * instruction and the ask, `stream: true`, `maxTokens` (a ceiling on the answer that
144
+ * keeps it inside the window) and `_meta: { compaction: true }`; return the summary
145
+ * text. When an earlier checkpoint exists the request opens with "Summary so far:" and
146
+ * that summary, and asks for an updated one. Your own call
147
+ * is not trimmed to the window — the sizing is yours — but a summary that is no
148
+ * shorter than the turns it replaces is still refused. For a
115
149
  * host with an endpoint of its own, or a cheaper model for summaries. With one, no
116
150
  * provider needs to be configured at all.
117
151
  */
118
152
  summarize?: CompactionSummarizer;
119
153
  /**
120
- * Resolve the chat to compact. Default: the element whose id is the `targetId`
121
- * (or, unnamed, the first `<aparte-chat>` / `<aparte-chat-viewport>` /
122
- * `[data-aparte-chat]` on the page), taking its viewport when the element itself
123
- * cannot render. Return your own object for a transcript that lives in a store.
154
+ * Resolve the chat to compact. Default: the chat element the request carries (the
155
+ * compactor seam passes it, so a chat inside a shadow root is found), else the element
156
+ * whose id is the `targetId` (or, unnamed, the first `<aparte-chat>` /
157
+ * `<aparte-chat-viewport>` / `[data-aparte-chat]` on the page), taking its viewport
158
+ * when the element itself cannot render. Return your own object for a transcript that
159
+ * lives in a store.
124
160
  */
125
161
  resolveTarget?: (targetId?: string) => CompactionTarget | null;
126
162
  /**
127
163
  * Answer only the `aparte-compact` and `aparte-abort` events that name this chat
128
- * id, and compact it when `compact()` is called without one. For a page with
129
- * several chats and one setup per chat.
164
+ * id, and compact it when `compact()` is called without one. A request for another chat
165
+ * is refused. For a page with several chats and one setup per chat — and note that the
166
+ * config has ONE compactor: the setup made last on a config is the one the client and
167
+ * the gauge call.
130
168
  */
131
169
  scopeToTargetId?: string;
132
- /** Listen for `aparte-compact` and `aparte-abort` on `window`. Default `true`; `false` for a host that calls `compact()` itself. */
170
+ /**
171
+ * Listen for `aparte-compact` and `aparte-abort` on `window`. Default `true`; `false` for
172
+ * a host that calls `compact()` itself.
173
+ *
174
+ * `false` also keeps compaction manual, whatever `auto` says (see `auto`): a setup that
175
+ * hears nothing decides nothing. A Stop still cancels a summary: the client hands the
176
+ * turn's abort signal to the compactor, so it does not depend on this listener.
177
+ */
133
178
  listen?: boolean;
179
+ /**
180
+ * Compact by itself. Default `true`: once set up, the plugin summarises a chat when a
181
+ * turn ends with the context at `threshold` of its window, before a send whose request
182
+ * would not fit — the client asks first (`beforeSend`) — and when the model refuses a
183
+ * request as over its window (the client asks again, with `reason: 'overflow'`, and
184
+ * sends the turn once more).
185
+ *
186
+ * `false` means MANUAL ONLY: only a person's request compacts (the gauge's Compact now,
187
+ * a separator's Retry, `compact()`, `aparte-compact`). None of the three automatic
188
+ * attempts is made, the overflow included — `compact()` answers an `'auto'` or
189
+ * `'overflow'` request with a skip, so the model's error stands exactly as it did before
190
+ * the plugin. Forced `false` with `listen: false` (a setup that hears nothing decides
191
+ * nothing).
192
+ *
193
+ * It acts only when a summariser can run: a provider registered for the current model,
194
+ * or a `summarize` of the host's. It never acts on a Restore, a conversation switch or a
195
+ * load, and never again after two failures in a row until something succeeds or a person
196
+ * asks (see the back-off in the docs).
197
+ */
198
+ auto?: boolean;
199
+ /**
200
+ * The share of the window, from 0 to 1, at which a turn's end triggers a compaction.
201
+ * Default `0.8`. The gauge's own levels are independent of it.
202
+ *
203
+ * A compaction aims lower than the threshold: with the default selector it leaves the
204
+ * history the model reads at about `threshold − 0.2` of the window, less the fixed costs
205
+ * (the system prompt, the tools, the room for the reply, the summary's slot), so one
206
+ * compaction buys several turns and the next is not asked for after this one. That holds
207
+ * for a person's request and an overflow retry too; a `selector` of your own is left
208
+ * alone. If the context is still at the threshold afterwards (a long recent tail the
209
+ * selector must keep), the after-turn trigger waits until a turn ends below it; a request
210
+ * that would not fit is still compacted before it is sent, and an overflow still retried.
211
+ */
212
+ threshold?: number;
213
+ /**
214
+ * The window to measure against when the current model declares none — a number, or a
215
+ * getter read at each use (`() => settings.contextWindow`). A local server has no
216
+ * window the page can read, and without one nothing is automatic and the default
217
+ * selector skips. The model's own `contextWindow` wins when it declares one. For one
218
+ * more release, the `window` a gauge sends with its `aparte-compact` request is read
219
+ * as a last resort.
220
+ */
221
+ contextWindow?: number | (() => number | undefined);
134
222
  }
135
- export interface CompactionController {
223
+ /**
224
+ * The controller `setupCompaction` returns — and the compactor it registers on the
225
+ * config (`AparteConfig.setCompactor`), so the gauge and the client call the same object
226
+ * a host does.
227
+ */
228
+ export interface CompactionController extends Omit<AparteCompactor, 'compact' | 'beforeSend' | 'preview'>, Required<Pick<AparteCompactor, 'beforeSend' | 'preview'>> {
136
229
  /**
137
- * Compact one chat — the one `targetId` names, else the scoped one, else the
138
- * first on the page. Never rejects: the outcome says what happened, and the
230
+ * Compact one chat. Takes the compactor seam's request (the chat element, its id, the
231
+ * ids of a turn being prepared, why) or, as it always did, a chat id, or nothing:
232
+ * the scoped chat, else the first on the page. A string or nothing is a person's
233
+ * request (`reason: 'manual'`). Never rejects: the outcome says what happened, and the
139
234
  * same information goes out as `aparte-compact-done` / `aparte-compact-error`.
140
235
  */
141
- compact(targetId?: string): Promise<CompactionOutcome>;
142
- /** Abort the summarisation in flight, if any. The transcript is left untouched. */
236
+ compact(request?: string | AparteCompactorRequest): Promise<CompactionOutcome>;
237
+ /** Abort the summarisations in flight, if any. The transcripts are left untouched. */
143
238
  abort(): void;
144
- /** `true` while a summarisation is in flight. */
239
+ /** `true` while a summarisation is in flight, for any chat of this setup. */
145
240
  readonly running: boolean;
146
- /** Remove the listeners and forget this setup; an in-flight summarisation is aborted. */
241
+ /** Remove the listeners and forget this setup; an in-flight summarisation is aborted and the compactor unregistered. */
147
242
  dispose(): void;
148
243
  }
149
244
  /**
150
- * Install compaction on a config. Returns the controller; the same call on the same
151
- * config replaces the previous setup (its listeners are removed first).
245
+ * Install compaction on a config. Returns the controller, which is also registered as the
246
+ * config's compactor; the same call on the same config replaces the previous setup (its
247
+ * listeners are removed first).
152
248
  *
153
249
  * ```ts
154
250
  * import { setupCompaction } from '@aparte/plugin-compaction';
155
251
  *
156
252
  * const compaction = setupCompaction(); // the global config, the current model's budget
157
- * // `<aparte-context auto-compact>` now asks for a compaction on reaching 90 %;
253
+ * // `<aparte-context>` now offers "Compact now", and the separator's Retry works;
158
254
  * // or ask yourself:
159
255
  * await compaction.compact();
160
256
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../src/compaction.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../src/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAEH,KAAK,YAAY,EAAE,KAAK,aAAa,EAA0B,KAAK,iBAAiB,EAElB,KAAK,eAAe,EACvF,KAAK,sBAAsB,EAA8B,KAAK,sBAAsB,EACvF,MAAM,cAAc,CAAC;AAKtB,OAAO,EACuE,KAAK,mBAAmB,EACrG,MAAM,eAAe,CAAC;AAGvB;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,WAAW,IAAI,aAAa,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3D,2CAA2C;IAC3C,aAAa,CAAC,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CAChD;AAED,qGAAqG;AACrG,MAAM,MAAM,yBAAyB,GAAG,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,mBAAmB,CAAC,aAAa,CAAC,CAAC;AAE1G,+GAA+G;AAC/G,MAAM,MAAM,qBAAqB,GAAG,CAChC,UAAU,EAAE,MAAM,KACjB,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;AAEtH;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAExG,oCAAoC;AACpC,MAAM,MAAM,oBAAoB;AAC5B,+BAA+B;AAC7B,OAAO;AACT;;;;;;GAMG;GACD,iBAAiB;AACnB,qDAAqD;GACnD,SAAS;AACX,uGAAuG;GACrG,WAAW;AACb;;;;;GAKG;GACD,WAAW,CAAC;AAElB,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAEvD,MAAM,WAAW,sBAAsB;IACnC;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,oBAAoB,CAAC;IACjC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;IAC/D;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;CACvD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAqB,SAClC,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC,EAC3D,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE,YAAY,GAAG,SAAS,CAAC,CAAC;IACzD;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC/E,sFAAsF;IACtF,KAAK,IAAI,IAAI,CAAC;IACd,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,wHAAwH;IACxH,OAAO,IAAI,IAAI,CAAC;CACnB;AAqWD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,EAAE,MAAM,GAAE,YAAiC,GAAG,oBAAoB,CA0hBrI"}
package/dist/cut.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * cut.ts — where a checkpoint sits, and the one thing a summary must not lose.
3
+ *
4
+ * Two small pure pieces `compact()` leans on, kept out of it so each can be read (and
5
+ * tested) without a model call in sight.
6
+ */
7
+ /**
8
+ * The index of the first message that stays verbatim, moved so it is a USER message.
9
+ *
10
+ * A checkpoint is read by the model as "the summary, then this message and everything
11
+ * after it", and every provider wants the first turn after a preamble to be the user's:
12
+ * a history that resumes on an assistant reply is refused by some templates and
13
+ * misread by the rest. So the boundary the selector chose (`boundary`, the count of
14
+ * leading messages it dropped) is moved toward the OLDER end until it lands on a user
15
+ * message, which keeps more verbatim and summarises less than the selector asked for.
16
+ * When that would summarise nothing at all, it moves toward the NEWER end instead, which
17
+ * summarises a little more. A kept message has to exist to carry the checkpoint, so the
18
+ * boundary never goes past the last message.
19
+ *
20
+ * `null` when no user message qualifies — there is nothing a checkpoint could sit on.
21
+ */
22
+ export declare function cutAtUser(messages: ReadonlyArray<{
23
+ role: string;
24
+ }>, boundary: number): number | null;
25
+ /**
26
+ * The line that introduces the conversation's first request inside a stored summary.
27
+ * English and fixed, because the summary is addressed to the model and because an
28
+ * update of the summary has to find the block again (see {@link splitFirstRequest}).
29
+ */
30
+ export declare const FIRST_REQUEST_LINE = "The conversation's first request, verbatim:";
31
+ /** The most of the first request a summary carries. */
32
+ export declare const FIRST_REQUEST_MAX = 1500;
33
+ /**
34
+ * The first request, appended to the model's summary under {@link FIRST_REQUEST_LINE}.
35
+ *
36
+ * It is not written by the model: what the user first asked is the one piece of a
37
+ * conversation every later turn is judged against, and a summariser paraphrases it away
38
+ * as readily as anything else. Clipped, so a pasted document does not become the summary.
39
+ */
40
+ export declare function firstRequestBlock(text: string): string;
41
+ /**
42
+ * A stored summary taken apart: what the model wrote, and the first-request block that
43
+ * was appended to it (empty when there is none).
44
+ *
45
+ * An update of a summary hands the model the first part only and puts the second back
46
+ * unchanged afterwards. Handing it the whole thing would have it copy the block into
47
+ * its own answer, and the block would be there twice.
48
+ */
49
+ export declare function splitFirstRequest(summary: string): {
50
+ base: string;
51
+ block: string;
52
+ };
53
+ //# sourceMappingURL=cut.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cut.d.ts","sourceRoot":"","sources":["../src/cut.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpG;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,gDAAgD,CAAC;AAEhF,uDAAuD;AACvD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAItD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAGlF"}