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 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: ev.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. Only use 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.";
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") return resolve2("allow");
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
- setEvents((es) => [
4096
- ...es,
4097
- { kind: "error", key: mkKey(), text: e.message ?? String(e) }
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();