🛰️ export from upstream (56c5a2dd)

This commit is contained in:
2026-05-25 17:51:04 +02:00
parent 5829546671
commit 3bcd2fa759
7 changed files with 2 additions and 1229 deletions
-268
View File
@@ -1,268 +0,0 @@
# dirigent_protocol
**Version:** 0.2.0
**Status:** Active Development
ACP/MCP-aligned protocol library for agent-client interactions.
## Quick Links
- **Package README**: [README.md](README.md) - Main documentation
- **Streaming Model**: [docs/streaming_model.md](docs/streaming_model.md) - Detailed SessionUpdate guide
- **Migration Guide**: [docs/migration_from_0.1.md](docs/migration_from_0.1.md) - Upgrading from 0.1.x
- **Architecture Doc**: [../../docs/architecture/protocol.md](../../docs/architecture/protocol.md) - System design
## Purpose
This package provides the core event protocol for Dirigent, enabling:
- Real-time streaming of agent interactions
- Provider-agnostic event representation
- Tool lifecycle management
- Structured content representation
## Key Types
```rust
use dirigent_protocol::{
Event, // Top-level event enum
SessionUpdate, // Streaming content updates
ContentBlock, // Structured content (Text, ResourceLink)
ToolCall, // Tool execution state
ToolCallStatus, // Pending → Running → Completed/Error
TurnCompleteTrigger, // How turn completion was detected
};
```
## Event Semantics: MessageCompleted vs TurnComplete vs SessionIdle
Understanding the distinction between these three events is critical for correct system behavior:
### MessageCompleted - "Metadata is ready"
- **Purpose**: Informational - signals that message metadata exists
- **Timing**: Emitted when message record is created, content may still be streaming
- **Consumer action**: Update UI status indicators ("Assistant is typing" → "Complete")
- **Example**: Show message timestamp, update message count
### TurnComplete - "All content received" (ACTIONABLE)
- **Purpose**: **Primary finalization signal** - all content for this turn is complete
- **Timing**: Emitted AFTER all content chunks, tool calls, and metadata updates
- **Consumer action**: **Finalize storage, lock state, trigger post-processing**
- **Example**: Write message to disk, mark as immutable, generate summaries
### SessionIdle - "No recent activity"
- **Purpose**: Informational - indicates session is quiet
- **Timing**: Emitted AFTER TurnComplete as final safety signal
- **Consumer action**: Hide spinners, update activity indicators
- **Example**: Remove "typing" animation, update last activity timestamp
### Event Ordering Guarantee
```text
1. MessageStarted (message created)
2. SessionUpdate::*Chunk (content streaming)
3. SessionUpdate::ToolCall* (tool execution)
4. MessageCompleted (metadata ready) ← UI: "Complete"
5. TurnComplete ← FINALIZE HERE!
6. SessionIdle ← UI: hide spinner
```
### Consumer Behavior Table
| Consumer | MessageCompleted | TurnComplete | SessionIdle |
|----------|------------------|--------------|-------------|
| **Archivist** | Ignore | **Finalize and write** | Safety net |
| **UI Cache** | Update status | **Lock state** | Hide spinner |
| **Conductor Bridge** | - | **Flush response** | Fallback flush |
### TurnCompleteTrigger Variants
The `TurnCompleteTrigger` enum indicates **how** the system determined completion:
- **`ExplicitSignal`**: Upstream provider sent explicit completion (e.g., OpenCode session.idle)
- **`ResponseReceived`**: JSON-RPC response received (ACP stdio - response is last message)
- **`OperationsComplete`**: All tracked operations finished (e.g., pending tool calls resolved)
- **`IdleTimeout { duration_ms }`**: Timeout-based detection (fallback mechanism)
**For most consumers**, treat all triggers the same - the turn is complete. The trigger type is primarily for debugging and observability.
## Usage Pattern
```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: {}", text);
}
}
SessionUpdate::ToolCall { tool_call, .. } => {
println!("Tool: {}", tool_call.tool_name);
}
_ => {}
}
}
_ => {}
}
}
```
## Architecture
```
dirigent_protocol/
├── src/
│ ├── types/ # Core types
│ │ ├── content.rs # ContentBlock definitions
│ │ ├── updates.rs # SessionUpdate variants
│ │ ├── tool.rs # ToolCall, ToolCallStatus
│ │ └── meta.rs # Provider metadata
│ ├── session.rs # Session types
│ ├── conversation.rs # Message types
│ ├── events.rs # Event enum
│ └── adapters/ # Provider adapters
│ ├── opencode.rs # OpenCode translation
│ └── rest.rs # REST translation
├── docs/ # Detailed documentation
├── examples/ # Usage examples
└── tests/ # Integration tests
```
## Version 0.2.0 Changes
**Breaking:**
- Removed `Event::MessagePartAdded`
**New:**
- `SessionUpdate` event system (ACP-style)
- `ContentBlock` types (MCP-compatible)
- `ToolCall` with lifecycle tracking
- Provider metadata via `_meta`
See [docs/migration_from_0.1.md](docs/migration_from_0.1.md) for migration guide.
## Development
### Running Tests
```bash
cargo test --package dirigent_protocol
```
### Checking Code
```bash
cargo check --package dirigent_protocol
```
### Running Examples
```bash
cargo run --package dirigent_protocol --example session_metadata_demo
```
## Integration
This package is used by:
- **api** package: Server functions consume protocol events
- **web** package: UI renders protocol events
- **dirigent_core** (future): Runtime emits protocol events
## Adapters
The adapter system translates provider-specific events to Dirigent Protocol:
- **OpenCodeAdapter**: Translates OpenCode.ai events
- **RESTAdapter**: Converts REST API responses
Adapters preserve provider metadata in the `_meta` field for debugging and traceability.
## Current Scope
**Phase 1 (Implemented):**
- User/Agent/Thought message streaming
- Tool lifecycle (Pending → Running → Completed/Error)
- Text and ResourceLink content types
- Provider metadata support
**Deferred to Future Phases:**
- Plans and mode switching
- Permission system
- Embedded resources (full content)
- Rich media (images, audio)
- Multi-agent communication
See [../../docs/building/03_acp_prep/04_first_order_refactor.md](../../docs/building/03_acp_prep/04_first_order_refactor.md) for the full plan.
## Standards Alignment
**Agent-Client Protocol (ACP):**
- Session-centric streaming
- Separate content types (user/agent/thought)
- Tool status tracking
**Model Context Protocol (MCP):**
- ContentBlock structure
- Resource links
- Extensible content types
Differences from standards are documented in [../../docs/architecture/protocol.md](../../docs/architecture/protocol.md).
## Anti-Patterns
### Timeout-Based Event Waiting (FORBIDDEN)
**Never use timeout-based waiting to receive events that should be available immediately.**
```rust
// ❌ BAD - Race condition waiting for event
async fn wait_for_metadata_event(
events: &mut broadcast::Receiver<Event>,
timeout: Duration,
) -> Option<SessionMetadataReceived> {
let start = Instant::now();
while start.elapsed() < timeout {
match tokio::time::timeout(Duration::from_millis(100), events.recv()).await {
Ok(Ok(Event::SessionMetadataReceived { .. })) => return Some(...),
_ => continue,
}
}
None
}
```
**Why this is wrong:**
1. **Race condition**: The event may have been emitted before the receiver subscribed
2. **Arbitrary delays**: 500ms waits add latency for no good reason
3. **Silent failures**: Timeout expiring doesn't indicate the real problem
4. **Fragile**: Works "most of the time" but fails under load or timing variations
**Instead, pass data directly:**
```rust
// ✅ GOOD - Extract data from existing events
async fn create_session_in_connector(...) -> Result<(String, Option<Models>, Option<Modes>), String> {
// The SessionCreated event already contains models/modes
match event {
Event::SessionCreated { session, .. } => {
Ok((session.id, session.models, session.modes))
}
}
}
```
**Rule**: If you find yourself writing `timeout(Duration::from_millis(N), events.recv())` to wait for an event that "should" arrive, the architecture is wrong. Refactor to pass data directly through return values or existing event payloads.
## Contributing
When adding features:
1. Update type definitions in `src/types/`
2. Add comprehensive tests
3. Update documentation in `docs/`
4. Add examples if applicable
5. Update CHANGELOG.md
## See Also
- Main project: [../../CLAUDE.md](../../CLAUDE.md)
- Architecture docs: [../../docs/architecture/](../../docs/architecture/)
- Building docs: [../../docs/building/](../../docs/building/)