kimiflare 0.65.0 → 0.66.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
  });
@@ -3404,7 +3422,8 @@ Use console.log() to return results. Only console.log output will be sent back t
3404
3422
  cloudMode: opts2.cloudMode,
3405
3423
  cloudToken: opts2.cloudToken,
3406
3424
  cloudDeviceId: opts2.cloudDeviceId,
3407
- idleTimeoutMs: 6e4
3425
+ idleTimeoutMs: opts2.idleTimeoutMs ?? 6e4,
3426
+ postFirstByteIdleTimeoutMs: opts2.postFirstByteIdleTimeoutMs
3408
3427
  });
3409
3428
  let gotFirstChunk = false;
3410
3429
  for await (const ev of events) {
@@ -3454,7 +3473,7 @@ Use console.log() to return results. Only console.log output will be sent back t
3454
3473
  if (lastUsage) {
3455
3474
  opts2.callbacks.onUsageFinal?.(lastUsage, gatewayMeta);
3456
3475
  cumulativePromptTokens += lastUsage.prompt_tokens;
3457
- if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens && toolCalls.length > 0) {
3476
+ if (!budgetExhausted && opts2.maxInputTokens !== void 0 && opts2.maxInputTokens > 0 && cumulativePromptTokens >= opts2.maxInputTokens) {
3458
3477
  budgetExhausted = true;
3459
3478
  }
3460
3479
  }
@@ -3693,7 +3712,22 @@ ${sandboxResult.output}` : sandboxResult.output;
3693
3712
  }
3694
3713
  }
3695
3714
  }
3696
- } catch {
3715
+ } catch (err) {
3716
+ const sid = opts2.sessionId ?? "default";
3717
+ const next = (memoryExtractionErrorCounts.get(sid) ?? 0) + 1;
3718
+ memoryExtractionErrorCounts.set(sid, next);
3719
+ const msg = err instanceof Error ? err.message : String(err);
3720
+ logger.debug("memory:extract_error", {
3721
+ sessionId: opts2.sessionId,
3722
+ tool: tc.function.name,
3723
+ count: next,
3724
+ error: msg
3725
+ });
3726
+ if (next === 1) {
3727
+ opts2.callbacks.onWarning?.(
3728
+ `[memory] auto-extraction failed (${msg}). Subsequent failures will be counted silently; check /memory health.`
3729
+ );
3730
+ }
3697
3731
  }
3698
3732
  })();
3699
3733
  }
@@ -3770,7 +3804,7 @@ function validateToolArguments(raw) {
3770
3804
  return "{}";
3771
3805
  }
3772
3806
  }
3773
- var BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
3807
+ var BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, memoryExtractionErrorCounts, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
3774
3808
  var init_loop = __esm({
3775
3809
  "src/agent/loop.ts"() {
3776
3810
  "use strict";
@@ -3800,6 +3834,7 @@ var init_loop = __esm({
3800
3834
  codeModeApiCache = /* @__PURE__ */ new Map();
3801
3835
  driftAccumulator = /* @__PURE__ */ new Map();
3802
3836
  DRIFT_THRESHOLD = 5;
3837
+ memoryExtractionErrorCounts = /* @__PURE__ */ new Map();
3803
3838
  MAX_PROMPT_TOKENS = 24e4;
3804
3839
  MAX_TOOL_CONTENT_CHARS = 1e4;
3805
3840
  }
@@ -3867,10 +3902,13 @@ var init_read = __esm({
3867
3902
  needsPermission: false,
3868
3903
  render: ({ path }) => ({ title: `read ${collapsePath(path, process.cwd())}` }),
3869
3904
  async run(args, ctx) {
3905
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3870
3906
  const abs = resolvePath(ctx.cwd, args.path);
3871
3907
  const st = await stat2(abs);
3872
3908
  if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
3873
- const text = await readFile3(abs, "utf8");
3909
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3910
+ const text = await readFile3(abs, { encoding: "utf8", signal: ctx.signal });
3911
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
3874
3912
  const lines = text.split("\n");
3875
3913
  const start = Math.max(0, (args.offset ?? 1) - 1);
3876
3914
  const end = args.limit ? Math.min(lines.length, start + args.limit) : lines.length;
@@ -4162,14 +4200,31 @@ var init_glob = __esm({
4162
4200
  needsPermission: false,
4163
4201
  render: (args) => ({ title: `glob ${args.pattern ?? ""}${args.path ? ` in ${collapsePath(String(args.path), process.cwd())}` : ""}` }),
4164
4202
  async run(args, ctx) {
4203
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4165
4204
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
4166
- const entries = await fg(args.pattern, {
4205
+ const stream = fg.stream(args.pattern, {
4167
4206
  cwd: root,
4168
4207
  absolute: true,
4169
4208
  dot: false,
4170
4209
  onlyFiles: false,
4171
4210
  stats: true
4172
4211
  });
4212
+ const entries = [];
4213
+ const onAbort = () => {
4214
+ try {
4215
+ stream.destroy(new DOMException("aborted", "AbortError"));
4216
+ } catch {
4217
+ }
4218
+ };
4219
+ ctx.signal?.addEventListener("abort", onAbort, { once: true });
4220
+ try {
4221
+ for await (const entry of stream) {
4222
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4223
+ entries.push(entry);
4224
+ }
4225
+ } finally {
4226
+ ctx.signal?.removeEventListener("abort", onAbort);
4227
+ }
4173
4228
  entries.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
4174
4229
  const paths = entries.slice(0, 200).map((e) => e.path);
4175
4230
  return paths.length ? paths.join("\n") : "(no matches)";
@@ -4193,14 +4248,14 @@ async function hasRipgrep() {
4193
4248
  }
4194
4249
  return cachedHasRg;
4195
4250
  }
4196
- async function runRipgrep(args, root, mode) {
4251
+ async function runRipgrep(args, root, mode, signal) {
4197
4252
  const rgArgs = ["--no-heading", "--color=never", "--line-number"];
4198
4253
  if (args.case_insensitive) rgArgs.push("-i");
4199
4254
  if (args.glob) rgArgs.push("--glob", args.glob);
4200
4255
  if (mode === "files") rgArgs.push("-l");
4201
4256
  rgArgs.push("--", args.pattern, root);
4202
4257
  try {
4203
- const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024 });
4258
+ const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024, signal });
4204
4259
  const trimmed = stdout.trim();
4205
4260
  if (!trimmed) return { content: "(no matches)", rawBytes: 0, reducedBytes: 0 };
4206
4261
  return {
@@ -4214,7 +4269,7 @@ async function runRipgrep(args, root, mode) {
4214
4269
  throw new Error(err.stderr || String(e));
4215
4270
  }
4216
4271
  }
4217
- async function runJsFallback(args, root, mode) {
4272
+ async function runJsFallback(args, root, mode, signal) {
4218
4273
  const re = new RegExp(args.pattern, args.case_insensitive ? "i" : "");
4219
4274
  const globPattern = args.glob ? `**/${args.glob}` : "**/*";
4220
4275
  const files = await fg2(globPattern, {
@@ -4225,7 +4280,9 @@ async function runJsFallback(args, root, mode) {
4225
4280
  ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"]
4226
4281
  });
4227
4282
  const out = [];
4228
- for (const file of files.slice(0, 5e3)) {
4283
+ for (let fi = 0; fi < Math.min(files.length, 5e3); fi++) {
4284
+ if (signal?.aborted) throw new DOMException("aborted", "AbortError");
4285
+ const file = files[fi];
4229
4286
  try {
4230
4287
  const content = await readFile6(file, "utf8");
4231
4288
  if (mode === "files") {
@@ -4280,10 +4337,11 @@ var init_grep = __esm({
4280
4337
  needsPermission: false,
4281
4338
  render: (args) => ({ title: `grep ${args.pattern ?? ""}${args.glob ? ` (${args.glob})` : ""}` }),
4282
4339
  async run(args, ctx) {
4340
+ if (ctx.signal?.aborted) throw new DOMException("aborted", "AbortError");
4283
4341
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
4284
4342
  const mode = args.output_mode ?? "content";
4285
- if (await hasRipgrep()) return runRipgrep(args, root, mode);
4286
- return runJsFallback(args, root, mode);
4343
+ if (await hasRipgrep()) return runRipgrep(args, root, mode, ctx.signal);
4344
+ return runJsFallback(args, root, mode, ctx.signal);
4287
4345
  }
4288
4346
  };
4289
4347
  }