sync from monorepo @ 2452e92e
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
# Dirigent Protocol
|
||||
|
||||
**Version:** 0.2.0
|
||||
|
||||
A Rust protocol library for agent-client interactions, aligned with **Agent-Client Protocol (ACP)** and **Model Context Protocol (MCP)** standards.
|
||||
|
||||
## Overview
|
||||
|
||||
The Dirigent Protocol provides a structured, streaming-first event model for real-time agent interactions. It's designed to support multi-agent orchestration, tool execution, and rich content streaming while maintaining compatibility with standard protocols.
|
||||
|
||||
## Features
|
||||
|
||||
- **ACP-Style Streaming**: Real-time content updates via `SessionUpdate` events
|
||||
- **MCP-Compatible Content**: Structured `ContentBlock` representation
|
||||
- **Tool Lifecycle Management**: Complete tool call tracking from initiation to completion
|
||||
- **Provider Agnostic**: Adapter system for integrating different AI providers
|
||||
- **Type-Safe**: Strongly-typed Rust API with comprehensive serde support
|
||||
- **Extensible**: Provider metadata and extensibility hooks
|
||||
|
||||
## Quick Start
|
||||
|
||||
Add to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
dirigent_protocol = "0.2"
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```rust
|
||||
use dirigent_protocol::{Event, SessionUpdate, ContentBlock};
|
||||
|
||||
fn handle_event(event: Event) {
|
||||
match event {
|
||||
Event::SessionUpdate { session_id, update } => {
|
||||
match update {
|
||||
SessionUpdate::AgentMessageChunk { message_id, content, .. } => {
|
||||
if let ContentBlock::Text { text } = content {
|
||||
println!("Agent says: {}", text);
|
||||
}
|
||||
}
|
||||
SessionUpdate::ToolCall { tool_call, .. } => {
|
||||
println!("Tool called: {}", tool_call.tool_name);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Event::SessionCreated { session } => {
|
||||
println!("New session: {}", session.id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### SessionUpdate Event Model
|
||||
|
||||
The protocol uses `SessionUpdate` for all streaming content:
|
||||
|
||||
```rust
|
||||
pub enum SessionUpdate {
|
||||
UserMessageChunk { message_id: String, content: ContentBlock, _meta: Option<Meta> },
|
||||
AgentMessageChunk { message_id: String, content: ContentBlock, _meta: Option<Meta> },
|
||||
AgentThoughtChunk { message_id: String, content: ContentBlock, _meta: Option<Meta> },
|
||||
ToolCall { message_id: String, tool_call: ToolCall, _meta: Option<Meta> },
|
||||
ToolCallUpdate { message_id: String, tool_call_id: String, tool_call: ToolCall, _meta: Option<Meta> },
|
||||
}
|
||||
```
|
||||
|
||||
### ContentBlock Types
|
||||
|
||||
Structured content representation:
|
||||
|
||||
```rust
|
||||
pub enum ContentBlock {
|
||||
Text { text: String },
|
||||
ResourceLink {
|
||||
uri: String,
|
||||
name: Option<String>,
|
||||
mime_type: Option<String>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Call Lifecycle
|
||||
|
||||
Complete tool execution tracking:
|
||||
|
||||
```rust
|
||||
pub struct ToolCall {
|
||||
pub id: ToolCallId,
|
||||
pub tool_name: String,
|
||||
pub status: ToolCallStatus, // Pending → Running → Completed/Error
|
||||
pub content: Vec<ContentBlock>,
|
||||
pub raw_input: Option<Value>,
|
||||
pub raw_output: Option<Value>,
|
||||
pub title: Option<String>,
|
||||
pub error: Option<String>,
|
||||
pub metadata: Option<Value>,
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[Streaming Model](docs/streaming_model.md)** - Detailed guide to the SessionUpdate event system
|
||||
- **[Migration Guide](docs/migration_from_0.1.md)** - Upgrading from 0.1.x to 0.2.0
|
||||
- **[CHANGELOG.md](CHANGELOG.md)** - Version history and breaking changes
|
||||
- **[Examples](examples/)** - Working code examples
|
||||
|
||||
## Examples
|
||||
|
||||
### Streaming Text
|
||||
|
||||
```rust
|
||||
use dirigent_protocol::{Event, SessionUpdate, ContentBlock};
|
||||
|
||||
// Agent streaming response
|
||||
Event::SessionUpdate {
|
||||
session_id: "session_123".to_string(),
|
||||
update: SessionUpdate::AgentMessageChunk {
|
||||
message_id: "msg_1".to_string(),
|
||||
content: ContentBlock::Text {
|
||||
text: "Hello, world!".to_string(),
|
||||
},
|
||||
_meta: None,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Execution
|
||||
|
||||
```rust
|
||||
use dirigent_protocol::{Event, SessionUpdate, ToolCall, ToolCallStatus};
|
||||
|
||||
// Tool call initiated
|
||||
Event::SessionUpdate {
|
||||
session_id: "session_123".to_string(),
|
||||
update: SessionUpdate::ToolCall {
|
||||
message_id: "msg_1".to_string(),
|
||||
tool_call: ToolCall {
|
||||
id: "call_1".to_string(),
|
||||
tool_name: "bash".to_string(),
|
||||
status: ToolCallStatus::Pending,
|
||||
content: vec![],
|
||||
raw_input: Some(json!({"command": "ls"})),
|
||||
raw_output: None,
|
||||
title: Some("List files".to_string()),
|
||||
error: None,
|
||||
metadata: None,
|
||||
},
|
||||
_meta: None,
|
||||
},
|
||||
}
|
||||
|
||||
// Tool call completed
|
||||
Event::SessionUpdate {
|
||||
session_id: "session_123".to_string(),
|
||||
update: SessionUpdate::ToolCallUpdate {
|
||||
message_id: "msg_1".to_string(),
|
||||
tool_call_id: "call_1".to_string(),
|
||||
tool_call: ToolCall {
|
||||
id: "call_1".to_string(),
|
||||
tool_name: "bash".to_string(),
|
||||
status: ToolCallStatus::Completed,
|
||||
content: vec![
|
||||
ContentBlock::Text {
|
||||
text: "file1.txt\nfile2.txt".to_string(),
|
||||
},
|
||||
],
|
||||
raw_input: Some(json!({"command": "ls"})),
|
||||
raw_output: Some(json!({"exit_code": 0})),
|
||||
title: Some("List files".to_string()),
|
||||
error: None,
|
||||
metadata: None,
|
||||
},
|
||||
_meta: None,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Agent Thinking
|
||||
|
||||
```rust
|
||||
use dirigent_protocol::{Event, SessionUpdate, ContentBlock};
|
||||
|
||||
// Agent internal reasoning
|
||||
Event::SessionUpdate {
|
||||
session_id: "session_123".to_string(),
|
||||
update: SessionUpdate::AgentThoughtChunk {
|
||||
message_id: "msg_1".to_string(),
|
||||
content: ContentBlock::Text {
|
||||
text: "Analyzing the user's request...".to_string(),
|
||||
},
|
||||
_meta: None,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Adapters
|
||||
|
||||
The protocol includes adapter modules for translating provider-specific events:
|
||||
|
||||
- **OpenCode Adapter**: Converts OpenCode.ai events to Dirigent Protocol
|
||||
- **REST Adapter**: Translates REST API responses
|
||||
|
||||
### Using an Adapter
|
||||
|
||||
```rust
|
||||
use dirigent_protocol::adapters::opencode::OpenCodeAdapter;
|
||||
|
||||
let adapter = OpenCodeAdapter::new();
|
||||
let dirigent_events = adapter.translate_event(opencode_event);
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
### 0.2.0 (Current)
|
||||
|
||||
**Breaking Changes:**
|
||||
- Removed `Event::MessagePartAdded` (replaced with `SessionUpdate`)
|
||||
|
||||
**New Features:**
|
||||
- ACP-style `SessionUpdate` event system
|
||||
- MCP-compatible `ContentBlock` types
|
||||
- Structured `ToolCall` with lifecycle tracking
|
||||
- Provider metadata via `_meta` field
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for details.
|
||||
|
||||
### 0.1.0
|
||||
|
||||
Initial release with basic event types and adapters.
|
||||
|
||||
## Migration from 0.1.x
|
||||
|
||||
If you're upgrading from version 0.1.x, see the [Migration Guide](docs/migration_from_0.1.md) for detailed instructions and examples.
|
||||
|
||||
**Quick Summary:**
|
||||
- Replace `Event::MessagePartAdded` with `Event::SessionUpdate`
|
||||
- Use `SessionUpdate` variants instead of `MessagePart`
|
||||
- Handle tool lifecycle with `ToolCall` and `ToolCallUpdate`
|
||||
- Access `ContentBlock::Text { text }` instead of `MessagePart::Text { content }`
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
dirigent_protocol/
|
||||
├── src/
|
||||
│ ├── types/ # Core types
|
||||
│ │ ├── content.rs # ContentBlock definitions
|
||||
│ │ ├── updates.rs # SessionUpdate variants
|
||||
│ │ ├── tool.rs # Tool call types
|
||||
│ │ └── meta.rs # Provider metadata
|
||||
│ ├── session.rs # Session types
|
||||
│ ├── conversation.rs # Message types
|
||||
│ ├── events.rs # Event enum
|
||||
│ └── adapters/ # Provider adapters
|
||||
│ ├── opencode.rs # OpenCode adapter
|
||||
│ └── rest.rs # REST adapter
|
||||
├── docs/ # Documentation
|
||||
├── examples/ # Code examples
|
||||
└── tests/ # Integration tests
|
||||
```
|
||||
|
||||
## Event Types
|
||||
|
||||
The protocol defines several event categories:
|
||||
|
||||
### Session Events
|
||||
- `SessionCreated` - New session started
|
||||
- `SessionUpdated` - Session metadata changed
|
||||
- `SessionDeleted` - Session removed
|
||||
- `SessionsListed` - Available sessions returned
|
||||
|
||||
### Streaming Events
|
||||
- `SessionUpdate` - Real-time content/tool updates (see SessionUpdate variants above)
|
||||
|
||||
### Message Events
|
||||
- `MessageStarted` - Message creation initiated
|
||||
- `MessageCompleted` - Message finalized
|
||||
- `MessageDeleted` - Message removed
|
||||
|
||||
### System Events
|
||||
- `ConnectorStateChanged` - Connector status changed
|
||||
- `Error` - Error occurred
|
||||
|
||||
## Best Practices
|
||||
|
||||
### For Consumers
|
||||
|
||||
1. **Use SessionUpdate for streaming**: The new event model provides granular updates
|
||||
2. **Track tools by ID**: Maintain a HashMap of tool calls keyed by `tool_call_id`
|
||||
3. **Replace tool state on update**: `ToolCallUpdate` sends complete state, not deltas
|
||||
4. **Distinguish thoughts from messages**: Render `AgentThoughtChunk` differently (e.g., collapsible sections)
|
||||
5. **Handle optional fields**: Provider metadata (`_meta`) may not always be present
|
||||
|
||||
### For Adapters
|
||||
|
||||
1. **Preserve provider info**: Store original IDs in `_meta.provider` for debugging
|
||||
2. **Send complete tool state**: Include all tool call fields in updates
|
||||
3. **Use appropriate chunk types**: Choose User/Agent/Thought for correct semantics
|
||||
4. **Keep metadata minimal**: Avoid large payloads in `_meta` (use excerpts)
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite:
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
Run with output:
|
||||
|
||||
```bash
|
||||
cargo test -- --nocapture
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Please ensure:
|
||||
- All tests pass
|
||||
- New features include tests
|
||||
- Documentation is updated
|
||||
- Code follows Rust conventions
|
||||
|
||||
## License
|
||||
|
||||
[License information here]
|
||||
|
||||
## Related Projects
|
||||
|
||||
- **dirigent_core** - Multi-agent orchestration runtime
|
||||
- **opencode_client** - OpenCode.ai HTTP client library
|
||||
- **dirigent_archive** - Session persistence
|
||||
|
||||
## Support
|
||||
|
||||
For questions or issues:
|
||||
- Check the [documentation](docs/)
|
||||
- Review [examples](examples/)
|
||||
- Open an issue on the repository
|
||||
|
||||
## Standards Alignment
|
||||
|
||||
This protocol is designed to align with:
|
||||
- **Agent-Client Protocol (ACP)** - Streaming model and event types
|
||||
- **Model Context Protocol (MCP)** - Content block structure
|
||||
|
||||
Differences from standards are documented for compatibility and future convergence.
|
||||
Reference in New Issue
Block a user