pub const RESULT_KEY: &str = "ok";
pub const SUCCESS_ENVELOPE_TYPES: &[&str] = &[
"agent_surface",
"applied",
"backup",
"calc",
"codemod_summary",
"commands_tree",
"config",
"copied",
"count",
"del",
"diff",
"doctor_report",
"edit",
"get",
"hash",
"hash_verify",
"installed",
"locale_report",
"moved",
"outline_summary",
"query_summary",
"read",
"recipe",
"recipe_result",
"regex",
"result",
"rollback",
"scratch_init",
"semantic_merge",
"semantic_summary",
"set",
"sparse_outline_budget",
"sparse_summary",
"summary",
"wal_heal",
"wal_stats",
"watch_summary",
"write",
];
pub const STREAM_RECORD_TYPES: &[&str] = &[
"artifact",
"batch_op",
"begin",
"cancelled",
"case",
"case_preview",
"change",
"codemod",
"command",
"context",
"deleted",
"end",
"entry",
"error",
"file",
"file_match",
"match",
"outline_item",
"plan",
"preview",
"progress",
"pruned",
"query_kind",
"query_match",
"recipe_step",
"replaced",
"rule_begin",
"rule_error",
"scoped",
"semantic_match",
"skipped",
"sparse_entry",
"sparse_read",
"stream_diff",
"text",
"tool",
"transformed",
"watch",
];
fn is_success_envelope(value: &Value) -> bool {
let Some(kind) = value.get("type").and_then(Value::as_str) else {
return false;
};
if value.get("ok") == Some(&Value::Bool(false)) {
return false;
}
SUCCESS_ENVELOPE_TYPES.contains(&kind)
}
fn stamp_ok(value: &mut Value) {
if !is_success_envelope(value) {
return;
}
if let Some(obj) = value.as_object_mut() {
obj.entry(RESULT_KEY).or_insert(Value::Bool(true));
}
}
fn stamp_failed(value: &mut Value, code: &str, exit: u8) {
let Some(obj) = value.as_object_mut() else {
return;
};
obj.insert(RESULT_KEY.to_string(), Value::Bool(false));
obj.entry("code")
.or_insert_with(|| Value::String(code.to_string()));
obj.entry("exit").or_insert_with(|| Value::from(exit));
}
const VERDICT_PUBLISHED_MESSAGE: &str = "verdict already published on the data channel";
#[derive(Debug, Clone, Copy)]
pub struct VerdictPublished;
impl std::fmt::Display for VerdictPublished {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(VERDICT_PUBLISHED_MESSAGE)
}
}
pub fn error_message(err: &anyhow::Error) -> String {
err.chain()
.map(ToString::to_string)
.find(|message| message != VERDICT_PUBLISHED_MESSAGE)
.unwrap_or_else(|| err.to_string())
}
const FAILURE_STAMPED_KEYS: [&str; 3] = ["ok", "code", "exit"];
fn with_typed_failure<R>(err: &anyhow::Error, read: impl FnOnce(&AtomwriteError) -> R) -> R {
if let Some(known) = err.downcast_ref::<AtomwriteError>() {
return read(known);
}
let mapped = match crate::runtime::find_io_error(err) {
Some(kind) => crate::runtime::io_to_atomwrite_error(kind, err),
None => AtomwriteError::InternalError {
reason: error_message(err),
},
};
read(&mapped)
}
pub fn failure_class(err: &anyhow::Error) -> (&'static str, bool) {
with_typed_failure(err, |known| {
(known.error_class().as_str(), known.is_retryable())
})
}
fn failure_detail(err: &anyhow::Error) -> Value {
with_typed_failure(err, |known| {
serde_json::to_value(ErrorJson::from_error(known)).unwrap_or(Value::Null)
})
}
fn stamp_failure(value: &mut Value, err: &anyhow::Error) {
let (code, exit) = failure_identity(err);
stamp_failed(value, code, exit);
let Some(obj) = value.as_object_mut() else {
return;
};
let Value::Object(detail) = failure_detail(err) else {
return;
};
for (key, field) in detail {
if !FAILURE_STAMPED_KEYS.contains(&key.as_str()) {
obj.entry(key).or_insert(field);
}
}
}
pub fn is_content_rejection(err: &anyhow::Error) -> bool {
matches!(
err.downcast_ref::<AtomwriteError>(),
Some(
AtomwriteError::InvalidInput { .. }
| AtomwriteError::FileTooLarge { .. }
| AtomwriteError::BinaryFile { .. }
)
)
}
pub fn failure_identity(err: &anyhow::Error) -> (&'static str, u8) {
if let Some(known) = err.downcast_ref::<AtomwriteError>() {
return (known.error_code(), known.exit_code());
}
if let Some(kind) = crate::runtime::find_io_error(err) {
let mapped = crate::runtime::io_to_atomwrite_error(kind, err);
return (mapped.error_code(), mapped.exit_code());
}
("INTERNAL_ERROR", 1)
}
fn is_terminal_envelope(value: &Value) -> bool {
if value.get("ok").and_then(Value::as_bool) == Some(false) && value.get("code").is_some() {
return true;
}
let Some(kind) = value.get("type").and_then(Value::as_str) else {
return false;
};
kind == "cancelled" || SUCCESS_ENVELOPE_TYPES.contains(&kind)
}
fn stamp_workspace_key(value: &mut Value, source: &str) {
if !is_terminal_envelope(value) {
return;
}
if let Some(obj) = value.as_object_mut() {
obj.entry("workspace_source")
.or_insert_with(|| Value::String(source.to_string()));
}
}
fn stamp_target_keys(value: &mut Value, resolved: &Value, source: &str, refusal: bool) {
if !is_terminal_envelope(value) {
return;
}
let repeated_path = refusal
&& value.get("path").and_then(Value::as_str).is_some_and(|path| {
resolved
.as_array()
.is_some_and(|items| items.len() == 1 && items[0].as_str() == Some(path))
});
if let Some(obj) = value.as_object_mut() {
if !(refusal && source == "argv") {
obj.entry("target_source")
.or_insert_with(|| Value::String(source.to_string()));
}
if !repeated_path {
obj.entry("target_resolved")
.or_insert_with(|| resolved.clone());
}
}
}