Skip to main content

objectiveai_cli/filesystem/plugins/
install_error.rs

1use reqwest::StatusCode;
2use std::path::PathBuf;
3
4/// Failures the plugin install pipeline can encounter. Shape modelled
5/// after `objectiveai-api`'s `github::Error`: split request / response
6/// / status / parse so diagnostics name what failed, plus IO variants
7/// for the local-disk side of the flow.
8#[derive(thiserror::Error, Debug)]
9pub enum InstallError {
10    #[error("manifest request failed: {0}")]
11    ManifestRequest(reqwest::Error),
12    #[error("manifest fetch returned bad status {code} from {url}: {body}")]
13    ManifestBadStatus {
14        code: StatusCode,
15        url: String,
16        body: String,
17    },
18    #[error("manifest body could not be read: {0}")]
19    ManifestResponse(reqwest::Error),
20    #[error("manifest parse failed: {0}")]
21    ManifestParse(serde_path_to_error::Error<serde_json::Error>),
22    #[error("cli-zip request failed: {0}")]
23    CliZipRequest(reqwest::Error),
24    #[error("cli-zip fetch returned bad status {code} from {url}")]
25    CliZipBadStatus { code: StatusCode, url: String },
26    #[error("cli-zip body could not be read: {0}")]
27    CliZipResponse(reqwest::Error),
28    #[error("failed to create plugin directory {0}: {1}")]
29    PluginDirCreate(PathBuf, std::io::Error),
30    #[error("failed to serialize manifest for persistence: {0}")]
31    ManifestSerialize(serde_json::Error),
32    #[error("failed to persist manifest at {0}: {1}")]
33    ManifestPersist(PathBuf, std::io::Error),
34    #[error("viewer-zip request failed: {0}")]
35    ViewerZipRequest(reqwest::Error),
36    #[error("viewer-zip fetch returned bad status {code} from {url}")]
37    ViewerZipBadStatus { code: StatusCode, url: String },
38    #[error("viewer-zip body could not be read: {0}")]
39    ViewerZipResponse(reqwest::Error),
40    #[error("failed to extract zip into {0}: {1}")]
41    ZipExtract(PathBuf, String),
42    #[error("invalid header name {name:?}: {reason}")]
43    InvalidHeaderName { name: String, reason: String },
44    #[error("invalid header value for {name:?}: {reason}")]
45    InvalidHeaderValue { name: String, reason: String },
46    #[error(
47        "plugin {repository} is already installed; pass `--upgrade` to replace it"
48    )]
49    AlreadyInstalled { repository: String },
50    #[error(
51        "repository name {repository:?} is reserved and cannot be used as a plugin name"
52    )]
53    ReservedRepositoryName { repository: String },
54    #[error("manifest failed validation: {0}")]
55    ManifestInvalid(&'static str),
56    /// `owner`, `repository`, or `commit` does not match the
57    /// `^[A-Za-z0-9_.-]{1,128}$` shape required for them to land in a
58    /// usable LLM tool name (Anthropic enforces `^[a-zA-Z0-9_-]{1,128}$`;
59    /// we additionally allow `.` so semver-shaped version strings can
60    /// flow through, with `.` -> `-` substitution applied when the tool
61    /// name is materialized).
62    #[error("invalid {kind}: {value:?} must match ^[A-Za-z0-9_.-]{{1,128}}$")]
63    InvalidIdentifier { kind: &'static str, value: String },
64    /// The materialized tool name (`{owner}-{name}-{version}` with `.`
65    /// substituted to `-`) is longer than the 100-character budget we
66    /// reserve. Anthropic's hard cap is 128 chars; the 28-char gap
67    /// leaves headroom for the proxy prefix the MCP server stamps on
68    /// (`oai_`, `oaifs_`, etc.) plus any future prefix growth.
69    #[error("tool name {tool_name:?} is {len} chars long; max 100")]
70    ToolNameTooLong { tool_name: String, len: usize },
71}