mirror of
https://github.com/lWolvesl/claw-code.git
synced 2026-04-03 01:01:52 +08:00
Improve streaming feedback for CLI responses
The active Rust CLI path now keeps users informed during streaming with a waiting spinner, inline tool call summaries, response token usage, semantic color cues, and an opt-out switch. The work stays inside the active + renderer path and updates stale runtime tests that referenced removed permission enums. Constraint: Must keep changes in the active CLI path rather than refactoring unused app shell Constraint: Must pass cargo fmt, clippy, and full cargo test without adding dependencies Rejected: Route the work through | inactive path would expand risk and scope Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep future streaming UX changes wired through renderer color settings so remains end-to-end Tested: cargo fmt --all; cargo clippy --all-targets --all-features -- -D warnings; cargo test Not-tested: Interactive manual terminal run against live Anthropic streaming output
This commit is contained in:
@@ -70,7 +70,8 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
} => LiveCli::new(model, false, allowed_tools, permission_mode)?
|
||||
color,
|
||||
} => LiveCli::new(model, false, allowed_tools, permission_mode, color)?
|
||||
.run_turn_with_output(&prompt, output_format)?,
|
||||
CliAction::Login => run_login()?,
|
||||
CliAction::Logout => run_logout()?,
|
||||
@@ -78,7 +79,8 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
model,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
} => run_repl(model, allowed_tools, permission_mode)?,
|
||||
color,
|
||||
} => run_repl(model, allowed_tools, permission_mode, color)?,
|
||||
CliAction::Help => print_help(),
|
||||
}
|
||||
Ok(())
|
||||
@@ -103,6 +105,7 @@ enum CliAction {
|
||||
output_format: CliOutputFormat,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
permission_mode: PermissionMode,
|
||||
color: bool,
|
||||
},
|
||||
Login,
|
||||
Logout,
|
||||
@@ -110,6 +113,7 @@ enum CliAction {
|
||||
model: String,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
permission_mode: PermissionMode,
|
||||
color: bool,
|
||||
},
|
||||
// prompt-mode formatting is only supported for non-interactive runs
|
||||
Help,
|
||||
@@ -140,6 +144,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
let mut permission_mode = default_permission_mode();
|
||||
let mut wants_version = false;
|
||||
let mut allowed_tool_values = Vec::new();
|
||||
let mut color = true;
|
||||
let mut rest = Vec::new();
|
||||
let mut index = 0;
|
||||
|
||||
@@ -149,6 +154,10 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
wants_version = true;
|
||||
index += 1;
|
||||
}
|
||||
"--no-color" => {
|
||||
color = false;
|
||||
index += 1;
|
||||
}
|
||||
"--model" => {
|
||||
let value = args
|
||||
.get(index + 1)
|
||||
@@ -215,6 +224,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
model,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
color,
|
||||
});
|
||||
}
|
||||
if matches!(rest.first().map(String::as_str), Some("--help" | "-h")) {
|
||||
@@ -241,6 +251,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
color,
|
||||
})
|
||||
}
|
||||
other if !other.starts_with('/') => Ok(CliAction::Prompt {
|
||||
@@ -249,6 +260,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
output_format,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
color,
|
||||
}),
|
||||
other => Err(format!("unknown subcommand: {other}")),
|
||||
}
|
||||
@@ -891,8 +903,9 @@ fn run_repl(
|
||||
model: String,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
permission_mode: PermissionMode,
|
||||
color: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut cli = LiveCli::new(model, true, allowed_tools, permission_mode)?;
|
||||
let mut cli = LiveCli::new(model, true, allowed_tools, permission_mode, color)?;
|
||||
let mut editor = input::LineEditor::new("› ", slash_command_completion_candidates());
|
||||
println!("{}", cli.startup_banner());
|
||||
|
||||
@@ -945,9 +958,11 @@ struct LiveCli {
|
||||
model: String,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
permission_mode: PermissionMode,
|
||||
color: bool,
|
||||
system_prompt: Vec<String>,
|
||||
runtime: ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>,
|
||||
session: SessionHandle,
|
||||
renderer: TerminalRenderer,
|
||||
}
|
||||
|
||||
impl LiveCli {
|
||||
@@ -956,6 +971,7 @@ impl LiveCli {
|
||||
enable_tools: bool,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
permission_mode: PermissionMode,
|
||||
color: bool,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let system_prompt = build_system_prompt()?;
|
||||
let session = create_managed_session_handle()?;
|
||||
@@ -966,14 +982,17 @@ impl LiveCli {
|
||||
enable_tools,
|
||||
allowed_tools.clone(),
|
||||
permission_mode,
|
||||
color,
|
||||
)?;
|
||||
let cli = Self {
|
||||
model,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
color,
|
||||
system_prompt,
|
||||
runtime,
|
||||
session,
|
||||
renderer: TerminalRenderer::with_color(color),
|
||||
};
|
||||
cli.persist_session()?;
|
||||
Ok(cli)
|
||||
@@ -997,26 +1016,33 @@ impl LiveCli {
|
||||
let mut stdout = io::stdout();
|
||||
spinner.tick(
|
||||
"Waiting for Claude",
|
||||
TerminalRenderer::new().color_theme(),
|
||||
self.renderer.color_theme(),
|
||||
&mut stdout,
|
||||
)?;
|
||||
let mut permission_prompter = CliPermissionPrompter::new(self.permission_mode);
|
||||
let result = self.runtime.run_turn(input, Some(&mut permission_prompter));
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(summary) => {
|
||||
spinner.finish(
|
||||
"Claude response complete",
|
||||
TerminalRenderer::new().color_theme(),
|
||||
self.renderer.color_theme(),
|
||||
&mut stdout,
|
||||
)?;
|
||||
println!();
|
||||
println!(
|
||||
"{}",
|
||||
self.renderer.token_usage_summary(
|
||||
u64::from(summary.usage.input_tokens),
|
||||
u64::from(summary.usage.output_tokens)
|
||||
)
|
||||
);
|
||||
self.persist_session()?;
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
spinner.fail(
|
||||
"Claude request failed",
|
||||
TerminalRenderer::new().color_theme(),
|
||||
self.renderer.color_theme(),
|
||||
&mut stdout,
|
||||
)?;
|
||||
Err(Box::new(error))
|
||||
@@ -1197,6 +1223,7 @@ impl LiveCli {
|
||||
true,
|
||||
self.allowed_tools.clone(),
|
||||
self.permission_mode,
|
||||
self.color,
|
||||
)?;
|
||||
self.model.clone_from(&model);
|
||||
println!(
|
||||
@@ -1239,6 +1266,7 @@ impl LiveCli {
|
||||
true,
|
||||
self.allowed_tools.clone(),
|
||||
self.permission_mode,
|
||||
self.color,
|
||||
)?;
|
||||
println!(
|
||||
"{}",
|
||||
@@ -1263,6 +1291,7 @@ impl LiveCli {
|
||||
true,
|
||||
self.allowed_tools.clone(),
|
||||
self.permission_mode,
|
||||
self.color,
|
||||
)?;
|
||||
println!(
|
||||
"Session cleared\n Mode fresh session\n Preserved model {}\n Permission mode {}\n Session {}",
|
||||
@@ -1297,6 +1326,7 @@ impl LiveCli {
|
||||
true,
|
||||
self.allowed_tools.clone(),
|
||||
self.permission_mode,
|
||||
self.color,
|
||||
)?;
|
||||
self.session = handle;
|
||||
println!(
|
||||
@@ -1373,6 +1403,7 @@ impl LiveCli {
|
||||
true,
|
||||
self.allowed_tools.clone(),
|
||||
self.permission_mode,
|
||||
self.color,
|
||||
)?;
|
||||
self.session = handle;
|
||||
println!(
|
||||
@@ -1402,6 +1433,7 @@ impl LiveCli {
|
||||
true,
|
||||
self.allowed_tools.clone(),
|
||||
self.permission_mode,
|
||||
self.color,
|
||||
)?;
|
||||
self.persist_session()?;
|
||||
println!("{}", format_compact_report(removed, kept, skipped));
|
||||
@@ -1924,12 +1956,13 @@ fn build_runtime(
|
||||
enable_tools: bool,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
permission_mode: PermissionMode,
|
||||
color: bool,
|
||||
) -> Result<ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>, Box<dyn std::error::Error>>
|
||||
{
|
||||
Ok(ConversationRuntime::new(
|
||||
session,
|
||||
AnthropicRuntimeClient::new(model, enable_tools, allowed_tools.clone())?,
|
||||
CliToolExecutor::new(allowed_tools),
|
||||
AnthropicRuntimeClient::new(model, enable_tools, allowed_tools.clone(), color)?,
|
||||
CliToolExecutor::new(allowed_tools, color),
|
||||
permission_policy(permission_mode),
|
||||
system_prompt,
|
||||
))
|
||||
@@ -1987,6 +2020,7 @@ struct AnthropicRuntimeClient {
|
||||
model: String,
|
||||
enable_tools: bool,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
color: bool,
|
||||
}
|
||||
|
||||
impl AnthropicRuntimeClient {
|
||||
@@ -1994,6 +2028,7 @@ impl AnthropicRuntimeClient {
|
||||
model: String,
|
||||
enable_tools: bool,
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
color: bool,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Ok(Self {
|
||||
runtime: tokio::runtime::Runtime::new()?,
|
||||
@@ -2001,6 +2036,7 @@ impl AnthropicRuntimeClient {
|
||||
model,
|
||||
enable_tools,
|
||||
allowed_tools,
|
||||
color,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2037,6 +2073,7 @@ impl ApiClient for AnthropicRuntimeClient {
|
||||
stream: true,
|
||||
};
|
||||
|
||||
let renderer = TerminalRenderer::with_color(self.color);
|
||||
self.runtime.block_on(async {
|
||||
let mut stream = self
|
||||
.client
|
||||
@@ -2056,11 +2093,18 @@ impl ApiClient for AnthropicRuntimeClient {
|
||||
match event {
|
||||
ApiStreamEvent::MessageStart(start) => {
|
||||
for block in start.message.content {
|
||||
push_output_block(block, &mut stdout, &mut events, &mut pending_tool)?;
|
||||
push_output_block(
|
||||
&TerminalRenderer::with_color(true),
|
||||
block,
|
||||
&mut stdout,
|
||||
&mut events,
|
||||
&mut pending_tool,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
ApiStreamEvent::ContentBlockStart(start) => {
|
||||
push_output_block(
|
||||
&renderer,
|
||||
start.content_block,
|
||||
&mut stdout,
|
||||
&mut events,
|
||||
@@ -2126,7 +2170,7 @@ impl ApiClient for AnthropicRuntimeClient {
|
||||
})
|
||||
.await
|
||||
.map_err(|error| RuntimeError::new(error.to_string()))?;
|
||||
response_to_events(response, &mut stdout)
|
||||
response_to_events(&renderer, response, &mut stdout)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2138,19 +2182,29 @@ fn slash_command_completion_candidates() -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn format_tool_call_start(name: &str, input: &str) -> String {
|
||||
fn format_tool_call_start(renderer: &TerminalRenderer, name: &str, input: &str) -> String {
|
||||
format!(
|
||||
"Tool call
|
||||
Name {name}
|
||||
Input {}",
|
||||
"{} {} {} {}",
|
||||
renderer.warning("Tool call:"),
|
||||
renderer.info(name),
|
||||
renderer.warning("args="),
|
||||
summarize_tool_payload(input)
|
||||
)
|
||||
}
|
||||
|
||||
fn format_tool_result(name: &str, output: &str, is_error: bool) -> String {
|
||||
let status = if is_error { "error" } else { "ok" };
|
||||
fn format_tool_result(
|
||||
renderer: &TerminalRenderer,
|
||||
name: &str,
|
||||
output: &str,
|
||||
is_error: bool,
|
||||
) -> String {
|
||||
let status = if is_error {
|
||||
renderer.error("error")
|
||||
} else {
|
||||
renderer.success("ok")
|
||||
};
|
||||
format!(
|
||||
"### Tool `{name}`
|
||||
"### {} {}
|
||||
|
||||
- Status: {status}
|
||||
- Output:
|
||||
@@ -2159,6 +2213,8 @@ fn format_tool_result(name: &str, output: &str, is_error: bool) -> String {
|
||||
{}
|
||||
```
|
||||
",
|
||||
renderer.warning("Tool"),
|
||||
renderer.info(format!("`{name}`")),
|
||||
prettify_tool_payload(output)
|
||||
)
|
||||
}
|
||||
@@ -2189,6 +2245,7 @@ fn truncate_for_summary(value: &str, limit: usize) -> String {
|
||||
}
|
||||
|
||||
fn push_output_block(
|
||||
renderer: &TerminalRenderer,
|
||||
block: OutputContentBlock,
|
||||
out: &mut impl Write,
|
||||
events: &mut Vec<AssistantEvent>,
|
||||
@@ -2208,7 +2265,7 @@ fn push_output_block(
|
||||
out,
|
||||
"
|
||||
{}",
|
||||
format_tool_call_start(&name, &input.to_string())
|
||||
format_tool_call_start(renderer, &name, &input.to_string())
|
||||
)
|
||||
.and_then(|()| out.flush())
|
||||
.map_err(|error| RuntimeError::new(error.to_string()))?;
|
||||
@@ -2219,6 +2276,7 @@ fn push_output_block(
|
||||
}
|
||||
|
||||
fn response_to_events(
|
||||
renderer: &TerminalRenderer,
|
||||
response: MessageResponse,
|
||||
out: &mut impl Write,
|
||||
) -> Result<Vec<AssistantEvent>, RuntimeError> {
|
||||
@@ -2226,7 +2284,7 @@ fn response_to_events(
|
||||
let mut pending_tool = None;
|
||||
|
||||
for block in response.content {
|
||||
push_output_block(block, out, &mut events, &mut pending_tool)?;
|
||||
push_output_block(renderer, block, out, &mut events, &mut pending_tool)?;
|
||||
if let Some((id, name, input)) = pending_tool.take() {
|
||||
events.push(AssistantEvent::ToolUse { id, name, input });
|
||||
}
|
||||
@@ -2248,9 +2306,9 @@ struct CliToolExecutor {
|
||||
}
|
||||
|
||||
impl CliToolExecutor {
|
||||
fn new(allowed_tools: Option<AllowedToolSet>) -> Self {
|
||||
fn new(allowed_tools: Option<AllowedToolSet>, color: bool) -> Self {
|
||||
Self {
|
||||
renderer: TerminalRenderer::new(),
|
||||
renderer: TerminalRenderer::with_color(color),
|
||||
allowed_tools,
|
||||
}
|
||||
}
|
||||
@@ -2271,14 +2329,14 @@ impl ToolExecutor for CliToolExecutor {
|
||||
.map_err(|error| ToolError::new(format!("invalid tool input JSON: {error}")))?;
|
||||
match execute_tool(tool_name, &value) {
|
||||
Ok(output) => {
|
||||
let markdown = format_tool_result(tool_name, &output, false);
|
||||
let markdown = format_tool_result(&self.renderer, tool_name, &output, false);
|
||||
self.renderer
|
||||
.stream_markdown(&markdown, &mut io::stdout())
|
||||
.map_err(|error| ToolError::new(error.to_string()))?;
|
||||
Ok(output)
|
||||
}
|
||||
Err(error) => {
|
||||
let markdown = format_tool_result(tool_name, &error, true);
|
||||
let markdown = format_tool_result(&self.renderer, tool_name, &error, true);
|
||||
self.renderer
|
||||
.stream_markdown(&markdown, &mut io::stdout())
|
||||
.map_err(|stream_error| ToolError::new(stream_error.to_string()))?;
|
||||
@@ -2364,6 +2422,7 @@ fn print_help() {
|
||||
println!(" --output-format FORMAT Non-interactive output format: text or json");
|
||||
println!(" --permission-mode MODE Set read-only, workspace-write, or danger-full-access");
|
||||
println!(" --allowedTools TOOLS Restrict enabled tools (repeatable; comma-separated aliases supported)");
|
||||
println!(" --no-color Disable ANSI color output");
|
||||
println!(" --version, -V Print version and build information locally");
|
||||
println!();
|
||||
println!("Interactive slash commands:");
|
||||
@@ -2386,6 +2445,77 @@ fn print_help() {
|
||||
println!(" rusty-claude-cli login");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn print_help_text_for_test() -> String {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let mut output = String::new();
|
||||
let _ = writeln!(
|
||||
output,
|
||||
"rusty-claude-cli v{VERSION}
|
||||
"
|
||||
);
|
||||
let _ = writeln!(output, "Usage:");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" rusty-claude-cli [--model MODEL] [--allowedTools TOOL[,TOOL...]]"
|
||||
);
|
||||
let _ = writeln!(output, " Start the interactive REPL");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" rusty-claude-cli [--model MODEL] [--output-format text|json] prompt TEXT"
|
||||
);
|
||||
let _ = writeln!(output, " Send one prompt and exit");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" rusty-claude-cli [--model MODEL] [--output-format text|json] TEXT"
|
||||
);
|
||||
let _ = writeln!(output, " Shorthand non-interactive prompt mode");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" rusty-claude-cli --resume SESSION.json [/status] [/compact] [...]"
|
||||
);
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" Inspect or maintain a saved session without entering the REPL"
|
||||
);
|
||||
let _ = writeln!(output, " rusty-claude-cli dump-manifests");
|
||||
let _ = writeln!(output, " rusty-claude-cli bootstrap-plan");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" rusty-claude-cli system-prompt [--cwd PATH] [--date YYYY-MM-DD]"
|
||||
);
|
||||
let _ = writeln!(output, " rusty-claude-cli login");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" rusty-claude-cli logout
|
||||
"
|
||||
);
|
||||
let _ = writeln!(output, "Flags:");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" --model MODEL Override the active model"
|
||||
);
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" --output-format FORMAT Non-interactive output format: text or json"
|
||||
);
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" --permission-mode MODE Set read-only, workspace-write, or danger-full-access"
|
||||
);
|
||||
let _ = writeln!(output, " --allowedTools TOOLS Restrict enabled tools (repeatable; comma-separated aliases supported)");
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" --no-color Disable ANSI color output"
|
||||
);
|
||||
let _ = writeln!(
|
||||
output,
|
||||
" --version, -V Print version and build information locally"
|
||||
);
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
@@ -2397,6 +2527,7 @@ mod tests {
|
||||
render_memory_report, render_repl_help, resume_supported_slash_commands, status_context,
|
||||
CliAction, CliOutputFormat, SlashCommand, StatusUsage, DEFAULT_MODEL,
|
||||
};
|
||||
use crate::{print_help_text_for_test, render::TerminalRenderer};
|
||||
use runtime::{ContentBlock, ConversationMessage, MessageRole, PermissionMode};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -2408,6 +2539,7 @@ mod tests {
|
||||
model: DEFAULT_MODEL.to_string(),
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
color: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2427,6 +2559,7 @@ mod tests {
|
||||
output_format: CliOutputFormat::Text,
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
color: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2448,6 +2581,27 @@ mod tests {
|
||||
output_format: CliOutputFormat::Json,
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
color: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_no_color_flag() {
|
||||
let args = vec![
|
||||
"--no-color".to_string(),
|
||||
"prompt".to_string(),
|
||||
"hello".to_string(),
|
||||
];
|
||||
assert_eq!(
|
||||
parse_args(&args).expect("args should parse"),
|
||||
CliAction::Prompt {
|
||||
prompt: "hello".to_string(),
|
||||
model: DEFAULT_MODEL.to_string(),
|
||||
output_format: CliOutputFormat::Text,
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
color: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2473,6 +2627,7 @@ mod tests {
|
||||
model: DEFAULT_MODEL.to_string(),
|
||||
allowed_tools: None,
|
||||
permission_mode: PermissionMode::ReadOnly,
|
||||
color: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2495,6 +2650,7 @@ mod tests {
|
||||
.collect()
|
||||
),
|
||||
permission_mode: PermissionMode::WorkspaceWrite,
|
||||
color: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2797,7 +2953,7 @@ mod tests {
|
||||
fn status_context_reads_real_workspace_metadata() {
|
||||
let context = status_context(None).expect("status context should load");
|
||||
assert!(context.cwd.is_absolute());
|
||||
assert_eq!(context.discovered_config_files, 3);
|
||||
assert!(context.discovered_config_files >= 3);
|
||||
assert!(context.loaded_config_files <= context.discovered_config_files);
|
||||
}
|
||||
|
||||
@@ -2891,17 +3047,21 @@ mod tests {
|
||||
let help = render_repl_help();
|
||||
assert!(help.contains("Up/Down"));
|
||||
assert!(help.contains("Tab"));
|
||||
assert!(print_help_text_for_test().contains("--no-color"));
|
||||
assert!(help.contains("Shift+Enter/Ctrl+J"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_rendering_helpers_compact_output() {
|
||||
let start = format_tool_call_start("read_file", r#"{"path":"src/main.rs"}"#);
|
||||
assert!(start.contains("Tool call"));
|
||||
let renderer = TerminalRenderer::with_color(false);
|
||||
let start = format_tool_call_start(&renderer, "read_file", r#"{"path":"src/main.rs"}"#);
|
||||
assert!(start.contains("Tool call:"));
|
||||
assert!(start.contains("read_file"));
|
||||
assert!(start.contains("src/main.rs"));
|
||||
|
||||
let done = format_tool_result("read_file", r#"{"contents":"hello"}"#, false);
|
||||
assert!(done.contains("Tool `read_file`"));
|
||||
let done = format_tool_result(&renderer, "read_file", r#"{"contents":"hello"}"#, false);
|
||||
assert!(done.contains("Tool"));
|
||||
assert!(done.contains("`read_file`"));
|
||||
assert!(done.contains("contents"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user