File tree Expand file tree Collapse file tree
src/cortex-app-server/src/tools Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
44
55use serde_json:: { Value , json} ;
66
7+ use super :: preview:: truncate_at_char_boundary;
78use super :: types:: ToolResult ;
89
910/// Read file contents.
@@ -119,7 +120,7 @@ pub async fn write_file(cwd: &Path, args: Value) -> ToolResult {
119120 "filename" : std:: path:: Path :: new( path) . file_name( ) . and_then( |n| n. to_str( ) ) . unwrap_or( "" ) ,
120121 "extension" : extension,
121122 "size" : content. len( ) ,
122- "content_preview" : if content . len ( ) > 500 { & content[ .. 500 ] } else { content }
123+ "content_preview" : truncate_at_char_boundary ( content, 500 )
123124 } ) ) ,
124125 } ,
125126 Err ( e) => ToolResult :: error ( format ! ( "Failed to write file: {e}" ) ) ,
Original file line number Diff line number Diff line change @@ -15,6 +15,7 @@ mod definitions;
1515mod executor;
1616mod filesystem;
1717mod planning;
18+ mod preview;
1819mod search;
1920mod security;
2021mod shell;
Original file line number Diff line number Diff line change 1+ //! Helpers for building tool output previews.
2+
3+ /// Truncate a string to at most `max_bytes` bytes without splitting a UTF-8 character.
4+ pub ( super ) fn truncate_at_char_boundary ( input : & str , max_bytes : usize ) -> & str {
5+ if input. len ( ) <= max_bytes {
6+ return input;
7+ }
8+
9+ let mut end = max_bytes;
10+ while end > 0 && !input. is_char_boundary ( end) {
11+ end -= 1 ;
12+ }
13+
14+ & input[ ..end]
15+ }
16+
17+ #[ cfg( test) ]
18+ mod tests {
19+ use super :: truncate_at_char_boundary;
20+
21+ #[ test]
22+ fn keeps_ascii_at_requested_byte_limit ( ) {
23+ assert_eq ! ( truncate_at_char_boundary( "abcdef" , 3 ) , "abc" ) ;
24+ }
25+
26+ #[ test]
27+ fn backs_up_to_utf8_boundary ( ) {
28+ let input = format ! ( "{}{}" , "A" . repeat( 499 ) , "\u{4E2D} " ) ;
29+
30+ assert_eq ! ( input. len( ) , 502 ) ;
31+ assert_eq ! ( truncate_at_char_boundary( & input, 500 ) , "A" . repeat( 499 ) ) ;
32+ }
33+
34+ #[ test]
35+ fn returns_full_input_when_under_limit ( ) {
36+ assert_eq ! ( truncate_at_char_boundary( "hello" , 500 ) , "hello" ) ;
37+ }
38+ }
Original file line number Diff line number Diff line change 33use serde_json:: Value ;
44use tokio:: process:: Command ;
55
6+ use super :: preview:: truncate_at_char_boundary;
67use super :: types:: ToolResult ;
78
89/// Fetch content from a URL.
@@ -79,9 +80,10 @@ pub async fn fetch_url(args: Value) -> ToolResult {
7980
8081 // Truncate for display if too long
8182 let truncated = if content. len ( ) > 100_000 {
83+ let preview = truncate_at_char_boundary ( & content, 100_000 ) ;
8284 format ! (
83- "{}...\n [Truncated at 100000 chars , full size: {} chars ]" ,
84- & content [ .. 100_000 ] ,
85+ "{}...\n [Truncated at 100000 bytes , full size: {} bytes ]" ,
86+ preview ,
8587 content. len( )
8688 )
8789 } else {
@@ -124,7 +126,7 @@ pub async fn web_search(args: Value) -> ToolResult {
124126 let html = String :: from_utf8_lossy ( & output. stdout ) ;
125127 // Simple extraction of text
126128 let truncated = if html. len ( ) > 10_000 {
127- & html[ .. 10_000 ]
129+ truncate_at_char_boundary ( & html, 10_000 )
128130 } else {
129131 & html
130132 } ;
You can’t perform that action at this time.
0 commit comments