kimiflare 0.49.0 → 0.50.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/README.md CHANGED
@@ -77,6 +77,107 @@ kimiflare -p "..." --dangerously-allow-all # auto-approve mutating tool
77
77
  kimiflare -p "..." --reasoning # include chain-of-thought in stderr
78
78
  ```
79
79
 
80
+ ### Headless SDK
81
+
82
+ Use KimiFlare programmatically from your own application — no TUI required.
83
+
84
+ ```ts
85
+ import { createAgentSession } from "kimiflare/sdk";
86
+
87
+ const { session } = await createAgentSession({
88
+ cwd: "/path/to/project",
89
+ config: {
90
+ accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
91
+ apiToken: process.env.CLOUDFLARE_API_TOKEN,
92
+ model: "@cf/moonshotai/kimi-k2.6",
93
+ },
94
+ });
95
+
96
+ // Stream every event: text deltas, tool calls, tasks, usage
97
+ session.subscribe((event) => {
98
+ console.log(event.type, event);
99
+ });
100
+
101
+ // Send a prompt
102
+ await session.prompt("Refactor auth to JWT + Redis");
103
+
104
+ // Mid-flight correction while the agent is still running
105
+ await session.steer("Use Redis instead of in-memory store");
106
+
107
+ // After the turn finishes
108
+ await session.followUp("Also add unit tests");
109
+
110
+ // Clean up
111
+ session.dispose();
112
+ ```
113
+
114
+ **Key features:**
115
+ - `subscribe()` — receive typed events (`text_delta`, `tool_call`, `tool_result`, `task_update`, `usage`, `error`, `done`, etc.)
116
+ - `prompt()` / `steer()` / `followUp()` — full conversation lifecycle
117
+ - `pause()` / `resume()` — graceful preemption
118
+ - `getStatus()` / `getUsage()` — inspect session state
119
+ - Custom `permissionHandler` — decide programmatically whether to allow mutating tools
120
+ - Optional `memoryEnabled`, `lspEnabled`, `costAttribution` flags
121
+
122
+ #### SDK Authentication
123
+
124
+ The SDK needs a Cloudflare **Account ID** and **API Token** to call Workers AI directly. Credentials are resolved in this priority order:
125
+
126
+ 1. **Explicit `config` object** (recommended for apps)
127
+ 2. **Environment variables**: `CLOUDFLARE_ACCOUNT_ID` / `CF_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN` / `CF_API_TOKEN`
128
+ 3. **Config file**: `~/.config/kimiflare/config.json`
129
+
130
+ **For Electron / desktop apps**, we recommend storing credentials in the OS keychain (e.g. Electron `safeStorage` or `keytar`) and passing them explicitly:
131
+
132
+ ```ts
133
+ import { createAgentSession } from "kimiflare/sdk";
134
+
135
+ const accountId = await keytar.getPassword("kimiflare", "accountId");
136
+ const apiToken = await keytar.getPassword("kimiflare", "apiToken");
137
+
138
+ const { session } = await createAgentSession({
139
+ cwd: projectPath,
140
+ config: { accountId, apiToken },
141
+ });
142
+ ```
143
+
144
+ **For zero-credential onboarding**, use KimiFlare Cloud mode. The user authenticates via GitHub device flow and a Cloudflare Worker proxies AI requests. Your app never sees raw Cloudflare credentials — only a GitHub token and `remoteWorkerUrl`.
145
+
146
+ #### RPC mode (subprocess)
147
+
148
+ If you need process isolation or a non-Node consumer, run KimiFlare in JSONL-over-stdio RPC mode:
149
+
150
+ ```sh
151
+ node bin/kimiflare.mjs --mode rpc
152
+ ```
153
+
154
+ ```ts
155
+ import { spawn } from "node:child_process";
156
+
157
+ const proc = spawn("npx", ["kimiflare", "--mode", "rpc"], {
158
+ cwd: projectPath,
159
+ stdio: ["pipe", "pipe", "pipe"],
160
+ });
161
+
162
+ // Read events
163
+ proc.stdout.on("data", (chunk) => {
164
+ for (const line of chunk.toString().split("\n")) {
165
+ if (!line.trim()) continue;
166
+ const event = JSON.parse(line);
167
+ console.log(event.type, event);
168
+ }
169
+ });
170
+
171
+ // Send commands
172
+ proc.stdin.write(JSON.stringify({ type: "new_session" }) + "\n");
173
+ proc.stdin.write(JSON.stringify({ type: "prompt", message: "Hello" }) + "\n");
174
+
175
+ // Resolve a permission request
176
+ proc.stdin.write(
177
+ JSON.stringify({ type: "resolve_permission", requestId: "req_0", decision: "allow" }) + "\n"
178
+ );
179
+ ```
180
+
80
181
  ### Image understanding
81
182
 
82
183
  ```sh
