1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use uuid::Uuid;
7
8use behest_provider::{ModelName, ProviderId, ToolChoice};
9
10pub use behest_core::id::RunId;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum RunStatus {
15 Pending,
17 SessionLoaded,
19 BuildingContext,
21 CallingModel,
23 WaitingForTools,
25 Persisting,
27 Completed,
29 Failed,
31 Cancelled,
33}
34
35impl RunStatus {
36 #[must_use]
38 pub fn is_terminal(&self) -> bool {
39 matches!(self, Self::Completed | Self::Failed | Self::Cancelled)
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct RunRequest {
46 pub session_id: Option<Uuid>,
48 pub run_id: Option<RunId>,
50 pub provider: ProviderId,
52 pub model: ModelName,
54 pub input: String,
56 pub metadata: Value,
58 pub tool_choice: ToolChoice,
60 pub client_request_id: Option<String>,
62}
63
64impl RunRequest {
65 #[must_use]
67 pub fn new(provider: ProviderId, model: ModelName, input: impl Into<String>) -> Self {
68 Self {
69 session_id: None,
70 run_id: None,
71 provider,
72 model,
73 input: input.into(),
74 metadata: Value::Null,
75 tool_choice: ToolChoice::Auto,
76 client_request_id: None,
77 }
78 }
79
80 #[must_use]
82 pub fn with_session_id(mut self, session_id: Uuid) -> Self {
83 self.session_id = Some(session_id);
84 self
85 }
86
87 #[must_use]
89 pub fn with_metadata(mut self, metadata: Value) -> Self {
90 self.metadata = metadata;
91 self
92 }
93
94 #[must_use]
96 pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
97 self.tool_choice = tool_choice;
98 self
99 }
100
101 #[must_use]
103 pub fn with_run_id(mut self, run_id: RunId) -> Self {
104 self.run_id = Some(run_id);
105 self
106 }
107
108 #[must_use]
110 pub fn with_client_request_id(mut self, id: String) -> Self {
111 self.client_request_id = Some(id);
112 self
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct RunRecord {
119 pub id: RunId,
121 pub session_id: Uuid,
123 pub status: RunStatus,
125 pub provider: ProviderId,
127 pub model: ModelName,
129 pub metadata: Value,
131 pub client_request_id: Option<String>,
133 pub created_at: DateTime<Utc>,
135 pub updated_at: DateTime<Utc>,
137}
138
139impl RunRecord {
140 #[must_use]
142 pub fn new(
143 id: RunId,
144 session_id: Uuid,
145 provider: ProviderId,
146 model: ModelName,
147 metadata: Value,
148 client_request_id: Option<String>,
149 ) -> Self {
150 let now = Utc::now();
151 Self {
152 id,
153 session_id,
154 status: RunStatus::Pending,
155 provider,
156 model,
157 metadata,
158 client_request_id,
159 created_at: now,
160 updated_at: now,
161 }
162 }
163
164 pub fn update_status(&mut self, status: RunStatus) {
166 self.status = status;
167 self.updated_at = Utc::now();
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn run_id_generation() {
177 let id1 = RunId::new();
178 let id2 = RunId::new();
179 assert_ne!(id1, id2);
180 }
181
182 #[test]
183 fn run_status_terminal() {
184 assert!(!RunStatus::Pending.is_terminal());
185 assert!(!RunStatus::CallingModel.is_terminal());
186 assert!(RunStatus::Completed.is_terminal());
187 assert!(RunStatus::Failed.is_terminal());
188 assert!(RunStatus::Cancelled.is_terminal());
189 }
190
191 #[test]
192 fn run_request_builder() {
193 let provider = ProviderId::new("test");
194 let model = ModelName::new("gpt-4");
195 let request = RunRequest::new(provider.clone(), model.clone(), "hello")
196 .with_metadata(Value::String("meta".to_string()))
197 .with_tool_choice(ToolChoice::Required);
198
199 assert_eq!(request.provider, provider);
200 assert_eq!(request.model, model);
201 assert_eq!(request.input, "hello");
202 assert_eq!(request.metadata, Value::String("meta".to_string()));
203 assert!(matches!(request.tool_choice, ToolChoice::Required));
204 }
205}