@ai-zen/cli 1.0.0-alpha.20 → 1.0.0-alpha.22

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 (249) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.md +43 -17
  3. package/README.zh.md +43 -17
  4. package/dist/agent-creator.d.ts.map +1 -1
  5. package/dist/agent-creator.js +32 -10
  6. package/dist/agent-creator.js.map +1 -1
  7. package/dist/agent-switch.d.ts +61 -0
  8. package/dist/agent-switch.d.ts.map +1 -0
  9. package/dist/agent-switch.js +83 -0
  10. package/dist/agent-switch.js.map +1 -0
  11. package/dist/agents-store.d.ts +6 -1
  12. package/dist/agents-store.d.ts.map +1 -1
  13. package/dist/agents-store.js +6 -1
  14. package/dist/agents-store.js.map +1 -1
  15. package/dist/auto-migrate-confirm.d.ts.map +1 -1
  16. package/dist/auto-migrate-confirm.js +2 -1
  17. package/dist/auto-migrate-confirm.js.map +1 -1
  18. package/dist/config-editor.d.ts +9 -1
  19. package/dist/config-editor.d.ts.map +1 -1
  20. package/dist/config-editor.js +30 -19
  21. package/dist/config-editor.js.map +1 -1
  22. package/dist/config.d.ts +1 -1
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/config.js +29 -13
  25. package/dist/config.js.map +1 -1
  26. package/dist/conversation-commands/registry.d.ts +123 -4
  27. package/dist/conversation-commands/registry.d.ts.map +1 -1
  28. package/dist/conversation-commands/registry.js +92 -17
  29. package/dist/conversation-commands/registry.js.map +1 -1
  30. package/dist/conversation-persist-plugin.d.ts.map +1 -1
  31. package/dist/conversation-persist-plugin.js +6 -14
  32. package/dist/conversation-persist-plugin.js.map +1 -1
  33. package/dist/conversation-repository.d.ts +68 -14
  34. package/dist/conversation-repository.d.ts.map +1 -1
  35. package/dist/conversation-repository.js +81 -23
  36. package/dist/conversation-repository.js.map +1 -1
  37. package/dist/cwd-tracker-plugin.d.ts.map +1 -1
  38. package/dist/cwd-tracker-plugin.js +6 -2
  39. package/dist/cwd-tracker-plugin.js.map +1 -1
  40. package/dist/format-time.d.ts +17 -6
  41. package/dist/format-time.d.ts.map +1 -1
  42. package/dist/format-time.js +32 -14
  43. package/dist/format-time.js.map +1 -1
  44. package/dist/handoff-service.d.ts.map +1 -1
  45. package/dist/handoff-service.js +22 -19
  46. package/dist/handoff-service.js.map +1 -1
  47. package/dist/hook.d.ts.map +1 -1
  48. package/dist/hook.js +69 -15
  49. package/dist/hook.js.map +1 -1
  50. package/dist/i18n/en-wizard.d.ts +8 -0
  51. package/dist/i18n/en-wizard.d.ts.map +1 -0
  52. package/dist/i18n/en-wizard.js +290 -0
  53. package/dist/i18n/en-wizard.js.map +1 -0
  54. package/dist/i18n/en.d.ts +7 -0
  55. package/dist/i18n/en.d.ts.map +1 -0
  56. package/dist/i18n/en.js +224 -0
  57. package/dist/i18n/en.js.map +1 -0
  58. package/dist/i18n/index.d.ts +57 -0
  59. package/dist/i18n/index.d.ts.map +1 -0
  60. package/dist/i18n/index.js +109 -0
  61. package/dist/i18n/index.js.map +1 -0
  62. package/dist/i18n/preferences.d.ts +34 -0
  63. package/dist/i18n/preferences.d.ts.map +1 -0
  64. package/dist/i18n/preferences.js +69 -0
  65. package/dist/i18n/preferences.js.map +1 -0
  66. package/dist/i18n/zh-wizard.d.ts +11 -0
  67. package/dist/i18n/zh-wizard.d.ts.map +1 -0
  68. package/dist/i18n/zh-wizard.js +293 -0
  69. package/dist/i18n/zh-wizard.js.map +1 -0
  70. package/dist/i18n/zh.d.ts +10 -0
  71. package/dist/i18n/zh.d.ts.map +1 -0
  72. package/dist/i18n/zh.js +228 -0
  73. package/dist/i18n/zh.js.map +1 -0
  74. package/dist/index.js +41 -23
  75. package/dist/index.js.map +1 -1
  76. package/dist/mcp-command.d.ts +32 -0
  77. package/dist/mcp-command.d.ts.map +1 -0
  78. package/dist/mcp-command.js +37 -0
  79. package/dist/mcp-command.js.map +1 -0
  80. package/dist/mode.d.ts +31 -4
  81. package/dist/mode.d.ts.map +1 -1
  82. package/dist/mode.js +42 -7
  83. package/dist/mode.js.map +1 -1
  84. package/dist/model-switch.d.ts.map +1 -1
  85. package/dist/model-switch.js +5 -4
  86. package/dist/model-switch.js.map +1 -1
  87. package/dist/reasoning-effort.d.ts +23 -4
  88. package/dist/reasoning-effort.d.ts.map +1 -1
  89. package/dist/reasoning-effort.js +51 -24
  90. package/dist/reasoning-effort.js.map +1 -1
  91. package/dist/sdk-log.d.ts +23 -16
  92. package/dist/sdk-log.d.ts.map +1 -1
  93. package/dist/sdk-log.js +43 -19
  94. package/dist/sdk-log.js.map +1 -1
  95. package/dist/stdio-runner.d.ts.map +1 -1
  96. package/dist/stdio-runner.js +19 -19
  97. package/dist/stdio-runner.js.map +1 -1
  98. package/dist/tui/chat-session.d.ts +27 -0
  99. package/dist/tui/chat-session.d.ts.map +1 -1
  100. package/dist/tui/chat-session.js +120 -28
  101. package/dist/tui/chat-session.js.map +1 -1
  102. package/dist/tui/components.d.ts +43 -2
  103. package/dist/tui/components.d.ts.map +1 -1
  104. package/dist/tui/components.js +97 -4
  105. package/dist/tui/components.js.map +1 -1
  106. package/dist/tui/config-wizard/detail.d.ts +12 -6
  107. package/dist/tui/config-wizard/detail.d.ts.map +1 -1
  108. package/dist/tui/config-wizard/detail.js +33 -32
  109. package/dist/tui/config-wizard/detail.js.map +1 -1
  110. package/dist/tui/config-wizard/engine.d.ts.map +1 -1
  111. package/dist/tui/config-wizard/engine.js +26 -20
  112. package/dist/tui/config-wizard/engine.js.map +1 -1
  113. package/dist/tui/config-wizard/fields.d.ts.map +1 -1
  114. package/dist/tui/config-wizard/fields.js +176 -164
  115. package/dist/tui/config-wizard/fields.js.map +1 -1
  116. package/dist/tui/config-wizard/kv-editor.d.ts.map +1 -1
  117. package/dist/tui/config-wizard/kv-editor.js +9 -5
  118. package/dist/tui/config-wizard/kv-editor.js.map +1 -1
  119. package/dist/tui/config-wizard/list.d.ts.map +1 -1
  120. package/dist/tui/config-wizard/list.js +2 -1
  121. package/dist/tui/config-wizard/list.js.map +1 -1
  122. package/dist/tui/config-wizard/mcp-helpers.d.ts.map +1 -1
  123. package/dist/tui/config-wizard/mcp-helpers.js +4 -3
  124. package/dist/tui/config-wizard/mcp-helpers.js.map +1 -1
  125. package/dist/tui/config-wizard/menu.d.ts.map +1 -1
  126. package/dist/tui/config-wizard/menu.js +32 -19
  127. package/dist/tui/config-wizard/menu.js.map +1 -1
  128. package/dist/tui/config-wizard/prompt-editor.d.ts.map +1 -1
  129. package/dist/tui/config-wizard/prompt-editor.js +4 -3
  130. package/dist/tui/config-wizard/prompt-editor.js.map +1 -1
  131. package/dist/tui/config-wizard/prompt.d.ts.map +1 -1
  132. package/dist/tui/config-wizard/prompt.js +8 -7
  133. package/dist/tui/config-wizard/prompt.js.map +1 -1
  134. package/dist/tui/config-wizard/step-agents.d.ts.map +1 -1
  135. package/dist/tui/config-wizard/step-agents.js +29 -20
  136. package/dist/tui/config-wizard/step-agents.js.map +1 -1
  137. package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
  138. package/dist/tui/config-wizard/step-defaults.js +21 -17
  139. package/dist/tui/config-wizard/step-defaults.js.map +1 -1
  140. package/dist/tui/config-wizard/step-endpoints.d.ts.map +1 -1
  141. package/dist/tui/config-wizard/step-endpoints.js +16 -6
  142. package/dist/tui/config-wizard/step-endpoints.js.map +1 -1
  143. package/dist/tui/config-wizard/step-mcp.d.ts.map +1 -1
  144. package/dist/tui/config-wizard/step-mcp.js +17 -19
  145. package/dist/tui/config-wizard/step-mcp.js.map +1 -1
  146. package/dist/tui/config-wizard/step-models.d.ts.map +1 -1
  147. package/dist/tui/config-wizard/step-models.js +41 -26
  148. package/dist/tui/config-wizard/step-models.js.map +1 -1
  149. package/dist/tui/config-wizard/step-new-endpoint.d.ts.map +1 -1
  150. package/dist/tui/config-wizard/step-new-endpoint.js +16 -12
  151. package/dist/tui/config-wizard/step-new-endpoint.js.map +1 -1
  152. package/dist/tui/config-wizard/step-simple.d.ts.map +1 -1
  153. package/dist/tui/config-wizard/step-simple.js +18 -17
  154. package/dist/tui/config-wizard/step-simple.js.map +1 -1
  155. package/dist/tui/index.d.ts +17 -1
  156. package/dist/tui/index.d.ts.map +1 -1
  157. package/dist/tui/index.js +25 -14
  158. package/dist/tui/index.js.map +1 -1
  159. package/dist/tui/input-editing.d.ts +4 -1
  160. package/dist/tui/input-editing.d.ts.map +1 -1
  161. package/dist/tui/input-editing.js +7 -3
  162. package/dist/tui/input-editing.js.map +1 -1
  163. package/dist/tui/phase.d.ts +64 -0
  164. package/dist/tui/phase.d.ts.map +1 -0
  165. package/dist/tui/phase.js +112 -0
  166. package/dist/tui/phase.js.map +1 -0
  167. package/dist/tui/screens/chat-commands.d.ts +54 -3
  168. package/dist/tui/screens/chat-commands.d.ts.map +1 -1
  169. package/dist/tui/screens/chat-commands.js +469 -330
  170. package/dist/tui/screens/chat-commands.js.map +1 -1
  171. package/dist/tui/screens/chat.d.ts +8 -0
  172. package/dist/tui/screens/chat.d.ts.map +1 -1
  173. package/dist/tui/screens/chat.js +206 -405
  174. package/dist/tui/screens/chat.js.map +1 -1
  175. package/dist/tui/screens/common-keys.d.ts +36 -0
  176. package/dist/tui/screens/common-keys.d.ts.map +1 -0
  177. package/dist/tui/screens/common-keys.js +67 -0
  178. package/dist/tui/screens/common-keys.js.map +1 -0
  179. package/dist/tui/screens/handoff-choice.d.ts.map +1 -1
  180. package/dist/tui/screens/handoff-choice.js +4 -3
  181. package/dist/tui/screens/handoff-choice.js.map +1 -1
  182. package/dist/tui/screens/index.d.ts +1 -1
  183. package/dist/tui/screens/index.d.ts.map +1 -1
  184. package/dist/tui/screens/index.js +1 -1
  185. package/dist/tui/screens/index.js.map +1 -1
  186. package/dist/tui/screens/input-row.d.ts +64 -0
  187. package/dist/tui/screens/input-row.d.ts.map +1 -0
  188. package/dist/tui/screens/input-row.js +67 -0
  189. package/dist/tui/screens/input-row.js.map +1 -0
  190. package/dist/tui/screens/input.d.ts +11 -1
  191. package/dist/tui/screens/input.d.ts.map +1 -1
  192. package/dist/tui/screens/input.js +21 -12
  193. package/dist/tui/screens/input.js.map +1 -1
  194. package/dist/tui/screens/layout.d.ts +8 -0
  195. package/dist/tui/screens/layout.d.ts.map +1 -1
  196. package/dist/tui/screens/layout.js +6 -2
  197. package/dist/tui/screens/layout.js.map +1 -1
  198. package/dist/tui/screens/lines.d.ts.map +1 -1
  199. package/dist/tui/screens/lines.js +6 -6
  200. package/dist/tui/screens/lines.js.map +1 -1
  201. package/dist/tui/screens/messages-view.d.ts.map +1 -1
  202. package/dist/tui/screens/messages-view.js +5 -4
  203. package/dist/tui/screens/messages-view.js.map +1 -1
  204. package/dist/tui/screens/notice-bar.d.ts +5 -0
  205. package/dist/tui/screens/notice-bar.d.ts.map +1 -0
  206. package/dist/tui/screens/notice-bar.js +15 -0
  207. package/dist/tui/screens/notice-bar.js.map +1 -0
  208. package/dist/tui/screens/notice.d.ts +40 -8
  209. package/dist/tui/screens/notice.d.ts.map +1 -1
  210. package/dist/tui/screens/notice.js +75 -15
  211. package/dist/tui/screens/notice.js.map +1 -1
  212. package/dist/tui/screens/overlays.d.ts.map +1 -1
  213. package/dist/tui/screens/overlays.js +3 -2
  214. package/dist/tui/screens/overlays.js.map +1 -1
  215. package/dist/tui/screens/splash.d.ts +1 -1
  216. package/dist/tui/screens/splash.d.ts.map +1 -1
  217. package/dist/tui/screens/splash.js +5 -2
  218. package/dist/tui/screens/splash.js.map +1 -1
  219. package/dist/tui/screens/use-chat-viewport.d.ts +22 -0
  220. package/dist/tui/screens/use-chat-viewport.d.ts.map +1 -0
  221. package/dist/tui/screens/use-chat-viewport.js +50 -0
  222. package/dist/tui/screens/use-chat-viewport.js.map +1 -0
  223. package/dist/tui/screens/use-input-editing.d.ts +44 -0
  224. package/dist/tui/screens/use-input-editing.d.ts.map +1 -0
  225. package/dist/tui/screens/use-input-editing.js +192 -0
  226. package/dist/tui/screens/use-input-editing.js.map +1 -0
  227. package/dist/tui/screens/use-overlays.d.ts +25 -0
  228. package/dist/tui/screens/use-overlays.d.ts.map +1 -0
  229. package/dist/tui/screens/use-overlays.js +32 -0
  230. package/dist/tui/screens/use-overlays.js.map +1 -0
  231. package/dist/tui/screens/use-wizard-host.d.ts +35 -0
  232. package/dist/tui/screens/use-wizard-host.d.ts.map +1 -0
  233. package/dist/tui/screens/use-wizard-host.js +103 -0
  234. package/dist/tui/screens/use-wizard-host.js.map +1 -0
  235. package/dist/tui/status-segments.d.ts +37 -0
  236. package/dist/tui/status-segments.d.ts.map +1 -0
  237. package/dist/tui/status-segments.js +166 -0
  238. package/dist/tui/status-segments.js.map +1 -0
  239. package/dist/tui/text.d.ts +8 -5
  240. package/dist/tui/text.d.ts.map +1 -1
  241. package/dist/tui/text.js +95 -35
  242. package/dist/tui/text.js.map +1 -1
  243. package/dist/tui/token-rate.d.ts +83 -0
  244. package/dist/tui/token-rate.d.ts.map +1 -0
  245. package/dist/tui/token-rate.js +195 -0
  246. package/dist/tui/token-rate.js.map +1 -0
  247. package/dist/types.d.ts +0 -3
  248. package/dist/types.d.ts.map +1 -1
  249. package/package.json +4 -3
