kimiflare 0.6.0 → 0.7.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 +2 -2
- package/dist/index.js +211 -96
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# kimiflare
|
|
2
2
|
|
|
3
|
-
A terminal coding agent powered by **[Kimi-K2.6](https://developers.cloudflare.com/workers-ai/models/kimi-k2.6/)** on Cloudflare Workers AI.
|
|
3
|
+
A terminal coding agent powered by **[Kimi-K2.6](https://developers.cloudflare.com/workers-ai/models/kimi-k2.6/)** on Cloudflare Workers AI. Moonshot's 1T-parameter open-source model runs directly on your Cloudflare account. You bring the token, your traffic goes straight to Cloudflare.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
$ kimiflare
|
|
@@ -180,7 +180,7 @@ All tool calls show inline; mutating ones require per-call approval the first ti
|
|
|
180
180
|
@cf/moonshotai/kimi-k2.6
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
Direct `fetch` to Workers AI, OpenAI-compatible `messages` + `tools` payload, SSE stream with reasoning + content + tool-call deltas accumulated by index.
|
|
184
184
|
|
|
185
185
|
## Development
|
|
186
186
|
|
package/dist/index.js
CHANGED
|
@@ -142,7 +142,33 @@ var init_errors = __esm({
|
|
|
142
142
|
}
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
+
// src/agent/messages.ts
|
|
146
|
+
function sanitizeString(str) {
|
|
147
|
+
return str.replace(/[\uD800-\uDFFF]/g, "\uFFFD");
|
|
148
|
+
}
|
|
149
|
+
function jsonReplacer(_key, value) {
|
|
150
|
+
if (typeof value === "string") {
|
|
151
|
+
return sanitizeString(value);
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
var init_messages = __esm({
|
|
156
|
+
"src/agent/messages.ts"() {
|
|
157
|
+
"use strict";
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
145
161
|
// src/agent/client.ts
|
|
162
|
+
function cleanErrorMessage(msg) {
|
|
163
|
+
return msg.replace(/^(AiError:\s*)+/, "").trim();
|
|
164
|
+
}
|
|
165
|
+
function isRetryable(err, attempt) {
|
|
166
|
+
if (attempt >= MAX_ATTEMPTS - 1) return false;
|
|
167
|
+
if (err.code !== void 0 && RETRYABLE_CODES.has(err.code)) return true;
|
|
168
|
+
if (err.httpStatus !== void 0 && err.httpStatus >= 500 && err.httpStatus < 600) return true;
|
|
169
|
+
if (err.message.includes("Internal server error")) return true;
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
146
172
|
async function* runKimi(opts2) {
|
|
147
173
|
const url = `https://api.cloudflare.com/client/v4/accounts/${opts2.accountId}/ai/run/${opts2.model}`;
|
|
148
174
|
const body = {
|
|
@@ -156,15 +182,26 @@ async function* runKimi(opts2) {
|
|
|
156
182
|
body.reasoning_effort = opts2.reasoningEffort;
|
|
157
183
|
}
|
|
158
184
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
185
|
+
let res;
|
|
186
|
+
try {
|
|
187
|
+
res = await fetch(url, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: {
|
|
190
|
+
Authorization: `Bearer ${opts2.apiToken}`,
|
|
191
|
+
"Content-Type": "application/json"
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify(body, jsonReplacer),
|
|
194
|
+
signal: opts2.signal
|
|
195
|
+
});
|
|
196
|
+
} catch (fetchErr) {
|
|
197
|
+
const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
198
|
+
if (attempt < MAX_ATTEMPTS - 1) {
|
|
199
|
+
const delay = 500 * 2 ** attempt + Math.random() * 250;
|
|
200
|
+
await sleep(delay, opts2.signal);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
throw new KimiApiError(`kimiflare: network error: ${msg}`, void 0, void 0);
|
|
204
|
+
}
|
|
168
205
|
const contentType = res.headers.get("content-type") ?? "";
|
|
169
206
|
if (!contentType.includes("text/event-stream")) {
|
|
170
207
|
const text = await res.text();
|
|
@@ -174,13 +211,15 @@ async function* runKimi(opts2) {
|
|
|
174
211
|
} catch {
|
|
175
212
|
}
|
|
176
213
|
const err = extractCloudflareError(parsed);
|
|
177
|
-
|
|
214
|
+
const rawMsg = err?.message ?? `HTTP ${res.status}: ${text.slice(0, 300)}`;
|
|
215
|
+
const msg = cleanErrorMessage(rawMsg);
|
|
216
|
+
const apiErr = new KimiApiError(`kimiflare: ${msg}`, err?.code, res.status);
|
|
217
|
+
if (isRetryable(apiErr, attempt)) {
|
|
178
218
|
const delay = 500 * 2 ** attempt + Math.random() * 250;
|
|
179
219
|
await sleep(delay, opts2.signal);
|
|
180
220
|
continue;
|
|
181
221
|
}
|
|
182
|
-
|
|
183
|
-
throw new KimiApiError(`kimiflare: ${msg}`, err?.code, res.status);
|
|
222
|
+
throw apiErr;
|
|
184
223
|
}
|
|
185
224
|
if (!res.body) throw new KimiApiError("kimiflare: empty response body", void 0, res.status);
|
|
186
225
|
yield* parseStream(res.body, opts2.signal);
|
|
@@ -257,9 +296,14 @@ async function* parseStream(body, signal) {
|
|
|
257
296
|
}
|
|
258
297
|
function extractCloudflareError(parsed) {
|
|
259
298
|
if (!parsed || typeof parsed !== "object") return null;
|
|
260
|
-
const
|
|
261
|
-
if (
|
|
262
|
-
return { code:
|
|
299
|
+
const cf = parsed;
|
|
300
|
+
if (cf.success === false && Array.isArray(cf.errors) && cf.errors.length > 0) {
|
|
301
|
+
return { code: cf.errors[0]?.code, message: cf.errors[0]?.message };
|
|
302
|
+
}
|
|
303
|
+
const oai = parsed;
|
|
304
|
+
if (oai.object === "error" && typeof oai.message === "string") {
|
|
305
|
+
const codeNum = typeof oai.code === "number" ? oai.code : void 0;
|
|
306
|
+
return { code: codeNum, message: oai.message };
|
|
263
307
|
}
|
|
264
308
|
return null;
|
|
265
309
|
}
|
|
@@ -277,13 +321,14 @@ function sleep(ms, signal) {
|
|
|
277
321
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
278
322
|
});
|
|
279
323
|
}
|
|
280
|
-
var
|
|
324
|
+
var RETRYABLE_CODES, MAX_ATTEMPTS;
|
|
281
325
|
var init_client = __esm({
|
|
282
326
|
"src/agent/client.ts"() {
|
|
283
327
|
"use strict";
|
|
284
328
|
init_sse();
|
|
285
329
|
init_errors();
|
|
286
|
-
|
|
330
|
+
init_messages();
|
|
331
|
+
RETRYABLE_CODES = /* @__PURE__ */ new Set([3040]);
|
|
287
332
|
MAX_ATTEMPTS = 5;
|
|
288
333
|
}
|
|
289
334
|
});
|
|
@@ -360,9 +405,17 @@ async function runAgentTurn(opts2) {
|
|
|
360
405
|
}
|
|
361
406
|
const assistantMsg = {
|
|
362
407
|
role: "assistant",
|
|
363
|
-
content: content
|
|
364
|
-
...reasoning ? { reasoning_content: reasoning } : {},
|
|
365
|
-
...toolCalls.length ? {
|
|
408
|
+
content: content ? sanitizeString(content) : null,
|
|
409
|
+
...reasoning ? { reasoning_content: sanitizeString(reasoning) } : {},
|
|
410
|
+
...toolCalls.length ? {
|
|
411
|
+
tool_calls: toolCalls.map((tc) => ({
|
|
412
|
+
...tc,
|
|
413
|
+
function: {
|
|
414
|
+
name: tc.function.name,
|
|
415
|
+
arguments: sanitizeString(tc.function.arguments)
|
|
416
|
+
}
|
|
417
|
+
}))
|
|
418
|
+
} : {}
|
|
366
419
|
};
|
|
367
420
|
opts2.messages.push(assistantMsg);
|
|
368
421
|
opts2.callbacks.onAssistantFinal?.(assistantMsg);
|
|
@@ -377,7 +430,7 @@ async function runAgentTurn(opts2) {
|
|
|
377
430
|
opts2.messages.push({
|
|
378
431
|
role: "tool",
|
|
379
432
|
tool_call_id: result.tool_call_id,
|
|
380
|
-
content: result.content,
|
|
433
|
+
content: sanitizeString(result.content),
|
|
381
434
|
name: result.name
|
|
382
435
|
});
|
|
383
436
|
opts2.callbacks.onToolResult?.(result);
|
|
@@ -390,6 +443,7 @@ var init_loop = __esm({
|
|
|
390
443
|
"use strict";
|
|
391
444
|
init_client();
|
|
392
445
|
init_registry();
|
|
446
|
+
init_messages();
|
|
393
447
|
}
|
|
394
448
|
});
|
|
395
449
|
|
|
@@ -1465,6 +1519,7 @@ var init_markdown = __esm({
|
|
|
1465
1519
|
|
|
1466
1520
|
// src/ui/chat.tsx
|
|
1467
1521
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
1522
|
+
import Spinner2 from "ink-spinner";
|
|
1468
1523
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1469
1524
|
function ChatView({ events, showReasoning, theme, verbose }) {
|
|
1470
1525
|
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: events.map((e, i) => {
|
|
@@ -1498,7 +1553,8 @@ function EventView({
|
|
|
1498
1553
|
" ",
|
|
1499
1554
|
evt.reasoning.length > 400 ? evt.reasoning.slice(0, 400) + "\u2026" : evt.reasoning
|
|
1500
1555
|
] }) }) : null,
|
|
1501
|
-
evt.text ? /* @__PURE__ */ jsx4(MD, { text: evt.text, theme }) : null
|
|
1556
|
+
evt.text ? /* @__PURE__ */ jsx4(MD, { text: evt.text, theme }) : null,
|
|
1557
|
+
evt.streaming && /* @__PURE__ */ jsx4(Text4, { color: theme.spinner, children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) })
|
|
1502
1558
|
] });
|
|
1503
1559
|
}
|
|
1504
1560
|
if (evt.kind === "tool") {
|
|
@@ -1524,26 +1580,21 @@ var init_chat = __esm({
|
|
|
1524
1580
|
});
|
|
1525
1581
|
|
|
1526
1582
|
// src/ui/status.tsx
|
|
1583
|
+
import { useEffect, useState } from "react";
|
|
1527
1584
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
1585
|
+
import Spinner3 from "ink-spinner";
|
|
1528
1586
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1529
|
-
function StatusBar({ model, usage, thinking, theme, mode, effort, contextLimit }) {
|
|
1587
|
+
function StatusBar({ model, usage, thinking, turnStartedAt, theme, mode, effort, contextLimit }) {
|
|
1588
|
+
const [now, setNow] = useState(Date.now());
|
|
1530
1589
|
const modeColor = mode === "plan" ? theme.modeBadge.plan : mode === "auto" ? theme.modeBadge.auto : theme.modeBadge.edit;
|
|
1531
1590
|
const warn = usage && usage.prompt_tokens / contextLimit >= 0.8;
|
|
1591
|
+
useEffect(() => {
|
|
1592
|
+
if (!thinking || turnStartedAt === null) return;
|
|
1593
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
1594
|
+
return () => clearInterval(id);
|
|
1595
|
+
}, [thinking, turnStartedAt]);
|
|
1596
|
+
const elapsed = turnStartedAt !== null ? formatElapsed(now - turnStartedAt) : null;
|
|
1532
1597
|
const leftParts = [`${shortModel(model)}`, effort];
|
|
1533
|
-
if (thinking) leftParts.push("thinking\u2026");
|
|
1534
|
-
const rightParts = [];
|
|
1535
|
-
if (usage) {
|
|
1536
|
-
const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
1537
|
-
const uncachedIn = usage.prompt_tokens - cached;
|
|
1538
|
-
const cost = uncachedIn * PRICE_IN_PER_M / 1e6 + cached * PRICE_IN_CACHED_PER_M / 1e6 + usage.completion_tokens * PRICE_OUT_PER_M / 1e6;
|
|
1539
|
-
const pct = Math.round(usage.prompt_tokens / contextLimit * 100);
|
|
1540
|
-
rightParts.push(
|
|
1541
|
-
`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
|
|
1542
|
-
`out ${usage.completion_tokens}`,
|
|
1543
|
-
`ctx ${pct}%`,
|
|
1544
|
-
`${cost.toFixed(5)}`
|
|
1545
|
-
);
|
|
1546
|
-
}
|
|
1547
1598
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1548
1599
|
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1549
1600
|
/* @__PURE__ */ jsxs5(Text5, { color: modeColor, bold: true, children: [
|
|
@@ -1552,10 +1603,18 @@ function StatusBar({ model, usage, thinking, theme, mode, effort, contextLimit }
|
|
|
1552
1603
|
"]"
|
|
1553
1604
|
] }),
|
|
1554
1605
|
/* @__PURE__ */ jsx5(Text5, { children: " " }),
|
|
1555
|
-
/* @__PURE__ */
|
|
1606
|
+
thinking ? /* @__PURE__ */ jsxs5(Text5, { color: theme.spinner, children: [
|
|
1607
|
+
/* @__PURE__ */ jsx5(Spinner3, { type: "dots" }),
|
|
1608
|
+
" ",
|
|
1609
|
+
"thinking",
|
|
1610
|
+
elapsed ? ` \xB7 ${elapsed}` : ""
|
|
1611
|
+
] }) : /* @__PURE__ */ jsxs5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children: [
|
|
1612
|
+
leftParts.join(" \xB7 "),
|
|
1613
|
+
" \xB7 ready"
|
|
1614
|
+
] })
|
|
1556
1615
|
] }),
|
|
1557
|
-
|
|
1558
|
-
/* @__PURE__ */ jsx5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children:
|
|
1616
|
+
usage && /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1617
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children: buildRightParts(usage, contextLimit).join(" \xB7 ") }),
|
|
1559
1618
|
warn ? /* @__PURE__ */ jsxs5(Text5, { color: theme.warn, bold: true, children: [
|
|
1560
1619
|
" \xB7 ",
|
|
1561
1620
|
"/compact recommended"
|
|
@@ -1563,10 +1622,29 @@ function StatusBar({ model, usage, thinking, theme, mode, effort, contextLimit }
|
|
|
1563
1622
|
] })
|
|
1564
1623
|
] });
|
|
1565
1624
|
}
|
|
1625
|
+
function buildRightParts(usage, contextLimit) {
|
|
1626
|
+
const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
1627
|
+
const uncachedIn = usage.prompt_tokens - cached;
|
|
1628
|
+
const cost = uncachedIn * PRICE_IN_PER_M / 1e6 + cached * PRICE_IN_CACHED_PER_M / 1e6 + usage.completion_tokens * PRICE_OUT_PER_M / 1e6;
|
|
1629
|
+
const pct = Math.round(usage.prompt_tokens / contextLimit * 100);
|
|
1630
|
+
return [
|
|
1631
|
+
`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
|
|
1632
|
+
`out ${usage.completion_tokens}`,
|
|
1633
|
+
`ctx ${pct}%`,
|
|
1634
|
+
`${cost.toFixed(5)}`
|
|
1635
|
+
];
|
|
1636
|
+
}
|
|
1566
1637
|
function shortModel(m) {
|
|
1567
1638
|
const last = m.split("/").at(-1) ?? m;
|
|
1568
1639
|
return last;
|
|
1569
1640
|
}
|
|
1641
|
+
function formatElapsed(ms) {
|
|
1642
|
+
const total = Math.floor(ms / 1e3);
|
|
1643
|
+
const m = Math.floor(total / 60);
|
|
1644
|
+
const s = total % 60;
|
|
1645
|
+
if (m === 0) return `${s}s`;
|
|
1646
|
+
return `${m}m ${s}s`;
|
|
1647
|
+
}
|
|
1570
1648
|
var PRICE_IN_PER_M, PRICE_IN_CACHED_PER_M, PRICE_OUT_PER_M;
|
|
1571
1649
|
var init_status = __esm({
|
|
1572
1650
|
"src/ui/status.tsx"() {
|
|
@@ -1671,12 +1749,13 @@ var init_resume_picker = __esm({
|
|
|
1671
1749
|
});
|
|
1672
1750
|
|
|
1673
1751
|
// src/ui/task-list.tsx
|
|
1674
|
-
import { useEffect, useState } from "react";
|
|
1752
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1675
1753
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
1754
|
+
import Spinner4 from "ink-spinner";
|
|
1676
1755
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1677
1756
|
function TaskList({ tasks, theme, startedAt, tokensDelta }) {
|
|
1678
|
-
const [now, setNow] =
|
|
1679
|
-
|
|
1757
|
+
const [now, setNow] = useState2(Date.now());
|
|
1758
|
+
useEffect2(() => {
|
|
1680
1759
|
if (startedAt === null) return;
|
|
1681
1760
|
const allDone2 = tasks.length > 0 && tasks.every((t) => t.status === "completed");
|
|
1682
1761
|
if (allDone2) return;
|
|
@@ -1689,7 +1768,7 @@ function TaskList({ tasks, theme, startedAt, tokensDelta }) {
|
|
|
1689
1768
|
const total = tasks.length;
|
|
1690
1769
|
const allDone = done === total;
|
|
1691
1770
|
const header = active ? active.title : allDone ? `${total} tasks done` : `${done}/${total}`;
|
|
1692
|
-
const elapsed = startedAt ?
|
|
1771
|
+
const elapsed = startedAt ? formatElapsed2(now - startedAt) : null;
|
|
1693
1772
|
const headerStats = [elapsed, tokensDelta > 0 ? `\u2191 ${formatTokens(tokensDelta)} tokens` : null].filter(Boolean).join(" \xB7 ");
|
|
1694
1773
|
const visibleTasks = tasks.slice(0, MAX_VISIBLE);
|
|
1695
1774
|
const hiddenPending = Math.max(0, tasks.length - visibleTasks.length);
|
|
@@ -1723,7 +1802,8 @@ function TaskRow({ task, theme }) {
|
|
|
1723
1802
|
if (task.status === "in_progress") {
|
|
1724
1803
|
return /* @__PURE__ */ jsxs8(Text8, { color: theme.accent, bold: true, children: [
|
|
1725
1804
|
" ",
|
|
1726
|
-
"
|
|
1805
|
+
/* @__PURE__ */ jsx8(Spinner4, { type: "dots" }),
|
|
1806
|
+
" ",
|
|
1727
1807
|
task.title
|
|
1728
1808
|
] });
|
|
1729
1809
|
}
|
|
@@ -1733,7 +1813,7 @@ function TaskRow({ task, theme }) {
|
|
|
1733
1813
|
task.title
|
|
1734
1814
|
] });
|
|
1735
1815
|
}
|
|
1736
|
-
function
|
|
1816
|
+
function formatElapsed2(ms) {
|
|
1737
1817
|
const total = Math.floor(ms / 1e3);
|
|
1738
1818
|
const m = Math.floor(total / 60);
|
|
1739
1819
|
const s = total % 60;
|
|
@@ -2273,7 +2353,7 @@ var init_source = __esm({
|
|
|
2273
2353
|
});
|
|
2274
2354
|
|
|
2275
2355
|
// src/ui/text-input.tsx
|
|
2276
|
-
import { useState as
|
|
2356
|
+
import { useState as useState3, useEffect as useEffect3, useRef } from "react";
|
|
2277
2357
|
import { Text as Text9, useInput } from "ink";
|
|
2278
2358
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2279
2359
|
function shouldTreatAsPaste(input) {
|
|
@@ -2308,9 +2388,9 @@ function CustomTextInput({
|
|
|
2308
2388
|
mask,
|
|
2309
2389
|
enablePaste = false
|
|
2310
2390
|
}) {
|
|
2311
|
-
const [cursorOffset, setCursorOffset] =
|
|
2391
|
+
const [cursorOffset, setCursorOffset] = useState3(value.length);
|
|
2312
2392
|
const pastesRef = useRef(/* @__PURE__ */ new Map());
|
|
2313
|
-
|
|
2393
|
+
useEffect3(() => {
|
|
2314
2394
|
if (!focus) return;
|
|
2315
2395
|
setCursorOffset((prev) => prev > value.length ? value.length : prev);
|
|
2316
2396
|
}, [value, focus]);
|
|
@@ -2469,7 +2549,7 @@ var init_text_input = __esm({
|
|
|
2469
2549
|
"use strict";
|
|
2470
2550
|
init_source();
|
|
2471
2551
|
PASTE_CHAR_THRESHOLD = 200;
|
|
2472
|
-
PASTE_NEWLINE_THRESHOLD =
|
|
2552
|
+
PASTE_NEWLINE_THRESHOLD = 1;
|
|
2473
2553
|
}
|
|
2474
2554
|
});
|
|
2475
2555
|
|
|
@@ -2586,15 +2666,15 @@ var init_update_check = __esm({
|
|
|
2586
2666
|
});
|
|
2587
2667
|
|
|
2588
2668
|
// src/ui/onboarding.tsx
|
|
2589
|
-
import { useState as
|
|
2669
|
+
import { useState as useState4 } from "react";
|
|
2590
2670
|
import { Box as Box9, Text as Text10 } from "ink";
|
|
2591
2671
|
import { Fragment, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2592
2672
|
function Onboarding({ onDone }) {
|
|
2593
|
-
const [step, setStep] =
|
|
2594
|
-
const [accountId, setAccountId] =
|
|
2595
|
-
const [apiToken, setApiToken] =
|
|
2596
|
-
const [model, setModel] =
|
|
2597
|
-
const [savedPath, setSavedPath] =
|
|
2673
|
+
const [step, setStep] = useState4("accountId");
|
|
2674
|
+
const [accountId, setAccountId] = useState4("");
|
|
2675
|
+
const [apiToken, setApiToken] = useState4("");
|
|
2676
|
+
const [model, setModel] = useState4(DEFAULT_MODEL);
|
|
2677
|
+
const [savedPath, setSavedPath] = useState4(null);
|
|
2598
2678
|
const stepIndex = STEPS.indexOf(step) + 1;
|
|
2599
2679
|
const handleAccountIdSubmit = (value) => {
|
|
2600
2680
|
const trimmed = value.trim();
|
|
@@ -2917,7 +2997,7 @@ var app_exports = {};
|
|
|
2917
2997
|
__export(app_exports, {
|
|
2918
2998
|
renderApp: () => renderApp
|
|
2919
2999
|
});
|
|
2920
|
-
import { useState as
|
|
3000
|
+
import { useState as useState5, useRef as useRef2, useEffect as useEffect4, useCallback } from "react";
|
|
2921
3001
|
import { Box as Box11, Text as Text12, useApp, useInput as useInput2, render } from "ink";
|
|
2922
3002
|
import { existsSync } from "fs";
|
|
2923
3003
|
import { join as join5 } from "path";
|
|
@@ -2925,27 +3005,28 @@ import { unlink } from "fs/promises";
|
|
|
2925
3005
|
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2926
3006
|
function App({ initialCfg }) {
|
|
2927
3007
|
const { exit } = useApp();
|
|
2928
|
-
const [cfg, setCfg] =
|
|
2929
|
-
const [events, setEvents] =
|
|
2930
|
-
const [input, setInput] =
|
|
2931
|
-
const [busy, setBusy] =
|
|
2932
|
-
const [usage, setUsage] =
|
|
2933
|
-
const [showReasoning, setShowReasoning] =
|
|
2934
|
-
const [perm, setPerm] =
|
|
2935
|
-
const [queue, setQueue] =
|
|
2936
|
-
const [history, setHistory] =
|
|
2937
|
-
const [historyIndex, setHistoryIndex] =
|
|
2938
|
-
const [draftInput, setDraftInput] =
|
|
2939
|
-
const [mode, setMode] =
|
|
2940
|
-
const [effort, setEffort] =
|
|
3008
|
+
const [cfg, setCfg] = useState5(initialCfg);
|
|
3009
|
+
const [events, setEvents] = useState5([]);
|
|
3010
|
+
const [input, setInput] = useState5("");
|
|
3011
|
+
const [busy, setBusy] = useState5(false);
|
|
3012
|
+
const [usage, setUsage] = useState5(null);
|
|
3013
|
+
const [showReasoning, setShowReasoning] = useState5(false);
|
|
3014
|
+
const [perm, setPerm] = useState5(null);
|
|
3015
|
+
const [queue, setQueue] = useState5([]);
|
|
3016
|
+
const [history, setHistory] = useState5([]);
|
|
3017
|
+
const [historyIndex, setHistoryIndex] = useState5(-1);
|
|
3018
|
+
const [draftInput, setDraftInput] = useState5("");
|
|
3019
|
+
const [mode, setMode] = useState5("edit");
|
|
3020
|
+
const [effort, setEffort] = useState5(
|
|
2941
3021
|
initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
|
|
2942
3022
|
);
|
|
2943
|
-
const [theme, setTheme] =
|
|
2944
|
-
const [resumeSessions, setResumeSessions] =
|
|
2945
|
-
const [tasks, setTasks] =
|
|
2946
|
-
const [tasksStartedAt, setTasksStartedAt] =
|
|
2947
|
-
const [tasksStartTokens, setTasksStartTokens] =
|
|
2948
|
-
const [
|
|
3023
|
+
const [theme, setTheme] = useState5(resolveTheme(initialCfg?.theme));
|
|
3024
|
+
const [resumeSessions, setResumeSessions] = useState5(null);
|
|
3025
|
+
const [tasks, setTasks] = useState5([]);
|
|
3026
|
+
const [tasksStartedAt, setTasksStartedAt] = useState5(null);
|
|
3027
|
+
const [tasksStartTokens, setTasksStartTokens] = useState5(0);
|
|
3028
|
+
const [turnStartedAt, setTurnStartedAt] = useState5(null);
|
|
3029
|
+
const [verbose, setVerbose] = useState5(false);
|
|
2949
3030
|
const messagesRef = useRef2([
|
|
2950
3031
|
{
|
|
2951
3032
|
role: "system",
|
|
@@ -2965,7 +3046,35 @@ function App({ initialCfg }) {
|
|
|
2965
3046
|
const effortRef = useRef2(effort);
|
|
2966
3047
|
const tasksRef = useRef2([]);
|
|
2967
3048
|
const usageRef = useRef2(null);
|
|
2968
|
-
|
|
3049
|
+
const updateCheckedRef = useRef2(false);
|
|
3050
|
+
const compactSuggestedRef = useRef2(false);
|
|
3051
|
+
useEffect4(() => {
|
|
3052
|
+
if (!cfg || updateCheckedRef.current) return;
|
|
3053
|
+
updateCheckedRef.current = true;
|
|
3054
|
+
void checkForUpdate().then((result) => {
|
|
3055
|
+
if (result.hasUpdate) {
|
|
3056
|
+
setEvents((e) => [
|
|
3057
|
+
...e,
|
|
3058
|
+
{
|
|
3059
|
+
kind: "info",
|
|
3060
|
+
key: mkKey(),
|
|
3061
|
+
text: `update available: ${result.localVersion} \u2192 ${result.latestVersion}`
|
|
3062
|
+
}
|
|
3063
|
+
]);
|
|
3064
|
+
void isGitRepo().then((git) => {
|
|
3065
|
+
setEvents((e) => [
|
|
3066
|
+
...e,
|
|
3067
|
+
{
|
|
3068
|
+
kind: "info",
|
|
3069
|
+
key: mkKey(),
|
|
3070
|
+
text: git ? "run: git pull && npm install && npm run build then restart kimiflare" : "run: npm update -g kimiflare then restart"
|
|
3071
|
+
}
|
|
3072
|
+
]);
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
});
|
|
3076
|
+
}, [cfg]);
|
|
3077
|
+
useEffect4(() => {
|
|
2969
3078
|
modeRef.current = mode;
|
|
2970
3079
|
messagesRef.current[0] = {
|
|
2971
3080
|
role: "system",
|
|
@@ -2980,7 +3089,7 @@ function App({ initialCfg }) {
|
|
|
2980
3089
|
executorRef.current.clearSessionPermissions();
|
|
2981
3090
|
}
|
|
2982
3091
|
}, [mode, cfg?.model]);
|
|
2983
|
-
|
|
3092
|
+
useEffect4(() => {
|
|
2984
3093
|
effortRef.current = effort;
|
|
2985
3094
|
}, [effort]);
|
|
2986
3095
|
const saveSessionSafe = useCallback(async () => {
|
|
@@ -3053,7 +3162,7 @@ function App({ initialCfg }) {
|
|
|
3053
3162
|
return;
|
|
3054
3163
|
}
|
|
3055
3164
|
setBusy(true);
|
|
3056
|
-
|
|
3165
|
+
setTurnStartedAt(Date.now());
|
|
3057
3166
|
const controller = new AbortController();
|
|
3058
3167
|
activeControllerRef.current = controller;
|
|
3059
3168
|
try {
|
|
@@ -3090,6 +3199,7 @@ function App({ initialCfg }) {
|
|
|
3090
3199
|
}
|
|
3091
3200
|
} finally {
|
|
3092
3201
|
setBusy(false);
|
|
3202
|
+
setTurnStartedAt(null);
|
|
3093
3203
|
activeControllerRef.current = null;
|
|
3094
3204
|
}
|
|
3095
3205
|
}, [cfg, busy, saveSessionSafe]);
|
|
@@ -3133,8 +3243,9 @@ function App({ initialCfg }) {
|
|
|
3133
3243
|
"Do not call `tasks_set` for this. Just read what you need, then write the file."
|
|
3134
3244
|
].join("\n");
|
|
3135
3245
|
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: "/init" }]);
|
|
3136
|
-
messagesRef.current.push({ role: "user", content: prompt });
|
|
3246
|
+
messagesRef.current.push({ role: "user", content: sanitizeString(prompt) });
|
|
3137
3247
|
setBusy(true);
|
|
3248
|
+
setTurnStartedAt(Date.now());
|
|
3138
3249
|
const controller = new AbortController();
|
|
3139
3250
|
activeControllerRef.current = controller;
|
|
3140
3251
|
try {
|
|
@@ -3229,6 +3340,7 @@ function App({ initialCfg }) {
|
|
|
3229
3340
|
}
|
|
3230
3341
|
} finally {
|
|
3231
3342
|
setBusy(false);
|
|
3343
|
+
setTurnStartedAt(null);
|
|
3232
3344
|
activeAsstIdRef.current = null;
|
|
3233
3345
|
activeControllerRef.current = null;
|
|
3234
3346
|
}
|
|
@@ -3273,11 +3385,12 @@ function App({ initialCfg }) {
|
|
|
3273
3385
|
if (c === "/clear") {
|
|
3274
3386
|
messagesRef.current = [messagesRef.current[0]];
|
|
3275
3387
|
sessionIdRef.current = null;
|
|
3276
|
-
setEvents([
|
|
3388
|
+
setEvents([]);
|
|
3277
3389
|
setUsage(null);
|
|
3278
3390
|
setTasks([]);
|
|
3279
3391
|
setTasksStartedAt(null);
|
|
3280
3392
|
setTasksStartTokens(0);
|
|
3393
|
+
compactSuggestedRef.current = false;
|
|
3281
3394
|
return true;
|
|
3282
3395
|
}
|
|
3283
3396
|
if (c === "/reasoning") {
|
|
@@ -3484,8 +3597,9 @@ use: /thinking low | medium | high`
|
|
|
3484
3597
|
if (trimmed.startsWith("/") && handleSlash(trimmed)) return;
|
|
3485
3598
|
const display = displayText?.trim() || trimmed;
|
|
3486
3599
|
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display }]);
|
|
3487
|
-
messagesRef.current.push({ role: "user", content: trimmed });
|
|
3600
|
+
messagesRef.current.push({ role: "user", content: sanitizeString(trimmed) });
|
|
3488
3601
|
setBusy(true);
|
|
3602
|
+
setTurnStartedAt(Date.now());
|
|
3489
3603
|
const controller = new AbortController();
|
|
3490
3604
|
activeControllerRef.current = controller;
|
|
3491
3605
|
try {
|
|
@@ -3599,13 +3713,14 @@ use: /thinking low | medium | high`
|
|
|
3599
3713
|
}
|
|
3600
3714
|
} finally {
|
|
3601
3715
|
setBusy(false);
|
|
3716
|
+
setTurnStartedAt(null);
|
|
3602
3717
|
activeAsstIdRef.current = null;
|
|
3603
3718
|
activeControllerRef.current = null;
|
|
3604
3719
|
}
|
|
3605
3720
|
},
|
|
3606
3721
|
[cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe]
|
|
3607
3722
|
);
|
|
3608
|
-
|
|
3723
|
+
useEffect4(() => {
|
|
3609
3724
|
if (!busy && queue.length > 0) {
|
|
3610
3725
|
const next = queue[0];
|
|
3611
3726
|
setQueue((q) => q.slice(1));
|
|
@@ -3632,20 +3747,18 @@ use: /thinking low | medium | high`
|
|
|
3632
3747
|
},
|
|
3633
3748
|
[busy, processMessage]
|
|
3634
3749
|
);
|
|
3635
|
-
|
|
3750
|
+
useEffect4(() => {
|
|
3751
|
+
if (compactSuggestedRef.current) return;
|
|
3636
3752
|
if (usage && usage.prompt_tokens / CONTEXT_LIMIT >= AUTO_COMPACT_SUGGEST_PCT) {
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
}
|
|
3647
|
-
];
|
|
3648
|
-
});
|
|
3753
|
+
compactSuggestedRef.current = true;
|
|
3754
|
+
setEvents((e) => [
|
|
3755
|
+
...e,
|
|
3756
|
+
{
|
|
3757
|
+
kind: "info",
|
|
3758
|
+
key: mkKey(),
|
|
3759
|
+
text: `context ${Math.round(usage.prompt_tokens / CONTEXT_LIMIT * 100)}% full \u2014 run /compact to summarize older turns`
|
|
3760
|
+
}
|
|
3761
|
+
]);
|
|
3649
3762
|
}
|
|
3650
3763
|
}, [usage]);
|
|
3651
3764
|
if (!cfg) {
|
|
@@ -3699,6 +3812,7 @@ use: /thinking low | medium | high`
|
|
|
3699
3812
|
model: cfg.model,
|
|
3700
3813
|
usage,
|
|
3701
3814
|
thinking: busy,
|
|
3815
|
+
turnStartedAt,
|
|
3702
3816
|
theme,
|
|
3703
3817
|
mode,
|
|
3704
3818
|
effort,
|
|
@@ -3767,6 +3881,7 @@ var init_app = __esm({
|
|
|
3767
3881
|
init_system_prompt();
|
|
3768
3882
|
init_compact();
|
|
3769
3883
|
init_executor();
|
|
3884
|
+
init_messages();
|
|
3770
3885
|
init_chat();
|
|
3771
3886
|
init_status();
|
|
3772
3887
|
init_permission();
|