@sunilp-org/jam-cli 0.1.0 → 0.1.2
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 +431 -63
- package/dist/commands/ask.d.ts +4 -0
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +202 -10
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/commit.d.ts +12 -0
- package/dist/commands/commit.d.ts.map +1 -0
- package/dist/commands/commit.js +135 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +2 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/context.d.ts +12 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +52 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/review.d.ts +25 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +117 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +199 -197
- package/dist/commands/run.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +39 -3
- package/dist/config/loader.js.map +1 -1
- package/dist/index.js +63 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/base.d.ts +26 -0
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/embedded.d.ts +20 -0
- package/dist/providers/embedded.d.ts.map +1 -0
- package/dist/providers/embedded.js +302 -0
- package/dist/providers/embedded.js.map +1 -0
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +25 -1
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/groq.d.ts +16 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/groq.js +23 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/providers/ollama.d.ts +6 -1
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +77 -4
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +18 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +229 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/tools/all-tools.d.ts +18 -0
- package/dist/tools/all-tools.d.ts.map +1 -0
- package/dist/tools/all-tools.js +95 -0
- package/dist/tools/all-tools.js.map +1 -0
- package/dist/tools/apply_patch.js +1 -1
- package/dist/tools/apply_patch.js.map +1 -1
- package/dist/tools/context-tools.d.ts +14 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +63 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/git_diff.js +1 -1
- package/dist/tools/git_diff.js.map +1 -1
- package/dist/tools/git_status.js +1 -1
- package/dist/tools/git_status.js.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +2 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run_command.d.ts +8 -3
- package/dist/tools/run_command.d.ts.map +1 -1
- package/dist/tools/run_command.js +90 -3
- package/dist/tools/run_command.js.map +1 -1
- package/dist/ui/chat.d.ts.map +1 -1
- package/dist/ui/chat.js +173 -1
- package/dist/ui/chat.js.map +1 -1
- package/dist/ui/logo.d.ts.map +1 -1
- package/dist/ui/logo.js +5 -1
- package/dist/ui/logo.js.map +1 -1
- package/dist/utils/agent.d.ts +130 -0
- package/dist/utils/agent.d.ts.map +1 -0
- package/dist/utils/agent.js +449 -0
- package/dist/utils/agent.js.map +1 -0
- package/dist/utils/cache.d.ts +30 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +62 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/context.d.ts +38 -0
- package/dist/utils/context.d.ts.map +1 -0
- package/dist/utils/context.js +383 -0
- package/dist/utils/context.js.map +1 -0
- package/dist/utils/critic.d.ts +31 -0
- package/dist/utils/critic.d.ts.map +1 -0
- package/dist/utils/critic.js +126 -0
- package/dist/utils/critic.js.map +1 -0
- package/dist/utils/index-builder.d.ts +53 -0
- package/dist/utils/index-builder.d.ts.map +1 -0
- package/dist/utils/index-builder.js +241 -0
- package/dist/utils/index-builder.js.map +1 -0
- package/dist/utils/memory.d.ts +104 -0
- package/dist/utils/memory.d.ts.map +1 -0
- package/dist/utils/memory.js +215 -0
- package/dist/utils/memory.js.map +1 -0
- package/dist/utils/past-sessions.d.ts +31 -0
- package/dist/utils/past-sessions.d.ts.map +1 -0
- package/dist/utils/past-sessions.js +126 -0
- package/dist/utils/past-sessions.js.map +1 -0
- package/dist/utils/tokens.d.ts +53 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +138 -0
- package/dist/utils/tokens.js.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working memory management for the agentic loop.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - **Context compaction** — summarize old tool results when approaching the
|
|
6
|
+
* context window limit.
|
|
7
|
+
* - **Scratchpad** — periodic "what have I learned" prompts that create a
|
|
8
|
+
* running working memory the model can reference.
|
|
9
|
+
* - **Tool result capping** — truncate oversized tool outputs before injection.
|
|
10
|
+
*
|
|
11
|
+
* The core idea: instead of feeding the LLM an ever-growing message array,
|
|
12
|
+
* we periodically compress old rounds into a compact summary and carry only
|
|
13
|
+
* recent rounds + the summary forward.
|
|
14
|
+
*/
|
|
15
|
+
import { checkBudget, truncateToolOutput, } from './tokens.js';
|
|
16
|
+
// ── Configuration ─────────────────────────────────────────────────────────────
|
|
17
|
+
/** After this many tool rounds, inject a scratchpad prompt. */
|
|
18
|
+
export const SCRATCHPAD_INTERVAL = 3;
|
|
19
|
+
/** When messages exceed this fraction of the context budget, compact. */
|
|
20
|
+
const COMPACTION_THRESHOLD = 0.70;
|
|
21
|
+
/** Max token budget for a single tool result injection. */
|
|
22
|
+
export const MAX_TOOL_RESULT_TOKENS = 1500;
|
|
23
|
+
/** Max token budget for compacted summary. */
|
|
24
|
+
const _MAX_SUMMARY_TOKENS = 800;
|
|
25
|
+
// ── Tool result capping (P0-2) ────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Cap a tool output to prevent oversized injections into the message array.
|
|
28
|
+
* Call this BEFORE pushing the tool result into `messages`.
|
|
29
|
+
*/
|
|
30
|
+
export function capToolResult(toolName, output) {
|
|
31
|
+
return truncateToolOutput(toolName, output, MAX_TOOL_RESULT_TOKENS);
|
|
32
|
+
}
|
|
33
|
+
// ── Scratchpad (P1-4) ────────────────────────────────────────────────────────
|
|
34
|
+
const SCRATCHPAD_PROMPT = `[WORKING MEMORY CHECKPOINT]
|
|
35
|
+
|
|
36
|
+
Pause and organize what you have learned so far. Write a brief, structured note:
|
|
37
|
+
|
|
38
|
+
1. **Files examined**: List every file path you have read or searched.
|
|
39
|
+
2. **Key findings**: List the most important facts you found (function names, patterns, locations).
|
|
40
|
+
3. **Still needed**: What information do you still need to answer the user's question?
|
|
41
|
+
|
|
42
|
+
Keep this under 200 words. This note will stay in your context as working memory.`;
|
|
43
|
+
/**
|
|
44
|
+
* Check if it's time to inject a scratchpad prompt.
|
|
45
|
+
* Returns true every SCRATCHPAD_INTERVAL tool rounds.
|
|
46
|
+
*/
|
|
47
|
+
export function shouldInjectScratchpad(toolRound) {
|
|
48
|
+
return toolRound > 0 && toolRound % SCRATCHPAD_INTERVAL === 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the scratchpad injection message.
|
|
52
|
+
*/
|
|
53
|
+
export function buildScratchpadPrompt() {
|
|
54
|
+
return { role: 'user', content: SCRATCHPAD_PROMPT };
|
|
55
|
+
}
|
|
56
|
+
// ── Context compaction / summarization (P0-3) ─────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Check whether the message array needs compaction.
|
|
59
|
+
*/
|
|
60
|
+
export function needsCompaction(messages, systemPrompt, model) {
|
|
61
|
+
const { budget, currentTokens } = checkBudget(messages, systemPrompt, model);
|
|
62
|
+
return currentTokens > budget * COMPACTION_THRESHOLD;
|
|
63
|
+
}
|
|
64
|
+
const SUMMARIZER_SYSTEM_PROMPT = `You are a context summarizer for a code assistant. You will receive a conversation between a user and an assistant that includes tool calls and their results.
|
|
65
|
+
|
|
66
|
+
Your job is to produce a COMPACT summary of all the information gathered so far. Include:
|
|
67
|
+
1. Files that were examined (paths only)
|
|
68
|
+
2. Key code facts discovered (function names, class names, patterns, important line numbers)
|
|
69
|
+
3. Any errors or dead-ends encountered
|
|
70
|
+
|
|
71
|
+
Rules:
|
|
72
|
+
- Be extremely concise — bullet points only
|
|
73
|
+
- Include file paths and line numbers where relevant
|
|
74
|
+
- Do NOT include opinions or analysis — just facts
|
|
75
|
+
- Do NOT include the full contents of files — just what was found
|
|
76
|
+
- Stay under 300 words`;
|
|
77
|
+
/**
|
|
78
|
+
* Compact the message array by summarizing old tool rounds.
|
|
79
|
+
*
|
|
80
|
+
* Strategy:
|
|
81
|
+
* 1. Keep the first message (original user query) and the last N messages (recent context).
|
|
82
|
+
* 2. Summarize everything in between via a cheap LLM call.
|
|
83
|
+
* 3. Replace the middle messages with a single summary message.
|
|
84
|
+
*
|
|
85
|
+
* Falls back to a simple truncation if the LLM call fails.
|
|
86
|
+
*/
|
|
87
|
+
export async function compactMessages(messages, provider, options) {
|
|
88
|
+
const keepRecent = options.keepRecent ?? 6; // Keep last 6 messages (3 rounds)
|
|
89
|
+
// Not enough messages to compact
|
|
90
|
+
if (messages.length <= keepRecent + 2)
|
|
91
|
+
return messages;
|
|
92
|
+
const firstMessage = messages[0];
|
|
93
|
+
const middleMessages = messages.slice(1, -keepRecent);
|
|
94
|
+
const recentMessages = messages.slice(-keepRecent);
|
|
95
|
+
// Build a condensed representation of middle messages for the summarizer
|
|
96
|
+
const middleText = middleMessages
|
|
97
|
+
.map((m) => `[${m.role}] ${m.content.slice(0, 500)}${m.content.length > 500 ? '…' : ''}`)
|
|
98
|
+
.join('\n---\n');
|
|
99
|
+
// Try to summarize via LLM
|
|
100
|
+
try {
|
|
101
|
+
const summaryRequest = {
|
|
102
|
+
messages: [{
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: `Summarize the following conversation context:\n\n${middleText}`,
|
|
105
|
+
}],
|
|
106
|
+
model: options.model,
|
|
107
|
+
temperature: 0.1, // Very focused
|
|
108
|
+
maxTokens: 500,
|
|
109
|
+
systemPrompt: SUMMARIZER_SYSTEM_PROMPT,
|
|
110
|
+
};
|
|
111
|
+
let summary = '';
|
|
112
|
+
const stream = provider.streamCompletion(summaryRequest);
|
|
113
|
+
for await (const chunk of stream) {
|
|
114
|
+
if (!chunk.done)
|
|
115
|
+
summary += chunk.delta;
|
|
116
|
+
}
|
|
117
|
+
const trimmedSummary = summary.trim();
|
|
118
|
+
if (trimmedSummary.length > 20) {
|
|
119
|
+
// Successfully summarized — replace middle with summary
|
|
120
|
+
const summaryMessage = {
|
|
121
|
+
role: 'user',
|
|
122
|
+
content: `[CONTEXT SUMMARY — earlier tool results compressed]\n\n${trimmedSummary}\n\n[End of summary — recent context follows]`,
|
|
123
|
+
};
|
|
124
|
+
return [firstMessage, summaryMessage, ...recentMessages];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Summarization failed — fall back to simple truncation
|
|
129
|
+
}
|
|
130
|
+
// Fallback: just keep first + truncated middle + recent
|
|
131
|
+
const fallbackSummary = {
|
|
132
|
+
role: 'user',
|
|
133
|
+
content: `[CONTEXT NOTE: ${middleMessages.length} earlier messages were compressed to save context space. Key info may need to be re-discovered if not in recent messages.]`,
|
|
134
|
+
};
|
|
135
|
+
return [firstMessage, fallbackSummary, ...recentMessages];
|
|
136
|
+
}
|
|
137
|
+
// ── Combined memory manager ───────────────────────────────────────────────────
|
|
138
|
+
/**
|
|
139
|
+
* WorkingMemory manages the conversation's context budget throughout
|
|
140
|
+
* the agentic loop. Commands use it like:
|
|
141
|
+
*
|
|
142
|
+
* ```ts
|
|
143
|
+
* const memory = new WorkingMemory(provider, model, systemPrompt);
|
|
144
|
+
* // In the tool loop:
|
|
145
|
+
* memory.addToolResult(toolName, output, messages);
|
|
146
|
+
* if (memory.shouldScratchpad(round)) messages.push(memory.scratchpadPrompt());
|
|
147
|
+
* if (memory.shouldCompact(messages)) messages = await memory.compact(messages);
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export class WorkingMemory {
|
|
151
|
+
provider;
|
|
152
|
+
model;
|
|
153
|
+
systemPrompt;
|
|
154
|
+
readFiles = new Set();
|
|
155
|
+
searchQueries = new Set();
|
|
156
|
+
factsLearned = [];
|
|
157
|
+
constructor(provider, model, systemPrompt) {
|
|
158
|
+
this.provider = provider;
|
|
159
|
+
this.model = model;
|
|
160
|
+
this.systemPrompt = systemPrompt;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Process and cap a tool result before injection into messages.
|
|
164
|
+
* Also tracks which files/searches have been done.
|
|
165
|
+
*/
|
|
166
|
+
processToolResult(toolName, args, output) {
|
|
167
|
+
// Track what we've accessed
|
|
168
|
+
if (toolName === 'read_file' && args['path']) {
|
|
169
|
+
this.readFiles.add(String(args['path']));
|
|
170
|
+
}
|
|
171
|
+
if (toolName === 'search_text' && args['query']) {
|
|
172
|
+
this.searchQueries.add(String(args['query']));
|
|
173
|
+
}
|
|
174
|
+
// Cap the output
|
|
175
|
+
return capToolResult(toolName, output);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Check if we should inject a scratchpad prompt at this round.
|
|
179
|
+
*/
|
|
180
|
+
shouldScratchpad(round) {
|
|
181
|
+
return shouldInjectScratchpad(round);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Build the scratchpad prompt.
|
|
185
|
+
*/
|
|
186
|
+
scratchpadPrompt() {
|
|
187
|
+
return buildScratchpadPrompt();
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check if messages need compaction.
|
|
191
|
+
*/
|
|
192
|
+
shouldCompact(messages) {
|
|
193
|
+
return needsCompaction(messages, this.systemPrompt, this.model);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Compact the messages array.
|
|
197
|
+
*/
|
|
198
|
+
async compact(messages) {
|
|
199
|
+
return compactMessages(messages, this.provider, { model: this.model });
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get a summary of what has been accessed (for diagnostics / JAM.md updates).
|
|
203
|
+
*/
|
|
204
|
+
getAccessLog() {
|
|
205
|
+
return {
|
|
206
|
+
readFiles: [...this.readFiles],
|
|
207
|
+
searchQueries: [...this.searchQueries],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** Reset for a new turn (multi-turn chat). */
|
|
211
|
+
reset() {
|
|
212
|
+
// Don't reset readFiles/searchQueries — they're session-level
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/utils/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EACL,WAAW,EACX,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,iFAAiF;AAEjF,+DAA+D;AAC/D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAErC,yEAAyE;AACzE,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAElC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAE3C,8CAA8C;AAC9C,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC,iFAAiF;AAEjF;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,MAAc;IAC5D,OAAO,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,sBAAsB,CAAC,CAAC;AACtE,CAAC;AAED,gFAAgF;AAEhF,MAAM,iBAAiB,GAAG;;;;;;;;mFAQyD,CAAC;AAEpF;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,OAAO,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,mBAAmB,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC;AACtD,CAAC;AAED,iFAAiF;AAEjF;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAmB,EACnB,YAAgC,EAChC,KAAc;IAEd,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;IAC7E,OAAO,aAAa,GAAG,MAAM,GAAG,oBAAoB,CAAC;AACvD,CAAC;AAED,MAAM,wBAAwB,GAAG;;;;;;;;;;;;uBAYV,CAAC;AAExB;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAmB,EACnB,QAAyB,EACzB,OAAgD;IAEhD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,kCAAkC;IAE9E,iCAAiC;IACjC,IAAI,QAAQ,CAAC,MAAM,IAAI,UAAU,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAEvD,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;IAClC,MAAM,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;IAEnD,yEAAyE;IACzE,MAAM,UAAU,GAAG,cAAc;SAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;SACxF,IAAI,CAAC,SAAS,CAAC,CAAC;IAEnB,2BAA2B;IAC3B,IAAI,CAAC;QACH,MAAM,cAAc,GAAG;YACrB,QAAQ,EAAE,CAAC;oBACT,IAAI,EAAE,MAAe;oBACrB,OAAO,EAAE,oDAAoD,UAAU,EAAE;iBAC1E,CAAC;YACF,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,WAAW,EAAE,GAAG,EAAE,eAAe;YACjC,SAAS,EAAE,GAAG;YACd,YAAY,EAAE,wBAAwB;SACvC,CAAC;QAEF,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QACzD,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,IAAI;gBAAE,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;QAC1C,CAAC;QAED,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,cAAc,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAC/B,wDAAwD;YACxD,MAAM,cAAc,GAAY;gBAC9B,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,0DAA0D,cAAc,+CAA+C;aACjI,CAAC;YAEF,OAAO,CAAC,YAAY,EAAE,cAAc,EAAE,GAAG,cAAc,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;IAC1D,CAAC;IAED,wDAAwD;IACxD,MAAM,eAAe,GAAY;QAC/B,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,kBAAkB,cAAc,CAAC,MAAM,4HAA4H;KAC7K,CAAC;IAEF,OAAO,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG,cAAc,CAAC,CAAC;AAC5D,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,aAAa;IAChB,QAAQ,CAAkB;IAC1B,KAAK,CAAU;IACf,YAAY,CAAU;IACtB,SAAS,GAAgB,IAAI,GAAG,EAAE,CAAC;IACnC,aAAa,GAAgB,IAAI,GAAG,EAAE,CAAC;IACvC,YAAY,GAAa,EAAE,CAAC;IAEpC,YAAY,QAAyB,EAAE,KAAc,EAAE,YAAqB;QAC1E,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,QAAgB,EAAE,IAA6B,EAAE,MAAc;QAC/E,4BAA4B;QAC5B,IAAI,QAAQ,KAAK,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,QAAQ,KAAK,aAAa,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC;QAED,iBAAiB;QACjB,OAAO,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,KAAa;QAC5B,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,qBAAqB,EAAE,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,QAAmB;QAC/B,OAAO,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,QAAmB;QAC/B,OAAO,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACzE,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO;YACL,SAAS,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;YAC9B,aAAa,EAAE,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC;SACvC,CAAC;IACJ,CAAC;IAED,8CAA8C;IAC9C,KAAK;QACH,8DAA8D;IAChE,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Past session search — find relevant Q&A from previous sessions.
|
|
3
|
+
*
|
|
4
|
+
* Uses a simple TF-IDF-like keyword overlap to find past conversations
|
|
5
|
+
* that are relevant to the current question. No vector embeddings needed —
|
|
6
|
+
* this is a pragmatic approach for local-first CLI tools.
|
|
7
|
+
*/
|
|
8
|
+
export interface PastExchange {
|
|
9
|
+
/** The user's question from the past session. */
|
|
10
|
+
question: string;
|
|
11
|
+
/** The assistant's answer from the past session. */
|
|
12
|
+
answer: string;
|
|
13
|
+
/** The session name / id. */
|
|
14
|
+
sessionId: string;
|
|
15
|
+
/** Relevance score (0–1). */
|
|
16
|
+
score: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Search past sessions for Q&A exchanges relevant to the current question.
|
|
20
|
+
*
|
|
21
|
+
* @param question The user's current question.
|
|
22
|
+
* @param workspaceRoot Current workspace root (to scope sessions).
|
|
23
|
+
* @param maxResults Maximum exchanges to return.
|
|
24
|
+
* @param minScore Minimum relevance score to include.
|
|
25
|
+
*/
|
|
26
|
+
export declare function searchPastSessions(question: string, workspaceRoot: string, maxResults?: number, minScore?: number): Promise<PastExchange[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Format past exchanges as context to inject into the system prompt or messages.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatPastExchanges(exchanges: PastExchange[]): string;
|
|
31
|
+
//# sourceMappingURL=past-sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"past-sessions.d.ts","sourceRoot":"","sources":["../../src/utils/past-sessions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf;AAyCD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,UAAU,GAAE,MAAU,EACtB,QAAQ,GAAE,MAAa,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CAwDzB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Past session search — find relevant Q&A from previous sessions.
|
|
3
|
+
*
|
|
4
|
+
* Uses a simple TF-IDF-like keyword overlap to find past conversations
|
|
5
|
+
* that are relevant to the current question. No vector embeddings needed —
|
|
6
|
+
* this is a pragmatic approach for local-first CLI tools.
|
|
7
|
+
*/
|
|
8
|
+
import { listSessions, getSession } from '../storage/history.js';
|
|
9
|
+
// ── Tokenization / scoring ────────────────────────────────────────────────────
|
|
10
|
+
const STOP_WORDS = new Set([
|
|
11
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
12
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
13
|
+
'should', 'may', 'might', 'can', 'shall', 'to', 'of', 'in', 'for',
|
|
14
|
+
'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'about',
|
|
15
|
+
'this', 'that', 'and', 'or', 'but', 'not', 'so', 'if', 'then',
|
|
16
|
+
'than', 'too', 'very', 'just', 'how', 'where', 'what', 'which',
|
|
17
|
+
'who', 'when', 'why', 'all', 'each', 'every', 'some', 'any',
|
|
18
|
+
'its', 'it', 'you', 'your', 'we', 'our', 'they', 'their',
|
|
19
|
+
'i', 'me', 'my', 'change', 'adding', 'using', 'make', 'use',
|
|
20
|
+
]);
|
|
21
|
+
function tokenize(text) {
|
|
22
|
+
return text
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9\s_.-]/g, ' ')
|
|
25
|
+
.split(/\s+/)
|
|
26
|
+
.filter(w => w.length > 2 && !STOP_WORDS.has(w));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compute a simple keyword overlap score between two texts.
|
|
30
|
+
* Returns 0–1 where 1 means perfect overlap.
|
|
31
|
+
*/
|
|
32
|
+
function keywordOverlap(queryTokens, targetTokens) {
|
|
33
|
+
if (queryTokens.length === 0 || targetTokens.length === 0)
|
|
34
|
+
return 0;
|
|
35
|
+
const targetSet = new Set(targetTokens);
|
|
36
|
+
const matches = queryTokens.filter(t => targetSet.has(t));
|
|
37
|
+
// Jaccard-like: overlap / union
|
|
38
|
+
const union = new Set([...queryTokens, ...targetTokens]);
|
|
39
|
+
return matches.length / union.size;
|
|
40
|
+
}
|
|
41
|
+
// ── Main search ───────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Search past sessions for Q&A exchanges relevant to the current question.
|
|
44
|
+
*
|
|
45
|
+
* @param question The user's current question.
|
|
46
|
+
* @param workspaceRoot Current workspace root (to scope sessions).
|
|
47
|
+
* @param maxResults Maximum exchanges to return.
|
|
48
|
+
* @param minScore Minimum relevance score to include.
|
|
49
|
+
*/
|
|
50
|
+
export async function searchPastSessions(question, workspaceRoot, maxResults = 3, minScore = 0.15) {
|
|
51
|
+
const queryTokens = tokenize(question);
|
|
52
|
+
if (queryTokens.length === 0)
|
|
53
|
+
return [];
|
|
54
|
+
let sessions;
|
|
55
|
+
try {
|
|
56
|
+
sessions = await listSessions();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
// Only look at sessions from the same workspace, limit to recent 20
|
|
62
|
+
const relevantSessions = sessions
|
|
63
|
+
.filter(s => s.workspaceRoot === workspaceRoot)
|
|
64
|
+
.slice(0, 20);
|
|
65
|
+
const candidates = [];
|
|
66
|
+
for (const sessionMeta of relevantSessions) {
|
|
67
|
+
let session;
|
|
68
|
+
try {
|
|
69
|
+
session = await getSession(sessionMeta.id);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!session?.messages)
|
|
75
|
+
continue;
|
|
76
|
+
// Extract user→assistant pairs
|
|
77
|
+
for (let i = 0; i < session.messages.length - 1; i++) {
|
|
78
|
+
const msg = session.messages[i];
|
|
79
|
+
const next = session.messages[i + 1];
|
|
80
|
+
if (msg.role === 'user' && next?.role === 'assistant') {
|
|
81
|
+
// Skip tool-result injections and system messages
|
|
82
|
+
if (msg.content.startsWith('[Tool result:') || msg.content.startsWith('[SYSTEM'))
|
|
83
|
+
continue;
|
|
84
|
+
if (msg.content.startsWith('[WORKING MEMORY') || msg.content.startsWith('[CONTEXT'))
|
|
85
|
+
continue;
|
|
86
|
+
const questionTokens = tokenize(msg.content);
|
|
87
|
+
const score = keywordOverlap(queryTokens, questionTokens);
|
|
88
|
+
if (score >= minScore) {
|
|
89
|
+
candidates.push({
|
|
90
|
+
question: msg.content.slice(0, 500),
|
|
91
|
+
answer: next.content.slice(0, 1500), // Cap size
|
|
92
|
+
sessionId: session.id,
|
|
93
|
+
score,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Sort by score descending, take top N
|
|
100
|
+
return candidates
|
|
101
|
+
.sort((a, b) => b.score - a.score)
|
|
102
|
+
.slice(0, maxResults);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Format past exchanges as context to inject into the system prompt or messages.
|
|
106
|
+
*/
|
|
107
|
+
export function formatPastExchanges(exchanges) {
|
|
108
|
+
if (exchanges.length === 0)
|
|
109
|
+
return '';
|
|
110
|
+
const parts = [
|
|
111
|
+
'## Relevant Past Conversations',
|
|
112
|
+
'',
|
|
113
|
+
'These previous Q&A exchanges from this project may provide useful context:',
|
|
114
|
+
'',
|
|
115
|
+
];
|
|
116
|
+
for (const ex of exchanges) {
|
|
117
|
+
parts.push(`**Q:** ${ex.question.slice(0, 200)}`);
|
|
118
|
+
parts.push(`**A:** ${ex.answer.slice(0, 500)}`);
|
|
119
|
+
parts.push('');
|
|
120
|
+
}
|
|
121
|
+
parts.push('---');
|
|
122
|
+
parts.push('Use the above as background context, but always verify by reading current code.');
|
|
123
|
+
parts.push('');
|
|
124
|
+
return parts.join('\n');
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=past-sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"past-sessions.js","sourceRoot":"","sources":["../../src/utils/past-sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token estimation utilities for context window management.
|
|
3
|
+
*
|
|
4
|
+
* Uses a heuristic character-to-token ratio rather than a full tokenizer
|
|
5
|
+
* to keep dependencies minimal. The ratios are calibrated for typical
|
|
6
|
+
* code / English text seen by llama-family models (~3.5–4 chars per token).
|
|
7
|
+
*
|
|
8
|
+
* The key concern is *budget management* — we don't need exact counts,
|
|
9
|
+
* just good-enough estimates to decide when to evict or summarize.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Known context-window sizes (tokens) for popular local models.
|
|
13
|
+
* Used as fallback when the provider doesn't report a limit.
|
|
14
|
+
*/
|
|
15
|
+
export declare const MODEL_CONTEXT_LIMITS: Record<string, number>;
|
|
16
|
+
/** Rough token count for a string. */
|
|
17
|
+
export declare function estimateTokens(text: string): number;
|
|
18
|
+
/** Estimate tokens for an array of messages (role + content). */
|
|
19
|
+
export declare function estimateMessageTokens(messages: Array<{
|
|
20
|
+
role: string;
|
|
21
|
+
content: string;
|
|
22
|
+
}>): number;
|
|
23
|
+
/**
|
|
24
|
+
* Get the effective context budget for a model (in tokens).
|
|
25
|
+
* This is the max tokens we allow in the message array before
|
|
26
|
+
* triggering summarization / eviction.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getContextBudget(model?: string): number;
|
|
29
|
+
/**
|
|
30
|
+
* Check whether the current messages exceed the context budget.
|
|
31
|
+
* Returns { overBudget, currentTokens, budget, excess }.
|
|
32
|
+
*/
|
|
33
|
+
export declare function checkBudget(messages: Array<{
|
|
34
|
+
role: string;
|
|
35
|
+
content: string;
|
|
36
|
+
}>, systemPrompt: string | undefined, model?: string): {
|
|
37
|
+
overBudget: boolean;
|
|
38
|
+
currentTokens: number;
|
|
39
|
+
budget: number;
|
|
40
|
+
excess: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Truncate text to fit within a token budget, keeping the beginning and end
|
|
44
|
+
* (most useful context is usually at boundaries).
|
|
45
|
+
*/
|
|
46
|
+
export declare function truncateToTokenBudget(text: string, maxTokens: number): string;
|
|
47
|
+
/**
|
|
48
|
+
* Truncate tool output (file contents, search results) to a sensible size.
|
|
49
|
+
* - File reads: keep first + last N lines
|
|
50
|
+
* - Search results: keep first N matches
|
|
51
|
+
*/
|
|
52
|
+
export declare function truncateToolOutput(toolName: string, output: string, maxTokens?: number): string;
|
|
53
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAgBvD,CAAC;AAaF,sCAAsC;AACtC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAOhG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,KAAK,CAAC,EAAE,MAAM,GACb;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAahF;AAID;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAY7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,MAAa,GACvB,MAAM,CA2BR"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token estimation utilities for context window management.
|
|
3
|
+
*
|
|
4
|
+
* Uses a heuristic character-to-token ratio rather than a full tokenizer
|
|
5
|
+
* to keep dependencies minimal. The ratios are calibrated for typical
|
|
6
|
+
* code / English text seen by llama-family models (~3.5–4 chars per token).
|
|
7
|
+
*
|
|
8
|
+
* The key concern is *budget management* — we don't need exact counts,
|
|
9
|
+
* just good-enough estimates to decide when to evict or summarize.
|
|
10
|
+
*/
|
|
11
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
12
|
+
/** Average characters per token for English + code. */
|
|
13
|
+
const CHARS_PER_TOKEN = 3.8;
|
|
14
|
+
/**
|
|
15
|
+
* Known context-window sizes (tokens) for popular local models.
|
|
16
|
+
* Used as fallback when the provider doesn't report a limit.
|
|
17
|
+
*/
|
|
18
|
+
export const MODEL_CONTEXT_LIMITS = {
|
|
19
|
+
'llama3.2': 128_000,
|
|
20
|
+
'llama3.2:1b': 8_192,
|
|
21
|
+
'llama3.2:3b': 128_000,
|
|
22
|
+
'llama3.1': 128_000,
|
|
23
|
+
'llama3': 8_192,
|
|
24
|
+
'llama2': 4_096,
|
|
25
|
+
'mistral': 8_192,
|
|
26
|
+
'mixtral': 32_768,
|
|
27
|
+
'codellama': 16_384,
|
|
28
|
+
'deepseek-coder': 16_384,
|
|
29
|
+
'deepseek-coder-v2': 128_000,
|
|
30
|
+
'qwen2.5-coder': 128_000,
|
|
31
|
+
'phi3': 128_000,
|
|
32
|
+
'gemma2': 8_192,
|
|
33
|
+
'command-r': 128_000,
|
|
34
|
+
};
|
|
35
|
+
/** Safe default when model is unknown (conservative). */
|
|
36
|
+
const DEFAULT_CONTEXT_LIMIT = 8_192;
|
|
37
|
+
/**
|
|
38
|
+
* How much of the context window we're willing to fill with messages.
|
|
39
|
+
* Leave headroom for the model's own generation and system prompt.
|
|
40
|
+
*/
|
|
41
|
+
const USAGE_RATIO = 0.75;
|
|
42
|
+
// ── Estimation functions ──────────────────────────────────────────────────────
|
|
43
|
+
/** Rough token count for a string. */
|
|
44
|
+
export function estimateTokens(text) {
|
|
45
|
+
if (!text)
|
|
46
|
+
return 0;
|
|
47
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
48
|
+
}
|
|
49
|
+
/** Estimate tokens for an array of messages (role + content). */
|
|
50
|
+
export function estimateMessageTokens(messages) {
|
|
51
|
+
let total = 0;
|
|
52
|
+
for (const msg of messages) {
|
|
53
|
+
// ~4 tokens overhead per message for role, delimiters
|
|
54
|
+
total += 4 + estimateTokens(msg.content);
|
|
55
|
+
}
|
|
56
|
+
return total;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the effective context budget for a model (in tokens).
|
|
60
|
+
* This is the max tokens we allow in the message array before
|
|
61
|
+
* triggering summarization / eviction.
|
|
62
|
+
*/
|
|
63
|
+
export function getContextBudget(model) {
|
|
64
|
+
if (!model)
|
|
65
|
+
return Math.floor(DEFAULT_CONTEXT_LIMIT * USAGE_RATIO);
|
|
66
|
+
// Try exact match first, then prefix match
|
|
67
|
+
const lower = model.toLowerCase();
|
|
68
|
+
const limit = MODEL_CONTEXT_LIMITS[lower]
|
|
69
|
+
?? Object.entries(MODEL_CONTEXT_LIMITS).find(([k]) => lower.startsWith(k))?.[1]
|
|
70
|
+
?? DEFAULT_CONTEXT_LIMIT;
|
|
71
|
+
return Math.floor(limit * USAGE_RATIO);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check whether the current messages exceed the context budget.
|
|
75
|
+
* Returns { overBudget, currentTokens, budget, excess }.
|
|
76
|
+
*/
|
|
77
|
+
export function checkBudget(messages, systemPrompt, model) {
|
|
78
|
+
const budget = getContextBudget(model);
|
|
79
|
+
const systemTokens = systemPrompt ? estimateTokens(systemPrompt) + 4 : 0;
|
|
80
|
+
const msgTokens = estimateMessageTokens(messages);
|
|
81
|
+
const currentTokens = systemTokens + msgTokens;
|
|
82
|
+
const excess = currentTokens - budget;
|
|
83
|
+
return {
|
|
84
|
+
overBudget: excess > 0,
|
|
85
|
+
currentTokens,
|
|
86
|
+
budget,
|
|
87
|
+
excess: Math.max(0, excess),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// ── Text truncation helpers ───────────────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Truncate text to fit within a token budget, keeping the beginning and end
|
|
93
|
+
* (most useful context is usually at boundaries).
|
|
94
|
+
*/
|
|
95
|
+
export function truncateToTokenBudget(text, maxTokens) {
|
|
96
|
+
const estimated = estimateTokens(text);
|
|
97
|
+
if (estimated <= maxTokens)
|
|
98
|
+
return text;
|
|
99
|
+
const maxChars = Math.floor(maxTokens * CHARS_PER_TOKEN);
|
|
100
|
+
const keepChars = Math.floor(maxChars * 0.45); // 45% from start, 45% from end, ~10% for marker
|
|
101
|
+
const head = text.slice(0, keepChars);
|
|
102
|
+
const tail = text.slice(-keepChars);
|
|
103
|
+
const omittedTokens = estimated - maxTokens;
|
|
104
|
+
return `${head}\n\n[… ~${omittedTokens} tokens omitted …]\n\n${tail}`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Truncate tool output (file contents, search results) to a sensible size.
|
|
108
|
+
* - File reads: keep first + last N lines
|
|
109
|
+
* - Search results: keep first N matches
|
|
110
|
+
*/
|
|
111
|
+
export function truncateToolOutput(toolName, output, maxTokens = 1500) {
|
|
112
|
+
const estimated = estimateTokens(output);
|
|
113
|
+
if (estimated <= maxTokens)
|
|
114
|
+
return output;
|
|
115
|
+
if (toolName === 'read_file') {
|
|
116
|
+
// For file reads, keep head + tail for best context
|
|
117
|
+
const lines = output.split('\n');
|
|
118
|
+
const maxLines = Math.floor(maxTokens / 10); // ~10 tokens per line avg
|
|
119
|
+
if (lines.length <= maxLines)
|
|
120
|
+
return output;
|
|
121
|
+
const keepLines = Math.floor(maxLines * 0.45);
|
|
122
|
+
const head = lines.slice(0, keepLines).join('\n');
|
|
123
|
+
const tail = lines.slice(-keepLines).join('\n');
|
|
124
|
+
const omitted = lines.length - keepLines * 2;
|
|
125
|
+
return `${head}\n\n[… ${omitted} lines omitted …]\n\n${tail}`;
|
|
126
|
+
}
|
|
127
|
+
if (toolName === 'search_text') {
|
|
128
|
+
// For search results, keep first N results (most relevant)
|
|
129
|
+
const lines = output.split('\n');
|
|
130
|
+
const maxLines = Math.floor(maxTokens / 8);
|
|
131
|
+
if (lines.length <= maxLines)
|
|
132
|
+
return output;
|
|
133
|
+
return lines.slice(0, maxLines).join('\n') + `\n\n[… ${lines.length - maxLines} more results truncated]`;
|
|
134
|
+
}
|
|
135
|
+
// Generic truncation
|
|
136
|
+
return truncateToTokenBudget(output, maxTokens);
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sunilp-org/jam-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Jam — developer-first AI assistant CLI for the terminal. Ask questions, explain code, review diffs, generate patches, and run agentic tasks powered by Ollama.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"assistant",
|
|
24
24
|
"terminal",
|
|
25
25
|
"ollama",
|
|
26
|
+
"embedded",
|
|
26
27
|
"llm",
|
|
27
28
|
"developer-tools",
|
|
28
29
|
"code-review",
|
|
@@ -53,13 +54,16 @@
|
|
|
53
54
|
"test:coverage": "vitest run --coverage",
|
|
54
55
|
"clean": "rm -rf dist"
|
|
55
56
|
},
|
|
57
|
+
"optionalDependencies": {
|
|
58
|
+
"keytar": "^7.9.0",
|
|
59
|
+
"node-llama-cpp": "^3.0.0"
|
|
60
|
+
},
|
|
56
61
|
"dependencies": {
|
|
57
62
|
"chalk": "^5.3.0",
|
|
58
63
|
"commander": "^12.1.0",
|
|
59
64
|
"cosmiconfig": "^9.0.0",
|
|
60
65
|
"ink": "^5.0.1",
|
|
61
66
|
"ink-text-input": "^6.0.0",
|
|
62
|
-
"keytar": "^7.9.0",
|
|
63
67
|
"marked": "^12.0.0",
|
|
64
68
|
"marked-terminal": "^7.1.0",
|
|
65
69
|
"minimatch": "^9.0.5",
|