kimiflare 0.9.0 → 0.9.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/dist/index.js CHANGED
@@ -172,7 +172,7 @@ function isRetryable(err, attempt) {
172
172
  async function* runKimi(opts2) {
173
173
  const url = `https://api.cloudflare.com/client/v4/accounts/${opts2.accountId}/ai/run/${opts2.model}`;
174
174
  const body = {
175
- messages: opts2.messages,
175
+ messages: sanitizeMessagesForApi(opts2.messages),
176
176
  ...opts2.tools && opts2.tools.length ? { tools: opts2.tools, tool_choice: "auto", parallel_tool_calls: true } : {},
177
177
  stream: true,
178
178
  temperature: opts2.temperature ?? 0.2,
@@ -294,6 +294,30 @@ async function* parseStream(body, signal) {
294
294
  }
295
295
  yield { type: "done", finishReason, usage: lastUsage };
296
296
  }
297
+ function sanitizeMessagesForApi(messages) {
298
+ return messages.map((m) => {
299
+ if (!m.tool_calls || m.tool_calls.length === 0) return m;
300
+ return {
301
+ ...m,
302
+ tool_calls: m.tool_calls.map((tc) => ({
303
+ ...tc,
304
+ function: {
305
+ name: tc.function.name,
306
+ arguments: validateJsonArguments(tc.function.arguments)
307
+ }
308
+ }))
309
+ };
310
+ });
311
+ }
312
+ function validateJsonArguments(raw) {
313
+ if (!raw || !raw.trim()) return "{}";
314
+ try {
315
+ JSON.parse(raw);
316
+ return raw;
317
+ } catch {
318
+ return "{}";
319
+ }
320
+ }
297
321
  function extractCloudflareError(parsed) {
298
322
  if (!parsed || typeof parsed !== "object") return null;
299
323
  const cf = parsed;
@@ -387,10 +411,11 @@ async function runAgentTurn(opts2) {
387
411
  opts2.callbacks.onToolCallArgs?.(ev.index, ev.argsDelta);
388
412
  break;
389
413
  case "tool_call_complete": {
414
+ const safeArgs = validateToolArguments(ev.arguments);
390
415
  const call = {
391
416
  id: ev.id,
392
417
  type: "function",
393
- function: { name: ev.name, arguments: ev.arguments }
418
+ function: { name: ev.name, arguments: safeArgs }
394
419
  };
395
420
  toolCalls.push(call);
396
421
  opts2.callbacks.onToolCallFinalized?.(call);
@@ -438,6 +463,15 @@ async function runAgentTurn(opts2) {
438
463
  }
439
464
  throw new Error(`kimiflare: tool iteration limit reached (${opts2.maxToolIterations ?? 50})`);
440
465
  }
466
+ function validateToolArguments(raw) {
467
+ if (!raw || !raw.trim()) return "{}";
468
+ try {
469
+ JSON.parse(raw);
470
+ return raw;
471
+ } catch {
472
+ return "{}";
473
+ }
474
+ }
441
475
  var init_loop = __esm({
442
476
  "src/agent/loop.ts"() {
443
477
  "use strict";
@@ -1474,10 +1508,11 @@ var init_tool_view = __esm({
1474
1508
  });
1475
1509
 
1476
1510
  // src/ui/markdown.tsx
1511
+ import { useMemo } from "react";
1477
1512
  import { Box as Box3, Text as Text3 } from "ink";
1478
1513
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1479
1514
  function MD({ text, theme }) {
1480
- const blocks = parseBlocks(text);
1515
+ const blocks = useMemo(() => parseBlocks(text), [text]);
1481
1516
  return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: blocks.map((b, i) => /* @__PURE__ */ jsx3(Block, { block: b, theme }, i)) });
1482
1517
  }
1483
1518
  function parseBlocks(src) {
@@ -1633,19 +1668,10 @@ var init_markdown = __esm({
1633
1668
  });
1634
1669
 
1635
1670
  // src/ui/chat.tsx
1671
+ import React2 from "react";
1636
1672
  import { Box as Box4, Text as Text4 } from "ink";
1637
1673
  import Spinner2 from "ink-spinner";
1638
1674
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1639
- function ChatView({ events, showReasoning, theme, verbose }) {
1640
- return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: events.map((e, i) => {
1641
- const prev = events[i - 1];
1642
- const showSeparator = e.kind === "user" && prev && (prev.kind === "assistant" || prev.kind === "tool");
1643
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1644
- showSeparator && /* @__PURE__ */ jsx4(Box4, { marginY: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.info.color, dimColor: theme.info.dim, children: "\u2500".repeat(40) }) }),
1645
- /* @__PURE__ */ jsx4(EventView, { evt: e, showReasoning, theme, verbose })
1646
- ] }, e.key);
1647
- }) });
1648
- }
1649
1675
  function EventView({
1650
1676
  evt,
1651
1677
  showReasoning,
@@ -1686,11 +1712,22 @@ function EventView({
1686
1712
  evt.text
1687
1713
  ] });
1688
1714
  }
