kimiflare 0.65.0 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -401,11 +401,12 @@ var init_logger = __esm({
401
401
  });
402
402
 
403
403
  // src/util/sse.ts
404
- async function* readSSE(stream, signal, idleTimeoutMs) {
404
+ async function* readSSE(stream, signal, idleTimeoutMs, postFirstByteIdleTimeoutMs) {
405
405
  const reader = stream.getReader();
406
406
  const decoder = new TextDecoder("utf-8");
407
407
  let buffer = "";
408
408
  let lastDataAt = Date.now();
409
+ let gotFirstByte = false;
409
410
  const onAbort = () => {
410
411
  reader.cancel(new DOMException("aborted", "AbortError")).catch(() => {
411
412
  });
@@ -427,16 +428,18 @@ async function* readSSE(stream, signal, idleTimeoutMs) {
427
428
  try {
428
429
  while (true) {
429
430
  if (signal?.aborted) throw new DOMException("aborted", "AbortError");
430
- if (idleTimeoutMs !== void 0 && Date.now() - lastDataAt > idleTimeoutMs) {
431
- logger.warn("sse:idle_timeout", { idleTimeoutMs });
431
+ const activeIdleTimeoutMs = gotFirstByte && postFirstByteIdleTimeoutMs !== void 0 ? postFirstByteIdleTimeoutMs : idleTimeoutMs;
432
+ if (activeIdleTimeoutMs !== void 0 && Date.now() - lastDataAt > activeIdleTimeoutMs) {
433
+ logger.warn("sse:idle_timeout", { idleTimeoutMs: activeIdleTimeoutMs, gotFirstByte });
432
434
  throw new DOMException(
433
- `kimiflare: stream idle for ${idleTimeoutMs}ms \u2014 no data received from API`,
435
+ `kimiflare: stream idle for ${activeIdleTimeoutMs}ms \u2014 no data received from API`,
434
436
  "TimeoutError"
435
437
  );
436
438
  }
437
439
  const { done, value } = await abortRace(reader.read());
438
440
  if (done) break;
439
441
  lastDataAt = Date.now();
442
+ gotFirstByte = true;
440
443
  buffer += decoder.decode(value, { stream: true });
441
444
  buffer = buffer.replace(/\r\n/g, "\n");
442
445
  let sep3;
@@ -717,7 +720,7 @@ async function* runKimi(opts2) {
717
720
  if (meta) yield { type: "gateway_meta", meta };
718
721
  let lastUsage = null;
719
722
  logger.debug("runKimi:stream_start", { requestId });
720
- for await (const ev of parseStream(res.body, opts2.signal, opts2.idleTimeoutMs)) {
723
+ for await (const ev of parseStream(res.body, opts2.signal, opts2.idleTimeoutMs, opts2.postFirstByteIdleTimeoutMs)) {
721
724
  if (ev.type === "usage") lastUsage = ev.usage;
722
725
  yield ev;
723
726
  }
@@ -799,12 +802,11 @@ function readGatewayMeta(headers) {
799
802
  if (model) meta.model = model;
800
803
  return Object.keys(meta).length > 0 ? meta : null;
801
804
  }
802
- async function* parseStream(body, signal, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS) {
805
+ async function* parseStream(body, signal, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS, postFirstByteIdleTimeoutMs = DEFAULT_POST_FIRST_BYTE_IDLE_TIMEOUT_MS) {
803
806
  const toolCalls = /* @__PURE__ */ new Map();
804
807
  let lastUsage = null;
805
808
  let finishReason = null;
806
- let lastDataAt = Date.now();
807
- for await (const dataStr of readSSE(body, signal, idleTimeoutMs)) {
809
+ for await (const dataStr of readSSE(body, signal, idleTimeoutMs, postFirstByteIdleTimeoutMs)) {
808
810
  if (dataStr === "[DONE]") break;
809
811
  let chunk = null;
810
812
  try {
@@ -942,7 +944,7 @@ function sleep(ms, signal) {
942
944
  signal?.addEventListener("abort", onAbort, { once: true });
943
945
  });
944
946
  }
945
- var RETRYABLE_CODES, MAX_ATTEMPTS, DEFAULT_IDLE_TIMEOUT_MS;
947
+ var RETRYABLE_CODES, MAX_ATTEMPTS, DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_POST_FIRST_BYTE_IDLE_TIMEOUT_MS;
946
948
  var init_client = __esm({
947
949
  "src/agent/client.ts"() {
948
950
  "use strict";
@@ -954,6 +956,7 @@ var init_client = __esm({
954
956
  RETRYABLE_CODES = /* @__PURE__ */ new Set([3040]);
955
957
  MAX_ATTEMPTS = 5;
956
958
  DEFAULT_IDLE_TIMEOUT_MS = 6e4;
959
+ DEFAULT_POST_FIRST_BYTE_IDLE_TIMEOUT_MS = 3e4;
957
960
  }
958
961
  });
959
962
 
@@ -2155,7 +2158,7 @@ var init_session_state = __esm({
2155
2158
  }
2156
2159
  add(a) {
2157
2160
  while (this.totalChars() + a.raw.length > this.maxTotalChars && this.artifacts.size > 0) {
2158
- this.evictOldest();
2161
+ this.evictSizeWeighted();
2159
2162
  }
2160
2163
  while (this.artifacts.size >= this.maxArtifacts) {
2161
2164
  this.evictOldest();
@@ -2196,6 +2199,21 @@ var init_session_state = __esm({
2196
2199
  }
2197
2200
  if (oldest) this.artifacts.delete(oldest.id);
2198
2201
  }
2202
+ /** Evict the largest artifact among the oldest quartile (by timestamp).
2203
+ * Bounded by the oldest quartile so we never evict freshly-added artifacts;
2204
+ * size-weighted within that window so one big artifact gets dropped instead
2205
+ * of many small ones. */
2206
+ evictSizeWeighted() {
2207
+ const sorted = [...this.artifacts.values()].sort((a, b) => a.ts < b.ts ? -1 : 1);
2208
+ if (sorted.length === 0) return;
2209
+ const quartile = Math.max(1, Math.ceil(sorted.length / 4));
2210
+ const candidates = sorted.slice(0, quartile);
2211
+ let pick3 = candidates[0];
2212
+ for (const a of candidates) {
2213
+ if (a.raw.length > pick3.raw.length) pick3 = a;
2214
+ }
2215
+ this.artifacts.delete(pick3.id);
2216
+ }
2199
2217
  };
2200
2218
  }
2201
2219
  });
@@ -3132,6 +3150,15 @@ var init_system_prompt = __esm({
3132
3150
  });
3133
3151
 
3134
3152
  // src/agent/loop.ts
3153
+ function getSessionWebFetchHistory(sessionId) {
3154
+ const key = sessionId ?? "default";
3155
+ let arr = sessionWebFetchHistory.get(key);
3156
+ if (!arr) {
3157
+ arr = [];
3158
+ sessionWebFetchHistory.set(key, arr);
3159
+ }
3160
+ return arr;
3161
+ }
3135
3162
  function isHighSignalMemory(memory) {
3136
3163
  return memory.topicKey === "project_dependencies" || memory.topicKey === "project_tsconfig" || memory.topicKey === "project_entry_point" || memory.category === "instruction" || memory.category === "preference" || memory.category === "event" && memory.importance >= 3;
3137
3164
  }
@@ -3286,7 +3313,8 @@ Use console.log() to return results. Only console.log output will be sent back t
3286
3313
  const recentToolCalls = [];
3287
3314
  const LOOP_WINDOW = 8;
3288
3315
  const LOOP_THRESHOLD = 2;
3289
- const webFetchHistory = [];
3316
+ const webFetchHistory = getSessionWebFetchHistory(opts2.sessionId);
3317
+ let webFetchesThisTurn = 0;
3290
3318
  const MAX_WEB_FETCH_PER_TURN = 5;
3291
3319
  const WEB_FETCH_DOMAIN_THRESHOLD = 2;
3292
3320
  let cumulativePromptTokens = 0;
@@ -3404,7 +3432,8 @@ Use console.log() to return results. Only console.log output will be sent back t
3404
3432
  cloudMode: opts2.cloudMode,
3405
3433
  cloudToken: opts2.cloudToken,
3406
3434
  cloudDeviceId: opts2.cloudDeviceId,
3407
- idleTimeoutMs: 6e4
3435
+ idleTimeoutMs: opts2.idleTimeoutMs ?? 6e4,
3436
+ postFirstByteIdleTimeoutMs: opts2.postFirstByteIdleTimeoutMs
3408
3437
  });
3409
3438
  let gotFirstChunk = false;
3410
3439
  for await (const ev of events) {
@@ -3454,7 +3483,7 @@ Use console.log() to return results. Only console.log output will be sent back t
3454
3483
  if (lastUsage) {
3455
3484
  opts2.callbacks.onUsageFinal?.(lastUsage, gatewayMeta);
3456
3485
  cumulativePromptTokens += lastUsage.prompt_tokens;
3457
- if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens && toolCalls.length > 0) {
3486
+ if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens) {
3458
3487
  budgetExhausted = true;
3459
3488
  }
3460
3489
  }
@@ -3524,8 +3553,8 @@ Use console.log() to return results. Only console.log output will be sent back t
3524
3553
  try {
3525
3554
  const domain = new URL(url).hostname;
3526
3555
  const domainCount = webFetchHistory.filter((h) => h.domain === domain).length;
3527
- const totalWebFetches = webFetchHistory.length;
3528
- if (totalWebFetches >= MAX_WEB_FETCH_PER_TURN) {
3556
+ const totalSessionFetches = webFetchHistory.length;
3557
+ if (webFetchesThisTurn >= MAX_WEB_FETCH_PER_TURN) {
3529
3558
  const warning = `Research budget exceeded: you have already made ${MAX_WEB_FETCH_PER_TURN} web requests this turn. Synthesize what you have learned instead of fetching more pages.`;
3530
3559
  const budgetResult = {
3531
3560
  tool_call_id: tc.id,
@@ -3546,6 +3575,27 @@ Use console.log() to return results. Only console.log output will be sent back t
3546
3575
  blockedCount++;
3547
3576
  continue;
3548
3577
  }
3578
+ if (totalSessionFetches >= SESSION_WEB_FETCH_CAP) {
3579
+ const warning = `Session research budget exceeded: ${totalSessionFetches} web fetches across this session. Synthesize what you have learned from prior fetches instead of starting another page.`;
3580
+ const sessionCapResult = {
3581
+ tool_call_id: tc.id,
3582
+ name: "web_fetch",
3583
+ content: warning,
3584
+ ok: false
3585
+ };
3586
+ toolResults.push(sessionCapResult);
3587
+ opts2.messages.push({
3588
+ role: "tool",
3589
+ tool_call_id: tc.id,
3590
+ content: sanitizeString(warning),
3591
+ name: "web_fetch"
3592
+ });
3593
+ opts2.callbacks.onToolResult?.(sessionCapResult);
3594
+ recentToolCalls.push(loopSignature);
3595
+ if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
3596
+ blockedCount++;
3597
+ continue;
3598
+ }
3549
3599
  if (domainCount >= WEB_FETCH_DOMAIN_THRESHOLD) {
3550
3600
  const warning = `Loop detected: you have fetched from ${domain} multiple times. Consider a different approach or synthesize existing findings.`;
3551
3601
  const loopResult = {
@@ -3568,6 +3618,7 @@ Use console.log() to return results. Only console.log output will be sent back t
3568
3618
  continue;
3569
3619
  }
3570
3620
  webFetchHistory.push({ url, domain });
3621
+ webFetchesThisTurn++;
3571
3622
  } catch {
3572
3623
  }
3573
3624
  }
@@ -3603,9 +3654,16 @@ Use console.log() to return results. Only console.log output will be sent back t
3603
3654
  Output:
3604
3655
  ${sandboxResult.output}` : sandboxResult.output;
3605
3656
  if (resultContent.length > MAX_TOOL_CONTENT_CHARS) {
3657
+ const rawBytes = resultContent.length;
3606
3658
  resultContent = resultContent.slice(0, MAX_TOOL_CONTENT_CHARS) + `
3607
3659
 
3608
- [truncated: ${resultContent.length - MAX_TOOL_CONTENT_CHARS} chars omitted]`;
3660
+ [truncated: ${rawBytes - MAX_TOOL_CONTENT_CHARS} chars omitted]`;
3661
+ opts2.callbacks.onTruncation?.({
3662
+ tool: "execute_code",
3663
+ toolCallId: tc.id,
3664
+ rawBytes,
3665
+ reducedBytes: resultContent.length
3666
+ });
3609
3667
  }
3610
3668
  const result = {
3611
3669
  tool_call_id: tc.id,
@@ -3634,9 +3692,17 @@ ${sandboxResult.output}` : sandboxResult.output;
3634
3692
  );
3635
3693
  let content2 = result.content;
3636
3694
  if (content2.length > MAX_TOOL_CONTENT_CHARS) {
3695
+ const rawBytes = content2.length;
3637
3696
  content2 = content2.slice(0, MAX_TOOL_CONTENT_CHARS) + `
3638
3697
 
3639
- [truncated: ${content2.length - MAX_TOOL_CONTENT_CHARS} chars omitted]`;
3698
+ [truncated: ${rawBytes - MAX_TOOL_CONTENT_CHARS} chars omitted]`;
3699
+ opts2.callbacks.onTruncation?.({
3700
+ tool: tc.function.name,
3701
+ toolCallId: tc.id,
3702
+ rawBytes,
3703
+ reducedBytes: content2.length,
3704
+ artifactId: result.artifactId
3705
+ });
3640
3706
  }
3641
3707
  logger.debug("turn:tool_end", { sessionId: opts2.sessionId, tool: tc.function.name, toolCallId: tc.id, ok: result.ok });
3642
3708
  toolResults.push(result);
@@ -3660,6 +3726,7 @@ ${sandboxResult.output}` : sandboxResult.output;
3660
3726
  );
3661
3727
  const assistantMessage = lastAssistant?.content ?? "";
3662
3728
  const llmOpts = opts2.memoryManager.getExtractionLlmOpts();
3729
+ const turnAtMemoryCommit = turn;
3663
3730
  for (const extractor of EXTRACTORS) {
3664
3731
  if (extractor.match(tc.function.name, filePath)) {
3665
3732
  void (async () => {
@@ -3685,15 +3752,33 @@ ${sandboxResult.output}` : sandboxResult.output;
3685
3752
  );
3686
3753
  if (isHighSignalMemory(memory)) {
3687
3754
  const sid = opts2.sessionId ?? "default";
3688
- const current = (driftAccumulator.get(sid) ?? 0) + 1;
3689
- driftAccumulator.set(sid, current);
3690
- if (current >= DRIFT_THRESHOLD) {
3755
+ const events2 = driftEvents.get(sid) ?? [];
3756
+ events2.push(turnAtMemoryCommit);
3757
+ const cutoff = turnAtMemoryCommit - DRIFT_WINDOW + 1;
3758
+ const recent = events2.filter((t) => t >= cutoff);
3759
+ driftEvents.set(sid, recent);
3760
+ if (recent.length >= DRIFT_THRESHOLD) {
3691
3761
  opts2.callbacks.onKimiMdStale?.();
3692
- driftAccumulator.set(sid, 0);
3762
+ driftEvents.set(sid, []);
3693
3763
  }
3694
3764
  }
3695
3765
  }
3696
- } catch {
3766
+ } catch (err) {
3767
+ const sid = opts2.sessionId ?? "default";
3768
+ const next = (memoryExtractionErrorCounts.get(sid) ?? 0) + 1;
3769
+ memoryExtractionErrorCounts.set(sid, next);
3770
+ const msg = err instanceof Error ? err.message : String(err);
3771
+ logger.debug("memory:extract_error", {
3772
+ sessionId: opts2.sessionId,
3773
+ tool: tc.function.name,
3774
+ count: next,
3775
+ error: msg
3776
+ });
3777
+ if (next === 1) {
3778
+ opts2.callbacks.onWarning?.(
3779
+ `[memory] auto-extraction failed (${msg}). Subsequent failures will be counted silently; check /memory health.`
3780
+ );
3781
+ }
3697
3782
  }
3698
3783
  })();
3699
3784
  }
@@ -3706,12 +3791,6 @@ ${sandboxResult.output}` : sandboxResult.output;
3706
3791
  if (blockedCount === toolCalls.length && toolCalls.length > 0) {
3707
3792
  loopExhausted = true;
3708
3793
  }
3709
- if (opts2.sessionId) {
3710
- const current = driftAccumulator.get(opts2.sessionId) ?? 0;
3711
- if (current > 0) {
3712
- driftAccumulator.set(opts2.sessionId, Math.max(0, current - 1));
3713
- }
3714
- }
3715
3794
  if (opts2.onIterationEnd) {
3716
3795
  opts2.messages = await opts2.onIterationEnd(opts2.messages, opts2.signal);
3717
3796
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
@@ -3770,7 +3849,7 @@ function validateToolArguments(raw) {
3770
3849
  return "{}";
3771
3850
  }
3772
3851
  }
3773
- var BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
3852
+ var BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftEvents, DRIFT_WINDOW, DRIFT_THRESHOLD, memoryExtractionErrorCounts, sessionWebFetchHistory, SESSION_WEB_FETCH_CAP, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
3774
3853
  var init_loop = __esm({
3775
3854
  "src/agent/loop.ts"() {
3776
3855
  "use strict";
@@ -3798,8 +3877,12 @@ var init_loop = __esm({
3798
3877
  }
3799
3878
  };
3800
3879
  codeModeApiCache = /* @__PURE__ */ new Map();
3801
- driftAccumulator = /* @__PURE__ */ new Map();
3802
- DRIFT_THRESHOLD = 5;
3880
+ driftEvents = /* @__PURE__ */ new Map();
3881
+ DRIFT_WINDOW = 10;
3882
+ DRIFT_THRESHOLD = 3;
3883
+ memoryExtractionErrorCounts = /* @__PURE__ */ new Map();
3884
+ sessionWebFetchHistory = /* @__PURE__ */ new Map();
3885
+ SESSION_WEB_FETCH_CAP = 25;
3803
3886
  MAX_PROMPT_TOKENS = 24e4;
3804
3887
  MAX_TOOL_CONTENT_CHARS = 1e4;
3805
3888
  }
@@ -3867,10 +3950,13 @@ var init_read = __esm({
3867
3950
  needsPermission: false,
3868
3951
  render: ({ path }) => ({ title: `read ${collapsePath(path, process.cwd())}` }),
3869
3952
  async run(args, ctx) {
3953
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3870
3954
  const abs = resolvePath(ctx.cwd, args.path);
3871
3955
  const st = await stat2(abs);
3872
3956
  if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
3873
- const text = await readFile3(abs, "utf8");
3957
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3958
+ const text = await readFile3(abs, { encoding: "utf8", signal: ctx.signal });
3959
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3874
3960
  const lines = text.split("\n");
3875
3961
  const start = Math.max(0, (args.offset ?? 1) - 1);
3876
3962
  const end = args.limit ? Math.min(lines.length, start + args.limit) : lines.length;
@@ -4162,14 +4248,31 @@ var init_glob = __esm({
4162
4248
  needsPermission: false,
4163
4249
  render: (args) => ({ title: `glob ${args.pattern ?? ""}${args.path ? ` in ${collapsePath(String(args.path), process.cwd())}` : ""}` }),
4164
4250
  async run(args, ctx) {
4251
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4165
4252
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
4166
- const entries = await fg(args.pattern, {
4253
+ const stream = fg.stream(args.pattern, {
4167
4254
  cwd: root,
4168
4255
  absolute: true,
4169
4256
  dot: false,
4170
4257
  onlyFiles: false,
4171
4258
  stats: true
4172
4259
  });
4260
+ const entries = [];
4261
+ const onAbort = () => {
4262
+ try {
4263
+ stream.destroy(new DOMException("aborted", "AbortError"));
4264
+ } catch {
4265
+ }
4266
+ };
4267
+ ctx.signal?.addEventListener("abort", onAbort, { once: true });
4268
+ try {
4269
+ for await (const entry of stream) {
4270
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4271
+ entries.push(entry);
4272
+ }
4273
+ } finally {
4274
+ ctx.signal?.removeEventListener("abort", onAbort);
4275
+ }
4173
4276
  entries.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
4174
4277
  const paths = entries.slice(0, 200).map((e) => e.path);
4175
4278
  return paths.length ? paths.join("\n") : "(no matches)";
@@ -4193,14 +4296,14 @@ async function hasRipgrep() {
4193
4296
  }
4194
4297
  return cachedHasRg;
4195
4298
  }
4196
- async function runRipgrep(args, root, mode) {
4299
+ async function runRipgrep(args, root, mode, signal) {
4197
4300
  const rgArgs = ["--no-heading", "--color=never", "--line-number"];
4198
4301
  if (args.case_insensitive) rgArgs.push("-i");
4199
4302
  if (args.glob) rgArgs.push("--glob", args.glob);
4200
4303
  if (mode === "files") rgArgs.push("-l");
4201
4304
  rgArgs.push("--", args.pattern, root);
4202
4305
  try {
4203
- const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024 });
4306
+ const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024, signal });
4204
4307
  const trimmed = stdout.trim();
4205
4308
  if (!trimmed) return { content: "(no matches)", rawBytes: 0, reducedBytes: 0 };
4206
4309
  return {
@@ -4214,7 +4317,7 @@ async function runRipgrep(args, root, mode) {
4214
4317
  throw new Error(err.stderr || String(e));
4215
4318
  }
4216
4319
  }
4217
- async function runJsFallback(args, root, mode) {
4320
+ async function runJsFallback(args, root, mode, signal) {
4218
4321
  const re = new RegExp(args.pattern, args.case_insensitive ? "i" : "");
4219
4322
  const globPattern = args.glob ? `**/${args.glob}` : "**/*";
4220
4323
  const files = await fg2(globPattern, {
@@ -4225,7 +4328,9 @@ async function runJsFallback(args, root, mode) {
4225
4328
  ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"]
4226
4329
  });
4227
4330
  const out = [];
4228
- for (const file of files.slice(0, 5e3)) {
4331
+ for (let fi = 0; fi < Math.min(files.length, 5e3); fi++) {
4332
+ if (signal?.aborted) throw new DOMException("aborted", "AbortError");
4333
+ const file = files[fi];
4229
4334
  try {
4230
4335
  const content = await readFile6(file, "utf8");
4231
4336
  if (mode === "files") {
@@ -4280,10 +4385,11 @@ var init_grep = __esm({
4280
4385
  needsPermission: false,
4281
4386
  render: (args) => ({ title: `grep ${args.pattern ?? ""}${args.glob ? ` (${args.glob})` : ""}` }),
4282
4387
  async run(args, ctx) {
4388
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4283
4389
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
4284
4390
  const mode = args.output_mode ?? "content";
4285
- if (await hasRipgrep()) return runRipgrep(args, root, mode);
4286
- return runJsFallback(args, root, mode);
4391
+ if (await hasRipgrep()) return runRipgrep(args, root, mode, ctx.signal);
4392
+ return runJsFallback(args, root, mode, ctx.signal);
4287
4393
  }
4288
4394
  };
4289
4395
  }