Skip to main content

agent_workspace_contract/
io.rs

1use super::*;
2
3pub const WORKSPACE_VERSION_CONFLICT_CODE: &str = "VERSION_CONFLICT";
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct WorkspaceVersionConflict {
7    pub expected: String,
8    pub actual: Option<String>,
9}
10
11impl fmt::Display for WorkspaceVersionConflict {
12    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
13        write!(
14            formatter,
15            "workspace file revision conflict: expected {}, actual {}",
16            self.expected,
17            self.actual.as_deref().unwrap_or("<missing>")
18        )
19    }
20}
21
22impl std::error::Error for WorkspaceVersionConflict {}
23
24/// Collision-resistant content revision shared by CAS and idempotency paths.
25pub fn content_revision(bytes: &[u8]) -> String {
26    use sha2::{Digest as _, Sha256};
27    format!("sha256:{:x}", Sha256::digest(bytes))
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct VersionedFile {
32    pub bytes: Vec<u8>,
33    pub revision: String,
34}
35
36/// A directory entry returned by `list_dir`.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct DirEntry {
39    pub name: String,
40    pub is_dir: bool,
41    pub size: Option<u64>,
42}
43
44#[cfg(test)]
45mod revision_tests {
46    use super::*;
47
48    #[test]
49    fn revision_is_sha256_and_separates_known_fnv_collision_inputs() {
50        // "costarring" and "liquid" are a well-known FNV-1a-32 collision.
51        // Security revisions no longer use any FNV width or accept its output.
52        let left = content_revision(b"costarring");
53        let right = content_revision(b"liquid");
54        assert_ne!(left, right);
55        for revision in [left, right] {
56            let digest = revision.strip_prefix("sha256:").unwrap();
57            assert_eq!(digest.len(), 64);
58            assert!(digest.bytes().all(|byte| byte.is_ascii_hexdigit()));
59        }
60    }
61}
62
63/// Output of a command execution.
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct CmdOutput {
66    pub exit_code: i32,
67    pub stdout: String,
68    pub stderr: String,
69    pub stdout_truncated: bool,
70    pub stderr_truncated: bool,
71}
72
73/// Public preview URL for a port exposed by a workspace backend.
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct PortUrl {
76    pub port: u16,
77    pub url: String,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub host: Option<String>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    /// Opaque credential reference; preview bearer values never enter the
82    /// resource JSON or Debug output.
83    pub token: Option<SecretRef>,
84    #[serde(default)]
85    pub signed: bool,
86    /// Effective lifetime returned by a signed-preview provider.
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub expires_in_seconds: Option<u64>,
89}
90
91/// Provider-only authentication for an application-port target.
92///
93/// Keeping the header scheme in the type prevents provider adapters from
94/// accidentally sending a management credential through the wrong header.
95#[derive(Clone, PartialEq, Eq)]
96pub enum ProviderPortAuthorization {
97    AccessToken(String),
98    TrafficAccessToken(String),
99    Bearer(String),
100}
101
102impl std::fmt::Debug for ProviderPortAuthorization {
103    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            Self::AccessToken(_) => formatter.write_str("AccessToken([REDACTED])"),
106            Self::TrafficAccessToken(_) => formatter.write_str("TrafficAccessToken([REDACTED])"),
107            Self::Bearer(_) => formatter.write_str("Bearer([REDACTED])"),
108        }
109    }
110}
111
112/// Provider-only application-port target resolved inside Agent Infra.
113/// Credentials are deliberately non-serializable and redacted from Debug.
114#[derive(Clone, PartialEq, Eq)]
115pub struct ProviderPortTarget {
116    pub url: String,
117    pub authorization: Option<ProviderPortAuthorization>,
118}
119
120impl std::fmt::Debug for ProviderPortTarget {
121    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        formatter
123            .debug_struct("ProviderPortTarget")
124            .field("url", &self.url)
125            .field("authorization", &self.authorization)
126            .finish()
127    }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct StaticPreviewServer {
132    pub id: String,
133    pub port: u16,
134}
135
136/// A grep match result.
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct GrepMatch {
139    pub file: String,
140    pub line_number: usize,
141    pub line: String,
142}