1715
+ var ChatView;
1689
1716
  var init_chat = __esm({
1690
1717
  "src/ui/chat.tsx"() {
1691
1718
  "use strict";
1692
1719
  init_tool_view();
1693
1720
  init_markdown();
1721
+ ChatView = React2.memo(function ChatView2({ events, showReasoning, theme, verbose }) {
1722
+ return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: events.map((e, i) => {
1723
+ const prev = events[i - 1];
1724
+ const showSeparator = e.kind === "user" && prev && (prev.kind === "assistant" || prev.kind === "tool");
1725
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1726
+ showSeparator && /* @__PURE__ */ jsx4(Box4, { marginY: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.info.color, dimColor: theme.info.dim, children: "\u2500".repeat(40) }) }),
1727
+ /* @__PURE__ */ jsx4(EventView, { evt: e, showReasoning, theme, verbose })
1728
+ ] }, e.key);
1729
+ }) });
1730
+ });
1694
1731
  }
1695
1732
  });
1696
1733
 
@@ -1943,19 +1980,25 @@ var init_theme_picker = __esm({
1943
1980
  });
1944
1981
 
1945
1982
  // src/ui/task-list.tsx
1946
- import { useEffect as useEffect2, useState as useState3 } from "react";
1983
+ import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
1947
1984
  import { Box as Box9, Text as Text9 } from "ink";
1948
1985
  import Spinner4 from "ink-spinner";
1949
1986
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1950
1987
  function TaskList({ tasks, theme, startedAt, tokensDelta }) {
1951
1988
  const [now, setNow] = useState3(Date.now());
1989
+ const tasksRef = useRef(tasks);
1990
+ tasksRef.current = tasks;
1952
1991
  useEffect2(() => {
1953
1992
  if (startedAt === null) return;
1954
- const allDone2 = tasks.length > 0 && tasks.every((t) => t.status === "completed");
1955
- if (allDone2) return;
1956
- const id = setInterval(() => setNow(Date.now()), 1e3);
1993
+ const id = setInterval(() => {
1994
+ setNow(Date.now());
1995
+ const current = tasksRef.current;
1996
+ if (current.length > 0 && current.every((t) => t.status === "completed")) {
1997
+ clearInterval(id);
1998
+ }
1999
+ }, 1e3);
1957
2000
  return () => clearInterval(id);
1958
- }, [startedAt, tasks]);
2001
+ }, [startedAt]);
1959
2002
  if (tasks.length === 0) return null;
1960
2003
  const active = tasks.find((t) => t.status === "in_progress");
1961
2004
  const done = tasks.filter((t) => t.status === "completed").length;
@@ -2547,7 +2590,7 @@ var init_source = __esm({
2547
2590
  });
2548
2591
 
2549
2592
  // src/ui/text-input.tsx
2550
- import { useState as useState4, useEffect as useEffect3, useRef } from "react";
2593
+ import { useState as useState4, useEffect as useEffect3, useRef as useRef2 } from "react";
2551
2594
  import { Text as Text10, useInput } from "ink";
2552
2595
  import { jsx as jsx10 } from "react/jsx-runtime";
