kimiflare 0.12.0 → 0.13.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/dist/index.js CHANGED
@@ -230,57 +230,87 @@ async function* parseStream(body, signal) {
230
230
  const toolCalls = /* @__PURE__ */ new Map();
231
231
  let lastUsage = null;
232
232
  let finishReason = null;
233
- for await (const dataStr of readSSE(body, signal)) {
234
- if (dataStr === "[DONE]") break;
235
- let chunk = null;
236
- try {
237
- chunk = JSON.parse(dataStr);
238
- } catch {
239
- continue;
240
- }
241
- if (!chunk) continue;
242
- if (chunk.usage) {
243
- lastUsage = chunk.usage;
244
- yield { type: "usage", usage: chunk.usage };
245
- }
246
- const choice = chunk.choices?.[0];
247
- if (!choice) continue;
248
- const d = choice.delta;
249
- if (d) {
250
- if (typeof d.reasoning_content === "string" && d.reasoning_content.length) {
251
- yield { type: "reasoning", delta: d.reasoning_content };
233
+ let timeoutId = null;
234
+ let timedOut = false;
235
+ const resetTimeout = () => {
236
+ if (timeoutId) clearTimeout(timeoutId);
237
+ timeoutId = setTimeout(() => {
238
+ timedOut = true;
239
+ }, STREAM_TIMEOUT_MS);
240
+ };
241
+ try {
242
+ for await (const dataStr of readSSE(body, signal)) {
243
+ if (timedOut) {
244
+ throw new KimiApiError(
245
+ `kimiflare: stream timed out (no data for ${STREAM_TIMEOUT_MS / 1e3}s)`,
246
+ void 0,
247
+ void 0
248
+ );
249
+ }
250
+ resetTimeout();
251
+ if (dataStr === "[DONE]") break;
252
+ let chunk = null;
253
+ try {
254
+ chunk = JSON.parse(dataStr);
255
+ } catch {
256
+ continue;
252
257
  }
253
- if (typeof d.content === "string" && d.content.length) {
254
- yield { type: "text", delta: d.content };
258
+ if (!chunk) continue;
259
+ if (chunk.usage) {
260
+ lastUsage = chunk.usage;
261
+ yield { type: "usage", usage: chunk.usage };
255
262
  }
256
- if (Array.isArray(d.tool_calls)) {
257
- for (const tc of d.tool_calls) {
258
- const idx = typeof tc.index === "number" ? tc.index : 0;
259
- let buf = toolCalls.get(idx);
260
- const incomingName = tc.function?.name ?? null;
261
- const incomingId = tc.id ?? null;
262
- if (!buf) {
263
- buf = { id: incomingId ?? `tc_${idx}`, name: incomingName ?? "", args: "" };
264
- toolCalls.set(idx, buf);
265
- if (buf.name) {
266
- yield { type: "tool_call_start", index: idx, id: buf.id, name: buf.name };
263
+ const choice = chunk.choices?.[0];
264
+ if (!choice) continue;
265
+ const d = choice.delta;
266
+ if (d) {
267
+ if (typeof d.reasoning_content === "string" && d.reasoning_content.length) {
268
+ yield { type: "reasoning", delta: d.reasoning_content };
269
+ }
270
+ if (typeof d.content === "string" && d.content.length) {
271
+ yield { type: "text", delta: d.content };
272
+ }
273
+ if (Array.isArray(d.tool_calls)) {
274
+ for (const tc of d.tool_calls) {
275
+ const idx = typeof tc.index === "number" ? tc.index : 0;
276
+ let buf = toolCalls.get(idx);
277
+ const incomingName = tc.function?.name ?? null;
278
+ const incomingId = tc.id ?? null;
279
+ if (!buf) {
280
+ buf = { id: incomingId ?? `tc_${idx}`, name: incomingName ?? "", args: "" };
281
+ toolCalls.set(idx, buf);
282
+ if (buf.name) {
283
+ yield { type: "tool_call_start", index: idx, id: buf.id, name: buf.name };
284
+ }
285
+ } else {
286
+ if (!buf.name && incomingName) {
287
+ buf.name = incomingName;
288
+ yield { type: "tool_call_start", index: idx, id: buf.id, name: buf.name };
289
+ }
290
+ if (buf.id.startsWith("tc_") && incomingId) buf.id = incomingId;
267
291
  }
268
- } else {
269
- if (!buf.name && incomingName) {
270
- buf.name = incomingName;
271
- yield { type: "tool_call_start", index: idx, id: buf.id, name: buf.name };
292
+ const argDelta = tc.function?.arguments;
293
+ if (typeof argDelta === "string" && argDelta.length) {
294
+ buf.args += argDelta;
295
+ yield { type: "tool_call_args", index: idx, argsDelta: argDelta };
272
296
  }
273
- if (buf.id.startsWith("tc_") && incomingId) buf.id = incomingId;
274
- }
275
- const argDelta = tc.function?.arguments;
276
- if (typeof argDelta === "string" && argDelta.length) {
277
- buf.args += argDelta;
278
- yield { type: "tool_call_args", index: idx, argsDelta: argDelta };
279
297
  }
280
298
  }
281
299
  }
300
+ if (choice.finish_reason) finishReason = choice.finish_reason;
282
301
  }
283
- if (choice.finish_reason) finishReason = choice.finish_reason;
302
+ for (const [idx, buf] of [...toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
303
+ if (!buf.name) continue;
304
+ yield {
305
+ type: "tool_call_complete",
306
+ index: idx,
307
+ id: buf.id,
308
+ name: buf.name,
309
+ arguments: buf.args
310
+ };
311
+ }
312
+ } finally {
313
+ if (timeoutId) clearTimeout(timeoutId);
284
314
  }
285
315
  for (const [idx, buf] of [...toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
286
316
  if (!buf.name) continue;
@@ -354,7 +384,7 @@ function sleep(ms, signal) {
354
384
  signal?.addEventListener("abort", onAbort, { once: true });
355
385
  });
356
386
  }
357
- var RETRYABLE_CODES, MAX_ATTEMPTS;
387
+ var RETRYABLE_CODES, MAX_ATTEMPTS, STREAM_TIMEOUT_MS;
358
388
  var init_client = __esm({
359
389
  "src/agent/client.ts"() {
360
390
  "use strict";
@@ -363,6 +393,7 @@ var init_client = __esm({
363
393
  init_messages();
364
394
  RETRYABLE_CODES = /* @__PURE__ */ new Set([3040]);
365
395
  MAX_ATTEMPTS = 5;
396
+ STREAM_TIMEOUT_MS = 6e4;
366
397
  }
367
398
  });
368
399
 
@@ -3575,6 +3606,60 @@ function findImagePaths(text) {
3575
3606
  }
3576
3607
  return [...new Set(paths)];
3577
3608
  }
3609
+ function stripImagesFromHistory(messages) {
3610
+ for (const m of messages) {
3611
+ if (!Array.isArray(m.content)) continue;
3612
+ let changed = false;
3613
+ const next = [];
3614
+ for (const part of m.content) {
3615
+ if (part.type === "image_url") {
3616
+ changed = true;
3617
+ next.push({ type: "text", text: "[image]" });
3618
+ } else {
3619
+ next.push(part);
3620
+ }
3621
+ }
3622
+ if (changed) {
3623
+ m.content = next;
3624
+ }
3625
+ }
3626
+ }
3627
+ function truncateOldToolResults(messages, keepRecent) {
3628
+ const toolIndices = [];
3629
+ for (let i = 0; i < messages.length; i++) {
3630
+ if (messages[i].role === "tool") toolIndices.push(i);
3631
+ }
3632
+ const cutoff = toolIndices.length - keepRecent;
3633
+ for (let i = 0; i < cutoff; i++) {
3634
+ const idx = toolIndices[i];
3635
+ const m = messages[idx];
3636
+ const text = typeof m.content === "string" ? m.content : "";
3637
+ if (text.length > 500) {
3638
+ m.content = text.slice(0, 500) + "\n\u2026 [truncated]";
3639
+ }
3640
+ }
3641
+ }
3642
+ async function maybeAutoCompact(messages, cfg, onInfo) {
3643
+ const heapUsed = process.memoryUsage().heapUsed;
3644
+ if (heapUsed < HEAP_AUTO_COMPACT_THRESHOLD || messages.length <= MSG_AUTO_COMPACT_THRESHOLD) {
3645
+ return false;
3646
+ }
3647
+ onInfo("auto-compacting to reduce memory usage");
3648
+ const result = await compactMessages({
3649
+ accountId: cfg.accountId,
3650
+ apiToken: cfg.apiToken,
3651
+ model: cfg.model,
3652
+ messages,
3653
+ keepLastTurns: 2
3654
+ });
3655
+ if (result.replacedCount > 0) {
3656
+ messages.length = 0;
3657
+ messages.push(...result.newMessages);
3658
+ onInfo(`compacted ${result.replacedCount} messages into a summary`);
3659
+ return true;
3660
+ }
3661
+ return false;
3662
+ }
3578
3663
  function App({ initialCfg, initialUpdateResult }) {
3579
3664
  const { exit } = useApp();
3580
3665
  const [cfg, setCfg] = useState6(initialCfg);
@@ -3626,6 +3711,7 @@ function App({ initialCfg, initialUpdateResult }) {
3626
3711
  const executorRef = useRef3(new ToolExecutor(ALL_TOOLS));
3627
3712
  const activeAsstIdRef = useRef3(null);
3628
3713
  const activeControllerRef = useRef3(null);
3714
+ const permResolveRef = useRef3(null);
3629
3715
  const sessionIdRef = useRef3(null);
3630
3716
  const modeRef = useRef3(mode);
3631
3717
  const effortRef = useRef3(effort);
@@ -3767,8 +3853,11 @@ function App({ initialCfg, initialUpdateResult }) {
3767
3853
  }, [cfg]);
3768
3854
  useInput2((inputChar, key) => {
3769
3855
  if (key.ctrl && inputChar === "c") {
3770
- if (busy && activeControllerRef.current) {
3771
- activeControllerRef.current.abort();
3856
+ if (busy) {
3857
+ activeControllerRef.current?.abort();
3858
+ permResolveRef.current?.("deny");
3859
+ permResolveRef.current = null;
3860
+ setPerm(null);
3772
3861
  setQueue([]);
3773
3862
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
3774
3863
  } else {
@@ -3990,10 +4079,21 @@ function App({ initialCfg, initialUpdateResult }) {
3990
4079
  resolve2("deny");
3991
4080
  return;
3992
4081
  }
3993
- setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
4082
+ permResolveRef.current = resolve2;
4083
+ setPerm({ tool: req.tool, args: req.args, resolve: (d) => {
4084
+ permResolveRef.current = null;
4085
+ resolve2(d);
4086
+ } });
3994
4087
  })
3995
4088
  }
3996
4089
  });
4090
+ stripImagesFromHistory(messagesRef.current);
4091
+ truncateOldToolResults(messagesRef.current, 8);
4092
+ await maybeAutoCompact(
4093
+ messagesRef.current,
4094
+ cfg,
4095
+ (text) => setEvents((es) => [...es, { kind: "info", key: mkKey(), text }])
4096
+ );
3997
4097
  if (existsSync(join5(cwd, "KIMI.md"))) {
3998
4098
  messagesRef.current[0] = {
3999
4099
  role: "system",
@@ -4010,17 +4110,25 @@ function App({ initialCfg, initialUpdateResult }) {
4010
4110
  ]);
4011
4111
  }
4012
4112
  } catch (e) {
4013
- if (e.name !== "AbortError") {
4113
+ if (e.name === "AbortError") {
4114
+ setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
4115
+ setEvents(
4116
+ (evts) => evts.map((e2) => e2.kind === "tool" && e2.status === "running" ? { ...e2, status: "error", result: "(interrupted)" } : e2)
4117
+ );
4118
+ } else {
4014
4119
  setEvents((es) => [
4015
4120
  ...es,
4016
4121
  { kind: "error", key: mkKey(), text: `init failed: ${e.message}` }
4017
4122
  ]);
4018
4123
  }
4019
4124
  } finally {
4125
+ const asstId = activeAsstIdRef.current;
4126
+ if (asstId !== null) updateAssistant(asstId, () => ({ streaming: false }));
4020
4127
  setBusy(false);
4021
4128
  setTurnStartedAt(null);
4022
4129
  activeAsstIdRef.current = null;
4023
4130
  activeControllerRef.current = null;
4131
+ permResolveRef.current = null;
4024
4132
  }
4025
4133
  }, [cfg, busy, updateAssistant, updateTool]);
4026
4134
  const handleResumePick = useCallback(
@@ -4432,14 +4540,28 @@ use: /thinking low | medium | high`
4432
4540
  resolve2("deny");
4433
4541
  return;
4434
4542
  }
4435
- setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
4543
+ permResolveRef.current = resolve2;
4544
+ setPerm({ tool: req.tool, args: req.args, resolve: (d) => {
4545
+ permResolveRef.current = null;
4546
+ resolve2(d);
4547
+ } });
4436
4548
  })
4437
4549
  }
4438
4550
  });
4439
- await saveSessionSafe();
4551
+ stripImagesFromHistory(messagesRef.current);
4552
+ truncateOldToolResults(messagesRef.current, 8);
4553
+ await maybeAutoCompact(
4554
+ messagesRef.current,
4555
+ cfg,
4556
+ (text2) => setEvents((es) => [...es, { kind: "info", key: mkKey(), text: text2 }])
4557
+ );
4558
+ void saveSessionSafe();
4440
4559
  } catch (e) {
4441
4560
  if (e.name === "AbortError") {
4442
- setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
4561
+ setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
4562
+ setEvents(
4563
+ (evts) => evts.map((e2) => e2.kind === "tool" && e2.status === "running" ? { ...e2, status: "error", result: "(interrupted)" } : e2)
4564
+ );
4443
4565
  } else {
4444
4566
  const isInvalidJson400 = e instanceof KimiApiError && e.httpStatus === 400 && e.message.includes("invalid escaped character");
4445
4567
  if (isInvalidJson400) {
@@ -4460,10 +4582,13 @@ use: /thinking low | medium | high`
4460
4582
  }
4461
4583
  }
4462
4584
  } finally {
4585
+ const asstId = activeAsstIdRef.current;
4586
+ if (asstId !== null) updateAssistant(asstId, () => ({ streaming: false }));
4463
4587
  setBusy(false);
4464
4588
  setTurnStartedAt(null);
4465
4589
  activeAsstIdRef.current = null;
4466
4590
  activeControllerRef.current = null;
4591
+ permResolveRef.current = null;
4467
4592
  }
4468
4593
  },
4469
4594
  [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe]
@@ -4626,7 +4751,7 @@ async function renderApp(cfg, updateResult) {
4626
4751
  const instance = render(/* @__PURE__ */ jsx13(App, { initialCfg: cfg, initialUpdateResult: updateResult }));
4627
4752
  await instance.waitUntilExit();
4628
4753
  }
4629
- var CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, MAX_IMAGES_PER_MESSAGE, EFFORT_DESCRIPTIONS;
4754
+ var CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, MAX_IMAGES_PER_MESSAGE, EFFORT_DESCRIPTIONS, HEAP_AUTO_COMPACT_THRESHOLD, MSG_AUTO_COMPACT_THRESHOLD;
4630
4755
  var init_app = __esm({
4631
4756
  "src/app.tsx"() {
4632
4757
  "use strict";
@@ -4663,6 +4788,8 @@ var init_app = __esm({
4663
4788
  medium: "medium \u2014 balanced (default). Solid quality on most edits, fast on trivial prompts.",
4664
4789
  high: "high \u2014 deepest reasoning; slowest. Best for complex debugging, architecture, multi-file refactors."
4665
4790
  };
4791
+ HEAP_AUTO_COMPACT_THRESHOLD = 2e9;
4792
+ MSG_AUTO_COMPACT_THRESHOLD = 50;
4666
4793
  }
4667
4794
  });
4668
4795