Skip to main content

agent_workspace_contract/
config.rs

1use super::*;
2use std::collections::BTreeMap;
3
4/// Options for directory listing and recursive search operations.
5#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
6pub struct FileVisibilityOptions {
7    #[serde(default)]
8    pub include_hidden: bool,
9    #[serde(default)]
10    pub include_ignored: bool,
11}
12
13/// Opaque reference resolved only by a trusted adapter.  The secret value is
14/// deliberately impossible to serialize or expose through `Debug`.
15#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(deny_unknown_fields, rename_all = "camelCase")]
17pub struct SecretRef {
18    pub provider: String,
19    pub key: String,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub version: Option<String>,
22}
23
24impl Debug for SecretRef {
25    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        formatter
27            .debug_struct("SecretRef")
28            .field("provider", &self.provider)
29            .field("key", &"[REDACTED]")
30            .field("version", &self.version.as_ref().map(|_| "[REDACTED]"))
31            .finish()
32    }
33}
34
35/// Provider-neutral provisioning input. Provider-specific SDK/configuration
36/// belongs to an adapter crate; the contract contains only a backend kind,
37/// opaque settings and secret references.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(deny_unknown_fields, rename_all = "camelCase")]
40pub struct WorkspaceConfig {
41    pub backend: String,
42    #[serde(default)]
43    pub settings: BTreeMap<String, String>,
44    #[serde(default)]
45    pub secrets: BTreeMap<String, SecretRef>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub durable_backend_id: Option<String>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub working_root: Option<PathBuf>,
50}
51
52impl Default for WorkspaceConfig {
53    fn default() -> Self {
54        Self {
55            backend: "local".into(),
56            settings: BTreeMap::new(),
57            secrets: BTreeMap::new(),
58            durable_backend_id: None,
59            working_root: Some(std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))),
60        }
61    }
62}
63
64/// Resolved durable backend ID discovered while initializing a workspace.
65/// This remains provider-neutral on the wire.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(transparent)]
68pub struct ResolvedBackendId(pub String);
69
70impl ResolvedBackendId {
71    pub fn as_str(&self) -> &str {
72        &self.0
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn secret_ref_never_debugs_secret_coordinates() {
82        let secret = SecretRef {
83            provider: "vault".into(),
84            key: "prod/workspace/api-token".into(),
85            version: Some("42".into()),
86        };
87        let debug = format!("{secret:?}");
88        assert!(!debug.contains("prod/workspace/api-token"));
89        assert!(!debug.contains("42"));
90        let json = serde_json::to_string(&secret).unwrap();
91        assert!(!json.contains("token-value"));
92    }
93
94    #[test]
95    fn provider_config_contains_references_not_plaintext_credentials() {
96        let json = r#"{"backend":"daytona","settings":{"apiUrl":"https://provider"},"secrets":{"credential":{"provider":"vault","key":"workspace/daytona"}}}"#;
97        let config: WorkspaceConfig = serde_json::from_str(json).unwrap();
98        assert_eq!(config.backend, "daytona");
99        assert_eq!(config.secrets["credential"].provider, "vault");
100    }
101}