@@ -0,0 +1,166 @@
1
+ /**
2
+ * 状态栏的折行 —— 输入是「按优先级排好的信息块」,输出是「确定的若干行」
3
+ *
4
+ * 为什么不用 Ink 自己的 wrap:状态栏高度必须是**确定值**,`computeChatLayout` 要靠它
5
+ * 算出对话区还能占几行(整帧高度恒等于终端行数,输入框才吸得住底)。让 Ink 在渲染时
6
+ * 折行,行数只有渲染完才知道,布局预算就会算错——轻则顶部留白多一行,重则把输入框
7
+ * 顶出屏幕。所以这里先在纯逻辑里折好,渲染层只负责按行铺颜色。
8
+ *
9
+ * 折行以「块」为单位(见 `packSegments`):身份、用量、快捷键各是一块,窄屏时整块
10
+ * 顺延到下一行,而不是把一块劈成两半——也不再用 `justify-content: space-between` 把
11
+ * 两侧各切一半(左边「思考 高」被吃掉、右边提示只剩半截)。
12
+ */
13
+ import { clusterWidth, graphemes } from "./text.js";
14
+ function toChars(segments) {
15
+ const chars = [];
16
+ for (const segment of segments) {
17
+ // 按**字素簇**切,不按 code point:`✌️`(VS16)是两个 code point 但占 2 列,
18
+ // 逐 code point 累加会算成 1 列 → 状态栏行超宽 → Ink 二次折行 → 帧高多一行。
19
+ for (const ch of graphemes(segment.text)) {
20
+ chars.push({ ch, color: segment.color, bold: segment.bold, width: clusterWidth(ch) });
21
+ }
22
+ }
23
+ return chars;
24
+ }
25
+ /** 连续同色同粗细的字符合并回一个片段 */
26
+ function toSegments(chars) {
27
+ const segments = [];
28
+ for (const char of chars) {
29
+ const last = segments[segments.length - 1];
30
+ if (last && last.color === char.color && last.bold === char.bold) {
31
+ last.text += char.ch;
32
+ }
33
+ else {
34
+ segments.push({ text: char.ch, color: char.color, bold: char.bold });
35
+ }
36
+ }
37
+ return segments;
38
+ }
39
+ /** 行尾空格没有意义(会视觉上「多出一截」),折行前后都裁掉 */
40
+ function trimTail(line) {
41
+ while (line.length > 0 && line[line.length - 1].ch === " ")
42
+ line.pop();
43
+ }
44
+ /**
45
+ * 把片段流按显示宽度折成若干行。
46
+ *
47
+ * 断行策略与 `wrapText` 一致:优先在**空格**处断(英文单词不被劈开),没有空格可断
48
+ * 时按字符硬断(CJK 本来就能任意断行)。折行后的行首空格会被吃掉,避免出现
49
+ * 「第二行无故缩进两格」。
50
+ */
51
+ export function wrapSegments(segments, width) {
52
+ const limit = Math.max(1, width);
53
+ const chars = toChars(segments);
54
+ const lines = [];
55
+ let line = [];
56
+ let lineWidth = 0;
57
+ for (let index = 0; index < chars.length; index += 1) {
58
+ const char = chars[index];
59
+ // 行已满且下一个正好是空格:直接断行并把这个空格丢掉,而不是往回找上一处空格
60
+ // (否则会把已经排好的「aaaa bbbb」硬拆成「aaaa」+「bbbb」,白白浪费半行)
61
+ if (char.ch === " " && lineWidth + char.width > limit && line.length > 0) {
62
+ trimTail(line);
63
+ lines.push(line);
64
+ line = [];
65
+ lineWidth = 0;
66
+ continue;
67
+ }
68
+ if (lineWidth + char.width > limit && line.length > 0) {
69
+ // 当前行最后一个空格的位置:能在此断就在此断,把尾巴挪到下一行
70
+ let spaceAt = -1;
71
+ for (let i = line.length - 1; i > 0; i -= 1) {
72
+ if (line[i].ch === " ") {
73
+ spaceAt = i;
74
+ break;
75
+ }
76
+ }
77
+ if (spaceAt > 0) {
78
+ const carry = line.slice(spaceAt + 1);
79
+ line = line.slice(0, spaceAt);
80
+ trimTail(line);
81
+ lines.push(line);
82
+ line = carry;
83
+ lineWidth = carry.reduce((sum, item) => sum + item.width, 0);
84
+ }
85
+ else {
86
+ trimTail(line);
87
+ lines.push(line);
88
+ line = [];
89
+ lineWidth = 0;
90
+ }
91
+ }
92
+ if (line.length === 0) {
93
+ if (char.ch === " ")
94
+ continue; // 行首不吃空格
95
+ // 行首的孤儿分隔符(`·`)是折行切出来的,留在开头只会变成「· ⚡ 47 tok/s」,
96
+ // 难看且无意义(换行本身已表达分隔),连同其后的空格一并丢掉。
97
+ if (char.ch === "·" && index > 0) {
98
+ while (index + 1 < chars.length && chars[index + 1].ch === " ")
99
+ index += 1;
100
+ continue;
101
+ }
102
+ }
103
+ line.push(char);
104
+ lineWidth += char.width;
105
+ }
106
+ trimTail(line);
107
+ lines.push(line);
108
+ return lines.map(toSegments);
109
+ }
110
+ /** 片段流的总显示宽度(宽字符算 2 列) */
111
+ export function segmentsWidth(segments) {
112
+ let width = 0;
113
+ for (const segment of segments) {
114
+ for (const ch of graphemes(segment.text))
115
+ width += clusterWidth(ch);
116
+ }
117
+ return width;
118
+ }
119
+ /**
120
+ * 以**块**为单位装行:能塞进当前行剩余宽度就并排(用 `separator` 连起来),
121
+ * 塞不下就整块挪到下一行;只有单块本身就比一整行还宽时,才退化成块内折行
122
+ * (`wrapSegments`),此时优先在空格 / CJK 边界断,尽量不劈开英文单词。
123
+ *
124
+ * 这样「换行时一块占一行」是默认行为:块是原子的排版单位,行的边界只会落在块边界上。
125
+ */
126
+ export function packSegments(blocks, width, separator = [{ text: " · " }]) {
127
+ const limit = Math.max(1, width);
128
+ const separatorWidth = segmentsWidth(separator);
129
+ const lines = [];
130
+ let line = [];
131
+ let lineWidth = 0;
132
+ const flush = () => {
133
+ if (line.length === 0)
134
+ return;
135
+ lines.push(line);
136
+ line = [];
137
+ lineWidth = 0;
138
+ };
139
+ for (const block of blocks) {
140
+ const blockWidth = segmentsWidth(block);
141
+ if (blockWidth === 0)
142
+ continue; // 空块(如本轮没有速率)不占位,也不留分隔符
143
+ if (line.length > 0) {
144
+ if (lineWidth + separatorWidth + blockWidth <= limit) {
145
+ line = [...line, ...separator, ...block];
146
+ lineWidth += separatorWidth + blockWidth;
147
+ continue;
148
+ }
149
+ flush();
150
+ }
151
+ if (blockWidth <= limit) {
152
+ line = [...block];
153
+ lineWidth = blockWidth;
154
+ continue;
155
+ }
156
+ // 单块超一整行:块内折行兜底,尾巴留在 `line` 上等着接下一块
157
+ const chunks = wrapSegments(block, limit);
158
+ const tail = chunks.pop();
159
+ lines.push(...chunks);
160
+ line = tail;
161
+ lineWidth = segmentsWidth(tail);
162
+ }
163
+ flush();
164
+ return lines.length > 0 ? lines : [[]];
165
+ }
166
+ //# sourceMappingURL=status-segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-segments.js","sourceRoot":"","sources":["../../src/tui/status-segments.ts"],"names":[],"mappings":"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"}
@@ -4,11 +4,14 @@
4
4
  * 从 `screens.tsx` 中抽出,供对话屏、凭据设置屏、配置中心共用,
