Skip to main content

lash_remote_protocol/
registry_errors.rs

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