@nextclaw/ui 0.11.20 → 0.11.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 (125) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/assets/{ChannelsList-DAx7wv0_.js → ChannelsList-Zeys_w43.js} +6 -6
  3. package/dist/assets/ChatPage-DWOU_8P6.js +43 -0
  4. package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
  5. package/dist/assets/{DocBrowser-DKkE3Y4I.js → DocBrowser-BmtBLFU0.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-BcZRBsCg.js → DocBrowserContext-YIKkPb76.js} +1 -1
  7. package/dist/assets/{LogoBadge-BIPDLEwK.js → LogoBadge-F7ZWdxLT.js} +1 -1
  8. package/dist/assets/MarketplacePage-BfaTTqN6.js +1 -0
  9. package/dist/assets/{MarketplacePage-Dlp5BgCh.js → MarketplacePage-Cd4faegU.js} +2 -2
  10. package/dist/assets/{McpMarketplacePage-CwKtAil8.js → McpMarketplacePage-C09Ngs7O.js} +2 -2
  11. package/dist/assets/ModelConfig-DJgdcgvQ.js +1 -0
  12. package/dist/assets/ProvidersList-w0rVFIBf.js +1 -0
  13. package/dist/assets/RemoteAccessPage-BJ_ckkOV.js +1 -0
  14. package/dist/assets/RuntimeConfig-Cmn2xPQO.js +1 -0
  15. package/dist/assets/{SearchConfig-v46R5a2U.js → SearchConfig-BT13qpR_.js} +1 -1
  16. package/dist/assets/{SecretsConfig-CXvUpbB_.js → SecretsConfig-CvqEVn0B.js} +2 -2
  17. package/dist/assets/{SessionsConfig-7vUHMtOh.js → SessionsConfig-DHHcYznk.js} +2 -2
  18. package/dist/assets/{book-open-DzSduAaw.js → book-open-CXoF5nQC.js} +1 -1
  19. package/dist/assets/chat-session-display-VW6ZMvZP.js +1 -0
  20. package/dist/assets/{chunk-JZWAC4HX-C1vpvW4r.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
  21. package/dist/assets/{config-Df97LeLR.js → config-DJswxxE8.js} +1 -1
  22. package/dist/assets/{createLucideIcon-CcR5wVoU.js → createLucideIcon-CjGHOWb6.js} +1 -1
  23. package/dist/assets/{dist-Dii9v3X9.js → dist-Cl2QB-2y.js} +1 -1
  24. package/dist/assets/{dist-BMlnBah3.js → dist-nqTTbVdA.js} +1 -1
  25. package/dist/assets/{external-link-CnSDrvJE.js → external-link-tIO7zING.js} +1 -1
  26. package/dist/assets/{hash-CAnX6PNt.js → hash-JWUyl1pT.js} +1 -1
  27. package/dist/assets/i18n-CDHMXlRZ.js +1 -0
  28. package/dist/assets/index-BlH4-cBw.css +1 -0
  29. package/dist/assets/{index-B0DzQqwv.js → index-C6d0xmtm.js} +3 -3
  30. package/dist/assets/{label-CtIFj7_6.js → label-BIpeNu4r.js} +1 -1
  31. package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
  32. package/dist/assets/{logos-3KFNiOej.js → logos-DThdM9lk.js} +1 -1
  33. package/dist/assets/{page-layout-BMwpn87D.js → page-layout-D3Xo605Z.js} +1 -1
  34. package/dist/assets/plus-PHf8q-Ct.js +1 -0
  35. package/dist/assets/{popover-BIzq25oH.js → popover-BJRUGA_H.js} +1 -1
  36. package/dist/assets/provider-models-bz5y28rq.js +1 -0
  37. package/dist/assets/{react-ji6GGP_j.js → react-7ZHqQtEV.js} +1 -1
  38. package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
  39. package/dist/assets/{save-CMgYkJ-y.js → save-DJM5RRWW.js} +1 -1
  40. package/dist/assets/search-C91yH_6y.js +1 -0
  41. package/dist/assets/{security-config-Xi5DYW7j.js → security-config-T5zpg16O.js} +1 -1
  42. package/dist/assets/{select-Cz82gl01.js → select-DSkTc61S.js} +1 -1
  43. package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
  44. package/dist/assets/{status-dot-C7q1HvLH.js → status-dot-LNBlDu3q.js} +1 -1
  45. package/dist/assets/{switch-DYswvkYj.js → switch-Bo-Y46HZ.js} +1 -1
  46. package/dist/assets/tabs-custom-DXv507_2.js +1 -0
  47. package/dist/assets/{trash-2-DfXI7-ap.js → trash-2-DFZmW6Gg.js} +1 -1
  48. package/dist/assets/useConfirmDialog-Bs5Ll17m.js +1 -0
  49. package/dist/assets/{useMutation-s2sn2yzh.js → useMutation-DrZrOgVL.js} +1 -1
  50. package/dist/assets/x-D7Q1yqSF.js +1 -0
  51. package/dist/index.html +18 -18
  52. package/package.json +6 -6
  53. package/src/api/ncp-session.test.ts +37 -0
  54. package/src/api/ncp-session.ts +29 -1
  55. package/src/api/server-path.ts +23 -0
  56. package/src/api/types.ts +41 -0
  57. package/src/components/chat/ChatConversationPanel.test.tsx +43 -7
  58. package/src/components/chat/ChatConversationPanel.tsx +23 -17
  59. package/src/components/chat/ChatSidebar.test.tsx +2 -2
  60. package/src/components/chat/ChatSidebar.tsx +2 -2
  61. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
  62. package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
  63. package/src/components/chat/adapters/chat-message-part.adapter.ts +81 -6
  64. package/src/components/chat/adapters/chat-message.adapter.test.ts +393 -3
  65. package/src/components/chat/adapters/chat-message.partial-json.ts +89 -0
  66. package/src/components/chat/adapters/file-operation/card.ts +330 -0
  67. package/src/components/chat/adapters/file-operation/diff.ts +398 -0
  68. package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
  69. package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
  70. package/src/components/chat/chat-composer-state.ts +3 -3
  71. package/src/components/chat/chat-session-display.test.ts +21 -0
  72. package/src/components/chat/chat-session-display.ts +6 -1
  73. package/src/components/chat/containers/chat-input-bar.container.tsx +29 -32
  74. package/src/components/chat/containers/chat-message-list.container.tsx +1 -0
  75. package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
  76. package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
  77. package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
  78. package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
  79. package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
  80. package/src/components/chat/ncp/NcpChatPage.tsx +55 -17
  81. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
  82. package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
  83. package/src/components/chat/ncp/ncp-session-adapter.test.ts +176 -0
  84. package/src/components/chat/ncp/ncp-session-adapter.ts +16 -0
  85. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
  86. package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
  87. package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
  88. package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
  89. package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
  90. package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
  91. package/src/components/chat/stores/chat-input.store.ts +6 -3
  92. package/src/components/chat/stores/chat-thread.store.ts +6 -2
  93. package/src/components/chat/useNcpAgentRuntime.test.tsx +90 -0
  94. package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
  95. package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
  96. package/src/hooks/server-path/use-server-path-browse.ts +19 -0
  97. package/src/hooks/useConfig.ts +26 -1
  98. package/src/lib/i18n/i18n-language-owner.ts +94 -0
  99. package/src/lib/i18n/i18n.path-picker.ts +12 -0
  100. package/src/lib/i18n.chat.ts +25 -1
  101. package/src/lib/i18n.ts +21 -84
  102. package/src/lib/session-project/session-project.utils.ts +30 -0
  103. package/src/remote/remote-access-feedback.service.test.ts +18 -0
  104. package/src/remote/remote-access-feedback.service.ts +10 -1
  105. package/dist/assets/ChatPage-l2PYwCeB.js +0 -38
  106. package/dist/assets/DocBrowser-CIHLqoIm.js +0 -1
  107. package/dist/assets/MarketplacePage-TVeyVOuO.js +0 -1
  108. package/dist/assets/ModelConfig-Dg6F3Ldb.js +0 -1
  109. package/dist/assets/ProvidersList-f7bQdRxA.js +0 -1
  110. package/dist/assets/RemoteAccessPage-w_dY7P4T.js +0 -1
  111. package/dist/assets/RuntimeConfig-M4OKjmgU.js +0 -1
  112. package/dist/assets/chat-session-display-CGfXhJoT.js +0 -1
  113. package/dist/assets/i18n-CXBpwAwA.js +0 -1
  114. package/dist/assets/index-BahpXJg8.css +0 -1
  115. package/dist/assets/loader-circle-qgU4zQDw.js +0 -1
  116. package/dist/assets/plus-C9cYVbL-.js +0 -1
  117. package/dist/assets/provider-models-C8JQUd1E.js +0 -1
  118. package/dist/assets/search-sl1OeJFl.js +0 -1
  119. package/dist/assets/skeleton-rgIt7a5q.js +0 -1
  120. package/dist/assets/tabs-custom-DKYQxrx1.js +0 -1
  121. package/dist/assets/useConfirmDialog-CXDAxtRL.js +0 -1
  122. package/dist/assets/x-MIimOGs6.js +0 -1
  123. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
  124. /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
  125. /package/dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
@@ -0,0 +1,89 @@
1
+ export type PartialJsonStringValue = {
2
+ value: string;
3
+ truncated: boolean;
4
+ };
5
+
6
+ function decodeJsonStringChar(value: string): string {
7
+ switch (value) {
8
+ case '"':
9
+ case "\\":
10
+ case "/":
11
+ return value;
12
+ case "b":
13
+ return "\b";
14
+ case "f":
15
+ return "\f";
16
+ case "n":
17
+ return "\n";
18
+ case "r":
19
+ return "\r";
20
+ case "t":
21
+ return "\t";
22
+ default:
23
+ return value;
24
+ }
25
+ }
26
+
27
+ function locatePartialJsonStringValueStart(raw: string, fieldName: string): number | null {
28
+ const marker = `"${fieldName}"`;
29
+ const markerIndex = raw.indexOf(marker);
30
+ if (markerIndex < 0) {
31
+ return null;
32
+ }
33
+ const colonIndex = raw.indexOf(":", markerIndex + marker.length);
34
+ if (colonIndex < 0) {
35
+ return null;
36
+ }
37
+ let valueStart = colonIndex + 1;
38
+ while (valueStart < raw.length && /\s/.test(raw[valueStart] ?? "")) {
39
+ valueStart += 1;
40
+ }
41
+ return raw[valueStart] === '"' ? valueStart + 1 : null;
42
+ }
43
+
44
+ function consumePartialJsonStringValue(
45
+ raw: string,
46
+ valueStart: number,
47
+ maxChars = Number.POSITIVE_INFINITY,
48
+ ): PartialJsonStringValue | null {
49
+ let output = "";
50
+ let escaped = false;
51
+ for (let index = valueStart; index < raw.length; index += 1) {
52
+ const current = raw[index];
53
+ if (current == null) {
54
+ break;
55
+ }
56
+ if (escaped) {
57
+ output += decodeJsonStringChar(current);
58
+ escaped = false;
59
+ continue;
60
+ }
61
+ if (current === "\\") {
62
+ escaped = true;
63
+ continue;
64
+ }
65
+ if (current === '"') {
66
+ return { value: output, truncated: false };
67
+ }
68
+ output += current;
69
+ if (output.length >= maxChars) {
70
+ return { value: output, truncated: true };
71
+ }
72
+ }
73
+ return output.length > 0 ? { value: output, truncated: false } : null;
74
+ }
75
+
76
+ export function readPartialJsonStringField(
77
+ raw: string,
78
+ fieldNames: readonly string[],
79
+ maxChars = Number.POSITIVE_INFINITY,
80
+ ): PartialJsonStringValue | null {
81
+ for (const fieldName of fieldNames) {
82
+ const valueStart = locatePartialJsonStringValueStart(raw, fieldName);
83
+ if (valueStart == null) {
84
+ continue;
85
+ }
86
+ return consumePartialJsonStringValue(raw, valueStart, maxChars);
87
+ }
88
+ return null;
89
+ }
@@ -0,0 +1,330 @@
1
+ import type { ChatFileOperationBlockViewModel } from "@nextclaw/agent-chat-ui";
2
+ import {
3
+ buildFullReplaceBlock,
4
+ buildRawPreviewBlock,
5
+ parsePatchBlocks,
6
+ type ParsedBlock,
7
+ } from "@/components/chat/adapters/file-operation/diff";
8
+ import {
9
+ isRecord,
10
+ readAfterText,
11
+ readBeforeText,
12
+ readNewStartLine,
13
+ readNonEmptyString,
14
+ readOldStartLine,
15
+ readOperation,
16
+ readPartialRecordPayload,
17
+ readPatchText,
18
+ readPath,
19
+ readRecordPayload,
20
+ } from "@/components/chat/adapters/file-operation/record-readers";
21
+ import { readPartialJsonStringField } from "@/components/chat/adapters/chat-message.partial-json";
22
+
23
+ type ToolInvocationSource = {
24
+ toolName: string;
25
+ status?: string;
26
+ toolCallId?: string;
27
+ args?: unknown;
28
+ parsedArgs?: unknown;
29
+ result?: unknown;
30
+ };
31
+
32
+ type FileOperationCardData = {
33
+ summary?: string;
34
+ fileOperation?: {
35
+ blocks: ChatFileOperationBlockViewModel[];
36
+ };
37
+ };
38
+
39
+ const FILE_TOOL_NAMES = new Set([
40
+ "file_change",
41
+ "read_file",
42
+ "write_file",
43
+ "edit_file",
44
+ "apply_patch",
45
+ ]);
46
+
47
+ function finalizeParsedBlocks(
48
+ blocks: ParsedBlock[],
49
+ ): FileOperationCardData | null {
50
+ const normalizedBlocks = blocks
51
+ .map((block, index) => ({
52
+ key: `${block.path}-${index + 1}`,
53
+ path: block.path,
54
+ display: block.display,
55
+ ...(block.caption ? { caption: block.caption } : {}),
56
+ lines: block.lines,
57
+ ...(block.rawText ? { rawText: block.rawText } : {}),
58
+ ...(block.truncated ? { truncated: true } : {}),
59
+ }))
60
+ .filter((block) => block.lines.length > 0 || Boolean(block.rawText));
61
+
62
+ if (normalizedBlocks.length === 0) {
63
+ return null;
64
+ }
65
+
66
+ const paths = normalizedBlocks.map((block) => block.path);
67
+ const summary =
68
+ paths.length === 1
69
+ ? paths[0]
70
+ : `${paths.length} files · ${paths.slice(0, 2).join(" · ")}${paths.length > 2 ? " …" : ""}`;
71
+
72
+ return {
73
+ summary,
74
+ fileOperation: {
75
+ blocks: normalizedBlocks,
76
+ },
77
+ };
78
+ }
79
+
80
+ function buildBlockFromChangeRecord(
81
+ record: Record<string, unknown>,
82
+ fallbackPath: string,
83
+ ): ParsedBlock | null {
84
+ const path = readPath(record) ?? fallbackPath;
85
+ const operation = readOperation(record);
86
+ const oldStartLine = readOldStartLine(record);
87
+ const newStartLine = readNewStartLine(record);
88
+ const patchText = readPatchText(record);
89
+ if (patchText) {
90
+ const parsedBlocks = parsePatchBlocks(patchText);
91
+ if (parsedBlocks.length > 0) {
92
+ return parsedBlocks[0] ?? null;
93
+ }
94
+ }
95
+
96
+ const beforeText = readBeforeText(record);
97
+ const afterText = readAfterText(record);
98
+ if (beforeText != null || afterText != null) {
99
+ return buildFullReplaceBlock({
100
+ path,
101
+ beforeText,
102
+ afterText,
103
+ operation,
104
+ oldStartLine,
105
+ newStartLine,
106
+ });
107
+ }
108
+
109
+ const previewText = readNonEmptyString(record.preview);
110
+ if (previewText) {
111
+ return buildRawPreviewBlock({
112
+ path,
113
+ text: previewText,
114
+ operation,
115
+ oldStartLine,
116
+ newStartLine,
117
+ });
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ function buildBlocksFromChanges(changes: unknown): ParsedBlock[] {
124
+ if (!Array.isArray(changes)) {
125
+ return [];
126
+ }
127
+
128
+ const blocks: ParsedBlock[] = [];
129
+ changes.forEach((entry, index) => {
130
+ if (typeof entry === "string") {
131
+ const parsedBlocks = parsePatchBlocks(entry);
132
+ if (parsedBlocks.length > 0) {
133
+ blocks.push(...parsedBlocks);
134
+ }
135
+ return;
136
+ }
137
+ if (!isRecord(entry)) {
138
+ return;
139
+ }
140
+ const path = readPath(entry) ?? `file-${index + 1}`;
141
+ const block = buildBlockFromChangeRecord(entry, path);
142
+ if (block) {
143
+ blocks.push(block);
144
+ return;
145
+ }
146
+ const nestedChanges = Array.isArray(entry.changes)
147
+ ? buildBlocksFromChanges(entry.changes)
148
+ : [];
149
+ if (nestedChanges.length > 0) {
150
+ blocks.push(...nestedChanges);
151
+ }
152
+ });
153
+ return blocks;
154
+ }
155
+
156
+ function buildFileChangeCardData(
157
+ invocation: ToolInvocationSource,
158
+ ): FileOperationCardData | null {
159
+ const sourceRecord =
160
+ readRecordPayload(invocation.result) ??
161
+ readRecordPayload(invocation.parsedArgs) ??
162
+ readRecordPayload(invocation.args);
163
+ if (!sourceRecord) {
164
+ return null;
165
+ }
166
+ const blocks = buildBlocksFromChanges(sourceRecord.changes);
167
+ return finalizeParsedBlocks(blocks);
168
+ }
169
+
170
+ function buildReadFileCardData(
171
+ invocation: ToolInvocationSource,
172
+ ): FileOperationCardData | null {
173
+ const argsRecord =
174
+ readRecordPayload(invocation.parsedArgs) ??
175
+ readRecordPayload(invocation.args) ??
176
+ readPartialRecordPayload(invocation.args);
177
+ const path = argsRecord && readPath(argsRecord);
178
+ const content = readNonEmptyString(invocation.result);
179
+ if (!path || !content) {
180
+ return null;
181
+ }
182
+ return finalizeParsedBlocks(
183
+ [
184
+ buildRawPreviewBlock({
185
+ path,
186
+ text: content,
187
+ operation: "read",
188
+ }),
189
+ ].filter((block): block is ParsedBlock => Boolean(block)),
190
+ );
191
+ }
192
+
193
+ function buildWriteFileCardData(
194
+ invocation: ToolInvocationSource,
195
+ ): FileOperationCardData | null {
196
+ const isStreamingPartialCall = invocation.status === "partial-call";
197
+ if (isStreamingPartialCall && typeof invocation.args === "string") {
198
+ const pathField = readPartialJsonStringField(invocation.args, [
199
+ "path",
200
+ "filePath",
201
+ "file_path",
202
+ "targetPath",
203
+ "target_path",
204
+ "filename",
205
+ "name",
206
+ ]);
207
+ const contentField = readPartialJsonStringField(invocation.args, [
208
+ "content",
209
+ "text",
210
+ "afterText",
211
+ "after_text",
212
+ ]);
213
+ if (pathField?.value && contentField?.value) {
214
+ const previewBlock = buildRawPreviewBlock({
215
+ path: pathField.value,
216
+ text: contentField.value,
217
+ operation: "write",
218
+ });
219
+ if (previewBlock) {
220
+ return finalizeParsedBlocks([previewBlock]);
221
+ }
222
+ }
223
+ }
224
+
225
+ const argsRecord =
226
+ readRecordPayload(invocation.parsedArgs) ??
227
+ readRecordPayload(invocation.args) ??
228
+ readPartialRecordPayload(invocation.args);
229
+ if (!argsRecord) {
230
+ return null;
231
+ }
232
+ const path = readPath(argsRecord);
233
+ const content = readNonEmptyString(argsRecord.content);
234
+ if (!path || !content) {
235
+ return null;
236
+ }
237
+ return finalizeParsedBlocks(
238
+ [
239
+ buildRawPreviewBlock({
240
+ path,
241
+ text: content,
242
+ operation: "write",
243
+ }),
244
+ ].filter((block): block is ParsedBlock => Boolean(block)),
245
+ );
246
+ }
247
+
248
+ function buildEditFileCardData(
249
+ invocation: ToolInvocationSource,
250
+ ): FileOperationCardData | null {
251
+ const resultRecord = readRecordPayload(invocation.result);
252
+ const argsRecord =
253
+ readRecordPayload(invocation.parsedArgs) ??
254
+ readRecordPayload(invocation.args) ??
255
+ readPartialRecordPayload(invocation.args);
256
+ if (!resultRecord && !argsRecord) {
257
+ return null;
258
+ }
259
+ const path =
260
+ (resultRecord ? readPath(resultRecord) : null) ??
261
+ (argsRecord ? readPath(argsRecord) : null);
262
+ const beforeText =
263
+ (resultRecord ? readBeforeText(resultRecord) : null) ??
264
+ (argsRecord ? readBeforeText(argsRecord) : null);
265
+ const afterText =
266
+ (resultRecord ? readAfterText(resultRecord) : null) ??
267
+ (argsRecord ? readAfterText(argsRecord) : null);
268
+ const oldStartLine =
269
+ (resultRecord ? readOldStartLine(resultRecord) : null) ??
270
+ (argsRecord ? readOldStartLine(argsRecord) : null);
271
+ const newStartLine =
272
+ (resultRecord ? readNewStartLine(resultRecord) : null) ??
273
+ (argsRecord ? readNewStartLine(argsRecord) : null);
274
+ if (!path || (beforeText == null && afterText == null)) {
275
+ return null;
276
+ }
277
+ return finalizeParsedBlocks(
278
+ [
279
+ buildFullReplaceBlock({
280
+ path,
281
+ beforeText,
282
+ afterText,
283
+ operation: "edit",
284
+ oldStartLine,
285
+ newStartLine,
286
+ }),
287
+ ].filter((block): block is ParsedBlock => Boolean(block)),
288
+ );
289
+ }
290
+
291
+ function buildApplyPatchCardData(
292
+ invocation: ToolInvocationSource,
293
+ ): FileOperationCardData | null {
294
+ const parsedArgsRecord = readRecordPayload(invocation.parsedArgs);
295
+ const argsRecord =
296
+ readRecordPayload(invocation.args) ??
297
+ readPartialRecordPayload(invocation.args);
298
+ const patchText =
299
+ readNonEmptyString(invocation.args) ??
300
+ (parsedArgsRecord ? readNonEmptyString(parsedArgsRecord.patch) : null) ??
301
+ (argsRecord ? readNonEmptyString(argsRecord.patch) : null);
302
+ if (!patchText) {
303
+ return null;
304
+ }
305
+ return finalizeParsedBlocks(parsePatchBlocks(patchText));
306
+ }
307
+
308
+ export function buildFileOperationCardData(
309
+ invocation: ToolInvocationSource,
310
+ ): FileOperationCardData | null {
311
+ if (!FILE_TOOL_NAMES.has(invocation.toolName)) {
312
+ return null;
313
+ }
314
+ if (invocation.toolName === "file_change") {
315
+ return buildFileChangeCardData(invocation);
316
+ }
317
+ if (invocation.toolName === "read_file") {
318
+ return buildReadFileCardData(invocation);
319
+ }
320
+ if (invocation.toolName === "write_file") {
321
+ return buildWriteFileCardData(invocation);
322
+ }
323
+ if (invocation.toolName === "edit_file") {
324
+ return buildEditFileCardData(invocation);
325
+ }
326
+ if (invocation.toolName === "apply_patch") {
327
+ return buildApplyPatchCardData(invocation);
328
+ }
329
+ return null;
330
+ }