use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{AgentSpec, ArtifactId, ExecutionHandle, Run, RunError, RunId, RunStatus, RuntimeKind};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionContext {
pub id: String,
pub runtime_kind: RuntimeKind,
pub working_dir: Option<std::path::PathBuf>,
pub env: std::collections::HashMap<String, String>,
pub limits: ResourceLimits,
}
impl ExecutionContext {
pub fn new(id: impl Into<String>, runtime_kind: RuntimeKind) -> Self {
ExecutionContext {
id: id.into(),
runtime_kind,
working_dir: None,
env: std::collections::HashMap::new(),
limits: ResourceLimits::default(),
}
}
pub fn with_working_dir(mut self, path: std::path::PathBuf) -> Self {
self.working_dir = Some(path);
self
}
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.insert(key.into(), value.into());
self
}
pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
self.limits = limits;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceLimits {
pub max_cpu_cores: u32,
pub max_memory_bytes: u64,
pub max_time_seconds: u64,
}
#[derive(Debug, Clone)]
pub struct ExecutionResult {
pub run_id: RunId,
pub status: RunStatus,
pub artifacts: Vec<ArtifactId>,
pub error: Option<RunError>,
pub metrics: ExecutionMetrics,
pub output: Option<Value>,
}
impl ExecutionResult {
pub fn new(run_id: RunId, status: RunStatus) -> Self {
ExecutionResult {
run_id,
status,
artifacts: Vec::new(),
error: None,
metrics: ExecutionMetrics::default(),
output: None,
}
}
pub fn completed(run_id: RunId) -> Self {
ExecutionResult::new(run_id, RunStatus::Completed)
}
pub fn failed(run_id: RunId, error: RunError) -> Self {
ExecutionResult {
run_id,
status: RunStatus::Failed,
artifacts: Vec::new(),
error: Some(error),
metrics: ExecutionMetrics::default(),
output: None,
}
}
pub fn cancelled(run_id: RunId, reason: String) -> Self {
ExecutionResult {
run_id,
status: RunStatus::Cancelled,
artifacts: Vec::new(),
error: Some(RunError::Cancelled { reason }),
metrics: ExecutionMetrics::default(),
output: None,
}
}
pub fn with_artifacts(mut self, artifacts: Vec<ArtifactId>) -> Self {
self.artifacts = artifacts;
self
}
pub fn with_error(mut self, error: RunError) -> Self {
self.error = Some(error);
self
}
pub fn with_metrics(mut self, metrics: ExecutionMetrics) -> Self {
self.metrics = metrics;
self
}
pub fn with_output(mut self, output: Value) -> Self {
self.output = Some(output);
self
}
pub fn with_exposed_output(mut self, output: Value) -> Self {
self.output = Some(output);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ExecutionMetrics {
pub wall_time_ms: u64,
pub cpu_time_ms: u64,
pub peak_memory_bytes: u64,
pub retries: u32,
pub selection_trace: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusResult {
pub run_id: RunId,
pub status: RunStatus,
pub current_step: Option<String>,
pub progress: u8,
pub elapsed_ms: u64,
pub artifacts: Vec<ArtifactId>,
}
#[async_trait::async_trait]
pub trait RuntimeAdapter: Send + Sync {
fn kind(&self) -> RuntimeKind;
async fn create(&self, spec: &AgentSpec) -> Result<ExecutionContext, RunError>;
async fn execute(&self, ctx: &ExecutionContext, run: &Run)
-> Result<ExecutionHandle, RunError>;
async fn execute_background(
&self,
ctx: &ExecutionContext,
run: &Run,
) -> Result<ExecutionHandle, RunError>;
async fn status(&self, handle: &ExecutionHandle) -> Result<StatusResult, RunError>;
async fn destroy(&self, ctx: &ExecutionContext) -> Result<(), RunError>;
async fn cancel(&self, handle: &ExecutionHandle) -> Result<(), RunError>;
async fn wait(&self, handle: &ExecutionHandle) -> Result<ExecutionResult, RunError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_context() {
let ctx = ExecutionContext::new("test-ctx", RuntimeKind::Local)
.with_working_dir(std::path::PathBuf::from("/tmp"))
.with_env("KEY", "value");
assert_eq!(ctx.id, "test-ctx");
assert_eq!(ctx.runtime_kind, RuntimeKind::Local);
assert!(ctx.working_dir.is_some());
assert_eq!(ctx.env.get("KEY"), Some(&"value".to_string()));
}
#[test]
fn test_resource_limits() {
let limits = ResourceLimits {
max_cpu_cores: 4,
max_memory_bytes: 1024 * 1024 * 1024, max_time_seconds: 3600,
};
assert_eq!(limits.max_cpu_cores, 4);
assert_eq!(limits.max_memory_bytes, 1024 * 1024 * 1024);
}
}