kimiflare 0.9.1 → 0.10.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/dist/index.js +268 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
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";
|
|
@@ -455,21 +489,209 @@ function nextMode(m) {
|
|
|
455
489
|
function isBlockedInPlanMode(toolName) {
|
|
456
490
|
return MUTATING_TOOLS.has(toolName);
|
|
457
491
|
}
|
|
492
|
+
function tokenizeCommand(command) {
|
|
493
|
+
const tokens = [];
|
|
494
|
+
let current = "";
|
|
495
|
+
let inQuote = null;
|
|
496
|
+
for (const ch of command) {
|
|
497
|
+
if (inQuote) {
|
|
498
|
+
if (ch === inQuote) {
|
|
499
|
+
inQuote = null;
|
|
500
|
+
} else {
|
|
501
|
+
current += ch;
|
|
502
|
+
}
|
|
503
|
+
} else if (ch === '"' || ch === "'") {
|
|
504
|
+
inQuote = ch;
|
|
505
|
+
} else if (/\s/.test(ch)) {
|
|
506
|
+
if (current) {
|
|
507
|
+
tokens.push(current);
|
|
508
|
+
current = "";
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
current += ch;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (current) tokens.push(current);
|
|
515
|
+
return tokens;
|
|
516
|
+
}
|
|
517
|
+
function isReadOnlyBash(command) {
|
|
518
|
+
const trimmed = command.trim();
|
|
519
|
+
if (!trimmed) return false;
|
|
520
|
+
if (DANGEROUS_PATTERNS.test(trimmed)) return false;
|
|
521
|
+
const tokens = tokenizeCommand(trimmed);
|
|
522
|
+
if (tokens.length === 0) return false;
|
|
523
|
+
const first = tokens[0];
|
|
524
|
+
let cmdIndex = 0;
|
|
525
|
+
if (first === "cd" && tokens.length >= 3 && tokens[1] && tokens[2] === "&&") {
|
|
526
|
+
cmdIndex = 3;
|
|
527
|
+
}
|
|
528
|
+
const cmd = tokens[cmdIndex];
|
|
529
|
+
if (!cmd) return false;
|
|
530
|
+
const args = tokens.slice(cmdIndex + 1);
|
|
531
|
+
if (cmd === "git") {
|
|
532
|
+
const sub = args[0] ?? "";
|
|
533
|
+
const allowed = GIT_READONLY_SUBCOMMANDS[sub];
|
|
534
|
+
if (allowed === void 0) return false;
|
|
535
|
+
if (allowed === true) return true;
|
|
536
|
+
switch (sub) {
|
|
537
|
+
case "branch":
|
|
538
|
+
return !args.some((a) => /^-[dDmMcC]/.test(a));
|
|
539
|
+
case "stash":
|
|
540
|
+
return args[1] === "list";
|
|
541
|
+
case "remote":
|
|
542
|
+
return args[1] === "-v" || args[1] === "--verbose" || args.length === 1;
|
|
543
|
+
case "tag":
|
|
544
|
+
return args[1] === "-l" || args[1] === "--list" || args.length === 1;
|
|
545
|
+
case "config":
|
|
546
|
+
return args[1] === "--list" || args[1]?.startsWith("--get") === true || args.length === 1;
|
|
547
|
+
default:
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const argCheck = COMMANDS_NEEDING_ARG_CHECK[cmd];
|
|
552
|
+
if (argCheck) {
|
|
553
|
+
return argCheck(args);
|
|
554
|
+
}
|
|
555
|
+
return READONLY_COMMANDS.has(cmd);
|
|
556
|
+
}
|
|
458
557
|
function systemPromptForMode(m) {
|
|
459
558
|
if (m === "plan") {
|
|
460
|
-
return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or bash.
|
|
559
|
+
return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or mutating bash commands. You may use read-only bash commands (e.g., git log, git diff, ls, cat) along with read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
|
|
461
560
|
}
|
|
462
561
|
if (m === "auto") {
|
|
463
562
|
return "\n\nAUTO MODE is active. The user has opted into autonomous execution \u2014 every tool call will be auto-approved. Work efficiently, but do not take irreversible destructive actions (rm -rf, git push --force, dropping tables, etc.) without pausing to describe them in chat first. Prefer smaller reversible steps.";
|
|
464
563
|
}
|
|
465
564
|
return "";
|
|
466
565
|
}
|
|
467
|
-
var MODES, MUTATING_TOOLS;
|
|
566
|
+
var MODES, MUTATING_TOOLS, DANGEROUS_PATTERNS, GIT_READONLY_SUBCOMMANDS, READONLY_COMMANDS, COMMANDS_NEEDING_ARG_CHECK;
|
|
468
567
|
var init_mode = __esm({
|
|
469
568
|
"src/mode.ts"() {
|
|
470
569
|
"use strict";
|
|
471
570
|
MODES = ["edit", "plan", "auto"];
|
|
472
571
|
MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "bash"]);
|
|
572
|
+
DANGEROUS_PATTERNS = /[<>|;&`$]|\$\(|\$\{|&&|\|\||\b&\s*$/;
|
|
573
|
+
GIT_READONLY_SUBCOMMANDS = {
|
|
574
|
+
log: true,
|
|
575
|
+
diff: true,
|
|
576
|
+
status: true,
|
|
577
|
+
show: true,
|
|
578
|
+
blame: true,
|
|
579
|
+
describe: true,
|
|
580
|
+
"rev-parse": true,
|
|
581
|
+
"ls-files": true,
|
|
582
|
+
reflog: true,
|
|
583
|
+
shortlog: true,
|
|
584
|
+
whatchanged: true,
|
|
585
|
+
grep: true,
|
|
586
|
+
branch: false,
|
|
587
|
+
// needs check: block -d/-D/-m/-M/-c/-C
|
|
588
|
+
stash: false,
|
|
589
|
+
// needs check: only allow "list"
|
|
590
|
+
remote: false,
|
|
591
|
+
// needs check: only allow -v
|
|
592
|
+
tag: false,
|
|
593
|
+
// needs check: only allow -l
|
|
594
|
+
config: false
|
|
595
|
+
// needs check: only allow --list/--get
|
|
596
|
+
};
|
|
597
|
+
READONLY_COMMANDS = /* @__PURE__ */ new Set([
|
|
598
|
+
// File system
|
|
599
|
+
"ls",
|
|
600
|
+
"cat",
|
|
601
|
+
"head",
|
|
602
|
+
"tail",
|
|
603
|
+
"pwd",
|
|
604
|
+
"echo",
|
|
605
|
+
"file",
|
|
606
|
+
"stat",
|
|
607
|
+
"readlink",
|
|
608
|
+
"realpath",
|
|
609
|
+
"dirname",
|
|
610
|
+
"basename",
|
|
611
|
+
"wc",
|
|
612
|
+
"sort",
|
|
613
|
+
"uniq",
|
|
614
|
+
"diff",
|
|
615
|
+
"cmp",
|
|
616
|
+
// Search
|
|
617
|
+
"grep",
|
|
618
|
+
"rg",
|
|
619
|
+
"ag",
|
|
620
|
+
"fd",
|
|
621
|
+
// System info
|
|
622
|
+
"ps",
|
|
623
|
+
"df",
|
|
624
|
+
"du",
|
|
625
|
+
"env",
|
|
626
|
+
"printenv",
|
|
627
|
+
"which",
|
|
628
|
+
"whereis",
|
|
629
|
+
"uname",
|
|
630
|
+
"hostname",
|
|
631
|
+
"uptime",
|
|
632
|
+
"free",
|
|
633
|
+
"date",
|
|
634
|
+
"id",
|
|
635
|
+
"whoami",
|
|
636
|
+
"groups",
|
|
637
|
+
// Dev tools (version/info only)
|
|
638
|
+
"node",
|
|
639
|
+
"npx",
|
|
640
|
+
"python3",
|
|
641
|
+
"ruby",
|
|
642
|
+
"perl",
|
|
643
|
+
// Utilities
|
|
644
|
+
"jq",
|
|
645
|
+
"yq",
|
|
646
|
+
"awk",
|
|
647
|
+
"cut",
|
|
648
|
+
"tr",
|
|
649
|
+
"base64",
|
|
650
|
+
"sha256sum",
|
|
651
|
+
"md5sum",
|
|
652
|
+
"shasum",
|
|
653
|
+
"hexdump",
|
|
654
|
+
"xxd",
|
|
655
|
+
"strings",
|
|
656
|
+
"less",
|
|
657
|
+
"more",
|
|
658
|
+
"man",
|
|
659
|
+
"clear",
|
|
660
|
+
"history",
|
|
661
|
+
// Archive inspection
|
|
662
|
+
"zipinfo",
|
|
663
|
+
// Network
|
|
664
|
+
"ping",
|
|
665
|
+
"netstat",
|
|
666
|
+
"ss",
|
|
667
|
+
"lsof"
|
|
668
|
+
]);
|
|
669
|
+
COMMANDS_NEEDING_ARG_CHECK = {
|
|
670
|
+
find: (args) => !args.some((a) => a === "-delete" || a === "-exec"),
|
|
671
|
+
sed: (args) => !args.some((a) => a === "-i" || a.startsWith("-i")),
|
|
672
|
+
tar: (args) => args[0] === "-tf" || args[0] === "--list",
|
|
673
|
+
unzip: (args) => args[0] === "-l",
|
|
674
|
+
curl: (args) => !args.some((a) => a === "-o" || a === "-O" || a === "-d" || a === "--data" || a.startsWith("-X")),
|
|
675
|
+
wget: (args) => !args.some((a) => a === "-O" || a === "--output-document" || a.startsWith("--post")),
|
|
676
|
+
npm: (args) => ["list", "view", "config"].includes(args[0] ?? "") && !(args[0] === "config" && args[1] && !args[1].startsWith("get") && args[1] !== "list"),
|
|
677
|
+
tsc: (args) => args.every(
|
|
678
|
+
(a) => ["--noEmit", "--version", "--showConfig", "--help", "-h", "--init"].includes(a)
|
|
679
|
+
),
|
|
680
|
+
eslint: (args) => args.every(
|
|
681
|
+
(a) => ["--version", "--print-config", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
682
|
+
),
|
|
683
|
+
prettier: (args) => args.every(
|
|
684
|
+
(a) => ["--version", "--check", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
685
|
+
),
|
|
686
|
+
jest: (args) => args.every(
|
|
687
|
+
(a) => ["--version", "--listTests", "--showConfig", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
688
|
+
),
|
|
689
|
+
vitest: (args) => args.every(
|
|
690
|
+
(a) => ["--version", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
691
|
+
),
|
|
692
|
+
go: (args) => ["version", "env", "list", "mod"].includes(args[0] ?? "") && !(args[0] === "mod" && args[1] && !["graph", "download", "why", "verify"].includes(args[1])),
|
|
693
|
+
cargo: (args) => ["--version", "-V", "check", "test", "metadata"].includes(args[0] ?? "") && !(args[0] === "test" && args.includes("--no-run") === false)
|
|
694
|
+
};
|
|
473
695
|
}
|
|
474
696
|
});
|
|
475
697
|
|
|
@@ -3677,7 +3899,26 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
3677
3899
|
setUsage(u);
|
|
3678
3900
|
},
|
|
3679
3901
|
askPermission: (req) => new Promise((resolve2) => {
|
|
3680
|
-
if (modeRef.current === "auto")
|
|
3902
|
+
if (modeRef.current === "auto") {
|
|
3903
|
+
resolve2("allow");
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
if (modeRef.current === "plan" && isBlockedInPlanMode(req.tool.name)) {
|
|
3907
|
+
if (req.tool.name === "bash" && typeof req.args.command === "string" && isReadOnlyBash(req.args.command)) {
|
|
3908
|
+
resolve2("allow");
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3911
|
+
setEvents((e) => [
|
|
3912
|
+
...e,
|
|
3913
|
+
{
|
|
3914
|
+
kind: "info",
|
|
3915
|
+
key: mkKey(),
|
|
3916
|
+
text: `plan mode blocked ${req.tool.name}; exit plan mode to execute`
|
|
3917
|
+
}
|
|
3918
|
+
]);
|
|
3919
|
+
resolve2("deny");
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3681
3922
|
setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
|
|
3682
3923
|
})
|
|
3683
3924
|
}
|
|
@@ -4072,6 +4313,10 @@ use: /thinking low | medium | high`
|
|
|
4072
4313
|
return;
|
|
4073
4314
|
}
|
|
4074
4315
|
if (modeRef.current === "plan" && isBlockedInPlanMode(req.tool.name)) {
|
|
4316
|
+
if (req.tool.name === "bash" && typeof req.args.command === "string" && isReadOnlyBash(req.args.command)) {
|
|
4317
|
+
resolve2("allow");
|
|
4318
|
+
return;
|
|
4319
|
+
}
|
|
4075
4320
|
setEvents((e) => [
|
|
4076
4321
|
...e,
|
|
4077
4322
|
{
|
|
@@ -4092,10 +4337,23 @@ use: /thinking low | medium | high`
|
|
|
4092
4337
|
if (e.name === "AbortError") {
|
|
4093
4338
|
setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
|
|
4094
4339
|
} else {
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4340
|
+
const isInvalidJson400 = e instanceof KimiApiError && e.httpStatus === 400 && e.message.includes("invalid escaped character");
|
|
4341
|
+
if (isInvalidJson400) {
|
|
4342
|
+
messagesRef.current.pop();
|
|
4343
|
+
setEvents((es) => [
|
|
4344
|
+
...es,
|
|
4345
|
+
{
|
|
4346
|
+
kind: "error",
|
|
4347
|
+
key: mkKey(),
|
|
4348
|
+
text: "API rejected request (invalid JSON in conversation history). Retrying may work; run /clear to reset if it persists."
|
|
4349
|
+
}
|
|
4350
|
+
]);
|
|
4351
|
+
} else {
|
|
4352
|
+
setEvents((es) => [
|
|
4353
|
+
...es,
|
|
4354
|
+
{ kind: "error", key: mkKey(), text: e.message ?? String(e) }
|
|
4355
|
+
]);
|
|
4356
|
+
}
|
|
4099
4357
|
}
|
|
4100
4358
|
} finally {
|
|
4101
4359
|
setBusy(false);
|
|
@@ -4273,6 +4531,7 @@ var init_app = __esm({
|
|
|
4273
4531
|
init_compact();
|
|
4274
4532
|
init_executor();
|
|
4275
4533
|
init_messages();
|
|
4534
|
+
init_errors();
|
|
4276
4535
|
init_chat();
|
|
4277
4536
|
init_status();
|
|
4278
4537
|
init_permission();
|