返回 CodeWhale
overflow.rs
根目录 / crates / tui / src / tools / web / overflow.rs
1 //! Shared route-sized inline output with recoverable session spillover.
2
3 use std::path::PathBuf;
4
5 use crate::tools::spec::{ToolContext, ToolError};
6
7 #[derive(Debug)]
8 pub(crate) struct OverflowArtifact {
9 pub(crate) session_id: String,
10 pub(crate) absolute_path: PathBuf,
11 pub(crate) relative_path: PathBuf,
12 pub(crate) byte_size: u64,
13 pub(crate) preview: String,
14 }
15
16 #[derive(Debug)]
17 pub(crate) struct BoundedText {
18 pub(crate) content: String,
19 pub(crate) artifact: Option<OverflowArtifact>,
20 }
21
22 pub(crate) fn inline_char_budget(context: &ToolContext) -> usize {
23 context
24 .route_context_window
25 .map(|tokens| {
26 let chars = u64::from(tokens).saturating_mul(4).saturating_mul(3) / 100;
27 usize::try_from(chars).unwrap_or(100_000)
28 })
29 .unwrap_or(100_000)
30 .clamp(1, 100_000)
31 }
32
33 pub(crate) fn bound_text<F>(
34 content: String,
35 context: &ToolContext,
36 artifact_id: F,
37 subject: &str,
38 ) -> Result<BoundedText, ToolError>
39 where
40 F: FnOnce(&str) -> String,
41 {
42 let budget = inline_char_budget(context);
43 if content.chars().count() <= budget {
44 return Ok(BoundedText {
45 content,
46 artifact: None,
47 });
48 }
49
50 let artifact_id = artifact_id(&content);
51 let (absolute_path, relative_path) =
52 crate::artifacts::write_session_artifact(&context.state_namespace, &artifact_id, &content)
53 .map_err(|error| {
54 ToolError::execution_failed(format!(
55 "failed to preserve {subject} content artifact: {error}"
56 ))
57 })?;
58 let relative = crate::artifacts::format_artifact_relative_path(&relative_path);
59 let mut head = content
60 .chars()
61 .take(budget.saturating_sub(256))
62 .collect::<String>();
63 let mut footer = overflow_footer(subject, &relative, head.len(), content.len());
64 let allowed_head = budget.saturating_sub(footer.chars().count());
65 head = content.chars().take(allowed_head).collect();
66 footer = overflow_footer(subject, &relative, head.len(), content.len());
67 while !head.is_empty() && head.chars().count() + footer.chars().count() > budget {
68 head.pop();
69 footer = overflow_footer(subject, &relative, head.len(), content.len());
70 }
71 let preview = content.chars().take(200).collect();
72
73 Ok(BoundedText {
74 content: format!("{head}{footer}"),
75 artifact: Some(OverflowArtifact {
76 session_id: context.state_namespace.clone(),
77 absolute_path,
78 relative_path,
79 byte_size: content.len() as u64,
80 preview,
81 }),
82 })
83 }
84
85 fn overflow_footer(subject: &str, relative: &str, head_bytes: usize, total_bytes: usize) -> String {
86 format!(
87 "\n\n[Content overflow: first {head_bytes} of {total_bytes} bytes shown; full {subject} saved to {relative}. Recovery: call retrieve_tool_result with ref={relative}.]"
88 )
89 }
90
90 lines RUST