#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
Filesystem(#[from] objectiveai_sdk::filesystem::Error),
#[error("{}", format_http_error(.0))]
Http(#[from] objectiveai_sdk::HttpError),
#[error("{0}")]
ResponseError(objectiveai_sdk::error::ResponseError),
#[error("{0} source is not supported for function-profile pairs")]
PairsSourceNotSupported(&'static str),
#[error("favorite not found: {0}")]
FavoriteNotFound(String),
#[error("{0}")]
MissingArgs(&'static str),
#[error("no python interpreter found (install Python or enable the rustpython feature)")]
PythonNotFound,
#[error("failed to read python file {0}: {1}")]
PythonFileRead(std::path::PathBuf, std::io::Error),
#[error("python exception:\n{0}")]
PythonException(String),
#[error("python output deserialization failed: {0}")]
PythonDeserialize(serde_path_to_error::Error<serde_json::Error>),
#[error("internal error: python harness output is malformed: {0}")]
PythonHarnessBroken(String),
#[error("inline JSON deserialization failed: {0}")]
InlineDeserialize(serde_path_to_error::Error<serde_json::Error>),
#[error("stream ended without producing any chunks")]
EmptyStream,
#[error("config set forbidden by server configuration")]
ConfigSetForbidden,
#[error("log writer task panicked or was cancelled")]
WriterPanic,
#[error("unknown --instructions-id: run the matching `instructions` subcommand to get one")]
UnknownInstructionsId,
#[error("subscribe timed out")]
LogSubscribeTimedOut,
#[error("plugin not found: {0}")]
PluginNotFound(String),
#[error("failed to spawn plugin: {0}")]
PluginSpawn(std::io::Error),
#[error("failed to read plugin output: {0}")]
PluginRead(std::io::Error),
#[error("plugin exited with non-zero status: {0}")]
PluginExit(i32),
#[error("tool not found: {0}")]
ToolNotFound(String),
#[error("failed to spawn tool: {0}")]
ToolSpawn(std::io::Error),
#[error("failed to read tool output: {0}")]
ToolRead(std::io::Error),
#[error("tool exited with non-zero status: {0}")]
ToolExit(i32),
#[error("plugin {owner}/{repository} (commit {commit_sha}, version {version}) is not in the install whitelist; pass --allow-untrusted to install anyway")]
PluginNotWhitelisted {
owner: String,
repository: String,
commit_sha: String,
version: String,
},
#[error("whitelist regex error: {0}")]
WhitelistRegex(regex::Error),
#[error("viewer address is not configured; set VIEWER_ADDRESS in the env or run `objectiveai viewer address config set <addr>` (and optionally `objectiveai viewer port config set <port>`)")]
ViewerAddressNotConfigured,
#[error("viewer path must start with `/`, got {0:?}")]
ViewerPathMissingSlash(String),
#[error("viewer body is not valid JSON: {0}")]
ViewerBodyJsonParse(String),
#[error("viewer http error: {0}")]
ViewerSendHttp(String),
#[error("viewer returned status {status}: {body}")]
ViewerSendBadStatus { status: u16, body: String },
#[error("updater: {0}")]
Updater(String),
#[error("{name} is already running (pids: {pids:?})")]
AlreadyRunning { name: String, pids: Vec<u32> },
#[error("{name} did not announce \"listening\" on stderr before exiting")]
SpawnNoListeningLine { name: String },
#[error("spawn {0}: {1}")]
Spawn(String, std::io::Error),
}
impl Error {
pub fn to_output(
&self,
level: objectiveai_sdk::cli::output::Level,
fatal: bool,
) -> objectiveai_sdk::cli::output::Error {
objectiveai_sdk::cli::output::Error {
level,
fatal,
message: self.output_message(),
agent_id: None,
}
}
pub fn output_message(&self) -> serde_json::Value {
match self {
Error::ResponseError(re) => {
serde_json::to_value(re).unwrap_or_else(|_| self.to_string().into())
}
_ => self.to_string().into(),
}
}
}
fn http_is_connect_failure(err: &objectiveai_sdk::HttpError) -> bool {
use objectiveai_sdk::HttpError as H;
let reqwest_err = match err {
H::StreamError(reqwest_eventsource::Error::Transport(e)) => e,
H::RequestError(e) | H::HttpError(e) => e,
_ => return false,
};
reqwest_err.is_connect() || reqwest_err.is_timeout()
}
fn format_http_error(err: &objectiveai_sdk::HttpError) -> String {
if http_is_connect_failure(err) {
format!(
"{err}\n\nhint: this looks like a connection failure to the configured API address. \
to run an API locally:\n \
1. configure address + port (use an available port):\n \
objectiveai api address config set 127.0.0.1\n \
objectiveai api port config set <port>\n \
2. spawn the server:\n \
objectiveai api spawn"
)
} else {
err.to_string()
}
}