kimiflare 0.14.0 → 0.15.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
@@ -35,6 +35,8 @@ async function loadConfig() {
35
35
  const envEffort = readReasoningEffortEnv();
36
36
  const envTheme = process.env.KIMI_THEME;
37
37
  const envCoauthor = readCoauthorEnv();
38
+ const envCacheStable = process.env.KIMIFLARE_CACHE_STABLE_PROMPTS;
39
+ const cacheStablePrompts = envCacheStable === "0" || envCacheStable === "false" ? false : true;
38
40
  if (envAccount && envToken) {
39
41
  return {
40
42
  accountId: envAccount,
@@ -44,7 +46,8 @@ async function loadConfig() {
44
46
  reasoningEffort: envEffort,
45
47
  coauthor: envCoauthor?.enabled ?? true,
46
48
  coauthorName: envCoauthor?.name,
47
- coauthorEmail: envCoauthor?.email
49
+ coauthorEmail: envCoauthor?.email,
50
+ cacheStablePrompts
48
51
  };
49
52
  }
50
53
  try {
@@ -60,7 +63,8 @@ async function loadConfig() {
60
63
  coauthor: envCoauthor?.enabled ?? parsed.coauthor ?? true,
61
64
  coauthorName: envCoauthor?.name ?? parsed.coauthorName,
62
65
  coauthorEmail: envCoauthor?.email ?? parsed.coauthorEmail,
63
- mcpServers: parsed.mcpServers
66
+ mcpServers: parsed.mcpServers,
67
+ cacheStablePrompts: parsed.cacheStablePrompts ?? cacheStablePrompts
64
68
  };
65
69
  }
66
70
  } catch {
@@ -153,6 +157,20 @@ function jsonReplacer(_key, value) {
153
157
  }
154
158
  return value;
155
159
  }
160
+ function stableStringify(value, replacer, space) {
161
+ function sortKeys(obj) {
162
+ if (obj === null || typeof obj !== "object") return obj;
163
+ if (Array.isArray(obj)) return obj.map(sortKeys);
164
+ const sorted2 = {};
165
+ const keys = Object.keys(obj).sort();
166
+ for (const k of keys) {
167
+ sorted2[k] = sortKeys(obj[k]);
168
+ }
169
+ return sorted2;
170
+ }
171
+ const sorted = sortKeys(value);
172
+ return JSON.stringify(sorted, replacer, space);
173
+ }
156
174
  var init_messages = __esm({
157
175
  "src/agent/messages.ts"() {
158
176
  "use strict";
@@ -185,13 +203,17 @@ async function* runKimi(opts2) {
185
203
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
186
204
  let res;
187
205
  try {
206
+ const headers = {
207
+ Authorization: `Bearer ${opts2.apiToken}`,
208
+ "Content-Type": "application/json"
209
+ };
210
+ if (opts2.sessionId) {
211
+ headers["X-Session-ID"] = opts2.sessionId;
212
+ }
188
213
  res = await fetch(url, {
189
214
  method: "POST",
190
- headers: {
191
- Authorization: `Bearer ${opts2.apiToken}`,
192
- "Content-Type": "application/json"
193
- },
194
- body: JSON.stringify(body, jsonReplacer),
215
+ headers,
216
+ body: stableStringify(body, jsonReplacer),
195
217
  signal: opts2.signal
196
218
  });
197
219
  } catch (fetchErr) {
@@ -451,12 +473,76 @@ async function logCostDebug(entry) {
451
473
  await mkdir2(debugDir(), { recursive: true });
452
474
  await appendFile(debugPath(), JSON.stringify(entry) + "\n", "utf8");
453
475
  }
476
+ function serializePrefix(messages) {
477
+ let end = 0;
478
+ while (end < messages.length && messages[end].role === "system") {
479
+ end++;
480
+ }
481
+ return messages.slice(0, end).map((m) => typeof m.content === "string" ? m.content : JSON.stringify(m.content)).join("\n---\n");
482
+ }
483
+ function comparePromptPrefixes(prev, curr) {
484
+ const prevPrefix = prev ? serializePrefix(prev) : "";
485
+ const currPrefix = serializePrefix(curr);
486
+ const totalChars = curr.reduce((sum, m) => {
487
+ if (typeof m.content === "string") return sum + m.content.length;
488
+ if (Array.isArray(m.content)) return sum + m.content.map((p) => p.type === "text" ? p.text.length : 0).reduce((a, b) => a + b, 0);
489
+ return sum;
490
+ }, 0);
491
+ let firstDiffByte = null;
492
+ let changedSegment = null;
493
+ if (prevPrefix !== currPrefix) {
494
+ const minLen = Math.min(prevPrefix.length, currPrefix.length);
495
+ for (let i = 0; i < minLen; i++) {
496
+ if (prevPrefix[i] !== currPrefix[i]) {
497
+ firstDiffByte = i;
498
+ break;
499
+ }
500
+ }
501
+ if (firstDiffByte === null && prevPrefix.length !== currPrefix.length) {
502
+ firstDiffByte = minLen;
503
+ }
504
+ if (curr.length >= 1 && curr[0].role === "system") {
505
+ const staticLen = typeof curr[0].content === "string" ? curr[0].content.length : JSON.stringify(curr[0].content).length;
506
+ if (firstDiffByte !== null && firstDiffByte < staticLen) {
507
+ changedSegment = "static";
508
+ } else if (curr.length >= 2 && curr[1].role === "system") {
509
+ const sessionLen = typeof curr[1].content === "string" ? curr[1].content.length : JSON.stringify(curr[1].content).length;
510
+ if (firstDiffByte !== null && firstDiffByte < staticLen + 5 + sessionLen) {
511
+ changedSegment = "session";
512
+ } else {
513
+ changedSegment = "dynamic";
514
+ }
515
+ } else {
516
+ changedSegment = "dynamic";
517
+ }
518
+ } else {
519
+ changedSegment = "dynamic";
520
+ }
521
+ } else {
522
+ changedSegment = "none";
523
+ }
524
+ const staticPrefixChars = curr.length > 0 && curr[0].role === "system" && typeof curr[0].content === "string" ? curr[0].content.length : 0;
525
+ const sessionPrefixChars = curr.length > 1 && curr[1].role === "system" && typeof curr[1].content === "string" ? curr[1].content.length : 0;
526
+ const dynamicSuffixChars = totalChars - staticPrefixChars - sessionPrefixChars;
527
+ return {
528
+ staticPrefixChars,
529
+ sessionPrefixChars,
530
+ dynamicSuffixChars,
531
+ firstDiffByte,
532
+ changedSegment,
533
+ cacheHitRatio: 0
534
+ // populated by caller with actual usage data
535
+ };
536
+ }
454
537
  async function logTurnDebug(ctx) {
455
538
  const promptSections = analyzePrompt(ctx.messages);
456
539
  const promptTotalChars = promptSections.reduce((sum, s) => sum + s.chars, 0);
457
540
  const toolStats = buildToolStats(ctx.toolResults);
458
541
  const toolTotalRaw = toolStats.reduce((sum, t) => sum + t.rawBytes, 0);
459
542
  const toolTotalReduced = toolStats.reduce((sum, t) => sum + t.reducedBytes, 0);
543
+ const cacheDiagnostics = comparePromptPrefixes(ctx.previousMessages, ctx.messages);
544
+ const cachedTokens = ctx.usage.prompt_tokens_details?.cached_tokens ?? 0;
545
+ cacheDiagnostics.cacheHitRatio = ctx.usage.prompt_tokens > 0 ? cachedTokens / ctx.usage.prompt_tokens : 0;
460
546
  await logCostDebug({
461
547
  v: LOG_VERSION,
462
548
  ts: now(),
@@ -469,7 +555,8 @@ async function logTurnDebug(ctx) {
469
555
  toolStats,
470
556
  toolTotalRawBytes: toolTotalRaw,
471
557
  toolTotalReducedBytes: toolTotalReduced,
472
- toolSavingsPct: toolTotalRaw > 0 ? Math.round((toolTotalRaw - toolTotalReduced) / toolTotalRaw * 100) : 0
558
+ toolSavingsPct: toolTotalRaw > 0 ? Math.round((toolTotalRaw - toolTotalReduced) / toolTotalRaw * 100) : 0,
559
+ cacheDiagnostics
473
560
  });
474
561
  }
475
562
  var LOG_VERSION;
@@ -488,6 +575,7 @@ async function runAgentTurn(opts2) {
488
575
  let lastUsage = null;
489
576
  for (let iter = 0; iter < max; iter++) {
490
577
  turn++;
578
+ const previousMessages = opts2.messages.slice();
491
579
  const toolCalls = [];
492
580
  const toolResults = [];
493
581
  let content = "";
@@ -502,7 +590,8 @@ async function runAgentTurn(opts2) {
502
590
  signal: opts2.signal,
503
591
  temperature: opts2.temperature,
504
592
  maxCompletionTokens: opts2.maxCompletionTokens,
505
- reasoningEffort: opts2.reasoningEffort
593
+ reasoningEffort: opts2.reasoningEffort,
594
+ sessionId: opts2.sessionId
506
595
  });
507
596
  for await (const ev of events) {
508
597
  switch (ev.type) {
@@ -561,6 +650,7 @@ async function runAgentTurn(opts2) {
561
650
  sessionId: opts2.sessionId,
562
651
  turn,
563
652
  messages: opts2.messages,
653
+ previousMessages,
564
654
  toolResults,
565
655
  usage: lastUsage
566
656
  });
@@ -588,6 +678,7 @@ async function runAgentTurn(opts2) {
588
678
  sessionId: opts2.sessionId,
589
679
  turn,
590
680
  messages: opts2.messages,
681
+ previousMessages,
591
682
  toolResults,
592
683
  usage: lastUsage
593
684
  });
@@ -847,25 +938,8 @@ function loadContextFile(cwd) {
847
938
  }
848
939
  return null;
849
940
  }
850
- function buildSystemPrompt(opts2) {
851
- const now2 = opts2.now ?? /* @__PURE__ */ new Date();
852
- const date = now2.toISOString().slice(0, 10);
853
- const shell = process.env.SHELL ? basename(process.env.SHELL) : "sh";
854
- const toolsBlock = opts2.tools.map((t) => {
855
- const perm = t.needsPermission ? " [needs user permission]" : "";
856
- return `- \`${t.name}\`${perm}: ${t.description.split("\n")[0]}`;
857
- }).join("\n");
858
- const base = `You are kimiflare, an interactive coding assistant running in the user's terminal. You act on the user's local filesystem through the tools listed below. You are powered by the ${opts2.model} model on Cloudflare Workers AI.
859
-
860
- Environment:
861
- - Working directory: ${opts2.cwd}
862
- - Platform: ${platform()} ${release()}
863
- - Shell: ${shell}
864
- - Home: ${homedir3()}
865
- - Today: ${date}
866
-
867
- Tools available:
868
- ${toolsBlock}
941
+ function buildStaticPrefix(opts2) {
942
+ return `You are kimiflare, an interactive coding assistant running in the user's terminal. You act on the user's local filesystem through the tools listed below. You are powered by the ${opts2.model} model on Cloudflare Workers AI.
869
943
 
870
944
  How to work:
871
945
  - Prefer calling tools over guessing. Read files before editing them. Use \`glob\` and \`grep\` to explore code before assuming structure.
@@ -878,13 +952,39 @@ How to work:
878
952
  - If a request is ambiguous, ask one focused question instead of making large assumptions.
879
953
  - When you finish a task, stop. Do not add a closing summary.
880
954
  - When creating git commits, you must include \`Co-authored-by: kimiflare <kimiflare@proton.me>\` in the commit message so kimiflare is credited as a contributor. The bash tool will also auto-append this trailer when it detects git commit-creating commands.`;
955
+ }
956
+ function buildSessionPrefix(opts2) {
957
+ const now2 = opts2.now ?? /* @__PURE__ */ new Date();
958
+ const date = now2.toISOString().slice(0, 10);
959
+ const shell = process.env.SHELL ? basename(process.env.SHELL) : "sh";
960
+ const toolsBlock = opts2.tools.map((t) => {
961
+ const perm = t.needsPermission ? " [needs user permission]" : "";
962
+ return `- \`${t.name}\`${perm}: ${t.description.split("\n")[0]}`;
963
+ }).join("\n");
964
+ const env2 = `Environment:
965
+ - Working directory: ${opts2.cwd}
966
+ - Platform: ${platform()} ${release()}
967
+ - Shell: ${shell}
968
+ - Home: ${homedir3()}
969
+ - Today: ${date}`;
970
+ const tools = `Tools available:
971
+ ${toolsBlock}`;
881
972
  const ctx = loadContextFile(opts2.cwd);
882
973
  const contextBlock = ctx ? `
883
974
 
884
975
  Project context from ${ctx.name} (${ctx.lineCount} lines, treat as authoritative):
885
976
  ${ctx.content.trim()}` : "";
886
977
  const modeBlock = opts2.mode ? systemPromptForMode(opts2.mode) : "";
887
- return base + contextBlock + modeBlock;
978
+ return env2 + "\n\n" + tools + contextBlock + modeBlock;
979
+ }
980
+ function buildSystemPrompt(opts2) {
981
+ return buildStaticPrefix(opts2) + "\n\n" + buildSessionPrefix(opts2);
982
+ }
983
+ function buildSystemMessages(opts2) {
984
+ return [
985
+ { role: "system", content: buildStaticPrefix(opts2) },
986
+ { role: "system", content: buildSessionPrefix(opts2) }
987
+ ];
888
988
  }
889
989
  var CONTEXT_FILENAMES, MAX_CONTEXT_BYTES;
890
990
  var init_system_prompt = __esm({
@@ -1712,11 +1812,15 @@ function indexOfNthUserFromEnd(messages, n) {
1712
1812
  async function compactMessages(opts2) {
1713
1813
  const keep = opts2.keepLastTurns ?? 4;
1714
1814
  const messages = opts2.messages;
1715
- const systemMsg = messages.find((m) => m.role === "system");
1716
- if (!systemMsg) throw new Error("compact: no system message found");
1815
+ let prefixEnd = 0;
1816
+ while (prefixEnd < messages.length && messages[prefixEnd].role === "system") {
1817
+ prefixEnd++;
1818
+ }
1819
+ const prefix = messages.slice(0, prefixEnd);
1820
+ if (prefix.length === 0) throw new Error("compact: no system message found");
1717
1821
  const cutoffUserIdx = indexOfNthUserFromEnd(messages, keep);
1718
1822
  const firstKeepIdx = cutoffUserIdx >= 0 ? cutoffUserIdx : messages.length;
1719
- const toSummarize = messages.slice(1, firstKeepIdx);
1823
+ const toSummarize = messages.slice(prefixEnd, firstKeepIdx);
1720
1824
  const toKeep = messages.slice(firstKeepIdx);
1721
1825
  if (toSummarize.length === 0) {
1722
1826
  return { summary: "", newMessages: messages, replacedCount: 0 };
@@ -1758,7 +1862,7 @@ ${summary.trim()}`
1758
1862
  };
1759
1863
  return {
1760
1864
  summary: summary.trim(),
1761
- newMessages: [systemMsg, summaryMsg, ...toKeep],
1865
+ newMessages: [...prefix, summaryMsg, ...toKeep],
1762
1866
  replacedCount: toSummarize.length
1763
1867
  };
1764
1868
  }
@@ -3970,6 +4074,17 @@ function capEvents(prev) {
3970
4074
  if (prev.length <= MAX_EVENTS) return prev;
3971
4075
  return prev.slice(prev.length - MAX_EVENTS);
3972
4076
  }
4077
+ function makePrefixMessages(cacheStable, model, mode, tools) {
4078
+ if (cacheStable) {
4079
+ return buildSystemMessages({ cwd: process.cwd(), tools, model, mode });
4080
+ }
4081
+ return [
4082
+ {
4083
+ role: "system",
4084
+ content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode })
4085
+ }
4086
+ ];
4087
+ }
3973
4088
  function findImagePaths(text) {
3974
4089
  const paths = [];
3975
4090
  for (const token of text.split(/\s+/)) {
@@ -4017,17 +4132,10 @@ function App({ initialCfg, initialUpdateResult }) {
4017
4132
  const [verbose, setVerbose] = useState6(false);
4018
4133
  const [hasUpdate, setHasUpdate] = useState6(initialUpdateResult?.hasUpdate ?? false);
4019
4134
  const [latestVersion, setLatestVersion] = useState6(initialUpdateResult?.latestVersion ?? null);
4020
- const messagesRef = useRef3([
4021
- {
4022
- role: "system",
4023
- content: buildSystemPrompt({
4024
- cwd: process.cwd(),
4025
- tools: ALL_TOOLS,
4026
- model: cfg?.model ?? DEFAULT_MODEL,
4027
- mode: "edit"
4028
- })
4029
- }
4030
- ]);
4135
+ const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
4136
+ const messagesRef = useRef3(
4137
+ makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
4138
+ );
4031
4139
  const executorRef = useRef3(new ToolExecutor(ALL_TOOLS));
4032
4140
  const activeAsstIdRef = useRef3(null);
4033
4141
  const activeControllerRef = useRef3(null);
@@ -4095,15 +4203,27 @@ function App({ initialCfg, initialUpdateResult }) {
4095
4203
  }, [cfg, initialUpdateResult]);
4096
4204
  useEffect4(() => {
4097
4205
  modeRef.current = mode;
4098
- messagesRef.current[0] = {
4099
- role: "system",
4100
- content: buildSystemPrompt({
4101
- cwd: process.cwd(),
4102
- tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4103
- model: cfg?.model ?? DEFAULT_MODEL,
4104
- mode
4105
- })
4106
- };
4206
+ if (cacheStableRef.current) {
4207
+ messagesRef.current[1] = {
4208
+ role: "system",
4209
+ content: buildSessionPrefix({
4210
+ cwd: process.cwd(),
4211
+ tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4212
+ model: cfg?.model ?? DEFAULT_MODEL,
4213
+ mode
4214
+ })
4215
+ };
4216
+ } else {
4217
+ messagesRef.current[0] = {
4218
+ role: "system",
4219
+ content: buildSystemPrompt({
4220
+ cwd: process.cwd(),
4221
+ tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4222
+ model: cfg?.model ?? DEFAULT_MODEL,
4223
+ mode
4224
+ })
4225
+ };
4226
+ }
4107
4227
  if (mode === "plan") {
4108
4228
  executorRef.current.clearSessionPermissions();
4109
4229
  }
@@ -4176,15 +4296,27 @@ function App({ initialCfg, initialUpdateResult }) {
4176
4296
  }
4177
4297
  }
4178
4298
  if (totalTools > 0) {
4179
- messagesRef.current[0] = {
4180
- role: "system",
4181
- content: buildSystemPrompt({
4182
- cwd: process.cwd(),
4183
- tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4184
- model: cfg.model ?? DEFAULT_MODEL,
4185
- mode: modeRef.current
4186
- })
4187
- };
4299
+ if (cacheStableRef.current) {
4300
+ messagesRef.current[1] = {
4301
+ role: "system",
4302
+ content: buildSessionPrefix({
4303
+ cwd: process.cwd(),
4304
+ tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4305
+ model: cfg.model ?? DEFAULT_MODEL,
4306
+ mode: modeRef.current
4307
+ })
4308
+ };
4309
+ } else {
4310
+ messagesRef.current[0] = {
4311
+ role: "system",
4312
+ content: buildSystemPrompt({
4313
+ cwd: process.cwd(),
4314
+ tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4315
+ model: cfg.model ?? DEFAULT_MODEL,
4316
+ mode: modeRef.current
4317
+ })
4318
+ };
4319
+ }
4188
4320
  setEvents((e) => [
4189
4321
  ...e,
4190
4322
  { kind: "info", key: mkKey(), text: `MCP connected \u2014 ${totalTools} external tool${totalTools === 1 ? "" : "s"} available` }
@@ -4457,15 +4589,27 @@ function App({ initialCfg, initialUpdateResult }) {
4457
4589
  }
4458
4590
  });
4459
4591
  if (existsSync(join8(cwd, "KIMI.md"))) {
4460
- messagesRef.current[0] = {
4461
- role: "system",
4462
- content: buildSystemPrompt({
4463
- cwd,
4464
- tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4465
- model: cfg.model,
4466
- mode: modeRef.current
4467
- })
4468
- };
4592
+ if (cacheStableRef.current) {
4593
+ messagesRef.current[1] = {
4594
+ role: "system",
4595
+ content: buildSessionPrefix({
4596
+ cwd,
4597
+ tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4598
+ model: cfg.model,
4599
+ mode: modeRef.current
4600
+ })
4601
+ };
4602
+ } else {
4603
+ messagesRef.current[0] = {
4604
+ role: "system",
4605
+ content: buildSystemPrompt({
4606
+ cwd,
4607
+ tools: [...ALL_TOOLS, ...mcpToolsRef.current],
4608
+ model: cfg.model,
4609
+ mode: modeRef.current
4610
+ })
4611
+ };
4612
+ }
4469
4613
  setEvents((e) => [
4470
4614
  ...e,
4471
4615
  { kind: "info", key: mkKey(), text: "KIMI.md generated; context loaded for future turns" }
@@ -4549,7 +4693,11 @@ function App({ initialCfg, initialUpdateResult }) {
4549
4693
  return true;
4550
4694
  }
4551
4695
  if (c === "/clear") {
4552
- messagesRef.current = [messagesRef.current[0]];
4696
+ if (cacheStableRef.current && messagesRef.current.length >= 2) {
4697
+ messagesRef.current = [messagesRef.current[0], messagesRef.current[1]];
4698
+ } else {
4699
+ messagesRef.current = [messagesRef.current[0]];
4700
+ }
4553
4701
  sessionIdRef.current = null;
4554
4702
  setEvents([]);
4555
4703
  setUsage(null);