@nextclaw/ui 0.14.1-beta.0 → 0.14.1

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 (144) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/assets/{api-C6EDHBDR.js → api-DPcvCLfa.js} +3 -3
  3. package/dist/assets/{channels-list-page-DKj6m1Hc.js → channels-list-page-D6jtziu4.js} +2 -2
  4. package/dist/assets/chat-page-BBnNJoKG.js +105 -0
  5. package/dist/assets/{config-split-page-CjlmJy8X.js → config-split-page-BCJhqdK9.js} +1 -1
  6. package/dist/assets/{confirm-dialog-B39X0pYp.js → confirm-dialog-CWh5FfK2.js} +1 -1
  7. package/dist/assets/{desktop-update-config-DfUQ8nly.js → desktop-update-config-DsRhBizi.js} +1 -1
  8. package/dist/assets/{dist-CmMro3nV.js → dist-DPrgcEC0.js} +1 -1
  9. package/dist/assets/{dist-BIYV2-Ia.js → dist-DsE4OsSS.js} +1 -1
  10. package/dist/assets/doc-browser-C8xOF_9F.js +1 -0
  11. package/dist/assets/{doc-browser-CDtUtcim.js → doc-browser-DgQedeAn.js} +1 -1
  12. package/dist/assets/{doc-browser-context-DZbC9zVB.js → doc-browser-context-B46tQGmO.js} +1 -1
  13. package/dist/assets/{ellipsis-DDpgTzgx.js → ellipsis-B81FStLm.js} +1 -1
  14. package/dist/assets/{external-link-D2cBOCqn.js → external-link-C_dmG-WJ.js} +1 -1
  15. package/dist/assets/index-7o1WdUbU.js +103 -0
  16. package/dist/assets/index-DQWmEWzk.css +1 -0
  17. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +1 -0
  18. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +40 -0
  19. package/dist/assets/model-config-page-Cv0O3BIj.js +1 -0
  20. package/dist/assets/plus-Bnt56oqG.js +1 -0
  21. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +1 -0
  22. package/dist/assets/providers-config-page-CPDz0Lxc.js +1 -0
  23. package/dist/assets/{react-DYQXXGbH.js → react-sZz5HSCm.js} +1 -1
  24. package/dist/assets/remote-B0LMs8u7.js +1 -0
  25. package/dist/assets/rotate-cw-BfyG_KEF.js +1 -0
  26. package/dist/assets/runtime-config-page-q0fvv8ME.js +1 -0
  27. package/dist/assets/{save-BNVTgBVn.js → save-MzjoiHri.js} +1 -1
  28. package/dist/assets/{search-CKCzwLId.js → search-B29dE4GN.js} +1 -1
  29. package/dist/assets/{search-config-page-DG5Q2hF1.js → search-config-page-Dl5sZzob.js} +1 -1
  30. package/dist/assets/secrets-config-page-C_l-IFCK.js +3 -0
  31. package/dist/assets/{select-CIRwTNP4.js → select-BKmh05hd.js} +2 -2
  32. package/dist/assets/{tag-chip-DKhVFtTE.js → tag-chip-BS_7Ueom.js} +1 -1
  33. package/dist/assets/use-config-DZkBWIdL.js +1 -0
  34. package/dist/assets/x-CW2KO-Hz.js +1 -0
  35. package/dist/index.html +14 -14
  36. package/package.json +9 -9
  37. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +12 -63
  38. package/src/features/chat/components/conversation/chat-conversation-content.tsx +11 -50
  39. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +15 -2
  40. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +70 -7
  41. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +104 -16
  42. package/src/features/chat/components/layout/chat-sidebar.tsx +21 -2
  43. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +84 -6
  44. package/src/features/chat/features/input/hooks/use-chat-input-bar-query-state.ts +2 -0
  45. package/src/features/chat/features/input/hooks/use-chat-model-favorites.ts +88 -0
  46. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +64 -24
  47. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +1 -0
  48. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +21 -12
  49. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +80 -25
  50. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +310 -0
  51. package/src/features/chat/features/message/utils/__tests__/chat-message-test.utils.ts +43 -0
  52. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +1 -346
  53. package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +10 -9
  54. package/src/features/chat/features/message/utils/file-operation/diff.utils.ts +39 -36
  55. package/src/features/chat/features/message/utils/file-operation/line-builder.utils.ts +46 -38
  56. package/src/features/chat/features/message/utils/file-operation/parsed-block.utils.ts +18 -15
  57. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +3 -2
  58. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +25 -25
  59. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +15 -2
  60. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +24 -0
  61. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +27 -1
  62. package/src/features/chat/features/session-type/components/{chat-sidebar-create-menu.tsx → chat-session-type-menu.tsx} +17 -4
  63. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +15 -3
  64. package/src/features/chat/features/session-type/hooks/__tests__/use-chat-new-session-type-preference.test.tsx +152 -0
  65. package/src/features/chat/features/session-type/hooks/use-chat-new-session-type-preference.ts +159 -0
  66. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +193 -0
  67. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +123 -0
  68. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +104 -0
  69. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +72 -0
  70. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +54 -0
  71. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +132 -0
  72. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +82 -0
  73. package/src/features/chat/features/welcome/components/chat-welcome.tsx +121 -0
  74. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +53 -0
  75. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-selection.utils.test.ts +63 -0
  76. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-visibility.utils.test.ts +34 -0
  77. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +71 -0
  78. package/src/features/chat/features/welcome/utils/chat-welcome-selection.utils.ts +79 -0
  79. package/src/features/chat/features/welcome/utils/chat-welcome-visibility.utils.ts +17 -0
  80. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +65 -0
  81. package/src/features/chat/managers/chat-input.manager.ts +24 -2
  82. package/src/features/chat/stores/chat-input.store.ts +2 -0
  83. package/src/features/chat/types/chat-input-bar.types.ts +5 -0
  84. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +24 -0
  85. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +8 -3
  86. package/src/features/marketplace/components/marketplace-item-list-view.tsx +2 -2
  87. package/src/features/marketplace/components/marketplace-list-card.tsx +124 -98
  88. package/src/features/marketplace/components/marketplace-page-parts.tsx +11 -6
  89. package/src/features/marketplace/utils/marketplace-installed-cache.utils.ts +27 -23
  90. package/src/features/settings/components/config/provider-form.tsx +284 -387
  91. package/src/features/settings/components/config/provider-status-badge.tsx +4 -4
  92. package/src/features/settings/components/config/secrets-config-form.tsx +11 -135
  93. package/src/features/settings/hooks/use-provider-auth-flow.ts +140 -0
  94. package/src/features/settings/pages/__tests__/model-config-page.test.tsx +16 -0
  95. package/src/features/settings/pages/secrets-config-page.tsx +2 -1
  96. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +60 -1
  97. package/src/features/settings/utils/__tests__/secrets-config-form.utils.test.ts +66 -0
  98. package/src/features/settings/utils/provider-form-context.utils.ts +131 -0
  99. package/src/features/settings/utils/provider-form-model.utils.ts +101 -0
  100. package/src/features/settings/utils/provider-form-support.utils.ts +102 -1
  101. package/src/features/settings/utils/secrets-config-form.utils.ts +139 -0
  102. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +17 -11
  103. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +14 -9
  104. package/src/features/system-status/components/config/runtime-config-editor.tsx +8 -5
  105. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +14 -9
  106. package/src/features/system-status/components/config/runtime-settings-card.tsx +16 -7
  107. package/src/features/system-status/components/runtime-security-card.tsx +79 -25
  108. package/src/shared/components/common/__tests__/agent-avatar.test.tsx +3 -1
  109. package/src/shared/components/common/__tests__/searchable-model-input.test.tsx +34 -0
  110. package/src/shared/components/common/agent-avatar.tsx +5 -2
  111. package/src/shared/components/common/provider-scoped-model-input.tsx +1 -0
  112. package/src/shared/components/common/searchable-model-input.tsx +102 -87
  113. package/src/shared/components/ui/README.md +7 -0
  114. package/src/shared/components/ui/popover.tsx +19 -3
  115. package/src/shared/components/ui/select.tsx +14 -4
  116. package/src/shared/hooks/use-infinite-scroll-loader.ts +14 -10
  117. package/src/shared/lib/api/index.ts +3 -0
  118. package/src/shared/lib/api/preferences/preference-keys.config.ts +9 -0
  119. package/src/shared/lib/api/preferences/preference.types.ts +22 -0
  120. package/src/shared/lib/api/preferences/preference.utils.ts +40 -0
  121. package/src/shared/lib/i18n/locales/en-US/chat.json +16 -1
  122. package/src/shared/lib/i18n/locales/en-US/core.json +1 -0
  123. package/src/shared/lib/i18n/locales/en-US/marketplace.json +2 -0
  124. package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -1
  125. package/src/shared/lib/i18n/locales/zh-CN/core.json +1 -0
  126. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +2 -0
  127. package/dist/assets/chat-page-BXngLJY6.js +0 -105
  128. package/dist/assets/doc-browser-B6Wc_TIY.js +0 -1
  129. package/dist/assets/index-DTb_XVIH.js +0 -103
  130. package/dist/assets/index-ayiPYhf1.css +0 -1
  131. package/dist/assets/mcp-marketplace-page-DZ9H0v05.js +0 -1
  132. package/dist/assets/mcp-marketplace-page-cnVWf7QQ.js +0 -40
  133. package/dist/assets/model-config-page-Bcb35AVv.js +0 -1
  134. package/dist/assets/plus-CYE1onU2.js +0 -1
  135. package/dist/assets/provider-scoped-model-input-kdoNp4HG.js +0 -1
  136. package/dist/assets/providers-config-page-CQSibSwO.js +0 -1
  137. package/dist/assets/remote-BlJDbXOI.js +0 -1
  138. package/dist/assets/rotate-cw-BWbvFDAX.js +0 -1
  139. package/dist/assets/runtime-config-page-Tn2yLQ0c.js +0 -1
  140. package/dist/assets/secrets-config-page-BXn5Uuwp.js +0 -3
  141. package/dist/assets/use-config-DgKM9VIU.js +0 -1
  142. package/dist/assets/x-Cm3JjYut.js +0 -1
  143. package/src/features/chat/components/__tests__/chat-welcome.test.tsx +0 -45
  144. package/src/features/chat/components/chat-welcome.tsx +0 -102
