kimiflare 0.49.0 → 0.50.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/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
  }
@@ -8415,6 +8450,9 @@ function usageDir2() {
8415
8450
  function usagePath2() {
8416
8451
  return join18(usageDir2(), "usage.json");
8417
8452
  }
8453
+ function historyPath() {
8454
+ return join18(usageDir2(), "history.jsonl");
8455
+ }
8418
8456
  function today2() {
8419
8457
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
8420
8458
  }
@@ -8435,6 +8473,35 @@ async function saveLog(log2) {
8435
8473
  await mkdir9(usageDir2(), { recursive: true });
8436
8474
  await writeFile10(usagePath2(), JSON.stringify(log2, null, 2), "utf8");
8437
8475
  }
8476
+ async function loadHistory() {
8477
+ try {
8478
+ const raw = await readFile13(historyPath(), "utf8");
8479
+ const lines = raw.split("\n").filter((l) => l.trim());
8480
+ const entries = [];
8481
+ for (const line of lines) {
8482
+ try {
8483
+ const parsed = JSON.parse(line);
8484
+ if (parsed.date) entries.push(parsed);
8485
+ } catch {
8486
+ }
8487
+ }
8488
+ return entries;
8489
+ } catch {
8490
+ }
8491
+ return [];
8492
+ }
8493
+ async function upsertHistoryDay(day) {
8494
+ const entries = await loadHistory();
8495
+ const idx = entries.findIndex((e) => e.date === day.date);
8496
+ if (idx >= 0) {
8497
+ entries[idx] = day;
8498
+ } else {
8499
+ entries.push(day);
8500
+ }
8501
+ const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
8502
+ await mkdir9(usageDir2(), { recursive: true });
8503
+ await writeFile10(historyPath(), lines, "utf8");
8504
+ }
8438
8505
  function getOrCreateDay(log2, date) {
8439
8506
  let day = log2.days.find((d) => d.date === date);
8440
8507
  if (!day) {
@@ -8534,9 +8601,18 @@ async function recordUsage(sessionId, usage, gateway) {
8534
8601
  session.gatewayLogs = [...session.gatewayLogs ?? [], gatewaySnapshot].slice(-100);
8535
8602
  }
8536
8603
  await saveLog(log2);
8604
+ await upsertHistoryDay(day);
8605
+ }
8606
+ function mergeDays(usageDays, historyDays) {
8607
+ const map = /* @__PURE__ */ new Map();
8608
+ for (const d of historyDays) map.set(d.date, d);
8609
+ for (const d of usageDays) map.set(d.date, d);
8610
+ return Array.from(map.values()).sort((a, b) => a.date < b.date ? -1 : a.date > b.date ? 1 : 0);
8537
8611
  }
8538
8612
  async function getCostReport(sessionId) {
8539
8613
  const log2 = pruneUsageLog(await loadLog2());
8614
+ const history = await loadHistory();
8615
+ const allDays = mergeDays(log2.days, history);
8540
8616
  const date = today2();
8541
8617
  const currentMonth = date.slice(0, 7);
8542
8618
  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 +8624,7 @@ async function getCostReport(sessionId) {
8548
8624
  cachedTokens: 0,
8549
8625
  cost: 0
8550
8626
  };
8551
- for (const d of log2.days) {
8627
+ for (const d of allDays) {
8552
8628
  if (d.date.startsWith(currentMonth)) {
8553
8629
  monthUsage.promptTokens += d.promptTokens;
8554
8630
  monthUsage.completionTokens += d.completionTokens;
@@ -8566,7 +8642,7 @@ async function getCostReport(sessionId) {
8566
8642
  cachedTokens: 0,
8567
8643
  cost: 0
8568
8644
  };
8569
- for (const d of log2.days) {
8645
+ for (const d of allDays) {
8570
8646
  allTime.promptTokens += d.promptTokens;
8571
8647
  allTime.completionTokens += d.completionTokens;
8572
8648
  allTime.cachedTokens += d.cachedTokens;
@@ -17960,9 +18036,10 @@ ${wcagWarnings.join("\n")}` }
17960
18036
  { kind: "cloud_quota_exhausted", key: mkKey(), used, limit, expiresAt }
17961
18037
  ]);
17962
18038
  } else {
18039
+ const displayText = e instanceof KimiApiError ? humanizeCloudflareError(e) : `init failed: ${e.message}`;
17963
18040
  setEvents((es) => [
17964
18041
  ...es,
17965
- { kind: "error", key: mkKey(), text: `init failed: ${e.message}` }
18042
+ { kind: "error", key: mkKey(), text: displayText }
17966
18043
  ]);
17967
18044
  }
17968
18045
  } finally {
@@ -19512,23 +19589,11 @@ ${lines.join("\n")}` }]);
19512
19589
  { kind: "cloud_quota_exhausted", key: mkKey(), used, limit, expiresAt }
19513
19590
  ]);
19514
19591
  } 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
- }
19592
+ const displayText2 = e instanceof KimiApiError ? humanizeCloudflareError(e) : e.message ?? String(e);
19593
+ setEvents((es) => [
19594
+ ...es,
19595
+ { kind: "error", key: mkKey(), text: displayText2 }
19596
+ ]);
19532
19597
  }
19533
19598
  cleanupTurn();
19534
19599
  }
@@ -20007,6 +20072,7 @@ var init_app = __esm({
20007
20072
  init_config();
20008
20073
  init_lsp_config();
20009
20074
  init_loop();
20075
+ init_errors();
20010
20076
  init_system_prompt();
20011
20077
  init_executor();
20012
20078
  init_update_check();
@@ -20359,6 +20425,13 @@ async function runPrintMode(opts2) {
20359
20425
  process.exitCode = 42;
20360
20426
  return;
20361
20427
  }
20428
+ if (err instanceof KimiApiError) {
20429
+ process.stderr.write(`
20430
+ \x1B[31mError: ${humanizeCloudflareError(err)}\x1B[0m
20431
+ `);
20432
+ process.exitCode = 1;
20433
+ return;
20434
+ }
20362
20435
  throw err;
20363
20436
  }
20364
20437
  process.stdout.write("\n");