thatgfsj-code 1.0.3 → 3.0.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.
Files changed (184) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/dist/app/index.d.ts +40 -3
  4. package/dist/app/index.d.ts.map +1 -1
  5. package/dist/app/index.js +87 -12
  6. package/dist/app/index.js.map +1 -1
  7. package/dist/cache/fingerprint.d.ts +49 -0
  8. package/dist/cache/fingerprint.d.ts.map +1 -0
  9. package/dist/cache/fingerprint.js +100 -0
  10. package/dist/cache/fingerprint.js.map +1 -0
  11. package/dist/cache/index.d.ts +15 -0
  12. package/dist/cache/index.d.ts.map +1 -0
  13. package/dist/cache/index.js +15 -0
  14. package/dist/cache/index.js.map +1 -0
  15. package/dist/cache/smartModel.d.ts +40 -0
  16. package/dist/cache/smartModel.d.ts.map +1 -0
  17. package/dist/cache/smartModel.js +43 -0
  18. package/dist/cache/smartModel.js.map +1 -0
  19. package/dist/cache/stats.d.ts +93 -0
  20. package/dist/cache/stats.d.ts.map +1 -0
  21. package/dist/cache/stats.js +155 -0
  22. package/dist/cache/stats.js.map +1 -0
  23. package/dist/cache/volatile.d.ts +32 -0
  24. package/dist/cache/volatile.d.ts.map +1 -0
  25. package/dist/cache/volatile.js +44 -0
  26. package/dist/cache/volatile.js.map +1 -0
  27. package/dist/cmd/index.d.ts +21 -0
  28. package/dist/cmd/index.d.ts.map +1 -1
  29. package/dist/cmd/index.js +155 -31
  30. package/dist/cmd/index.js.map +1 -1
  31. package/dist/config/index.d.ts.map +1 -1
  32. package/dist/config/index.js +8 -0
  33. package/dist/config/index.js.map +1 -1
  34. package/dist/config/types.d.ts +14 -0
  35. package/dist/config/types.d.ts.map +1 -1
  36. package/dist/llm/anthropic.d.ts +55 -5
  37. package/dist/llm/anthropic.d.ts.map +1 -1
  38. package/dist/llm/anthropic.js +172 -26
  39. package/dist/llm/anthropic.js.map +1 -1
  40. package/dist/llm/index.d.ts +26 -8
  41. package/dist/llm/index.d.ts.map +1 -1
  42. package/dist/llm/index.js +83 -15
  43. package/dist/llm/index.js.map +1 -1
  44. package/dist/llm/openai.d.ts +28 -8
  45. package/dist/llm/openai.d.ts.map +1 -1
  46. package/dist/llm/openai.js +58 -8
  47. package/dist/llm/openai.js.map +1 -1
  48. package/dist/llm/provider.d.ts +40 -8
  49. package/dist/llm/provider.d.ts.map +1 -1
  50. package/dist/llm/provider.js +8 -1
  51. package/dist/llm/provider.js.map +1 -1
  52. package/dist/prompts/index.d.ts +32 -0
  53. package/dist/prompts/index.d.ts.map +1 -1
  54. package/dist/prompts/index.js +33 -11
  55. package/dist/prompts/index.js.map +1 -1
  56. package/dist/session/index.d.ts +43 -3
  57. package/dist/session/index.d.ts.map +1 -1
  58. package/dist/session/index.js +114 -17
  59. package/dist/session/index.js.map +1 -1
  60. package/dist/skills/brainstorming.js +33 -33
  61. package/dist/skills/code-review.js +35 -35
  62. package/dist/skills/executing-plans.js +18 -18
  63. package/dist/skills/frontend-design.js +25 -25
  64. package/dist/skills/git-workflow.js +26 -26
  65. package/dist/skills/improve-architecture.js +28 -28
  66. package/dist/skills/neuroweave.js +37 -37
  67. package/dist/skills/playwright.js +62 -62
  68. package/dist/skills/prototype.js +20 -20
  69. package/dist/skills/subagent.js +19 -19
  70. package/dist/skills/supabase.js +34 -34
  71. package/dist/skills/systematic-debugging.js +34 -34
  72. package/dist/skills/tdd.js +29 -29
  73. package/dist/skills/triage.js +25 -25
  74. package/dist/skills/verification.js +22 -22
  75. package/dist/skills/writing-plans.js +32 -32
  76. package/dist/tools/nwt.js +16 -16
  77. package/dist/tui/app.d.ts.map +1 -1
  78. package/dist/tui/app.js +9 -2
  79. package/dist/tui/app.js.map +1 -1
  80. package/dist/tui/components/ChatList.d.ts.map +1 -1
  81. package/dist/tui/components/ChatList.js +4 -3
  82. package/dist/tui/components/ChatList.js.map +1 -1
  83. package/dist/tui/components/Header.d.ts +8 -0
  84. package/dist/tui/components/Header.d.ts.map +1 -1
  85. package/dist/tui/components/Header.js +17 -3
  86. package/dist/tui/components/Header.js.map +1 -1
  87. package/dist/tui/components/InitWizard.d.ts +12 -1
  88. package/dist/tui/components/InitWizard.d.ts.map +1 -1
  89. package/dist/tui/components/InitWizard.js +58 -3
  90. package/dist/tui/components/InitWizard.js.map +1 -1
  91. package/dist/tui/components/ToolCall.d.ts +1 -1
  92. package/dist/tui/components/ToolCall.d.ts.map +1 -1
  93. package/dist/tui/components/ToolCall.js +4 -4
  94. package/dist/tui/components/ToolCall.js.map +1 -1
  95. package/dist/tui/hooks/useChat.d.ts +22 -0
  96. package/dist/tui/hooks/useChat.d.ts.map +1 -1
  97. package/dist/tui/hooks/useChat.js +127 -50
  98. package/dist/tui/hooks/useChat.js.map +1 -1
  99. package/dist/tui/hooks/useCommands.d.ts.map +1 -1
  100. package/dist/tui/hooks/useCommands.js +78 -0
  101. package/dist/tui/hooks/useCommands.js.map +1 -1
  102. package/dist/types.d.ts +79 -6
  103. package/dist/types.d.ts.map +1 -1
  104. package/dist/types.js +7 -0
  105. package/dist/types.js.map +1 -1
  106. package/dist/utils/stableStringify.d.ts +21 -0
  107. package/dist/utils/stableStringify.d.ts.map +1 -0
  108. package/dist/utils/stableStringify.js +53 -0
  109. package/dist/utils/stableStringify.js.map +1 -0
  110. package/dist/utils/thinking.d.ts +59 -0
  111. package/dist/utils/thinking.d.ts.map +1 -0
  112. package/dist/utils/thinking.js +104 -0
  113. package/dist/utils/thinking.js.map +1 -0
  114. package/install.bat +63 -0
  115. package/install.ps1 +238 -0
  116. package/install.sh +113 -0
  117. package/package.json +7 -4
  118. package/src/app/index.ts +180 -108
  119. package/src/cache/fingerprint.ts +101 -0
  120. package/src/cache/index.ts +15 -0
  121. package/src/cache/smartModel.ts +52 -0
  122. package/src/cache/stats.ts +199 -0
  123. package/src/cache/volatile.ts +47 -0
  124. package/src/cmd/index.tsx +288 -160
  125. package/src/config/index.ts +156 -148
  126. package/src/config/providers.ts +234 -234
  127. package/src/config/types.ts +67 -53
  128. package/src/hooks/index.ts +111 -111
  129. package/src/llm/anthropic.ts +388 -243
  130. package/src/llm/gemini.ts +169 -169
  131. package/src/llm/index.ts +265 -200
  132. package/src/llm/openai.ts +243 -196
  133. package/src/llm/provider.ts +66 -34
  134. package/src/prompts/index.ts +260 -220
  135. package/src/session/compactor.ts +103 -103
  136. package/src/session/index.ts +181 -87
  137. package/src/session/message.ts +42 -42
  138. package/src/skills/brainstorming.ts +43 -43
  139. package/src/skills/code-review.ts +45 -45
  140. package/src/skills/executing-plans.ts +27 -27
  141. package/src/skills/frontend-design.ts +35 -35
  142. package/src/skills/git-workflow.ts +36 -36
  143. package/src/skills/improve-architecture.ts +38 -38
  144. package/src/skills/index.ts +136 -136
  145. package/src/skills/neuroweave.ts +47 -47
  146. package/src/skills/playwright.ts +72 -72
  147. package/src/skills/prototype.ts +30 -30
  148. package/src/skills/subagent.ts +28 -28
  149. package/src/skills/supabase.ts +44 -44
  150. package/src/skills/systematic-debugging.ts +44 -44
  151. package/src/skills/tdd.ts +39 -39
  152. package/src/skills/triage.ts +35 -35
  153. package/src/skills/verification.ts +31 -31
  154. package/src/skills/writing-plans.ts +42 -42
  155. package/src/tools/nwt.ts +598 -598
  156. package/src/tools/types.ts +122 -122
  157. package/src/tui/app.tsx +199 -186
  158. package/src/tui/components/ChatList.tsx +41 -32
  159. package/src/tui/components/ChatMessage.tsx +54 -54
  160. package/src/tui/components/Header.tsx +56 -28
  161. package/src/tui/components/InitWizard.tsx +217 -132
  162. package/src/tui/components/Markdown.tsx +35 -35
  163. package/src/tui/components/ModelSelector.tsx +87 -87
  164. package/src/tui/components/StatusBar.tsx +30 -30
  165. package/src/tui/components/Thinking.tsx +17 -17
  166. package/src/tui/components/ToolCall.tsx +102 -71
  167. package/src/tui/components/UserInput.tsx +131 -131
  168. package/src/tui/hooks/useChat.ts +287 -194
  169. package/src/tui/hooks/useCommands.ts +234 -154
  170. package/src/tui/index.ts +6 -6
  171. package/src/tui/welcome.ts +177 -177
  172. package/src/types.ts +104 -42
  173. package/src/utils/diff.ts +71 -71
  174. package/src/utils/project.ts +99 -99
  175. package/src/utils/stableStringify.ts +47 -0
  176. package/src/utils/thinking.ts +119 -0
  177. package/tests/cache/fingerprint.test.ts +75 -0
  178. package/tests/cache/stableStringify.test.ts +43 -0
  179. package/tests/cache/stats.test.ts +146 -0
  180. package/tests/cache/volatile.test.ts +75 -0
  181. package/tests/smoke-pollution.mjs +78 -0
  182. package/tests/smoke-thinking.mjs +110 -0
  183. package/tests/smoke-tool-stream.mjs +69 -0
  184. package/tests/smoke-tool.mjs +117 -0