package/dist/index.js CHANGED
@@ -453,6 +453,34 @@ var init_sse = __esm({
453
453
  function isCloudQuotaExhaustedError(err) {
454
454
  return err instanceof KimiApiError && err.httpStatus === 429 && /token quota exhausted/i.test(err.message);
455
455
  }
456
+ function humanizeCloudflareError(err) {
457
+ const { code, httpStatus, message: message2 } = err;
458
+ if (code === 3040) {
459
+ return "Cloudflare Workers AI is at capacity. Retrying automatically\u2026";
460
+ }
461
+ if (httpStatus === 429) {
462
+ return "Rate limit hit. Please wait a moment and try again.";
463
+ }
464
+ if (httpStatus === 403 || code === 1e4) {
465
+ return "Authentication failed. Check that your Cloudflare API token has the 'Workers AI' permission.\nGet a new token: https://dash.cloudflare.com/profile/api-tokens";
466
+ }
467
+ if (httpStatus === 401) {
468
+ return "Authentication required. Please check your API token or run `kimiflare auth cloud` if using cloud mode.";
469
+ }
470
+ if (httpStatus === 400) {
471
+ if (message2.includes("invalid escaped character")) {
472
+ return "API rejected request (invalid JSON in conversation history). Run /clear to reset if it persists.";
473
+ }
474
+ if (message2.includes("Invalid model ID")) {
475
+ return message2;
476
+ }
477
+ return "Bad request. The conversation may be too long or contain invalid characters. Run /compact or /clear.";
478
+ }
479
+ if (httpStatus && httpStatus >= 500) {
480
+ return "Cloudflare servers are experiencing issues. Retrying automatically\u2026";
481
+ }
482
+ return message2.replace(/\{[\s\S]*?\}/g, "(see logs for details)");
483
+ }
456
484
  var KimiApiError;
457
485
  var init_errors = __esm({
458
486
  "src/util/errors.ts"() {
@@ -620,7 +648,7 @@ async function* runKimi(opts2) {
620
648
  parsed = JSON.parse(text);
621
649
  } catch {
622
650
  }
623
- const err = extractCloudflareError(parsed);
651
+ const err = extractCloudflareError(parsed, text);
624
652
  const rawMsg = err?.message ?? `HTTP ${res.status}: ${text.slice(0, 300)}`;
625
653
  const msg = cleanErrorMessage(rawMsg);
626
654
  const apiErr = new KimiApiError(`kimiflare: ${msg}`, err?.code, res.status);
@@ -827,16 +855,23 @@ function validateJsonArguments(raw) {
827
855
  return "{}";
828
856
  }
829
857
  }
830
- function extractCloudflareError(parsed) {
831
- if (!parsed || typeof parsed !== "object") return null;
832
- const cf = parsed;
833
- if (cf.success === false && Array.isArray(cf.errors) && cf.errors.length > 0) {
834
- return { code: cf.errors[0]?.code, message: cf.errors[0]?.message };
858
+ function extractCloudflareError(parsed, rawText) {
859
+ if (parsed && typeof parsed === "object") {
860
+ const cf = parsed;
861
+ if (cf.success === false && Array.isArray(cf.errors) && cf.errors.length > 0) {
862
+ return { code: cf.errors[0]?.code, message: cf.errors[0]?.message };
863
+ }
864
+ const oai = parsed;
865
+ if (oai.object === "error" && typeof oai.message === "string") {
866
+ const codeNum = typeof oai.code === "number" ? oai.code : void 0;
867
+ return { code: codeNum, message: oai.message };
868
+ }
835
869
  }
836
- const oai = parsed;
837
- if (oai.object === "error" && typeof oai.message === "string") {
838
- const codeNum = typeof oai.code === "number" ? oai.code : void 0;
839
- return { code: codeNum, message: oai.message };
870
+ if (rawText) {
871
+ const msgMatch = rawText.match(/"message"\s*:\s*"([^"]+)"/);
872
+ if (msgMatch?.[1]) {
873
+ return { message: msgMatch[1] };
874
+ }
840
875
  }
841
876
  return null;
842
877
  }
@@ -3314,8 +3349,8 @@ var init_write = __esm({
3314
3349
  },
3315
3350
  needsPermission: true,
3316
3351
  render: (args) => ({
3317
- title: `write ${collapsePath(args.path, process.cwd())} (${args.content.length} chars)`,
3318
- diff: { path: args.path, before: "", after: args.content }
3352
+ title: `write ${collapsePath(String(args.path ?? ""), process.cwd())} (${String(args.content ?? "").length} chars)`,
3353
+ diff: { path: String(args.path ?? ""), before: "", after: String(args.content ?? "") }
3319
3354
  }),
3320
3355
  async run(args, ctx) {
3321
3356
  const abs = resolvePath(ctx.cwd, args.path);
@@ -3367,8 +3402,8 @@ var init_edit = __esm({
3367
3402
  },
3368
3403
  needsPermission: true,
3369
3404
  render: (args) => ({
3370
- title: `edit ${collapsePath(args.path, process.cwd())}${args.replace_all ? " (replace_all)" : ""}`,
3371
- diff: { path: args.path, before: args.old_string, after: args.new_string }
3405
+ title: `edit ${collapsePath(String(args.path ?? ""), process.cwd())}${args.replace_all ? " (replace_all)" : ""}`,
3406
+ diff: { path: String(args.path ?? ""), before: String(args.old_string ?? ""), after: String(args.new_string ?? "") }
3372
3407
  }),
3373
3408
  async run(args, ctx) {
3374
3409
  const abs = resolvePath(ctx.cwd, args.path);
@@ -3516,7 +3551,7 @@ var init_bash = __esm({
3516
3551
  additionalProperties: false
3517
3552
  },
3518
3553
  needsPermission: true,
3519
- render: (args) => ({ title: formatBashTitle(args.command) }),
3554
+ render: (args) => ({ title: formatBashTitle(String(args.command ?? "")) }),
3520
3555
  run: (args, ctx) => runBash(args, ctx)
3521
3556
  };
3522
3557
  }
@@ -3542,7 +3577,7 @@ var init_glob = __esm({
3542
3577
  additionalProperties: false
3543
3578
  },
3544
3579
  needsPermission: false,
3545
- render: (args) => ({ title: `glob ${args.pattern}${args.path ? ` in ${collapsePath(args.path, process.cwd())}` : ""}` }),
3580
+ render: (args) => ({ title: `glob ${args.pattern ?? ""}${args.path ? ` in ${collapsePath(String(args.path), process.cwd())}` : ""}` }),
3546
3581
  async run(args, ctx) {
3547
3582
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
3548
3583
  const entries = await fg(args.pattern, {
@@ -3660,7 +3695,7 @@ var init_grep = __esm({
3660
3695
  additionalProperties: false
3661
3696
  },
3662
3697
  needsPermission: false,
3663
- render: (args) => ({ title: `grep ${args.pattern}${args.glob ? ` (${args.glob})` : ""}` }),
3698
+ render: (args) => ({ title: `grep ${args.pattern ?? ""}${args.glob ? ` (${args.glob})` : ""}` }),
3664
3699
  async run(args, ctx) {
3665
3700
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
3666
3701
  const mode = args.output_mode ?? "content";
@@ -3692,7 +3727,7 @@ var init_web_fetch = __esm({
3692
3727
  additionalProperties: false
3693
3728
  },
3694
3729
  needsPermission: false,
3695
- render: (args) => ({ title: `GET ${args.url}` }),
3730
+ render: (args) => ({ title: `GET ${args.url ?? ""}` }),
3696
3731
  async run(args) {
3697
3732
  const controller = new AbortController();
3698
3733
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
@@ -3827,7 +3862,7 @@ var init_web_search = __esm({
3827
3862
  additionalProperties: false
3828
3863
  },
3829
3864
  needsPermission: false,
3830
- render: (args) => ({ title: `search web: ${args.query}` }),
3865
+ render: (args) => ({ title: `search web: ${args.query ?? ""}` }),
3831
3866
  async run(args) {
3832
3867
  const count = Math.min(Math.max(args.count ?? DEFAULT_RESULTS, 1), MAX_RESULTS);
3833
3868
  try {
@@ -3913,7 +3948,7 @@ var init_github = __esm({
3913
3948
  additionalProperties: false
3914
3949
  },
3915
3950
  needsPermission: false,
3916
- render: (args) => ({ title: `GitHub PR ${args.owner}/${args.repo}#${args.number}` }),
3951
+ render: (args) => ({ title: `GitHub PR ${args.owner ?? ""}/${args.repo ?? ""}#${args.number ?? ""}` }),
3917
3952
  async run(args, ctx) {
3918
3953
  const token = getToken(ctx);
3919
3954
  const pr = await githubFetch(`/repos/${args.owner}/${args.repo}/pulls/${args.number}`, token);
@@ -3955,7 +3990,7 @@ var init_github = __esm({
3955
3990
  additionalProperties: false
3956
3991
  },
3957
3992
  needsPermission: false,
3958
- render: (args) => ({ title: `GitHub issue ${args.owner}/${args.repo}#${args.number}` }),
3993
+ render: (args) => ({ title: `GitHub issue ${args.owner ?? ""}/${args.repo ?? ""}#${args.number ?? ""}` }),
3959
3994
  async run(args, ctx) {
3960
3995
  const token = getToken(ctx);
3961
3996
  const issue = await githubFetch(`/repos/${args.owner}/${args.repo}/issues/${args.number}`, token);
@@ -4002,7 +4037,7 @@ var init_github = __esm({
4002
4037
  },
4003
4038
  needsPermission: false,
4004
4039
  render: (args) => ({
4005
- title: `GitHub code ${args.owner}/${args.repo}/${args.path}${args.ref ? `@${args.ref}` : ""}`
4040
+ title: `GitHub code ${args.owner ?? ""}/${args.repo ?? ""}/${args.path ?? ""}${args.ref ? `@${args.ref}` : ""}`
4006
4041
  }),
4007
4042
  async run(args, ctx) {
4008
4043
  const token = getToken(ctx);
@@ -4080,7 +4115,7 @@ var init_browser = __esm({
4080
4115
  },
4081
4116
  needsPermission: false,
4082
4117
  render: (args) => ({
4083
- title: `browser ${args.url}${args.screenshot ? " (screenshot)" : ""}`
4118
+ title: `browser ${args.url ?? ""}${args.screenshot ? " (screenshot)" : ""}`
4084
4119
  }),
4085
4120
  async run(args, ctx) {
4086
4121
  let playwright;
@@ -4206,10 +4241,13 @@ var init_tasks = __esm({
4206
4241
  required: ["tasks"]
4207
4242
  },
4208
4243
  needsPermission: false,
4209
- render: (args) => ({
4210
- title: `tasks (${args.tasks.length} items)`,
4211
- body: args.tasks.map((t) => `${t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u25B8" : "\xB7"} ${t.title}`).join("\n")
4212
- }),
4244
+ render: (args) => {
4245
+ const tasks = Array.isArray(args.tasks) ? args.tasks : [];
4246
+ return {
4247
+ title: `tasks (${tasks.length} items)`,
4248
+ body: tasks.map((t) => `${t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u25B8" : "\xB7"} ${t.title}`).join("\n")
4249
+ };
4250
+ },
4213
4251
  run: async (args, ctx) => {
4214
4252
  let tasks;
4215
4253
  try {
@@ -4260,7 +4298,7 @@ var init_memory = __esm({
4260
4298
  needsPermission: false,
4261
4299
  render: (args) => ({
4262
4300
  title: "memory_remember",
4263
- body: `[${args.category}] ${args.content} (importance: ${args.importance})`
4301
+ body: `[${args.category ?? "unknown"}] ${args.content ?? ""} (importance: ${args.importance ?? 1})`
4264
4302
  }),
4265
4303
  run: async (args, ctx) => {
4266
4304
  if (!isMemoryCtx(ctx) || !ctx.memoryManager) {
@@ -4315,7 +4353,7 @@ var init_memory = __esm({
4315
4353
  needsPermission: false,
4316
4354
  render: (args) => ({
4317
4355
  title: "memory_recall",
4318
- body: `Query: "${args.query}"`
4356
+ body: `Query: "${args.query ?? ""}"`
4319
4357
  }),
4320
4358
  run: async (args, ctx) => {
4321
4359
  if (!isMemoryCtx(ctx) || !ctx.memoryManager) {
@@ -4360,7 +4398,7 @@ var init_memory = __esm({
4360
4398
  needsPermission: false,
4361
4399
  render: (args) => ({
4362
4400
  title: "memory_forget",
4363
- body: `Forgetting memory ${args.memory_id}`
4401
+ body: `Forgetting memory ${args.memory_id ?? ""}`
4364
4402
  }),
4365
4403
  run: async (args, ctx) => {
4366
4404
  if (!isMemoryCtx(ctx) || !ctx.memoryManager) {
@@ -4920,7 +4958,7 @@ function makeExpandArtifactTool(store) {
4920
4958
  additionalProperties: false
4921
4959
  },
4922
4960
  needsPermission: false,
4923
- render: (args) => ({ title: `expand ${args.artifact_id}` }),
4961
+ render: (args) => ({ title: `expand ${args.artifact_id ?? ""}` }),
4924
4962
  run: async (args) => {
4925
4963
  const raw = store.retrieve(args.artifact_id);
4926
4964
  if (!raw) {
@@ -8415,6 +8453,9 @@ function usageDir2() {
8415
8453
  function usagePath2() {
8416
8454
  return join18(usageDir2(), "usage.json");
8417
8455
  }
8456
+ function historyPath() {
8457
+ return join18(usageDir2(), "history.jsonl");
8458
+ }
8418
8459
  function today2() {
8419
8460
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8420
8461
  }
@@ -8435,6 +8476,35 @@ async function saveLog(log2) {
8435
8476
  await mkdir9(usageDir2(), { recursive: true });
8436
8477
  await writeFile10(usagePath2(), JSON.stringify(log2, null, 2), "utf8");
8437
8478
  }
8479
+ async function loadHistory() {
8480
+ try {
8481
+ const raw = await readFile13(historyPath(), "utf8");
8482
+ const lines = raw.split("\n").filter((l) => l.trim());
8483
+ const entries = [];
8484
+ for (const line of lines) {
8485
+ try {
8486
+ const parsed = JSON.parse(line);
8487
+ if (parsed.date) entries.push(parsed);
8488
+ } catch {
8489
+ }
8490
+ }
8491
+ return entries;
8492
+ } catch {
8493
+ }
8494
+ return [];
8495
+ }
8496
+ async function upsertHistoryDay(day) {
8497
+ const entries = await loadHistory();
8498
+ const idx = entries.findIndex((e) => e.date === day.date);
8499
+ if (idx >= 0) {
8500
+ entries[idx] = day;
8501
+ } else {
8502
+ entries.push(day);
8503
+ }
8504
+ const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
8505
+ await mkdir9(usageDir2(), { recursive: true });
8506
+ await writeFile10(historyPath(), lines, "utf8");
8507
+ }
8438
8508
  function getOrCreateDay(log2, date) {
8439
8509
  let day = log2.days.find((d) => d.date === date);
8440
8510
  if (!day) {
@@ -8534,9 +8604,18 @@ async function recordUsage(sessionId, usage, gateway) {
8534
8604
  session.gatewayLogs = [...session.gatewayLogs ?? [], gatewaySnapshot].slice(-100);
8535
8605
  }
8536
8606
  await saveLog(log2);
8607
+ await upsertHistoryDay(day);
8608
+ }
8609
+ function mergeDays(usageDays, historyDays) {
8610
+ const map = /* @__PURE__ */ new Map();
8611
+ for (const d of historyDays) map.set(d.date, d);
8612
+ for (const d of usageDays) map.set(d.date, d);
8613
+ return Array.from(map.values()).sort((a, b) => a.date < b.date ? -1 : a.date > b.date ? 1 : 0);
8537
8614
  }
8538
8615
  async function getCostReport(sessionId) {
8539
8616
  const log2 = pruneUsageLog(await loadLog2());
8617
+ const history = await loadHistory();
8618
+ const allDays = mergeDays(log2.days, history);
8540
8619
  const date = today2();
8541
8620
  const currentMonth = date.slice(0, 7);
8542
8621
  const session = sessionId ? log2.sessions.find((s) => s.id === sessionId) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 } : { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
@@ -8548,7 +8627,7 @@ async function getCostReport(sessionId) {
8548
8627
  cachedTokens: 0,
8549
8628
  cost: 0
8550
8629
  };
8551
- for (const d of log2.days) {
8630
+ for (const d of allDays) {
8552
8631
  if (d.date.startsWith(currentMonth)) {
8553
8632
  monthUsage.promptTokens += d.promptTokens;
8554
8633
  monthUsage.completionTokens += d.completionTokens;
@@ -8566,7 +8645,7 @@ async function getCostReport(sessionId) {
8566
8645
  cachedTokens: 0,
8567
8646
  cost: 0
8568
8647
  };
8569
- for (const d of log2.days) {
8648
+ for (const d of allDays) {
8570
8649
  allTime.promptTokens += d.promptTokens;
8571
8650
  allTime.completionTokens += d.completionTokens;
8572
8651
  allTime.cachedTokens += d.cachedTokens;
@@ -9654,7 +9733,9 @@ import { createTwoFilesPatch } from "diff";
9654
9733
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
9655
9734
  function DiffView({ path, before, after, maxLines = 40 }) {
9656
9735
  const theme = useTheme();
9657
- const patch = createTwoFilesPatch(path, path, before, after, "", "", { context: 2 });
9736
+ const safeBefore = before ?? "";
9737
+ const safeAfter = after ?? "";
9738
+ const patch = createTwoFilesPatch(path, path, safeBefore, safeAfter, "", "", { context: 2 });
9658
9739
  const raw = patch.split("\n").slice(4);
9659
9740
  const lines = raw.filter((l) => {
9660
9741
  if (l.startsWith("--- ") || l.startsWith("+++ ")) return false;
@@ -10669,7 +10750,11 @@ import SelectInput from "ink-select-input";
10669
10750
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
10670
10751
  function PermissionModal({ tool, args, onDecide }) {
10671
10752
  const theme = useTheme();
10672
- const render2 = tool.render?.(args);
10753
+ let render2;
10754
+ try {
10755
+ render2 = tool.render?.(args);
10756
+ } catch {
10757
+ }
10673
10758
  const items = [
10674
10759
  { label: "Allow once", value: "allow" },
10675
10760
  { label: "Allow for this session", value: "allow_session" },
@@ -17960,9 +18045,10 @@ ${wcagWarnings.join("\n")}` }
17960
18045
  { kind: "cloud_quota_exhausted", key: mkKey(), used, limit, expiresAt }
17961
18046
  ]);
17962
18047
  } else {
18048
+ const displayText = e instanceof KimiApiError ? humanizeCloudflareError(e) : `init failed: ${e.message}`;
17963
18049
  setEvents((es) => [
17964
18050
  ...es,
17965
- { kind: "error", key: mkKey(), text: `init failed: ${e.message}` }
18051
+ { kind: "error", key: mkKey(), text: displayText }
17966
18052
  ]);
17967
18053
  }
17968
18054
  } finally {
@@ -19512,23 +19598,11 @@ ${lines.join("\n")}` }]);
19512
19598
  { kind: "cloud_quota_exhausted", key: mkKey(), used, limit, expiresAt }
19513
19599
  ]);
19514
19600
  } else {
19515
- const isInvalidJson400 = e instanceof KimiApiError && e.httpStatus === 400 && e.message.includes("invalid escaped character");
19516
- if (isInvalidJson400) {
19517
- messagesRef.current.pop();
19518
- setEvents((es) => [
19519
- ...es,
19520
- {
19521
- kind: "error",
19522
- key: mkKey(),
19523
- text: "API rejected request (invalid JSON in conversation history). Retrying may work; run /clear to reset if it persists."
19524
- }
19525
- ]);
19526
- } else {
19527
- setEvents((es) => [
19528
- ...es,
19529
- { kind: "error", key: mkKey(), text: e.message ?? String(e) }
19530
- ]);
19531
- }
19601
+ const displayText2 = e instanceof KimiApiError ? humanizeCloudflareError(e) : e.message ?? String(e);
19602
+ setEvents((es) => [
19603
+ ...es,
19604
+ { kind: "error", key: mkKey(), text: displayText2 }
19605
+ ]);
19532
19606
  }
19533
19607
  cleanupTurn();
19534
19608
  }
@@ -20007,6 +20081,7 @@ var init_app = __esm({
20007
20081
  init_config();
20008
20082
  init_lsp_config();
20009
20083
  init_loop();
20084
+ init_errors();
20010
20085
  init_system_prompt();
20011
20086
  init_executor();
20012
20087
  init_update_check();
@@ -20359,6 +20434,13 @@ async function runPrintMode(opts2) {
20359
20434
  process.exitCode = 42;
20360
20435
  return;
20361
20436
  }
20437
+ if (err instanceof KimiApiError) {
20438
+ process.stderr.write(`
20439
+ \x1B[31mError: ${humanizeCloudflareError(err)}\x1B[0m
20440
+ `);
20441
+ process.exitCode = 1;
20442
+ return;
20443
+ }
20362
20444
  throw err;
20363
20445
  }
20364
20446
  process.stdout.write("\n");