byterover-cli 2.0.0 → 2.1.1

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 (183) hide show
  1. package/README.md +6 -81
  2. package/dist/agent/core/domain/llm/index.d.ts +1 -1
  3. package/dist/agent/core/domain/llm/index.js +1 -1
  4. package/dist/agent/core/domain/llm/registry.d.ts +8 -0
  5. package/dist/agent/core/domain/llm/registry.js +34 -0
  6. package/dist/agent/core/domain/sandbox/types.d.ts +2 -0
  7. package/dist/agent/core/domain/tools/constants.d.ts +3 -0
  8. package/dist/agent/core/domain/tools/constants.js +3 -0
  9. package/dist/agent/core/interfaces/cipher-services.d.ts +2 -4
  10. package/dist/agent/core/interfaces/i-cipher-agent.d.ts +9 -1
  11. package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
  12. package/dist/agent/core/interfaces/i-tool-provider.d.ts +10 -0
  13. package/dist/agent/core/interfaces/i-tool-scheduler.d.ts +9 -0
  14. package/dist/agent/infra/agent/agent-schemas.d.ts +0 -9
  15. package/dist/agent/infra/agent/agent-schemas.js +0 -3
  16. package/dist/agent/infra/agent/cipher-agent.d.ts +25 -1
  17. package/dist/agent/infra/agent/cipher-agent.js +138 -11
  18. package/dist/agent/infra/agent/provider-update-config.d.ts +0 -2
  19. package/dist/agent/infra/agent/service-initializer.d.ts +2 -6
  20. package/dist/agent/infra/agent/service-initializer.js +45 -38
  21. package/dist/agent/infra/blob/blob-storage-factory.d.ts +2 -2
  22. package/dist/agent/infra/blob/blob-storage-factory.js +4 -4
  23. package/dist/agent/infra/blob/file-blob-storage.d.ts +96 -0
  24. package/dist/agent/infra/blob/file-blob-storage.js +454 -0
  25. package/dist/agent/infra/blob/index.d.ts +2 -3
  26. package/dist/agent/infra/blob/index.js +4 -6
  27. package/dist/agent/infra/llm/agent-llm-service.d.ts +3 -0
  28. package/dist/agent/infra/llm/agent-llm-service.js +34 -52
  29. package/dist/agent/infra/llm/context/compression/compression-helpers.d.ts +35 -0
  30. package/dist/agent/infra/llm/context/compression/compression-helpers.js +124 -0
  31. package/dist/agent/infra/llm/context/compression/escalated-compression.d.ts +62 -0
  32. package/dist/agent/infra/llm/context/compression/escalated-compression.js +144 -0
  33. package/dist/agent/infra/llm/context/compression/index.d.ts +3 -0
  34. package/dist/agent/infra/llm/context/compression/index.js +3 -0
  35. package/dist/agent/infra/llm/context/compression/reactive-overflow.d.ts +0 -27
  36. package/dist/agent/infra/llm/context/compression/reactive-overflow.js +5 -122
  37. package/dist/agent/infra/llm/context/context-manager.d.ts +20 -1
  38. package/dist/agent/infra/llm/context/context-manager.js +37 -7
  39. package/dist/agent/infra/llm/providers/index.js +0 -2
  40. package/dist/agent/infra/llm/providers/types.d.ts +1 -5
  41. package/dist/agent/infra/map/agentic-map-service.d.ts +97 -0
  42. package/dist/agent/infra/map/agentic-map-service.js +309 -0
  43. package/dist/agent/infra/map/context-tree-store.d.ts +94 -0
  44. package/dist/agent/infra/map/context-tree-store.js +278 -0
  45. package/dist/agent/infra/map/index.d.ts +4 -0
  46. package/dist/agent/infra/map/index.js +4 -0
  47. package/dist/agent/infra/map/llm-map-memory.d.ts +59 -0
  48. package/dist/agent/infra/map/llm-map-memory.js +187 -0
  49. package/dist/agent/infra/map/llm-map-service.d.ts +36 -0
  50. package/dist/agent/infra/map/llm-map-service.js +118 -0
  51. package/dist/agent/infra/map/map-shared.d.ts +140 -0
  52. package/dist/agent/infra/map/map-shared.js +325 -0
  53. package/dist/agent/infra/map/worker-pool.d.ts +45 -0
  54. package/dist/agent/infra/map/worker-pool.js +73 -0
  55. package/dist/agent/infra/sandbox/curation-helpers.d.ts +62 -0
  56. package/dist/agent/infra/sandbox/curation-helpers.js +219 -0
  57. package/dist/agent/infra/sandbox/sandbox-service.d.ts +12 -0
  58. package/dist/agent/infra/sandbox/sandbox-service.js +39 -7
  59. package/dist/agent/infra/sandbox/tools-sdk.d.ts +48 -1
  60. package/dist/agent/infra/sandbox/tools-sdk.js +52 -1
  61. package/dist/agent/infra/session/session-manager.d.ts +8 -1
  62. package/dist/agent/infra/session/session-manager.js +24 -4
  63. package/dist/agent/infra/storage/file-key-storage.d.ts +142 -0
  64. package/dist/agent/infra/storage/file-key-storage.js +572 -0
  65. package/dist/agent/infra/storage/granular-history-storage.d.ts +1 -1
  66. package/dist/agent/infra/storage/granular-history-storage.js +1 -1
  67. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.d.ts +4 -0
  68. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.js +42 -14
  69. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.d.ts +16 -0
  70. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.js +47 -0
  71. package/dist/agent/infra/tools/core-tool-scheduler.js +3 -1
  72. package/dist/agent/infra/tools/implementations/agentic-map-tool.d.ts +35 -0
  73. package/dist/agent/infra/tools/implementations/agentic-map-tool.js +156 -0
  74. package/dist/agent/infra/tools/implementations/code-exec-tool.js +1 -0
  75. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +9 -9
  76. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +18 -0
  77. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +43 -0
  78. package/dist/agent/infra/tools/implementations/llm-map-tool.d.ts +24 -0
  79. package/dist/agent/infra/tools/implementations/llm-map-tool.js +87 -0
  80. package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +28 -1
  81. package/dist/agent/infra/tools/implementations/memory-symbol-tree.js +27 -3
  82. package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -0
  83. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +83 -12
  84. package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
  85. package/dist/agent/infra/tools/tool-manager.js +6 -0
  86. package/dist/agent/infra/tools/tool-provider.d.ts +12 -0
  87. package/dist/agent/infra/tools/tool-provider.js +78 -0
  88. package/dist/agent/infra/tools/tool-registry.d.ts +14 -0
  89. package/dist/agent/infra/tools/tool-registry.js +32 -0
  90. package/dist/agent/resources/prompts/system-prompt.yml +48 -74
  91. package/dist/agent/resources/tools/expand_knowledge.txt +20 -0
  92. package/dist/oclif/commands/curate/index.js +1 -2
  93. package/dist/oclif/commands/main.js +1 -0
  94. package/dist/oclif/commands/providers/connect.d.ts +1 -3
  95. package/dist/oclif/commands/providers/connect.js +7 -29
  96. package/dist/oclif/commands/query.js +1 -2
  97. package/dist/oclif/hooks/init/update-notifier.d.ts +8 -0
  98. package/dist/oclif/hooks/init/update-notifier.js +18 -2
  99. package/dist/server/constants.d.ts +7 -0
  100. package/dist/server/constants.js +8 -0
  101. package/dist/server/core/domain/entities/provider-registry.js +1 -15
  102. package/dist/server/core/domain/knowledge/memory-scoring.js +1 -1
  103. package/dist/server/core/domain/knowledge/summary-types.d.ts +126 -0
  104. package/dist/server/core/domain/knowledge/summary-types.js +7 -0
  105. package/dist/server/core/domain/transport/schemas.d.ts +0 -4
  106. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.d.ts +30 -0
  107. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.js +1 -0
  108. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.d.ts +30 -0
  109. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.js +1 -0
  110. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.d.ts +29 -0
  111. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.js +1 -0
  112. package/dist/server/infra/cogit/context-tree-to-push-context-mapper.js +10 -3
  113. package/dist/server/infra/connectors/skill/skill-connector.d.ts +4 -0
  114. package/dist/server/infra/connectors/skill/skill-connector.js +4 -0
  115. package/dist/server/infra/context-tree/children-hash.d.ts +20 -0
  116. package/dist/server/infra/context-tree/children-hash.js +22 -0
  117. package/dist/server/infra/context-tree/derived-artifact.d.ts +28 -0
  118. package/dist/server/infra/context-tree/derived-artifact.js +48 -0
  119. package/dist/server/infra/context-tree/file-context-tree-archive-service.d.ts +37 -0
  120. package/dist/server/infra/context-tree/file-context-tree-archive-service.js +219 -0
  121. package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +50 -0
  122. package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +278 -0
  123. package/dist/server/infra/context-tree/file-context-tree-merger.js +4 -0
  124. package/dist/server/infra/context-tree/file-context-tree-snapshot-service.js +12 -4
  125. package/dist/server/infra/context-tree/file-context-tree-summary-service.d.ts +44 -0
  126. package/dist/server/infra/context-tree/file-context-tree-summary-service.js +313 -0
  127. package/dist/server/infra/context-tree/file-context-tree-writer-service.js +5 -0
  128. package/dist/server/infra/context-tree/prompts/summary-generation.d.ts +22 -0
  129. package/dist/server/infra/context-tree/prompts/summary-generation.js +45 -0
  130. package/dist/server/infra/context-tree/snapshot-diff.d.ts +19 -0
  131. package/dist/server/infra/context-tree/snapshot-diff.js +39 -0
  132. package/dist/server/infra/context-tree/summary-frontmatter.d.ts +24 -0
  133. package/dist/server/infra/context-tree/summary-frontmatter.js +111 -0
  134. package/dist/server/infra/daemon/agent-process.js +2 -14
  135. package/dist/server/infra/executor/curate-executor.d.ts +1 -0
  136. package/dist/server/infra/executor/curate-executor.js +82 -34
  137. package/dist/server/infra/executor/folder-pack-executor.js +1 -1
  138. package/dist/server/infra/executor/pre-compaction/compaction-escalation.d.ts +6 -0
  139. package/dist/server/infra/executor/pre-compaction/compaction-escalation.js +6 -0
  140. package/dist/server/infra/executor/pre-compaction/index.d.ts +3 -0
  141. package/dist/server/infra/executor/pre-compaction/index.js +1 -0
  142. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.d.ts +59 -0
  143. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.js +124 -0
  144. package/dist/server/infra/executor/pre-compaction/prompts.d.ts +24 -0
  145. package/dist/server/infra/executor/pre-compaction/prompts.js +47 -0
  146. package/dist/server/infra/executor/query-executor.d.ts +3 -0
  147. package/dist/server/infra/executor/query-executor.js +39 -4
  148. package/dist/server/infra/http/authenticated-http-client.js +4 -0
  149. package/dist/server/infra/http/provider-model-fetcher-registry.js +1 -5
  150. package/dist/server/infra/http/provider-model-fetchers.d.ts +0 -14
  151. package/dist/server/infra/http/provider-model-fetchers.js +0 -132
  152. package/dist/server/infra/provider/provider-config-resolver.js +0 -55
  153. package/dist/server/utils/curate-result-parser.d.ts +4 -4
  154. package/dist/shared/constants/curation.d.ts +6 -0
  155. package/dist/shared/constants/curation.js +6 -0
  156. package/dist/shared/utils/escalation-utils.d.ts +59 -0
  157. package/dist/shared/utils/escalation-utils.js +141 -0
  158. package/dist/tui/components/command-input.js +1 -1
  159. package/dist/tui/components/inline-prompts/inline-confirm.js +6 -1
  160. package/dist/tui/features/commands/definitions/exit.d.ts +2 -0
  161. package/dist/tui/features/commands/definitions/exit.js +9 -0
  162. package/dist/tui/features/commands/definitions/index.js +3 -0
  163. package/dist/tui/features/exit/components/exit-flow.d.ts +10 -0
  164. package/dist/tui/features/exit/components/exit-flow.js +19 -0
  165. package/dist/tui/features/provider/components/provider-flow.js +1 -21
  166. package/oclif.manifest.json +100 -109
  167. package/package.json +14 -4
  168. package/dist/agent/infra/blob/migrations.d.ts +0 -63
  169. package/dist/agent/infra/blob/migrations.js +0 -148
  170. package/dist/agent/infra/blob/sqlite-blob-storage.d.ts +0 -82
  171. package/dist/agent/infra/blob/sqlite-blob-storage.js +0 -307
  172. package/dist/agent/infra/llm/providers/google-vertex.d.ts +0 -15
  173. package/dist/agent/infra/llm/providers/google-vertex.js +0 -36
  174. package/dist/agent/infra/storage/blob-history-storage.d.ts +0 -81
  175. package/dist/agent/infra/storage/blob-history-storage.js +0 -193
  176. package/dist/agent/infra/storage/dual-format-history-storage.d.ts +0 -83
  177. package/dist/agent/infra/storage/dual-format-history-storage.js +0 -165
  178. package/dist/agent/infra/storage/sqlite-key-storage.d.ts +0 -113
  179. package/dist/agent/infra/storage/sqlite-key-storage.js +0 -438
  180. package/dist/server/infra/provider/vertex-ai-utils.d.ts +0 -10
  181. package/dist/server/infra/provider/vertex-ai-utils.js +0 -28
  182. package/dist/tui/features/provider/components/credential-path-dialog.d.ts +0 -30
  183. package/dist/tui/features/provider/components/credential-path-dialog.js +0 -85
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Shared compression helper functions.
3
+ *
4
+ * Extracted from ReactiveOverflowStrategy to enable reuse
5
+ * by EscalatedCompressionStrategy and other compression implementations.
6
+ */
7
+ import { isTextPart } from '../../../../core/interfaces/message-type-guards.js';
8
+ /**
9
+ * Count tokens in message history.
10
+ */
11
+ export function countHistoryTokens(history, tokenizer) {
12
+ let total = 0;
13
+ for (const message of history) {
14
+ total += countMessageTokens(message, tokenizer);
15
+ }
16
+ return total;
17
+ }
18
+ /**
19
+ * Count tokens in a single message.
20
+ */
21
+ export function countMessageTokens(message, tokenizer) {
22
+ // Role overhead (approximately 4 tokens)
23
+ let tokens = 4;
24
+ if (typeof message.content === 'string') {
25
+ tokens += tokenizer.countTokens(message.content);
26
+ }
27
+ else if (Array.isArray(message.content)) {
28
+ for (const part of message.content) {
29
+ tokens += isTextPart(part) ? tokenizer.countTokens(part.text) : 100;
30
+ }
31
+ }
32
+ // Tool calls overhead
33
+ if (message.toolCalls) {
34
+ for (const call of message.toolCalls) {
35
+ tokens += tokenizer.countTokens(call.function.name);
36
+ tokens += tokenizer.countTokens(call.function.arguments);
37
+ }
38
+ }
39
+ return tokens;
40
+ }
41
+ /**
42
+ * Extract text content from a message.
43
+ */
44
+ export function extractTextContent(message) {
45
+ if (typeof message.content === 'string') {
46
+ return message.content;
47
+ }
48
+ if (Array.isArray(message.content)) {
49
+ return message.content
50
+ .filter((p) => isTextPart(p))
51
+ .map((p) => p.text)
52
+ .join('\n');
53
+ }
54
+ return '';
55
+ }
56
+ /**
57
+ * Find turn boundaries in message history.
58
+ *
59
+ * A turn boundary is the index where a user message starts.
60
+ * Returns indices of all user messages.
61
+ */
62
+ export function findTurnBoundaries(messages) {
63
+ const boundaries = [];
64
+ for (const [index, message] of messages.entries()) {
65
+ if (message.role === 'user') {
66
+ boundaries.push(index);
67
+ }
68
+ }
69
+ return boundaries;
70
+ }
71
+ /**
72
+ * Format messages for the summary prompt.
73
+ */
74
+ export function formatMessagesForSummary(messages) {
75
+ const MAX_TOTAL_CHARS = 50_000;
76
+ const MAX_PER_MESSAGE_CHARS = 1000;
77
+ const lines = [];
78
+ let totalChars = 0;
79
+ for (const message of messages) {
80
+ if (totalChars >= MAX_TOTAL_CHARS) {
81
+ lines.push(`[... ${messages.length - lines.length} more messages truncated for summarization]`);
82
+ break;
83
+ }
84
+ const role = formatRole(message.role);
85
+ const content = extractTextContent(message);
86
+ // Truncate very long messages (capped at 1K chars to prevent overflow)
87
+ const truncatedContent = content.length > MAX_PER_MESSAGE_CHARS
88
+ ? `${content.slice(0, MAX_PER_MESSAGE_CHARS)}... [truncated]`
89
+ : content;
90
+ if (truncatedContent) {
91
+ lines.push(`${role}: ${truncatedContent}`);
92
+ totalChars += truncatedContent.length;
93
+ }
94
+ // Include tool call information
95
+ if (message.toolCalls && message.toolCalls.length > 0) {
96
+ const toolNames = message.toolCalls.map((tc) => tc.function.name).join(', ');
97
+ lines.push(`[Used tools: ${toolNames}]`);
98
+ totalChars += toolNames.length + 15;
99
+ }
100
+ }
101
+ return lines.join('\n\n');
102
+ }
103
+ /**
104
+ * Format role for display.
105
+ */
106
+ export function formatRole(role) {
107
+ switch (role) {
108
+ case 'assistant': {
109
+ return 'Assistant';
110
+ }
111
+ case 'system': {
112
+ return 'System';
113
+ }
114
+ case 'tool': {
115
+ return 'Tool Result';
116
+ }
117
+ case 'user': {
118
+ return 'User';
119
+ }
120
+ default: {
121
+ return role.charAt(0).toUpperCase() + role.slice(1);
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Escalated Compression Strategy.
3
+ *
4
+ * Implements the three-level escalation protocol from the LCM paper:
5
+ * 1. Normal LLM summarization
6
+ * 2. Aggressive LLM summarization (0.6× token budget)
7
+ * 3. Deterministic binary-search prefix truncation (guaranteed convergence)
8
+ *
9
+ * Convergence guarantee: output token count is always strictly less than input
10
+ * token count under the same counting function. All byterover-cli tokenizers use
11
+ * char-per-token heuristics, so the binary search in Level 3 always terminates.
12
+ *
13
+ * This strategy is designed to be prepended to the compression chain (before
14
+ * MiddleRemoval + OldestRemoval) so that LLM-quality summaries are attempted
15
+ * first, with hard-cut fallbacks after.
16
+ */
17
+ import type { IContentGenerator } from '../../../../core/interfaces/i-content-generator.js';
18
+ import type { ITokenizer } from '../../../../core/interfaces/i-tokenizer.js';
19
+ import type { InternalMessage } from '../../../../core/interfaces/message-types.js';
20
+ import type { ICompressionStrategy } from './types.js';
21
+ /**
22
+ * Options for EscalatedCompressionStrategy.
23
+ */
24
+ export interface EscalatedCompressionOptions {
25
+ /** IContentGenerator for LLM summarization passes */
26
+ generator: IContentGenerator;
27
+ /** Model name for generateContent requests (default: 'default') */
28
+ model?: string;
29
+ /** Number of recent user turns to protect from summarization (default: 2) */
30
+ preserveTurns?: number;
31
+ /** Maximum output tokens for summary (default: 2200) */
32
+ summaryMaxOutputTokens?: number;
33
+ }
34
+ /**
35
+ * Escalated Compression Strategy implementing ICompressionStrategy.
36
+ *
37
+ * Three-level escalation ensures practical convergence:
38
+ * - Level 1 & 2: LLM-based — may not reach maxHistoryTokens in one pass
39
+ * - Level 3: Deterministic — binary search always produces output < input
40
+ *
41
+ * ContextManager runs strategies sequentially and stops when totalTokens ≤ maxInputTokens,
42
+ * so MiddleRemoval + OldestRemoval after this strategy serve as hard-cut fallbacks.
43
+ */
44
+ export declare class EscalatedCompressionStrategy implements ICompressionStrategy {
45
+ private readonly generator;
46
+ private readonly model;
47
+ private readonly preserveTurns;
48
+ private readonly summaryMaxOutputTokens;
49
+ constructor(options: EscalatedCompressionOptions);
50
+ compress(history: InternalMessage[], maxHistoryTokens: number, tokenizer: ITokenizer): Promise<InternalMessage[]>;
51
+ getName(): string;
52
+ /**
53
+ * Build the final compressed history with a summary message.
54
+ */
55
+ private buildResult;
56
+ /**
57
+ * Attempt LLM summarization at a given escalation level.
58
+ *
59
+ * @returns Summary text if accepted, undefined if escalation needed
60
+ */
61
+ private tryLlmSummarization;
62
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Escalated Compression Strategy.
3
+ *
4
+ * Implements the three-level escalation protocol from the LCM paper:
5
+ * 1. Normal LLM summarization
6
+ * 2. Aggressive LLM summarization (0.6× token budget)
7
+ * 3. Deterministic binary-search prefix truncation (guaranteed convergence)
8
+ *
9
+ * Convergence guarantee: output token count is always strictly less than input
10
+ * token count under the same counting function. All byterover-cli tokenizers use
11
+ * char-per-token heuristics, so the binary search in Level 3 always terminates.
12
+ *
13
+ * This strategy is designed to be prepended to the compression chain (before
14
+ * MiddleRemoval + OldestRemoval) so that LLM-quality summaries are attempted
15
+ * first, with hard-cut fallbacks after.
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import { buildDeterministicFallbackCompaction, isCompactionOutputValid, withAggressiveCompactionDirective, } from '../../../../../shared/utils/escalation-utils.js';
19
+ import { countHistoryTokens, findTurnBoundaries, formatMessagesForSummary, } from './compression-helpers.js';
20
+ const SUMMARIZE_PROMPT = `Summarize the following conversation concisely, preserving:
21
+ - Key decisions made and rationale
22
+ - Important actions taken and their results
23
+ - Critical context for continuing the conversation
24
+ - Any unresolved questions or pending tasks
25
+ - File paths, function names, and technical details that are still relevant
26
+
27
+ Keep the summary focused and actionable. Do not include unnecessary narrative.
28
+
29
+ Conversation:
30
+ `;
31
+ /**
32
+ * Escalated Compression Strategy implementing ICompressionStrategy.
33
+ *
34
+ * Three-level escalation ensures practical convergence:
35
+ * - Level 1 & 2: LLM-based — may not reach maxHistoryTokens in one pass
36
+ * - Level 3: Deterministic — binary search always produces output < input
37
+ *
38
+ * ContextManager runs strategies sequentially and stops when totalTokens ≤ maxInputTokens,
39
+ * so MiddleRemoval + OldestRemoval after this strategy serve as hard-cut fallbacks.
40
+ */
41
+ export class EscalatedCompressionStrategy {
42
+ generator;
43
+ model;
44
+ preserveTurns;
45
+ summaryMaxOutputTokens;
46
+ constructor(options) {
47
+ this.generator = options.generator;
48
+ this.model = options.model ?? 'default';
49
+ this.preserveTurns = options.preserveTurns ?? 2;
50
+ this.summaryMaxOutputTokens = options.summaryMaxOutputTokens ?? 2200;
51
+ }
52
+ async compress(history, maxHistoryTokens, tokenizer) {
53
+ const currentTokens = countHistoryTokens(history, tokenizer);
54
+ if (currentTokens <= maxHistoryTokens) {
55
+ return history;
56
+ }
57
+ // Separate system messages from non-system messages
58
+ const systemMessages = history.filter((m) => m.role === 'system');
59
+ const nonSystemMessages = history.filter((m) => m.role !== 'system');
60
+ // Find turn boundaries and split into summarize/keep
61
+ const turnBoundaries = findTurnBoundaries(nonSystemMessages);
62
+ const turnsToPreserve = Math.min(this.preserveTurns, turnBoundaries.length);
63
+ const preserveFromIndex = turnsToPreserve > 0
64
+ ? turnBoundaries[turnBoundaries.length - turnsToPreserve]
65
+ : nonSystemMessages.length;
66
+ const messagesToSummarize = nonSystemMessages.slice(0, preserveFromIndex);
67
+ const messagesToKeep = nonSystemMessages.slice(preserveFromIndex);
68
+ // Need messages to summarize
69
+ if (messagesToSummarize.length === 0) {
70
+ return history;
71
+ }
72
+ const inputText = formatMessagesForSummary(messagesToSummarize);
73
+ const inputTokens = tokenizer.countTokens(inputText);
74
+ // Try Level 1: Normal summarization
75
+ const level1Result = await this.tryLlmSummarization(inputText, inputTokens, tokenizer, false);
76
+ if (level1Result) {
77
+ return this.buildResult(systemMessages, level1Result, messagesToSummarize.length, messagesToKeep);
78
+ }
79
+ // Try Level 2: Aggressive summarization
80
+ const level2Result = await this.tryLlmSummarization(inputText, inputTokens, tokenizer, true);
81
+ if (level2Result) {
82
+ return this.buildResult(systemMessages, level2Result, messagesToSummarize.length, messagesToKeep);
83
+ }
84
+ // Level 3: Deterministic fallback (guaranteed convergence)
85
+ const level3Result = buildDeterministicFallbackCompaction({
86
+ inputTokens,
87
+ sourceText: inputText,
88
+ suffixLabel: 'escalated-compression',
89
+ tokenizer,
90
+ });
91
+ return this.buildResult(systemMessages, level3Result, messagesToSummarize.length, messagesToKeep);
92
+ }
93
+ getName() {
94
+ return 'EscalatedCompression';
95
+ }
96
+ /**
97
+ * Build the final compressed history with a summary message.
98
+ */
99
+ buildResult(systemMessages, summaryContent, summarizedCount, messagesToKeep) {
100
+ const summaryMessage = {
101
+ content: `[Conversation Summary]\n${summaryContent}`,
102
+ metadata: {
103
+ compactedAt: Date.now(),
104
+ isSummary: true,
105
+ strategy: 'escalated-compression',
106
+ summarizedMessageCount: summarizedCount,
107
+ },
108
+ role: 'system',
109
+ };
110
+ return [...systemMessages, summaryMessage, ...messagesToKeep];
111
+ }
112
+ /**
113
+ * Attempt LLM summarization at a given escalation level.
114
+ *
115
+ * @returns Summary text if accepted, undefined if escalation needed
116
+ */
117
+ async tryLlmSummarization(inputText, inputTokens, tokenizer, aggressive) {
118
+ try {
119
+ const prompt = aggressive
120
+ ? withAggressiveCompactionDirective(SUMMARIZE_PROMPT + inputText)
121
+ : SUMMARIZE_PROMPT + inputText;
122
+ const maxTokens = aggressive
123
+ ? Math.floor(0.6 * this.summaryMaxOutputTokens)
124
+ : this.summaryMaxOutputTokens;
125
+ const response = await this.generator.generateContent({
126
+ config: { maxTokens, temperature: 0 },
127
+ contents: [{ content: prompt, role: 'user' }],
128
+ model: this.model,
129
+ systemPrompt: 'You are a conversation summarizer. Produce concise, information-dense summaries.',
130
+ taskId: randomUUID(),
131
+ });
132
+ const result = response.content;
133
+ if (result &&
134
+ tokenizer.countTokens(result) < inputTokens &&
135
+ isCompactionOutputValid(result)) {
136
+ return result;
137
+ }
138
+ return undefined;
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ }
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * Context compression strategies module
3
3
  */
4
+ export { countHistoryTokens, countMessageTokens, extractTextContent, findTurnBoundaries, formatMessagesForSummary, formatRole, } from './compression-helpers.js';
4
5
  export { createEnhancedCompactionStrategy, EnhancedCompactionStrategy } from './enhanced-compaction.js';
5
6
  export type { CompactionResult, EnhancedCompactionOptions } from './enhanced-compaction.js';
7
+ export { EscalatedCompressionStrategy } from './escalated-compression.js';
8
+ export type { EscalatedCompressionOptions } from './escalated-compression.js';
6
9
  export { filterCompacted, findSummaryMessage, getCompressionStats, getFilteredMessageCount, hasSummaryMessage, isSummaryMessage, } from './filter-compacted.js';
7
10
  export { MiddleRemovalStrategy } from './middle-removal.js';
8
11
  export { OldestRemovalStrategy } from './oldest-removal.js';
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * Context compression strategies module
3
3
  */
4
+ // Compression helpers (shared across strategies)
5
+ export { countHistoryTokens, countMessageTokens, extractTextContent, findTurnBoundaries, formatMessagesForSummary, formatRole, } from './compression-helpers.js';
4
6
  // Compression strategies (alphabetical order)
5
7
  export { createEnhancedCompactionStrategy, EnhancedCompactionStrategy } from './enhanced-compaction.js';
8
+ export { EscalatedCompressionStrategy } from './escalated-compression.js';
6
9
  // Filter utilities
7
10
  export { filterCompacted, findSummaryMessage, getCompressionStats, getFilteredMessageCount, hasSummaryMessage, isSummaryMessage, } from './filter-compacted.js';
8
11
  // More compression strategies
@@ -65,33 +65,6 @@ export declare class ReactiveOverflowStrategy implements ICompressionStrategy {
65
65
  constructor(options: ReactiveOverflowOptions);
66
66
  compress(history: InternalMessage[], maxHistoryTokens: number, tokenizer: ITokenizer): Promise<InternalMessage[]>;
67
67
  getName(): string;
68
- /**
69
- * Count tokens in message history.
70
- */
71
- private countHistoryTokens;
72
- /**
73
- * Count tokens in a single message.
74
- */
75
- private countMessageTokens;
76
- /**
77
- * Extract text content from a message.
78
- */
79
- private extractTextContent;
80
- /**
81
- * Find turn boundaries in message history.
82
- *
83
- * A turn boundary is the index where a user message starts.
84
- * Returns indices of all user messages.
85
- */
86
- private findTurnBoundaries;
87
- /**
88
- * Format messages for the summary prompt.
89
- */
90
- private formatMessagesForSummary;
91
- /**
92
- * Format role for display.
93
- */
94
- private formatRole;
95
68
  /**
96
69
  * Generate a fallback summary without LLM.
97
70
  */
@@ -19,7 +19,7 @@
19
19
  * - Full history preserved in storage for audit
20
20
  *
21
21
  */
22
- import { isTextPart } from '../../../../core/interfaces/message-type-guards.js';
22
+ import { countHistoryTokens, extractTextContent, findTurnBoundaries, formatMessagesForSummary, } from './compression-helpers.js';
23
23
  /**
24
24
  * Default configuration values.
25
25
  */
@@ -48,7 +48,7 @@ export class ReactiveOverflowStrategy {
48
48
  }
49
49
  async compress(history, maxHistoryTokens, tokenizer) {
50
50
  // Calculate current token count
51
- const currentTokens = this.countHistoryTokens(history, tokenizer);
51
+ const currentTokens = countHistoryTokens(history, tokenizer);
52
52
  // Check if compression is needed
53
53
  if (currentTokens <= maxHistoryTokens) {
54
54
  return history;
@@ -62,7 +62,7 @@ export class ReactiveOverflowStrategy {
62
62
  return history;
63
63
  }
64
64
  // Calculate how many messages to keep (preserve last N turns)
65
- const turnBoundaries = this.findTurnBoundaries(nonSystemMessages);
65
+ const turnBoundaries = findTurnBoundaries(nonSystemMessages);
66
66
  const turnsToPreserve = Math.min(this.preserveLastNTurns, turnBoundaries.length);
67
67
  const preserveFromIndex = turnsToPreserve > 0
68
68
  ? turnBoundaries[turnBoundaries.length - turnsToPreserve]
@@ -92,123 +92,6 @@ export class ReactiveOverflowStrategy {
92
92
  getName() {
93
93
  return 'ReactiveOverflow';
94
94
  }
95
- /**
96
- * Count tokens in message history.
97
- */
98
- countHistoryTokens(history, tokenizer) {
99
- let total = 0;
100
- for (const message of history) {
101
- total += this.countMessageTokens(message, tokenizer);
102
- }
103
- return total;
104
- }
105
- /**
106
- * Count tokens in a single message.
107
- */
108
- countMessageTokens(message, tokenizer) {
109
- // Role overhead (approximately 4 tokens)
110
- let tokens = 4;
111
- if (typeof message.content === 'string') {
112
- tokens += tokenizer.countTokens(message.content);
113
- }
114
- else if (Array.isArray(message.content)) {
115
- for (const part of message.content) {
116
- tokens += isTextPart(part) ? tokenizer.countTokens(part.text) : 100;
117
- }
118
- }
119
- // Tool calls overhead
120
- if (message.toolCalls) {
121
- for (const call of message.toolCalls) {
122
- tokens += tokenizer.countTokens(call.function.name);
123
- tokens += tokenizer.countTokens(call.function.arguments);
124
- }
125
- }
126
- return tokens;
127
- }
128
- /**
129
- * Extract text content from a message.
130
- */
131
- extractTextContent(message) {
132
- if (typeof message.content === 'string') {
133
- return message.content;
134
- }
135
- if (Array.isArray(message.content)) {
136
- return message.content
137
- .filter((p) => isTextPart(p))
138
- .map((p) => p.text)
139
- .join('\n');
140
- }
141
- return '';
142
- }
143
- /**
144
- * Find turn boundaries in message history.
145
- *
146
- * A turn boundary is the index where a user message starts.
147
- * Returns indices of all user messages.
148
- */
149
- findTurnBoundaries(messages) {
150
- const boundaries = [];
151
- for (const [index, message] of messages.entries()) {
152
- if (message.role === 'user') {
153
- boundaries.push(index);
154
- }
155
- }
156
- return boundaries;
157
- }
158
- /**
159
- * Format messages for the summary prompt.
160
- */
161
- formatMessagesForSummary(messages) {
162
- const MAX_TOTAL_CHARS = 50_000;
163
- const MAX_PER_MESSAGE_CHARS = 1000;
164
- const lines = [];
165
- let totalChars = 0;
166
- for (const message of messages) {
167
- if (totalChars >= MAX_TOTAL_CHARS) {
168
- lines.push(`[... ${messages.length - lines.length} more messages truncated for summarization]`);
169
- break;
170
- }
171
- const role = this.formatRole(message.role);
172
- const content = this.extractTextContent(message);
173
- // Truncate very long messages (capped at 1K chars to prevent overflow)
174
- const truncatedContent = content.length > MAX_PER_MESSAGE_CHARS
175
- ? `${content.slice(0, MAX_PER_MESSAGE_CHARS)}... [truncated]`
176
- : content;
177
- if (truncatedContent) {
178
- lines.push(`${role}: ${truncatedContent}`);
179
- totalChars += truncatedContent.length;
180
- }
181
- // Include tool call information
182
- if (message.toolCalls && message.toolCalls.length > 0) {
183
- const toolNames = message.toolCalls.map((tc) => tc.function.name).join(', ');
184
- lines.push(`[Used tools: ${toolNames}]`);
185
- totalChars += toolNames.length + 15;
186
- }
187
- }
188
- return lines.join('\n\n');
189
- }
190
- /**
191
- * Format role for display.
192
- */
193
- formatRole(role) {
194
- switch (role) {
195
- case 'assistant': {
196
- return 'Assistant';
197
- }
198
- case 'system': {
199
- return 'System';
200
- }
201
- case 'tool': {
202
- return 'Tool Result';
203
- }
204
- case 'user': {
205
- return 'User';
206
- }
207
- default: {
208
- return role.charAt(0).toUpperCase() + role.slice(1);
209
- }
210
- }
211
- }
212
95
  /**
213
96
  * Generate a fallback summary without LLM.
214
97
  */
@@ -225,7 +108,7 @@ export class ReactiveOverflowStrategy {
225
108
  // Extract key topics from user messages
226
109
  const topics = new Set();
227
110
  for (const msg of userMessages.slice(0, 5)) {
228
- const content = this.extractTextContent(msg);
111
+ const content = extractTextContent(msg);
229
112
  const words = content.split(/\s+/).slice(0, 10).join(' ');
230
113
  if (words) {
231
114
  topics.add(words);
@@ -244,7 +127,7 @@ export class ReactiveOverflowStrategy {
244
127
  * Generate a summary of messages using the LLM.
245
128
  */
246
129
  async generateSummary(messages) {
247
- const conversationText = this.formatMessagesForSummary(messages);
130
+ const conversationText = formatMessagesForSummary(messages);
248
131
  const prompt = `You are a conversation summarizer. Summarize the following conversation concisely, preserving:
249
132
  - Key decisions made
250
133
  - Important actions taken
@@ -188,6 +188,22 @@ export declare class ContextManager<T> {
188
188
  * Also clears persisted history if storage is enabled.
189
189
  */
190
190
  clearHistory(): Promise<void>;
191
+ /**
192
+ * Compress messages using the strategy chain and replace in-memory state.
193
+ * Called by AgentLLMService when context exceeds the threshold.
194
+ *
195
+ * Delegates to compressHistoryIfNeeded() which iterates compressionStrategies
196
+ * (EscalatedCompression → MiddleRemoval → OldestRemoval) until the history
197
+ * fits within the token budget.
198
+ *
199
+ * @param systemPromptTokens - Tokens reserved for the system prompt
200
+ * @param targetHistoryBudget - Target token budget for message history.
201
+ * When provided, overrides maxInputTokens for threshold/budget calculations
202
+ * so the strategy chain compresses to the caller's target (e.g. 70% utilization)
203
+ * rather than the full context window.
204
+ * @returns The compressed message array (same reference as this.messages)
205
+ */
206
+ compressAndReplace(systemPromptTokens: number, targetHistoryBudget?: number): Promise<InternalMessage[]>;
191
207
  /**
192
208
  * Compress messages by removing oldest messages until total tokens fit within the budget.
193
209
  * This directly modifies the internal messages array by slicing from the beginning.
@@ -292,10 +308,13 @@ export declare class ContextManager<T> {
292
308
  * Compress conversation history if needed to fit within token limits.
293
309
  *
294
310
  * This method applies compression strategies sequentially until the history
295
- * fits within the available token budget (maxInputTokens - systemPromptTokens).
311
+ * fits within the available token budget.
296
312
  *
297
313
  * @param systemPromptTokens - Tokens used by system prompt (reserved, not compressible)
298
314
  * @param messagesToCompress - Messages to compress (defaults to all messages)
315
+ * @param targetMaxTokens - Override for maxInputTokens. When provided, the method
316
+ * uses this as the total token ceiling (system + history) instead of this.maxInputTokens.
317
+ * This allows the caller to target a lower utilization (e.g. 70%) rather than 100%.
299
318
  * @returns Compressed message history
300
319
  */
301
320
  private compressHistoryIfNeeded;