2553
2596
  function shouldTreatAsPaste(input) {
@@ -2583,7 +2626,7 @@ function CustomTextInput({
2583
2626
  enablePaste = false
2584
2627
  }) {
2585
2628
  const [cursorOffset, setCursorOffset] = useState4(value.length);
2586
- const pastesRef = useRef(/* @__PURE__ */ new Map());
2629
+ const pastesRef = useRef2(/* @__PURE__ */ new Map());
2587
2630
  useEffect3(() => {
2588
2631
  if (!focus) return;
2589
2632
  setCursorOffset((prev) => prev > value.length ? value.length : prev);
@@ -3269,16 +3312,29 @@ var app_exports = {};
3269
3312
  __export(app_exports, {
3270
3313
  renderApp: () => renderApp
3271
3314
  });
3272
- import { useState as useState6, useRef as useRef2, useEffect as useEffect4, useCallback } from "react";
3315
+ import { useState as useState6, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
3273
3316
  import { Box as Box12, Text as Text13, useApp, useInput as useInput2, render } from "ink";
3274
3317
  import { existsSync } from "fs";
3275
3318
  import { join as join5 } from "path";
3276
3319
  import { unlink } from "fs/promises";
3277
3320
  import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3321
+ function capEvents(prev) {
3322
+ if (prev.length <= MAX_EVENTS) return prev;
3323
+ return prev.slice(prev.length - MAX_EVENTS);
3324
+ }
3278
3325
  function App({ initialCfg, initialUpdateResult }) {
3279
3326
  const { exit } = useApp();
3280
3327
  const [cfg, setCfg] = useState6(initialCfg);
3281
- const [events, setEvents] = useState6([]);
3328
+ const [events, setRawEvents] = useState6([]);
3329
+ const setEvents = useCallback(
3330
+ (updater) => {
3331
+ setRawEvents((prev) => {
3332
+ const next = typeof updater === "function" ? updater(prev) : updater;
3333
+ return capEvents(next);
3334
+ });
3335
+ },
3336
+ []
3337
+ );
3282
3338
  const [input, setInput] = useState6("");
3283
3339
  const [busy, setBusy] = useState6(false);
3284
3340
  const [usage, setUsage] = useState6(null);
@@ -3303,7 +3359,7 @@ function App({ initialCfg, initialUpdateResult }) {
3303
3359
  const [verbose, setVerbose] = useState6(false);
3304
3360
  const [hasUpdate, setHasUpdate] = useState6(initialUpdateResult?.hasUpdate ?? false);
3305
3361
  const [latestVersion, setLatestVersion] = useState6(initialUpdateResult?.latestVersion ?? null);
3306
- const messagesRef = useRef2([
3362
+ const messagesRef = useRef3([
3307
3363
  {
3308
3364
  role: "system",
3309
3365
  content: buildSystemPrompt({
@@ -3314,17 +3370,17 @@ function App({ initialCfg, initialUpdateResult }) {
3314
3370
  })
3315
3371
  }
3316
3372
  ]);
3317
- const executorRef = useRef2(new ToolExecutor(ALL_TOOLS));
3318
- const activeAsstIdRef = useRef2(null);
3319
- const activeControllerRef = useRef2(null);
3320
- const sessionIdRef = useRef2(null);
3321
- const modeRef = useRef2(mode);
3322
- const effortRef = useRef2(effort);
3323
- const tasksRef = useRef2([]);
3324
- const usageRef = useRef2(null);
3325
- const updateCheckedRef = useRef2(false);
3326
- const updateNudgedRef = useRef2(false);
3327
- const compactSuggestedRef = useRef2(false);
3373
+ const executorRef = useRef3(new ToolExecutor(ALL_TOOLS));
3374
+ const activeAsstIdRef = useRef3(null);
3375
+ const activeControllerRef = useRef3(null);
3376
+ const sessionIdRef = useRef3(null);
3377
+ const modeRef = useRef3(mode);
3378
+ const effortRef = useRef3(effort);
3379
+ const tasksRef = useRef3([]);
3380
+ const usageRef = useRef3(null);
3381
+ const updateCheckedRef = useRef3(false);
3382
+ const updateNudgedRef = useRef3(false);
3383
+ const compactSuggestedRef = useRef3(false);
3328
3384
  useEffect4(() => {
3329
3385
  if (!cfg || updateCheckedRef.current) return;
3330
3386
  updateCheckedRef.current = true;
@@ -4070,10 +4126,23 @@ use: /thinking low | medium | high`
4070
4126
  if (e.name === "AbortError") {
4071
4127
  setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
4072
4128
  } else {
4073
- setEvents((es) => [
4074
- ...es,
4075
- { kind: "error", key: mkKey(), text: e.message ?? String(e) }
4076
- ]);
4129
+ const isInvalidJson400 = e instanceof KimiApiError && e.httpStatus === 400 && e.message.includes("invalid escaped character");
4130
+ if (isInvalidJson400) {
4131
+ messagesRef.current.pop();
4132
+ setEvents((es) => [
4133
+ ...es,
4134
+ {
4135
+ kind: "error",
4136
+ key: mkKey(),
4137
+ text: "API rejected request (invalid JSON in conversation history). Retrying may work; run /clear to reset if it persists."
4138
+ }
4139
+ ]);
4140
+ } else {
4141
+ setEvents((es) => [
4142
+ ...es,
4143
+ { kind: "error", key: mkKey(), text: e.message ?? String(e) }
4144
+ ]);
4145
+ }
4077
4146
  }
4078
4147
  } finally {
4079
4148
  setBusy(false);
@@ -4242,7 +4311,7 @@ async function renderApp(cfg, updateResult) {
4242
4311
  const instance = render(/* @__PURE__ */ jsx13(App, { initialCfg: cfg, initialUpdateResult: updateResult }));
4243
4312
  await instance.waitUntilExit();
4244
4313
  }
4245
- var CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, nextAssistantId, nextKey, mkKey, EFFORT_DESCRIPTIONS;
4314
+ var CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, EFFORT_DESCRIPTIONS;
4246
4315
  var init_app = __esm({
4247
4316
  "src/app.tsx"() {
4248
4317
  "use strict";
@@ -4251,6 +4320,7 @@ var init_app = __esm({
4251
4320
  init_compact();
4252
4321
  init_executor();
4253
4322
  init_messages();
4323
+ init_errors();
4254
4324
  init_chat();
4255
4325
  init_status();
4256
4326
  init_permission();
@@ -4267,6 +4337,7 @@ var init_app = __esm({
4267
4337
  init_sessions();
4268
4338
  CONTEXT_LIMIT = 262e3;
4269
4339
  AUTO_COMPACT_SUGGEST_PCT = 0.8;
4340
+ MAX_EVENTS = 500;
4270
4341
  nextAssistantId = 1;
4271
4342
  nextKey = 1;
4272
4343
  mkKey = () => `evt_${nextKey++}`;