mod set_goal;
pub(super) mod start_workflow;
use argonauts_core::asking::AskReply;
use argonauts_core::names::TurnId;
use serde_json::Value;
use tokio::sync::oneshot;
use super::{ConversationState, talk};
use crate::controller::{Ctx, Effect};
pub(super) struct Call {
pub turn: TurnId,
pub arguments: Value,
pub reply: oneshot::Sender<AskReply>,
}
impl Call {
pub(super) fn answer(self, text: impl Into<String>) {
let _ = self.reply.send(AskReply::text(text));
}
}
pub(super) struct MainTool {
pub name: &'static str,
pub about: &'static str,
pub offered: fn(&Ctx, &ConversationState) -> bool,
pub call: fn(&mut Ctx, &mut ConversationState, Call) -> Vec<Effect>,
}
fn all() -> Vec<MainTool> {
vec![
start_workflow::tool(),
set_goal::tool(),
MainTool {
name: talk::setup::NAME,
about: talk::setup::ABOUT,
offered: talk::setup::offered,
call: talk::setup::call,
},
MainTool {
name: talk::guide::NAME,
about: talk::guide::ABOUT,
offered: talk::guide::offered,
call: talk::guide::call,
},
MainTool {
name: talk::lookup::NAME,
about: talk::lookup::ABOUT,
offered: talk::lookup::offered,
call: talk::lookup::call,
},
]
}
pub(super) fn offered(ctx: &Ctx, conversation: &ConversationState) -> Vec<String> {
if !super::takes_tools(ctx, conversation) {
return Vec::new();
}
all()
.into_iter()
.filter(|tool| (tool.offered)(ctx, conversation))
.map(|tool| tool.name.to_owned())
.collect()
}
pub(super) fn about(offered: &[String]) -> Vec<String> {
all()
.into_iter()
.filter(|tool| offered.iter().any(|name| name == tool.name))
.map(|tool| tool.about.to_owned())
.collect()
}
pub(super) fn call(
ctx: &mut Ctx,
conversation: &mut ConversationState,
name: &str,
call: Call,
) -> Vec<Effect> {
let Some(turn) = conversation
.turn
.as_ref()
.filter(|turn| turn.id == call.turn)
else {
call.answer("That turn is over, so nothing starts for it.");
return Vec::new();
};
let offered = turn.step.offers_tool(name);
match all().into_iter().find(|tool| tool.name == name) {
Some(tool) if offered => (tool.call)(ctx, conversation, call),
_ => {
let _ = call.reply.send(AskReply {
text: format!("Argonauts Code gave this session no tool named {name:?}."),
is_error: true,
});
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use argonauts_mcp::session::main_tools;
use super::*;
#[test]
fn the_session_lists_exactly_the_tools_the_app_runs() {
let listed: BTreeSet<&str> = main_tools::all().iter().map(|tool| tool.name).collect();
let run: BTreeSet<&str> = all().iter().map(|tool| tool.name).collect();
assert_eq!(listed, run);
for tool in all() {
assert!(
tool.about.starts_with(&format!("`{}`", tool.name)),
"{}",
tool.about
);
}
}
}