use serde_json::{Value, json};
use super::{Action, Gesture};
mod balls;
mod config;
mod control;
mod deposit;
mod fan;
pub(crate) mod fields;
mod fleet;
pub(crate) use fleet::{ARM as FLEET_ARM, DISARM as FLEET_DISARM};
mod fork;
mod monitor;
mod query;
mod start;
mod tools;
use config::{encode_file, encode_tuning};
use deposit::{INTERRUPT, MESSAGE, deposit, deposited};
use fields::{act, obj, opt_path_of, opt_str_of, path_of, str_of, strings_of, usize_of};
use start::{decode_payload, decode_prepared, encode_start, opt_field};
pub(crate) use start::{
decode_prepared as prepared_from_value, encode_prepared as prepared_value, join_token,
origin_token, parse_join, parse_origin,
};
pub fn encode(gesture: &Gesture) -> Value {
match gesture {
Gesture::Act(action) => encode_action(action),
Gesture::Ask(query) => query::encode(query),
}
}
fn encode_action(action: &Action) -> Value {
match action {
Action::Message {
workspace,
agent,
content,
} => deposit(MESSAGE, workspace, agent, content),
Action::Interrupt {
workspace,
agent,
content,
} => deposit(INTERRUPT, workspace, agent, content),
Action::Stop {
workspace,
agent,
children,
} => json!({ "op": "stop", "workspace": workspace,
"agent": agent, "children": children }),
Action::Scan { workspace } => json!({ "op": "scan", "workspace": workspace }),
Action::Nudge { workspace, agent } => at_agent("nudge", workspace, agent),
Action::Retarget { workspace, agent } => at_agent("retarget", workspace, agent),
Action::Ball(verb) => balls::encode(verb),
Action::Prepare { .. } | Action::Prompt { .. } => encode_start(action),
Action::Fan(verb) => fan::encode_verb(verb),
Action::DeleteWorkspace { workspace, typed } => {
json!({ "op": "delete-workspace", "workspace": workspace,
"typed": typed })
}
Action::DeleteAgent {
workspace,
agent,
typed,
} => json!({ "op": "delete-agent", "workspace": workspace,
"agent": agent, "typed": typed }),
Action::Monitor(verb) => monitor::encode(verb),
Action::Fleet(verb) => fleet::encode(verb),
Action::AnswerHold {
workspace,
agent,
ruling,
} => control::encode(workspace, agent, *ruling),
Action::Floor {
workspace,
agent,
raised,
} => control::encode_floor(workspace, agent, *raised),
Action::Ack => json!({ "op": "ack" }),
Action::MarkSeen { workspace, agent } => at_agent("seen", workspace, agent),
Action::ClearTrail => json!({ "op": "clear-trail" }),
Action::ApplyConfig { file, text } => {
json!({ "op": "config", "target": encode_file(file), "text": text })
}
Action::SetMarks { workspace, branch } => {
json!({ "op": "marks", "workspace": workspace, "branch": branch })
}
Action::PickModel {
workspace,
role,
provider,
model,
} => json!({ "op": "model", "workspace": workspace,
"role": role, "provider": provider, "model": model }),
Action::Tune(tuning) => encode_tuning(tuning),
Action::Fork {
workspace,
parent,
attempt,
goal,
} => fork::encode(workspace, parent, attempt, goal),
Action::Advertise { tools } => tools::encode(tools),
Action::Enroll(request) => json!({ "op": ENROLL, "workspace": request.workspace,
"name": request.name,
"grade": request.grade.word() }),
Action::Route(verb) => tools::encode_route(verb),
}
}
pub(crate) const ENROLL: &str = "enroll";
fn at_agent(op: &str, workspace: &str, agent: &str) -> Value {
json!({ "op": op, "workspace": workspace, "agent": agent })
}
pub(crate) fn grade_of(word: &str) -> Result<crate::registry::Grade, String> {
crate::registry::Grade::of(word).ok_or_else(|| format!("unknown grade {word:?}"))
}
pub fn decode(v: &Value) -> Result<Gesture, String> {
let o = v.as_object().ok_or("gesture: not a JSON object")?;
let op = str_of(o, "op")?;
match op.as_str() {
MESSAGE | INTERRUPT => Ok(act(deposited(&op, o)?)),
"stop" => Ok(act(Action::Stop {
workspace: str_of(o, "workspace")?,
agent: str_of(o, "agent")?,
children: o.get("children").and_then(Value::as_bool).unwrap_or(false),
})),
"scan" => Ok(act(Action::Scan {
workspace: str_of(o, "workspace")?,
})),
"nudge" => Ok(act(Action::Nudge {
workspace: str_of(o, "workspace")?,
agent: str_of(o, "agent")?,
})),
"retarget" => Ok(act(Action::Retarget {
workspace: str_of(o, "workspace")?,
agent: str_of(o, "agent")?,
})),
"close" | "assign" | "release" | "create" | "update" => {
balls::decode(op.as_str(), o).map(act)
}
"prepare" => Ok(act(Action::Prepare {
workspace: str_of(o, "workspace")?,
payload: decode_payload(o.get("payload").ok_or("prepare: missing payload")?)?,
})),
"prompt" => Ok(act(Action::Prompt {
prepared: decode_prepared(o.get("prepared").ok_or("prompt: missing prepared")?)?,
goal: str_of(o, "goal")?,
seed: fields::opt(o, "seed", fields::u64_of)?,
})),
"delete-workspace" => Ok(act(Action::DeleteWorkspace {
workspace: str_of(o, "workspace")?,
typed: str_of(o, "typed")?,
})),
"delete-agent" => Ok(act(Action::DeleteAgent {
workspace: str_of(o, "workspace")?,
agent: str_of(o, "agent")?,
typed: str_of(o, "typed")?,
})),
"arm" | "disarm" | "flag" => monitor::decode(op.as_str(), o),
fleet::ARM | fleet::DISARM => fleet::decode(op.as_str(), o),
"answer" => control::decode(o),
"revoke" | "restore" => control::decode_floor(op.as_str(), o),
"fork" => fork::decode(o).map(act),
fan::FAN | fan::RETIRE | fan::DELIVER => fan::decode(op.as_str(), o).map(act),
"ack" => Ok(act(Action::Ack)),
"seen" => Ok(act(Action::MarkSeen {
workspace: str_of(o, "workspace")?,
agent: str_of(o, "agent")?,
})),
"clear-trail" => Ok(act(Action::ClearTrail)),
tools::ADVERTISE | tools::INVOKE | tools::COMPLETE => {
tools::decode(op.as_str(), o).map(act)
}
ENROLL => Ok(act(Action::Enroll(crate::registry::enroll::Request {
workspace: str_of(o, "workspace")?,
name: str_of(o, "name")?,
grade: grade_of(&str_of(o, "grade")?)?,
}))),
other => query::decode(other, o)
.map(|query| query.map(Gesture::Ask))
.or_else(|| config::decode_action(other, o).map(|action| action.map(act)))
.unwrap_or_else(|| Err(format!("unknown op {other:?}"))),
}
}
#[cfg(test)]
pub(crate) mod tests;