use std::fmt;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use crate::tool::ToolResult;
pub const MAX_NONTERMINAL_BACKGROUND_JOBS: usize = 8;
pub const MAX_TERMINAL_BACKGROUND_JOBS: usize = 32;
pub const MAX_BACKGROUND_OUTPUT_BYTES: usize = 64 * 1024;
pub const BACKGROUND_PROCESS_EVENT_CAPACITY: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct BackgroundJobId(String);
impl BackgroundJobId {
#[must_use]
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for BackgroundJobId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum BackgroundJobState {
Starting,
Running,
Completed,
Failed,
TimedOut,
Cancelled,
SpawnFailed,
SupervisionFailed,
}
impl BackgroundJobState {
#[must_use]
pub const fn is_terminal(self) -> bool {
!matches!(self, Self::Starting | Self::Running)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum BackgroundOutputStream {
Stdout,
Stderr,
}
#[derive(Debug)]
pub struct BackgroundOutputChunk {
pub stream: BackgroundOutputStream,
pub bytes: Vec<u8>,
pub captured_at: SystemTime,
}
#[derive(Debug, Clone, Copy)]
pub struct BackgroundProcessExit {
pub code: Option<i32>,
pub success: bool,
}
#[derive(Debug)]
pub enum BackgroundProcessEvent {
Output(BackgroundOutputChunk),
Exited(BackgroundProcessExit),
SupervisionFailed(String),
}
#[async_trait]
pub trait BackgroundProcessControl: Send + Sync {
async fn terminate(&self) -> Result<(), String>;
async fn force_terminate(&self) -> Result<(), String>;
}
pub struct LaunchedBackgroundJob {
pub control: Arc<dyn BackgroundProcessControl>,
pub events: mpsc::Receiver<BackgroundProcessEvent>,
}
#[async_trait]
pub trait BackgroundJobLauncher: Send {
async fn launch(self: Box<Self>) -> Result<LaunchedBackgroundJob, String>;
}
pub enum ToolExecutionAdmission {
Foreground,
Background(BackgroundJobRequest),
}
pub struct BackgroundJobRequest {
pub tool_name: String,
pub timeout: Duration,
}
#[async_trait]
pub trait BackgroundJobPermit: Send {
async fn launch(self: Box<Self>, launcher: Box<dyn BackgroundJobLauncher>) -> ToolResult;
}
#[async_trait]
pub trait BackgroundJobHost: Send + Sync {
async fn reserve(
&self,
request: BackgroundJobRequest,
) -> Result<Box<dyn BackgroundJobPermit>, String>;
async fn reserve_with_permission_resource(
&self,
request: BackgroundJobRequest,
_start_resource: Option<String>,
) -> Result<Box<dyn BackgroundJobPermit>, String> {
self.reserve(request).await
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum BackgroundCleanupOutcome {
Natural,
Terminated,
ForceTerminated,
Incomplete,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct BackgroundJobTerminalSummary {
pub job_id: BackgroundJobId,
pub tool_name: String,
pub state: BackgroundJobState,
pub exit_code: Option<i32>,
pub stdout_bytes: u64,
pub stderr_bytes: u64,
pub earliest_cursor: u64,
pub next_cursor: u64,
pub truncated: bool,
pub started_at_unix_ms: u64,
pub finished_at_unix_ms: u64,
pub cleanup_outcome: BackgroundCleanupOutcome,
pub cleanup_error: Option<String>,
}