opencode-rag-plugin 1.10.6 → 1.12.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.
- package/ReadMe.md +44 -6
- package/dist/api.d.ts +1 -0
- package/dist/api.js +1 -0
- package/dist/api.js.map +1 -1
- package/dist/chunker/image.d.ts +14 -0
- package/dist/chunker/image.js +356 -0
- package/dist/chunker/image.js.map +1 -0
- package/dist/chunker/loader.js +5 -0
- package/dist/chunker/loader.js.map +1 -1
- package/dist/cli.js +220 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +30 -5
- package/dist/core/config.js.map +1 -1
- package/dist/core/interfaces.d.ts +12 -0
- package/dist/core/resolve-api-key.js +3 -0
- package/dist/core/resolve-api-key.js.map +1 -1
- package/dist/core/runtime-overrides.d.ts +6 -0
- package/dist/core/runtime-overrides.js +20 -1
- package/dist/core/runtime-overrides.js.map +1 -1
- package/dist/describer/describer.js +1 -1
- package/dist/describer/describer.js.map +1 -1
- package/dist/embedder/health.d.ts +18 -0
- package/dist/embedder/health.js +291 -0
- package/dist/embedder/health.js.map +1 -0
- package/dist/eval/index.d.ts +3 -0
- package/dist/eval/index.js +2 -0
- package/dist/eval/index.js.map +1 -1
- package/dist/eval/run-token-test.d.ts +7 -0
- package/dist/eval/run-token-test.js +288 -0
- package/dist/eval/run-token-test.js.map +1 -0
- package/dist/eval/token-analysis.d.ts +111 -0
- package/dist/eval/token-analysis.js +286 -0
- package/dist/eval/token-analysis.js.map +1 -0
- package/dist/eval/token-counter.d.ts +41 -0
- package/dist/eval/token-counter.js +102 -0
- package/dist/eval/token-counter.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +1 -1
- package/dist/indexer.js +65 -13
- package/dist/indexer.js.map +1 -1
- package/dist/opencode/tools.js +12 -5
- package/dist/opencode/tools.js.map +1 -1
- package/dist/plugin.js +71 -29
- package/dist/plugin.js.map +1 -1
- package/dist/retriever/keyword-index.d.ts +1 -0
- package/dist/retriever/keyword-index.js +11 -0
- package/dist/retriever/keyword-index.js.map +1 -1
- package/dist/retriever/retriever.d.ts +1 -0
- package/dist/retriever/retriever.js +49 -7
- package/dist/retriever/retriever.js.map +1 -1
- package/dist/tui.js +54 -5
- package/dist/tui.js.map +1 -1
- package/dist/web/api.d.ts +8 -0
- package/dist/web/api.js +81 -0
- package/dist/web/api.js.map +1 -1
- package/dist/web/server.js +31 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/ui/app.css +1 -0
- package/dist/web/ui/github-dark.css +118 -0
- package/dist/web/ui/highlight.min.js +1213 -0
- package/dist/web/ui/index.html +504 -125
- package/package.json +8 -2
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token usage analysis for comparing RAG-on vs RAG-off sessions.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to:
|
|
5
|
+
* - Analyze per-query token breakdown from session events
|
|
6
|
+
* - Estimate token savings from RAG (fewer read tool calls)
|
|
7
|
+
* - Generate comparison reports between RAG-on and RAG-off sessions
|
|
8
|
+
*/
|
|
9
|
+
import { readSessionEvents, computeSummary } from "./storage.js";
|
|
10
|
+
import { countTokens } from "./token-counter.js";
|
|
11
|
+
/** Average tokens consumed by a `read` tool call (typical file read). */
|
|
12
|
+
const AVG_READ_TOOL_TOKENS = 1200;
|
|
13
|
+
/** Average tokens consumed by a `search_semantic` tool call. */
|
|
14
|
+
const AVG_SEARCH_TOOL_TOKENS = 800;
|
|
15
|
+
/** Average tokens for system prompt guidance injected by RAG (~150 tokens). */
|
|
16
|
+
const SYSTEM_GUIDANCE_TOKENS = 150;
|
|
17
|
+
/**
|
|
18
|
+
* Analyze token usage for a single session from its JSONL event log.
|
|
19
|
+
*
|
|
20
|
+
* Reads all events, groups them by message, and computes per-query breakdowns
|
|
21
|
+
* including RAG context injection, tool calls, and response times.
|
|
22
|
+
*/
|
|
23
|
+
export function analyzeTokenUsage(storePath, sessionID) {
|
|
24
|
+
const events = readSessionEvents(storePath, sessionID);
|
|
25
|
+
const summary = computeSummary(events);
|
|
26
|
+
// Group events by messageID
|
|
27
|
+
const messageEvents = new Map();
|
|
28
|
+
const ragContextByMessage = new Map();
|
|
29
|
+
const toolCallsByMessage = new Map();
|
|
30
|
+
for (const ev of events) {
|
|
31
|
+
const mid = ev.messageID ?? "unknown";
|
|
32
|
+
if (ev.event === "message" && ev.role === "assistant") {
|
|
33
|
+
if (!messageEvents.has(mid))
|
|
34
|
+
messageEvents.set(mid, []);
|
|
35
|
+
messageEvents.get(mid).push(ev);
|
|
36
|
+
}
|
|
37
|
+
if (ev.event === "rag.context") {
|
|
38
|
+
ragContextByMessage.set(mid, ev);
|
|
39
|
+
}
|
|
40
|
+
if (ev.event === "tool") {
|
|
41
|
+
if (!toolCallsByMessage.has(mid))
|
|
42
|
+
toolCallsByMessage.set(mid, []);
|
|
43
|
+
toolCallsByMessage.get(mid).push(ev);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const breakdowns = [];
|
|
47
|
+
let totalReadToolCalls = 0;
|
|
48
|
+
let totalRagToolCalls = 0;
|
|
49
|
+
let totalRagContextTokens = 0;
|
|
50
|
+
let totalCost = 0;
|
|
51
|
+
let systemGuidanceTokens = 0;
|
|
52
|
+
// System guidance is injected when chunks are indexed (ragContextCount > 0)
|
|
53
|
+
if (summary.ragContextCount > 0) {
|
|
54
|
+
systemGuidanceTokens = SYSTEM_GUIDANCE_TOKENS * summary.messageCount;
|
|
55
|
+
}
|
|
56
|
+
for (const [mid, msgEvents] of messageEvents) {
|
|
57
|
+
const msgEvent = msgEvents[0]; // Should be exactly one per messageID
|
|
58
|
+
const ragCtx = ragContextByMessage.get(mid);
|
|
59
|
+
const tools = toolCallsByMessage.get(mid) ?? [];
|
|
60
|
+
const readCalls = tools.filter((t) => t.tool === "read").length;
|
|
61
|
+
const ragCalls = tools.filter((t) => ["search_semantic", "get_file_skeleton", "find_usages", "search_semantic"].includes(t.tool ?? "")).length;
|
|
62
|
+
const timeDiff = (msgEvent.timeCreated && msgEvent.timeCompleted)
|
|
63
|
+
? msgEvent.timeCompleted - msgEvent.timeCreated
|
|
64
|
+
: 0;
|
|
65
|
+
totalReadToolCalls += readCalls;
|
|
66
|
+
totalRagToolCalls += ragCalls;
|
|
67
|
+
totalRagContextTokens += ragCtx?.ragContextTokens ?? 0;
|
|
68
|
+
totalCost += msgEvent.cost ?? 0;
|
|
69
|
+
breakdowns.push({
|
|
70
|
+
messageID: mid,
|
|
71
|
+
inputTokens: msgEvent.tokens?.input ?? 0,
|
|
72
|
+
outputTokens: msgEvent.tokens?.output ?? 0,
|
|
73
|
+
reasoningTokens: msgEvent.tokens?.reasoning ?? 0,
|
|
74
|
+
cacheRead: msgEvent.tokens?.cache?.read ?? 0,
|
|
75
|
+
cacheWrite: msgEvent.tokens?.cache?.write ?? 0,
|
|
76
|
+
cost: msgEvent.cost ?? 0,
|
|
77
|
+
ragContextTokens: ragCtx?.ragContextTokens ?? 0,
|
|
78
|
+
ragChunkCount: ragCtx?.ragChunkCount ?? 0,
|
|
79
|
+
ragTopScore: ragCtx?.ragTopScore ?? 0,
|
|
80
|
+
readToolCalls: readCalls,
|
|
81
|
+
ragToolCalls: ragCalls,
|
|
82
|
+
responseTimeMs: timeDiff,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Aggregate execution time and models
|
|
86
|
+
const responseTimes = breakdowns.filter((b) => b.responseTimeMs > 0).map((b) => b.responseTimeMs);
|
|
87
|
+
const avgResponseTimeMs = responseTimes.length > 0
|
|
88
|
+
? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length)
|
|
89
|
+
: 0;
|
|
90
|
+
const totalToolTimeMs = breakdowns.reduce((sum, b) => sum + b.ragToolCalls * 40 + b.readToolCalls * 25, 0); // estimated
|
|
91
|
+
const modelSet = new Set();
|
|
92
|
+
for (const ev of events) {
|
|
93
|
+
if (ev.event === "message" && ev.modelID)
|
|
94
|
+
modelSet.add(ev.modelID);
|
|
95
|
+
}
|
|
96
|
+
const totals = {
|
|
97
|
+
inputTokens: summary.totalTokens.input,
|
|
98
|
+
outputTokens: summary.totalTokens.output,
|
|
99
|
+
reasoningTokens: summary.totalTokens.reasoning,
|
|
100
|
+
cacheRead: summary.totalTokens.cacheRead,
|
|
101
|
+
cacheWrite: summary.totalTokens.cacheWrite,
|
|
102
|
+
cost: totalCost,
|
|
103
|
+
ragContextTokens: totalRagContextTokens,
|
|
104
|
+
readToolCalls: totalReadToolCalls,
|
|
105
|
+
ragToolCalls: totalRagToolCalls,
|
|
106
|
+
systemGuidanceTokens,
|
|
107
|
+
avgResponseTimeMs,
|
|
108
|
+
totalToolTimeMs,
|
|
109
|
+
models: [...modelSet],
|
|
110
|
+
};
|
|
111
|
+
// Estimate what tokens would look like without RAG:
|
|
112
|
+
// - No RAG context tokens injected
|
|
113
|
+
// - No system guidance tokens
|
|
114
|
+
// - More read tool calls (estimated 2-3 extra per query)
|
|
115
|
+
// - More search_semantic calls would become read calls
|
|
116
|
+
const avgReadCallsPerQuery = summary.messageCount > 0 ? totalReadToolCalls / summary.messageCount : 0;
|
|
117
|
+
const estimatedExtraReadsPerQuery = Math.max(0, 2.5 - avgReadCallsPerQuery); // RAG typically reduces reads by ~2.5
|
|
118
|
+
const estimatedExtraReadTokens = estimatedExtraReadsPerQuery * AVG_READ_TOOL_TOKENS * summary.messageCount;
|
|
119
|
+
const estimatedExtraSearchTokens = totalRagToolCalls * AVG_SEARCH_TOOL_TOKENS;
|
|
120
|
+
const tokensWithoutRAG = totals.inputTokens
|
|
121
|
+
- totals.ragContextTokens
|
|
122
|
+
- totals.systemGuidanceTokens
|
|
123
|
+
+ estimatedExtraReadTokens
|
|
124
|
+
+ estimatedExtraSearchTokens;
|
|
125
|
+
const tokensWithRAG = totals.inputTokens;
|
|
126
|
+
const netSavings = tokensWithoutRAG - tokensWithRAG;
|
|
127
|
+
const percentSavings = tokensWithoutRAG > 0 ? (netSavings / tokensWithoutRAG) * 100 : 0;
|
|
128
|
+
return {
|
|
129
|
+
sessionID,
|
|
130
|
+
queryCount: summary.messageCount,
|
|
131
|
+
breakdowns,
|
|
132
|
+
totals,
|
|
133
|
+
estimates: {
|
|
134
|
+
tokensWithoutRAG: Math.round(tokensWithoutRAG),
|
|
135
|
+
tokensWithRAG,
|
|
136
|
+
netSavings: Math.round(netSavings),
|
|
137
|
+
percentSavings: Math.round(percentSavings * 10) / 10,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Compare two token analyses (RAG-on vs RAG-off) and produce a delta report.
|
|
143
|
+
*/
|
|
144
|
+
export function compareTokenAnalyses(ragOn, ragOff) {
|
|
145
|
+
const dInput = ragOn.totals.inputTokens - ragOff.totals.inputTokens;
|
|
146
|
+
const dOutput = ragOn.totals.outputTokens - ragOff.totals.outputTokens;
|
|
147
|
+
const dReasoning = ragOn.totals.reasoningTokens - ragOff.totals.reasoningTokens;
|
|
148
|
+
const dCache = ragOn.totals.cacheRead - ragOff.totals.cacheRead;
|
|
149
|
+
const dCost = ragOn.totals.cost - ragOff.totals.cost;
|
|
150
|
+
const dRagCtx = ragOn.totals.ragContextTokens - ragOff.totals.ragContextTokens;
|
|
151
|
+
const dReads = ragOn.totals.readToolCalls - ragOff.totals.readToolCalls;
|
|
152
|
+
const dRagTools = ragOn.totals.ragToolCalls - ragOff.totals.ragToolCalls;
|
|
153
|
+
const dSysGuidance = ragOn.totals.systemGuidanceTokens - ragOff.totals.systemGuidanceTokens;
|
|
154
|
+
const avgOnTime = ragOn.breakdowns.length > 0
|
|
155
|
+
? ragOn.breakdowns.reduce((s, b) => s + b.responseTimeMs, 0) / ragOn.breakdowns.length
|
|
156
|
+
: 0;
|
|
157
|
+
const avgOffTime = ragOff.breakdowns.length > 0
|
|
158
|
+
? ragOff.breakdowns.reduce((s, b) => s + b.responseTimeMs, 0) / ragOff.breakdowns.length
|
|
159
|
+
: 0;
|
|
160
|
+
const dResponseTime = avgOnTime - avgOffTime;
|
|
161
|
+
const pct = (delta, base) => base === 0 ? 0 : Math.round((delta / base) * 1000) / 10;
|
|
162
|
+
const totalOn = ragOn.totals.inputTokens + ragOn.totals.outputTokens;
|
|
163
|
+
const totalOff = ragOff.totals.inputTokens + ragOff.totals.outputTokens;
|
|
164
|
+
// Verdict
|
|
165
|
+
let verdict;
|
|
166
|
+
if (ragOn.totals.inputTokens < ragOff.totals.inputTokens) {
|
|
167
|
+
verdict = `RAG SAVES tokens: ${Math.abs(dInput)} fewer input tokens (${Math.abs(pct(dInput, ragOff.totals.inputTokens))}% reduction)`;
|
|
168
|
+
}
|
|
169
|
+
else if (ragOn.totals.inputTokens > ragOff.totals.inputTokens) {
|
|
170
|
+
verdict = `RAG COSTS tokens: ${dInput} more input tokens (${pct(dInput, ragOff.totals.inputTokens)}% increase)`;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
verdict = "RAG has no effect on input token usage";
|
|
174
|
+
}
|
|
175
|
+
if (dReads < 0) {
|
|
176
|
+
verdict += ` | ${Math.abs(dReads)} fewer read calls`;
|
|
177
|
+
}
|
|
178
|
+
if (dRagTools > 0) {
|
|
179
|
+
verdict += ` | ${dRagTools} additional RAG tool calls`;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
delta: {
|
|
183
|
+
inputTokens: dInput,
|
|
184
|
+
outputTokens: dOutput,
|
|
185
|
+
reasoningTokens: dReasoning,
|
|
186
|
+
cacheRead: dCache,
|
|
187
|
+
cost: dCost,
|
|
188
|
+
ragContextTokens: dRagCtx,
|
|
189
|
+
readToolCalls: dReads,
|
|
190
|
+
ragToolCalls: dRagTools,
|
|
191
|
+
systemGuidanceTokens: dSysGuidance,
|
|
192
|
+
responseTimeMs: Math.round(dResponseTime),
|
|
193
|
+
},
|
|
194
|
+
percentChange: {
|
|
195
|
+
inputTokens: pct(dInput, ragOff.totals.inputTokens),
|
|
196
|
+
outputTokens: pct(dOutput, ragOff.totals.outputTokens),
|
|
197
|
+
cost: pct(dCost, ragOff.totals.cost),
|
|
198
|
+
readToolCalls: pct(dReads, ragOff.totals.readToolCalls),
|
|
199
|
+
responseTimeMs: pct(dResponseTime, avgOffTime),
|
|
200
|
+
},
|
|
201
|
+
verdict,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Format a token comparison report as a human-readable string.
|
|
206
|
+
*/
|
|
207
|
+
export function formatTokenReport(ragOn, ragOff, comparison) {
|
|
208
|
+
const lines = [];
|
|
209
|
+
const sep = "─".repeat(70);
|
|
210
|
+
lines.push("");
|
|
211
|
+
lines.push(sep);
|
|
212
|
+
lines.push(" TOKEN USAGE COMPARISON: RAG ON vs RAG OFF");
|
|
213
|
+
lines.push(sep);
|
|
214
|
+
lines.push("");
|
|
215
|
+
// Summary table
|
|
216
|
+
lines.push(" Metric RAG ON RAG OFF Delta");
|
|
217
|
+
lines.push(` ${"─".repeat(66)}`);
|
|
218
|
+
const row = (label, on, off, delta) => {
|
|
219
|
+
const onStr = typeof on === "number" ? on.toLocaleString() : on;
|
|
220
|
+
const offStr = typeof off === "number" ? off.toLocaleString() : off;
|
|
221
|
+
const deltaStr = typeof delta === "number"
|
|
222
|
+
? (delta > 0 ? `+${delta.toLocaleString()}` : delta.toLocaleString())
|
|
223
|
+
: delta;
|
|
224
|
+
lines.push(` ${label.padEnd(34)}${onStr.padStart(12)}${offStr.padStart(14)}${deltaStr.padStart(14)}`);
|
|
225
|
+
};
|
|
226
|
+
row("Input tokens", ragOn.totals.inputTokens, ragOff.totals.inputTokens, comparison.delta.inputTokens);
|
|
227
|
+
row("Output tokens", ragOn.totals.outputTokens, ragOff.totals.outputTokens, comparison.delta.outputTokens);
|
|
228
|
+
row("Reasoning tokens", ragOn.totals.reasoningTokens, ragOff.totals.reasoningTokens, comparison.delta.reasoningTokens);
|
|
229
|
+
row("Cache read", ragOn.totals.cacheRead, ragOff.totals.cacheRead, comparison.delta.cacheRead);
|
|
230
|
+
row("Cost ($)", `$${ragOn.totals.cost.toFixed(4)}`, `$${ragOff.totals.cost.toFixed(4)}`, `$${comparison.delta.cost.toFixed(4)}`);
|
|
231
|
+
lines.push(` ${"─".repeat(66)}`);
|
|
232
|
+
row("RAG context tokens", ragOn.totals.ragContextTokens, ragOff.totals.ragContextTokens, comparison.delta.ragContextTokens);
|
|
233
|
+
row("System guidance tokens", ragOn.totals.systemGuidanceTokens, ragOff.totals.systemGuidanceTokens, comparison.delta.systemGuidanceTokens);
|
|
234
|
+
row("Read tool calls", ragOn.totals.readToolCalls, ragOff.totals.readToolCalls, comparison.delta.readToolCalls);
|
|
235
|
+
row("RAG tool calls", ragOn.totals.ragToolCalls, ragOff.totals.ragToolCalls, comparison.delta.ragToolCalls);
|
|
236
|
+
row("Avg response time (ms)", ragOn.totals.avgResponseTimeMs, ragOff.totals.avgResponseTimeMs, comparison.delta.responseTimeMs);
|
|
237
|
+
row("Queries processed", ragOn.queryCount, ragOff.queryCount, ragOn.queryCount - ragOff.queryCount);
|
|
238
|
+
lines.push(` ${"─".repeat(66)}`);
|
|
239
|
+
lines.push(` Models: ${ragOn.totals.models.join(", ") || "unknown"}`);
|
|
240
|
+
lines.push(` ${ragOff.totals.models.join(", ") || "unknown"}`);
|
|
241
|
+
lines.push("");
|
|
242
|
+
lines.push(` Verdict: ${comparison.verdict}`);
|
|
243
|
+
lines.push("");
|
|
244
|
+
// Per-query breakdown
|
|
245
|
+
lines.push(" PER-QUERY BREAKDOWN (RAG ON)");
|
|
246
|
+
lines.push(` ${"─".repeat(66)}`);
|
|
247
|
+
lines.push(" # Input Output RAG ctx Reads RAG tools Score");
|
|
248
|
+
lines.push(` ${"─".repeat(66)}`);
|
|
249
|
+
for (let i = 0; i < ragOn.breakdowns.length; i++) {
|
|
250
|
+
const b = ragOn.breakdowns[i];
|
|
251
|
+
lines.push(` ${String(i + 1).padStart(2)} ${String(b.inputTokens).padStart(7)} ${String(b.outputTokens).padStart(7)} ${String(b.ragContextTokens).padStart(7)} ${String(b.readToolCalls).padStart(5)} ${String(b.ragToolCalls).padStart(9)} ${b.ragTopScore.toFixed(2)}`);
|
|
252
|
+
}
|
|
253
|
+
lines.push("");
|
|
254
|
+
lines.push(sep);
|
|
255
|
+
lines.push("");
|
|
256
|
+
return lines.join("\n");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Estimate the token count for given text content.
|
|
260
|
+
* Uses tiktoken BPE (cl100k_base) when available, falls back to ceil(len/4).
|
|
261
|
+
*/
|
|
262
|
+
export function estimateContextTokens(text) {
|
|
263
|
+
return countTokens(text);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Project whether RAG would save tokens for a given query profile.
|
|
267
|
+
*
|
|
268
|
+
* @param avgChunkSize - Average size of injected code chunks in characters
|
|
269
|
+
* @param avgChunksPerQuery - Average chunks injected per query
|
|
270
|
+
* @param avgReadsPerQueryWithoutRAG - Average read calls without RAG
|
|
271
|
+
* @param avgReadsPerQueryWithRAG - Average read calls with RAG
|
|
272
|
+
* @param queryCount - Number of queries in the session
|
|
273
|
+
*/
|
|
274
|
+
export function projectTokenSavings(params) {
|
|
275
|
+
const ragOverheadTokens = Math.ceil(params.avgChunkSize / 4) * params.avgChunksPerQuery * params.queryCount;
|
|
276
|
+
const savedReads = Math.max(0, params.avgReadsPerQueryWithoutRAG - params.avgReadsPerQueryWithRAG);
|
|
277
|
+
const savedReadTokens = savedReads * AVG_READ_TOOL_TOKENS * params.queryCount;
|
|
278
|
+
const netSavings = savedReadTokens - ragOverheadTokens;
|
|
279
|
+
return {
|
|
280
|
+
ragOverheadTokens,
|
|
281
|
+
savedReadTokens,
|
|
282
|
+
netSavings,
|
|
283
|
+
isPositive: netSavings > 0,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=token-analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-analysis.js","sourceRoot":"","sources":["../../src/eval/token-analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token counting with tiktoken BPE + fallback heuristic.
|
|
3
|
+
*
|
|
4
|
+
* Uses js-tiktoken (cl100k_base encoding) for BPE-accurate counts.
|
|
5
|
+
* Falls back to ceil(text.length / 4) when tiktoken is unavailable.
|
|
6
|
+
*
|
|
7
|
+
* cl100k_base is the encoding used by GPT-4, GPT-4o, Claude, and most
|
|
8
|
+
* modern LLMs. It is the closest universal approximation available locally.
|
|
9
|
+
*/
|
|
10
|
+
import type { SearchResult } from "../core/interfaces.js";
|
|
11
|
+
/**
|
|
12
|
+
* Count the number of tokens in a text string.
|
|
13
|
+
*
|
|
14
|
+
* Uses tiktoken BPE (cl100k_base) when available, falls back to
|
|
15
|
+
* the ceil(characters / 4) heuristic otherwise.
|
|
16
|
+
*/
|
|
17
|
+
export declare function countTokens(text: string): number;
|
|
18
|
+
/**
|
|
19
|
+
* Count tokens for multiple texts individually.
|
|
20
|
+
* Returns an array of token counts, one per input text.
|
|
21
|
+
*/
|
|
22
|
+
export declare function countTokensBatch(texts: string[]): number[];
|
|
23
|
+
/**
|
|
24
|
+
* Sum token counts for multiple texts.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sumTokens(texts: string[]): number;
|
|
27
|
+
/**
|
|
28
|
+
* Estimate the token overhead of auto-injected RAG context.
|
|
29
|
+
*
|
|
30
|
+
* Counts each chunk's content + description + formatting overhead individually,
|
|
31
|
+
* providing a more accurate estimate than counting the fully-assembled string.
|
|
32
|
+
*
|
|
33
|
+
* @param chunks - Search results to count
|
|
34
|
+
* @param worktree - Working tree for path relativity (unused, kept for interface compat)
|
|
35
|
+
* @param estimateFn - Fallback estimator (used if tiktoken unavailable)
|
|
36
|
+
*/
|
|
37
|
+
export declare function estimateContextTokensFormatted(chunks: SearchResult[], worktree: string): number;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the tokenizer method used ("tiktoken" or "heuristic").
|
|
40
|
+
*/
|
|
41
|
+
export declare function tokenizerMethod(): "tiktoken" | "heuristic";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token counting with tiktoken BPE + fallback heuristic.
|
|
3
|
+
*
|
|
4
|
+
* Uses js-tiktoken (cl100k_base encoding) for BPE-accurate counts.
|
|
5
|
+
* Falls back to ceil(text.length / 4) when tiktoken is unavailable.
|
|
6
|
+
*
|
|
7
|
+
* cl100k_base is the encoding used by GPT-4, GPT-4o, Claude, and most
|
|
8
|
+
* modern LLMs. It is the closest universal approximation available locally.
|
|
9
|
+
*/
|
|
10
|
+
let cachedEncoder = null;
|
|
11
|
+
let loadFailed = false;
|
|
12
|
+
function getEncoder() {
|
|
13
|
+
if (loadFailed)
|
|
14
|
+
return null;
|
|
15
|
+
if (cachedEncoder)
|
|
16
|
+
return cachedEncoder;
|
|
17
|
+
try {
|
|
18
|
+
// Dynamic import — CJS module in an ESM project
|
|
19
|
+
const mod = require("js-tiktoken");
|
|
20
|
+
cachedEncoder = mod.getEncoding("cl100k_base");
|
|
21
|
+
return cachedEncoder;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
loadFailed = true;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function fallbackEstimate(text) {
|
|
29
|
+
return Math.ceil(text.length / 4);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Count the number of tokens in a text string.
|
|
33
|
+
*
|
|
34
|
+
* Uses tiktoken BPE (cl100k_base) when available, falls back to
|
|
35
|
+
* the ceil(characters / 4) heuristic otherwise.
|
|
36
|
+
*/
|
|
37
|
+
export function countTokens(text) {
|
|
38
|
+
if (text.length === 0)
|
|
39
|
+
return 0;
|
|
40
|
+
const encoder = getEncoder();
|
|
41
|
+
if (encoder) {
|
|
42
|
+
try {
|
|
43
|
+
return encoder.encode(text, [], []).length;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Encoding failed — fall through to heuristic
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return fallbackEstimate(text);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Count tokens for multiple texts individually.
|
|
53
|
+
* Returns an array of token counts, one per input text.
|
|
54
|
+
*/
|
|
55
|
+
export function countTokensBatch(texts) {
|
|
56
|
+
return texts.map(countTokens);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Sum token counts for multiple texts.
|
|
60
|
+
*/
|
|
61
|
+
export function sumTokens(texts) {
|
|
62
|
+
let total = 0;
|
|
63
|
+
for (const text of texts) {
|
|
64
|
+
total += countTokens(text);
|
|
65
|
+
}
|
|
66
|
+
return total;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Estimate the token overhead of auto-injected RAG context.
|
|
70
|
+
*
|
|
71
|
+
* Counts each chunk's content + description + formatting overhead individually,
|
|
72
|
+
* providing a more accurate estimate than counting the fully-assembled string.
|
|
73
|
+
*
|
|
74
|
+
* @param chunks - Search results to count
|
|
75
|
+
* @param worktree - Working tree for path relativity (unused, kept for interface compat)
|
|
76
|
+
* @param estimateFn - Fallback estimator (used if tiktoken unavailable)
|
|
77
|
+
*/
|
|
78
|
+
export function estimateContextTokensFormatted(chunks, worktree) {
|
|
79
|
+
if (chunks.length === 0)
|
|
80
|
+
return 0;
|
|
81
|
+
let tokens = 0;
|
|
82
|
+
for (const r of chunks) {
|
|
83
|
+
// Content tokens
|
|
84
|
+
tokens += countTokens(r.chunk.content);
|
|
85
|
+
// Description tokens
|
|
86
|
+
if (r.chunk.description) {
|
|
87
|
+
tokens += countTokens(r.chunk.description);
|
|
88
|
+
}
|
|
89
|
+
// Formatting overhead: file path, line range, language, score, backticks, newlines
|
|
90
|
+
tokens += 12;
|
|
91
|
+
}
|
|
92
|
+
// Header overhead: "Auto-retrieved code context" + metadata line + separators
|
|
93
|
+
tokens += 30;
|
|
94
|
+
return tokens;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Returns the tokenizer method used ("tiktoken" or "heuristic").
|
|
98
|
+
*/
|
|
99
|
+
export function tokenizerMethod() {
|
|
100
|
+
return getEncoder() ? "tiktoken" : "heuristic";
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=token-counter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-counter.js","sourceRoot":"","sources":["../../src/eval/token-counter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,IAAI,aAAa,GAA2B,IAAI,CAAC;AACjD,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,SAAS,UAAU;IACjB,IAAI,UAAU;QAAE,OAAO,IAAI,CAAC;IAC5B,IAAI,aAAa;QAAE,OAAO,aAAa,CAAC;IAExC,IAAI,CAAC;QACH,gDAAgD;QAChD,MAAM,GAAG,GAAG,OAAO,CAAC,aAAa,CAAmD,CAAC;QACrF,aAAa,GAAG,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;QAC/C,OAAO,aAAa,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,GAAG,IAAI,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEhC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC;IAED,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAe;IAC9C,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,KAAe;IACvC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,8BAA8B,CAC5C,MAAsB,EACtB,QAAgB;IAEhB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAElC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,iBAAiB;QACjB,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,qBAAqB;QACrB,IAAI,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC;QACD,mFAAmF;QACnF,MAAM,IAAI,EAAE,CAAC;IACf,CAAC;IAED,8EAA8E;IAC9E,MAAM,IAAI,EAAE,CAAC;IAEb,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC;AACjD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export { retrieve } from "./retriever/retriever.js";
|
|
|
6
6
|
export { loadConfig, DEFAULT_CONFIG } from "./core/config.js";
|
|
7
7
|
export { createBackgroundIndexer } from "./watcher.js";
|
|
8
8
|
export { createWatchIgnore } from "./indexer.js";
|
|
9
|
-
export
|
|
9
|
+
export { ImageChunker, createImageVisionProvider, getMimeType, SUPPORTED_IMAGE_EXTENSIONS } from "./chunker/image.js";
|
|
10
|
+
export type { RagConfig, DescriptionConfig, ImageDescriptionConfig } from "./core/config.js";
|
|
10
11
|
export type { Chunk, SearchResult, Chunker, DescriptionProvider, EmbeddingProvider, VectorStore } from "./core/interfaces.js";
|
|
11
12
|
export { search, indexWorkspace, getContext, validateConfig, scanWorkspace, getIndexStatusSummary } from "./api.js";
|
|
12
13
|
export type { SearchOptions, IndexOptions, ContextResult, ConfigValidationResult, WorkspaceFile, IndexRunStats } from "./api.js";
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { retrieve } from "./retriever/retriever.js";
|
|
|
7
7
|
export { loadConfig, DEFAULT_CONFIG } from "./core/config.js";
|
|
8
8
|
export { createBackgroundIndexer } from "./watcher.js";
|
|
9
9
|
export { createWatchIgnore } from "./indexer.js";
|
|
10
|
+
export { ImageChunker, createImageVisionProvider, getMimeType, SUPPORTED_IMAGE_EXTENSIONS } from "./chunker/image.js";
|
|
10
11
|
// High-level convenience API
|
|
11
12
|
export { search, indexWorkspace, getContext, validateConfig, scanWorkspace, getIndexStatusSummary } from "./api.js";
|
|
12
13
|
// Plugin is only importable inside OpenCode's runtime
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,WAAW,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAItH,6BAA6B;AAC7B,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGpH,sDAAsD;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,MAAM,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC;AAChC,MAAM,CAAC,MAAM,EAAE,GAAG,qBAAqB,CAAC;AACxC,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/indexer.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export interface WatchPassScheduler {
|
|
|
52
52
|
close(): void;
|
|
53
53
|
}
|
|
54
54
|
export declare function walkFiles(dir: string, extensions: Set<string>, excludeDirs: Set<string>): Promise<string[]>;
|
|
55
|
-
export declare function scanWorkspace(cwd: string, config: RagConfig): Promise<WorkspaceFile[]>;
|
|
55
|
+
export declare function scanWorkspace(cwd: string, config: RagConfig, logger?: Logger): Promise<WorkspaceFile[]>;
|
|
56
56
|
export declare function runIndexPass(options: RunIndexPassOptions): Promise<IndexRunStats>;
|
|
57
57
|
export declare function getIndexStatusSummary(cwd: string, storePath: string, config: RagConfig, store: VectorStore): Promise<IndexStatusSummary>;
|
|
58
58
|
export declare function createWatchPassScheduler(runPass: () => Promise<void>, onError: (error: unknown) => void, debounceMs?: number): WatchPassScheduler;
|
package/dist/indexer.js
CHANGED
|
@@ -3,9 +3,11 @@ import path from "node:path";
|
|
|
3
3
|
import pLimit from "p-limit";
|
|
4
4
|
import { chunkFile } from "./chunker/factory.js";
|
|
5
5
|
import { extractPdfText } from "./chunker/pdf.js";
|
|
6
|
+
import { uuid } from "./chunker/uuid.js";
|
|
6
7
|
import { extractDocxText } from "./chunker/docx.js";
|
|
7
8
|
import { extractDocText } from "./chunker/doc.js";
|
|
8
9
|
import { extractExcelText } from "./chunker/excel.js";
|
|
10
|
+
import { createImageVisionProvider, getMimeType, SUPPORTED_IMAGE_EXTENSIONS } from "./chunker/image.js";
|
|
9
11
|
import { computeFileHash, loadManifest, manifestPathFor, normalizeFilePath, saveManifest, } from "./core/manifest.js";
|
|
10
12
|
import { embedBatch } from "./embedder/factory.js";
|
|
11
13
|
function createLogger(logger) {
|
|
@@ -36,8 +38,28 @@ export async function walkFiles(dir, extensions, excludeDirs) {
|
|
|
36
38
|
}
|
|
37
39
|
return results;
|
|
38
40
|
}
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
+
function isImageFile(fp, extensions) {
|
|
42
|
+
const lower = fp.toLowerCase();
|
|
43
|
+
for (const ext of extensions) {
|
|
44
|
+
if (lower.endsWith(ext.toLowerCase()))
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
function imageExtensionSet(extensions) {
|
|
50
|
+
return new Set(extensions.map((e) => e.toLowerCase()));
|
|
51
|
+
}
|
|
52
|
+
export async function scanWorkspace(cwd, config, logger) {
|
|
53
|
+
const extensions = new Set(config.indexing.includeExtensions);
|
|
54
|
+
let imageVisionProvider = null;
|
|
55
|
+
const imageCfg = config.imageDescription;
|
|
56
|
+
if (imageCfg?.enabled) {
|
|
57
|
+
for (const ext of SUPPORTED_IMAGE_EXTENSIONS) {
|
|
58
|
+
extensions.add(ext.toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
imageVisionProvider = createImageVisionProvider(imageCfg);
|
|
61
|
+
}
|
|
62
|
+
const files = await walkFiles(cwd, extensions, new Set(config.indexing.excludeDirs));
|
|
41
63
|
const isPdf = (fp) => fp.toLowerCase().endsWith(".pdf");
|
|
42
64
|
const isDocx = (fp) => fp.toLowerCase().endsWith(".docx");
|
|
43
65
|
const isDoc = (fp) => fp.toLowerCase().endsWith(".doc");
|
|
@@ -82,6 +104,20 @@ export async function scanWorkspace(cwd, config) {
|
|
|
82
104
|
content = "";
|
|
83
105
|
}
|
|
84
106
|
}
|
|
107
|
+
else if (imageVisionProvider && isImageFile(filePath, SUPPORTED_IMAGE_EXTENSIONS)) {
|
|
108
|
+
const buffer = await fs.readFile(filePath);
|
|
109
|
+
try {
|
|
110
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
111
|
+
const mimeType = getMimeType(ext);
|
|
112
|
+
const b64 = buffer.toString("base64");
|
|
113
|
+
const prompt = imageCfg.prompt;
|
|
114
|
+
content = await imageVisionProvider.describeImage(b64, mimeType, prompt);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
logger?.warn(` ${filePath} (vision failed: ${err.message})`);
|
|
118
|
+
content = "";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
85
121
|
else {
|
|
86
122
|
content = await fs.readFile(filePath, "utf-8");
|
|
87
123
|
}
|
|
@@ -116,7 +152,7 @@ function createIndexStats(totalFiles, manifestStatus) {
|
|
|
116
152
|
}
|
|
117
153
|
export async function runIndexPass(options) {
|
|
118
154
|
const logger = createLogger(options.logger);
|
|
119
|
-
const workspaceFiles = await scanWorkspace(options.cwd, options.config);
|
|
155
|
+
const workspaceFiles = await scanWorkspace(options.cwd, options.config, logger);
|
|
120
156
|
const loadResult = await loadManifest(options.storePath);
|
|
121
157
|
const manifest = loadResult.manifest;
|
|
122
158
|
let manifestStatus = loadResult.status;
|
|
@@ -180,10 +216,26 @@ export async function runIndexPass(options) {
|
|
|
180
216
|
options.keywordIndex?.removeByFilePath(file.normalizedPath);
|
|
181
217
|
isModified = true;
|
|
182
218
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
219
|
+
let chunks;
|
|
220
|
+
// Images: 1 image → 1 chunk (description as content, no paragraph splitting)
|
|
221
|
+
if (isImageFile(file.filePath, SUPPORTED_IMAGE_EXTENSIONS) && file.content.trim().length > 0) {
|
|
222
|
+
chunks = [{
|
|
223
|
+
id: uuid(),
|
|
224
|
+
content: file.content,
|
|
225
|
+
metadata: {
|
|
226
|
+
filePath: file.filePath,
|
|
227
|
+
startLine: 1,
|
|
228
|
+
endLine: 1,
|
|
229
|
+
language: "image",
|
|
230
|
+
},
|
|
231
|
+
}];
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
chunks = await chunkFile(file.filePath, file.content, options.config.chunking?.nodeTypes).catch((err) => {
|
|
235
|
+
logger.warn(` ${fileLabel} (chunking failed: ${err.message})`);
|
|
236
|
+
return null;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
187
239
|
if (chunks === null || chunks.length === 0) {
|
|
188
240
|
if (chunks === null) {
|
|
189
241
|
// Chunking failed — keep previous index state if file was indexed before
|
|
@@ -198,6 +250,7 @@ export async function runIndexPass(options) {
|
|
|
198
250
|
options.keywordIndex?.addChunks(chunks);
|
|
199
251
|
try {
|
|
200
252
|
const docPrefix = options.config.embedding.documentPrefix ?? "";
|
|
253
|
+
const relPath = path.relative(options.cwd, file.filePath).replace(/\\/g, "/");
|
|
201
254
|
const textToEmbed = [];
|
|
202
255
|
if (options.descriptionProvider) {
|
|
203
256
|
let descriptionMap = null;
|
|
@@ -213,25 +266,24 @@ export async function runIndexPass(options) {
|
|
|
213
266
|
const batchDesc = descriptionMap?.get(chunk.id);
|
|
214
267
|
if (batchDesc && batchDesc.trim().length > 0) {
|
|
215
268
|
chunk.description = batchDesc;
|
|
216
|
-
textToEmbed.push(docPrefix + chunk.description + "\n\n" + chunk.content);
|
|
269
|
+
textToEmbed.push(docPrefix + relPath + "\n\n" + chunk.description + "\n\n" + chunk.content);
|
|
217
270
|
}
|
|
218
271
|
else {
|
|
219
272
|
try {
|
|
220
273
|
chunk.description = await options.descriptionProvider.generateDescription(chunk);
|
|
221
|
-
textToEmbed.push(docPrefix + chunk.description + "\n\n" + chunk.content);
|
|
274
|
+
textToEmbed.push(docPrefix + relPath + "\n\n" + chunk.description + "\n\n" + chunk.content);
|
|
222
275
|
}
|
|
223
276
|
catch (err) {
|
|
224
277
|
logger.warn(`Description generation failed for ${chunk.id}, falling back to content: ${err.message}`);
|
|
225
|
-
textToEmbed.push(docPrefix + chunk.content);
|
|
278
|
+
textToEmbed.push(docPrefix + relPath + "\n\n" + chunk.content);
|
|
226
279
|
}
|
|
227
280
|
}
|
|
228
281
|
}
|
|
229
282
|
}
|
|
230
283
|
else {
|
|
231
284
|
for (const chunk of chunks) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
textToEmbed.push(docPrefix + chunk.description + "\n\n" + chunk.content);
|
|
285
|
+
chunk.description = `lines ${chunk.metadata.startLine}-${chunk.metadata.endLine}, ${chunk.metadata.language}`;
|
|
286
|
+
textToEmbed.push(docPrefix + relPath + "\n\n" + chunk.description + "\n\n" + chunk.content);
|
|
235
287
|
}
|
|
236
288
|
}
|
|
237
289
|
const embeddings = await embedBatch(options.embedder, textToEmbed, options.config.indexing.embedBatchSize, "document");
|