Skip to content

Commit b239add

Browse files
Copilot0xrinegade
andcommitted
Implement comprehensive SDK improvements: async consistency, centralized deserialization, error handling, and integration tests
Co-authored-by: 0xrinegade <[email protected]>
1 parent baa2ab3 commit b239add

File tree

9 files changed

+490
-44
lines changed

9 files changed

+490
-44
lines changed

rust/src/agent/mod.rs

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,29 +91,108 @@ pub enum AgentRegistryInstruction {
9191
}
9292

9393
/// Maximum length constants (from the on-chain program)
94+
/// These limits are enforced by the Solana AI Registries program to ensure
95+
/// efficient storage and consistent behavior across the network.
96+
///
97+
/// **References:**
98+
/// - On-chain program constraints: [Agent Registry Program Documentation](https://docs.solana-ai-registries.org/program/agent-registry)
99+
/// - Program source: `programs/agent-registry/src/lib.rs`
100+
101+
/// Maximum length for agent ID field (64 bytes)
102+
/// This allows for reasonably descriptive identifiers while keeping storage efficient.
103+
/// Must be unique within the owner's namespace.
94104
pub const MAX_AGENT_ID_LEN: usize = 64;
105+
106+
/// Maximum length for agent name field (128 bytes)
107+
/// Provides space for human-readable agent names and titles.
95108
pub const MAX_AGENT_NAME_LEN: usize = 128;
109+
110+
/// Maximum length for agent description field (512 bytes)
111+
/// Allows for detailed descriptions while preventing excessive storage costs.
96112
pub const MAX_AGENT_DESCRIPTION_LEN: usize = 512;
113+
114+
/// Maximum length for agent version string (32 bytes)
115+
/// Follows semantic versioning conventions (e.g., "1.2.3-beta").
97116
pub const MAX_AGENT_VERSION_LEN: usize = 32;
117+
118+
/// Maximum length for provider name field (128 bytes)
119+
/// Name of the organization or individual providing the agent.
98120
pub const MAX_PROVIDER_NAME_LEN: usize = 128;
121+
122+
/// Maximum length for provider URL field (256 bytes)
123+
/// URL to the provider's website or profile page.
99124
pub const MAX_PROVIDER_URL_LEN: usize = 256;
125+
126+
/// Maximum length for documentation URL field (256 bytes)
127+
/// URL pointing to the agent's documentation or API reference.
100128
pub const MAX_DOCUMENTATION_URL_LEN: usize = 256;
129+
130+
/// Maximum number of service endpoints per agent (3 endpoints)
131+
/// Supports primary, fallback, and development endpoints without excessive complexity.
101132
pub const MAX_SERVICE_ENDPOINTS: usize = 3;
133+
134+
/// Maximum length for service endpoint protocol field (64 bytes)
135+
/// e.g., "http", "https", "websocket", "grpc"
102136
pub const MAX_ENDPOINT_PROTOCOL_LEN: usize = 64;
137+
138+
/// Maximum length for service endpoint URL field (256 bytes)
139+
/// Full URL including protocol, domain, port, and path.
103140
pub const MAX_ENDPOINT_URL_LEN: usize = 256;
141+
142+
/// Maximum number of supported input/output modes per agent (5 modes)
143+
/// Balances flexibility with storage efficiency.
104144
pub const MAX_SUPPORTED_MODES: usize = 5;
145+
146+
/// Maximum length for input/output mode strings (64 bytes)
147+
/// e.g., "text", "json", "image", "audio"
105148
pub const MAX_MODE_LEN: usize = 64;
149+
150+
/// Maximum number of skills per agent (10 skills)
151+
/// Allows comprehensive skill representation while preventing abuse.
106152
pub const MAX_SKILLS: usize = 10;
153+
154+
/// Maximum length for skill ID field (64 bytes)
155+
/// Unique identifier for the skill within the agent.
107156
pub const MAX_SKILL_ID_LEN: usize = 64;
157+
158+
/// Maximum length for skill name field (128 bytes)
159+
/// Human-readable name for the skill.
108160
pub const MAX_SKILL_NAME_LEN: usize = 128;
161+
162+
/// Maximum number of tags per skill (5 tags)
163+
/// Enables categorization and discovery without overwhelming metadata.
109164
pub const MAX_SKILL_TAGS: usize = 5;
165+
166+
/// Maximum length for skill tag strings (32 bytes)
167+
/// e.g., "nlp", "vision", "reasoning"
110168
pub const MAX_SKILL_TAG_LEN: usize = 32;
169+
170+
/// Maximum length for security info URI field (256 bytes)
171+
/// URL pointing to security audit reports or vulnerability disclosures.
111172
pub const MAX_SECURITY_INFO_URI_LEN: usize = 256;
173+
174+
/// Maximum length for AEA (Autonomous Economic Agent) address field (128 bytes)
175+
/// Address format specific to the AEA framework.
112176
pub const MAX_AEA_ADDRESS_LEN: usize = 128;
177+
178+
/// Maximum length for economic intent summary field (256 bytes)
179+
/// Brief description of the agent's economic model and objectives.
113180
pub const MAX_ECONOMIC_INTENT_LEN: usize = 256;
181+
182+
/// Maximum length for extended metadata URI field (256 bytes)
183+
/// URL pointing to additional metadata stored off-chain.
114184
pub const MAX_EXTENDED_METADATA_URI_LEN: usize = 256;
185+
186+
/// Maximum number of tags per agent (10 tags)
187+
/// Enables rich categorization for discovery and filtering.
115188
pub const MAX_AGENT_TAGS: usize = 10;
189+
190+
/// Maximum length for agent tag strings (32 bytes)
191+
/// e.g., "chatbot", "trading", "analytics"
116192
pub const MAX_AGENT_TAG_LEN: usize = 32;
193+
194+
/// Hash size for content addressing (32 bytes)
195+
/// SHA-256 hash size used for content verification.
117196
pub const HASH_SIZE: usize = 32;
118197

