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 +101 -0
- package/dist/index.js +103 -30
- package/dist/index.js.map +1 -1
- package/dist/sdk/index.js +50 -10
- package/dist/sdk/index.js.map +1 -1
- package/package.json +1 -1
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 (
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
19516
|
-
|
|
19517
|
-
|
|
19518
|
-
|
|
19519
|
-
|
|
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");
|