Skip to content

Commit a8beca1

Browse files
fix: ultraworkers#136 support --output-format json with --compact flag
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 21adae9 commit a8beca1

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

rust/crates/rusty-claude-cli/src/main.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3870,6 +3870,7 @@ impl LiveCli {
38703870
compact: bool,
38713871
) -> Result<(), Box<dyn std::error::Error>> {
38723872
match output_format {
3873+
CliOutputFormat::Json if compact => self.run_prompt_compact_json(input),
38733874
CliOutputFormat::Text if compact => self.run_prompt_compact(input),
38743875
CliOutputFormat::Text => self.run_turn(input),
38753876
CliOutputFormat::Json => self.run_prompt_json(input),
@@ -3889,6 +3890,32 @@ impl LiveCli {
38893890
Ok(())
38903891
}
38913892

3893+
3894+
fn run_prompt_compact_json(&mut self, input: &str) -> Result<(), Box<dyn std::error::Error>> {
3895+
let (mut runtime, hook_abort_monitor) = self.prepare_turn_runtime(false)?;
3896+
let mut permission_prompter = CliPermissionPrompter::new(self.permission_mode);
3897+
let result = runtime.run_turn(input, Some(&mut permission_prompter));
3898+
hook_abort_monitor.stop();
3899+
let summary = result?;
3900+
self.replace_runtime(runtime)?;
3901+
self.persist_session()?;
3902+
println!(
3903+
"{}",
3904+
json!({
3905+
"message": final_assistant_text(&summary),
3906+
"compact": true,
3907+
"model": self.model,
3908+
"usage": {
3909+
"input_tokens": summary.usage.input_tokens,
3910+
"output_tokens": summary.usage.output_tokens,
3911+
"cache_creation_input_tokens": summary.usage.cache_creation_input_tokens,
3912+
"cache_read_input_tokens": summary.usage.cache_read_input_tokens,
3913+
},
3914+
})
3915+
);
3916+
Ok(())
3917+
}
3918+
38923919
fn run_prompt_json(&mut self, input: &str) -> Result<(), Box<dyn std::error::Error>> {
38933920
let (mut runtime, hook_abort_monitor) = self.prepare_turn_runtime(false)?;
38943921
let mut permission_prompter = CliPermissionPrompter::new(self.permission_mode);

rust/crates/rusty-claude-cli/tests/compact_output.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
55
use std::time::{SystemTime, UNIX_EPOCH};
66

77
use mock_anthropic_service::{MockAnthropicService, SCENARIO_PREFIX};
8+
use serde_json::Value;
89

910
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
1011

@@ -125,6 +126,60 @@ fn compact_flag_streaming_text_only_emits_final_message_text() {
125126
fs::remove_dir_all(&workspace).expect("workspace cleanup should succeed");
126127
}
127128

129+
#[test]
130+
fn compact_flag_with_json_output_emits_structured_json() {
131+
let runtime = tokio::runtime::Runtime::new().expect("tokio runtime should build");
132+
let server = runtime
133+
.block_on(MockAnthropicService::spawn())
134+
.expect("mock service should start");
135+
let base_url = server.base_url();
136+
137+
let workspace = unique_temp_dir("compact-json");
138+
let config_home = workspace.join("config-home");
139+
let home = workspace.join("home");
140+
fs::create_dir_all(&workspace).expect("workspace should exist");
141+
fs::create_dir_all(&config_home).expect("config home should exist");
142+
fs::create_dir_all(&home).expect("home should exist");
143+
144+
let prompt = format!("{SCENARIO_PREFIX}streaming_text");
145+
let output = run_claw(
146+
&workspace,
147+
&config_home,
148+
&home,
149+
&base_url,
150+
&[
151+
"--model",
152+
"sonnet",
153+
"--permission-mode",
154+
"read-only",
155+
"--output-format",
156+
"json",
157+
"--compact",
158+
&prompt,
159+
],
160+
);
161+
162+
assert!(
163+
output.status.success(),
164+
"compact json run should succeed
165+
stdout:
166+
{}
167+
168+
stderr:
169+
{}",
170+
String::from_utf8_lossy(&output.stdout),
171+
String::from_utf8_lossy(&output.stderr),
172+
);
173+
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
174+
let parsed: Value = serde_json::from_str(&stdout).expect("compact json stdout should parse");
175+
assert_eq!(parsed["message"], "Mock streaming says hello from the parity harness.");
176+
assert_eq!(parsed["compact"], true);
177+
assert_eq!(parsed["model"], "claude-sonnet-4-6");
178+
assert!(parsed["usage"].is_object());
179+
180+
fs::remove_dir_all(&workspace).expect("workspace cleanup should succeed");
181+
}
182+
128183
fn run_claw(
129184
cwd: &std::path::Path,
130185
config_home: &std::path::Path,

0 commit comments

Comments
 (0)