@takara-ai/miru-code 1.5.0 → 1.5.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/package.json +1 -1
- package/src/benchmark/grep.ts +99 -72
- package/src/benchmark/rg-literal.ts +3 -9
- package/src/credentials.ts +40 -39
- package/src/git.ts +3 -8
- package/src/help.ts +4 -0
- package/src/mcp/server.ts +61 -44
- package/src/setup.ts +11 -1
package/package.json
CHANGED
package/src/benchmark/grep.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Native grep baseline for Miru benchmark comparisons. */
|
|
2
2
|
|
|
3
3
|
import { relative } from "node:path";
|
|
4
|
+
import { envOptionalInt } from "../env.ts";
|
|
4
5
|
import { countTokens } from "../token-count.ts";
|
|
5
6
|
|
|
6
7
|
const STOPWORDS = new Set(
|
|
@@ -12,6 +13,9 @@ const STOPWORDS = new Set(
|
|
|
12
13
|
export const GREP_LINES_PER_FILE = 3;
|
|
13
14
|
export const GREP_CONTEXT = 2;
|
|
14
15
|
|
|
16
|
+
/** Default cap for each rg/grep/findstr spawn in benchmark mode (seconds). */
|
|
17
|
+
const DEFAULT_BENCHMARK_SEARCH_TIMEOUT_SEC = 10;
|
|
18
|
+
|
|
15
19
|
/** Shared `-g` excludes for ripgrep baselines (search + locate). */
|
|
16
20
|
export const RG_EXCLUDE_ARGS = [
|
|
17
21
|
"-g",
|
|
@@ -24,6 +28,44 @@ export const RG_EXCLUDE_ARGS = [
|
|
|
24
28
|
"!tokenizer/**",
|
|
25
29
|
] as const;
|
|
26
30
|
|
|
31
|
+
export class BenchmarkSearchTimeoutError extends Error {
|
|
32
|
+
constructor(timeoutSec: number) {
|
|
33
|
+
super(`Benchmark search timed out after ${timeoutSec}s`);
|
|
34
|
+
this.name = "BenchmarkSearchTimeoutError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `Bun.spawn` for benchmark baselines, killed after MIRU_BENCHMARK_SEARCH_TIMEOUT. */
|
|
39
|
+
export async function spawnBenchmarkSearch(args: string[], cwd?: string): Promise<string> {
|
|
40
|
+
const timeoutSec =
|
|
41
|
+
envOptionalInt(["MIRU_BENCHMARK_SEARCH_TIMEOUT"], 1) ?? DEFAULT_BENCHMARK_SEARCH_TIMEOUT_SEC;
|
|
42
|
+
const proc = Bun.spawn(args, {
|
|
43
|
+
stdout: "pipe",
|
|
44
|
+
stderr: "pipe",
|
|
45
|
+
cwd,
|
|
46
|
+
timeout: timeoutSec * 1000,
|
|
47
|
+
});
|
|
48
|
+
const text = await new Response(proc.stdout).text();
|
|
49
|
+
await proc.exited;
|
|
50
|
+
// Prefer signalCode over killed — Bun may set killed=true after a normal exit.
|
|
51
|
+
if (proc.signalCode != null) {
|
|
52
|
+
throw new BenchmarkSearchTimeoutError(timeoutSec);
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Run a Grep baseline; return null on timeout so Miru results can still be returned. */
|
|
58
|
+
export async function withGrepTimeoutFallback<T>(run: () => Promise<T>): Promise<T | null> {
|
|
59
|
+
try {
|
|
60
|
+
return await run();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err instanceof BenchmarkSearchTimeoutError) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
27
69
|
export interface GrepFileHit {
|
|
28
70
|
file: string;
|
|
29
71
|
matchCount: number;
|
|
@@ -172,34 +214,31 @@ export async function grepSearch(
|
|
|
172
214
|
}
|
|
173
215
|
|
|
174
216
|
async function rgRankedMatches(repoRoot: string, pattern: string, topK: number) {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
217
|
+
const countText = await spawnBenchmarkSearch([
|
|
218
|
+
"rg",
|
|
219
|
+
"-i",
|
|
220
|
+
"--count-matches",
|
|
221
|
+
pattern,
|
|
222
|
+
repoRoot,
|
|
223
|
+
...RG_EXCLUDE_ARGS,
|
|
224
|
+
]);
|
|
181
225
|
return parseCountMatches(repoRoot, countText, topK);
|
|
182
226
|
}
|
|
183
227
|
|
|
184
228
|
async function grepRankedMatches(repoRoot: string, pattern: string, topK: number) {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
],
|
|
199
|
-
{ stdout: "pipe", stderr: "pipe" },
|
|
200
|
-
);
|
|
201
|
-
const countText = await new Response(countProc.stdout).text();
|
|
202
|
-
await countProc.exited;
|
|
229
|
+
const countText = await spawnBenchmarkSearch([
|
|
230
|
+
"grep",
|
|
231
|
+
"-R",
|
|
232
|
+
"-I",
|
|
233
|
+
"-i",
|
|
234
|
+
"-E",
|
|
235
|
+
"-o",
|
|
236
|
+
"--exclude-dir=node_modules",
|
|
237
|
+
"--exclude-dir=.git",
|
|
238
|
+
"--exclude-dir=tokenizer",
|
|
239
|
+
pattern,
|
|
240
|
+
repoRoot,
|
|
241
|
+
]);
|
|
203
242
|
const counts = new Map<string, number>();
|
|
204
243
|
for (const line of countText.split("\n")) {
|
|
205
244
|
if (!line) {
|
|
@@ -240,13 +279,10 @@ function splitSearchToolLine(line: string): { path: string; rest: string } | nul
|
|
|
240
279
|
async function findstrRankedMatches(repoRoot: string, keywords: string[], topK: number) {
|
|
241
280
|
const counts = new Map<string, number>();
|
|
242
281
|
for (const keyword of keywords) {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
const text = await new Response(proc.stdout).text();
|
|
249
|
-
await proc.exited;
|
|
282
|
+
const text = await spawnBenchmarkSearch(
|
|
283
|
+
["findstr", "/S", "/N", "/I", "/P", `/C:${keyword}`, "*"],
|
|
284
|
+
repoRoot,
|
|
285
|
+
);
|
|
250
286
|
for (const line of text.split("\n")) {
|
|
251
287
|
const parsed = parsePathLinePrefix(line);
|
|
252
288
|
if (!parsed?.path) {
|
|
@@ -302,51 +338,42 @@ function parseCountMatches(repoRoot: string, countText: string, topK: number) {
|
|
|
302
338
|
}
|
|
303
339
|
|
|
304
340
|
async function rgFilePreview(absPath: string, pattern: string): Promise<string> {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
],
|
|
317
|
-
{ stdout: "pipe", stderr: "pipe" },
|
|
318
|
-
);
|
|
319
|
-
const output = await new Response(proc.stdout).text();
|
|
320
|
-
await proc.exited;
|
|
321
|
-
return output;
|
|
341
|
+
return spawnBenchmarkSearch([
|
|
342
|
+
"rg",
|
|
343
|
+
"-i",
|
|
344
|
+
"-n",
|
|
345
|
+
"-C",
|
|
346
|
+
String(GREP_CONTEXT),
|
|
347
|
+
"-m",
|
|
348
|
+
String(GREP_LINES_PER_FILE),
|
|
349
|
+
pattern,
|
|
350
|
+
absPath,
|
|
351
|
+
]);
|
|
322
352
|
}
|
|
323
353
|
|
|
324
354
|
async function grepFilePreview(absPath: string, pattern: string): Promise<string> {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
],
|
|
339
|
-
{ stdout: "pipe", stderr: "pipe" },
|
|
340
|
-
);
|
|
341
|
-
const output = await new Response(proc.stdout).text();
|
|
342
|
-
await proc.exited;
|
|
343
|
-
return output;
|
|
355
|
+
return spawnBenchmarkSearch([
|
|
356
|
+
"grep",
|
|
357
|
+
"-I",
|
|
358
|
+
"-i",
|
|
359
|
+
"-n",
|
|
360
|
+
"-E",
|
|
361
|
+
"-C",
|
|
362
|
+
String(GREP_CONTEXT),
|
|
363
|
+
"-m",
|
|
364
|
+
String(GREP_LINES_PER_FILE),
|
|
365
|
+
pattern,
|
|
366
|
+
absPath,
|
|
367
|
+
]);
|
|
344
368
|
}
|
|
345
369
|
|
|
346
370
|
async function findstrFilePreview(absPath: string, keywords: string[]): Promise<string> {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
371
|
+
return spawnBenchmarkSearch([
|
|
372
|
+
"findstr",
|
|
373
|
+
"/N",
|
|
374
|
+
"/I",
|
|
375
|
+
"/P",
|
|
376
|
+
...keywords.map((k) => `/C:${k}`),
|
|
377
|
+
absPath,
|
|
378
|
+
]);
|
|
352
379
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { relative } from "node:path";
|
|
4
4
|
import { countTokens } from "../token-count.ts";
|
|
5
|
-
import { RG_EXCLUDE_ARGS, selectBenchmarkSearchTool } from "./grep.ts";
|
|
5
|
+
import { RG_EXCLUDE_ARGS, selectBenchmarkSearchTool, spawnBenchmarkSearch } from "./grep.ts";
|
|
6
6
|
|
|
7
7
|
export interface RgLiteralOutput {
|
|
8
8
|
text: string;
|
|
@@ -76,16 +76,10 @@ export async function rgLiteralOutput(
|
|
|
76
76
|
throw new Error("No search tool found in PATH (tried rg, grep, and findstr on Windows)");
|
|
77
77
|
}
|
|
78
78
|
const start = performance.now();
|
|
79
|
-
const
|
|
79
|
+
const text = await spawnBenchmarkSearch(
|
|
80
80
|
buildLiteralArgs(tool, repoRoot, literals, context, maxCount, !!options.ignoreCase),
|
|
81
|
-
|
|
82
|
-
stdout: "pipe",
|
|
83
|
-
stderr: "pipe",
|
|
84
|
-
...(tool === "findstr" ? { cwd: repoRoot } : {}),
|
|
85
|
-
},
|
|
81
|
+
tool === "findstr" ? repoRoot : undefined,
|
|
86
82
|
);
|
|
87
|
-
const text = await new Response(proc.stdout).text();
|
|
88
|
-
await proc.exited;
|
|
89
83
|
const latency_ms = performance.now() - start;
|
|
90
84
|
const stats = parseRgLiteralStats(text, repoRoot);
|
|
91
85
|
return { text, tokens: countTokens(text), latency_ms, ...stats };
|
package/src/credentials.ts
CHANGED
|
@@ -68,28 +68,49 @@ function hydrateSageMakerEnv(sagemaker: StoredSageMakerCredentials): void {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return false;
|
|
71
|
+
function clearSageMakerEnv(sagemaker?: StoredSageMakerCredentials): void {
|
|
72
|
+
delete process.env.MIRU_SAGEMAKER_ENDPOINT_ARN;
|
|
73
|
+
delete process.env.MIRU_SAGEMAKER_ENDPOINT_NAME;
|
|
74
|
+
delete process.env.MIRU_SAGEMAKER_REGION;
|
|
75
|
+
if (sagemaker?.profile && process.env.AWS_PROFILE === sagemaker.profile) {
|
|
76
|
+
delete process.env.AWS_PROFILE;
|
|
78
77
|
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function clearTakaraEnv(): void {
|
|
81
|
+
delete process.env.TAKARA_API_KEY;
|
|
82
|
+
}
|
|
79
83
|
|
|
84
|
+
/** Apply credentials.json as the sole mode — never leave Takara and SageMaker both active. */
|
|
85
|
+
export async function loadStoredCredentials(): Promise<boolean> {
|
|
86
|
+
normalizeTakaraApiKeyEnv();
|
|
80
87
|
const stored = await readStoredCredentials();
|
|
81
88
|
if (!stored) {
|
|
82
89
|
return false;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
let changed = false;
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
if (stored.takara_api_key) {
|
|
94
|
+
if (!hasTakaraApiKeyInEnv()) {
|
|
95
|
+
process.env.TAKARA_API_KEY = stored.takara_api_key;
|
|
96
|
+
changed = true;
|
|
97
|
+
}
|
|
98
|
+
if (process.env.MIRU_SAGEMAKER_ENDPOINT_ARN || process.env.MIRU_SAGEMAKER_ENDPOINT_NAME) {
|
|
99
|
+
clearSageMakerEnv(stored.sagemaker);
|
|
100
|
+
changed = true;
|
|
101
|
+
}
|
|
102
|
+
return changed;
|
|
89
103
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
|
|
105
|
+
if (stored.sagemaker) {
|
|
106
|
+
if (!process.env.MIRU_SAGEMAKER_ENDPOINT_ARN?.trim()) {
|
|
107
|
+
hydrateSageMakerEnv(stored.sagemaker);
|
|
108
|
+
changed = true;
|
|
109
|
+
}
|
|
110
|
+
if (hasTakaraApiKeyInEnv()) {
|
|
111
|
+
clearTakaraEnv();
|
|
112
|
+
changed = true;
|
|
113
|
+
}
|
|
93
114
|
}
|
|
94
115
|
return changed;
|
|
95
116
|
}
|
|
@@ -98,9 +119,7 @@ export async function saveStoredCredentials(apiKey: string): Promise<string> {
|
|
|
98
119
|
const dir = resolveCredentialsDir();
|
|
99
120
|
const path = resolveCredentialsPath();
|
|
100
121
|
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
101
|
-
const
|
|
102
|
-
// Takara and SageMaker modes are mutually exclusive — saving a key drops any stored endpoint.
|
|
103
|
-
const previousSageMaker = existing?.sagemaker;
|
|
122
|
+
const previousSageMaker = (await readStoredCredentials())?.sagemaker;
|
|
104
123
|
const payload: StoredCredentials = {
|
|
105
124
|
version: CREDENTIALS_VERSION,
|
|
106
125
|
takara_api_key: apiKey,
|
|
@@ -111,14 +130,7 @@ export async function saveStoredCredentials(apiKey: string): Promise<string> {
|
|
|
111
130
|
} catch {
|
|
112
131
|
// Windows may not support Unix mode bits on all filesystems.
|
|
113
132
|
}
|
|
114
|
-
|
|
115
|
-
if (process.env.MIRU_SAGEMAKER_ENDPOINT_ARN === previousSageMaker.endpoint_arn) {
|
|
116
|
-
delete process.env.MIRU_SAGEMAKER_ENDPOINT_ARN;
|
|
117
|
-
}
|
|
118
|
-
if (previousSageMaker.profile && process.env.AWS_PROFILE === previousSageMaker.profile) {
|
|
119
|
-
delete process.env.AWS_PROFILE;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
133
|
+
clearSageMakerEnv(previousSageMaker);
|
|
122
134
|
return path;
|
|
123
135
|
}
|
|
124
136
|
|
|
@@ -128,9 +140,6 @@ export async function saveStoredSageMakerCredentials(
|
|
|
128
140
|
const dir = resolveCredentialsDir();
|
|
129
141
|
const path = resolveCredentialsPath();
|
|
130
142
|
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
131
|
-
const existing = await readStoredCredentials();
|
|
132
|
-
// Takara and SageMaker modes are mutually exclusive — saving an endpoint drops any stored key.
|
|
133
|
-
const previousTakaraKey = existing?.takara_api_key;
|
|
134
143
|
const payload: StoredCredentials = {
|
|
135
144
|
version: CREDENTIALS_VERSION,
|
|
136
145
|
sagemaker,
|
|
@@ -141,9 +150,7 @@ export async function saveStoredSageMakerCredentials(
|
|
|
141
150
|
} catch {
|
|
142
151
|
// Windows may not support Unix mode bits on all filesystems.
|
|
143
152
|
}
|
|
144
|
-
|
|
145
|
-
delete process.env.TAKARA_API_KEY;
|
|
146
|
-
}
|
|
153
|
+
clearTakaraEnv();
|
|
147
154
|
return path;
|
|
148
155
|
}
|
|
149
156
|
|
|
@@ -157,16 +164,10 @@ export async function clearStoredCredentials(): Promise<{ cleared: boolean; path
|
|
|
157
164
|
await Bun.file(path).delete();
|
|
158
165
|
|
|
159
166
|
if (stored?.takara_api_key && process.env.TAKARA_API_KEY === stored.takara_api_key) {
|
|
160
|
-
|
|
167
|
+
clearTakaraEnv();
|
|
161
168
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (process.env.MIRU_SAGEMAKER_ENDPOINT_ARN === sagemaker.endpoint_arn) {
|
|
165
|
-
delete process.env.MIRU_SAGEMAKER_ENDPOINT_ARN;
|
|
166
|
-
}
|
|
167
|
-
if (sagemaker.profile && process.env.AWS_PROFILE === sagemaker.profile) {
|
|
168
|
-
delete process.env.AWS_PROFILE;
|
|
169
|
-
}
|
|
169
|
+
if (stored?.sagemaker) {
|
|
170
|
+
clearSageMakerEnv(stored.sagemaker);
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
return { cleared: true, path };
|
package/src/git.ts
CHANGED
|
@@ -20,22 +20,17 @@ export async function cloneGitRepository(url: string, ref?: string | null): Prom
|
|
|
20
20
|
stdout: "pipe",
|
|
21
21
|
stderr: "pipe",
|
|
22
22
|
stdin: "ignore",
|
|
23
|
+
timeout: timeoutSec * 1000,
|
|
23
24
|
});
|
|
24
25
|
} catch {
|
|
25
26
|
await rm(dir, { recursive: true, force: true });
|
|
26
27
|
throw new Error("git is not installed or not on PATH");
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
let timedOut = false;
|
|
30
|
-
const timer = setTimeout(() => {
|
|
31
|
-
timedOut = true;
|
|
32
|
-
proc.kill();
|
|
33
|
-
}, timeoutSec * 1000);
|
|
34
|
-
|
|
35
30
|
const code = await proc.exited;
|
|
36
|
-
clearTimeout(timer);
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
// Prefer signalCode over killed — Bun may set killed=true after a normal exit.
|
|
33
|
+
if (proc.signalCode != null) {
|
|
39
34
|
await rm(dir, { recursive: true, force: true });
|
|
40
35
|
throw new Error(`git clone timed out for ${url} (limit: ${timeoutSec}s)`);
|
|
41
36
|
}
|
package/src/help.ts
CHANGED
|
@@ -202,6 +202,10 @@ export function printCommandHelp(command: string): void {
|
|
|
202
202
|
writeStdout("to a Grep baseline (rg search + read of the top matched file),");
|
|
203
203
|
writeStdout("not bare chunk content and not the agent's full tool chain.");
|
|
204
204
|
writeStdout("Local paths only — git URL repos skip comparison with benchmark_skipped.");
|
|
205
|
+
writeStdout(
|
|
206
|
+
"Cap each rg/grep spawn with MIRU_BENCHMARK_SEARCH_TIMEOUT (seconds, default 10).",
|
|
207
|
+
);
|
|
208
|
+
writeStdout("On timeout Miru still returns results with benchmark_skipped=grep_timeout.");
|
|
205
209
|
writeStdout("");
|
|
206
210
|
return;
|
|
207
211
|
case "init":
|
package/src/mcp/server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import packageJson from "../../package.json";
|
|
3
3
|
import { benchmarkSearchComparison, toAgentBenchmarkSummary } from "../benchmark/compare.ts";
|
|
4
|
+
import { withGrepTimeoutFallback } from "../benchmark/grep.ts";
|
|
4
5
|
import {
|
|
5
6
|
appendBenchmarkQuery,
|
|
6
7
|
readBenchmarkRollup,
|
|
@@ -46,16 +47,32 @@ const REPO_DESCRIPTION =
|
|
|
46
47
|
"Pass the project root for local workspaces. " +
|
|
47
48
|
"The index is built on the first tool call and cached for the session.";
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
-
"Benchmark comparisons require a local repo path; git URL repos are skipped."
|
|
50
|
+
const BENCHMARK_SKIP_NOTES = {
|
|
51
|
+
local_repo_only: "Benchmark comparisons require a local repo path; git URL repos are skipped.",
|
|
52
|
+
grep_timeout:
|
|
53
|
+
"Benchmark Grep baseline timed out; Miru results are still returned. " +
|
|
54
|
+
"Raise MIRU_BENCHMARK_SEARCH_TIMEOUT (seconds) if needed.",
|
|
55
|
+
} as const;
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
type BenchmarkSkipReason = keyof typeof BENCHMARK_SKIP_NOTES;
|
|
58
|
+
|
|
59
|
+
function withBenchmarkSkippedNote(body: string, reason: BenchmarkSkipReason): string {
|
|
53
60
|
return `${body}\n\n${JSON.stringify({
|
|
54
|
-
benchmark_skipped:
|
|
55
|
-
note:
|
|
61
|
+
benchmark_skipped: reason,
|
|
62
|
+
note: BENCHMARK_SKIP_NOTES[reason],
|
|
56
63
|
})}`;
|
|
57
64
|
}
|
|
58
65
|
|
|
66
|
+
async function persistBenchmarkQuery(
|
|
67
|
+
...args: Parameters<typeof appendBenchmarkQuery>
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
try {
|
|
70
|
+
await appendBenchmarkQuery(...args);
|
|
71
|
+
} catch {
|
|
72
|
+
// History is best-effort; never fail the tool on persist errors.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
export function createMcpServer(
|
|
60
77
|
cache: IndexCache,
|
|
61
78
|
options?: { benchmark?: boolean },
|
|
@@ -101,29 +118,33 @@ export function createMcpServer(
|
|
|
101
118
|
const repoRoot = localRepoRoot(repo);
|
|
102
119
|
const k = clampMcpTopK(topK);
|
|
103
120
|
|
|
121
|
+
let skip: BenchmarkSkipReason | undefined;
|
|
104
122
|
if (benchmark && repoRoot) {
|
|
105
|
-
const comparison = await
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (results.length === 0) {
|
|
114
|
-
return toolText("No results found.");
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
await appendBenchmarkQuery(recordFromBenchmark(repoRoot, comparison.benchmark));
|
|
118
|
-
} catch {
|
|
119
|
-
// History is best-effort; never fail the search on persist errors.
|
|
120
|
-
}
|
|
121
|
-
const body = formatResultsText(
|
|
122
|
-
formatResults(query, results, { repoRoot, snippet: true }),
|
|
123
|
-
);
|
|
124
|
-
return toolText(
|
|
125
|
-
appendAgentBenchmark(body, toAgentBenchmarkSummary(comparison.benchmark)),
|
|
123
|
+
const comparison = await withGrepTimeoutFallback(() =>
|
|
124
|
+
benchmarkSearchComparison({
|
|
125
|
+
query,
|
|
126
|
+
repoPath: repoRoot,
|
|
127
|
+
index,
|
|
128
|
+
topK: k,
|
|
129
|
+
dedupeByFile: dedupeByFile !== false,
|
|
130
|
+
}),
|
|
126
131
|
);
|
|
132
|
+
if (comparison) {
|
|
133
|
+
const results = comparison.results;
|
|
134
|
+
if (results.length === 0) {
|
|
135
|
+
return toolText("No results found.");
|
|
136
|
+
}
|
|
137
|
+
await persistBenchmarkQuery(recordFromBenchmark(repoRoot, comparison.benchmark));
|
|
138
|
+
const body = formatResultsText(
|
|
139
|
+
formatResults(query, results, { repoRoot, snippet: true }),
|
|
140
|
+
);
|
|
141
|
+
return toolText(
|
|
142
|
+
appendAgentBenchmark(body, toAgentBenchmarkSummary(comparison.benchmark)),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
skip = "grep_timeout";
|
|
146
|
+
} else if (benchmark && !repoRoot) {
|
|
147
|
+
skip = "local_repo_only";
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
let results = await index.search({ query, topK: k });
|
|
@@ -135,10 +156,7 @@ export function createMcpServer(
|
|
|
135
156
|
}
|
|
136
157
|
const payload = formatResults(query, results, { repoRoot, snippet: true });
|
|
137
158
|
const body = formatResultsText(payload);
|
|
138
|
-
|
|
139
|
-
return toolText(withBenchmarkSkippedNote(body));
|
|
140
|
-
}
|
|
141
|
-
return toolText(body);
|
|
159
|
+
return toolText(skip ? withBenchmarkSkippedNote(body, skip) : body);
|
|
142
160
|
} catch (err) {
|
|
143
161
|
return toolText(err instanceof Error ? err.message : String(err));
|
|
144
162
|
}
|
|
@@ -200,32 +218,31 @@ export function createMcpServer(
|
|
|
200
218
|
try {
|
|
201
219
|
const index = await getIndexForRepo(repo, cache);
|
|
202
220
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
221
|
+
let skip: BenchmarkSkipReason | undefined;
|
|
222
|
+
const repoRoot = localRepoRoot(repo);
|
|
223
|
+
if (benchmark && repoRoot && typeof literal === "string") {
|
|
224
|
+
const comparison = await withGrepTimeoutFallback(() =>
|
|
225
|
+
benchmarkLocateComparison({
|
|
207
226
|
literal,
|
|
208
227
|
repoPath: repoRoot,
|
|
209
228
|
index,
|
|
210
229
|
locate: locateOpts,
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// History is best-effort; never fail locate on persist errors.
|
|
216
|
-
}
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
if (comparison) {
|
|
233
|
+
await persistBenchmarkQuery(recordFromAgentSummary(repoRoot, comparison.benchmark));
|
|
217
234
|
const body = formatLiteralLocateText(comparison.payload);
|
|
218
235
|
return toolText(appendAgentBenchmark(body, comparison.benchmark));
|
|
219
236
|
}
|
|
237
|
+
skip = "grep_timeout";
|
|
238
|
+
} else if (benchmark && !repoRoot) {
|
|
239
|
+
skip = "local_repo_only";
|
|
220
240
|
}
|
|
221
241
|
|
|
222
242
|
const result = index.locateLiteral(literal, locateOpts);
|
|
223
243
|
const payload = formatLiteralLocate(result);
|
|
224
244
|
const body = formatLiteralLocateText(payload);
|
|
225
|
-
|
|
226
|
-
return toolText(withBenchmarkSkippedNote(body));
|
|
227
|
-
}
|
|
228
|
-
return toolText(body);
|
|
245
|
+
return toolText(skip ? withBenchmarkSkippedNote(body, skip) : body);
|
|
229
246
|
} catch (err) {
|
|
230
247
|
return toolText(err instanceof Error ? err.message : String(err));
|
|
231
248
|
}
|
package/src/setup.ts
CHANGED
|
@@ -206,7 +206,17 @@ export async function runSetup(options: RunSetupOptions = {}): Promise<RunSetupR
|
|
|
206
206
|
if (!options.force && hasTakaraApiKeyInEnv()) {
|
|
207
207
|
const path = resolveCredentialsPath();
|
|
208
208
|
const stored = await readStoredCredentials();
|
|
209
|
-
if (stored) {
|
|
209
|
+
if (stored?.sagemaker) {
|
|
210
|
+
const apiKey = resolveEmbeddingApiKey();
|
|
211
|
+
await saveStoredCredentials(apiKey);
|
|
212
|
+
process.env.TAKARA_API_KEY = apiKey;
|
|
213
|
+
writeStdout("");
|
|
214
|
+
success(`Saved credentials to ${path}`);
|
|
215
|
+
hint("Removed the stored SageMaker endpoint — Miru now embeds only via Takara.");
|
|
216
|
+
writeStdout("");
|
|
217
|
+
return { path, newlySaved: true };
|
|
218
|
+
}
|
|
219
|
+
if (stored?.takara_api_key) {
|
|
210
220
|
info(`API key already configured (env + ${path}). Use --force to replace stored key.`);
|
|
211
221
|
return { path, newlySaved: false };
|
|
212
222
|
}
|