use std::fmt;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
use crate::provider::{ModelName, ProviderId, ToolChoice};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RunId(Uuid);
impl RunId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
#[must_use]
pub fn from_uuid(uuid: Uuid) -> Self {
Self(uuid)
}
#[must_use]
pub fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for RunId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for RunId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RunStatus {
Pending,
SessionLoaded,
BuildingContext,
CallingModel,
WaitingForTools,
Persisting,
Completed,
Failed,
Cancelled,
}
impl RunStatus {
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Completed | Self::Failed | Self::Cancelled)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunRequest {
pub session_id: Option<Uuid>,
pub run_id: Option<RunId>,
pub provider: ProviderId,
pub model: ModelName,
pub input: String,
pub metadata: Value,
pub tool_choice: ToolChoice,
pub client_request_id: Option<String>,
}
impl RunRequest {
#[must_use]
pub fn new(provider: ProviderId, model: ModelName, input: impl Into<String>) -> Self {
Self {
session_id: None,
run_id: None,
provider,
model,
input: input.into(),
metadata: Value::Null,
tool_choice: ToolChoice::Auto,
client_request_id: None,
}
}
#[must_use]
pub fn with_session_id(mut self, session_id: Uuid) -> Self {
self.session_id = Some(session_id);
self
}
#[must_use]
pub fn with_metadata(mut self, metadata: Value) -> Self {
self.metadata = metadata;
self
}
#[must_use]
pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
self.tool_choice = tool_choice;
self
}
#[must_use]
pub fn with_run_id(mut self, run_id: RunId) -> Self {
self.run_id = Some(run_id);
self
}
#[must_use]
pub fn with_client_request_id(mut self, id: String) -> Self {
self.client_request_id = Some(id);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunRecord {
pub id: RunId,
pub session_id: Uuid,
pub status: RunStatus,
pub provider: ProviderId,
pub model: ModelName,
pub metadata: Value,
pub client_request_id: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl RunRecord {
#[must_use]
pub fn new(
id: RunId,
session_id: Uuid,
provider: ProviderId,
model: ModelName,
metadata: Value,
client_request_id: Option<String>,
) -> Self {
let now = Utc::now();
Self {
id,
session_id,
status: RunStatus::Pending,
provider,
model,
metadata,
client_request_id,
created_at: now,
updated_at: now,
}
}
pub fn update_status(&mut self, status: RunStatus) {
self.status = status;
self.updated_at = Utc::now();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_id_generation() {
let id1 = RunId::new();
let id2 = RunId::new();
assert_ne!(id1, id2);
}
#[test]
fn run_status_terminal() {
assert!(!RunStatus::Pending.is_terminal());
assert!(!RunStatus::CallingModel.is_terminal());
assert!(RunStatus::Completed.is_terminal());
assert!(RunStatus::Failed.is_terminal());
assert!(RunStatus::Cancelled.is_terminal());
}
#[test]
fn run_request_builder() {
let provider = ProviderId::new("test");
let model = ModelName::new("gpt-4");
let request = RunRequest::new(provider.clone(), model.clone(), "hello")
.with_metadata(Value::String("meta".to_string()))
.with_tool_choice(ToolChoice::Required);
assert_eq!(request.provider, provider);
assert_eq!(request.model, model);
assert_eq!(request.input, "hello");
assert_eq!(request.metadata, Value::String("meta".to_string()));
assert!(matches!(request.tool_choice, ToolChoice::Required));
}
}