5
5
  * 避免 `screens.tsx ↔ config-wizard.tsx` 相互 import 形成循环依赖。
6
6
  */
7
- /** 终端显示宽度(宽字符计 2,组合字符计 0) */
7
+ /** 终端显示宽度(按字素簇,与 Ink 的排版完全一致) */
8
8
  export declare function displayWidth(text: string): number;
9
+ /** 单个字素簇的显示宽度(结果带缓存) */
10
+ export declare function clusterWidth(cluster: string): number;
11
+ export declare function graphemes(text: string): string[];
9
12
  /**
10
13
  * 按显示宽度把文本折成多行(保留显式换行)。
11
- * 拉丁文优先在空格处断行,CJK / 超长词按字符硬断;保证每行显示宽度 ≤ limit。
14
+ * 拉丁文优先在空格处断行,CJK / 超长词按**字素簇**硬断;保证每行显示宽度 ≤ limit。
12
15
  */
13
16
  export declare function wrapText(text: string, limit: number): string[];
14
17
  /** 带光标位置的输入区折行结果 */
@@ -17,12 +20,12 @@ export interface InputLayout {
17
20
  lines: string[];
18
21
  /** 光标所在行(0 起始) */
19
22
  cursorRow: number;
20
- /** 光标在该行内的字符偏移(code point 计) */
23
+ /** 光标在该行内的字符偏移(**字素簇**计,不是 code point) */
21
24
  cursorCol: number;
22
25
  }
