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..34df8df9 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 +[OpenCode](https://opencode.ai) is a fun, fast-moving TUI coding agent - but it ships +breaking changes regularly. It's a TypeScript application that uses ~400 MiB of RAM +and runs as a separate process. -- **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 +What if you need the same agent tooling for a **server**? A **Discord bot**? +A **CI pipeline**? A **custom product**? -## Feature Flags (llm-coding-tools-core) +**llm-coding-tools** takes the core agent tooling and ships it as a headless +Rust library. Same tool set, same agent format, same system prompts - at a +fraction of the resource cost. -- `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 | ~400 MiB | ~10 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..83d768e9 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,79 @@ +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 + - Sandboxing: sandboxing.md + - Models Catalog: models-catalog.md + - Guides: + - Examples: guides/examples.md + - Feature Flags: guides/feature-flags.md + - Custom Framework: guides/custom-framework.md + - Architecture: architecture.md + - Comparison with OpenCode: comparison.md + - Migrating from OpenCode: migration.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/assets/landing.css b/docs/src/assets/landing.css new file mode 100644 index 00000000..85e8d0be --- /dev/null +++ b/docs/src/assets/landing.css @@ -0,0 +1,165 @@ +/* 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; +} + +.comparison-table { + width: 100%; + margin: 1.5rem 0; +} + +.comparison-table th { + text-align: left; +} + +.comparison-table td, .comparison-table th { + padding: 0.5rem 1rem; + border-bottom: 1px solid rgba(var(--md-primary-fg-color-rgb, 250, 119, 116), 0.1); +} + +.md-tooltip2[role="tooltip"] .md-tooltip2__inner { + font-size: 0.75rem; + white-space: pre-line; +} diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md new file mode 100644 index 00000000..6721fe15 --- /dev/null +++ b/docs/src/getting-started.md @@ -0,0 +1,242 @@ +# 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](guides/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](guides/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 +Integration](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 +- [Architecture](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/examples.md b/docs/src/guides/examples.md new file mode 100644 index 00000000..da18567e --- /dev/null +++ b/docs/src/guides/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/index.md b/docs/src/index.md new file mode 100644 index 00000000..28a109f3 --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,232 @@ +--- +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? + +[OpenCode] is a fun, fast-moving TUI coding agent - but it ships breaking changes +regularly. It's a **TypeScript application** that uses ~400 MiB of RAM and runs +as a separate process. What if you need the same agent tooling for a server? A Discord bot? +A CI pipeline? Custom software? + +**llm-coding-tools** ships the core agent tooling as a **lightweight headless Rust +library** - same tool set, similar agent format, same system prompts - at a fraction +of the resource cost. + +
+
+
~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~400 MiB~10 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 library (crate)
+ +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/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/tools.md b/docs/src/tools.md new file mode 100644 index 00000000..98d1d358 --- /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 00000000..65a7eb46 Binary files /dev/null and b/docs/src/vendor/Reloaded/Images/Nexus-Heart-40.avif differ 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 00000000..9e3e3b26 Binary files /dev/null and b/docs/src/vendor/Reloaded/Images/Nexus-Icon-40.avif differ 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 00000000..be000d31 Binary files /dev/null and b/docs/src/vendor/Reloaded/Images/Reloaded-Heart-40.avif differ 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 00000000..691dc3ea Binary files /dev/null and b/docs/src/vendor/Reloaded/Images/Reloaded-Icon-40.avif differ diff --git a/docs/src/vendor/Reloaded/Stylesheets/reloaded.css b/docs/src/vendor/Reloaded/Stylesheets/reloaded.css new file mode 100644 index 00000000..4812481f --- /dev/null +++ b/docs/src/vendor/Reloaded/Stylesheets/reloaded.css @@ -0,0 +1,584 @@ +/* + Import Title Font: Montserrat + Falls back to Roboto +*/ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@600&display=swap'); + +/* + Nexus Mods Theme +*/ + +:root { + --md-admonition-icon--nexus: url('../Images/Nexus-Icon-40.avif'); + --md-admonition-icon--nexusheart: url('../Images/Nexus-Heart-40.avif'); + --md-admonition-icon--reloaded: url('../Images/Reloaded-Icon-40.avif'); + --md-admonition-icon--reloadedheart: url('../Images/Reloaded-Heart-40.avif'); +} + +/* Slate theme, i.e. dark mode */ + +[data-md-color-scheme="nexus-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 217, 143, 64; + + /* Primary color shades */ + --md-primary-fg-color: #D98F40; + --md-primary-fg-color--light: #E0A362; + --md-primary-fg-color--dark: #C87B28; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #C87B28; + --md-accent-fg-color--transparent: #C87B2810; + + --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: 31; + --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="nexus-slate"] .md-header { + background-color: var(--nexus-background-primary); +} + +[data-md-color-scheme="reloaded-slate"] { + /* + Default Variables + */ + + /* For reuse later */ + --md-primary-fg-color-rgb: 182, 99, 99; + + /* Primary color shades */ + --md-primary-fg-color: #793939; + --md-primary-fg-color--light: #B66363; + --md-primary-fg-color--lightest: #d6a8a8; + --md-primary-fg-color--dark: #572929; + + --md-primary-bg-color: #FFFFFF; + --md-primary-bg-color--light: #FFFFFFB2; + + /* Accent color shades */ + --md-accent-fg-color: #793939; + --md-accent-fg-color--transparent: #79393960; + + --md-accent-bg-color: #181818; + --md-accent-bg-color--light: #181818; + + /* + // 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: 0; + --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.04); + --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--lightest); + + /* 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="reloaded-slate"] .md-typeset a:hover { + color: var(--md-primary-fg-color--light); +} + +[data-md-color-scheme="reloaded-slate"] .md-nav--primary .md-nav__item--active>.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/Cargo.toml b/src/Cargo.toml index 479e530f..dde3f896 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -9,6 +9,85 @@ members = [ "llm-coding-tools-bubblewrap", ] +[workspace.dependencies] +# Serialization +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" +bitcode = "0.6.9" +bitflags = "2.11.0" + +# Error handling +thiserror = "2.0" + +# Async/Runtime +tokio = { version = "1.51", features = ["fs", "io-util", "process", "time"] } +maybe-async = "0.2" +async-trait = "0.1" +futures = "0.3" + +# HTTP/Network +reqwest = { version = "0.13", default-features = false, features = ["rustls", "rustls-native-certs"] } +html-to-markdown-rs = "3.1" + +# Filesystem/Path +shellexpand = "3.1.2" +soft-canonicalize = "0.5.5" +dirs = "6" +tempfile = "3.27" +ignore = "0.4.25" + +# Data structures +hashbrown = "0.17" +indexmap = { version = "2", features = ["serde"] } +tinyvec = { version = "1.11", features = ["alloc"] } +lite-strtab = "0.2" +ahash = { version = "0.8", features = ["serde"] } +parking_lot = "0.12" + +# Bit operations +bitfields = "1.0.3" + +# Grep/Glob tools +globset = "0.4.18" +grep-regex = "0.1.14" +grep-searcher = "0.1.16" +memchr = "2.8.0" + +# Process management +process-wrap = { version = "9.1", default-features = false } + +# String formatting +const_format = "0.2.35" +crlf-to-lf-inplace = "0.2" + +# Schema/Validation +schemars = "1.2" + +# Compression/Encoding +zstd = "0.13.3" +endian-writer = "2.2.0" +endian-writer-derive = "0.1.0" + +# serdes-ai ecosystem +serdes-ai = { version = "0.2.6", default-features = false } +serdes-ai-models = { version = "0.2.6", default-features = false } +serdes-ai-streaming = "0.2" + +# Internal crates +llm-coding-tools-core = { version = "0.2.0", path = "llm-coding-tools-core" } +llm-coding-tools-bubblewrap = { version = "0.1.0", path = "llm-coding-tools-bubblewrap" } +llm-coding-tools-agents = { version = "0.1.0", path = "llm-coding-tools-agents" } +llm-coding-tools-models-dev = { version = "0.1.0", path = "llm-coding-tools-models-dev" } + +# Dev dependencies +criterion = "0.8" +rstest = "0.26" +serial_test = "3" +wiremock = "0.6" +indoc = "2" +temp-env = "0.3.6" + # Profile Build [profile.profile] inherits = "release" 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..77d39df0 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"] @@ -10,37 +10,36 @@ readme = "README.md" [dependencies] # YAML parsing for frontmatter -serde = { version = "1.0", features = ["derive"] } -serde_yaml = "0.9" -serde_json = "1.0" +serde = { workspace = true } +serde_yaml = { workspace = true } +serde_json = { workspace = true } # Preserve insertion order for permission maps -indexmap = { version = "2", features = ["serde"] } +indexmap = { workspace = true } # Error handling -thiserror = "2.0" +thiserror = { workspace = true } # Fast CRLF to LF conversion -crlf-to-lf-inplace = "0.2" - +crlf-to-lf-inplace = { workspace = true } # Directory scanning with gitignore support -ignore = "0.4.25" +ignore = { workspace = true } # High-performance hashing (sync version with core crate) -ahash = { version = "0.8", features = ["serde"] } +ahash = { workspace = true } # Core library for permissions and tool types -llm-coding-tools-core = { path = "../llm-coding-tools-core", version = "0.2.0" } +llm-coding-tools-core = { workspace = true } # Canonicalizes paths, resolving symlinks without requiring the full path to exist -soft-canonicalize = "0.5" +soft-canonicalize = { workspace = true } [dev-dependencies] -tempfile = "3.27" -criterion = "0.8" -indoc = "2" -rstest = "0.26" +tempfile = { workspace = true } +criterion = { workspace = true } +indoc = { workspace = true } +rstest = { workspace = true } [[bench]] name = "parser" 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/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/Cargo.toml b/src/llm-coding-tools-bubblewrap/Cargo.toml index 29370527..bdebe6a3 100644 --- a/src/llm-coding-tools-bubblewrap/Cargo.toml +++ b/src/llm-coding-tools-bubblewrap/Cargo.toml @@ -13,13 +13,13 @@ default = ["tokio"] tokio = ["dep:process-wrap", "process-wrap/tokio1", "process-wrap/process-group"] blocking = ["dep:process-wrap", "process-wrap/std", "process-wrap/process-group"] [dependencies] -parking_lot = "0.12" -thiserror = "2.0" -process-wrap = { version = "9", default-features = false, optional = true } -tempfile = "3.27" +parking_lot = { workspace = true } +thiserror = { workspace = true } +process-wrap = { workspace = true, optional = true } +tempfile = { workspace = true } [dev-dependencies] -rstest = "0.26" -serial_test = "3" -tempfile = "3.27" -tokio = { version = "1.51", features = ["rt", "macros"] } +rstest = { workspace = true } +serial_test = { workspace = true } +tempfile = { workspace = true } +tokio = { workspace = true, features = ["rt", "macros"] } 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/Cargo.toml b/src/llm-coding-tools-core/Cargo.toml index adc56470..4aba59c8 100644 --- a/src/llm-coding-tools-core/Cargo.toml +++ b/src/llm-coding-tools-core/Cargo.toml @@ -38,84 +38,81 @@ linux-bubblewrap = ["dep:llm-coding-tools-bubblewrap"] [dependencies] # Tool outputs (BashOutput, GrepOutput, etc.) serialize to JSON for LLM consumption -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde = { workspace = true } +serde_json = { workspace = true } # Zero overhead compile time bitflag generation -bitflags = "2.11.0" +bitflags = { workspace = true } # Shell-like expansion for home directory paths (~/ and $HOME/) -shellexpand = "3.1.2" +shellexpand = { workspace = true } # Cross-platform canonicalization for non-existent paths -soft-canonicalize = "0.5.5" +soft-canonicalize = { workspace = true } # Fast binary serialization for catalog cache types -bitcode = "0.6.9" +bitcode = { workspace = true } # Compile-time generated packed bitfield structs for model metadata -bitfields = "1.0.3" +bitfields = { workspace = true } # High-performance hashing for permission keys -ahash = "0.8" +ahash = { workspace = true } # Explicit low-level hash table for model catalog lookup -hashbrown = "0.17" +hashbrown = { workspace = true } # Inline vector storage for ProviderEnvVars -tinyvec = { version = "1.11", features = ["alloc"] } +tinyvec = { workspace = true } # Efficient immutable string table for provider URLs and env vars -lite-strtab = "0.2" +lite-strtab = { workspace = true } # ToolError type uses thiserror for ergonomic error definitions -thiserror = "2.0" +thiserror = { workspace = true } # Todo types derive JsonSchema for LLM tool parameter validation -schemars = "1.2" +schemars = { workspace = true } # Sync RwLock for TodoState (no tokio dependency) -parking_lot = "0.12" +parking_lot = { workspace = true } # Glob and grep tool implementations (aligned with ripgrep) -globset = "0.4.18" # Glob matching with ripgrep-optimized engine -grep-regex = "0.1.14" # Regex matcher for grep_search -grep-searcher = "0.1.16" # File content searching for grep_search -ignore = "0.4.25" # Respects .gitignore when walking directories -memchr = "2.8.0" # Fast newline detection in read_file +globset = { workspace = true } # Glob matching with ripgrep-optimized engine +grep-regex = { workspace = true } # Regex matcher for grep_search +grep-searcher = { workspace = true } # File content searching for grep_search +ignore = { workspace = true } # Respects .gitignore when walking directories +memchr = { workspace = true } # Fast newline detection in read_file # Webfetch tool converts HTML to markdown for LLM-friendly output -html-to-markdown-rs = "3.1" -reqwest = { version = "0.13", default-features = false, features = [ - "rustls", - "rustls-native-certs", -], optional = true } +html-to-markdown-rs = { workspace = true } +reqwest = { workspace = true, optional = true } # Unifies async/sync code via procedural macros -maybe-async = "0.2" +maybe-async = { workspace = true } # Async file I/O, process execution, and timeouts -tokio = { version = "1.51", features = ["fs", "io-util", "process", "time"], optional = true } +tokio = { workspace = true, optional = true } # Cross-platform process tree management (Job Objects on Windows, process groups on Unix) -process-wrap = { version = "9.1", default-features = false } +process-wrap = { workspace = true } # Compile-time string formatting for prompt text and parameter descriptions -const_format = "0.2.35" +const_format = { workspace = true } # Linux sandbox types and runtime -llm-coding-tools-bubblewrap = { version = "0.1.0", path = "../llm-coding-tools-bubblewrap", optional = true } +llm-coding-tools-bubblewrap = { workspace = true, optional = true } [dev-dependencies] -serial_test = "3" -tempfile = "3.27" -dirs = "6" +serial_test = { workspace = true } +tempfile = { workspace = true } +dirs = { workspace = true } # For tests (async and blocking with wiremock) -tokio = { version = "1.51", features = ["rt-multi-thread", "macros"] } -wiremock = "0.6" -indoc = "2" -criterion = "0.8" -temp-env = "0.3.6" -rstest = "0.26" +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +wiremock = { workspace = true } +indoc = { workspace = true } +criterion = { workspace = true } +temp-env = { workspace = true } +rstest = { workspace = true } [[bench]] name = "model_catalog_builder" 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-models-dev/Cargo.toml b/src/llm-coding-tools-models-dev/Cargo.toml index 8f67f0c3..52bc3460 100644 --- a/src/llm-coding-tools-models-dev/Cargo.toml +++ b/src/llm-coding-tools-models-dev/Cargo.toml @@ -24,44 +24,41 @@ blocking = [ [dependencies] # Core library for ModelCatalog and related types -llm-coding-tools-core = { path = "../llm-coding-tools-core", version = "0.2.0", default-features = false } +llm-coding-tools-core = { workspace = true, default-features = false } # Cross-platform cache directory detection -dirs = "6.0.0" +dirs = { workspace = true } # HTTP client for conditional GET requests -reqwest = { version = "0.13", default-features = false, features = [ - "rustls", - "rustls-native-certs", -], optional = true } +reqwest = { workspace = true, optional = true } # Fast binary serialization -bitcode = "0.6.9" +bitcode = { workspace = true } # Compression for cache payload -zstd = "0.13.3" +zstd = { workspace = true } # Shared async/sync implementation for load/cache APIs -maybe-async = "0.2" +maybe-async = { workspace = true } # Endian-aware fixed-header serialization helpers -endian-writer = "2.2.0" -endian-writer-derive = "0.1.0" +endian-writer = { workspace = true } +endian-writer-derive = { workspace = true } # JSON parsing for models.dev API responses -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.145" +serde = { workspace = true } +serde_json = { workspace = true } # Ergonomic error definitions -thiserror = "2.0.18" +thiserror = { workspace = true } # Temp file with atomic rename support -tempfile = "3.27" +tempfile = { workspace = true } # Async runtime (when tokio feature enabled) -tokio = { version = "1.51", features = ["fs", "io-util"], optional = true } +tokio = { workspace = true, optional = true } [dev-dependencies] -tokio = { version = "1.51", features = ["rt", "macros"] } -serial_test = "3" -rstest = "0.26" +tokio = { workspace = true, features = ["rt", "macros"] } +serial_test = { workspace = true } +rstest = { workspace = true } diff --git a/src/llm-coding-tools-models-dev/README.md b/src/llm-coding-tools-models-dev/README.md index d77bc19d..ec41fc2a 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. -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,7 @@ Exactly one runtime mode must be enabled. Apache-2.0 -[models.dev]: https://models.dev +[models.dev/api.json]: https://models.dev/api.json +[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-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/Cargo.toml b/src/llm-coding-tools-serdesai/Cargo.toml index 0355ab86..662520c2 100644 --- a/src/llm-coding-tools-serdesai/Cargo.toml +++ b/src/llm-coding-tools-serdesai/Cargo.toml @@ -51,50 +51,45 @@ linux-bubblewrap = [ [dependencies] # Core tool operations (file read/write/edit, glob, grep, bash, etc.) -llm-coding-tools-core = { version = "0.2.0", path = "../llm-coding-tools-core", features = [ - "tokio", -] } +llm-coding-tools-core = { workspace = true, features = ["tokio"] } # Linux sandboxing via bubblewrap. # Provides sandbox profiles, presets, and sandbox availability detection. -llm-coding-tools-bubblewrap = { version = "0.1.0", path = "../llm-coding-tools-bubblewrap", optional = true } +llm-coding-tools-bubblewrap = { workspace = true, optional = true } # Generic agent runtime dependencies -llm-coding-tools-agents = { version = "0.1.0", path = "../llm-coding-tools-agents" } +llm-coding-tools-agents = { workspace = true } # serdes-ai provides Tool trait, ToolDefinition, RunContext -serdes-ai = { version = "0.2.6", default-features = false } -serdes-ai-models = { version = "0.2.6", default-features = false } -serdes-ai-streaming = "0.2" -futures = "0.3" +serdes-ai = { workspace = true } +serdes-ai-models = { workspace = true } +serdes-ai-streaming = { workspace = true } +futures = { workspace = true } # Tool trait is async - async-trait is NOT re-exported from serdes-ai -async-trait = "0.1" +async-trait = { workspace = true } # Error derives -thiserror = "2.0" +thiserror = { workspace = true } # Tool parameters use serde for deserialization, serde_json for schema -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde = { workspace = true } +serde_json = { workspace = true } # HTTP client for webfetch tool -reqwest = { version = "0.13", default-features = false, features = [ - "rustls", - "rustls-native-certs", -] } +reqwest = { workspace = true } # Used for IndexMap in build.rs (permission config) -indexmap = "2" +indexmap = { workspace = true } [dev-dependencies] -serial_test = "3" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -tempfile = "3" -wiremock = "0.6" -ahash = "0.8" -indexmap = "2" -temp-env = "0.3.6" -rstest = "0.26" +serial_test = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +tempfile = { workspace = true } +wiremock = { workspace = true } +ahash = { workspace = true } +indexmap = { workspace = true } +temp-env = { workspace = true } +rstest = { workspace = true } # models.dev catalog loader for examples -llm-coding-tools-models-dev = { version = "0.1.0", path = "../llm-coding-tools-models-dev" } +llm-coding-tools-models-dev = { workspace = true } 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();