Skip to main content

lash_remote_protocol/protocol/
registry_errors.rs

1pub trait RemoteToolRegistry {
2    fn grants(&self) -> Vec<RemoteToolGrant>;
3
4    fn validate_registry(&self) -> Result<(), RemoteProtocolError> {
5        RemoteToolGrant::validate_all(&self.grants())
6    }
7}
8
9pub fn assert_remote_tool_registry_reopenable(
10    before: &dyn RemoteToolRegistry,
11    after_reopen: &dyn RemoteToolRegistry,
12) -> Result<(), RemoteProtocolError> {
13    let before_grants = before.grants();
14    let after_grants = after_reopen.grants();
15    RemoteToolGrant::validate_all(&before_grants)?;
16    RemoteToolGrant::validate_all(&after_grants)?;
17    let before_paths = remote_registry_call_paths(&before_grants)?;
18    let after_paths = remote_registry_call_paths(&after_grants)?;
19    if before_paths != after_paths {
20        return Err(RemoteProtocolError::RemoteToolRegistryReopenMismatch {
21            before_call_paths: before_paths,
22            after_call_paths: after_paths,
23        });
24    }
25    Ok(())
26}
27
28fn remote_registry_call_paths(
29    grants: &[RemoteToolGrant],
30) -> Result<Vec<String>, RemoteProtocolError> {
31    let mut call_paths = grants
32        .iter()
33        .map(RemoteToolGrant::call_path)
34        .collect::<Result<Vec<_>, _>>()?;
35    call_paths.sort();
36    Ok(call_paths)
37}
38
39fn require_non_empty(
40    type_name: &'static str,
41    field: &'static str,
42    value: &str,
43) -> Result<(), RemoteProtocolError> {
44    if value.trim().is_empty() {
45        Err(RemoteProtocolError::MissingRequiredField { type_name, field })
46    } else {
47        Ok(())
48    }
49}
50
51#[derive(Debug, thiserror::Error)]
52pub enum RemoteProtocolError {
53    #[error("unsupported remote protocol version {actual}; expected {expected}")]
54    UnsupportedProtocolVersion { actual: u32, expected: u32 },
55    #[error(
56        "mismatched protocol version in {parent}.{child}: got {child_version}, expected {parent_version}"
57    )]
58    MismatchedNestedProtocolVersion {
59        parent: &'static str,
60        child: &'static str,
61        parent_version: u32,
62        child_version: u32,
63    },
64    #[error("{type_name}.{field} is required")]
65    MissingRequiredField {
66        type_name: &'static str,
67        field: &'static str,
68    },
69    #[error("invalid {type_name}: {message}")]
70    InvalidEnvelope {
71        type_name: &'static str,
72        message: String,
73    },
74    #[error("invalid image blob `{id}`: {message}")]
75    InvalidImageBlob { id: String, message: String },
76    #[error("invalid attachment reference `{id}`: {message}")]
77    InvalidAttachmentRef { id: String, message: String },
78    #[error("turn input is not remote-safe: {0}")]
79    NonRemoteSafeTurnInput(String),
80    #[error("remote tool grant `{tool_name}` is missing an explicit lashlang binding")]
81    MissingLashlangToolBinding { tool_name: String },
82    #[error("invalid remote tool grant `{tool_name}`: {message}")]
83    InvalidToolGrant { tool_name: String, message: String },
84    #[error("duplicate remote tool call path `{call_path}`")]
85    DuplicateRemoteCallPath { call_path: String },
86    #[error(
87        "remote tool registry changed across reopen: before={before_call_paths:?}, after={after_call_paths:?}"
88    )]
89    RemoteToolRegistryReopenMismatch {
90        before_call_paths: Vec<String>,
91        after_call_paths: Vec<String>,
92    },
93    #[error("unknown remote tool `{tool_name}`")]
94    UnknownRemoteTool { tool_name: String },
95    #[error("remote tool transport failed: {0}")]
96    RemoteToolTransport(String),
97    #[error("failed to serialize remote activity: {0}")]
98    ActivitySerialization(#[from] serde_json::Error),
99    #[error("failed to write remote activity: {0}")]
100    ActivityWrite(String),
101}