@animalabs/context-manager 0.1.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/src/blob-manager.d.ts +37 -0
- package/dist/src/blob-manager.d.ts.map +1 -0
- package/dist/src/blob-manager.js +128 -0
- package/dist/src/blob-manager.js.map +1 -0
- package/dist/src/context-log.d.ts +99 -0
- package/dist/src/context-log.d.ts.map +1 -0
- package/dist/src/context-log.js +277 -0
- package/dist/src/context-log.js.map +1 -0
- package/dist/src/context-manager.d.ts +245 -0
- package/dist/src/context-manager.d.ts.map +1 -0
- package/dist/src/context-manager.js +553 -0
- package/dist/src/context-manager.js.map +1 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/message-store.d.ts +135 -0
- package/dist/src/message-store.d.ts.map +1 -0
- package/dist/src/message-store.js +372 -0
- package/dist/src/message-store.js.map +1 -0
- package/dist/src/strategies/autobiographical.d.ts +199 -0
- package/dist/src/strategies/autobiographical.d.ts.map +1 -0
- package/dist/src/strategies/autobiographical.js +1122 -0
- package/dist/src/strategies/autobiographical.js.map +1 -0
- package/dist/src/strategies/index.d.ts +3 -0
- package/dist/src/strategies/index.d.ts.map +1 -0
- package/dist/src/strategies/index.js +3 -0
- package/dist/src/strategies/index.js.map +1 -0
- package/dist/src/strategies/knowledge.d.ts +46 -0
- package/dist/src/strategies/knowledge.d.ts.map +1 -0
- package/dist/src/strategies/knowledge.js +270 -0
- package/dist/src/strategies/knowledge.js.map +1 -0
- package/dist/src/strategies/passthrough.d.ts +17 -0
- package/dist/src/strategies/passthrough.d.ts.map +1 -0
- package/dist/src/strategies/passthrough.js +69 -0
- package/dist/src/strategies/passthrough.js.map +1 -0
- package/dist/src/types/context.d.ts +108 -0
- package/dist/src/types/context.d.ts.map +1 -0
- package/dist/src/types/context.js +2 -0
- package/dist/src/types/context.js.map +1 -0
- package/dist/src/types/index.d.ts +5 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +2 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/message.d.ts +129 -0
- package/dist/src/types/message.d.ts.map +1 -0
- package/dist/src/types/message.js +2 -0
- package/dist/src/types/message.js.map +1 -0
- package/dist/src/types/strategy.d.ts +233 -0
- package/dist/src/types/strategy.d.ts.map +1 -0
- package/dist/src/types/strategy.js +32 -0
- package/dist/src/types/strategy.js.map +1 -0
- package/dist/test/autobiographical.test.d.ts +2 -0
- package/dist/test/autobiographical.test.d.ts.map +1 -0
- package/dist/test/autobiographical.test.js +46 -0
- package/dist/test/autobiographical.test.js.map +1 -0
- package/dist/test/head-window-reset.test.d.ts +17 -0
- package/dist/test/head-window-reset.test.d.ts.map +1 -0
- package/dist/test/head-window-reset.test.js +342 -0
- package/dist/test/head-window-reset.test.js.map +1 -0
- package/dist/test/integration.test.d.ts +2 -0
- package/dist/test/integration.test.d.ts.map +1 -0
- package/dist/test/integration.test.js +1341 -0
- package/dist/test/integration.test.js.map +1 -0
- package/dist/test/knowledge.test.d.ts +2 -0
- package/dist/test/knowledge.test.d.ts.map +1 -0
- package/dist/test/knowledge.test.js +617 -0
- package/dist/test/knowledge.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +48 -0
- package/src/blob-manager.ts +155 -0
- package/src/context-log.ts +342 -0
- package/src/context-manager.ts +726 -0
- package/src/index.ts +50 -0
- package/src/message-store.ts +479 -0
- package/src/strategies/autobiographical.ts +1355 -0
- package/src/strategies/index.ts +2 -0
- package/src/strategies/knowledge.ts +336 -0
- package/src/strategies/passthrough.ts +98 -0
- package/src/types/context.ts +119 -0
- package/src/types/index.ts +42 -0
- package/src/types/message.ts +140 -0
- package/src/types/strategy.ts +282 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import type { JsStore } from '@animalabs/chronicle';
|
|
2
|
+
import type { ContentBlock } from '@animalabs/membrane';
|
|
3
|
+
import type {
|
|
4
|
+
MessageId,
|
|
5
|
+
ContextEntry,
|
|
6
|
+
ContextEntryInternal,
|
|
7
|
+
SourceRelation,
|
|
8
|
+
ContextLogView,
|
|
9
|
+
} from './types/index.js';
|
|
10
|
+
import { BlobManager } from './blob-manager.js';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_CONTEXT_STATE_ID = 'context';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wrapper around Chronicle append_log state for context log storage.
|
|
16
|
+
* The context log is a materialized, editable working set derived from the message store.
|
|
17
|
+
*
|
|
18
|
+
* Supports namespacing for multi-agent scenarios where each agent has its own context log
|
|
19
|
+
* but shares the same message store.
|
|
20
|
+
*/
|
|
21
|
+
export class ContextLog {
|
|
22
|
+
private blobManager: BlobManager;
|
|
23
|
+
private sourceToIndices: Map<MessageId, Set<number>> = new Map();
|
|
24
|
+
private tokenEstimator: (text: string) => number;
|
|
25
|
+
private stateId: string;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private store: JsStore,
|
|
29
|
+
options: {
|
|
30
|
+
estimator?: (text: string) => number;
|
|
31
|
+
/** Namespace for multi-agent support. Creates state ID: `{namespace}/context` */
|
|
32
|
+
namespace?: string;
|
|
33
|
+
} = {}
|
|
34
|
+
) {
|
|
35
|
+
this.stateId = options.namespace
|
|
36
|
+
? `${options.namespace}/context`
|
|
37
|
+
: DEFAULT_CONTEXT_STATE_ID;
|
|
38
|
+
this.blobManager = new BlobManager(store);
|
|
39
|
+
this.tokenEstimator = options.estimator ?? defaultTokenEstimator;
|
|
40
|
+
this.rebuildSourceIndex();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register the context log state in Chronicle.
|
|
45
|
+
* Should be called once when setting up the store.
|
|
46
|
+
*
|
|
47
|
+
* @param store The Chronicle store
|
|
48
|
+
* @param namespace Optional namespace for multi-agent support
|
|
49
|
+
*/
|
|
50
|
+
static register(store: JsStore, namespace?: string): void {
|
|
51
|
+
const stateId = namespace ? `${namespace}/context` : DEFAULT_CONTEXT_STATE_ID;
|
|
52
|
+
store.registerState({
|
|
53
|
+
id: stateId,
|
|
54
|
+
strategy: 'append_log',
|
|
55
|
+
deltaSnapshotEvery: 50,
|
|
56
|
+
fullSnapshotEvery: 10,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private rebuildSourceIndex(): void {
|
|
61
|
+
this.sourceToIndices.clear();
|
|
62
|
+
const entries = this.getAllInternal();
|
|
63
|
+
for (let i = 0; i < entries.length; i++) {
|
|
64
|
+
const entry = entries[i];
|
|
65
|
+
if (entry.sourceMessageId) {
|
|
66
|
+
const set = this.sourceToIndices.get(entry.sourceMessageId) ?? new Set();
|
|
67
|
+
set.add(i);
|
|
68
|
+
this.sourceToIndices.set(entry.sourceMessageId, set);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Append a new entry to the context log.
|
|
75
|
+
*/
|
|
76
|
+
append(
|
|
77
|
+
participant: string,
|
|
78
|
+
content: ContentBlock[],
|
|
79
|
+
sourceMessageId?: MessageId,
|
|
80
|
+
sourceRelation?: SourceRelation,
|
|
81
|
+
cacheMarker?: boolean
|
|
82
|
+
): ContextEntry {
|
|
83
|
+
const storedContent = this.blobManager.extractBlobs(content);
|
|
84
|
+
const index = this.length();
|
|
85
|
+
|
|
86
|
+
const internal: ContextEntryInternal = {
|
|
87
|
+
index,
|
|
88
|
+
sourceMessageId,
|
|
89
|
+
sourceRelation,
|
|
90
|
+
participant,
|
|
91
|
+
content: storedContent,
|
|
92
|
+
cacheMarker,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.store.appendToStateJson(this.stateId, internal);
|
|
96
|
+
|
|
97
|
+
// Update source index
|
|
98
|
+
if (sourceMessageId) {
|
|
99
|
+
const set = this.sourceToIndices.get(sourceMessageId) ?? new Set();
|
|
100
|
+
set.add(index);
|
|
101
|
+
this.sourceToIndices.set(sourceMessageId, set);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
index,
|
|
106
|
+
sourceMessageId,
|
|
107
|
+
sourceRelation,
|
|
108
|
+
participant,
|
|
109
|
+
content,
|
|
110
|
+
cacheMarker,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Edit an entry's content at a specific index.
|
|
116
|
+
*/
|
|
117
|
+
edit(index: number, content: ContentBlock[]): void {
|
|
118
|
+
const internal = this.getInternal(index);
|
|
119
|
+
if (!internal) {
|
|
120
|
+
throw new Error(`Context entry not found at index: ${index}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const storedContent = this.blobManager.extractBlobs(content);
|
|
124
|
+
const updated: ContextEntryInternal = {
|
|
125
|
+
...internal,
|
|
126
|
+
content: storedContent,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
this.store.editStateItem(this.stateId, index, Buffer.from(JSON.stringify(updated)));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Remove an entry at a specific index.
|
|
134
|
+
*/
|
|
135
|
+
remove(index: number): void {
|
|
136
|
+
const internal = this.getInternal(index);
|
|
137
|
+
if (!internal) {
|
|
138
|
+
throw new Error(`Context entry not found at index: ${index}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.store.redactStateItems(this.stateId, index, index + 1);
|
|
142
|
+
this.rebuildSourceIndex();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Remove a range of entries.
|
|
147
|
+
*/
|
|
148
|
+
removeRange(start: number, end: number): void {
|
|
149
|
+
this.store.redactStateItems(this.stateId, start, end);
|
|
150
|
+
this.rebuildSourceIndex();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Replace the entire context log with new entries.
|
|
155
|
+
* Useful for strategies that rebuild the context.
|
|
156
|
+
*/
|
|
157
|
+
replaceAll(entries: Array<{
|
|
158
|
+
participant: string;
|
|
159
|
+
content: ContentBlock[];
|
|
160
|
+
sourceMessageId?: MessageId;
|
|
161
|
+
sourceRelation?: SourceRelation;
|
|
162
|
+
cacheMarker?: boolean;
|
|
163
|
+
}>): ContextEntry[] {
|
|
164
|
+
// Clear existing entries
|
|
165
|
+
const len = this.length();
|
|
166
|
+
if (len > 0) {
|
|
167
|
+
this.store.redactStateItems(this.stateId, 0, len);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Add new entries
|
|
171
|
+
const result: ContextEntry[] = [];
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
result.push(this.append(
|
|
174
|
+
entry.participant,
|
|
175
|
+
entry.content,
|
|
176
|
+
entry.sourceMessageId,
|
|
177
|
+
entry.sourceRelation,
|
|
178
|
+
entry.cacheMarker
|
|
179
|
+
));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get an entry by index.
|
|
187
|
+
*/
|
|
188
|
+
get(index: number): ContextEntry | null {
|
|
189
|
+
const internal = this.getInternal(index);
|
|
190
|
+
if (!internal) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return this.internalToEntry(internal);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get all entries.
|
|
198
|
+
*/
|
|
199
|
+
getAll(): ContextEntry[] {
|
|
200
|
+
return this.getAllInternal().map((internal) => this.internalToEntry(internal));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get entries from a specific index.
|
|
205
|
+
*/
|
|
206
|
+
getFrom(index: number): ContextEntry[] {
|
|
207
|
+
return this.getAll().slice(index);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get the last N entries.
|
|
212
|
+
*/
|
|
213
|
+
getTail(count: number): ContextEntry[] {
|
|
214
|
+
const all = this.getAll();
|
|
215
|
+
return all.slice(Math.max(0, all.length - count));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get the total number of entries.
|
|
220
|
+
*/
|
|
221
|
+
length(): number {
|
|
222
|
+
return this.store.getStateLen(this.stateId) ?? 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Find all entries that reference a specific source message.
|
|
227
|
+
*/
|
|
228
|
+
findBySource(sourceMessageId: MessageId): ContextEntry[] {
|
|
229
|
+
const indices = this.sourceToIndices.get(sourceMessageId);
|
|
230
|
+
if (!indices) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const entries: ContextEntry[] = [];
|
|
235
|
+
for (const index of indices) {
|
|
236
|
+
const entry = this.get(index);
|
|
237
|
+
if (entry) {
|
|
238
|
+
entries.push(entry);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return entries;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get the source relation for entries referencing a message.
|
|
246
|
+
*/
|
|
247
|
+
getSourceRelation(sourceMessageId: MessageId): Map<number, SourceRelation | undefined> {
|
|
248
|
+
const result = new Map<number, SourceRelation | undefined>();
|
|
249
|
+
const indices = this.sourceToIndices.get(sourceMessageId);
|
|
250
|
+
if (!indices) {
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const index of indices) {
|
|
255
|
+
const entry = this.get(index);
|
|
256
|
+
if (entry) {
|
|
257
|
+
result.set(index, entry.sourceRelation);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Estimate tokens for an entry.
|
|
265
|
+
*/
|
|
266
|
+
estimateTokens(entry: ContextEntry): number {
|
|
267
|
+
let tokens = 0;
|
|
268
|
+
for (const block of entry.content) {
|
|
269
|
+
tokens += this.estimateBlockTokens(block);
|
|
270
|
+
}
|
|
271
|
+
return tokens;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private estimateBlockTokens(block: ContentBlock): number {
|
|
275
|
+
switch (block.type) {
|
|
276
|
+
case 'text':
|
|
277
|
+
return this.tokenEstimator(block.text);
|
|
278
|
+
case 'thinking':
|
|
279
|
+
return this.tokenEstimator(block.thinking);
|
|
280
|
+
case 'tool_use':
|
|
281
|
+
return this.tokenEstimator(JSON.stringify(block.input)) + 20;
|
|
282
|
+
case 'tool_result':
|
|
283
|
+
if (!block.content) return 0;
|
|
284
|
+
if (typeof block.content === 'string') {
|
|
285
|
+
return this.tokenEstimator(block.content);
|
|
286
|
+
}
|
|
287
|
+
return block.content.reduce((sum, b) => sum + this.estimateBlockTokens(b), 0);
|
|
288
|
+
case 'image':
|
|
289
|
+
return block.tokenEstimate ?? 1000;
|
|
290
|
+
case 'document':
|
|
291
|
+
case 'audio':
|
|
292
|
+
case 'video':
|
|
293
|
+
return 1000;
|
|
294
|
+
default:
|
|
295
|
+
return 0;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Create a read-only view of the log for strategies.
|
|
301
|
+
*/
|
|
302
|
+
createView(): ContextLogView {
|
|
303
|
+
return {
|
|
304
|
+
getAll: () => this.getAll(),
|
|
305
|
+
getFrom: (index) => this.getFrom(index),
|
|
306
|
+
getTail: (count) => this.getTail(count),
|
|
307
|
+
length: () => this.length(),
|
|
308
|
+
estimateTokens: (entry) => this.estimateTokens(entry),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private getAllInternal(): ContextEntryInternal[] {
|
|
313
|
+
const state = this.store.getStateJson(this.stateId);
|
|
314
|
+
if (!state || !Array.isArray(state)) {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
return state as ContextEntryInternal[];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private getInternal(index: number): ContextEntryInternal | null {
|
|
321
|
+
const all = this.getAllInternal();
|
|
322
|
+
return all[index] ?? null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private internalToEntry(internal: ContextEntryInternal): ContextEntry {
|
|
326
|
+
return {
|
|
327
|
+
index: internal.index,
|
|
328
|
+
sourceMessageId: internal.sourceMessageId,
|
|
329
|
+
sourceRelation: internal.sourceRelation,
|
|
330
|
+
participant: internal.participant,
|
|
331
|
+
content: this.blobManager.resolveBlobs(internal.content),
|
|
332
|
+
cacheMarker: internal.cacheMarker,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Default token estimator: chars / 4
|
|
339
|
+
*/
|
|
340
|
+
function defaultTokenEstimator(text: string): number {
|
|
341
|
+
return Math.ceil(text.length / 4);
|
|
342
|
+
}
|