use serde_json::{Map, Value, json};
use super::fields::opt_str_of;
use super::start::opt_field;
use super::{obj, str_of, usize_of};
use crate::boundary::Query;
use crate::boundary::config::Read;
mod inspector;
pub(super) fn encode(query: &Query) -> Value {
match query {
Query::Workspaces => json!({ "op": "workspaces" }),
Query::Conversations { workspace } => {
json!({ "op": "conversations", "workspace": workspace })
}
Query::Balls => json!({ "op": "balls" }),
Query::WorkspaceBalls { workspace } => {
json!({ "op": WORKSPACE_BALLS, "workspace": workspace })
}
Query::WorkDiff { workspace, file } => {
let mut map = obj(&[("op", "work-diff")]);
map.insert("workspace".to_owned(), json!(workspace));
if let Some(file) = file {
let mut f = obj(&[]);
f.insert("ball".to_owned(), json!(file.ball));
if let Some(handle) = &file.handle {
f.insert("handle".to_owned(), json!(handle));
}
f.insert("path".to_owned(), json!(file.path));
map.insert("file".to_owned(), Value::Object(f));
}
Value::Object(map)
}
Query::Transcript { workspace, agent } => inspector::at("transcript", workspace, agent),
Query::Follow { workspace, agent } => inspector::at(inspector::FOLLOW, workspace, agent),
Query::Steps { workspace, agent } => inspector::at("steps", workspace, agent),
Query::Rail { workspace, agent } => inspector::at("rail", workspace, agent),
Query::Agent { workspace, agent } => inspector::at("agent", workspace, agent),
Query::Inbox { workspace, agent } => inspector::at("inbox", workspace, agent),
Query::Step {
workspace,
agent,
seq,
} => inspector::step(workspace, agent, seq),
Query::Files {
workspace,
agent,
path,
at,
} => inspector::files(workspace, agent, path.as_ref(), at.as_ref()),
Query::Governing {
workspace,
agent,
at,
} => inspector::governing(workspace, agent, at.as_ref()),
Query::Science { workspace } => json!({ "op": SCIENCE, "workspace": workspace }),
Query::Board => json!({ "op": "board" }),
Query::Attention => json!({ "op": "attention" }),
Query::Ops { max } => json!({ "op": "ops", "max": max }),
Query::Search { text } => json!({ "op": "search", "text": text }),
Query::Help { verb } => {
let mut map = obj(&[("op", "help")]);
opt_field(&mut map, "verb", verb.as_ref());
Value::Object(map)
}
Query::Config(read) => super::config::encode_read(read),
Query::Doctor { workspace } => {
let mut map = serde_json::Map::new();
map.insert("op".to_owned(), json!("doctor"));
if let Some(workspace) = workspace {
map.insert("workspace".to_owned(), json!(workspace));
}
Value::Object(map)
}
Query::Clients { workspace } => {
json!({ "op": "clients", "workspace": workspace })
}
Query::LoginTail {
workspace,
provider,
} => json!({ "op": LOGIN_TAIL, "workspace": workspace, "provider": provider }),
Query::Invocations => json!({ "op": INVOCATIONS }),
Query::Capture { invocation } => {
json!({ "op": CAPTURE, "invocation": invocation })
}
Query::Prices => json!({ "op": super::spend::PRICES }),
}
}
const WORKSPACE_BALLS: &str = "workspace-balls";
pub(super) const SCIENCE: &str = "science";
pub(super) const LOGIN_TAIL: &str = "login-tail";
pub(super) const INVOCATIONS: &str = "invocations";
pub(super) const CAPTURE: &str = "capture";
pub(super) fn decode(op: &str, o: &Map<String, Value>) -> Option<Result<Query, String>> {
match read(op, o) {
Ok(query) => query.map(Ok),
Err(reason) => Some(Err(reason)),
}
}
fn read(op: &str, o: &Map<String, Value>) -> Result<Option<Query>, String> {
if let Some(query) = inspector::read(op, o)? {
return Ok(Some(query));
}
Ok(Some(match op {
"workspaces" => Query::Workspaces,
"conversations" => Query::Conversations {
workspace: str_of(o, "workspace")?,
},
"balls" => Query::Balls,
WORKSPACE_BALLS => Query::WorkspaceBalls {
workspace: str_of(o, "workspace")?,
},
"work-diff" => Query::WorkDiff {
workspace: str_of(o, "workspace")?,
file: work_file(o)?,
},
SCIENCE => Query::Science {
workspace: str_of(o, "workspace")?,
},
"board" => Query::Board,
"attention" => Query::Attention,
"ops" => Query::Ops {
max: usize_of(o, "max")?,
},
"search" => Query::Search {
text: str_of(o, "text")?,
},
"help" => Query::Help {
verb: match opt_str_of(o, "verb")? {
Some(verb) if !crate::boundary::help::known(&verb) => {
return Err(format!("help: unknown verb {verb:?}"));
}
other => other,
},
},
"config" if !o.contains_key("text") => Query::Config(Read::File {
file: super::config::decode_file(o.get("target").ok_or("config: missing target")?)?,
}),
"marks" if !o.contains_key("branch") => Query::Config(Read::Marks {
workspace: str_of(o, "workspace")?,
}),
"providers" => Query::Config(Read::Providers {
workspace: str_of(o, "workspace")?,
}),
"roles" => Query::Config(Read::Roles {
workspace: str_of(o, "workspace")?,
}),
"doctor" => Query::Doctor {
workspace: crate::boundary::codec::fields::opt_str_of(o, "workspace")?,
},
"clients" => Query::Clients {
workspace: str_of(o, "workspace")?,
},
LOGIN_TAIL => Query::LoginTail {
workspace: str_of(o, "workspace")?,
provider: str_of(o, "provider")?,
},
INVOCATIONS => Query::Invocations,
CAPTURE => Query::Capture {
invocation: str_of(o, "invocation")?,
},
super::spend::PRICES => Query::Prices,
"lineages" => Query::Config(Read::Lineages {
workspace: str_of(o, "workspace")?,
}),
"models" => Query::Config(Read::Models {
workspace: str_of(o, "workspace")?,
provider: str_of(o, "provider")?,
}),
super::config::PROPOSALS => Query::Config(Read::Proposals {
workspace: str_of(o, "workspace")?,
id: opt_str_of(o, "id")?,
}),
_ => return Ok(None),
}))
}
fn work_file(obj: &Map<String, Value>) -> Result<Option<crate::workdiff::WorkFile>, String> {
let Some(value) = obj.get("file") else {
return Ok(None);
};
let file = value.as_object().ok_or("file: not a JSON object")?;
Ok(Some(crate::workdiff::WorkFile {
ball: str_of(file, "ball")?,
handle: opt_str_of(file, "handle")?,
path: str_of(file, "path")?,
}))
}