use std::sync::Arc;
use serde::Serialize;
use crate::client::{AkribesClient, Inner};
use crate::error::Result;
use crate::models::*;
#[derive(Clone, Debug)]
pub struct BenchClient {
pub(crate) inner: Arc<Inner>,
pub(crate) project_id: i64,
}
impl BenchClient {
pub(crate) fn new(inner: Arc<Inner>, project_id: i64) -> Self {
Self { inner, project_id }
}
fn c(&self) -> AkribesClient {
AkribesClient {
inner: Arc::clone(&self.inner),
}
}
fn bench_url(&self, script_name: &str) -> String {
format!(
"{}/projects/{}/scripts/{}/bench",
self.inner.base_url,
self.project_id,
urlencoding::encode(script_name),
)
}
pub async fn get(&self, script_name: &str) -> Result<Option<Bench>> {
self.c().get_opt(&self.bench_url(script_name)).await
}
pub async fn create_or_update(
&self,
script_name: &str,
req: &CreateOrUpdateBenchRequest,
) -> Result<Bench> {
self.c().post(&self.bench_url(script_name), req).await
}
pub async fn delete(&self, script_name: &str) -> Result<bool> {
self.c().delete(&self.bench_url(script_name)).await
}
pub async fn get_signature(&self, script_name: &str) -> Result<serde_json::Value> {
let url = format!(
"{}/projects/{}/scripts/{}/signature",
self.inner.base_url,
self.project_id,
urlencoding::encode(script_name),
);
Ok(self
.c()
.get_opt::<serde_json::Value>(&url)
.await?
.unwrap_or(serde_json::json!({})))
}
pub async fn contract_preview(
&self,
script_name: &str,
judge_script_id: i64,
channel: Option<&str>,
) -> Result<serde_json::Value> {
#[derive(Serialize)]
struct Q<'a> {
judge: i64,
#[serde(skip_serializing_if = "Option::is_none")]
channel: Option<&'a str>,
}
let base = format!("{}/contract-preview", self.bench_url(script_name));
let url = AkribesClient::url_with_query(
&base,
&Q {
judge: judge_script_id,
channel,
},
);
Ok(self
.c()
.get_opt::<serde_json::Value>(&url)
.await?
.unwrap_or(serde_json::json!({})))
}
pub async fn list_cases(&self, script_name: &str) -> Result<Vec<BenchCase>> {
let url = format!("{}/cases", self.bench_url(script_name));
self.c().get_list(&url).await
}
pub async fn create_case(
&self,
script_name: &str,
req: &CreateBenchCaseRequest,
) -> Result<BenchCase> {
let url = format!("{}/cases", self.bench_url(script_name));
self.c().post(&url, req).await
}
pub async fn case_contract_drift(&self, script_name: &str) -> Result<DriftReport> {
let url = format!("{}/cases/contract-drift", self.bench_url(script_name));
Ok(self
.c()
.get_opt::<DriftReport>(&url)
.await?
.unwrap_or(DriftReport {
drifted: Vec::new(),
script_version_id: None,
published_at: None,
published_by: None,
summary: String::new(),
}))
}
pub async fn list_runs(
&self,
script_name: &str,
limit: Option<i64>,
offset: Option<i64>,
) -> Result<Vec<BenchRun>> {
#[derive(Serialize)]
struct Q {
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
offset: Option<i64>,
}
let base = format!("{}/runs", self.bench_url(script_name));
let url = AkribesClient::url_with_query(&base, &Q { limit, offset });
self.c().get_list(&url).await
}
pub async fn trigger_run(
&self,
script_name: &str,
req: &TriggerBenchRunRequest,
) -> Result<BenchRun> {
let url = format!("{}/runs", self.bench_url(script_name));
self.c().post(&url, req).await
}
}
#[derive(Clone, Debug)]
pub struct BenchRunsClient {
pub(crate) inner: Arc<Inner>,
}
impl BenchRunsClient {
pub(crate) fn new(inner: Arc<Inner>) -> Self {
Self { inner }
}
fn c(&self) -> AkribesClient {
AkribesClient {
inner: Arc::clone(&self.inner),
}
}
fn run_url(&self, run_id: i64) -> String {
format!("{}/bench-runs/{}", self.inner.base_url, run_id)
}
pub async fn get(&self, run_id: i64) -> Result<Option<BenchRun>> {
self.c().get_opt(&self.run_url(run_id)).await
}
pub async fn delete(&self, run_id: i64) -> Result<()> {
self.c().delete(&self.run_url(run_id)).await?;
Ok(())
}
pub async fn list_results(&self, run_id: i64) -> Result<Vec<BenchResult>> {
let url = format!("{}/results", self.run_url(run_id));
self.c().get_list(&url).await
}
pub async fn events(&self, run_id: i64, after_id: Option<i64>) -> Result<BenchRunEventsPage> {
#[derive(Serialize)]
struct Q {
#[serde(skip_serializing_if = "Option::is_none")]
after_id: Option<i64>,
}
let base = format!("{}/events", self.run_url(run_id));
let url = AkribesClient::url_with_query(&base, &Q { after_id });
Ok(self
.c()
.get_opt::<BenchRunEventsPage>(&url)
.await?
.unwrap_or_default())
}
pub async fn cancel(&self, run_id: i64) -> Result<BenchRun> {
let url = format!("{}/cancel", self.run_url(run_id));
let empty: serde_json::Value = serde_json::json!({});
self.c().post(&url, &empty).await
}
pub async fn tag_session(
&self,
run_id: i64,
mcp_session_id: &str,
) -> Result<BenchRunTagSessionResponse> {
#[derive(Serialize)]
struct Body<'a> {
mcp_session_id: &'a str,
}
let url = format!("{}/tag-session", self.run_url(run_id));
self.c().patch(&url, &Body { mcp_session_id }).await
}
pub async fn promote_execution(
&self,
execution_id: &str,
req: &PromoteExecutionRequest,
) -> Result<BenchCase> {
let url = format!(
"{}/executions/{}/promote-to-case",
self.inner.base_url,
urlencoding::encode(execution_id),
);
self.c().post(&url, req).await
}
pub async fn compare(&self, run_a: i64, run_b: i64) -> Result<CompareReport> {
let url = format!(
"{}/bench-runs/{}/compare/{}",
self.inner.base_url, run_a, run_b,
);
Ok(self
.c()
.get_opt::<CompareReport>(&url)
.await?
.ok_or_else(|| crate::error::AkribesError::HttpStatus {
status: 404,
message: format!("compare runs {}↔{} returned 404", run_a, run_b),
})?)
}
pub async fn get_case(&self, case_id: &str) -> Result<serde_json::Value> {
let url = format!(
"{}/executions/{}",
self.inner.base_url,
urlencoding::encode(case_id),
);
Ok(self
.c()
.get_opt::<serde_json::Value>(&url)
.await?
.unwrap_or(serde_json::Value::Null))
}
pub async fn patch_case(
&self,
case_id: &str,
req: &PatchBenchCaseRequest,
) -> Result<BenchCase> {
let url = format!(
"{}/cases/{}",
self.inner.base_url,
urlencoding::encode(case_id),
);
self.c().patch(&url, req).await
}
pub async fn delete_case(&self, case_id: &str) -> Result<()> {
let url = format!(
"{}/cases/{}",
self.inner.base_url,
urlencoding::encode(case_id),
);
self.c().delete(&url).await?;
Ok(())
}
pub async fn bench_by_id(&self, bench_id: i64) -> Result<Option<serde_json::Value>> {
let url = format!("{}/benches/{}", self.inner.base_url, bench_id);
self.c().get_json_value_opt(&url).await
}
pub async fn mcp_session_cost(&self, session_id: &str) -> Result<serde_json::Value> {
let url = format!(
"{}/mcp-sessions/{}/cost",
self.inner.base_url,
urlencoding::encode(session_id),
);
self.c().get_json_value(&url).await
}
}