use std::sync::Arc;
use std::time::Duration;
use serde_json::Value;
use crate::admission::scheduler::Scheduler;
use crate::agent::delegation::{Outcome, Refused, Sources, parse_delegation, resolve_source};
use crate::provider::ask::{Endpoint, Question, Sender, ask};
#[derive(Debug, Clone, PartialEq)]
pub struct Answer {
pub text: String,
pub model: String,
pub describes: String,
pub tokens: Option<i64>,
pub kept_out: usize,
}
pub type DelegationOutcome = Outcome<Answer>;
pub struct TurnDelegations<S: Sources, P: Sender> {
endpoint: Endpoint,
scheduler: Arc<Scheduler>,
sources: Arc<S>,
deadline_ms: u64,
per_turn: usize,
used: usize,
send: Arc<P>,
}
impl<S: Sources + 'static, P: Sender> TurnDelegations<S, P> {
pub fn new(
session_id: &str,
endpoint: Endpoint,
scheduler: Arc<Scheduler>,
sources: Arc<S>,
deadline_ms: u64,
per_turn: usize,
send: Arc<P>,
) -> Self {
let _ = session_id;
Self {
endpoint,
scheduler,
sources,
deadline_ms,
per_turn,
used: 0,
send,
}
}
pub fn remaining(&self) -> usize {
self.per_turn.saturating_sub(self.used)
}
pub async fn run(&mut self, raw: &Value) -> DelegationOutcome {
if self.remaining() == 0 {
return Outcome::Refused(Refused {
refused: format!("this turn has already delegated {} times", self.per_turn),
});
}
let delegation = match parse_delegation(raw) {
Outcome::Ready(delegation) => delegation,
Outcome::Refused(refused) => return Outcome::Refused(refused),
};
let resolved = match resolve_source(&delegation, self.sources.as_ref()).await {
Outcome::Ready(resolved) => resolved,
Outcome::Refused(refused) => return Outcome::Refused(refused),
};
self.used += 1;
let Some(ticket) = self.scheduler.try_admit() else {
return Outcome::Refused(Refused {
refused: if self.scheduler.paused_because().is_none() {
"there was no free slot to ask a second model".to_owned()
} else {
"the provider is being backed off, so nothing was asked of it".to_owned()
},
});
};
let asked = Question {
question: resolved.question,
describes: resolved.describes.clone(),
content: resolved.content.clone(),
};
let cancel = tokio::time::sleep(Duration::from_millis(self.deadline_ms));
tokio::pin!(cancel);
let given = ask(&self.endpoint, &asked, cancel, self.send.as_ref()).await;
self.scheduler.release(&ticket);
match given {
Ok(given) => Outcome::Ready(Answer {
text: given.text,
model: self.endpoint.model.clone(),
describes: resolved.describes,
tokens: given.tokens,
kept_out: resolved.content.chars().count(),
}),
Err(failed) => Outcome::Refused(Refused { refused: failed.0 }),
}
}
}
#[allow(dead_code)]
fn _answer_shape(answer: &Answer) -> Value {
serde_json::json!({
"text": answer.text,
"model": answer.model,
})
}
#[cfg(test)]
mod tests;