@@ -0,0 +1,310 @@
1
+ import { ToolInvocationStatus } from "@nextclaw/agent-chat";
2
+ import { adapt, type ChatMessageSource } from "./chat-message-test.utils";
3
+
4
+ it("builds edit-file previews from structured args before the tool finishes", () => {
5
+ const adapted = adapt([
6
+ {
7
+ id: "assistant-edit-preview",
8
+ role: "assistant",
9
+ parts: [
10
+ {
11
+ type: "tool-invocation",
12
+ toolInvocation: {
13
+ status: ToolInvocationStatus.CALL,
14
+ toolCallId: "edit-call-1",
15
+ toolName: "edit_file",
16
+ args: JSON.stringify({
17
+ path: "src/app.ts",
18
+ oldText: "const color = 'red';",
19
+ newText: "const color = 'blue';",
20
+ }),
21
+ parsedArgs: {
22
+ path: "src/app.ts",
23
+ oldText: "const color = 'red';",
24
+ newText: "const color = 'blue';",
25
+ },
26
+ },
27
+ },
28
+ ],
29
+ },
30
+ ] as unknown as ChatMessageSource[]);
31
+
32
+ const editLines =
33
+ adapted[0]?.parts[0]?.type === "tool-card"
34
+ ? (adapted[0].parts[0].card.fileOperation?.blocks[0]?.lines ?? [])
35
+ : [];
36
+
37
+ expect(adapted[0]?.parts[0]).toMatchObject({
38
+ type: "tool-card",
39
+ card: {
40
+ toolName: "edit_file",
41
+ summary: "src/app.ts",
42
+ statusTone: "running",
43
+ fileOperation: {
44
+ blocks: [
45
+ {
46
+ path: "src/app.ts",
47
+ lines: [
48
+ {
49
+ kind: "remove",
50
+ text: "const color = 'red';",
51
+ },
52
+ {
53
+ kind: "add",
54
+ text: "const color = 'blue';",
55
+ },
56
+ ],
57
+ },
58
+ ],
59
+ },
60
+ },
61
+ });
62
+ expect(editLines[0]).not.toHaveProperty("oldLineNumber");
63
+ expect(editLines[1]).not.toHaveProperty("newLineNumber");
64
+ });
65
+
66
+ it("uses structured edit-file result line numbers after the tool finishes", () => {
67
+ const adapted = adapt([
68
+ {
69
+ id: "assistant-edit-result",
70
+ role: "assistant",
71
+ parts: [
72
+ {
73
+ type: "tool-invocation",
74
+ toolInvocation: {
75
+ status: ToolInvocationStatus.RESULT,
76
+ toolCallId: "edit-result-1",
77
+ toolName: "edit_file",
78
+ args: JSON.stringify({
79
+ path: "src/app.ts",
80
+ oldText: "const color = 'red';",
81
+ newText: "const color = 'blue';",
82
+ }),
83
+ parsedArgs: {
84
+ path: "src/app.ts",
85
+ oldText: "const color = 'red';",
86
+ newText: "const color = 'blue';",
87
+ },
88
+ result: {
89
+ path: "src/app.ts",
90
+ oldStartLine: 27,
91
+ newStartLine: 27,
92
+ message: "Edited src/app.ts",
93
+ },
94
+ },
95
+ },
96
+ ],
97
+ },
98
+ ] as unknown as ChatMessageSource[]);
99
+
100
+ expect(adapted[0]?.parts[0]).toMatchObject({
101
+ type: "tool-card",
102
+ card: {
103
+ toolName: "edit_file",
104
+ summary: "src/app.ts",
105
+ statusTone: "success",
106
+ fileOperation: {
107
+ blocks: [
108
+ {
109
+ path: "src/app.ts",
110
+ lines: [
111
+ {
112
+ kind: "remove",
113
+ text: "const color = 'red';",
114
+ oldLineNumber: 27,
115
+ },
116
+ {
117
+ kind: "add",
118
+ text: "const color = 'blue';",
119
+ newLineNumber: 27,
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ },
125
+ },
126
+ });
127
+ });
128
+
129
+ it("builds write-file previews from partial native args before the JSON is complete", () => {
130
+ const adapted = adapt([
131
+ {
132
+ id: "assistant-write-preview",
133
+ role: "assistant",
134
+ parts: [
135
+ {
136
+ type: "tool-invocation",
137
+ toolInvocation: {
138
+ status: ToolInvocationStatus.PARTIAL_CALL,
139
+ toolCallId: "write-call-1",
140
+ toolName: "write_file",
141
+ args: '{"path":"games/snake.html","content":"<!DOCTYPE html>\\n<canvas id=\\"game\\"></canvas>\\n<script>const score = 1;',
142
+ },
143
+ },
144
+ ],
145
+ },
146
+ ] as unknown as ChatMessageSource[]);
147
+
148
+ expect(adapted[0]?.parts[0]).toMatchObject({
149
+ type: "tool-card",
150
+ card: {
151
+ toolName: "write_file",
152
+ summary: "games/snake.html",
153
+ statusTone: "running",
154
+ statusLabel: "Running",
155
+ fileOperation: {
156
+ blocks: [
157
+ {
158
+ display: "preview",
159
+ path: "games/snake.html",
160
+ lines: expect.arrayContaining([
161
+ expect.objectContaining({
162
+ kind: "add",
163
+ text: "<!DOCTYPE html>",
164
+ }),
165
+ expect.objectContaining({
166
+ kind: "add",
167
+ text: '<canvas id="game"></canvas>',
168
+ }),
169
+ ]),
170
+ },
171
+ ],
172
+ },
173
+ },
174
+ });
175
+ });
176
+
177
+ it("keeps completed write-file cards in preview mode instead of falling back to raw byte summaries", () => {
178
+ const adapted = adapt([
179
+ {
180
+ id: "assistant-write-result",
181
+ role: "assistant",
182
+ parts: [
183
+ {
184
+ type: "tool-invocation",
185
+ toolInvocation: {
186
+ status: ToolInvocationStatus.RESULT,
187
+ toolCallId: "write-result-1",
188
+ toolName: "write_file",
189
+ args: JSON.stringify({
190
+ path: "games/snake.html",
191
+ content: '<!DOCTYPE html>\n<canvas id="game"></canvas>',
192
+ }),
193
+ result: "Wrote 3906 bytes to games/snake.html",
194
+ },
195
+ },
196
+ ],
197
+ },
198
+ ] as unknown as ChatMessageSource[]);
199
+
200
+ expect(adapted[0]?.parts[0]).toMatchObject({
201
+ type: "tool-card",
202
+ card: {
203
+ toolName: "write_file",
204
+ summary: "games/snake.html",
205
+ statusTone: "success",
206
+ fileOperation: {
207
+ blocks: [
208
+ {
209
+ display: "preview",
210
+ path: "games/snake.html",
211
+ lines: [
212
+ {
213
+ kind: "add",
214
+ text: "<!DOCTYPE html>",
215
+ newLineNumber: 1,
216
+ },
217
+ {
218
+ kind: "add",
219
+ text: '<canvas id="game"></canvas>',
220
+ newLineNumber: 2,
221
+ },
222
+ ],
223
+ },
224
+ ],
225
+ },
226
+ },
227
+ });
228
+ expect(adapted[0]?.parts[0]).not.toMatchObject({
229
+ type: "tool-card",
230
+ card: {
231
+ output: "Wrote 3906 bytes to games/snake.html",
232
+ },
233
+ });
234
+ });
235
+
236
+ it("renders codex file_change results as structured diff previews", () => {
237
+ const adapted = adapt([
238
+ {
239
+ id: "assistant-file-change",
240
+ role: "assistant",
241
+ parts: [
242
+ {
243
+ type: "tool-invocation",
244
+ toolInvocation: {
245
+ status: ToolInvocationStatus.RESULT,
246
+ toolCallId: "file-change-1",
247
+ toolName: "file_change",
248
+ args: JSON.stringify({
249
+ changes: [
250
+ {
251
+ path: "src/main.ts",
252
+ diff: [
253
+ "--- a/src/main.ts",
254
+ "+++ b/src/main.ts",
255
+ "@@ -109,1 +109,1 @@",
256
+ "-console.log('old');",
257
+ "+console.log('new');",
258
+ ].join("\n"),
259
+ },
260
+ ],
261
+ }),
262
+ result: {
263
+ status: "completed",
264
+ changes: [
265
+ {
266
+ path: "src/main.ts",
267
+ diff: [
268
+ "--- a/src/main.ts",
269
+ "+++ b/src/main.ts",
270
+ "@@ -109,1 +109,1 @@",
271
+ "-console.log('old');",
272
+ "+console.log('new');",
273
+ ].join("\n"),
274
+ },
275
+ ],
276
+ },
277
+ },
278
+ },
279
+ ],
280
+ },
281
+ ] as unknown as ChatMessageSource[]);
282
+
283
+ expect(adapted[0]?.parts[0]).toMatchObject({
284
+ type: "tool-card",
285
+ card: {
286
+ toolName: "file_change",
287
+ summary: "src/main.ts",
288
+ statusTone: "success",
289
+ fileOperation: {
290
+ blocks: [
291
+ {
292
+ path: "src/main.ts",
293
+ lines: [
294
+ {
295
+ kind: "remove",
296
+ text: "console.log('old');",
297
+ oldLineNumber: 109,
298
+ },
299
+ {
300
+ kind: "add",
301
+ text: "console.log('new');",
302
+ newLineNumber: 109,
303
+ },
304
+ ],
305
+ },
306
+ ],
307
+ },
308
+ },
309
+ });
310
+ });
@@ -0,0 +1,43 @@
1
+ import type { UiMessage } from "@nextclaw/agent-chat";
2
+ import {
3
+ adaptChatMessages,
4
+ type ChatMessageSource,
5
+ } from "@/features/chat/features/message/utils/chat-message.utils";
6
+
7
+ export type { ChatMessageSource } from "@/features/chat/features/message/utils/chat-message.utils";
8
+
9
+ export function toSource(uiMessages: UiMessage[]): ChatMessageSource[] {
10
+ return uiMessages as unknown as ChatMessageSource[];
11
+ }
12
+
13
+ const defaultTexts = {
14
+ roleLabels: {
15
+ user: "You",
16
+ assistant: "Assistant",
17
+ tool: "Tool",
18
+ system: "System",
19
+ fallback: "Message",
20
+ },
21
+ reasoningLabel: "Reasoning",
22
+ toolCallLabel: "Tool Call",
23
+ toolResultLabel: "Tool Result",
24
+ toolInputLabel: "Input",
25
+ toolNoOutputLabel: "No output",
26
+ toolOutputLabel: "Output",
27
+ toolStatusPreparingLabel: "Preparing",
28
+ toolStatusRunningLabel: "Running",
29
+ toolStatusCompletedLabel: "Completed",
30
+ toolStatusFailedLabel: "Failed",
31
+ toolStatusCancelledLabel: "Cancelled",
32
+ imageAttachmentLabel: "Image attachment",
33
+ fileAttachmentLabel: "File attachment",
34
+ unknownPartLabel: "Unknown Part",
35
+ };
36
+
37
+ export function adapt(uiMessages: ChatMessageSource[]) {
38
+ return adaptChatMessages({
39
+ uiMessages,
40
+ formatTimestamp: (value) => `formatted:${value}`,
41
+ texts: defaultTexts,
42
+ });
43
+ }
@@ -1,42 +1,5 @@
1
1
  import { ToolInvocationStatus, type UiMessage } from "@nextclaw/agent-chat";
2
- import { adaptChatMessages } from "../chat-message.utils";
3
- import type { ChatMessageSource } from "../chat-message.utils";
4
-
5
- function toSource(uiMessages: UiMessage[]): ChatMessageSource[] {
6
- return uiMessages as unknown as ChatMessageSource[];
7
- }
8
-
9
- const defaultTexts = {
10
- roleLabels: {
11
- user: "You",
12
- assistant: "Assistant",
13
- tool: "Tool",
14
- system: "System",
15
- fallback: "Message",
16
- },
17
- reasoningLabel: "Reasoning",
18
- toolCallLabel: "Tool Call",
19
- toolResultLabel: "Tool Result",
20
- toolInputLabel: "Input",
21
- toolNoOutputLabel: "No output",
22
- toolOutputLabel: "Output",
23
- toolStatusPreparingLabel: "Preparing",
24
- toolStatusRunningLabel: "Running",
25
- toolStatusCompletedLabel: "Completed",
26
- toolStatusFailedLabel: "Failed",
27
- toolStatusCancelledLabel: "Cancelled",
28
- imageAttachmentLabel: "Image attachment",
29
- fileAttachmentLabel: "File attachment",
30
- unknownPartLabel: "Unknown Part",
31
- };
32
-
33
- function adapt(uiMessages: ChatMessageSource[]) {
34
- return adaptChatMessages({
35
- uiMessages,
36
- formatTimestamp: (value) => `formatted:${value}`,
37
- texts: defaultTexts,
38
- });
39
- }
2
+ import { adapt, toSource, type ChatMessageSource } from "./chat-message-test.utils";
40
3
 
41
4
  it("maps markdown, reasoning, and tool parts into UI view models", () => {
42
5
  const messages: UiMessage[] = [
@@ -576,311 +539,3 @@ it("renders asset tool results as previewable files", () => {
576
539
  },
577
540
  });
578
541
  });
579
-
580
- it("builds edit-file previews from structured args before the tool finishes", () => {
581
- const adapted = adapt([
582
- {
583
- id: "assistant-edit-preview",
584
- role: "assistant",
585
- parts: [
586
- {
587
- type: "tool-invocation",
588
- toolInvocation: {
589
- status: ToolInvocationStatus.CALL,
590
- toolCallId: "edit-call-1",
591
- toolName: "edit_file",
592
- args: JSON.stringify({
593
- path: "src/app.ts",
594
- oldText: "const color = 'red';",
595
- newText: "const color = 'blue';",
596
- }),
597
- parsedArgs: {
598
- path: "src/app.ts",
599
- oldText: "const color = 'red';",
600
- newText: "const color = 'blue';",
601
- },
602
- },
603
- },
604
- ],
605
- },
606
- ] as unknown as ChatMessageSource[]);
607
-
608
- const editLines =
609
- adapted[0]?.parts[0]?.type === "tool-card"
610
- ? (adapted[0].parts[0].card.fileOperation?.blocks[0]?.lines ?? [])
611
- : [];
612
-
613
- expect(adapted[0]?.parts[0]).toMatchObject({
614
- type: "tool-card",
615
- card: {
616
- toolName: "edit_file",
617
- summary: "src/app.ts",
618
- statusTone: "running",
619
- fileOperation: {
620
- blocks: [
621
- {
622
- path: "src/app.ts",
623
- lines: [
624
- {
625
- kind: "remove",
626
- text: "const color = 'red';",
627
- },
628
- {
629
- kind: "add",
630
- text: "const color = 'blue';",
631
- },
632
- ],
633
- },
634
- ],
635
- },
636
- },
637
- });
638
- expect(editLines[0]).not.toHaveProperty("oldLineNumber");
639
- expect(editLines[1]).not.toHaveProperty("newLineNumber");
640
- });
641
-
642
- it("uses structured edit-file result line numbers after the tool finishes", () => {
643
- const adapted = adapt([
644
- {
645
- id: "assistant-edit-result",
646
- role: "assistant",
647
- parts: [
648
- {
649
- type: "tool-invocation",
650
- toolInvocation: {
651
- status: ToolInvocationStatus.RESULT,
652
- toolCallId: "edit-result-1",
653
- toolName: "edit_file",
654
- args: JSON.stringify({
655
- path: "src/app.ts",
656
- oldText: "const color = 'red';",
657
- newText: "const color = 'blue';",
658
- }),
659
- parsedArgs: {
660
- path: "src/app.ts",
661
- oldText: "const color = 'red';",
662
- newText: "const color = 'blue';",
663
- },
664
- result: {
665
- path: "src/app.ts",
666
- oldStartLine: 27,
667
- newStartLine: 27,
668
- message: "Edited src/app.ts",
669
- },
670
- },
671
- },
672
- ],
673
- },
674
- ] as unknown as ChatMessageSource[]);
675
-
676
- expect(adapted[0]?.parts[0]).toMatchObject({
677
- type: "tool-card",
678
- card: {
679
- toolName: "edit_file",
680
- summary: "src/app.ts",
681
- statusTone: "success",
682
- fileOperation: {
683
- blocks: [
684
- {
685
- path: "src/app.ts",
686
- lines: [
687
- {
688
- kind: "remove",
689
- text: "const color = 'red';",
690
- oldLineNumber: 27,
691
- },
692
- {
693
- kind: "add",
694
- text: "const color = 'blue';",
695
- newLineNumber: 27,
696
- },
697
- ],
698
- },
699
- ],
700
- },
701
- },
702
- });
703
- });
704
-
705
- it("builds write-file previews from partial native args before the JSON is complete", () => {
706
- const adapted = adapt([
707
- {
708
- id: "assistant-write-preview",
709
- role: "assistant",
710
- parts: [
711
- {
712
- type: "tool-invocation",
713
- toolInvocation: {
714
- status: ToolInvocationStatus.PARTIAL_CALL,
715
- toolCallId: "write-call-1",
716
- toolName: "write_file",
717
- args: '{"path":"games/snake.html","content":"<!DOCTYPE html>\\n<canvas id=\\"game\\"></canvas>\\n<script>const score = 1;',
718
- },
719
- },
720
- ],
721
- },
722
- ] as unknown as ChatMessageSource[]);
723
-
724
- expect(adapted[0]?.parts[0]).toMatchObject({
725
- type: "tool-card",
726
- card: {
727
- toolName: "write_file",
728
- summary: "games/snake.html",
729
- statusTone: "running",
730
- statusLabel: "Running",
731
- fileOperation: {
732
- blocks: [
733
- {
734
- display: "preview",
735
- path: "games/snake.html",
736
- lines: expect.arrayContaining([
737
- expect.objectContaining({
738
- kind: "add",
739
- text: "<!DOCTYPE html>",
740
- }),
741
- expect.objectContaining({
742
- kind: "add",
743
- text: '<canvas id="game"></canvas>',
744
- }),
745
- ]),
746
- },
747
- ],
748
- },
749
- },
750
- });
751
- });
752
-
753
- it("keeps completed write-file cards in preview mode instead of falling back to raw byte summaries", () => {
754
- const adapted = adapt([
755
- {
756
- id: "assistant-write-result",
757
- role: "assistant",
758
- parts: [
759
- {
760
- type: "tool-invocation",
761
- toolInvocation: {
762
- status: ToolInvocationStatus.RESULT,
763
- toolCallId: "write-result-1",
764
- toolName: "write_file",
765
- args: JSON.stringify({
766
- path: "games/snake.html",
767
- content: '<!DOCTYPE html>\n<canvas id="game"></canvas>',
768
- }),
769
- result: "Wrote 3906 bytes to games/snake.html",
770
- },
771
- },
772
- ],
773
- },
774
- ] as unknown as ChatMessageSource[]);
775
-
776
- expect(adapted[0]?.parts[0]).toMatchObject({
777
- type: "tool-card",
778
- card: {
779
- toolName: "write_file",
780
- summary: "games/snake.html",
781
- statusTone: "success",
782
- fileOperation: {
783
- blocks: [
784
- {
785
- display: "preview",
786
- path: "games/snake.html",
787
- lines: [
788
- {
789
- kind: "add",
790
- text: "<!DOCTYPE html>",
791
- newLineNumber: 1,
792
- },
793
- {
794
- kind: "add",
795
- text: '<canvas id="game"></canvas>',
796
- newLineNumber: 2,
797
- },
798
- ],
799
- },
800
- ],
801
- },
802
- },
803
- });
804
- expect(adapted[0]?.parts[0]).not.toMatchObject({
805
- type: "tool-card",
806
- card: {
807
- output: "Wrote 3906 bytes to games/snake.html",
808
- },
809
- });
810
- });
811
-
812
- it("renders codex file_change results as structured diff previews", () => {
813
- const adapted = adapt([
814
- {
815
- id: "assistant-file-change",
816
- role: "assistant",
817
- parts: [
818
- {
819
- type: "tool-invocation",
820
- toolInvocation: {
821
- status: ToolInvocationStatus.RESULT,
822
- toolCallId: "file-change-1",
823
- toolName: "file_change",
824
- args: JSON.stringify({
825
- changes: [
826
- {
827
- path: "src/main.ts",
828
- diff: [
829
- "--- a/src/main.ts",
830
- "+++ b/src/main.ts",
831
- "@@ -109,1 +109,1 @@",
832
- "-console.log('old');",
833
- "+console.log('new');",
834
- ].join("\n"),
835
- },
836
- ],
837
- }),
838
- result: {
839
- status: "completed",
840
- changes: [
841
- {
842
- path: "src/main.ts",
843
- diff: [
844
- "--- a/src/main.ts",
845
- "+++ b/src/main.ts",
846
- "@@ -109,1 +109,1 @@",
847
- "-console.log('old');",
848
- "+console.log('new');",
849
- ].join("\n"),
850
- },
851
- ],
852
- },
853
- },
854
- },
855
- ],
856
- },
857
- ] as unknown as ChatMessageSource[]);
858
-
859
- expect(adapted[0]?.parts[0]).toMatchObject({
860
- type: "tool-card",
861
- card: {
862
- toolName: "file_change",
863
- summary: "src/main.ts",
864
- statusTone: "success",
865
- fileOperation: {
866
- blocks: [
867
- {
868
- path: "src/main.ts",
869
- lines: [
870
- {
871
- kind: "remove",
872
- text: "console.log('old');",
873
- oldLineNumber: 109,
874
- },
875
- {
876
- kind: "add",
877
- text: "console.log('new');",
878
- newLineNumber: 109,
879
- },
880
- ],
881
- },
882
- ],
883
- },
884
- },
885
- });
886
- });