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.
Files changed (65) hide show
  1. package/ReadMe.md +44 -6
  2. package/dist/api.d.ts +1 -0
  3. package/dist/api.js +1 -0
  4. package/dist/api.js.map +1 -1
  5. package/dist/chunker/image.d.ts +14 -0
  6. package/dist/chunker/image.js +356 -0
  7. package/dist/chunker/image.js.map +1 -0
  8. package/dist/chunker/loader.js +5 -0
  9. package/dist/chunker/loader.js.map +1 -1
  10. package/dist/cli.js +220 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/core/config.d.ts +17 -0
  13. package/dist/core/config.js +30 -5
  14. package/dist/core/config.js.map +1 -1
  15. package/dist/core/interfaces.d.ts +12 -0
  16. package/dist/core/resolve-api-key.js +3 -0
  17. package/dist/core/resolve-api-key.js.map +1 -1
  18. package/dist/core/runtime-overrides.d.ts +6 -0
  19. package/dist/core/runtime-overrides.js +20 -1
  20. package/dist/core/runtime-overrides.js.map +1 -1
  21. package/dist/describer/describer.js +1 -1
  22. package/dist/describer/describer.js.map +1 -1
  23. package/dist/embedder/health.d.ts +18 -0
  24. package/dist/embedder/health.js +291 -0
  25. package/dist/embedder/health.js.map +1 -0
  26. package/dist/eval/index.d.ts +3 -0
  27. package/dist/eval/index.js +2 -0
  28. package/dist/eval/index.js.map +1 -1
  29. package/dist/eval/run-token-test.d.ts +7 -0
  30. package/dist/eval/run-token-test.js +288 -0
  31. package/dist/eval/run-token-test.js.map +1 -0
  32. package/dist/eval/token-analysis.d.ts +111 -0
  33. package/dist/eval/token-analysis.js +286 -0
  34. package/dist/eval/token-analysis.js.map +1 -0
  35. package/dist/eval/token-counter.d.ts +41 -0
  36. package/dist/eval/token-counter.js +102 -0
  37. package/dist/eval/token-counter.js.map +1 -0
  38. package/dist/index.d.ts +2 -1
  39. package/dist/index.js +1 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/indexer.d.ts +1 -1
  42. package/dist/indexer.js +65 -13
  43. package/dist/indexer.js.map +1 -1
  44. package/dist/opencode/tools.js +12 -5
  45. package/dist/opencode/tools.js.map +1 -1
  46. package/dist/plugin.js +71 -29
  47. package/dist/plugin.js.map +1 -1
  48. package/dist/retriever/keyword-index.d.ts +1 -0
  49. package/dist/retriever/keyword-index.js +11 -0
  50. package/dist/retriever/keyword-index.js.map +1 -1
  51. package/dist/retriever/retriever.d.ts +1 -0
  52. package/dist/retriever/retriever.js +49 -7
  53. package/dist/retriever/retriever.js.map +1 -1
  54. package/dist/tui.js +54 -5
  55. package/dist/tui.js.map +1 -1
  56. package/dist/web/api.d.ts +8 -0
  57. package/dist/web/api.js +81 -0
  58. package/dist/web/api.js.map +1 -1
  59. package/dist/web/server.js +31 -0
  60. package/dist/web/server.js.map +1 -1
  61. package/dist/web/ui/app.css +1 -0
  62. package/dist/web/ui/github-dark.css +118 -0
  63. package/dist/web/ui/highlight.min.js +1213 -0
  64. package/dist/web/ui/index.html +504 -125
  65. 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 type { RagConfig, DescriptionConfig } from "./core/config.js";
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;AAIjD,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"}
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
- export async function scanWorkspace(cwd, config) {
40
- const files = await walkFiles(cwd, new Set(config.indexing.includeExtensions), new Set(config.indexing.excludeDirs));
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
- const chunks = await chunkFile(file.filePath, file.content, options.config.chunking?.nodeTypes).catch((err) => {
184
- logger.warn(` ${fileLabel} (chunking failed: ${err.message})`);
185
- return null;
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
- const relPath = path.relative(options.cwd, chunk.metadata.filePath).replace(/\\/g, "/");
233
- chunk.description = `${relPath}, lines ${chunk.metadata.startLine}-${chunk.metadata.endLine}`;
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");