lash_remote_protocol/protocol/
registry_errors.rs1pub 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}