@alisio/core 0.1.0-alpha.6 → 0.1.0-alpha.7
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/core/compaction.d.ts +25 -0
- package/dist/core/compaction.js +54 -0
- package/dist/core/contracts.d.ts +6 -0
- package/dist/core/runner.js +19 -4
- package/dist/runtime/store.d.ts +1 -0
- package/dist/runtime/store.js +9 -0
- package/package.json +1 -1
|
@@ -44,6 +44,31 @@ export interface CompactionPlan {
|
|
|
44
44
|
kept: Message[];
|
|
45
45
|
}
|
|
46
46
|
export declare function planCompaction(messages: Message[], options?: CompactionOptions): CompactionPlan | undefined;
|
|
47
|
+
/** Marker appended to message content cut by the context-budget reduction. */
|
|
48
|
+
export declare const TRUNCATION_MARKER = "\u2026 [truncated by context budget]";
|
|
49
|
+
/** Default per-message cap for kept tool result text after a failed compaction. */
|
|
50
|
+
export declare const MAX_TOOL_RESULT_CHARS = 8000;
|
|
51
|
+
/** Default per-message cap for kept user/assistant text after a failed compaction. */
|
|
52
|
+
export declare const MAX_TEXT_CHARS = 16000;
|
|
53
|
+
export interface MessageReductionOptions {
|
|
54
|
+
/** Keep at most this many characters of text per tool result (default 8_000). */
|
|
55
|
+
maxToolResultChars?: number;
|
|
56
|
+
/** Keep at most this many characters per user/assistant text (default 16_000). */
|
|
57
|
+
maxTextChars?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface MessageReduction {
|
|
60
|
+
messages: Message[];
|
|
61
|
+
/** Number of messages whose content was truncated. */
|
|
62
|
+
truncated: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Cuts oversized retained content so the session fits the context-budget hard limit after a
|
|
66
|
+
* compaction could not. Only message CONTENT changes (user/assistant `text` and the text parts
|
|
67
|
+
* of tool results); roles, call IDs, order and message boundaries stay identical, so the
|
|
68
|
+
* transcript remains valid and replayable and a tool call is never separated from its results.
|
|
69
|
+
* Checkpoint summaries (`summary: true`) are bounded by design and never truncated.
|
|
70
|
+
*/
|
|
71
|
+
export declare function reduceMessageSizes(messages: Message[], options?: MessageReductionOptions): MessageReduction;
|
|
47
72
|
/**
|
|
48
73
|
* Plain-text transcript used as summarizer input. Provider continuation data (for example
|
|
49
74
|
* encrypted reasoning) is opaque and is intentionally not included.
|
package/dist/core/compaction.js
CHANGED
|
@@ -91,6 +91,60 @@ export function planCompaction(messages, options = {}) {
|
|
|
91
91
|
return { cut, summarized: messages.slice(0, cut), kept: messages.slice(cut) };
|
|
92
92
|
}
|
|
93
93
|
const clip = (text, max) => text.length > max ? `${text.slice(0, max)}… [truncated]` : text;
|
|
94
|
+
/** Marker appended to message content cut by the context-budget reduction. */
|
|
95
|
+
export const TRUNCATION_MARKER = "… [truncated by context budget]";
|
|
96
|
+
/** Default per-message cap for kept tool result text after a failed compaction. */
|
|
97
|
+
export const MAX_TOOL_RESULT_CHARS = 8_000;
|
|
98
|
+
/** Default per-message cap for kept user/assistant text after a failed compaction. */
|
|
99
|
+
export const MAX_TEXT_CHARS = 16_000;
|
|
100
|
+
/**
|
|
101
|
+
* Cuts oversized retained content so the session fits the context-budget hard limit after a
|
|
102
|
+
* compaction could not. Only message CONTENT changes (user/assistant `text` and the text parts
|
|
103
|
+
* of tool results); roles, call IDs, order and message boundaries stay identical, so the
|
|
104
|
+
* transcript remains valid and replayable and a tool call is never separated from its results.
|
|
105
|
+
* Checkpoint summaries (`summary: true`) are bounded by design and never truncated.
|
|
106
|
+
*/
|
|
107
|
+
export function reduceMessageSizes(messages, options = {}) {
|
|
108
|
+
const maxToolResultChars = options.maxToolResultChars ?? MAX_TOOL_RESULT_CHARS;
|
|
109
|
+
const maxTextChars = options.maxTextChars ?? MAX_TEXT_CHARS;
|
|
110
|
+
let truncated = 0;
|
|
111
|
+
const reduced = messages.map((m) => {
|
|
112
|
+
if (m.role === "user") {
|
|
113
|
+
if (m.summary || m.text.length <= maxTextChars)
|
|
114
|
+
return m;
|
|
115
|
+
truncated++;
|
|
116
|
+
return { ...m, text: `${m.text.slice(0, maxTextChars)}\n${TRUNCATION_MARKER}` };
|
|
117
|
+
}
|
|
118
|
+
if (m.role === "assistant") {
|
|
119
|
+
if (m.text.length <= maxTextChars)
|
|
120
|
+
return m;
|
|
121
|
+
truncated++;
|
|
122
|
+
return { ...m, text: `${m.text.slice(0, maxTextChars)}\n${TRUNCATION_MARKER}` };
|
|
123
|
+
}
|
|
124
|
+
let used = 0;
|
|
125
|
+
let changed = false;
|
|
126
|
+
const content = m.result.content.map((part) => {
|
|
127
|
+
const room = maxToolResultChars - used;
|
|
128
|
+
const text = room <= 0
|
|
129
|
+
? TRUNCATION_MARKER
|
|
130
|
+
: part.text.length > room
|
|
131
|
+
? `${part.text.slice(0, room)}\n${TRUNCATION_MARKER}`
|
|
132
|
+
: part.text;
|
|
133
|
+
if (text !== part.text) {
|
|
134
|
+
changed = true;
|
|
135
|
+
used = maxToolResultChars;
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
used += part.text.length;
|
|
139
|
+
return { type: "text", text };
|
|
140
|
+
});
|
|
141
|
+
if (!changed)
|
|
142
|
+
return m;
|
|
143
|
+
truncated++;
|
|
144
|
+
return { ...m, result: { ...m.result, content } };
|
|
145
|
+
});
|
|
146
|
+
return truncated ? { messages: reduced, truncated } : { messages, truncated: 0 };
|
|
147
|
+
}
|
|
94
148
|
/**
|
|
95
149
|
* Plain-text transcript used as summarizer input. Provider continuation data (for example
|
|
96
150
|
* encrypted reasoning) is opaque and is intentionally not included.
|
package/dist/core/contracts.d.ts
CHANGED
|
@@ -56,6 +56,12 @@ export interface SessionStore {
|
|
|
56
56
|
* rest verbatim and in order. Callers must never split a tool call from its results.
|
|
57
57
|
*/
|
|
58
58
|
compact(id: string, replaced: number, summary: Message): void;
|
|
59
|
+
/**
|
|
60
|
+
* Atomically replace the whole active history with `messages` (like `compact` without a
|
|
61
|
+
* summary): used after a context-budget reduction so the persisted transcript matches what
|
|
62
|
+
* was actually sent. Callers must never split a tool call from its results.
|
|
63
|
+
*/
|
|
64
|
+
overwrite(id: string, messages: Message[]): void;
|
|
59
65
|
acquire(id: string): void;
|
|
60
66
|
release(id: string): void;
|
|
61
67
|
beginCall(session: string, call: ToolCall): void;
|
package/dist/core/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { textResult, } from "@alisio/sdk";
|
|
2
|
-
import { checkpointInstructions, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, shouldCompactContext, summarize, summaryMessage, } from "./compaction.js";
|
|
2
|
+
import { checkpointInstructions, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, reduceMessageSizes, shouldCompactContext, summarize, summaryMessage, } from "./compaction.js";
|
|
3
3
|
/**
|
|
4
4
|
* Default cumulative token budget for one run: several context windows (each turn re-sends the
|
|
5
5
|
* context), clamped; 1M tokens when the window is unknown.
|
|
@@ -263,6 +263,7 @@ export class AgentRunner {
|
|
|
263
263
|
async run(sessionId, prompt, signal, options = {}) {
|
|
264
264
|
const controller = this.claim(sessionId);
|
|
265
265
|
const o = this.options, emit = this.emitter(sessionId);
|
|
266
|
+
const limit = o.maxContextChars ?? 160_000;
|
|
266
267
|
let tokens = 0, lastText = "", acquired = false;
|
|
267
268
|
const usageTotal = { input: 0, output: 0 };
|
|
268
269
|
const timeout = AbortSignal.timeout(options.timeoutMs ?? o.timeoutMs ?? 300_000);
|
|
@@ -351,13 +352,27 @@ export class AgentRunner {
|
|
|
351
352
|
// estimate (`chars / 4 >= maxContextChars / 4`). `maxContextChars` below stays as the
|
|
352
353
|
// post-compaction hard limit.
|
|
353
354
|
if (compaction.auto !== false &&
|
|
354
|
-
shouldCompactContext(used(), chars(), o.contextWindow?.(model),
|
|
355
|
+
shouldCompactContext(used(), chars(), o.contextWindow?.(model), limit, compaction.threshold ?? 0.85)) {
|
|
355
356
|
await this.compactLocked(sessionId, emit, model, combined, "auto");
|
|
356
357
|
instructions = await withPersona();
|
|
357
358
|
messages = o.store.messages(sessionId);
|
|
358
359
|
}
|
|
359
|
-
|
|
360
|
-
|
|
360
|
+
// A compaction that still leaves the session over the hard limit (typically a kept tail
|
|
361
|
+
// holding huge tool outputs) is reduced in place instead of failing outright: oversized
|
|
362
|
+
// kept content is cut per message (roles/callIds/boundaries untouched, so the transcript
|
|
363
|
+
// stays valid and replayable). Only if even the reduced tail exceeds the limit the run
|
|
364
|
+
// fails with an actionable error; the reduction is persisted either way, so the session
|
|
365
|
+
// stays usable for later prompts.
|
|
366
|
+
if (chars() > limit) {
|
|
367
|
+
const reduction = reduceMessageSizes(messages);
|
|
368
|
+
if (reduction.truncated > 0) {
|
|
369
|
+
o.store.overwrite(sessionId, reduction.messages);
|
|
370
|
+
messages = reduction.messages;
|
|
371
|
+
emit("context_reduced", { messages: reduction.truncated });
|
|
372
|
+
}
|
|
373
|
+
if (chars() > limit)
|
|
374
|
+
throw new Error(`Context budget exceeded and compaction could not reduce it (approximately ${chars()} characters; limit ${limit}). Run /compact, trim large tool outputs, or start a new session.`);
|
|
375
|
+
}
|
|
361
376
|
let completion;
|
|
362
377
|
let truncated = false;
|
|
363
378
|
let usage;
|
package/dist/runtime/store.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare class SQLiteStore implements SessionStore {
|
|
|
25
25
|
append(id: string, message: Message): void;
|
|
26
26
|
setModel(id: string, model: string): void;
|
|
27
27
|
compact(id: string, replaced: number, summary: Message): void;
|
|
28
|
+
overwrite(id: string, messages: Message[]): void;
|
|
28
29
|
acquire(id: string): void;
|
|
29
30
|
release(id: string): void;
|
|
30
31
|
beginCall(id: string, call: ToolCall): void;
|
package/dist/runtime/store.js
CHANGED
|
@@ -168,6 +168,15 @@ export class SQLiteStore {
|
|
|
168
168
|
this.db.prepare("INSERT INTO messages(session,body) VALUES(?,?)").run(id, row.body);
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
+
overwrite(id, messages) {
|
|
172
|
+
this.db.transaction(() => {
|
|
173
|
+
// Retire the active rows (originals stay marked compacted for auditing), then rewrite
|
|
174
|
+
// the reduced transcript in the same order.
|
|
175
|
+
this.db.prepare("UPDATE messages SET compacted=1 WHERE session=? AND compacted=0").run(id);
|
|
176
|
+
for (const message of messages)
|
|
177
|
+
this.append(id, message);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
171
180
|
acquire(id) {
|
|
172
181
|
this.db.transaction(() => {
|
|
173
182
|
this.get(id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/core",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
4
|
"description": "Embeddable provider-agnostic Alisio agent core: tool loop, context compaction, local tools, MCP, plugin host and SQLite session store. Runs on Node >=22.16 and Bun.",
|
|
5
5
|
"author": "Gustavo Gutiérrez",
|
|
6
6
|
"license": "MIT",
|