package/dist/app/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * App - Core application singleton
3
3
  * Simplified: directly uses LLMService (which has built-in agent loop)
4
+ *
5
+ * v3.0.0+: streamResponse yields structured StreamChunk
6
+ * - runPrompt streams { type: 'text' } chunks to stdout and captures the
7
+ * final usage for cache stats recording.
4
8
  */
5
9
  import { ConfigManager } from '../config/index.js';
6
10
  import { LLMService } from '../llm/index.js';
@@ -9,6 +13,8 @@ import { ToolRegistry } from '../tools/index.js';
9
13
  import { HookManager } from '../hooks/index.js';
10
14
  import { SystemPromptBuilder } from '../prompts/index.js';
11
15
  import { SkillRegistry } from '../skills/index.js';
16
+ import { CacheStatsStore } from '../cache/stats.js';
17
+ import { compressThinking } from '../utils/thinking.js';
12
18
  export class App {
13
19
  config;
14
20
  llm;
@@ -17,7 +23,19 @@ export class App {
17
23
  hooks;
18
24
  prompts;
19
25
  skills;
20
- constructor(config, llm, session, tools, hooks, prompts, skills) {
26
+ /**
27
+ * v3.0.0: persistent cache stats store. The single source of truth for
28
+ * cache hit-rate and estimated savings, surfaced through the TUI Header
29
+ * and the /cache command.
30
+ */
31
+ cacheStats;
32
+ /**
33
+ * v2.2.5 (product 0.4.2): toggle block compression. Default
34
+ * true. Toggled by `--show-thinking` on the CLI or `/thinking on|off`
35
+ * in the REPL.
36
+ */
37
+ showThinking = false;
38
+ constructor(config, llm, session, tools, hooks, prompts, skills, cacheStats) {
21
39
  this.config = config;
22
40
  this.llm = llm;
23
41
  this.session = session;
@@ -25,12 +43,21 @@ export class App {
25
43
  this.hooks = hooks;
26
44
  this.prompts = prompts;
27
45
  this.skills = skills;
46
+ this.cacheStats = cacheStats;
28
47
  }
29
48
  static async create() {
30
49
  const config = await ConfigManager.load();
31
50
  const aiConfig = config.getAIConfig();
32
51
  const llm = LLMService.fromConfig(aiConfig);
52
+ const cacheStats = new CacheStatsStore();
33
53
  const session = new SessionManager(config.get().contextLength || 50);
54
+ // v3.0.0: do not mutate messages when context grows — instead, surface
55
+ // a "consider /new" toast via onSuggestNewSession. The TUI wires this
56
+ // up in app.tsx; in CLI single-prompt mode it's a no-op (one-shot).
57
+ session.onSuggestNewSession = (info) => {
58
+ console.warn(`\n ⚠️ 上下文较长(${info.currentLength}/${info.max})。` +
59
+ `建议调 /new 开新会话(NWT 已自动归档历史)\n`);
60
+ };
34
61
  const tools = new ToolRegistry();
35
62
  const hooks = new HookManager();
36
63
  const skills = new SkillRegistry();
@@ -49,39 +76,87 @@ export class App {
49
76
  skillsPrompt: skills.getActivePrompts(),
50
77
  });
51
78
  session.addMessage('system', prompts.build());
52
- return new App(config, llm, session, tools, hooks, prompts, skills);
79
+ return new App(config, llm, session, tools, hooks, prompts, skills, cacheStats);
53
80
  }
54
81
  /**
55
82
  * Stream a response for the current session messages.
56
83
  * The LLMService handles the full agent loop internally.
84
+ *
85
+ * Yields structured StreamChunks. Returns the final ChatResponse (with usage
86
+ * if the provider reported it) so the caller can record cache stats.
87
+ *
88
+ * Implementation note: we drain the inner stream manually so the final
89
+ * ChatResponse returned by LLMService.chatStream is propagated as this
90
+ * generator's return value. Using yield* doesn't carry the return value
91
+ * through TS's AsyncGenerator<T, R> type inference in this version of
92
+ * TypeScript, so we wrap with an inner for-await and explicit return.
57
93
  */
58
94
  async *streamResponse(messages) {
59
95
  const msgs = messages || this.session.getMessages();
60
- for await (const chunk of this.llm.chatStream(msgs)) {
61
- yield chunk;
96
+ const inner = this.llm.chatStream(msgs);
97
+ let next = await inner.next();
98
+ while (!next.done) {
99
+ // Forward chunks unchanged, but capture usage into the persistent
100
+ // cache stats store so the TUI Header / /cache command can read it.
101
+ if (next.value && next.value.type === 'usage') {
102
+ try {
103
+ this.cacheStats.record(next.value.usage);
104
+ }
105
+ catch { /* best-effort */ }
106
+ }
107
+ yield next.value;
108
+ next = await inner.next();
62
109
  }
110
+ // The generator's return value (ChatResponse with usage) is propagated
111
+ // to callers via `for await ... await streamResponse.next()` semantics.
112
+ return next.value;
63
113
  }
64
114
  /**
65
115
  * Run a single prompt (non-interactive mode)
116
+ *
117
+ * v2.2.4 (port from v2.1.0): persistence of the assistant message
118
+ * uses addMessageSafe, which drops the message if it contains
119
+ * pollution markers like "[已中断]".
120
+ *
121
+ * v2.2.5 (product 0.4.2): persistence also strips blocks
122
+ * (and similar reasoning delimiters) when showThinking is false,
123
+ * so the conversation log stays compact.
124
+ *
125
+ * v3.0.0: yields structured StreamChunks; final usage is captured
126
+ * into onUsage callback for cache stats persistence.
66
127
  */
67
- async runPrompt(prompt) {
128
+ async runPrompt(prompt, onUsage) {
68
129
  this.session.addMessage('user', prompt);
69
130
  let fullResponse = '';
131
+ let finalUsage;
70
132
  try {
71
133
  for await (const chunk of this.streamResponse()) {
72
- process.stdout.write(chunk);
73
- fullResponse += chunk;
134
+ if (chunk.type === 'text' && chunk.content) {
135
+ process.stdout.write(chunk.content);
136
+ fullResponse += chunk.content;
137
+ }
138
+ else if (chunk.type === 'usage') {
139
+ finalUsage = chunk.usage;
140
+ }
74
141
  }
75
142
  }
76
143
  catch (err) {
77
- // Re-throw after saving partial response
78
- if (fullResponse) {
79
- this.session.addMessage('assistant', fullResponse);
80
- }
144
+ // Re-throw without persisting partial response. Persisting
145
+ // truncated output here was the source of the [已中断] loop in
146
+ // v2.2.3.
81
147
  throw err;
82
148
  }
83
149
  console.log();
84
- this.session.addMessage('assistant', fullResponse);
150
+ // v2.2.5: compress blocks before persisting.
151
+ const toPersist = compressThinking(fullResponse, this.showThinking);
152
+ this.session.addMessageSafe('assistant', toPersist);
153
+ // v3.0.0: forward usage to caller (CLI single-shot mode records stats too)
154
+ if (finalUsage && onUsage) {
155
+ try {
156
+ onUsage(finalUsage);
157
+ }
158
+ catch { /* best-effort */ }
159
+ }
85
160
  return fullResponse;
86
161
  }
87
162
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Cache fingerprints — small, stable hashes used to detect when the
3
+ * "cacheable prefix" of an LLM request changes between rounds.
4
+ *
5
+ * Why this exists:
6
+ * Anthropic prompt cache and DeepSeek automatic prefix cache both break
7
+ * when the request body changes upstream of the cache breakpoint. To
8
+ * debug "why is my hit-rate 0%", we want a quick fingerprint of the
9
+ * things that *should* be cacheable (tools + system prefix) so we can
10
+ * log it on every request and compare across rounds.
11
+ *
12
+ * The fingerprint itself is NOT used to decide anything (the upstream
13
+ * provider does the actual cache lookup). It's purely a debug /
14
+ * observability tool — same role as `reasoning_content` in the message
15
+ * schema: captured for transparency, not for control flow.
16
+ *
17
+ * Implementation:
18
+ * - Uses stableStringify (sibling module) so logically equal objects
19
+ * always produce the same JSON.
20
+ * - Hashes with sha256 (Node built-in `crypto`), truncated to 16 chars.
21
+ * 16 hex chars = 64 bits, which is plenty for collision-resistance
22
+ * across a single user's session.
23
+ */
24
+ import type { Tool } from '../tools/types.js';
25
+ import type { SystemSegment } from '../prompts/index.js';
26
+ /**
27
+ * Hash a stable JSON representation of `value` and return a 16-char hex prefix.
28
+ * Recursively normalizes object keys (sorted) so insertion order does not
29
+ * affect the output.
30
+ */
31
+ export declare function fingerprint(value: unknown): string;
32
+ /**
33
+ * Compute a fingerprint of the registered tool schemas.
34
+ *
35
+ * We deliberately pick only `name`, `description`, and `inputSchema` — the
36
+ * three fields that matter for the upstream prompt cache. Other Tool
37
+ * metadata (version strings, descriptions of internal handlers, etc) is
38
+ * ignored because it never reaches the wire.
39
+ */
40
+ export declare function fingerprintTools(tools: Tool[]): string;
41
+ /**
42
+ * Compute a fingerprint of the system prompt's *immutable* portion.
43
+ *
44
+ * Volatile segments (NWT history, current time) are deliberately excluded
45
+ * because they change between rounds and would defeat the fingerprint's
46
+ * purpose as a "did the cacheable prefix change?" signal.
47
+ */
48
+ export declare function fingerprintSystemPrefix(segments: SystemSegment[]): string;
49
+ //# sourceMappingURL=fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/cache/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAUlD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAOtD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,CAGzE"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Cache fingerprints — small, stable hashes used to detect when the
3
+ * "cacheable prefix" of an LLM request changes between rounds.
4
+ *
5
+ * Why this exists:
6
+ * Anthropic prompt cache and DeepSeek automatic prefix cache both break
7
+ * when the request body changes upstream of the cache breakpoint. To
8
+ * debug "why is my hit-rate 0%", we want a quick fingerprint of the
9
+ * things that *should* be cacheable (tools + system prefix) so we can
10
+ * log it on every request and compare across rounds.
11
+ *
12
+ * The fingerprint itself is NOT used to decide anything (the upstream
13
+ * provider does the actual cache lookup). It's purely a debug /
14
+ * observability tool — same role as `reasoning_content` in the message
15
+ * schema: captured for transparency, not for control flow.
16
+ *
17
+ * Implementation:
18
+ * - Uses stableStringify (sibling module) so logically equal objects
19
+ * always produce the same JSON.
20
+ * - Hashes with sha256 (Node built-in `crypto`), truncated to 16 chars.
21
+ * 16 hex chars = 64 bits, which is plenty for collision-resistance
22
+ * across a single user's session.
23
+ */
24
+ import { createHash } from 'crypto';
25
+ /**
26
+ * Hash a stable JSON representation of `value` and return a 16-char hex prefix.
27
+ * Recursively normalizes object keys (sorted) so insertion order does not
28
+ * affect the output.
29
+ */
30
+ export function fingerprint(value) {
31
+ // stableStringify is imported lazily to avoid a circular dep — fingerprint
32
+ // is consumed by both providers (which sit above utils/) and the cache
33
+ // stats store (which sits next to fingerprint). Top-level import works
34
+ // because utils has no other consumers besides fingerprint, so we just
35
+ // import statically.
36
+ return createHash('sha256')
37
+ .update(stableStringifyLocal(value))
38
+ .digest('hex')
39
+ .slice(0, 16);
40
+ }
41
+ /**
42
+ * Compute a fingerprint of the registered tool schemas.
43
+ *
44
+ * We deliberately pick only `name`, `description`, and `inputSchema` — the
45
+ * three fields that matter for the upstream prompt cache. Other Tool
46
+ * metadata (version strings, descriptions of internal handlers, etc) is
47
+ * ignored because it never reaches the wire.
48
+ */
49
+ export function fingerprintTools(tools) {
50
+ const minimal = tools.map(t => ({
51
+ name: t.name,
52
+ description: t.description,
53
+ inputSchema: t.inputSchema,
54
+ }));
55
+ return fingerprint({ tools: minimal });
56
+ }
57
+ /**
58
+ * Compute a fingerprint of the system prompt's *immutable* portion.
59
+ *
60
+ * Volatile segments (NWT history, current time) are deliberately excluded
61
+ * because they change between rounds and would defeat the fingerprint's
62
+ * purpose as a "did the cacheable prefix change?" signal.
63
+ */
64
+ export function fingerprintSystemPrefix(segments) {
65
+ const immutable = segments.filter(s => !s.volatile);
66
+ return fingerprint({ system: immutable.map(s => ({ name: s.name, content: s.content })) });
67
+ }
68
+ // -- Local copy of stableStringify to avoid a circular dep. This must stay
69
+ // byte-for-byte identical to src/utils/stableStringify.ts. If you change
70
+ // one, change both.
71
+ function stableStringifyLocal(value) {
72
+ if (value === null)
73
+ return 'null';
74
+ if (value === undefined)
75
+ return 'null';
76
+ const t = typeof value;
77
+ if (t === 'string')
78
+ return JSON.stringify(value);
79
+ if (t === 'number')
80
+ return Number.isFinite(value) ? String(value) : 'null';
81
+ if (t === 'boolean')
82
+ return value ? 'true' : 'false';
83
+ if (Array.isArray(value)) {
84
+ return '[' + value.map((v) => stableStringifyLocal(v)).join(',') + ']';
85
+ }
86
+ if (t === 'object') {
87
+ const obj = value;
88
+ const keys = Object.keys(obj).sort();
89
+ const parts = [];
90
+ for (const k of keys) {
91
+ const v = obj[k];
92
+ if (v === undefined)
93
+ continue;
94
+ parts.push(JSON.stringify(k) + ':' + stableStringifyLocal(v));
95
+ }
96
+ return '{' + parts.join(',') + '}';
97
+ }
98
+ return 'null';
99
+ }
100
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/cache/fingerprint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cache subsystem barrel. Re-exports the public surface used by the TUI,
3
+ * the App singleton, and the LLM service.
4
+ *
5
+ * Modules:
6
+ * - fingerprint.ts: stable hashes for tools + immutable system prefix
7
+ * - stats.ts: CacheStatsStore — per-round hit/miss + cost savings
8
+ * - volatile.ts: VolatileScratch — per-round scratch that never reaches the API
9
+ * - smartModel.ts: smart-model routing hook (simple → mini, complex → main)
10
+ */
11
+ export { fingerprint, fingerprintTools, fingerprintSystemPrefix } from './fingerprint.js';
12
+ export { CacheStatsStore, estimateSavingsCNY, type CacheStats, type CacheSnapshot } from './stats.js';
13
+ export { VolatileScratch } from './volatile.js';
14
+ export { shouldDowngrade, type SmartModelDecision } from './smartModel.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cache subsystem barrel. Re-exports the public surface used by the TUI,
3
+ * the App singleton, and the LLM service.
4
+ *
5
+ * Modules:
6
+ * - fingerprint.ts: stable hashes for tools + immutable system prefix
7
+ * - stats.ts: CacheStatsStore — per-round hit/miss + cost savings
8
+ * - volatile.ts: VolatileScratch — per-round scratch that never reaches the API
9
+ * - smartModel.ts: smart-model routing hook (simple → mini, complex → main)
10
+ */
11
+ export { fingerprint, fingerprintTools, fingerprintSystemPrefix } from './fingerprint.js';
12
+ export { CacheStatsStore, estimateSavingsCNY } from './stats.js';
13
+ export { VolatileScratch } from './volatile.js';
14
+ export { shouldDowngrade } from './smartModel.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAuC,MAAM,YAAY,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,eAAe,EAA2B,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Smart-model routing (P3 of the Reasonix plan).
3
+ *
4
+ * Idea: classify the user's input as "simple" or "complex" and route
5
+ * simple queries to a cheaper / faster model while keeping the main model
6
+ * for complex work. The classification is intentionally trivial — prompt
7
+ * length + presence of recent tool calls — because the cost of a
8
+ * misclassification (a slightly worse answer on a simple query) is much
9
+ * lower than the cost of running a frontier model on every greeting.
10
+ *
11
+ * Trade-off: each routing decision adds one model swap (different
12
+ * `model` field on the wire). If the cache prefix is keyed on the model
13
+ * string, the cache miss rate can spike on every simple query. To keep
14
+ * the prefix stable, callers should pass a *family* (e.g. "anthropic" or
15
+ * "deepseek") rather than a specific model name when forwarding the
16
+ * prompt to the downstream provider — that's outside the scope of this
17
+ * hook; this module only answers the question "should I downgrade?".
18
+ *
19
+ * v3.0.0: shipped as a stub. Real decision logic is conservative
20
+ * (downgrade only when prompt is short AND no recent tool activity).
21
+ * Future iterations can add heuristic or classifier-based routing.
22
+ */
23
+ import type { ChatMessage } from '../types.js';
24
+ export interface SmartModelDecision {
25
+ /** Should we route this turn to a cheaper model? */
26
+ downgrade: boolean;
27
+ /** Why we made this call — useful for `/cache` debug output. */
28
+ reason: string;
29
+ }
30
+ /**
31
+ * Heuristic: downgrade when
32
+ * 1. The latest user message is short (< 200 chars), AND
33
+ * 2. The last 3 messages contain no assistant tool_calls.
34
+ *
35
+ * This covers greetings, one-line questions, and simple follow-ups
36
+ * ("thanks", "ok", "explain this one line") while routing anything
37
+ * that triggered a tool to the main model.
38
+ */
39
+ export declare function shouldDowngrade(messages: ChatMessage[], lastUserInput: string): SmartModelDecision;
40
+ //# sourceMappingURL=smartModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smartModel.d.ts","sourceRoot":"","sources":["../../src/cache/smartModel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;IACnB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,kBAAkB,CAUlG"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Smart-model routing (P3 of the Reasonix plan).
3
+ *
4
+ * Idea: classify the user's input as "simple" or "complex" and route
5
+ * simple queries to a cheaper / faster model while keeping the main model
6
+ * for complex work. The classification is intentionally trivial — prompt
7
+ * length + presence of recent tool calls — because the cost of a
8
+ * misclassification (a slightly worse answer on a simple query) is much
9
+ * lower than the cost of running a frontier model on every greeting.
10
+ *
11
+ * Trade-off: each routing decision adds one model swap (different
12
+ * `model` field on the wire). If the cache prefix is keyed on the model
13
+ * string, the cache miss rate can spike on every simple query. To keep
14
+ * the prefix stable, callers should pass a *family* (e.g. "anthropic" or
15
+ * "deepseek") rather than a specific model name when forwarding the
16
+ * prompt to the downstream provider — that's outside the scope of this
17
+ * hook; this module only answers the question "should I downgrade?".
18
+ *
19
+ * v3.0.0: shipped as a stub. Real decision logic is conservative
20
+ * (downgrade only when prompt is short AND no recent tool activity).
21
+ * Future iterations can add heuristic or classifier-based routing.
22
+ */
23
+ /**
24
+ * Heuristic: downgrade when
25
+ * 1. The latest user message is short (< 200 chars), AND
26
+ * 2. The last 3 messages contain no assistant tool_calls.
27
+ *
28
+ * This covers greetings, one-line questions, and simple follow-ups
29
+ * ("thanks", "ok", "explain this one line") while routing anything
30
+ * that triggered a tool to the main model.
31
+ */
32
+ export function shouldDowngrade(messages, lastUserInput) {
33
+ if (lastUserInput.length > 200) {
34
+ return { downgrade: false, reason: 'prompt-too-long' };
35
+ }
36
+ const recent = messages.slice(-3);
37
+ const hasRecentToolCalls = recent.some(m => m.tool_calls && m.tool_calls.length > 0);
38
+ if (hasRecentToolCalls) {
39
+ return { downgrade: false, reason: 'recent-tool-call' };
40
+ }
41
+ return { downgrade: true, reason: 'short-no-tools' };
42
+ }
43
+ //# sourceMappingURL=smartModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smartModel.js","sourceRoot":"","sources":["../../src/cache/smartModel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAWH;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,QAAuB,EAAE,aAAqB;IAC5E,IAAI,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QAC/B,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrF,IAAI,kBAAkB,EAAE,CAAC;QACvB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Cache statistics store.
3
+ *
4
+ * Tracks per-round cache hit/miss token counts across the user's local
5
+ * sessions and persists them to `~/.thatgfsj/cache-stats.json`. The store
6
+ * is consumed by:
7
+ * - the TUI Header (`⚡ 命中率 87%`)
8
+ * - the /cache CLI command (detailed breakdown)
9
+ * - the /cache reset command (clears the file)
10
+ *
11
+ * Provider-agnostic. Anthropic reports cache_creation_input_tokens /
12
+ * cache_read_input_tokens; DeepSeek reports prompt_cache_hit_tokens /
13
+ * prompt_cache_miss_tokens; OpenAI does not surface any cache stats at
14
+ * all (their automatic cache is invisible to the client). We normalize
15
+ * both shapes into a single internal model.
16
+ *
17
+ * Cost estimation is intentionally simplified:
18
+ * - cache_read = 10% of normal input price
19
+ * - cache_creation = 125% of normal input price (Anthropic charges extra
20
+ * on first write)
21
+ * - We compare against the baseline "what would this have cost without
22
+ * caching" and report the delta as estimated savings.
23
+ *
24
+ * Prices are in CNY per million tokens. Defaults reflect Anthropic Claude
25
+ * Sonnet on the official API; users on cheaper providers may see savings
26
+ * proportional to their actual price floor. The numbers are *estimates*
27
+ * — the goal is to give the user a sense of order of magnitude, not a
28
+ * billable invoice.
29
+ */
30
+ import type { Usage } from '../types.js';
31
+ export interface CacheStats {
32
+ /** Total tokens served from cache (Anthropic cache_read / DeepSeek hit). */
33
+ totalReadTokens: number;
34
+ /** Total tokens written into cache (Anthropic cache_creation / DeepSeek miss prefix written). */
35
+ totalCreationTokens: number;
36
+ /** Total input tokens (prompt_tokens). */
37
+ totalInputTokens: number;
38
+ /** Total output tokens. */
39
+ totalOutputTokens: number;
40
+ /** Number of completed rounds. */
41
+ totalRequests: number;
42
+ /** Last N round snapshots (oldest first). */
43
+ history: Array<{
44
+ ts: number;
45
+ read: number;
46
+ creation: number;
47
+ input: number;
48
+ output: number;
49
+ hitRate: number;
50
+ }>;
51
+ }
52
+ export interface CacheSnapshot extends CacheStats {
53
+ /** 0..1 ratio of cached tokens to total input tokens. */
54
+ hitRate: number;
55
+ /** Estimated CNY saved vs uncached baseline. */
56
+ estimatedSavingsCNY: number;
57
+ }
58
+ /**
59
+ * Estimate the CNY saved by cache_read vs a hypothetical uncached baseline.
60
+ * Negative values are clamped to zero (e.g. a relay station that returned
61
+ * bogus cache stats).
62
+ */
63
+ export declare function estimateSavingsCNY(stats: CacheStats): number;
64
+ /**
65
+ * Persists cache stats to disk. Safe to call from a hot path — uses atomic
66
+ * write (write to .tmp, rename) to avoid corruption on crash.
67
+ */
68
+ export declare class CacheStatsStore {
69
+ private stats;
70
+ private path;
71
+ constructor(path?: string);
72
+ /**
73
+ * Record a single round's usage. Provider-agnostic: accepts both Anthropic
74
+ * (cache_creation_input_tokens / cache_read_input_tokens) and DeepSeek
75
+ * (prompt_cache_hit_tokens / prompt_cache_miss_tokens) shapes. If a
76
+ * provider returned neither (e.g. OpenAI), the call is a no-op apart
77
+ * from updating totalRequest count.
78
+ */
79
+ record(usage: Usage): void;
80
+ /**
81
+ * Reset the stats. Used by `/cache reset`.
82
+ */
83
+ reset(): void;
84
+ /**
85
+ * Read-only snapshot for rendering (TUI Header, /cache command).
86
+ */
87
+ snapshot(): CacheSnapshot;
88
+ /** Direct accessor for tests. */
89
+ get raw(): CacheStats;
90
+ private load;
91
+ private save;
92
+ }
93
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/cache/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAazC,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,eAAe,EAAE,MAAM,CAAC;IACxB,iGAAiG;IACjG,mBAAmB,EAAE,MAAM,CAAC;IAC5B,0CAA0C;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,OAAO,EAAE,KAAK,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAWD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAI5D;AAED;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,IAAI,CAAS;gBAET,IAAI,CAAC,EAAE,MAAM;IAKzB;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IA6B1B;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,QAAQ,IAAI,aAAa;IAWzB,iCAAiC;IACjC,IAAI,GAAG,IAAI,UAAU,CAAuB;IAI5C,OAAO,CAAC,IAAI;IAmBZ,OAAO,CAAC,IAAI;CAQb"}