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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .cargo-husky/hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
#!/bin/sh
set -e
make ci
exec make ci
69 changes: 69 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Release

on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"

permissions:
contents: write

env:
CARGO_TERM_COLOR: always

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

- name: Extract changelog
id: changelog
run: |
notes=$(awk '/^## \[${{ steps.version.outputs.version }}\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
{
echo "notes<<CHANGELOG_EOF"
echo "$notes"
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes "${{ steps.changelog.outputs.notes }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2

- name: Publish mixtape-anthropic-sdk
run: cargo publish -p mixtape-anthropic-sdk
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

- name: Publish mixtape-core
run: cargo publish -p mixtape-core
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

- name: Publish mixtape-tools
run: cargo publish -p mixtape-tools
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

- name: Publish mixtape-cli
run: cargo publish -p mixtape-cli
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

- name: Publish mixtape-server
run: cargo publish -p mixtape-server
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/target
Cargo.lock

# Rust artifacts
**/*.rs.bk
*.pdb

# IDE
.idea/
.vscode/
Expand All @@ -15,3 +19,6 @@ Cargo.lock
*.profraw
*.profdata
lcov.info

# cargo-mutants artifacts
**/mutants.out*/
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **mixtape-server** *(experimental)*: HTTP server with AG-UI protocol support. API surface may change in future releases.
- Claude Opus 4.6 model (flagship, 200K context, 128K output)
- Claude Opus 4.1 model (200K context, 32K output)
- Nova 2 Sonic model (1M context, 65K output)
Expand Down Expand Up @@ -38,6 +39,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Claude Opus 4.5 output token limit: 32,000 → 64,000
- Stale `--providers` CLI flag in model_verification example docs (correct flag is `--vendors`)

## [0.2.1] - 2026-01-05

### Fixed

- Use i64 for rusqlite in session store for cross-platform compatibility
- Use i64 for rusqlite COUNT queries for cross-platform compatibility

## [0.2.0] - 2026-01-05

### Added

- Claude Sonnet 4.5 1M model support
- Tool grouping exports for filesystem and process modules
- `builder.add_trusted_tool()` convenience method
- Animated spinner for thinking indicator in CLI
- Improved tool execution event model for approval

### Changed

- Updated non-interactive examples to use `add_trusted_tool()`

## [0.1.1] - 2026-01-04

### Added
Expand All @@ -59,5 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **mixtape-cli**: Session storage and REPL utilities for interactive agents

[Unreleased]: https://github.com/adlio/mixtape/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/adlio/mixtape/compare/v0.1.1...v0.3.0
[0.3.0]: https://github.com/adlio/mixtape/compare/v0.2.1...v0.3.0
[0.2.1]: https://github.com/adlio/mixtape/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/adlio/mixtape/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/adlio/mixtape/releases/tag/v0.1.1
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
[workspace]
members = ["mixtape-core", "mixtape-anthropic-sdk", "mixtape-tools", "mixtape-cli"]
members = ["mixtape-core", "mixtape-anthropic-sdk", "mixtape-tools", "mixtape-cli", "mixtape-server"]
resolver = "2"

[workspace.package]
version = "0.3.0"
edition = "2021"
license = "MIT"
homepage = "https://github.com/adlio/mixtape"
repository = "https://github.com/adlio/mixtape"

[workspace.dependencies]
Expand All @@ -14,14 +15,21 @@ mixtape-core = { version = "0.3.0", path = "./mixtape-core" }
mixtape-anthropic-sdk = { version = "0.3.0", path = "./mixtape-anthropic-sdk" }
mixtape-tools = { path = "./mixtape-tools" }
mixtape-cli = { path = "./mixtape-cli" }
mixtape-server = { path = "./mixtape-server" }

# Async runtime
tokio = { version = "1.41", features = ["full"] }
tokio-test = "0.4"
tokio-stream = "0.1"
async-trait = "0.1"
async-stream = "0.3"
futures = "0.3"

# HTTP Server
axum = { version = "0.7", features = ["macros"] }
tower = { version = "0.5", features = ["util"] }
tower-http = { version = "0.6", features = ["cors", "trace"] }

# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
16 changes: 12 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

.DEFAULT_GOAL := help

.PHONY: help test coverage coverage-html build build-release clean fmt fmt-check lint check doc doc-check all ci ensure-tools
.PHONY: help test coverage coverage-html coverage-ci build build-release clean fmt fmt-check clippy clippy-fix lint check doc doc-check all ci ensure-tools

# Tool installation helpers
CARGO_NEXTEST := $(shell command -v cargo-nextest 2>/dev/null)
Expand Down Expand Up @@ -31,6 +31,9 @@ coverage: ensure-tools ## Show coverage summary in console
coverage-html: ensure-tools ## Generate HTML coverage report and open
cargo llvm-cov nextest --workspace --all-features --html --open

coverage-ci: ensure-tools ## Generate LCOV coverage for CI upload
cargo llvm-cov nextest --workspace --all-features --lcov --output-path lcov.info

build: ## Build debug
cargo build --workspace --all-targets --all-features

Expand All @@ -49,9 +52,14 @@ fmt: ## Format code
fmt-check: ## Check formatting
cargo fmt --all -- --check

lint: ## Run clippy
clippy: ## Run clippy with warnings as errors
cargo clippy --workspace --all-targets --all-features -- -D warnings

clippy-fix: ## Run clippy and auto-fix
cargo clippy --workspace --all-targets --all-features --fix --allow-dirty -- -D warnings

lint: clippy ## Alias for clippy

clean: ## Clean build artifacts
cargo clean

Expand All @@ -61,6 +69,6 @@ doc: ## Generate docs
doc-check: ## Check docs build without warnings
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps

all: ensure-tools fmt lint build test ## Format, lint, build, and test
all: ensure-tools fmt clippy build test ## Format, lint, build, and test

ci: ensure-tools fmt-check lint build doc-check test ## Check formatting, lint, build, docs, test (for CI/hooks)
ci: ensure-tools fmt-check clippy build doc-check test ## Check formatting, lint, build, docs, test (for CI/hooks)
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Mixtape

[![Crates.io](https://img.shields.io/crates/v/mixtape-core.svg)](https://crates.io/crates/mixtape-core)
[![Documentation](https://docs.rs/mixtape-core/badge.svg)](https://docs.rs/mixtape-core)
[![CI](https://github.com/adlio/mixtape/actions/workflows/ci.yml/badge.svg)](https://github.com/adlio/mixtape/actions/workflows/ci.yml)
[![Coverage](https://codecov.io/gh/adlio/mixtape/branch/main/graph/badge.svg)](https://codecov.io/gh/adlio/mixtape)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![codecov](https://codecov.io/gh/adlio/mixtape/graph/badge.svg)](https://codecov.io/gh/adlio/mixtape)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

An agentic AI framework for Rust.

Expand Down Expand Up @@ -51,13 +53,14 @@ Add `mcp` for MCP server integration, `session` for conversation persistence.

## Workspace Crates

This repository contains four crates:
This repository contains five crates:

| Crate | Purpose |
|---------------------------|--------------------------------------------------------|
| **mixtape-core** | Core agent framework |
| **mixtape-tools** | Pre-built filesystem, process, web, and database tools |
| **mixtape-cli** | Session storage and interactive REPL features |
| **mixtape-server** | HTTP server with AG-UI protocol support *(experimental)* |
| **mixtape-anthropic-sdk** | Low-level Anthropic API client (used internally) |

Most projects need only `mixtape-core`. Add `mixtape-tools` for ready-to-use tools.
Expand Down
11 changes: 11 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
coverage:
status:
project:
default:
target: auto
threshold: 1%
patch:
default:
target: 80%
ignore:
- "mixtape-server/**"
2 changes: 2 additions & 0 deletions mixtape-anthropic-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "Minimal Anthropic API client for the mixtape agent framework"
documentation = "https://docs.rs/mixtape-anthropic-sdk"
readme = "README.md"
keywords = ["anthropic", "claude", "llm", "ai", "api"]
categories = ["api-bindings", "asynchronous"]
exclude = [".cargo-husky/", ".claude/", ".github/", ".idea/"]

[features]
default = []
Expand Down
2 changes: 2 additions & 0 deletions mixtape-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "Session storage and REPL utilities for the mixtape agent framework"
documentation = "https://docs.rs/mixtape-cli"
readme = "README.md"
keywords = ["ai", "agents", "cli", "repl", "session"]
categories = ["command-line-utilities", "development-tools"]
exclude = [".cargo-husky/", ".claude/", ".github/", ".idea/"]

[dependencies]
mixtape-core = { workspace = true, features = ["session"] }
Expand Down
3 changes: 3 additions & 0 deletions mixtape-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "An agentic AI framework for Rust"
documentation = "https://docs.rs/mixtape-core"
readme = "../README.md"
keywords = ["ai", "agents", "llm", "anthropic", "tools"]
categories = ["development-tools", "api-bindings", "asynchronous"]
exclude = [".cargo-husky/", ".claude/", ".github/", ".idea/"]

[features]
default = []
session = []
bedrock = ["dep:aws-config", "dep:aws-sdk-bedrockruntime", "dep:aws-smithy-types"]
anthropic = ["dep:mixtape-anthropic-sdk", "dep:base64"]
mcp = ["dep:rmcp", "dep:reqwest", "dep:shellexpand"]
test-utils = []

[dependencies]
async-stream.workspace = true
Expand Down
4 changes: 3 additions & 1 deletion mixtape-core/src/agent/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
Expand Down Expand Up @@ -559,7 +560,8 @@ impl AgentBuilder {
system_prompt: self.system_prompt,
max_concurrent_tools: self.max_concurrent_tools,
tools: self.tools,
hooks: Arc::new(parking_lot::RwLock::new(Vec::new())),
hooks: Arc::new(parking_lot::RwLock::new(HashMap::new())),
next_hook_id: AtomicU64::new(0),
authorizer: Arc::new(RwLock::new(authorizer)),
authorization_timeout: self.authorization_timeout,
pending_authorizations: Arc::new(RwLock::new(HashMap::new())),
Expand Down
30 changes: 23 additions & 7 deletions mixtape-core/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ pub use types::{
pub use types::SessionInfo;

use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{mpsc, RwLock};

use crate::conversation::BoxedConversationManager;
use crate::events::{AgentEvent, AgentHook};
use crate::events::{AgentEvent, AgentHook, HookId};
use crate::permission::{AuthorizationResponse, ToolCallAuthorizer};
use crate::provider::ModelProvider;
use crate::tool::DynTool;
Expand Down Expand Up @@ -68,7 +69,8 @@ pub struct Agent {
pub(super) system_prompt: Option<String>,
pub(super) max_concurrent_tools: usize,
pub(super) tools: Vec<Box<dyn DynTool>>,
pub(super) hooks: Arc<parking_lot::RwLock<Vec<Arc<dyn AgentHook>>>>,
pub(super) hooks: Arc<parking_lot::RwLock<HashMap<HookId, Arc<dyn AgentHook>>>>,
pub(super) next_hook_id: AtomicU64,
/// Tool call authorizer (always present, uses MemoryGrantStore by default)
pub(super) authorizer: Arc<RwLock<ToolCallAuthorizer>>,
/// Timeout for authorization requests
Expand All @@ -95,7 +97,9 @@ pub struct Agent {
}

impl Agent {
/// Add an event hook to observe agent execution
/// Add an event hook to observe agent execution.
///
/// Returns a [`HookId`] that can be used to remove the hook later via [`remove_hook`](Self::remove_hook).
///
/// Hooks receive notifications about agent lifecycle, model calls,
/// and tool executions in real-time.
Expand All @@ -116,16 +120,28 @@ impl Agent {
/// .bedrock(ClaudeSonnet4_5)
/// .build()
/// .await?;
/// agent.add_hook(Logger);
/// let hook_id = agent.add_hook(Logger);
///
/// // Later, remove the hook
/// agent.remove_hook(hook_id);
/// ```
pub fn add_hook(&self, hook: impl AgentHook + 'static) {
self.hooks.write().push(Arc::new(hook));
pub fn add_hook(&self, hook: impl AgentHook + 'static) -> HookId {
let id = HookId(self.next_hook_id.fetch_add(1, Ordering::SeqCst));
self.hooks.write().insert(id, Arc::new(hook));
id
}

/// Remove a previously registered hook.
///
/// Returns `true` if the hook was found and removed, `false` otherwise.
pub fn remove_hook(&self, id: HookId) -> bool {
self.hooks.write().remove(&id).is_some()
}

/// Emit an event to all registered hooks
pub(crate) fn emit_event(&self, event: AgentEvent) {
let hooks = self.hooks.read();
for hook in hooks.iter() {
for hook in hooks.values() {
hook.on_event(&event);
}
}
Expand Down
Loading