1use async_trait::async_trait;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11use behest_provider::Message;
12use behest_store::{
13 ArtifactStore, EmbeddingStore, ExecutionStore, MessageRecord, MessageRole, SessionStore,
14};
15
16use super::error::{RuntimeError, RuntimeResult};
17use super::event::AgentEvent;
18use super::extension::ExtensionPoint;
19use super::extensions::Extensions;
20use super::run::{RunId, RunRecord, RunStatus};
21use super::state::RunState;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct RunEventRecord {
26 pub sequence: u64,
28 pub run_id: RunId,
30 pub event: AgentEvent,
32 pub timestamp: DateTime<Utc>,
34}
35
36impl RunEventRecord {
37 #[must_use]
39 pub fn new(sequence: u64, run_id: RunId, event: AgentEvent) -> Self {
40 Self {
41 sequence,
42 run_id,
43 event,
44 timestamp: Utc::now(),
45 }
46 }
47}
48
49#[async_trait]
57pub trait RunStore: Send + Sync {
58 async fn create_run(&self, record: RunRecord) -> RuntimeResult<()>;
64
65 async fn get_run(&self, run_id: RunId) -> RuntimeResult<Option<RunRecord>>;
73
74 async fn get_run_state(&self, run_id: RunId) -> RuntimeResult<Option<RunState>> {
80 let Some(record) = self.get_run(run_id).await? else {
81 return Ok(None);
82 };
83 let events = self.list_events(run_id).await?;
84 Ok(Some(RunState::create(&record, &events)))
85 }
86
87 async fn update_run_status(&self, run_id: RunId, status: RunStatus) -> RuntimeResult<()>;
93
94 async fn append_event(&self, record: RunEventRecord) -> RuntimeResult<()>;
103
104 async fn list_events(&self, run_id: RunId) -> RuntimeResult<Vec<RunEventRecord>>;
111
112 async fn list_runs(&self, session_id: Uuid) -> RuntimeResult<Vec<RunRecord>>;
118
119 async fn list_runs_filtered(
124 &self,
125 session_id: Option<Uuid>,
126 status: Option<RunStatus>,
127 limit: usize,
128 offset: usize,
129 ) -> RuntimeResult<Vec<RunRecord>> {
130 let _ = (session_id, status, limit, offset);
131 Err(RuntimeError::Storage(
132 behest_core::error::StorageError::BackendError {
133 backend: "run".to_owned(),
134 message: "list_runs_filtered not implemented".to_owned(),
135 source: None,
136 },
137 ))
138 }
139
140 async fn delete_run(&self, run_id: RunId) -> RuntimeResult<()>;
146
147 async fn health_check(&self) -> RuntimeResult<()>;
153}
154
155pub struct RuntimeStore {
160 sessions: Box<dyn SessionStore>,
161 executions: Box<dyn ExecutionStore>,
162 runs: Box<dyn RunStore>,
163 embeddings: Option<Box<dyn EmbeddingStore>>,
164 artifacts: Option<Box<dyn ArtifactStore>>,
165
166 sessions_ep: ExtensionPoint<dyn SessionStore>,
167 executions_ep: ExtensionPoint<dyn ExecutionStore>,
168 runs_ep: ExtensionPoint<dyn RunStore>,
169 embeddings_ep: ExtensionPoint<dyn EmbeddingStore>,
170 artifacts_ep: ExtensionPoint<dyn ArtifactStore>,
171}
172
173impl RuntimeStore {
174 #[must_use]
180 pub fn new(
181 sessions: Box<dyn SessionStore>,
182 executions: Box<dyn ExecutionStore>,
183 runs: Box<dyn RunStore>,
184 ) -> Self {
185 Self {
186 sessions,
187 executions,
188 runs,
189 embeddings: None,
190 artifacts: None,
191 sessions_ep: ExtensionPoint::new(),
192 executions_ep: ExtensionPoint::new(),
193 runs_ep: ExtensionPoint::new(),
194 embeddings_ep: ExtensionPoint::new(),
195 artifacts_ep: ExtensionPoint::new(),
196 }
197 }
198
199 #[must_use]
201 pub fn with_embeddings(mut self, store: Box<dyn EmbeddingStore>) -> Self {
202 self.embeddings = Some(store);
203 self
204 }
205
206 #[must_use]
208 pub fn with_artifacts(mut self, store: Box<dyn ArtifactStore>) -> Self {
209 self.artifacts = Some(store);
210 self
211 }
212
213 #[must_use]
215 pub fn sessions(&self) -> &dyn SessionStore {
216 &*self.sessions
217 }
218
219 #[must_use]
221 pub fn executions(&self) -> &dyn ExecutionStore {
222 &*self.executions
223 }
224
225 #[must_use]
227 pub fn runs(&self) -> &dyn RunStore {
228 &*self.runs
229 }
230
231 #[must_use]
233 pub fn embeddings(&self) -> Option<&dyn EmbeddingStore> {
234 self.embeddings.as_deref()
235 }
236
237 #[must_use]
239 pub fn artifacts(&self) -> Option<&dyn ArtifactStore> {
240 self.artifacts.as_deref()
241 }
242
243 #[must_use]
247 pub fn from_extensions(exts: &Extensions) -> Self {
248 Self {
249 sessions: Box::new(behest_store::memory::MemorySessionStore::new()),
250 executions: Box::new(behest_store::memory::MemoryExecutionStore::new()),
251 runs: Box::new(super::memory::MemoryRunStore::new()),
252 embeddings: None,
253 artifacts: None,
254 sessions_ep: exts.session_stores.clone(),
255 executions_ep: exts.execution_stores.clone(),
256 runs_ep: exts.run_stores.clone(),
257 embeddings_ep: exts.embedding_stores.clone(),
258 artifacts_ep: exts.artifact_stores.clone(),
259 }
260 }
261
262 #[must_use]
264 pub fn sessions_ep(&self) -> &ExtensionPoint<dyn SessionStore> {
265 &self.sessions_ep
266 }
267
268 #[must_use]
270 pub fn executions_ep(&self) -> &ExtensionPoint<dyn ExecutionStore> {
271 &self.executions_ep
272 }
273
274 #[must_use]
276 pub fn runs_ep(&self) -> &ExtensionPoint<dyn RunStore> {
277 &self.runs_ep
278 }
279
280 #[must_use]
282 pub fn embeddings_ep(&self) -> &ExtensionPoint<dyn EmbeddingStore> {
283 &self.embeddings_ep
284 }
285
286 #[must_use]
288 pub fn artifacts_ep(&self) -> &ExtensionPoint<dyn ArtifactStore> {
289 &self.artifacts_ep
290 }
291
292 pub async fn ensure_session(&self, session_id: Option<Uuid>) -> RuntimeResult<Uuid> {
299 if let Some(id) = session_id {
300 self.sessions
301 .get_session(&id)
302 .await
303 .map_err(RuntimeError::from)?
304 .ok_or(RuntimeError::SessionNotFound(id))?;
305 Ok(id)
306 } else {
307 let session =
308 behest_store::Session::new("Agent Run", behest_provider::ModelName::new("default"));
309 self.sessions
310 .create_session(session.clone())
311 .await
312 .map_err(RuntimeError::from)?;
313 Ok(session.id)
314 }
315 }
316
317 pub async fn append_message(&self, session_id: Uuid, message: &Message) -> RuntimeResult<Uuid> {
323 let record = message_to_record(session_id, message);
324 let result = self
325 .sessions
326 .append_message(record)
327 .await
328 .map_err(RuntimeError::from)?;
329 Ok(result.id)
330 }
331
332 pub async fn list_messages(&self, session_id: Uuid) -> RuntimeResult<Vec<Message>> {
338 let records = self
339 .sessions
340 .list_messages(&session_id)
341 .await
342 .map_err(RuntimeError::from)?;
343 Ok(records.into_iter().filter_map(record_to_message).collect())
344 }
345}
346
347fn message_to_record(session_id: Uuid, message: &Message) -> MessageRecord {
352 match message {
353 Message::System { content } => {
354 MessageRecord::new(session_id, MessageRole::System, content.clone())
355 }
356 Message::User { content } => {
357 MessageRecord::new(session_id, MessageRole::User, content.clone())
358 }
359 Message::Assistant {
360 content,
361 tool_calls,
362 } => MessageRecord::new(session_id, MessageRole::Assistant, content.clone())
363 .with_tool_calls(tool_calls.clone()),
364 Message::Tool {
365 tool_call_id,
366 name,
367 content,
368 } => MessageRecord::new(session_id, MessageRole::Tool, content.clone())
369 .with_tool_result(tool_call_id.clone(), name.clone()),
370 _ => MessageRecord::new(session_id, MessageRole::User, vec![]),
371 }
372}
373
374#[must_use]
379pub fn record_to_message(record: MessageRecord) -> Option<Message> {
380 match record.role {
381 MessageRole::System => Some(Message::System {
382 content: record.content,
383 }),
384 MessageRole::User => Some(Message::User {
385 content: record.content,
386 }),
387 MessageRole::Assistant => Some(Message::Assistant {
388 content: record.content,
389 tool_calls: record.tool_calls,
390 }),
391 MessageRole::Tool => Some(Message::Tool {
392 tool_call_id: record.tool_call_id.unwrap_or_default(),
393 name: record.tool_name.unwrap_or_default(),
394 content: record.content,
395 }),
396 _ => None,
397 }
398}