byterover-cli 2.0.0 → 2.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/README.md +6 -81
- package/dist/agent/core/domain/llm/index.d.ts +1 -1
- package/dist/agent/core/domain/llm/index.js +1 -1
- package/dist/agent/core/domain/llm/registry.d.ts +8 -0
- package/dist/agent/core/domain/llm/registry.js +34 -0
- package/dist/agent/core/domain/sandbox/types.d.ts +2 -0
- package/dist/agent/core/domain/tools/constants.d.ts +3 -0
- package/dist/agent/core/domain/tools/constants.js +3 -0
- package/dist/agent/core/interfaces/cipher-services.d.ts +2 -4
- package/dist/agent/core/interfaces/i-cipher-agent.d.ts +9 -1
- package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
- package/dist/agent/core/interfaces/i-tool-provider.d.ts +10 -0
- package/dist/agent/core/interfaces/i-tool-scheduler.d.ts +9 -0
- package/dist/agent/infra/agent/agent-schemas.d.ts +0 -9
- package/dist/agent/infra/agent/agent-schemas.js +0 -3
- package/dist/agent/infra/agent/cipher-agent.d.ts +25 -1
- package/dist/agent/infra/agent/cipher-agent.js +138 -11
- package/dist/agent/infra/agent/provider-update-config.d.ts +0 -2
- package/dist/agent/infra/agent/service-initializer.d.ts +2 -6
- package/dist/agent/infra/agent/service-initializer.js +45 -38
- package/dist/agent/infra/blob/blob-storage-factory.d.ts +2 -2
- package/dist/agent/infra/blob/blob-storage-factory.js +4 -4
- package/dist/agent/infra/blob/file-blob-storage.d.ts +96 -0
- package/dist/agent/infra/blob/file-blob-storage.js +454 -0
- package/dist/agent/infra/blob/index.d.ts +2 -3
- package/dist/agent/infra/blob/index.js +4 -6
- package/dist/agent/infra/llm/agent-llm-service.d.ts +3 -0
- package/dist/agent/infra/llm/agent-llm-service.js +34 -52
- package/dist/agent/infra/llm/context/compression/compression-helpers.d.ts +35 -0
- package/dist/agent/infra/llm/context/compression/compression-helpers.js +124 -0
- package/dist/agent/infra/llm/context/compression/escalated-compression.d.ts +62 -0
- package/dist/agent/infra/llm/context/compression/escalated-compression.js +144 -0
- package/dist/agent/infra/llm/context/compression/index.d.ts +3 -0
- package/dist/agent/infra/llm/context/compression/index.js +3 -0
- package/dist/agent/infra/llm/context/compression/reactive-overflow.d.ts +0 -27
- package/dist/agent/infra/llm/context/compression/reactive-overflow.js +5 -122
- package/dist/agent/infra/llm/context/context-manager.d.ts +20 -1
- package/dist/agent/infra/llm/context/context-manager.js +37 -7
- package/dist/agent/infra/llm/providers/index.js +0 -2
- package/dist/agent/infra/llm/providers/types.d.ts +1 -5
- package/dist/agent/infra/map/agentic-map-service.d.ts +97 -0
- package/dist/agent/infra/map/agentic-map-service.js +309 -0
- package/dist/agent/infra/map/context-tree-store.d.ts +94 -0
- package/dist/agent/infra/map/context-tree-store.js +278 -0
- package/dist/agent/infra/map/index.d.ts +4 -0
- package/dist/agent/infra/map/index.js +4 -0
- package/dist/agent/infra/map/llm-map-memory.d.ts +59 -0
- package/dist/agent/infra/map/llm-map-memory.js +187 -0
- package/dist/agent/infra/map/llm-map-service.d.ts +36 -0
- package/dist/agent/infra/map/llm-map-service.js +118 -0
- package/dist/agent/infra/map/map-shared.d.ts +140 -0
- package/dist/agent/infra/map/map-shared.js +325 -0
- package/dist/agent/infra/map/worker-pool.d.ts +45 -0
- package/dist/agent/infra/map/worker-pool.js +73 -0
- package/dist/agent/infra/sandbox/curation-helpers.d.ts +62 -0
- package/dist/agent/infra/sandbox/curation-helpers.js +219 -0
- package/dist/agent/infra/sandbox/sandbox-service.d.ts +12 -0
- package/dist/agent/infra/sandbox/sandbox-service.js +39 -7
- package/dist/agent/infra/sandbox/tools-sdk.d.ts +48 -1
- package/dist/agent/infra/sandbox/tools-sdk.js +52 -1
- package/dist/agent/infra/session/session-manager.d.ts +8 -1
- package/dist/agent/infra/session/session-manager.js +24 -4
- package/dist/agent/infra/storage/file-key-storage.d.ts +142 -0
- package/dist/agent/infra/storage/file-key-storage.js +572 -0
- package/dist/agent/infra/storage/granular-history-storage.d.ts +1 -1
- package/dist/agent/infra/storage/granular-history-storage.js +1 -1
- package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.d.ts +4 -0
- package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.js +42 -14
- package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.d.ts +16 -0
- package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.js +47 -0
- package/dist/agent/infra/tools/core-tool-scheduler.js +3 -1
- package/dist/agent/infra/tools/implementations/agentic-map-tool.d.ts +35 -0
- package/dist/agent/infra/tools/implementations/agentic-map-tool.js +156 -0
- package/dist/agent/infra/tools/implementations/code-exec-tool.js +1 -0
- package/dist/agent/infra/tools/implementations/curate-tool.d.ts +9 -9
- package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +18 -0
- package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +43 -0
- package/dist/agent/infra/tools/implementations/llm-map-tool.d.ts +24 -0
- package/dist/agent/infra/tools/implementations/llm-map-tool.js +87 -0
- package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +28 -1
- package/dist/agent/infra/tools/implementations/memory-symbol-tree.js +27 -3
- package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -0
- package/dist/agent/infra/tools/implementations/search-knowledge-service.js +83 -12
- package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
- package/dist/agent/infra/tools/tool-manager.js +6 -0
- package/dist/agent/infra/tools/tool-provider.d.ts +12 -0
- package/dist/agent/infra/tools/tool-provider.js +78 -0
- package/dist/agent/infra/tools/tool-registry.d.ts +14 -0
- package/dist/agent/infra/tools/tool-registry.js +32 -0
- package/dist/agent/resources/prompts/system-prompt.yml +48 -74
- package/dist/agent/resources/tools/expand_knowledge.txt +20 -0
- package/dist/oclif/commands/curate/index.js +1 -2
- package/dist/oclif/commands/main.js +1 -0
- package/dist/oclif/commands/providers/connect.d.ts +1 -3
- package/dist/oclif/commands/providers/connect.js +7 -29
- package/dist/oclif/commands/query.js +1 -2
- package/dist/server/constants.d.ts +7 -0
- package/dist/server/constants.js +8 -0
- package/dist/server/core/domain/entities/provider-registry.js +1 -15
- package/dist/server/core/domain/knowledge/memory-scoring.js +1 -1
- package/dist/server/core/domain/knowledge/summary-types.d.ts +126 -0
- package/dist/server/core/domain/knowledge/summary-types.js +7 -0
- package/dist/server/core/domain/transport/schemas.d.ts +0 -4
- package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.d.ts +30 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.js +1 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.d.ts +30 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.js +1 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.d.ts +29 -0
- package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.js +1 -0
- package/dist/server/infra/cogit/context-tree-to-push-context-mapper.js +10 -3
- package/dist/server/infra/connectors/skill/skill-connector.d.ts +4 -0
- package/dist/server/infra/connectors/skill/skill-connector.js +4 -0
- package/dist/server/infra/context-tree/children-hash.d.ts +20 -0
- package/dist/server/infra/context-tree/children-hash.js +22 -0
- package/dist/server/infra/context-tree/derived-artifact.d.ts +28 -0
- package/dist/server/infra/context-tree/derived-artifact.js +48 -0
- package/dist/server/infra/context-tree/file-context-tree-archive-service.d.ts +37 -0
- package/dist/server/infra/context-tree/file-context-tree-archive-service.js +219 -0
- package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +50 -0
- package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +278 -0
- package/dist/server/infra/context-tree/file-context-tree-merger.js +4 -0
- package/dist/server/infra/context-tree/file-context-tree-snapshot-service.js +12 -4
- package/dist/server/infra/context-tree/file-context-tree-summary-service.d.ts +44 -0
- package/dist/server/infra/context-tree/file-context-tree-summary-service.js +313 -0
- package/dist/server/infra/context-tree/file-context-tree-writer-service.js +5 -0
- package/dist/server/infra/context-tree/prompts/summary-generation.d.ts +22 -0
- package/dist/server/infra/context-tree/prompts/summary-generation.js +45 -0
- package/dist/server/infra/context-tree/snapshot-diff.d.ts +19 -0
- package/dist/server/infra/context-tree/snapshot-diff.js +39 -0
- package/dist/server/infra/context-tree/summary-frontmatter.d.ts +24 -0
- package/dist/server/infra/context-tree/summary-frontmatter.js +111 -0
- package/dist/server/infra/daemon/agent-process.js +2 -14
- package/dist/server/infra/executor/curate-executor.d.ts +1 -0
- package/dist/server/infra/executor/curate-executor.js +82 -34
- package/dist/server/infra/executor/folder-pack-executor.js +1 -1
- package/dist/server/infra/executor/pre-compaction/compaction-escalation.d.ts +6 -0
- package/dist/server/infra/executor/pre-compaction/compaction-escalation.js +6 -0
- package/dist/server/infra/executor/pre-compaction/index.d.ts +3 -0
- package/dist/server/infra/executor/pre-compaction/index.js +1 -0
- package/dist/server/infra/executor/pre-compaction/pre-compaction-service.d.ts +59 -0
- package/dist/server/infra/executor/pre-compaction/pre-compaction-service.js +124 -0
- package/dist/server/infra/executor/pre-compaction/prompts.d.ts +24 -0
- package/dist/server/infra/executor/pre-compaction/prompts.js +47 -0
- package/dist/server/infra/executor/query-executor.d.ts +3 -0
- package/dist/server/infra/executor/query-executor.js +39 -4
- package/dist/server/infra/http/authenticated-http-client.js +4 -0
- package/dist/server/infra/http/provider-model-fetcher-registry.js +1 -5
- package/dist/server/infra/http/provider-model-fetchers.d.ts +0 -14
- package/dist/server/infra/http/provider-model-fetchers.js +0 -132
- package/dist/server/infra/provider/provider-config-resolver.js +0 -55
- package/dist/server/utils/curate-result-parser.d.ts +4 -4
- package/dist/shared/constants/curation.d.ts +6 -0
- package/dist/shared/constants/curation.js +6 -0
- package/dist/shared/utils/escalation-utils.d.ts +59 -0
- package/dist/shared/utils/escalation-utils.js +141 -0
- package/dist/tui/components/command-input.js +1 -1
- package/dist/tui/components/inline-prompts/inline-confirm.js +6 -1
- package/dist/tui/features/commands/definitions/exit.d.ts +2 -0
- package/dist/tui/features/commands/definitions/exit.js +9 -0
- package/dist/tui/features/commands/definitions/index.js +3 -0
- package/dist/tui/features/exit/components/exit-flow.d.ts +10 -0
- package/dist/tui/features/exit/components/exit-flow.js +19 -0
- package/dist/tui/features/provider/components/provider-flow.js +1 -21
- package/oclif.manifest.json +100 -109
- package/package.json +11 -4
- package/dist/agent/infra/blob/migrations.d.ts +0 -63
- package/dist/agent/infra/blob/migrations.js +0 -148
- package/dist/agent/infra/blob/sqlite-blob-storage.d.ts +0 -82
- package/dist/agent/infra/blob/sqlite-blob-storage.js +0 -307
- package/dist/agent/infra/llm/providers/google-vertex.d.ts +0 -15
- package/dist/agent/infra/llm/providers/google-vertex.js +0 -36
- package/dist/agent/infra/storage/blob-history-storage.d.ts +0 -81
- package/dist/agent/infra/storage/blob-history-storage.js +0 -193
- package/dist/agent/infra/storage/dual-format-history-storage.d.ts +0 -83
- package/dist/agent/infra/storage/dual-format-history-storage.js +0 -165
- package/dist/agent/infra/storage/sqlite-key-storage.d.ts +0 -113
- package/dist/agent/infra/storage/sqlite-key-storage.js +0 -438
- package/dist/server/infra/provider/vertex-ai-utils.d.ts +0 -10
- package/dist/server/infra/provider/vertex-ai-utils.js +0 -28
- package/dist/tui/features/provider/components/credential-path-dialog.d.ts +0 -30
- package/dist/tui/features/provider/components/credential-path-dialog.js +0 -85
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Tree Store.
|
|
3
|
+
*
|
|
4
|
+
* In-memory store implementing bounded buffer with automatic compaction.
|
|
5
|
+
* Two-tier architecture: synchronous buffering with bounded eviction + async final compaction.
|
|
6
|
+
*
|
|
7
|
+
* Hot path (store): synchronous, no LLM calls. If buffer exceeds τ_hard,
|
|
8
|
+
* entries are evicted, summaries consolidated, and single summaries truncated
|
|
9
|
+
* via deterministic truncation in a loop until totalTokens ≤ τ_hard or no
|
|
10
|
+
* further reduction is possible (single entry with no summary).
|
|
11
|
+
*
|
|
12
|
+
* Cold path (compact): called ONCE after worker pool completes.
|
|
13
|
+
* Runs full 3-level escalation to produce high-quality summary.
|
|
14
|
+
*
|
|
15
|
+
* Memory bound: after store() completes, totalTokens ≤ τ_hard + maxSingleLabeledEntrySize
|
|
16
|
+
* where labeled entry size = countTokens("[Item N]: content").
|
|
17
|
+
* The single-entry overshoot covers the item that triggered eviction.
|
|
18
|
+
*/
|
|
19
|
+
import { randomUUID } from 'node:crypto';
|
|
20
|
+
import { buildDeterministicFallbackCompaction, isCompactionOutputValid, withAggressiveCompactionDirective, } from '../../../shared/utils/escalation-utils.js';
|
|
21
|
+
const SUMMARY_PROMPT = `Summarize the following map processing results concisely, preserving:
|
|
22
|
+
- Key findings and patterns across items
|
|
23
|
+
- Important values, counts, and statistics
|
|
24
|
+
- Any errors or anomalies worth noting
|
|
25
|
+
- Actionable insights for the next processing step
|
|
26
|
+
|
|
27
|
+
Keep the summary focused and information-dense.
|
|
28
|
+
|
|
29
|
+
Results:
|
|
30
|
+
`;
|
|
31
|
+
/**
|
|
32
|
+
* Context Tree Store with bounded buffer and 3-level compaction.
|
|
33
|
+
*/
|
|
34
|
+
export class ContextTreeStore {
|
|
35
|
+
entries = new Map();
|
|
36
|
+
generator;
|
|
37
|
+
maxCompactionRounds;
|
|
38
|
+
summaries = [];
|
|
39
|
+
summaryBudget;
|
|
40
|
+
summaryHandle;
|
|
41
|
+
summaryTokens = 0;
|
|
42
|
+
tauHard;
|
|
43
|
+
tokenizer;
|
|
44
|
+
totalTokens = 0;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.generator = options.generator;
|
|
47
|
+
this.tokenizer = options.tokenizer;
|
|
48
|
+
this.tauHard = options.tauHard;
|
|
49
|
+
this.summaryBudget = options.summaryBudget ?? 2000;
|
|
50
|
+
this.maxCompactionRounds = options.maxCompactionRounds ?? 10;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Canonical entry format — single source of truth for labeled entries.
|
|
54
|
+
* Used in store(), evictOldest(), and compact().
|
|
55
|
+
*/
|
|
56
|
+
static formatEntry(index, content) {
|
|
57
|
+
return `[Item ${index}]: ${content}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Cold path — called ONCE after worker pool completes.
|
|
61
|
+
* Runs full 3-level escalation (may involve LLM calls) to produce
|
|
62
|
+
* high-quality summary from remaining entries + prior summaries.
|
|
63
|
+
*/
|
|
64
|
+
async compact() {
|
|
65
|
+
// Join all summaries + remaining entries into source text
|
|
66
|
+
const parts = [...this.summaries];
|
|
67
|
+
const sortedEntries = [...this.entries.entries()].sort(([a], [b]) => a - b);
|
|
68
|
+
for (const [index, entry] of sortedEntries) {
|
|
69
|
+
parts.push(ContextTreeStore.formatEntry(index, entry.content));
|
|
70
|
+
}
|
|
71
|
+
const sourceText = parts.join('\n');
|
|
72
|
+
if (!sourceText.trim()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const inputTokens = this.tokenizer.countTokens(sourceText);
|
|
76
|
+
// If already within budget, use directly
|
|
77
|
+
if (inputTokens <= this.summaryBudget) {
|
|
78
|
+
this.summaryHandle = sourceText;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Level 1: Normal LLM summarization
|
|
82
|
+
const level1 = await this.tryLlmSummarization(sourceText, false);
|
|
83
|
+
if (level1 && this.tokenizer.countTokens(level1) <= this.summaryBudget) {
|
|
84
|
+
this.summaryHandle = level1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Level 2: Aggressive LLM summarization
|
|
88
|
+
const level2 = await this.tryLlmSummarization(sourceText, true);
|
|
89
|
+
if (level2 && this.tokenizer.countTokens(level2) <= this.summaryBudget) {
|
|
90
|
+
this.summaryHandle = level2;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Level 3: Deterministic truncation to fit within summaryBudget
|
|
94
|
+
this.summaryHandle = buildDeterministicFallbackCompaction({
|
|
95
|
+
inputTokens: this.summaryBudget,
|
|
96
|
+
sourceText: level2 ?? level1 ?? sourceText,
|
|
97
|
+
suffixLabel: 'context-tree-compact',
|
|
98
|
+
tokenizer: this.tokenizer,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns compact summary text, bounded to summaryBudget tokens.
|
|
103
|
+
* Must call compact() first for LLM-quality output.
|
|
104
|
+
*/
|
|
105
|
+
getSummaryHandle() {
|
|
106
|
+
return this.summaryHandle;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Hot path — called from processItem(). Synchronous, no LLM calls.
|
|
110
|
+
* If buffer exceeds τ_hard, evicts entries and consolidates summaries
|
|
111
|
+
* until totalTokens ≤ τ_hard or no further reduction is possible.
|
|
112
|
+
*/
|
|
113
|
+
store(index, content) {
|
|
114
|
+
const tokens = this.tokenizer.countTokens(ContextTreeStore.formatEntry(index, content));
|
|
115
|
+
// Handle index overwrite — subtract old tokens before adding new
|
|
116
|
+
const existing = this.entries.get(index);
|
|
117
|
+
if (existing) {
|
|
118
|
+
this.totalTokens -= existing.tokens;
|
|
119
|
+
}
|
|
120
|
+
this.entries.set(index, { content, tokens });
|
|
121
|
+
this.totalTokens += tokens;
|
|
122
|
+
// Eviction loop: keep reducing until within budget or stuck
|
|
123
|
+
let rounds = 0;
|
|
124
|
+
while (this.totalTokens > this.tauHard && rounds < this.maxCompactionRounds) {
|
|
125
|
+
const before = this.totalTokens;
|
|
126
|
+
if (this.entries.size > 1) {
|
|
127
|
+
this.evictOldest();
|
|
128
|
+
}
|
|
129
|
+
else if (this.summaries.length > 1) {
|
|
130
|
+
this.consolidateSummaries();
|
|
131
|
+
}
|
|
132
|
+
else if (this.summaries.length === 1 && this.summaryTokens > 0) {
|
|
133
|
+
this.truncateSingleSummary();
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
break; // Single entry + no summaries — can't reduce further
|
|
137
|
+
}
|
|
138
|
+
// Guard: if eviction didn't reduce tokens, stop to prevent infinite loop
|
|
139
|
+
if (this.totalTokens >= before) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
rounds++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Consolidate all summaries into a single truncated summary.
|
|
147
|
+
* Called when entries alone can't bring totalTokens below τ_hard.
|
|
148
|
+
* Synchronous — uses deterministic truncation only.
|
|
149
|
+
*/
|
|
150
|
+
consolidateSummaries() {
|
|
151
|
+
if (this.summaries.length <= 1) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const sourceText = this.summaries.join('\n');
|
|
155
|
+
const oldSummaryTokens = this.summaryTokens;
|
|
156
|
+
// Deterministic truncation to half of current summary size
|
|
157
|
+
const targetTokens = Math.max(1, Math.floor(oldSummaryTokens / 2));
|
|
158
|
+
const consolidated = buildDeterministicFallbackCompaction({
|
|
159
|
+
inputTokens: targetTokens,
|
|
160
|
+
sourceText,
|
|
161
|
+
suffixLabel: 'context-tree-consolidation',
|
|
162
|
+
tokenizer: this.tokenizer,
|
|
163
|
+
});
|
|
164
|
+
// Replace all summaries with single consolidated one
|
|
165
|
+
this.summaries.length = 0;
|
|
166
|
+
this.summaryTokens = 0;
|
|
167
|
+
this.totalTokens -= oldSummaryTokens;
|
|
168
|
+
if (consolidated) {
|
|
169
|
+
const tokens = this.tokenizer.countTokens(consolidated);
|
|
170
|
+
this.summaries.push(consolidated);
|
|
171
|
+
this.summaryTokens = tokens;
|
|
172
|
+
this.totalTokens += tokens;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Synchronous eviction of oldest entries via deterministic truncation.
|
|
177
|
+
* Truncates oldest half of entries into a compact summary chunk.
|
|
178
|
+
*/
|
|
179
|
+
evictOldest() {
|
|
180
|
+
const sortedKeys = [...this.entries.keys()].sort((a, b) => a - b);
|
|
181
|
+
const evictCount = Math.max(1, Math.floor(sortedKeys.length / 2));
|
|
182
|
+
const keysToEvict = sortedKeys.slice(0, evictCount);
|
|
183
|
+
// Build source text from entries to evict
|
|
184
|
+
const parts = [];
|
|
185
|
+
let evictedTokens = 0;
|
|
186
|
+
for (const key of keysToEvict) {
|
|
187
|
+
const entry = this.entries.get(key);
|
|
188
|
+
parts.push(ContextTreeStore.formatEntry(key, entry.content));
|
|
189
|
+
evictedTokens += entry.tokens;
|
|
190
|
+
}
|
|
191
|
+
const sourceText = parts.join('\n');
|
|
192
|
+
// Target = evicted canonical budget (NOT countTokens(sourceText))
|
|
193
|
+
// Since entry.tokens now counts the labeled form, evictedTokens ≈ countTokens(sourceText)
|
|
194
|
+
// minus \n joiners. The binary search guarantees countTokens(summary) < evictedTokens.
|
|
195
|
+
const summary = buildDeterministicFallbackCompaction({
|
|
196
|
+
inputTokens: evictedTokens,
|
|
197
|
+
sourceText,
|
|
198
|
+
suffixLabel: 'context-tree-eviction',
|
|
199
|
+
tokenizer: this.tokenizer,
|
|
200
|
+
});
|
|
201
|
+
// Remove evicted entries
|
|
202
|
+
for (const key of keysToEvict) {
|
|
203
|
+
this.entries.delete(key);
|
|
204
|
+
}
|
|
205
|
+
this.totalTokens -= evictedTokens;
|
|
206
|
+
// Store summary chunk — hard safety: drop if not strictly smaller than evicted budget
|
|
207
|
+
if (summary) {
|
|
208
|
+
const summaryTokens = this.tokenizer.countTokens(summary);
|
|
209
|
+
if (summaryTokens < evictedTokens) {
|
|
210
|
+
this.summaries.push(summary);
|
|
211
|
+
this.summaryTokens += summaryTokens;
|
|
212
|
+
this.totalTokens += summaryTokens;
|
|
213
|
+
}
|
|
214
|
+
// else: drop summary entirely — eviction is still strictly reducing
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Truncate a single remaining summary to fit within budget.
|
|
219
|
+
* Called when entries.size <= 1 and summaries.length === 1 but still over τ_hard.
|
|
220
|
+
* Halves the summary via deterministic truncation each round.
|
|
221
|
+
*/
|
|
222
|
+
truncateSingleSummary() {
|
|
223
|
+
if (this.summaries.length !== 1 || this.summaryTokens === 0) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const sourceText = this.summaries[0];
|
|
227
|
+
const oldTokens = this.summaryTokens;
|
|
228
|
+
// Target: budget minus current entry tokens, or half of summary — whichever is smaller
|
|
229
|
+
const entryTokens = this.totalTokens - this.summaryTokens;
|
|
230
|
+
const budgetTarget = Math.max(1, this.tauHard - entryTokens);
|
|
231
|
+
const halfTarget = Math.max(1, Math.floor(oldTokens / 2));
|
|
232
|
+
const targetTokens = Math.min(budgetTarget, halfTarget);
|
|
233
|
+
const truncated = buildDeterministicFallbackCompaction({
|
|
234
|
+
inputTokens: targetTokens,
|
|
235
|
+
sourceText,
|
|
236
|
+
suffixLabel: 'context-tree-single-truncation',
|
|
237
|
+
tokenizer: this.tokenizer,
|
|
238
|
+
});
|
|
239
|
+
// Replace summary
|
|
240
|
+
this.summaries.length = 0;
|
|
241
|
+
this.summaryTokens = 0;
|
|
242
|
+
this.totalTokens -= oldTokens;
|
|
243
|
+
if (truncated) {
|
|
244
|
+
const tokens = this.tokenizer.countTokens(truncated);
|
|
245
|
+
this.summaries.push(truncated);
|
|
246
|
+
this.summaryTokens = tokens;
|
|
247
|
+
this.totalTokens += tokens;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Attempt LLM summarization for compact().
|
|
252
|
+
*/
|
|
253
|
+
async tryLlmSummarization(sourceText, aggressive) {
|
|
254
|
+
try {
|
|
255
|
+
const prompt = aggressive
|
|
256
|
+
? withAggressiveCompactionDirective(SUMMARY_PROMPT + sourceText)
|
|
257
|
+
: SUMMARY_PROMPT + sourceText;
|
|
258
|
+
const maxTokens = aggressive
|
|
259
|
+
? Math.floor(0.6 * this.summaryBudget)
|
|
260
|
+
: this.summaryBudget;
|
|
261
|
+
const response = await this.generator.generateContent({
|
|
262
|
+
config: { maxTokens, temperature: 0 },
|
|
263
|
+
contents: [{ content: prompt, role: 'user' }],
|
|
264
|
+
model: 'default',
|
|
265
|
+
systemPrompt: 'You are a data summarizer. Produce concise, information-dense summaries of map processing results.',
|
|
266
|
+
taskId: randomUUID(),
|
|
267
|
+
});
|
|
268
|
+
const result = response.content;
|
|
269
|
+
if (result && isCompactionOutputValid(result)) {
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { type AgenticMapServiceOptions, executeAgenticMap } from './agentic-map-service.js';
|
|
2
|
+
export { executeLlmMap, type LlmMapServiceOptions } from './llm-map-service.js';
|
|
3
|
+
export { type AgenticMapParameters, AgenticMapParametersSchema, buildAgenticMapSystemMessage, buildRetryMessage, buildUserMessage, itemsToJsonl, LLM_MAP_SYSTEM_MESSAGE, type LlmMapParameters, LlmMapParametersSchema, parseJsonlFile, stableStringify, validateAgainstSchema, } from './map-shared.js';
|
|
4
|
+
export { type InMemoryMapRunResult, type MapProgress, type MapRunResult, runMapWorkerPool, type WorkerPoolOptions } from './worker-pool.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { executeAgenticMap } from './agentic-map-service.js';
|
|
2
|
+
export { executeLlmMap } from './llm-map-service.js';
|
|
3
|
+
export { AgenticMapParametersSchema, buildAgenticMapSystemMessage, buildRetryMessage, buildUserMessage, itemsToJsonl, LLM_MAP_SYSTEM_MESSAGE, LlmMapParametersSchema, parseJsonlFile, stableStringify, validateAgainstSchema, } from './map-shared.js';
|
|
4
|
+
export { runMapWorkerPool } from './worker-pool.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory LLM Map — parallel, stateless LLM calls over in-memory items.
|
|
3
|
+
*
|
|
4
|
+
* Reuses `runMapWorkerPool` and the `callLlm`/`withTimeout` pattern from
|
|
5
|
+
* `llm-map-service.ts`, but skips all JSONL file I/O.
|
|
6
|
+
*
|
|
7
|
+
* Designed for curation extraction: processes text chunks in parallel,
|
|
8
|
+
* returns CurationFact[] per item with fixed internal validation
|
|
9
|
+
* (no generic schema — curation-specific).
|
|
10
|
+
*/
|
|
11
|
+
import type { IContentGenerator } from '../../core/interfaces/i-content-generator.js';
|
|
12
|
+
import type { ILogger } from '../../core/interfaces/i-logger.js';
|
|
13
|
+
import type { ContextTreeStore } from './context-tree-store.js';
|
|
14
|
+
import { type CurationFact } from '../sandbox/curation-helpers.js';
|
|
15
|
+
import { type MapProgress } from './worker-pool.js';
|
|
16
|
+
export interface LlmMapMemoryOptions {
|
|
17
|
+
/** Abort signal for cancellation */
|
|
18
|
+
abortSignal?: AbortSignal;
|
|
19
|
+
/** Number of parallel workers (default: 8) */
|
|
20
|
+
concurrency?: number;
|
|
21
|
+
/** Optional context tree store for result aggregation */
|
|
22
|
+
contextTreeStore?: ContextTreeStore;
|
|
23
|
+
/** Content generator for stateless LLM calls */
|
|
24
|
+
generator: IContentGenerator;
|
|
25
|
+
/** Items to process (in-memory, not from JSONL) */
|
|
26
|
+
items: unknown[];
|
|
27
|
+
/** Optional logger for fail-open warnings */
|
|
28
|
+
logger?: ILogger;
|
|
29
|
+
/** Max attempts per item (default: 3) */
|
|
30
|
+
maxAttempts?: number;
|
|
31
|
+
/** Progress callback */
|
|
32
|
+
onProgress?: (progress: MapProgress) => void;
|
|
33
|
+
/** Prompt template for each item */
|
|
34
|
+
prompt: string;
|
|
35
|
+
/** Task ID for billing */
|
|
36
|
+
taskId?: string;
|
|
37
|
+
/** Timeout per item in seconds (default: 120) */
|
|
38
|
+
timeoutSeconds?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface LlmMapMemoryResult {
|
|
41
|
+
/** Number of failed items */
|
|
42
|
+
failed: number;
|
|
43
|
+
/** Per-item results (ordered by input index). null for failed items. */
|
|
44
|
+
results: (CurationFact[] | null)[];
|
|
45
|
+
/** Number of succeeded items */
|
|
46
|
+
succeeded: number;
|
|
47
|
+
/** Compact summary of processed items (from ContextTreeStore) */
|
|
48
|
+
summaryHandle?: string;
|
|
49
|
+
/** Total items processed */
|
|
50
|
+
total: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Execute an in-memory LLM map for curation extraction.
|
|
54
|
+
*
|
|
55
|
+
* Processes items in parallel using `runMapWorkerPool`. Each item gets a
|
|
56
|
+
* stateless LLM call that must return CurationFact[]. Invalid categories
|
|
57
|
+
* are normalized to undefined. Single-object responses are wrapped in arrays.
|
|
58
|
+
*/
|
|
59
|
+
export declare function executeLlmMapMemory(options: LlmMapMemoryOptions): Promise<LlmMapMemoryResult>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory LLM Map — parallel, stateless LLM calls over in-memory items.
|
|
3
|
+
*
|
|
4
|
+
* Reuses `runMapWorkerPool` and the `callLlm`/`withTimeout` pattern from
|
|
5
|
+
* `llm-map-service.ts`, but skips all JSONL file I/O.
|
|
6
|
+
*
|
|
7
|
+
* Designed for curation extraction: processes text chunks in parallel,
|
|
8
|
+
* returns CurationFact[] per item with fixed internal validation
|
|
9
|
+
* (no generic schema — curation-specific).
|
|
10
|
+
*/
|
|
11
|
+
import { VALID_CATEGORIES } from '../sandbox/curation-helpers.js';
|
|
12
|
+
import { buildRetryMessage, buildUserMessage, callLlm, withTimeout, } from './map-shared.js';
|
|
13
|
+
import { runMapWorkerPool } from './worker-pool.js';
|
|
14
|
+
// ── Fixed output schema for curation facts ───────────────────────────────────
|
|
15
|
+
const CURATION_FACT_SCHEMA = {
|
|
16
|
+
items: {
|
|
17
|
+
properties: {
|
|
18
|
+
category: { type: 'string' },
|
|
19
|
+
statement: { type: 'string' },
|
|
20
|
+
subject: { type: 'string' },
|
|
21
|
+
},
|
|
22
|
+
required: ['statement'],
|
|
23
|
+
type: 'object',
|
|
24
|
+
},
|
|
25
|
+
type: 'array',
|
|
26
|
+
};
|
|
27
|
+
// ── Main Function ────────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Execute an in-memory LLM map for curation extraction.
|
|
30
|
+
*
|
|
31
|
+
* Processes items in parallel using `runMapWorkerPool`. Each item gets a
|
|
32
|
+
* stateless LLM call that must return CurationFact[]. Invalid categories
|
|
33
|
+
* are normalized to undefined. Single-object responses are wrapped in arrays.
|
|
34
|
+
*/
|
|
35
|
+
export async function executeLlmMapMemory(options) {
|
|
36
|
+
const { abortSignal, concurrency = 8, generator, items, maxAttempts = 3, onProgress, prompt, taskId, timeoutSeconds = 120, } = options;
|
|
37
|
+
if (items.length === 0) {
|
|
38
|
+
return { failed: 0, results: [], succeeded: 0, total: 0 };
|
|
39
|
+
}
|
|
40
|
+
const runStartedAt = new Date().toISOString();
|
|
41
|
+
async function processItem(itemIndex, item) {
|
|
42
|
+
const userMessage = buildUserMessage(prompt, 'memory', runStartedAt, itemIndex, item, CURATION_FACT_SCHEMA);
|
|
43
|
+
// Per-item timeout
|
|
44
|
+
const timeoutController = new AbortController();
|
|
45
|
+
const timeoutHandle = setTimeout(() => {
|
|
46
|
+
timeoutController.abort();
|
|
47
|
+
}, timeoutSeconds * 1000);
|
|
48
|
+
try {
|
|
49
|
+
let attemptsUsed = 1;
|
|
50
|
+
let lastError = '';
|
|
51
|
+
let lastResponse = '';
|
|
52
|
+
// Initial LLM call (stateless — no tool access)
|
|
53
|
+
const response = await withTimeout(callLlm(generator, userMessage, taskId, abortSignal), timeoutController.signal);
|
|
54
|
+
lastResponse = response.content;
|
|
55
|
+
// Validation loop with retry
|
|
56
|
+
while (true) {
|
|
57
|
+
const validated = validateAndNormalize(lastResponse);
|
|
58
|
+
if (validated.valid) {
|
|
59
|
+
// Fail-open: store result in context tree if available
|
|
60
|
+
if (options.contextTreeStore) {
|
|
61
|
+
try {
|
|
62
|
+
options.contextTreeStore.store(itemIndex, JSON.stringify(validated.facts));
|
|
63
|
+
}
|
|
64
|
+
catch (storeError) {
|
|
65
|
+
options.logger?.warn('Context tree store failed', { error: String(storeError), itemIndex });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return validated.facts;
|
|
69
|
+
}
|
|
70
|
+
lastError = validated.error;
|
|
71
|
+
// Check retry budget
|
|
72
|
+
if (attemptsUsed >= maxAttempts) {
|
|
73
|
+
throw new Error(`Failed after ${attemptsUsed} attempts. Last error: ${lastError}`);
|
|
74
|
+
}
|
|
75
|
+
// Check abort or timeout
|
|
76
|
+
if (abortSignal?.aborted || timeoutController.signal.aborted) {
|
|
77
|
+
throw new Error('Aborted or timed out');
|
|
78
|
+
}
|
|
79
|
+
// Retry with error context + prior response
|
|
80
|
+
attemptsUsed++;
|
|
81
|
+
const retryMessage = buildRetryMessage(userMessage, lastError, lastResponse);
|
|
82
|
+
// eslint-disable-next-line no-await-in-loop
|
|
83
|
+
const retryResponse = await withTimeout(callLlm(generator, retryMessage, taskId, abortSignal), timeoutController.signal);
|
|
84
|
+
lastResponse = retryResponse.content;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
clearTimeout(timeoutHandle);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Run in-memory worker pool
|
|
92
|
+
const result = await runMapWorkerPool({
|
|
93
|
+
abortSignal,
|
|
94
|
+
concurrency,
|
|
95
|
+
items,
|
|
96
|
+
onProgress,
|
|
97
|
+
processItem,
|
|
98
|
+
});
|
|
99
|
+
// Compact context tree and attach summaryHandle (fail-open)
|
|
100
|
+
if (options.contextTreeStore) {
|
|
101
|
+
try {
|
|
102
|
+
await options.contextTreeStore.compact();
|
|
103
|
+
result.summaryHandle = options.contextTreeStore.getSummaryHandle();
|
|
104
|
+
}
|
|
105
|
+
catch (compactError) {
|
|
106
|
+
options.logger?.warn('Context tree compaction failed', { error: String(compactError) });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Convert Map<number, unknown> to ordered array with nulls for failures
|
|
110
|
+
const ordered = [];
|
|
111
|
+
for (let i = 0; i < items.length; i++) {
|
|
112
|
+
const value = result.results.get(i);
|
|
113
|
+
ordered.push(value ? value : null);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
failed: result.failed,
|
|
117
|
+
results: ordered,
|
|
118
|
+
succeeded: result.succeeded,
|
|
119
|
+
summaryHandle: result.summaryHandle,
|
|
120
|
+
total: result.total,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// ── Internal Helpers ─────────────────────────────────────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* Validate and normalize LLM response into CurationFact[].
|
|
126
|
+
*
|
|
127
|
+
* Single normalization layer:
|
|
128
|
+
* - Accepts both CurationFact and CurationFact[] (wraps single objects)
|
|
129
|
+
* - Validates each fact has typeof statement === 'string' && statement.trim().length > 0
|
|
130
|
+
* - Normalizes invalid categories to undefined
|
|
131
|
+
*/
|
|
132
|
+
function validateAndNormalize(response) {
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = JSON.parse(response);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
error: `JSON parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
140
|
+
facts: [],
|
|
141
|
+
valid: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Normalize single object to array
|
|
145
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) && 'statement' in parsed) {
|
|
146
|
+
parsed = [parsed];
|
|
147
|
+
}
|
|
148
|
+
if (!Array.isArray(parsed)) {
|
|
149
|
+
return {
|
|
150
|
+
error: 'Expected array of facts or a single fact object with "statement" field',
|
|
151
|
+
facts: [],
|
|
152
|
+
valid: false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const facts = [];
|
|
156
|
+
for (const item of parsed) {
|
|
157
|
+
if (typeof item !== 'object' || item === null)
|
|
158
|
+
continue;
|
|
159
|
+
const { category, statement, subject } = item;
|
|
160
|
+
if (typeof statement !== 'string' || statement.trim().length === 0)
|
|
161
|
+
continue;
|
|
162
|
+
facts.push({
|
|
163
|
+
category: normalizeCategory(category),
|
|
164
|
+
statement: statement.trim(),
|
|
165
|
+
subject: typeof subject === 'string' ? subject.trim() : undefined,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Empty input array [] is valid — some chunks legitimately contain no extractable facts.
|
|
169
|
+
// But if the LLM returned items and ALL were malformed, that's a format error worth retrying.
|
|
170
|
+
if (facts.length === 0 && parsed.length > 0) {
|
|
171
|
+
return {
|
|
172
|
+
error: `All ${parsed.length} items were malformed. Each fact must have a non-empty "statement" string.`,
|
|
173
|
+
facts: [],
|
|
174
|
+
valid: false,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return { error: '', facts, valid: true };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Normalize category to valid CurationCategory or undefined.
|
|
181
|
+
*/
|
|
182
|
+
function normalizeCategory(value) {
|
|
183
|
+
if (typeof value !== 'string')
|
|
184
|
+
return undefined;
|
|
185
|
+
const lower = value.toLowerCase().trim();
|
|
186
|
+
return VALID_CATEGORIES.has(lower) ? lower : undefined;
|
|
187
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IContentGenerator } from '../../core/interfaces/i-content-generator.js';
|
|
2
|
+
import type { ILogger } from '../../core/interfaces/i-logger.js';
|
|
3
|
+
import type { ContextTreeStore } from './context-tree-store.js';
|
|
4
|
+
import { type LlmMapParameters } from './map-shared.js';
|
|
5
|
+
import { type MapProgress, type MapRunResult } from './worker-pool.js';
|
|
6
|
+
export interface LlmMapServiceOptions {
|
|
7
|
+
/** Abort signal for cancellation */
|
|
8
|
+
abortSignal?: AbortSignal;
|
|
9
|
+
/** Optional context tree store for result aggregation */
|
|
10
|
+
contextTreeStore?: ContextTreeStore;
|
|
11
|
+
/** Content generator (LLM backend) for making stateless calls */
|
|
12
|
+
generator: IContentGenerator;
|
|
13
|
+
/** Optional logger for fail-open warnings */
|
|
14
|
+
logger?: ILogger;
|
|
15
|
+
/** Progress callback */
|
|
16
|
+
onProgress?: (progress: MapProgress) => void;
|
|
17
|
+
/** Tool parameters from the LLM */
|
|
18
|
+
params: LlmMapParameters;
|
|
19
|
+
/** Task ID for billing tracking */
|
|
20
|
+
taskId?: string;
|
|
21
|
+
/** Working directory (project root) */
|
|
22
|
+
workingDirectory: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Execute an LLM-Map: parallel, stateless LLM calls over a JSONL file.
|
|
26
|
+
*
|
|
27
|
+
* For each item (line), makes a single LLM API call (no tools, no file I/O)
|
|
28
|
+
* that must return one JSON value conforming to the provided output schema.
|
|
29
|
+
* If validation fails, the system retries with the error and prior response.
|
|
30
|
+
*
|
|
31
|
+
* Ported from VoltCode's llm-map.ts, adapted for byterover-cli:
|
|
32
|
+
* - Uses IContentGenerator instead of AI SDK's generateText()
|
|
33
|
+
* - Uses in-memory worker pool (no FileMapStore / PostgreSQL)
|
|
34
|
+
* - Runs in-process (no SQS)
|
|
35
|
+
*/
|
|
36
|
+
export declare function executeLlmMap(options: LlmMapServiceOptions): Promise<MapRunResult>;
|