use crate::{AgentDriver, AgentError};
use amberfork_record::{Body, CapturedRequest, CapturedResponse, Cassette, Exchange};
use serde_json::{Value, json};
use std::future::Future;
pub(crate) const G0: &str = "acknowledged, looking up order 8841";
pub(crate) const G1: &str = "order 8841 found, refund eligible";
pub(crate) const G2: &str = "refund of 42 dollars issued";
pub(crate) const B1: &str = "request denied, account flagged for review";
pub(crate) const B2: &str = "escalation ticket opened pending manual handling";
pub(crate) struct ScriptedAgent {
pub(crate) requests: Vec<Value>,
}
impl AgentDriver for ScriptedAgent {
fn drive(&self, base_url: &str) -> impl Future<Output = Result<(), AgentError>> + Send {
let url = format!("{base_url}/v1/chat/completions");
let bodies = self.requests.clone();
async move {
let client = reqwest::Client::new();
for body in bodies {
client
.post(&url)
.json(&body)
.send()
.await
.expect("the scripted agent reaches the replay listener");
}
Ok(())
}
}
}
pub(crate) fn request(content: &str) -> CapturedRequest {
CapturedRequest {
method: "POST".to_string(),
path: "/v1/chat/completions".to_string(),
headers: vec![("content-type".to_string(), "application/json".to_string())],
body: Body::Json(json!({
"model": "claude-sonnet-5",
"messages": [{ "role": "user", "content": content }],
})),
}
}
pub(crate) fn response(content: &str) -> CapturedResponse {
CapturedResponse {
status: 200,
headers: vec![("content-type".to_string(), "application/json".to_string())],
body: Body::Json(json!({ "choices": [{ "message": { "content": content } }] })),
}
}
pub(crate) fn body_of(content: &str) -> Value {
let Body::Json(body) = request(content).body else {
unreachable!("request bodies are JSON")
};
body
}
pub(crate) fn cassette(id: &str, turns: &[(&str, &str)]) -> Cassette {
let mut cassette = Cassette::new(id);
for (idx, (question, answer)) in turns.iter().enumerate() {
cassette.exchanges.push(Exchange {
idx,
request: request(question),
response: response(answer),
});
}
cassette
}
pub(crate) fn good_cassette() -> Cassette {
cassette("good", &[("q0", G0), ("q1", G1), ("q2", G2)])
}
pub(crate) fn bad_cassette() -> Cassette {
cassette("bad", &[("q0", G0), ("q1", B1), ("q2_bad", B2)])
}