119198
/// Agent status values
@@ -345,15 +424,7 @@ pub struct AgentEntry {
345424
impl AgentEntry {
346425
/// Try to deserialize from account data
347426
pub fn try_from_account_data(data: &[u8]) -> SdkResult<Self> {
348-
// Skip the 8-byte discriminator used by Anchor
349-
if data.len() < 8 {
350-
return Err(SdkError::InvalidAccountData);
351-
}
352-
353-
let account_data = &data[8..];
354-
Self::try_from_slice(account_data).map_err(|e| {
355-
SdkError::DeserializationError(format!("Failed to deserialize agent entry: {}", e))
356-
})
427+
crate::client::deserialize_account_data(data, "agent entry")
357428
}
358429
}
359430

rust/src/client.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use crate::agent::{AgentArgs, AgentEntry, AgentPatch};
77
use crate::errors::{SdkError, SdkResult};
88
use crate::mcp::{McpServerArgs, McpServerEntry, McpServerPatch};
9+
use borsh::BorshDeserialize;
910
use solana_client::rpc_client::RpcClient;
1011
use solana_sdk::{
1112
commitment_config::CommitmentConfig,
@@ -347,3 +348,28 @@ mod tests {
347348
);
348349
}
349350
}
351+
352+
/// Centralized helper for deserializing account data with Anchor discriminator handling
353+
///
354+
/// This function abstracts the common pattern of:
355+
/// 1. Validating minimum data length (8 bytes for discriminator)
356+
/// 2. Skipping the 8-byte Anchor discriminator
357+
/// 3. Deserializing the remaining data using Borsh
358+
/// 4. Providing consistent error handling and wrapping
359+
pub fn deserialize_account_data<T>(data: &[u8], type_name: &str) -> SdkResult<T>
360+
where
361+
T: borsh::BorshDeserialize,
362+
{
363+
// Validate minimum length for Anchor discriminator
364+
if data.len() < 8 {
365+
return Err(SdkError::InvalidAccountData);
366+
}
367+
368+
// Skip the 8-byte discriminator used by Anchor
369+
let account_data = &data[8..];
370+
371+
// Deserialize with type-specific error message
372+
T::try_from_slice(account_data).map_err(|e| {
373+
SdkError::DeserializationError(format!("Failed to deserialize {}: {}", type_name, e))
374+
})
375+
}

