Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/llm-coding-tools-agents/src/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub trait RulesetExt {
}

impl RulesetExt for Ruleset {
/// # Errors
/// - Returns [`ExpandError`] when a permission pattern is invalid (contains `:`, `//`, or empty segments).
fn from_permission_config(
config: &IndexMap<String, PermissionRule>,
) -> Result<Ruleset, ExpandError> {
Expand Down
34 changes: 24 additions & 10 deletions src/llm-coding-tools-agents/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ impl AgentLoader {
/// * `directory` - Root directory to scan
///
/// # Errors
///
/// Returns an error only for directory-level failures (e.g., path is not a directory).
/// - Returns [`AgentLoadError::Io`] when the directory path exists but is not a directory.
/// - Returns [`AgentLoadError::Io`] when the directory cannot be accessed (permissions, etc.).
Comment thread
Sewer56 marked this conversation as resolved.
pub fn add_directory(
&self,
catalog: &mut AgentCatalog,
Expand All @@ -103,10 +103,9 @@ impl AgentLoader {
/// * `on_error` - Callback invoked for each file that fails to load
///
/// # Errors
///
/// Returns an error only for directory-level failures (e.g., path is not a directory).
/// Individual file load failures are reported via `on_error` and do not fail the overall
/// operation.
/// - Returns [`AgentLoadError::Io`] when the directory path exists but is not a directory.
/// - Returns [`AgentLoadError::Io`] when the directory cannot be accessed (permissions, etc.).
/// - Individual file load failures are reported via `on_error` and do not fail the operation.
pub fn add_directory_with_errors(
&self,
catalog: &mut AgentCatalog,
Expand Down Expand Up @@ -135,6 +134,12 @@ impl AgentLoader {
///
/// * `catalog` - The catalog to insert the agent into
/// * `path` - Path to a markdown file with YAML frontmatter
///
/// # Errors
/// - Returns [`AgentLoadError::SchemaValidation`] when the file name (stem) is empty.
/// - Returns [`AgentLoadError::Io`] when the file cannot be read.
/// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails.
/// - Returns [`AgentLoadError::SchemaValidation`] when schema validation fails.
pub fn add_file(
&self,
catalog: &mut AgentCatalog,
Expand Down Expand Up @@ -165,6 +170,12 @@ impl AgentLoader {
/// * `catalog` - The catalog to insert the agent into
/// * `path` - Path to a markdown file with YAML frontmatter
/// * `name` - Explicit agent name to use
///
/// # Errors
/// - Returns [`AgentLoadError::SchemaValidation`] when the explicit name is empty.
/// - Returns [`AgentLoadError::Io`] when the file cannot be read.
/// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails.
/// - Returns [`AgentLoadError::SchemaValidation`] when schema validation fails.
pub fn add_file_named(
&self,
catalog: &mut AgentCatalog,
Expand Down Expand Up @@ -213,10 +224,8 @@ impl AgentLoader {
/// * `default_name` - Agent name to use if not specified in frontmatter
///
/// # Errors
///
/// Returns an error if:
/// - Parsing fails (propagates the underlying parse error)
/// - The resulting agent name is empty
/// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails.
/// - Returns [`AgentLoadError::SchemaValidation`] when the resulting agent name is empty.
pub fn add_from_str(
&self,
catalog: &mut AgentCatalog,
Expand All @@ -238,6 +247,11 @@ impl AgentLoader {
/// * `catalog` - The catalog to insert the agent into
/// * `bytes` - Raw markdown bytes with YAML frontmatter
/// * `default_name` - Agent name to use if not specified in frontmatter
///
/// # Errors
/// - Returns [`AgentLoadError::SchemaValidation`] when `bytes` is not valid UTF-8.
/// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails.
/// - Returns [`AgentLoadError::SchemaValidation`] when the resulting agent name is empty.
pub fn add_from_bytes(
&self,
catalog: &mut AgentCatalog,
Expand Down
6 changes: 3 additions & 3 deletions src/llm-coding-tools-agents/src/path/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ impl PathResolver for FileToolResolver {
/// The cheapest [`FileToolResolver`] variant satisfying the tool's permission config.
///
/// # Errors
///
/// - Returns [`ToolError::InvalidPath`] when shell expansion fails (e.g., unresolvable `$VAR` in a pattern).
/// - Returns [`ToolError::InvalidPattern`] when a glob pattern is syntactically invalid.
/// - Returns [`ToolError::InvalidPath`] when the workspace root does not exist or cannot be canonicalized.
/// - Returns [`ToolError::PermissionDenied`] when the tool is disabled by configuration (`deny`).
/// - Returns [`ToolError::InvalidPath`] when shell expansion fails (e.g., unresolvable environment variable).
/// - Returns [`ToolError::InvalidPattern`] when a glob pattern is syntactically invalid.
pub fn build_resolver_for_tool(
config: &IndexMap<String, PermissionRule>,
tool_name: &str,
Expand Down
3 changes: 3 additions & 0 deletions src/llm-coding-tools-agents/src/runtime/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ impl AgentRuntimeBuilder {
}

/// Finishes building and returns the [`AgentRuntime`].
///
/// # Errors
/// - Returns [`ExpandError`] when any agent's permission configuration contains invalid patterns.
#[inline]
pub fn build(self) -> Result<AgentRuntime, ExpandError> {
AgentRuntime::from_parts(self.catalog, self.defaults, self.task_settings, self.tools)
Expand Down
6 changes: 6 additions & 0 deletions src/llm-coding-tools-agents/src/runtime/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pub struct TaskTargetSummary {
/// # Returns
/// One [`TaskTargetSummary`] per callable target, sorted by name. Empty if
/// `caller_name` is not in the catalog or no non-primary targets are available.
///
/// # Errors
/// - Returns [`ExpandError`] when the caller's `permission.task` configuration contains invalid patterns.
pub fn summarize_callable_targets(
Comment thread
Sewer56 marked this conversation as resolved.
catalog: &AgentCatalog,
caller_name: &str,
Expand All @@ -52,6 +55,9 @@ pub fn summarize_callable_targets(
/// `mode: all` and `mode: subagent` targets for OpenCode compatibility. When
/// `permission.task` is present, its rules filter target names with the normal
/// last-match-wins permission semantics.
///
/// # Errors
/// - Returns [`ExpandError`] when the caller's `permission.task` configuration contains invalid patterns.
pub fn callable_targets<'a>(
catalog: &'a AgentCatalog,
caller_name: &str,
Expand Down
7 changes: 7 additions & 0 deletions src/llm-coding-tools-bubblewrap/src/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ pub(crate) fn probe_availability() -> Availability {
/// If `availability` already indicates unavailability, returns early without
/// probing again. Otherwise re-checks the host and returns an [`Arc<Path>`] on
/// success or a [`LinuxBwrapError::Execution`] on failure.
///
/// # Errors
/// - Returns [`LinuxBwrapError::Execution`] when the provided `availability` already
/// indicates unavailability (via [`Availability::reason`]).
/// - Returns [`LinuxBwrapError::Execution`] when the `bwrap` binary cannot be found on `PATH`.
/// - Returns [`LinuxBwrapError::Execution`] when `bwrap` exists but the current environment
/// cannot create sandboxes (e.g., missing user namespace capabilities).
pub(crate) fn resolve_backend_or_error_for(
preset: Option<Preset>,
availability: &Availability,
Expand Down
4 changes: 2 additions & 2 deletions src/llm-coding-tools-bubblewrap/src/wrap/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use std::process::Stdio;
/// Builds a sync [`CommandWrap`] from a [`Profile`].
///
/// # Errors
///
/// Returns [`LinuxBwrapError`] on invalid per-command workdir.
/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not an absolute path,
/// does not exist, is not a directory, or is not visible inside the sandbox.
pub fn build_command_wrap(
profile: &Profile,
command: &str,
Expand Down
6 changes: 6 additions & 0 deletions src/llm-coding-tools-bubblewrap/src/wrap/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ fn resolve_sandbox_cwd<'a>(

/// Builds a `bwrap` command line that runs `command` inside the sandbox
/// described by `profile`.
///
/// # Errors
/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not an absolute path.
/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` does not exist or is not a directory.
/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not visible inside the sandbox
/// (not under workspace, synthetic home, cache root, or any mounted directory).
#[inline]
pub fn wrap_command<'a>(
profile: &'a Profile,
Expand Down
4 changes: 2 additions & 2 deletions src/llm-coding-tools-bubblewrap/src/wrap/tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use std::process::Stdio;
/// Builds an async [`CommandWrap`] from a [`Profile`].
///
/// # Errors
///
/// Returns [`LinuxBwrapError`] on invalid per-command workdir.
/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not an absolute path,
/// does not exist, is not a directory, or is not visible inside the sandbox.
pub fn build_command_wrap(
profile: &Profile,
command: &str,
Expand Down
16 changes: 16 additions & 0 deletions src/llm-coding-tools-core/src/fs/blocking_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,37 @@ use crate::error::ToolResult;
use std::path::Path;

/// Reads a file to string.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the file cannot be read (e.g., file does not exist,
/// permission denied, or other I/O error).
pub fn read_to_string(path: impl AsRef<Path>) -> ToolResult<String> {
Ok(std::fs::read_to_string(path)?)
}

/// Writes content to a file.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the file cannot be written (e.g., parent directory
/// does not exist, permission denied, or other I/O error).
pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> ToolResult<()> {
Ok(std::fs::write(path, contents)?)
}

/// Creates a directory and all parent directories.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the directory cannot be created (e.g., permission
/// denied or other I/O error).
pub fn create_dir_all(path: impl AsRef<Path>) -> ToolResult<()> {
Ok(std::fs::create_dir_all(path)?)
}

/// Opens a file for buffered reading.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the file cannot be opened (e.g., file does not exist,
/// permission denied, or other I/O error).
pub fn open_buffered(
path: impl AsRef<Path>,
capacity: usize,
Expand Down
16 changes: 16 additions & 0 deletions src/llm-coding-tools-core/src/fs/tokio_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,37 @@ use crate::error::ToolResult;
use std::path::Path;

/// Reads a file to string.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the file cannot be read (e.g., file does not exist,
/// permission denied, or other I/O error).
pub async fn read_to_string(path: impl AsRef<Path>) -> ToolResult<String> {
Ok(tokio::fs::read_to_string(path).await?)
}

/// Writes content to a file.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the file cannot be written (e.g., parent directory
/// does not exist, permission denied, or other I/O error).
pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> ToolResult<()> {
Ok(tokio::fs::write(path, contents).await?)
}

/// Creates a directory and all parent directories.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the directory cannot be created (e.g., permission
/// denied or other I/O error).
pub async fn create_dir_all(path: impl AsRef<Path>) -> ToolResult<()> {
Ok(tokio::fs::create_dir_all(path).await?)
}

/// Opens a file for buffered reading.
///
/// # Errors
/// - Returns [`ToolError::Io`] when the file cannot be opened (e.g., file does not exist,
/// permission denied, or other I/O error).
pub async fn open_buffered(
path: impl AsRef<Path>,
capacity: usize,
Expand Down
2 changes: 2 additions & 0 deletions src/llm-coding-tools-core/src/path/absolute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ impl PathResolver for AbsolutePathResolver {
true
}

/// # Errors
/// - Returns [`ToolError::InvalidPath`] when `path` is not an absolute path.
fn resolve(&self, path: &str) -> ToolResult<PathBuf> {
let path = PathBuf::from(path);
if !path.is_absolute() {
Expand Down
4 changes: 4 additions & 0 deletions src/llm-coding-tools-core/src/path/allowed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ impl AllowedPathResolver {
/// Each directory is resolved during construction to ensure consistent path
/// comparison. Returns an error if any directory doesn't exist or can't be
/// resolved.
///
/// # Errors
/// - Returns [`ToolError::InvalidPath`] when any provided path is not an existing directory.
/// - Returns [`ToolError::InvalidPath`] when path canonicalization fails.
pub fn new(allowed_paths: impl IntoIterator<Item = impl AsRef<Path>>) -> ToolResult<Self> {
let canonicalized: Result<Arc<[PathBuf]>, _> = allowed_paths
.into_iter()
Expand Down
4 changes: 4 additions & 0 deletions src/llm-coding-tools-core/src/path/allowed_glob/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub(crate) fn expand_pattern(
///
/// Wraps the internal expansion logic with fail-fast error handling: returns
/// `ToolError::InvalidPath` if expansion fails (e.g., unset variable).
///
/// # Errors
/// - Returns [`ToolError::InvalidPath`] when shell expansion fails (e.g., unset
/// environment variable in the path pattern).
pub fn expand_shell(path: &str) -> ToolResult<PathBuf> {
expand_pattern(path)
.map(|cow| PathBuf::from(cow.into_owned()))
Expand Down
4 changes: 4 additions & 0 deletions src/llm-coding-tools-core/src/tools/bash/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ pub struct BashRequest {

impl BashRequest {
/// Parses a raw JSON tool payload into a bash request.
///
/// # Errors
/// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized
/// into a [`BashRequest`] (e.g., missing `command` field or invalid field types).
pub fn parse(args: Value) -> ToolResult<Self> {
serde_json::from_value(args).map_err(ToolError::from)
}
Expand Down
18 changes: 18 additions & 0 deletions src/llm-coding-tools-core/src/tools/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ pub struct EditRequest {

impl EditRequest {
/// Parses a raw JSON tool payload into an edit request.
///
/// # Errors
/// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized
/// into an [`EditRequest`] (e.g., missing required `file_path`, `old_string`,
/// or `new_string` fields, or invalid field types).
pub fn parse(args: Value) -> ToolResult<Self> {
serde_json::from_value(args).map_err(ToolError::from)
}
Expand Down Expand Up @@ -94,6 +99,19 @@ impl EditSettings {
/// Performs exact string replacement in a file.
///
/// Returns success message with replacement count.
///
/// # Errors
/// - Returns [`EditError::EmptyOldString`] when `request.old_string` is empty.
/// - Returns [`EditError::IdenticalStrings`] when `old_string` and `new_string`
/// are identical.
/// - Returns [`EditError::Tool`] wrapping [`ToolError::InvalidPath`] when path
/// resolution fails.
/// - Returns [`EditError::Tool`] wrapping [`ToolError::Io`] when reading the file fails.
/// - Returns [`EditError::NotFound`] when `old_string` is not found in the file content.
/// - Returns [`EditError::AmbiguousMatch`] when `replace_all=false` and multiple
/// occurrences of `old_string` are found (requires more context to identify unique match).
/// - Returns [`EditError::Tool`] wrapping [`ToolError::Io`] when writing the modified
/// content back to the file fails.
#[maybe_async::maybe_async]
pub async fn edit_file<R: PathResolver>(
resolver: &R,
Expand Down
5 changes: 5 additions & 0 deletions src/llm-coding-tools-core/src/tools/glob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ pub struct GlobRequest {

impl GlobRequest {
/// Parses a raw JSON tool payload into a glob request.
///
/// # Errors
/// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized
/// into a [`GlobRequest`] (e.g., missing required `pattern` or `path` fields,
/// or invalid field types).
pub fn parse(args: Value) -> ToolResult<Self> {
serde_json::from_value(args).map_err(ToolError::from)
}
Expand Down
5 changes: 5 additions & 0 deletions src/llm-coding-tools-core/src/tools/grep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ pub struct GrepRequest {

impl GrepRequest {
/// Parses a raw JSON tool payload into a grep request.
///
/// # Errors
/// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized
/// into a [`GrepRequest`] (e.g., missing required `pattern` or `path` fields,
/// or invalid field types).
pub fn parse(args: Value) -> ToolResult<Self> {
serde_json::from_value(args).map_err(ToolError::from)
}
Expand Down
5 changes: 5 additions & 0 deletions src/llm-coding-tools-core/src/tools/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ pub struct ReadRequest {

impl ReadRequest {
/// Parses a raw JSON tool payload into a read request.
///
/// # Errors
/// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized
/// into a [`ReadRequest`] (e.g., missing required `file_path` field or
/// invalid field types).
pub fn parse(args: Value) -> ToolResult<Self> {
serde_json::from_value(args).map_err(ToolError::from)
}
Expand Down
13 changes: 13 additions & 0 deletions src/llm-coding-tools-core/src/tools/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ pub struct TodoWriteRequest {

impl TodoWriteRequest {
/// Parses a raw JSON tool payload into a todo-write request.
///
/// # Errors
/// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized
/// into a [`TodoWriteRequest`] (e.g., missing `todos` field or invalid todo
/// structure).
pub fn parse(args: Value) -> ToolResult<Self> {
serde_json::from_value(args).map_err(ToolError::from)
}
Expand All @@ -86,6 +91,10 @@ pub struct TodoReadRequest {}

impl TodoReadRequest {
/// Parses a raw JSON tool payload into a todo-read request.
///
/// # Errors
/// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized
/// into a [`TodoReadRequest`].
pub fn parse(args: Value) -> ToolResult<Self> {
serde_json::from_value(args).map_err(ToolError::from)
}
Expand All @@ -102,6 +111,10 @@ impl TodoState {
/// Writes/replaces the todo list with new items.
///
/// Validates that all todos have non-empty id and content.
///
/// # Errors
/// - Returns [`ToolError::Validation`] when any todo has an empty or whitespace-only `id`.
/// - Returns [`ToolError::Validation`] when any todo has empty or whitespace-only `content`.
pub fn write_todos(state: &TodoState, request: TodoWriteRequest) -> ToolResult<String> {
for todo in &request.todos {
if todo.id.trim().is_empty() {
Expand Down
Loading
Loading