23
26
  /**
24
- * 把输入文本按显示宽度折行,并定位光标(`cursor` 为字符索引,以 code point 计)。
25
- * 折行采用逐字符硬断(不做空格优先),以保证光标映射精确、支持跨行编辑。
27
+ * 把输入文本按显示宽度折行,并定位光标(`cursor` 为**字素簇**索引)。
28
+ * 折行采用逐簇硬断(不做空格优先),以保证光标映射精确、支持跨行编辑。
26
29
  */
27
30
  export declare function layoutInput(text: string, cursor: number, width: number): InputLayout;
28
31
  /** 单行输入左侧留白:Box paddingX(1) + 提示符 `❯ `(宽 2) */
@@ -1 +1 @@
1
- {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/tui/text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH,6BAA6B;AAC7B,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA8B9D;AAID,oBAAoB;AACpB,MAAM,WAAW,WAAW;IAC1B,sBAAsB;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kBAAkB;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAgDpF;AAED,+CAA+C;AAC/C,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAY3D;AAID;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/tui/text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA0BH,iCAAiC;AACjC,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAwB;AACxB,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQpD;AAuCD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOhD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA8B9D;AAID,oBAAoB;AACpB,MAAM,WAAW,WAAW;IAC1B,sBAAsB;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kBAAkB;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAgDpF;AAED,+CAA+C;AAC/C,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAY3D;AAID;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMjD"}
package/dist/tui/text.js CHANGED
@@ -4,38 +4,94 @@
4
4
  * 从 `screens.tsx` 中抽出,供对话屏、凭据设置屏、配置中心共用,
5
5
  * 避免 `screens.tsx ↔ config-wizard.tsx` 相互 import 形成循环依赖。
6
6
  */
7
+ import stringWidth from "string-width";
7
8
  import { isMouseSequence } from "./mouse.js";
8
9
  // ==================== 显示宽度 ====================
9
- /** 是否宽字符(CJK / 全角 / 常见 Emoji),占 2 列 */
10
- function isWide(cp) {
11
- return ((cp >= 0x1100 && cp <= 0x115f) ||
12
- (cp >= 0x2e80 && cp <= 0x303e) ||
13
- (cp >= 0x3041 && cp <= 0x33ff) ||
14
- (cp >= 0x3400 && cp <= 0x4dbf) ||
15
- (cp >= 0x4e00 && cp <= 0x9fff) ||
16
- (cp >= 0xa000 && cp <= 0xa4cf) ||
17
- (cp >= 0xac00 && cp <= 0xd7a3) ||
18
- (cp >= 0xf900 && cp <= 0xfaff) ||
19
- (cp >= 0xfe30 && cp <= 0xfe6f) ||
20
- (cp >= 0xff00 && cp <= 0xff60) ||
21
- (cp >= 0xffe0 && cp <= 0xffe6) ||
22
- (cp >= 0x1f300 && cp <= 0x1faff) ||
23
- (cp >= 0x20000 && cp <= 0x3fffd));
24
- }
25
- /** 终端显示宽度(宽字符计 2,组合字符计 0) */
10
+ //
11
+ // 宽度计算直接交给 `string-width` —— **Ink 排版时用的就是它**
12
+ //(`ink/build/output.js` 第一行 `import stringWidth from 'string-width'`)。
13
+ //
14
+ // 曾经这里手写了一张 `isWide` 区间表,逐 code point 累加。结果是两把尺子:
15
+ // - `⚡` `✅` `✨` `⭐` 这类 East Asian Wide 符号我们算 1 列、Ink 算 2 列;
16
+ // - `👨‍👩‍👧` 逐 code point 累加得 8 列,实际只占 2 列;
17
+ // - `✌️`(VS16 序列)与 `🇨🇳`(国旗对)同样对不上。
18
+ // 于是一切依赖宽度的东西(折行、状态栏装行、光标列号)都差一截,还靠
19
+ // 「预留 1 列余量」硬顶。现在两边用同一把尺子,这类偏差从根上没了。
20
+ //
21
+ // 仍然需要 `graphemes()`:`string-width` 只管宽度,不管「光标该以什么为单位移动」。
22
+ /**
23
+ * 单簇宽度缓存 —— `string-width` 内部要跑 emoji / EAW 正则,而折行是每帧热路径;
24
+ * 字素簇的种类远少于出现次数,缓存后几乎零成本。
25
+ */
26
+ const clusterWidthCache = new Map();
27
+ const CLUSTER_CACHE_LIMIT = 4096;
28
+ /** 终端显示宽度(按字素簇,与 Ink 的排版完全一致) */
26
29
  export function displayWidth(text) {
27
- let width = 0;
28
- for (const ch of text) {
29
- const cp = ch.codePointAt(0);
30
- if (cp >= 0x0300 && cp <= 0x036f)
31
- continue; // 组合附加符号
32
- width += isWide(cp) ? 2 : 1;
33
- }
30
+ if (text === "")
31
+ return 0;
32
+ return stringWidth(text);
33
+ }
34
+ /** 单个字素簇的显示宽度(结果带缓存) */
35
+ export function clusterWidth(cluster) {
36
+ if (cluster === "")
37
+ return 0;
38
+ const cached = clusterWidthCache.get(cluster);
39
+ if (cached !== undefined)
40
+ return cached;
41
+ const width = stringWidth(cluster);
42
+ if (clusterWidthCache.size >= CLUSTER_CACHE_LIMIT)
43
+ clusterWidthCache.clear();
44
+ clusterWidthCache.set(cluster, width);
34
45
  return width;
35
46
  }
47
+ /**
48
+ * 把一个字符串切成**字素簇**(用户感知的「一个字符」)。
49
+ *
50
+ * 为什么不能按 code point 切:`✌️` 是 `U+270C U+FE0F` 两个 code point、`👨‍👩‍👧` 是
51
+ * 五个、`🇨🇳` 是两个区域指示符——它们都是**一个**字素簇。按 code point 切会让光标
52
+ * 停在簇中间(例如停在 `✌` 与 `U+FE0F` 之间),后果是:
53
+ * - 反显光标变成在反显一个变体选择符 —— 屏幕上什么都不显示(实测:Ink 的输出里
54
+ * 整个反显段直接消失);
55
+ * - 退格只删掉半个 emoji,留下一个孤立的 `U+FE0F`;
56
+ * - 光标列号也会因为「半个簇」的宽度而算错。
57
+ *
58
+ * 用 `Intl.Segmenter`(Node 内置,无需依赖);环境不支持时退回 code point 切分。
59
+ */
60
+ const segmenter = typeof Intl !== "undefined" && typeof Intl.Segmenter === "function"
61
+ ? new Intl.Segmenter("zh", { granularity: "grapheme" })
62
+ : null;
63
+ /**
64
+ * 「可能出现多个 code point 合为一簇」的码位集合。命中才需要 `Intl.Segmenter`。
65
+ *
66
+ * 折行是每帧热路径,而 `Segmenter.segment()` 比直接遍历慢一个数量级;但绝大多数
67
+ * 文本(ASCII / CJK / 代码)每一簇就是一个 code point,根本不需要分词器。
68
+ * 实测一帧 60 行中文:663 µs → 52 µs。
69
+ *
70
+ * 命中条件必须**宁可误报不可漏报**(误报只是走慢路径,漏报会切错字):
71
+ * - `\u{10000}-\u{10FFFF}`:astral 平面(emoji、区域指示符、肤色修饰符、
72
+ * tag 序列、扩展汉字)。注意**不能**写 `\uD800-\uDFFF`:`u` 模式下那是
73
+ * 「孤立代理项」,合法的 astral 字符(已合成为一个码位)反而匹配不到;
74
+ * - `\u000D`:CRLF 是一个字素簇(`\r\n` 两个 code point);
75
+ * - `\u200C` `\u200D`:ZWNJ / ZWJ(连字与 emoji 家族连接);
76
+ * - `\u1100-\u11FF`:韩文组合字母(`\u1100\u1161\u11A8` 三簇合一);
77
+ * - `\p{M}`:所有组合附加符(拉丁、天城文、阿拉伯文、键帽 U+20E3、
78
+ * 变体选择符 U+FE00-FE0F …)。这一条覆盖不了用枚举列全,必须用 Unicode 属性。
79
+ */
80
+ const NEEDS_SEGMENTER = /[\u{10000}-\u{10FFFF}\u000D\u200C\u200D\u1100-\u11FF\p{M}]/u;
81
+ export function graphemes(text) {
82
+ if (text === "")
83
+ return [];
84
+ // 每簇就是一个 code point 的普通文本:直接切,省掉整个分词器
85
+ if (!segmenter || !NEEDS_SEGMENTER.test(text))
86
+ return Array.from(text);
87
+ const out = [];
88
+ for (const part of segmenter.segment(text))
89
+ out.push(part.segment);
90
+ return out;
91
+ }
36
92
  /**
37
93
  * 按显示宽度把文本折成多行(保留显式换行)。
38
- * 拉丁文优先在空格处断行,CJK / 超长词按字符硬断;保证每行显示宽度 ≤ limit。
94
+ * 拉丁文优先在空格处断行,CJK / 超长词按**字素簇**硬断;保证每行显示宽度 ≤ limit。
39
95
  */
40
96
  export function wrapText(text, limit) {
41
97
  const width = Math.max(1, limit);
@@ -47,8 +103,8 @@ export function wrapText(text, limit) {
47
103
  }
48
104
  let line = "";
49
105
  let lineWidth = 0;
50
- for (const ch of raw) {
51
- const w = displayWidth(ch);
106
+ for (const ch of graphemes(raw)) {
107
+ const w = clusterWidth(ch);
52
108
  if (lineWidth + w > width && line !== "") {
53
109
  const space = line.lastIndexOf(" ");
54
110
  if (space > 0) {
@@ -70,12 +126,12 @@ export function wrapText(text, limit) {
70
126
  return out.length > 0 ? out : [""];
71
127
  }
72
128
  /**
73
- * 把输入文本按显示宽度折行,并定位光标(`cursor` 为字符索引,以 code point 计)。
74
- * 折行采用逐字符硬断(不做空格优先),以保证光标映射精确、支持跨行编辑。
129
+ * 把输入文本按显示宽度折行,并定位光标(`cursor` 为**字素簇**索引)。
130
+ * 折行采用逐簇硬断(不做空格优先),以保证光标映射精确、支持跨行编辑。
75
131
  */
76
132
  export function layoutInput(text, cursor, width) {
77
133
  const limit = Math.max(1, width);
78
- const chars = Array.from(text);
134
+ const chars = graphemes(text);
79
135
  const pos = Math.max(0, Math.min(cursor, chars.length));
80
136
  const lines = [];
81
137
  let line = "";
@@ -85,7 +141,7 @@ export function layoutInput(text, cursor, width) {
85
141
  let cursorRow = 0;
86
142
  let cursorCol = 0;
87
143
  let processed = 0;
88
- // 记录「处理完 processed 个字符后」的光标位置;processed === pos 即为光标处
144
+ // 记录「处理完 processed 个簇后」的光标位置;processed === pos 即为光标处
89
145
  const mark = () => {
90
146
  if (processed === pos) {
91
147
  cursorRow = row;
@@ -104,7 +160,7 @@ export function layoutInput(text, cursor, width) {
104
160
  mark();
105
161
  continue;
106
162
  }
107
- const w = displayWidth(ch);
163
+ const w = clusterWidth(ch);
108
164
  if (col > 0 && lineWidth + w > limit) {
109
165
  lines.push(line);
110
166
  line = "";
@@ -161,8 +217,8 @@ export function clipWidth(text, max) {
161
217
  return text;
162
218
  let out = "";
163
219
  let width = 0;
164
- for (const ch of text) {
165
- const w = displayWidth(ch);
220
+ for (const ch of graphemes(text)) {
221
+ const w = clusterWidth(ch);
166
222
  if (width + w > limit - 1)
167
223
  break; // 预留 1 列给省略号
168
224
  out += ch;
@@ -200,6 +256,10 @@ export function bottomPadding(terminalRows, contentLines) {
200
256
  * 该提示是喂给模型的上下文,不该显示给用户。
201
257
  */
202
258
  export function stripCwdNote(text) {
203
- return text.replace(/\n+(?:\[工作目录变更\]\n)?当前工作目录: [^\n]*$/, "");
259
+ // 两代格式都要认:新(英文 + system-injected 标记)与旧存档里的中文格式。
260
+ // 旧格式不认会把磁盘上已有会话的 cwd 笔记渲染出来。
261
+ return text
262
+ .replace(/\n+\[system-injected: [^\]\n]*\]\nCurrent working directory: [^\n]*$/, "")
263
+ .replace(/\n+(?:\[工作目录变更\]\n)?当前工作目录: [^\n]*$/, "");
204
264
  }
205
265
  //# sourceMappingURL=text.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/tui/text.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/tui/text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,iDAAiD;AACjD,EAAE;AACF,8CAA8C;AAC9C,uEAAuE;AACvE,EAAE;AACF,kDAAkD;AAClD,8DAA8D;AAC9D,gDAAgD;AAChD,uCAAuC;AACvC,oCAAoC;AACpC,qCAAqC;AACrC,EAAE;AACF,0DAA0D;AAE1D;;;GAGG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAkB,CAAC;AACpD,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC,iCAAiC;AACjC,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,iBAAiB,CAAC,IAAI,IAAI,mBAAmB;QAAE,iBAAiB,CAAC,KAAK,EAAE,CAAC;IAC7E,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,SAAS,GACb,OAAO,IAAI,KAAK,WAAW,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,UAAU;IACjE,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IACvD,CAAC,CAAC,IAAI,CAAC;AAEX;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,eAAe,GAAG,6DAA6D,CAAC;AAEtF,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC3B,sCAAsC;IACtC,IAAI,CAAC,SAAS,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnE,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAa;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YACf,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACb,SAAS;QACX,CAAC;QACD,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,SAAS,GAAG,CAAC,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;gBACzC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBACpC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;oBACd,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;oBAC/B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;oBAC7B,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACf,IAAI,GAAG,EAAE,CAAC;oBACV,SAAS,GAAG,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC;YACD,IAAI,IAAI,EAAE,CAAC;YACX,SAAS,IAAI,CAAC,CAAC;QACjB,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACrC,CAAC;AAcD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc,EAAE,KAAa;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,qDAAqD;IACrD,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,SAAS,GAAG,GAAG,CAAC;YAChB,SAAS,GAAG,GAAG,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;IACF,IAAI,EAAE,CAAC;IACP,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,GAAG,EAAE,CAAC;YACV,SAAS,GAAG,CAAC,CAAC;YACd,GAAG,IAAI,CAAC,CAAC;YACT,GAAG,GAAG,CAAC,CAAC;YACR,SAAS,IAAI,CAAC,CAAC;YACf,IAAI,EAAE,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,GAAG,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,GAAG,EAAE,CAAC;YACV,SAAS,GAAG,CAAC,CAAC;YACd,GAAG,IAAI,CAAC,CAAC;YACT,GAAG,GAAG,CAAC,CAAC;QACV,CAAC;QACD,IAAI,IAAI,EAAE,CAAC;QACX,SAAS,IAAI,CAAC,CAAC;QACf,GAAG,IAAI,CAAC,CAAC;QACT,SAAS,IAAI,CAAC,CAAC;QACf,IAAI,EAAE,CAAC;IACT,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AACzC,CAAC;AAED,+CAA+C;AAC/C,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,CACL,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,gCAAgC;QAC/D,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,4BAA4B;QAC7D,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,sBAAsB;QACrD,eAAe,CAAC,QAAQ,CAAC,CAAC,4BAA4B;KACvD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,GAAW;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC;YAAE,MAAM,CAAC,aAAa;QAC/C,GAAG,IAAI,EAAE,CAAC;QACV,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IACD,OAAO,GAAG,GAAG,GAAG,CAAC;AACnB,CAAC;AAED,iDAAiD;AAEjD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB;IAClD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,YAAoB,EAAE,YAAoB;IACtE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,gDAAgD;IAChD,8BAA8B;IAC9B,OAAO,IAAI;SACR,OAAO,CAAC,sEAAsE,EAAE,EAAE,CAAC;SACnF,OAAO,CAAC,qCAAqC,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * 生成过程的 token 速率统计 —— 纯逻辑,不依赖 React / Ink / SDK
3
+ *
4
+ * 需求是「每秒接收多少 token」,而 SDK 只在**每个请求的最后一个 chunk** 才给出
5
+ * `usage`(`stream_options.include_usage`),拿不到逐 token 的用量。所以这里分两路:
6
+ *
7
+ * - **实时速率**:按增量字符估算 token,用 2 秒滑动窗口算(`live`)。它必须靠估算,
8
+ * 否则要等一整轮结束才有数字,就谈不上「实时」;
9
+ * - **本轮平均**:一轮结束时优先用真实 `completion_tokens`(累加多轮工具调用),
10
+ * 除以「首字 → 末字」的时间跨度(`average`)。这段时间不含开头的等待首字。
11
+ *
12
+ * 两者口径不同,展示上也分开(生成中显示实时,结束后显示本轮平均)——把估算值
13
+ * 和精确值混成一个数字才是真的误导。
14
+ */
15
+ /** 实时速率的滑动窗口:取值越小越「跳」,越大越迟钝 */
16
+ export declare const LIVE_WINDOW_MS = 2000;
17
+ /** 计算速率的最小时间跨度:太短会把「首包爆发」放大成离谱的高速率 */
18
+ export declare const MIN_SPAN_MS = 400;
19
+ /**
20
+ * 按字符估算 token 数(浮点,调用方决定何时取整)。
21
+ *
22
+ * 权重取值依据:中文/全角约 0.7 token 一个字(DeepSeek / GLM 系分词器实测区间
23
+ * 0.6~0.8),其余非空白字符约 0.25(≈4 字符一个 token),空白约 0.1(多半与前词
24
+ * 合并,但不为零)。这是**估算**,用于实时速率;精确值一律以 `usage` 为准。
25
+ */
26
+ export declare function estimateTokens(text: string): number;
27
+ /**
28
+ * 从流式 chunk 里取出「本次新增的输出文本」:正文 + 思考 + 工具参数。
29
+ *
30
+ * 三者都是模型实打实吐出来的 token,只统计正文会低估(尤其推理模型的思考段)。
31
+ */
32
+ export declare function deltaText(chunk: any): string;
33
+ /** 一次快照:生成中看 `live`,结束后看 `average` */
34
+ export interface RateSample {
35
+ /** 实时速率(token/秒,最近窗口估算);无数据时为 null */
36
+ live: number | null;
37
+ /** 本轮平均速率(token/秒,优先真实 usage);一轮未结束时为 null */
38
+ average: number | null;
39
+ /** 本轮是否仍在生成 */
40
+ running: boolean;
41
+ }
42
+ /** 无数据的快照(状态栏据此不显示速率段) */
43
+ export declare const IDLE_RATE: RateSample;
44
+ /**
45
+ * 速率统计器。
46
+ *
47
+ * 生命周期由调用方驱动:`begin()`(一轮开始)→ 若干个 `addDelta()` / `addExact()`
48
+ * → `finish()`(一轮结束)。时间一律可注入,便于单测不依赖真实时钟。
49
+ */
50
+ export declare class TokenRateMeter {
51
+ private samples;
52
+ /** 估算的累计输出 token */
53
+ private estimated;
54
+ /** 真实 `completion_tokens` 累加(多轮工具调用逐轮相加) */
55
+ private exact;
56
+ private startedAt;
57
+ private firstOutputAt;
58
+ private lastOutputAt;
59
+ private finished;
60
+ private averageValue;
61
+ /** 一轮开始:清空上一轮的所有状态 */
62
+ begin(now?: number): void;
63
+ /** 收到一段增量文本:累加估算 token 并打点 */
64
+ addDelta(text: string, now?: number): void;
65
+ /** 收到真实用量(流末 chunk 的 `usage.completion_tokens`),多轮累加 */
66
+ addExact(completionTokens: number | undefined | null): void;
67
+ /**
68
+ * 一轮结束:算本轮平均速率。
69
+ *
70
+ * 分子优先用真实 `completion_tokens`;分母是「首字 → 末字」的跨度,即真正在接收
71
+ * 内容的那段时间 —— 不含开头的排队等首字,也避免把整轮的思考停顿算成慢。
72
+ */
73
+ finish(now?: number): void;
74
+ /** 当前快照(供状态栏直接渲染) */
75
+ snapshot(now?: number): RateSample;
76
+ /** 清空(退出 / 重建会话时调用) */
77
+ reset(): void;
78
+ /** 最近窗口的估算速率;窗口内没动静时返回 0(停顿本来就该显示 0,而不是上一秒的高值) */
79
+ private liveRate;
80
+ /** 丢掉窗口外的采样(保留一个窗口起点之前的锚点,用于算基准值) */
81
+ private trim;
82
+ }
83
+ //# sourceMappingURL=token-rate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-rate.d.ts","sourceRoot":"","sources":["../../src/tui/token-rate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,+BAA+B;AAC/B,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,sCAAsC;AACtC,eAAO,MAAM,WAAW,MAAM,CAAC;AAK/B;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQnD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAqB5C;AAED,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,0BAA0B;AAC1B,eAAO,MAAM,SAAS,EAAE,UAA0D,CAAC;AASnF;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAgB;IAC/B,oBAAoB;IACpB,OAAO,CAAC,SAAS,CAAK;IACtB,4CAA4C;IAC5C,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAuB;IAE3C,sBAAsB;IACtB,KAAK,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI;IAWrC,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI;IAWtD,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI;IAM3D;;;;;OAKG;IACH,MAAM,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI;IAatC,qBAAqB;IACrB,QAAQ,CAAC,GAAG,GAAE,MAAmB,GAAG,UAAU;IAM9C,uBAAuB;IACvB,KAAK,IAAI,IAAI;IAWb,kDAAkD;IAClD,OAAO,CAAC,QAAQ;IAkBhB,qCAAqC;IACrC,OAAO,CAAC,IAAI;CASb"}