Take rustfmt's defaults
The code was hand-formatted -- close to rustfmt's output but not it, mostly in keeping chains and call arguments on one line where the formatter would break them. That is a per-line decision every future change has to make again, and reproducing it would mean a config whose only job is to preserve how the code already looks. So this is `cargo fmt` at its defaults, with no rustfmt.toml, which is where the sibling dev-updater checkout already sits: it is clean at the defaults today, so the two repos now agree on layout without either of them configuring it. Formatting only -- no behaviour, no renames, nothing reordered. Verified after: cargo test (35 pass), cargo clippy --all-targets clean, cargo fmt --check clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
f014094fcd
commit
c12ab7f098
13 files changed
+557
-190
No files matched your search
+225
-83
@@ -173,11 +173,17 @@ impl ClaudeDriver {
|
||||
let _ = sink.send(Event::Error {
|
||||
message: format!(
|
||||
"{label} exited with {status}{}",
|
||||
if detail.is_empty() { String::new() } else { format!(": {detail}") }
|
||||
if detail.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(": {detail}")
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
let _ = sink.send(Event::Status { state: SessionStatus::Exited });
|
||||
let _ = sink.send(Event::Status {
|
||||
state: SessionStatus::Exited,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,7 +228,9 @@ impl Driver for ClaudeDriver {
|
||||
}
|
||||
// Sent mid-turn this queues for injection at the next tool
|
||||
// boundary; sent while idle it starts a turn.
|
||||
let _ = self.sink.send(Event::Status { state: SessionStatus::Running });
|
||||
let _ = self.sink.send(Event::Status {
|
||||
state: SessionStatus::Running,
|
||||
});
|
||||
self.send_line(
|
||||
json!({"type": "user", "message": {"role": "user", "content": content}}).to_string(),
|
||||
);
|
||||
@@ -235,7 +243,9 @@ impl Driver for ClaudeDriver {
|
||||
};
|
||||
match response {
|
||||
AnswerOutcome::Respond(control_response) => {
|
||||
let _ = self.sink.send(Event::Status { state: SessionStatus::Running });
|
||||
let _ = self.sink.send(Event::Status {
|
||||
state: SessionStatus::Running,
|
||||
});
|
||||
self.send_line(control_response.to_string());
|
||||
}
|
||||
// A multi-question AskUserQuestion still waiting on the rest.
|
||||
@@ -288,7 +298,10 @@ async fn read_stdout(
|
||||
let mut lines = BufReader::new(stdout).lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let Ok(message) = serde_json::from_str::<Value>(&line) else {
|
||||
tracing::warn!("unparseable claude output line: {}", &line[..line.len().min(200)]);
|
||||
tracing::warn!(
|
||||
"unparseable claude output line: {}",
|
||||
&line[..line.len().min(200)]
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let (events, new_session_id) = {
|
||||
@@ -329,7 +342,10 @@ fn write_resume_token(session_dir: &Path, session_id: &str) {
|
||||
fn attachment_block(session_dir: &Path, id: &str) -> Result<Value> {
|
||||
// Ids are server-generated hex (see routes::upload_attachment); the
|
||||
// check keeps a crafted "id" from naming an arbitrary file.
|
||||
if !id.chars().all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-') {
|
||||
if !id
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
|
||||
{
|
||||
anyhow::bail!("invalid attachment id");
|
||||
}
|
||||
let path = session_dir.join("attachments").join(id);
|
||||
@@ -380,13 +396,20 @@ struct Translator {
|
||||
|
||||
impl Translator {
|
||||
fn new(session_dir: PathBuf) -> Self {
|
||||
Self { session_id: None, pending: HashMap::new(), session_dir }
|
||||
Self {
|
||||
session_id: None,
|
||||
pending: HashMap::new(),
|
||||
session_dir,
|
||||
}
|
||||
}
|
||||
fn translate(&mut self, message: &Value) -> Vec<Event> {
|
||||
// Events from subagents (Task tool internals) carry a
|
||||
// parent_tool_use_id; the transcript shows the Task tool's own
|
||||
// start/end instead of every nested step.
|
||||
if message.get("parent_tool_use_id").is_some_and(|id| !id.is_null()) {
|
||||
if message
|
||||
.get("parent_tool_use_id")
|
||||
.is_some_and(|id| !id.is_null())
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
match message.get("type").and_then(Value::as_str) {
|
||||
@@ -405,18 +428,33 @@ impl Translator {
|
||||
Some("control_response") => {
|
||||
let response = &message["response"];
|
||||
if response.get("subtype").and_then(Value::as_str) == Some("error") {
|
||||
let error = response.get("error").and_then(Value::as_str).unwrap_or("unknown");
|
||||
vec![Event::Error { message: format!("claude rejected a request: {error}") }]
|
||||
let error = response
|
||||
.get("error")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown");
|
||||
vec![Event::Error {
|
||||
message: format!("claude rejected a request: {error}"),
|
||||
}]
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
Some("result") => {
|
||||
let usage = &message["usage"];
|
||||
let tokens = usage.get("input_tokens").and_then(Value::as_u64).unwrap_or(0)
|
||||
+ usage.get("output_tokens").and_then(Value::as_u64).unwrap_or(0);
|
||||
let tokens = usage
|
||||
.get("input_tokens")
|
||||
.and_then(Value::as_u64)
|
||||
.unwrap_or(0)
|
||||
+ usage
|
||||
.get("output_tokens")
|
||||
.and_then(Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let mut events = Vec::new();
|
||||
if message.get("is_error").and_then(Value::as_bool).unwrap_or(false) {
|
||||
if message
|
||||
.get("is_error")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
events.push(Event::Error {
|
||||
message: message
|
||||
.get("result")
|
||||
@@ -428,7 +466,9 @@ impl Translator {
|
||||
if tokens > 0 {
|
||||
events.push(Event::UsageDelta { tokens });
|
||||
}
|
||||
events.push(Event::Status { state: SessionStatus::Idle });
|
||||
events.push(Event::Status {
|
||||
state: SessionStatus::Idle,
|
||||
});
|
||||
events
|
||||
}
|
||||
_ => Vec::new(),
|
||||
@@ -444,7 +484,9 @@ impl Translator {
|
||||
&& event["delta"].get("type").and_then(Value::as_str) == Some("text_delta")
|
||||
&& let Some(text) = delta.as_str()
|
||||
{
|
||||
return vec![Event::AssistantText { delta: text.to_string() }];
|
||||
return vec![Event::AssistantText {
|
||||
delta: text.to_string(),
|
||||
}];
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
@@ -457,8 +499,16 @@ impl Translator {
|
||||
.iter()
|
||||
.filter(|block| block.get("type").and_then(Value::as_str) == Some("tool_use"))
|
||||
.map(|block| Event::ToolStart {
|
||||
id: block.get("id").and_then(Value::as_str).unwrap_or_default().to_string(),
|
||||
tool: block.get("name").and_then(Value::as_str).unwrap_or_default().to_string(),
|
||||
id: block
|
||||
.get("id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
tool: block
|
||||
.get("name")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
input: block.get("input").cloned().unwrap_or(Value::Null),
|
||||
})
|
||||
.collect()
|
||||
@@ -469,9 +519,15 @@ impl Translator {
|
||||
if request.get("subtype").and_then(Value::as_str) != Some("can_use_tool") {
|
||||
return Vec::new();
|
||||
}
|
||||
let request_id =
|
||||
message.get("request_id").and_then(Value::as_str).unwrap_or_default().to_string();
|
||||
let tool_name = request.get("tool_name").and_then(Value::as_str).unwrap_or("a tool");
|
||||
let request_id = message
|
||||
.get("request_id")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let tool_name = request
|
||||
.get("tool_name")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("a tool");
|
||||
let input = request.get("input").cloned().unwrap_or(Value::Null);
|
||||
|
||||
let mut events = Vec::new();
|
||||
@@ -515,9 +571,16 @@ impl Translator {
|
||||
}
|
||||
self.pending.insert(
|
||||
request_id.clone(),
|
||||
PendingRequest { request_id, input, questions, answers: HashMap::new() },
|
||||
PendingRequest {
|
||||
request_id,
|
||||
input,
|
||||
questions,
|
||||
answers: HashMap::new(),
|
||||
},
|
||||
);
|
||||
events.push(Event::Status { state: SessionStatus::AwaitingInput });
|
||||
events.push(Event::Status {
|
||||
state: SessionStatus::AwaitingInput,
|
||||
});
|
||||
events
|
||||
}
|
||||
|
||||
@@ -610,7 +673,9 @@ impl Translator {
|
||||
let source = part.get("source")?;
|
||||
let data = source.get("data")?.as_str()?;
|
||||
use base64::Engine;
|
||||
let bytes = base64::engine::general_purpose::STANDARD.decode(data).ok()?;
|
||||
let bytes = base64::engine::general_purpose::STANDARD
|
||||
.decode(data)
|
||||
.ok()?;
|
||||
// Screenshots are the overwhelming case, and they are PNG; an
|
||||
// unrecognized type is more likely a dialect change than a JPEG.
|
||||
let extension = source
|
||||
@@ -620,10 +685,9 @@ impl Translator {
|
||||
.unwrap_or("png");
|
||||
let name = format!("{}.{extension}", super::random_hex());
|
||||
let dir = self.session_dir.join("files");
|
||||
if let Err(err) =
|
||||
crate::private::create_dir(&dir)
|
||||
.map_err(std::io::Error::other)
|
||||
.and_then(|()| std::fs::write(dir.join(&name), bytes))
|
||||
if let Err(err) = crate::private::create_dir(&dir)
|
||||
.map_err(std::io::Error::other)
|
||||
.and_then(|()| std::fs::write(dir.join(&name), bytes))
|
||||
{
|
||||
tracing::error!("couldn't save produced image: {err}");
|
||||
return None;
|
||||
@@ -649,7 +713,9 @@ mod tests {
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[r#"{"type":"system","subtype":"init","cwd":"/x","session_id":"5ecf21da-d53f","tools":[],"model":"claude-haiku-4-5-20251001"}"#],
|
||||
&[
|
||||
r#"{"type":"system","subtype":"init","cwd":"/x","session_id":"5ecf21da-d53f","tools":[],"model":"claude-haiku-4-5-20251001"}"#,
|
||||
],
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
assert_eq!(translator.session_id.as_deref(), Some("5ecf21da-d53f"));
|
||||
@@ -660,39 +726,59 @@ mod tests {
|
||||
// Real lines (trimmed) from the 2.1.237 probe.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Done."}},"session_id":"s","parent_tool_use_id":null}"#,
|
||||
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Done."}]},"parent_tool_use_id":null,"session_id":"s"}"#,
|
||||
r#"{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"hmm"}},"session_id":"s","parent_tool_use_id":null}"#,
|
||||
]);
|
||||
assert_eq!(events, vec![Event::AssistantText { delta: "Done.".to_string() }]);
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Done."}},"session_id":"s","parent_tool_use_id":null}"#,
|
||||
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Done."}]},"parent_tool_use_id":null,"session_id":"s"}"#,
|
||||
r#"{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"hmm"}},"session_id":"s","parent_tool_use_id":null}"#,
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![Event::AssistantText {
|
||||
delta: "Done.".to_string()
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_use_and_result_become_tool_events() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01","name":"Bash","input":{"command":"echo probe-ok"}}]},"parent_tool_use_id":null}"#,
|
||||
r#"{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":"probe-ok","is_error":false}]},"parent_tool_use_id":null}"#,
|
||||
]);
|
||||
assert_eq!(events, vec![
|
||||
Event::ToolStart {
|
||||
id: "toolu_01".to_string(),
|
||||
tool: "Bash".to_string(),
|
||||
input: serde_json::json!({"command": "echo probe-ok"}),
|
||||
},
|
||||
Event::ToolEnd { id: "toolu_01".to_string(), output: "probe-ok".to_string() },
|
||||
]);
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01","name":"Bash","input":{"command":"echo probe-ok"}}]},"parent_tool_use_id":null}"#,
|
||||
r#"{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":"probe-ok","is_error":false}]},"parent_tool_use_id":null}"#,
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![
|
||||
Event::ToolStart {
|
||||
id: "toolu_01".to_string(),
|
||||
tool: "Bash".to_string(),
|
||||
input: serde_json::json!({"command": "echo probe-ok"}),
|
||||
},
|
||||
Event::ToolEnd {
|
||||
id: "toolu_01".to_string(),
|
||||
output: "probe-ok".to_string()
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_events_are_not_duplicated_into_the_transcript() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_02","name":"Bash","input":{}}]},"parent_tool_use_id":"toolu_parent"}"#,
|
||||
]);
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_02","name":"Bash","input":{}}]},"parent_tool_use_id":"toolu_parent"}"#,
|
||||
],
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
|
||||
@@ -700,16 +786,29 @@ mod tests {
|
||||
fn a_permission_request_becomes_an_allow_deny_question() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"control_request","request_id":"req-1","request":{"subtype":"can_use_tool","tool_name":"Bash","input":{"command":"rm -rf /tmp/x"},"tool_use_id":"toolu_03"}}"#,
|
||||
]);
|
||||
let Event::Question { id, prompt, options } = &events[0] else {
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"control_request","request_id":"req-1","request":{"subtype":"can_use_tool","tool_name":"Bash","input":{"command":"rm -rf /tmp/x"},"tool_use_id":"toolu_03"}}"#,
|
||||
],
|
||||
);
|
||||
let Event::Question {
|
||||
id,
|
||||
prompt,
|
||||
options,
|
||||
} = &events[0]
|
||||
else {
|
||||
panic!("expected a question, got {events:?}");
|
||||
};
|
||||
assert_eq!(id, "req-1");
|
||||
assert!(prompt.contains("Bash") && prompt.contains("rm -rf /tmp/x"));
|
||||
assert_eq!(options, &["Allow", "Deny"]);
|
||||
assert_eq!(events[1], Event::Status { state: SessionStatus::AwaitingInput });
|
||||
assert_eq!(
|
||||
events[1],
|
||||
Event::Status {
|
||||
state: SessionStatus::AwaitingInput
|
||||
}
|
||||
);
|
||||
|
||||
// Allowing echoes the input back; the request is then gone.
|
||||
let AnswerOutcome::Respond(response) = translator.answer("req-1", "Allow") else {
|
||||
@@ -721,16 +820,22 @@ mod tests {
|
||||
response["response"]["response"]["updatedInput"]["command"],
|
||||
"rm -rf /tmp/x"
|
||||
);
|
||||
assert!(matches!(translator.answer("req-1", "Allow"), AnswerOutcome::Unknown));
|
||||
assert!(matches!(
|
||||
translator.answer("req-1", "Allow"),
|
||||
AnswerOutcome::Unknown
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denying_a_permission_sends_deny() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
translate_lines(&mut translator, &[
|
||||
r#"{"type":"control_request","request_id":"req-2","request":{"subtype":"can_use_tool","tool_name":"Write","input":{"file_path":"/etc/passwd"}}}"#,
|
||||
]);
|
||||
translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"control_request","request_id":"req-2","request":{"subtype":"can_use_tool","tool_name":"Write","input":{"file_path":"/etc/passwd"}}}"#,
|
||||
],
|
||||
);
|
||||
let AnswerOutcome::Respond(response) = translator.answer("req-2", "Deny") else {
|
||||
panic!("expected a control response");
|
||||
};
|
||||
@@ -743,13 +848,20 @@ mod tests {
|
||||
// updatedInput, keyed by the question text.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"control_request","request_id":"req-3","request":{"subtype":"can_use_tool","tool_name":"AskUserQuestion","input":{"questions":[{"question":"Which color?","header":"Color","options":[{"label":"Red"},{"label":"Blue"}],"multiSelect":false},{"question":"Which size?","header":"Size","options":[{"label":"S"},{"label":"L"}],"multiSelect":false}]},"tool_use_id":"toolu_04","requires_user_interaction":true}}"#,
|
||||
]);
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"control_request","request_id":"req-3","request":{"subtype":"can_use_tool","tool_name":"AskUserQuestion","input":{"questions":[{"question":"Which color?","header":"Color","options":[{"label":"Red"},{"label":"Blue"}],"multiSelect":false},{"question":"Which size?","header":"Size","options":[{"label":"S"},{"label":"L"}],"multiSelect":false}]},"tool_use_id":"toolu_04","requires_user_interaction":true}}"#,
|
||||
],
|
||||
);
|
||||
let questions: Vec<_> = events
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
Event::Question { id, prompt, options } => Some((id.clone(), prompt.clone(), options.clone())),
|
||||
Event::Question {
|
||||
id,
|
||||
prompt,
|
||||
options,
|
||||
} => Some((id.clone(), prompt.clone(), options.clone())),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
@@ -760,7 +872,10 @@ mod tests {
|
||||
|
||||
// First answer alone isn't enough; the response goes out when the
|
||||
// last sub-question is answered, with all answers aboard.
|
||||
assert!(matches!(translator.answer("req-3#0", "Blue"), AnswerOutcome::Pending));
|
||||
assert!(matches!(
|
||||
translator.answer("req-3#0", "Blue"),
|
||||
AnswerOutcome::Pending
|
||||
));
|
||||
let AnswerOutcome::Respond(response) = translator.answer("req-3#1", "L") else {
|
||||
panic!("expected a control response");
|
||||
};
|
||||
@@ -787,44 +902,71 @@ mod tests {
|
||||
assert!(image.ends_with(".png"));
|
||||
let saved = dir.path().join("files").join(image);
|
||||
assert!(saved.is_file(), "image not saved at {}", saved.display());
|
||||
assert_eq!(events[1], Event::ToolEnd {
|
||||
id: "toolu_05".to_string(),
|
||||
output: "took a screenshot".to_string(),
|
||||
});
|
||||
assert_eq!(
|
||||
events[1],
|
||||
Event::ToolEnd {
|
||||
id: "toolu_05".to_string(),
|
||||
output: "took a screenshot".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_turn_result_reports_usage_and_returns_to_idle() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"result","subtype":"success","is_error":false,"num_turns":2,"stop_reason":"end_turn","session_id":"s","total_cost_usd":0.0149,"usage":{"input_tokens":18,"output_tokens":164}}"#,
|
||||
]);
|
||||
assert_eq!(events, vec![
|
||||
Event::UsageDelta { tokens: 182 },
|
||||
Event::Status { state: SessionStatus::Idle },
|
||||
]);
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"result","subtype":"success","is_error":false,"num_turns":2,"stop_reason":"end_turn","session_id":"s","total_cost_usd":0.0149,"usage":{"input_tokens":18,"output_tokens":164}}"#,
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![
|
||||
Event::UsageDelta { tokens: 182 },
|
||||
Event::Status {
|
||||
state: SessionStatus::Idle
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_error_result_surfaces_the_message() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"result","subtype":"error_during_execution","is_error":true,"result":"something broke","usage":{}}"#,
|
||||
]);
|
||||
assert_eq!(events[0], Event::Error { message: "something broke".to_string() });
|
||||
assert_eq!(*events.last().unwrap(), Event::Status { state: SessionStatus::Idle });
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"result","subtype":"error_during_execution","is_error":true,"result":"something broke","usage":{}}"#,
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
events[0],
|
||||
Event::Error {
|
||||
message: "something broke".to_string()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
*events.last().unwrap(),
|
||||
Event::Status {
|
||||
state: SessionStatus::Idle
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replayed_and_synthetic_user_text_is_skipped() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let mut translator = Translator::new(dir.path().to_path_buf());
|
||||
let events = translate_lines(&mut translator, &[
|
||||
r#"{"type":"user","message":{"role":"user","content":[{"type":"text","text":"hi"}]},"isReplay":true,"parent_tool_use_id":null}"#,
|
||||
r#"{"type":"user","message":{"role":"user","content":[{"type":"text","text":"[continue]"}]},"isSynthetic":true,"parent_tool_use_id":null}"#,
|
||||
]);
|
||||
let events = translate_lines(
|
||||
&mut translator,
|
||||
&[
|
||||
r#"{"type":"user","message":{"role":"user","content":[{"type":"text","text":"hi"}]},"isReplay":true,"parent_tool_use_id":null}"#,
|
||||
r#"{"type":"user","message":{"role":"user","content":[{"type":"text","text":"[continue]"}]},"isSynthetic":true,"parent_tool_use_id":null}"#,
|
||||
],
|
||||
);
|
||||
assert!(events.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user