use pushkin_core::edits::Replacement;
use serde_json::Value;
use super::super::shared::{patch_action, relativize, session_from};
use super::super::{FileWrite, Intent, ParseOutcome, ToolAction};
pub(crate) fn opencode_replacements(args: &Value) -> Vec<Replacement> {
let Some(old) = args.get("oldString").and_then(Value::as_str) else {
return Vec::new();
};
let Some(new) = args.get("newString").and_then(Value::as_str) else {
return Vec::new();
};
vec![Replacement {
old: old.to_owned(),
new: new.to_owned(),
replace_all: args
.get("replaceAll")
.and_then(Value::as_bool)
.unwrap_or(false),
anchor: None,
}]
}
pub(crate) fn normalize_opencode(value: &Value) -> ParseOutcome {
let session = session_from(value, "sessionID");
let Some(args) = value.get("args") else {
return Err(None);
};
let tool = value.get("tool").and_then(Value::as_str);
if tool == Some("apply_patch") {
if let Some(patch) = args.get("patchText").and_then(Value::as_str) {
return patch_action(session, patch);
}
}
let path = args
.get("filePath")
.and_then(Value::as_str)
.map(relativize)
.ok_or(None)?;
if tool == Some("read") {
let bounded = args.get("offset").is_some() || args.get("limit").is_some();
return Ok(ToolAction {
session,
files: vec![FileWrite {
path,
content: String::new(),
edits: Vec::new(),
}],
is_stop: false,
intent: if bounded {
Intent::ReadRange
} else {
Intent::ReadWhole
},
command: None,
});
}
if tool == Some("edit") {
return Ok(ToolAction {
session,
files: vec![FileWrite {
path,
content: String::new(),
edits: opencode_replacements(args),
}],
is_stop: false,
intent: Intent::MutateNoContent("edit"),
command: None,
});
}
let well_formed = tool.is_some();
let content = args.get("content").and_then(Value::as_str);
match (well_formed, content) {
(true, Some(content)) => Ok(ToolAction {
session,
files: vec![FileWrite {
path,
content: content.to_owned(),
edits: Vec::new(),
}],
is_stop: false,
intent: Intent::Write,
command: None,
}),
_ => Err(Some(path)),
}
}