sliding-context 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +245 -0
  2. package/dist/__tests__/context.test.d.ts +2 -0
  3. package/dist/__tests__/context.test.d.ts.map +1 -0
  4. package/dist/__tests__/context.test.js +441 -0
  5. package/dist/__tests__/context.test.js.map +1 -0
  6. package/dist/__tests__/fixtures/messages.d.ts +9 -0
  7. package/dist/__tests__/fixtures/messages.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/messages.js +44 -0
  9. package/dist/__tests__/fixtures/messages.js.map +1 -0
  10. package/dist/__tests__/token-counter.test.d.ts +2 -0
  11. package/dist/__tests__/token-counter.test.d.ts.map +1 -0
  12. package/dist/__tests__/token-counter.test.js +123 -0
  13. package/dist/__tests__/token-counter.test.js.map +1 -0
  14. package/dist/__tests__/types.test.d.ts +2 -0
  15. package/dist/__tests__/types.test.d.ts.map +1 -0
  16. package/dist/__tests__/types.test.js +92 -0
  17. package/dist/__tests__/types.test.js.map +1 -0
  18. package/dist/budget.d.ts +16 -0
  19. package/dist/budget.d.ts.map +1 -0
  20. package/dist/budget.js +31 -0
  21. package/dist/budget.js.map +1 -0
  22. package/dist/context.d.ts +16 -0
  23. package/dist/context.d.ts.map +1 -0
  24. package/dist/context.js +245 -0
  25. package/dist/context.js.map +1 -0
  26. package/dist/eviction.d.ts +14 -0
  27. package/dist/eviction.d.ts.map +1 -0
  28. package/dist/eviction.js +78 -0
  29. package/dist/eviction.js.map +1 -0
  30. package/dist/index.d.ts +9 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +23 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/prompt.d.ts +6 -0
  35. package/dist/prompt.d.ts.map +1 -0
  36. package/dist/prompt.js +28 -0
  37. package/dist/prompt.js.map +1 -0
  38. package/dist/serialization.d.ts +12 -0
  39. package/dist/serialization.d.ts.map +1 -0
  40. package/dist/serialization.js +33 -0
  41. package/dist/serialization.js.map +1 -0
  42. package/dist/summarization.d.ts +13 -0
  43. package/dist/summarization.d.ts.map +1 -0
  44. package/dist/summarization.js +23 -0
  45. package/dist/summarization.js.map +1 -0
  46. package/dist/token-counter.d.ts +7 -0
  47. package/dist/token-counter.d.ts.map +1 -0
  48. package/dist/token-counter.js +41 -0
  49. package/dist/token-counter.js.map +1 -0
  50. package/dist/types.d.ts +72 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +3 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +33 -0
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evictMessages = evictMessages;
4
+ const token_counter_1 = require("./token-counter");
5
+ /**
6
+ * Build a set of tool_call_id values that are "anchored" to a given assistant
7
+ * message — i.e. the message contains tool_calls whose results we still need.
8
+ */
9
+ function toolCallIds(message) {
10
+ const ids = new Set();
11
+ if (message.tool_calls) {
12
+ for (const tc of message.tool_calls) {
13
+ ids.add(tc.id);
14
+ }
15
+ }
16
+ return ids;
17
+ }
18
+ /**
19
+ * Compute total tokens for a list of messages.
20
+ */
21
+ function totalTokens(messages, tokenCounter, overhead) {
22
+ return messages.reduce((sum, m) => sum + (0, token_counter_1.countMessageTokens)(m, tokenCounter, overhead), 0);
23
+ }
24
+ /**
25
+ * Evict the oldest messages from `messages` until `totalTokens` fits within
26
+ * `targetTokens`. Tool-call pairs are kept atomic: an assistant message with
27
+ * `tool_calls` is always evicted together with all its paired `tool` result
28
+ * messages.
29
+ *
30
+ * Returns `{ evicted, remaining }`.
31
+ */
32
+ function evictMessages(messages, targetTokens, tokenCounter, overhead) {
33
+ // Nothing to evict.
34
+ if (totalTokens(messages, tokenCounter, overhead) <= targetTokens) {
35
+ return { evicted: [], remaining: messages.slice() };
36
+ }
37
+ const remaining = messages.slice();
38
+ const evicted = [];
39
+ while (remaining.length > 0 &&
40
+ totalTokens(remaining, tokenCounter, overhead) > targetTokens) {
41
+ // Find the first non-system message to evict.
42
+ const idx = remaining.findIndex((m) => m.role !== 'system');
43
+ if (idx === -1) {
44
+ // Only system messages left — cannot evict further.
45
+ break;
46
+ }
47
+ const candidate = remaining[idx];
48
+ // Collect the group to evict atomically.
49
+ const group = [candidate];
50
+ if (candidate.tool_calls && candidate.tool_calls.length > 0) {
51
+ // This is a tool-call message. Find all paired tool-result messages that
52
+ // immediately follow it (they may be interspersed in the remaining list).
53
+ const pendingIds = toolCallIds(candidate);
54
+ let searchIdx = idx + 1;
55
+ while (pendingIds.size > 0 && searchIdx < remaining.length) {
56
+ const next = remaining[searchIdx];
57
+ if (next.role === 'tool' && next.tool_call_id && pendingIds.has(next.tool_call_id)) {
58
+ group.push(next);
59
+ pendingIds.delete(next.tool_call_id);
60
+ }
61
+ searchIdx++;
62
+ }
63
+ }
64
+ // Remove the group from remaining.
65
+ const groupSet = new Set(group);
66
+ const newRemaining = remaining.filter((m) => !groupSet.has(m));
67
+ // Safety: never leave remaining empty if we started with messages.
68
+ // If removing the group would empty everything, stop.
69
+ if (newRemaining.length === 0 && messages.length > group.length) {
70
+ break;
71
+ }
72
+ // Commit the eviction.
73
+ evicted.push(...group);
74
+ remaining.splice(0, remaining.length, ...newRemaining);
75
+ }
76
+ return { evicted, remaining };
77
+ }
78
+ //# sourceMappingURL=eviction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eviction.js","sourceRoot":"","sources":["../src/eviction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type { Message, ToolCall, TokenCounter, Summarizer, SummarizationStrategy, SummaryRole, EventHooks, SlidingContextOptions, ContextState, SlidingContext } from './types';
2
+ export { approximateTokenCounter, countMessageTokens, DEFAULT_MESSAGE_OVERHEAD } from './token-counter';
3
+ export { createSlidingContext, serializeContext, restoreSlidingContext } from './context';
4
+ export { evictMessages } from './eviction';
5
+ export { allocateBudget } from './budget';
6
+ export { runSummarizer } from './summarization';
7
+ export { serialize, deserialize } from './serialization';
8
+ export { defaultSummarizationPrompt } from './prompt';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,qBAAqB,EAAE,WAAW,EAAE,UAAU,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAChL,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AACxG,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultSummarizationPrompt = exports.deserialize = exports.serialize = exports.runSummarizer = exports.allocateBudget = exports.evictMessages = exports.restoreSlidingContext = exports.serializeContext = exports.createSlidingContext = exports.DEFAULT_MESSAGE_OVERHEAD = exports.countMessageTokens = exports.approximateTokenCounter = void 0;
4
+ var token_counter_1 = require("./token-counter");
5
+ Object.defineProperty(exports, "approximateTokenCounter", { enumerable: true, get: function () { return token_counter_1.approximateTokenCounter; } });
6
+ Object.defineProperty(exports, "countMessageTokens", { enumerable: true, get: function () { return token_counter_1.countMessageTokens; } });
7
+ Object.defineProperty(exports, "DEFAULT_MESSAGE_OVERHEAD", { enumerable: true, get: function () { return token_counter_1.DEFAULT_MESSAGE_OVERHEAD; } });
8
+ var context_1 = require("./context");
9
+ Object.defineProperty(exports, "createSlidingContext", { enumerable: true, get: function () { return context_1.createSlidingContext; } });
10
+ Object.defineProperty(exports, "serializeContext", { enumerable: true, get: function () { return context_1.serializeContext; } });
11
+ Object.defineProperty(exports, "restoreSlidingContext", { enumerable: true, get: function () { return context_1.restoreSlidingContext; } });
12
+ var eviction_1 = require("./eviction");
13
+ Object.defineProperty(exports, "evictMessages", { enumerable: true, get: function () { return eviction_1.evictMessages; } });
14
+ var budget_1 = require("./budget");
15
+ Object.defineProperty(exports, "allocateBudget", { enumerable: true, get: function () { return budget_1.allocateBudget; } });
16
+ var summarization_1 = require("./summarization");
17
+ Object.defineProperty(exports, "runSummarizer", { enumerable: true, get: function () { return summarization_1.runSummarizer; } });
18
+ var serialization_1 = require("./serialization");
19
+ Object.defineProperty(exports, "serialize", { enumerable: true, get: function () { return serialization_1.serialize; } });
20
+ Object.defineProperty(exports, "deserialize", { enumerable: true, get: function () { return serialization_1.deserialize; } });
21
+ var prompt_1 = require("./prompt");
22
+ Object.defineProperty(exports, "defaultSummarizationPrompt", { enumerable: true, get: function () { return prompt_1.defaultSummarizationPrompt; } });
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,iDAAwG;AAA/F,wHAAA,uBAAuB,OAAA;AAAE,mHAAA,kBAAkB,OAAA;AAAE,yHAAA,wBAAwB,OAAA;AAC9E,qCAA0F;AAAjF,+GAAA,oBAAoB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAAE,gHAAA,qBAAqB,OAAA;AACtE,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AACtB,mCAA0C;AAAjC,wGAAA,cAAc,OAAA;AACvB,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,iDAAyD;AAAhD,0GAAA,SAAS,OAAA;AAAE,4GAAA,WAAW,OAAA;AAC/B,mCAAsD;AAA7C,oHAAA,0BAA0B,OAAA"}
@@ -0,0 +1,6 @@
1
+ import type { Message } from './types';
2
+ /**
3
+ * Returns a summarization prompt for the given messages.
4
+ */
5
+ export declare function defaultSummarizationPrompt(messages: Message[]): string;
6
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAoBvC;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAGtE"}
package/dist/prompt.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultSummarizationPrompt = defaultSummarizationPrompt;
4
+ /**
5
+ * Formats a message array into a plain-text block for the summarization prompt.
6
+ */
7
+ function formatMessages(messages) {
8
+ return messages
9
+ .map((m) => {
10
+ const role = m.role.toUpperCase();
11
+ if (m.tool_calls && m.tool_calls.length > 0) {
12
+ return `${role}: [tool_calls] ${JSON.stringify(m.tool_calls)}`;
13
+ }
14
+ if (m.role === 'tool') {
15
+ return `TOOL(${m.tool_call_id ?? ''}): ${m.content}`;
16
+ }
17
+ return `${role}: ${m.content}`;
18
+ })
19
+ .join('\n');
20
+ }
21
+ /**
22
+ * Returns a summarization prompt for the given messages.
23
+ */
24
+ function defaultSummarizationPrompt(messages) {
25
+ const formatted = formatMessages(messages);
26
+ return `Summarize the following conversation concisely:\n\n${formatted}`;
27
+ }
28
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":";;AAuBA,gEAGC;AAxBD;;GAEG;AACH,SAAS,cAAc,CAAC,QAAmB;IACzC,OAAO,QAAQ;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO,GAAG,IAAI,kBAAkB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACtB,OAAO,QAAQ,CAAC,CAAC,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QACvD,CAAC;QACD,OAAO,GAAG,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAgB,0BAA0B,CAAC,QAAmB;IAC5D,MAAM,SAAS,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC3C,OAAO,sDAAsD,SAAS,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ContextState } from './types';
2
+ /**
3
+ * Serialize context state to a JSON string.
4
+ * Always includes `version: 1`.
5
+ */
6
+ export declare function serialize(state: ContextState): string;
7
+ /**
8
+ * Deserialize a JSON string back to ContextState.
9
+ * Throws if the version field does not match the expected schema version.
10
+ */
11
+ export declare function deserialize(data: string): ContextState;
12
+ //# sourceMappingURL=serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAErD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAoBtD"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serialize = serialize;
4
+ exports.deserialize = deserialize;
5
+ const CURRENT_VERSION = 1;
6
+ /**
7
+ * Serialize context state to a JSON string.
8
+ * Always includes `version: 1`.
9
+ */
10
+ function serialize(state) {
11
+ return JSON.stringify({ ...state, version: CURRENT_VERSION });
12
+ }
13
+ /**
14
+ * Deserialize a JSON string back to ContextState.
15
+ * Throws if the version field does not match the expected schema version.
16
+ */
17
+ function deserialize(data) {
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(data);
21
+ }
22
+ catch (err) {
23
+ throw new Error(`sliding-context: failed to parse serialized state: ${String(err)}`);
24
+ }
25
+ if (typeof parsed !== 'object' ||
26
+ parsed === null ||
27
+ parsed['version'] !== CURRENT_VERSION) {
28
+ throw new Error(`sliding-context: schema mismatch — expected version ${CURRENT_VERSION}, ` +
29
+ `got ${String(parsed?.['version'])}`);
30
+ }
31
+ return parsed;
32
+ }
33
+ //# sourceMappingURL=serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.js","sourceRoot":"","sources":["../src/serialization.ts"],"names":[],"mappings":";;AAQA,8BAEC;AAMD,kCAoBC;AAlCD,MAAM,eAAe,GAAG,CAAU,CAAC;AAEnC;;;GAGG;AACH,SAAgB,SAAS,CAAC,KAAmB;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,SAAgB,WAAW,CAAC,IAAY;IACtC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,sDAAsD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,IACE,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACd,MAAkC,CAAC,SAAS,CAAC,KAAK,eAAe,EAClE,CAAC;QACD,MAAM,IAAI,KAAK,CACb,uDAAuD,eAAe,IAAI;YACxE,OAAO,MAAM,CAAE,MAAkC,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,CACpE,CAAC;IACJ,CAAC;IAED,OAAO,MAAsB,CAAC;AAChC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Message, Summarizer } from './types';
2
+ /**
3
+ * Invoke the summarizer with `messages` and the optional `existingSummary`.
4
+ * Returns the summary string on success, or `null` if the summarizer throws.
5
+ *
6
+ * The `prompt` function is intentionally not used here because the Summarizer
7
+ * type already receives the raw messages and existing summary — the caller
8
+ * (typically the LLM wrapper) is responsible for prompt construction. The
9
+ * `prompt` parameter is retained for callers that want to inspect or log the
10
+ * prompt used.
11
+ */
12
+ export declare function runSummarizer(messages: Message[], summarizer: Summarizer, existingSummary: string | undefined): Promise<string | null>;
13
+ //# sourceMappingURL=summarization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarization.d.ts","sourceRoot":"","sources":["../src/summarization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEnD;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSummarizer = runSummarizer;
4
+ /**
5
+ * Invoke the summarizer with `messages` and the optional `existingSummary`.
6
+ * Returns the summary string on success, or `null` if the summarizer throws.
7
+ *
8
+ * The `prompt` function is intentionally not used here because the Summarizer
9
+ * type already receives the raw messages and existing summary — the caller
10
+ * (typically the LLM wrapper) is responsible for prompt construction. The
11
+ * `prompt` parameter is retained for callers that want to inspect or log the
12
+ * prompt used.
13
+ */
14
+ async function runSummarizer(messages, summarizer, existingSummary) {
15
+ try {
16
+ const result = await summarizer(messages, existingSummary);
17
+ return result;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ //# sourceMappingURL=summarization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarization.js","sourceRoot":"","sources":["../src/summarization.ts"],"names":[],"mappings":";;AAYA,sCAWC;AArBD;;;;;;;;;GASG;AACI,KAAK,UAAU,aAAa,CACjC,QAAmB,EACnB,UAAsB,EACtB,eAAmC;IAEnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QAC3D,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Message, TokenCounter } from './types';
2
+ export declare const DEFAULT_MESSAGE_OVERHEAD = 4;
3
+ /** Default token counter: Math.ceil(text.length / 4). Returns 0 for empty/null. */
4
+ export declare function approximateTokenCounter(text: string): number;
5
+ /** Count tokens for a single message including per-message overhead. */
6
+ export declare function countMessageTokens(message: Message, tokenCounter: TokenCounter, messageOverhead: number): number;
7
+ //# sourceMappingURL=token-counter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-counter.d.ts","sourceRoot":"","sources":["../src/token-counter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIrD,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,YAAY,EAC1B,eAAe,EAAE,MAAM,GACtB,MAAM,CA2BR"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_MESSAGE_OVERHEAD = void 0;
4
+ exports.approximateTokenCounter = approximateTokenCounter;
5
+ exports.countMessageTokens = countMessageTokens;
6
+ const IMAGE_TOKEN_COST = 85;
7
+ exports.DEFAULT_MESSAGE_OVERHEAD = 4;
8
+ /** Default token counter: Math.ceil(text.length / 4). Returns 0 for empty/null. */
9
+ function approximateTokenCounter(text) {
10
+ if (!text)
11
+ return 0;
12
+ return Math.ceil(text.length / 4);
13
+ }
14
+ /** Count tokens for a single message including per-message overhead. */
15
+ function countMessageTokens(message, tokenCounter, messageOverhead) {
16
+ let contentTokens = 0;
17
+ const content = message.content;
18
+ if (Array.isArray(content)) {
19
+ for (const part of content) {
20
+ if (part.type === 'text' || part.type === undefined) {
21
+ contentTokens += tokenCounter(part.text ?? '');
22
+ }
23
+ else {
24
+ contentTokens += IMAGE_TOKEN_COST;
25
+ }
26
+ }
27
+ }
28
+ else if (typeof content === 'string' && content) {
29
+ contentTokens = tokenCounter(content);
30
+ }
31
+ let toolTokens = 0;
32
+ if (message.tool_calls && message.tool_calls.length > 0) {
33
+ toolTokens = tokenCounter(JSON.stringify(message.tool_calls));
34
+ }
35
+ let toolIdTokens = 0;
36
+ if (message.tool_call_id) {
37
+ toolIdTokens = tokenCounter(message.tool_call_id);
38
+ }
39
+ return contentTokens + toolTokens + toolIdTokens + messageOverhead;
40
+ }
41
+ //# sourceMappingURL=token-counter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-counter.js","sourceRoot":"","sources":["../src/token-counter.ts"],"names":[],"mappings":";;;AAOA,0DAGC;AAGD,gDA+BC;AA1CD,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEf,QAAA,wBAAwB,GAAG,CAAC,CAAC;AAE1C,mFAAmF;AACnF,SAAgB,uBAAuB,CAAC,IAAY;IAClD,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,wEAAwE;AACxE,SAAgB,kBAAkB,CAChC,OAAgB,EAChB,YAA0B,EAC1B,eAAuB;IAEvB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAkB,CAAC;IAE3C,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,OAAkD,EAAE,CAAC;YACtE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACpD,aAAa,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;iBAAM,CAAC;gBACN,aAAa,IAAI,gBAAgB,CAAC;YACpC,CAAC;QACH,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,EAAE,CAAC;QAClD,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;QACzB,YAAY,GAAG,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,aAAa,GAAG,UAAU,GAAG,YAAY,GAAG,eAAe,CAAC;AACrE,CAAC"}
@@ -0,0 +1,72 @@
1
+ export interface ToolCall {
2
+ id: string;
3
+ type: 'function';
4
+ function: {
5
+ name: string;
6
+ arguments: string;
7
+ };
8
+ }
9
+ export interface Message {
10
+ role: 'system' | 'user' | 'assistant' | 'tool';
11
+ content: string;
12
+ tool_calls?: ToolCall[];
13
+ tool_call_id?: string;
14
+ name?: string;
15
+ }
16
+ export type TokenCounter = (text: string) => number;
17
+ export type Summarizer = (messages: Message[], existingSummary?: string) => Promise<string>;
18
+ export type SummarizationStrategy = 'incremental' | 'rolling' | 'anchored';
19
+ export type SummaryRole = 'system' | 'user';
20
+ export interface EventHooks {
21
+ onEvict?: (messages: Message[], reason: string) => void;
22
+ onSummarize?: (inputMessages: Message[], existingSummary: string | undefined, newSummary: string, durationMs: number) => void;
23
+ onBudgetExceeded?: (totalTokens: number, budget: number) => void;
24
+ onSummaryCompressed?: (oldSummary: string, newSummary: string) => void;
25
+ }
26
+ export interface SlidingContextOptions {
27
+ tokenBudget: number;
28
+ systemPrompt?: string;
29
+ summarizer?: Summarizer;
30
+ strategy?: SummarizationStrategy;
31
+ maxSummaryTokens?: number;
32
+ minRecentTokens?: number;
33
+ summarizeThresholdTokens?: number;
34
+ summarizeThresholdMessages?: number;
35
+ tokenCounter?: TokenCounter;
36
+ messageOverhead?: number;
37
+ summaryRole?: SummaryRole;
38
+ maxSummaryRounds?: number;
39
+ anchor?: Message[];
40
+ maxAnchorTokens?: number;
41
+ hooks?: EventHooks;
42
+ }
43
+ export interface ContextState {
44
+ options: Omit<SlidingContextOptions, 'summarizer' | 'tokenCounter' | 'hooks'>;
45
+ messages: Message[];
46
+ summary: string | undefined;
47
+ anchor: Message[];
48
+ pendingBuffer: Message[];
49
+ summaryRounds: number;
50
+ tokenCounts: Record<string, number>;
51
+ version: 1;
52
+ }
53
+ export interface SlidingContext {
54
+ addMessage(message: Message): Promise<void>;
55
+ getMessages(): Message[];
56
+ getSummary(): string | undefined;
57
+ getTokenCount(): number;
58
+ getTokenBreakdown(): {
59
+ system: number;
60
+ anchor: number;
61
+ summary: number;
62
+ recent: number;
63
+ total: number;
64
+ };
65
+ getRecentMessageCount(): number;
66
+ getTotalMessageCount(): number;
67
+ setAnchor(messages: Message[]): void;
68
+ setTokenBudget(budget: number): void;
69
+ clear(): void;
70
+ serialize(): ContextState;
71
+ }
72
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "sliding-context",
3
+ "version": "0.3.0",
4
+ "description": "Provider-agnostic sliding window context manager for LLMs",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "vitest run",
13
+ "lint": "eslint src/",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.5.0",
27
+ "@typescript-eslint/eslint-plugin": "^8.57.1",
28
+ "@typescript-eslint/parser": "^8.57.1",
29
+ "eslint": "^10.1.0",
30
+ "typescript": "^5.9.3",
31
+ "vitest": "^4.1.0"
32
+ }
33
+ }