snow-ai 0.3.20 → 0.3.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.
package/dist/mcp/filesystem.js
CHANGED
|
@@ -369,7 +369,10 @@ export class FilesystemMCPService {
|
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
371
|
// Single file mode
|
|
372
|
-
if (
|
|
372
|
+
if (searchContent === undefined ||
|
|
373
|
+
searchContent === null ||
|
|
374
|
+
replaceContent === undefined ||
|
|
375
|
+
replaceContent === null) {
|
|
373
376
|
throw new Error('searchContent and replaceContent are required for single file mode');
|
|
374
377
|
}
|
|
375
378
|
return await this.editFileBySearchSingle(filePath, searchContent, replaceContent, occurrence, contextLines);
|
|
@@ -201,12 +201,20 @@ function formatTokens(tokens, compact = false) {
|
|
|
201
201
|
}
|
|
202
202
|
return String(tokens);
|
|
203
203
|
}
|
|
204
|
-
function renderStackedBarChart(stats, terminalWidth) {
|
|
204
|
+
function renderStackedBarChart(stats, terminalWidth, scrollOffset) {
|
|
205
205
|
if (stats.models.size === 0) {
|
|
206
206
|
return (React.createElement(Text, { color: "gray", dimColor: true }, "No data available"));
|
|
207
207
|
}
|
|
208
208
|
const sortedModels = Array.from(stats.models.entries()).sort((a, b) => b[1].total - a[1].total);
|
|
209
209
|
const isNarrow = terminalWidth < 100;
|
|
210
|
+
// Show maximum 2 models at a time for better readability
|
|
211
|
+
const maxVisibleModels = 2;
|
|
212
|
+
// Calculate visible range
|
|
213
|
+
const startIdx = scrollOffset;
|
|
214
|
+
const endIdx = Math.min(startIdx + maxVisibleModels, sortedModels.length);
|
|
215
|
+
const visibleModels = sortedModels.slice(startIdx, endIdx);
|
|
216
|
+
const hasMoreAbove = startIdx > 0;
|
|
217
|
+
const hasMoreBelow = endIdx < sortedModels.length;
|
|
210
218
|
// Calculate max total (including cache) for scaling
|
|
211
219
|
const maxTotal = Math.max(...Array.from(stats.models.values()).map(s => s.total + s.cacheCreation + s.cacheRead));
|
|
212
220
|
// Use almost full width for bars (leave some margin)
|
|
@@ -227,7 +235,12 @@ function renderStackedBarChart(stats, terminalWidth) {
|
|
|
227
235
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
228
236
|
' ',
|
|
229
237
|
"Cache Create")),
|
|
230
|
-
|
|
238
|
+
hasMoreAbove && (React.createElement(Box, { marginBottom: 1 },
|
|
239
|
+
React.createElement(Text, { color: "yellow", dimColor: true },
|
|
240
|
+
"\u2191 ",
|
|
241
|
+
startIdx,
|
|
242
|
+
" more above (use \u2191 arrow)"))),
|
|
243
|
+
visibleModels.map(([modelName, modelStats]) => {
|
|
231
244
|
const shortName = getModelShortName(modelName, 30);
|
|
232
245
|
// Calculate segment lengths based on proportion
|
|
233
246
|
// Ensure at least 1 character if value exists
|
|
@@ -298,7 +311,12 @@ function renderStackedBarChart(stats, terminalWidth) {
|
|
|
298
311
|
React.createElement(Text, { color: "yellow", bold: true },
|
|
299
312
|
"Create:",
|
|
300
313
|
' ',
|
|
301
|
-
formatTokens(Array.from(stats.models.values()).reduce((sum, s) => sum + s.cacheCreation, 0))))))))
|
|
314
|
+
formatTokens(Array.from(stats.models.values()).reduce((sum, s) => sum + s.cacheCreation, 0)))))))),
|
|
315
|
+
hasMoreBelow && (React.createElement(Box, { marginTop: 1 },
|
|
316
|
+
React.createElement(Text, { color: "yellow", dimColor: true },
|
|
317
|
+
"\u2193 ",
|
|
318
|
+
sortedModels.length - endIdx,
|
|
319
|
+
" more below (use \u2193 arrow)")))));
|
|
302
320
|
}
|
|
303
321
|
export default function UsagePanel() {
|
|
304
322
|
const [granularity, setGranularity] = useState('week');
|
|
@@ -308,6 +326,7 @@ export default function UsagePanel() {
|
|
|
308
326
|
});
|
|
309
327
|
const [isLoading, setIsLoading] = useState(true);
|
|
310
328
|
const [error, setError] = useState(null);
|
|
329
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
311
330
|
const { columns: terminalWidth } = useTerminalSize();
|
|
312
331
|
useEffect(() => {
|
|
313
332
|
const load = async () => {
|
|
@@ -328,6 +347,10 @@ export default function UsagePanel() {
|
|
|
328
347
|
};
|
|
329
348
|
load();
|
|
330
349
|
}, [granularity]);
|
|
350
|
+
// Reset scroll when changing granularity
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
setScrollOffset(0);
|
|
353
|
+
}, [granularity]);
|
|
331
354
|
useInput((_input, key) => {
|
|
332
355
|
if (key.tab) {
|
|
333
356
|
const granularities = ['hour', 'day', 'week', 'month'];
|
|
@@ -335,6 +358,17 @@ export default function UsagePanel() {
|
|
|
335
358
|
const nextIdx = (currentIdx + 1) % granularities.length;
|
|
336
359
|
setGranularity(granularities[nextIdx]);
|
|
337
360
|
}
|
|
361
|
+
// Calculate available space for scrolling
|
|
362
|
+
const sortedModels = Array.from(stats.models.entries()).sort((a, b) => b[1].total - a[1].total);
|
|
363
|
+
const totalModels = sortedModels.length;
|
|
364
|
+
if (key.upArrow) {
|
|
365
|
+
setScrollOffset(prev => Math.max(0, prev - 1));
|
|
366
|
+
}
|
|
367
|
+
if (key.downArrow) {
|
|
368
|
+
// Reserve space for header, legend, total summary
|
|
369
|
+
const maxScroll = Math.max(0, totalModels - 1);
|
|
370
|
+
setScrollOffset(prev => Math.min(maxScroll, prev + 1));
|
|
371
|
+
}
|
|
338
372
|
});
|
|
339
373
|
if (isLoading) {
|
|
340
374
|
return (React.createElement(Box, { borderColor: "cyan", borderStyle: "round", paddingX: 2, paddingY: 0 },
|
|
@@ -356,5 +390,5 @@ export default function UsagePanel() {
|
|
|
356
390
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
357
391
|
' ',
|
|
358
392
|
"- Tab to switch")),
|
|
359
|
-
stats.models.size === 0 ? (React.createElement(Text, { color: "gray", dimColor: true }, "No usage data for this period")) : (renderStackedBarChart(stats, terminalWidth))));
|
|
393
|
+
stats.models.size === 0 ? (React.createElement(Text, { color: "gray", dimColor: true }, "No usage data for this period")) : (renderStackedBarChart(stats, terminalWidth, scrollOffset))));
|
|
360
394
|
}
|
|
@@ -323,9 +323,20 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
323
323
|
// We need to truncate to the same user message in the session file
|
|
324
324
|
if (currentSession) {
|
|
325
325
|
// Count how many user messages we're deleting (from selectedIndex onwards in UI)
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
// But exclude any uncommitted user messages that weren't saved to session
|
|
327
|
+
const messagesAfterSelected = messages.slice(selectedIndex);
|
|
328
|
+
const hasDiscontinuedMessage = messagesAfterSelected.some(msg => msg.discontinued);
|
|
329
|
+
let uiUserMessagesToDelete = 0;
|
|
330
|
+
if (hasDiscontinuedMessage) {
|
|
331
|
+
// If there's a discontinued message, it means all messages from selectedIndex onwards
|
|
332
|
+
// (including user messages) were not saved to session
|
|
333
|
+
// So we don't need to delete any user messages from session
|
|
334
|
+
uiUserMessagesToDelete = 0;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
// Normal case: count all user messages from selectedIndex onwards
|
|
338
|
+
uiUserMessagesToDelete = messagesAfterSelected.filter(msg => msg.role === 'user').length;
|
|
339
|
+
}
|
|
329
340
|
// Check if the selected message is a user message that might not be in session
|
|
330
341
|
// (e.g., interrupted before AI response)
|
|
331
342
|
const selectedMessage = messages[selectedIndex];
|
|
@@ -621,9 +632,25 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
621
632
|
}
|
|
622
633
|
}
|
|
623
634
|
// If some tool results are missing, remove from this assistant message onwards
|
|
635
|
+
// But only if this is the last assistant message with tool_calls in the entire conversation
|
|
624
636
|
if (toolCallIds.size > 0) {
|
|
625
|
-
|
|
626
|
-
|
|
637
|
+
// Additional check: ensure this is the last assistant message with tool_calls
|
|
638
|
+
let hasLaterAssistantWithTools = false;
|
|
639
|
+
for (let k = i + 1; k < messages.length; k++) {
|
|
640
|
+
const laterMsg = messages[k];
|
|
641
|
+
if (laterMsg?.role === 'assistant' &&
|
|
642
|
+
laterMsg?.tool_calls &&
|
|
643
|
+
laterMsg.tool_calls.length > 0) {
|
|
644
|
+
hasLaterAssistantWithTools = true;
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// Only truncate if no later assistant messages have tool_calls
|
|
649
|
+
// This preserves complete historical conversations
|
|
650
|
+
if (!hasLaterAssistantWithTools) {
|
|
651
|
+
truncateIndex = i;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
627
654
|
}
|
|
628
655
|
}
|
|
629
656
|
// If we found a complete assistant response without tool calls, we're done
|