rust/src/errors.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ pub enum SdkError {
265265

266266
#[error("Validation error: {0}")]
267267
ValidationError(String),
268+
269+
/// Unknown program error code - used for safe error catching
270+
#[error("Unknown program error code: {0}")]
271+
UnknownError(u32),
268272
}
269273

270274
impl SdkError {
@@ -323,7 +327,7 @@ impl SdkError {
323327
49 => SdkError::InvalidStakingTier,
324328
50 => SdkError::LockPeriodTooShort,
325329
51 => SdkError::LockPeriodTooLong,
326-
_ => SdkError::InvalidAccountData,
330+
_ => SdkError::UnknownError(code),
327331
}
328332
}
329333
}

rust/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub mod mcp;
5656
pub mod payments;
5757

5858
// Re-export commonly used types
59-
pub use client::SolanaAiRegistriesClient;
59+
pub use client::{deserialize_account_data, SolanaAiRegistriesClient};
6060
pub use errors::{SdkError, SdkResult};
6161

6262
// Re-export agent types

rust/src/mcp/mod.rs

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,26 +90,96 @@ pub enum McpServerRegistryInstruction {
9090
}
9191

9292
/// Maximum length constants (from the on-chain program)
93+
/// These limits are enforced by the Solana AI Registries program to ensure
94+
/// efficient storage and consistent behavior across the network.
95+
///
96+
/// **References:**
97+
/// - On-chain program constraints: [MCP Server Registry Program Documentation](https://docs.solana-ai-registries.org/program/mcp-registry)
98+
/// - Program source: `programs/mcp-server-registry/src/lib.rs`
99+
/// - MCP Protocol Specification: [Model Context Protocol](https://modelcontextprotocol.io/docs)
100+
101+
/// Maximum length for MCP server ID field (64 bytes)
102+
/// Must be unique within the owner's namespace and follow MCP naming conventions.
93103
pub const MAX_SERVER_ID_LEN: usize = 64;
104+
105+
/// Maximum length for MCP server name field (128 bytes)
106+
/// Human-readable name for the MCP server.
94107
pub const MAX_SERVER_NAME_LEN: usize = 128;
108+
109+
/// Maximum length for server version string (32 bytes)
110+
/// Follows semantic versioning conventions (e.g., "1.2.3-beta").
95111
pub const MAX_SERVER_VERSION_LEN: usize = 32;
112+
113+
/// Maximum length for service endpoint URL field (256 bytes)
114+
/// Full URL where the MCP server can be accessed.
96115
pub const MAX_SERVICE_ENDPOINT_URL_LEN: usize = 256;
116+
117+
/// Maximum length for documentation URL field (256 bytes)
118+
/// URL pointing to the MCP server's documentation or API reference.
97119
pub const MAX_DOCUMENTATION_URL_LEN: usize = 256;
120+
121+
/// Maximum length for server capabilities summary field (256 bytes)
122+
/// Brief description of the server's capabilities and features.
98123
pub const MAX_SERVER_CAPABILITIES_SUMMARY_LEN: usize = 256;
124+
125+
/// Maximum number of on-chain tool definitions per server (5 tools)
126+
/// Balances discoverability with storage efficiency. Additional tools can be defined off-chain.
99127
pub const MAX_ONCHAIN_TOOL_DEFINITIONS: usize = 5;
128+
129+
/// Maximum length for tool name field (64 bytes)
130+
/// Name of the tool as defined in the MCP protocol.
100131
pub const MAX_TOOL_NAME_LEN: usize = 64;
132+
133+
/// Maximum number of tags per tool (3 tags)
134+
/// Enables categorization while keeping metadata concise.
101135
pub const MAX_TOOL_TAGS: usize = 3;
136+
137+
/// Maximum length for tool tag strings (32 bytes)
138+
/// e.g., "search", "analysis", "generation"
102139
pub const MAX_TOOL_TAG_LEN: usize = 32;
140+
141+
/// Maximum number of on-chain resource definitions per server (5 resources)
142+
/// Balances discoverability with storage efficiency. Additional resources can be defined off-chain.
103143
pub const MAX_ONCHAIN_RESOURCE_DEFINITIONS: usize = 5;
144+
145+
/// Maximum length for resource URI pattern field (128 bytes)
146+
/// Pattern matching resources provided by the server (e.g., "files://*", "docs/**/*.md").
104147
pub const MAX_RESOURCE_URI_PATTERN_LEN: usize = 128;
148+
149+
/// Maximum number of tags per resource definition (3 tags)
150+
/// Enables categorization while keeping metadata concise.
105151
pub const MAX_RESOURCE_TAGS: usize = 3;
152+
153+
/// Maximum length for resource tag strings (32 bytes)
154+
/// e.g., "document", "image", "data"
106155
pub const MAX_RESOURCE_TAG_LEN: usize = 32;
156+
157+
/// Maximum number of on-chain prompt definitions per server (5 prompts)
158+
/// Balances discoverability with storage efficiency. Additional prompts can be defined off-chain.
107159
pub const MAX_ONCHAIN_PROMPT_DEFINITIONS: usize = 5;
160+
161+
/// Maximum length for prompt name field (64 bytes)
162+
/// Name of the prompt template as defined in the MCP protocol.
108163
pub const MAX_PROMPT_NAME_LEN: usize = 64;
164+
165+
/// Maximum number of tags per prompt definition (3 tags)
166+
/// Enables categorization while keeping metadata concise.
109167
pub const MAX_PROMPT_TAGS: usize = 3;
168+
169+
/// Maximum length for prompt tag strings (32 bytes)
170+
/// e.g., "template", "assistant", "system"
110171
pub const MAX_PROMPT_TAG_LEN: usize = 32;
172+
173+
/// Maximum length for full capabilities URI field (256 bytes)
174+
/// URL pointing to complete MCP server capabilities document (JSON schema).
111175
pub const MAX_FULL_CAPABILITIES_URI_LEN: usize = 256;
176+
177+
/// Maximum number of tags per MCP server (10 tags)
178+
/// Enables rich categorization for discovery and filtering.
112179
pub const MAX_SERVER_TAGS: usize = 10;
180+
181+
/// Maximum length for server tag strings (32 bytes)
182+
/// e.g., "nlp", "database", "api", "workflow"
113183
pub const MAX_SERVER_TAG_LEN: usize = 32;
114184

115185
/// MCP Server status values
@@ -346,15 +416,7 @@ pub struct McpServerEntry {
346416
impl McpServerEntry {
347417
/// Try to deserialize from account data
348418
pub fn try_from_account_data(data: &[u8]) -> SdkResult<Self> {
349-
// Skip the 8-byte discriminator used by Anchor
350-
if data.len() < 8 {
351-
return Err(SdkError::InvalidAccountData);
352-
}
353-
354-
let account_data = &data[8..];
355-
Self::try_from_slice(account_data).map_err(|e| {
356-
SdkError::DeserializationError(format!("Failed to deserialize MCP server entry: {}", e))
357-
})
419+
crate::client::deserialize_account_data(data, "MCP server entry")
358420
}
359421
}
360422

0 commit comments

Comments
 (0)