Skip to main content

agent_workspace_contract/
ports.rs

1use super::*;
2
3/// Narrow file capability for consumers that must not gain command execution.
4#[async_trait]
5pub trait WorkspaceFiles: Send + Sync + Debug {
6    async fn file_read(&self, path: &str) -> Result<String>;
7    async fn file_write(&self, path: &str, content: &str) -> Result<()>;
8    async fn directory_create_all(&self, path: &str) -> Result<()>;
9    async fn file_remove(&self, path: &str) -> Result<()>;
10    async fn path_exists(&self, path: &str) -> Result<bool>;
11    async fn directory_list(&self, path: &str) -> Result<Vec<DirEntry>>;
12}
13
14/// Narrow bounded-search capability.
15#[async_trait]
16pub trait WorkspaceSearch: Send + Sync + Debug {
17    async fn search_walk_tree(&self, path: &str, max_depth: usize) -> Result<Vec<String>>;
18    async fn search_find_files(&self, pattern: &str, path: &str) -> Result<Vec<String>>;
19    async fn search_grep(
20        &self,
21        pattern: &str,
22        path: &str,
23        include: Option<&str>,
24    ) -> Result<Vec<GrepMatch>>;
25}
26
27/// Narrow command capability. Possession of this port is an explicit privilege.
28#[async_trait]
29pub trait WorkspaceCommands: Send + Sync + Debug {
30    async fn command_exec(&self, command: &str, cwd: Option<&str>) -> Result<CmdOutput>;
31}
32
33/// Provider-neutral failure signal consumed by transport resilience policies.
34///
35/// Keeping the decision input free of `reqwest` (or any other HTTP type) lets
36/// deployments replace the default backoff/circuit algorithm without pulling
37/// transport implementation into the contract or application layers.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct ProviderRequestFailure {
40    pub status_code: Option<u16>,
41    pub transport_failure: bool,
42    pub retry_after_ms: Option<u64>,
43}
44
45/// Narrow strategy port for provider retry and circuit-opening decisions.
46pub trait ProviderResiliencePolicy: Send + Sync + Debug {
47    /// Return the delay before the next attempt, or `None` to fail closed.
48    /// `completed_attempts` starts at one after the first failed attempt.
49    fn retry_delay_ms(
50        &self,
51        failure: ProviderRequestFailure,
52        completed_attempts: u32,
53        idempotent: bool,
54    ) -> Option<u64>;
55
56    /// Return how long to open the circuit after this many consecutive
57    /// retryable request failures, or `None` to keep it closed.
58    fn circuit_open_ms(&self, consecutive_failures: u32) -> Option<u64>;
59}
60
61#[async_trait]
62impl<T: Workspace + ?Sized> WorkspaceFiles for T {
63    async fn file_read(&self, path: &str) -> Result<String> {
64        Workspace::read_file(self, path).await
65    }
66    async fn file_write(&self, path: &str, content: &str) -> Result<()> {
67        Workspace::write_file(self, path, content).await
68    }
69    async fn directory_create_all(&self, path: &str) -> Result<()> {
70        Workspace::create_dir_all(self, path).await
71    }
72    async fn file_remove(&self, path: &str) -> Result<()> {
73        Workspace::remove_file(self, path).await
74    }
75    async fn path_exists(&self, path: &str) -> Result<bool> {
76        Workspace::exists(self, path).await
77    }
78    async fn directory_list(&self, path: &str) -> Result<Vec<DirEntry>> {
79        Workspace::list_dir(self, path).await
80    }
81}
82
83#[async_trait]
84impl<T: Workspace + ?Sized> WorkspaceSearch for T {
85    async fn search_walk_tree(&self, path: &str, max_depth: usize) -> Result<Vec<String>> {
86        Workspace::walk_tree(self, path, max_depth).await
87    }
88    async fn search_find_files(&self, pattern: &str, path: &str) -> Result<Vec<String>> {
89        Workspace::find_files(self, pattern, path).await
90    }
91    async fn search_grep(
92        &self,
93        pattern: &str,
94        path: &str,
95        include: Option<&str>,
96    ) -> Result<Vec<GrepMatch>> {
97        Workspace::grep(self, pattern, path, include).await
98    }
99}
100
101#[async_trait]
102impl<T: Workspace + ?Sized> WorkspaceCommands for T {
103    async fn command_exec(&self, command: &str, cwd: Option<&str>) -> Result<CmdOutput> {
104        Workspace::exec(self, command, cwd).await
105    }
106}