sacp 0.1.0

Core protocol types and traits for SACP (Symposium's extensions to ACP)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# SCP - Symposium Component Protocol

A Rust library providing foundational building blocks for implementing the Symposium Component Protocol. Currently provides a generic JSON-RPC 2.0 implementation and ACP (Agent Client Protocol) support for both agents and editors.

## Architecture Overview

The library is structured in three layers:

```
┌─────────────────────────────────────┐
│  ACP Protocol Layer                 │  ← Agent Client Protocol support
│  (acp/agent.rs, acp/editor.rs)     │
├─────────────────────────────────────┤
│  JSON-RPC 2.0 Layer                 │  ← Generic JSON-RPC implementation
│  (jsonrpc.rs, jsonrpc/actors.rs)   │
├─────────────────────────────────────┤
│  Async I/O (tokio, futures)         │  ← Transport layer
└─────────────────────────────────────┘
```

### Design Principles

1. **Layer independence**: The JSON-RPC layer has no knowledge of ACP. You can use it for any JSON-RPC application.
2. **Type safety**: Request/response pairs are statically typed using traits, catching mismatches at compile time.
3. **Handler composition**: Multiple handlers can be chained together, each claiming specific message types.
4. **Actor-based concurrency**: Message processing is split across three cooperating actors to prevent deadlocks.

## JSON-RPC Layer

The JSON-RPC layer provides a generic implementation of the JSON-RPC 2.0 protocol over stdio or any async read/write streams.

### Core Types

- **`JsonRpcConnection<H>`**: Main entry point. Manages bidirectional JSON-RPC communication.
- **`JsonRpcHandler`**: Trait for implementing message handlers.
- **`JsonRpcCx`**: Context for sending requests and notifications.
- **`JsonRpcRequestCx<T>`**: Context for responding to incoming requests.

### Actor Architecture

The connection spawns three actors that cooperate via channels:

```
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│ Outgoing Actor   │     │ Incoming Actor   │     │ Reply Actor      │
│                  │     │                  │     │                  │
│ Serializes and   │────▶│ Deserializes and │────▶│ Correlates       │
│ writes messages  │     │ routes to        │     │ request IDs with │
│ to transport     │     │ handlers         │     │ response futures │
└──────────────────┘     └──────────────────┘     └──────────────────┘
```

**Why this design?**
- The outgoing actor handles all writes, preventing interleaved JSON.
- The incoming actor routes messages to handlers without blocking on I/O.
- The reply actor manages the map of pending requests, keeping correlation logic centralized.

### Handler Chain Pattern

Handlers use a chain-of-responsibility pattern. When a message arrives, each handler gets a chance to claim it:

```rust
impl JsonRpcHandler for MyHandler {
    async fn handle_request(&mut self, method: &str, params: &Option<Params>,
                           response: JsonRpcRequestCx<Response>)
                           -> Result<Handled<JsonRpcRequestCx<Response>>, Error> {
        if method == "my_method" {
            // Process and respond
            response.respond(MyResponse { ... })?;
            Ok(Handled::Yes)
        } else {
            // Pass to next handler
            Ok(Handled::No(response))
        }
    }
}
```

Handlers are added via `add_handler()` and tried in order until one returns `Handled::Yes`.

### Example: JSON-RPC Echo Server

```rust
use scp::{JsonRpcConnection, JsonRpcHandler, JsonRpcRequestCx, Handled};
use futures::io::{AsyncRead, AsyncWrite};

// Define request/response types
#[derive(serde::Deserialize, serde::Serialize)]
struct EchoRequest { message: String }

#[derive(serde::Deserialize, serde::Serialize)]
struct EchoResponse { echo: String }

impl JsonRpcRequest for EchoRequest {
    type Response = EchoResponse;
    const METHOD: &'static str = "echo";
}

// Implement handler
struct EchoHandler;

impl JsonRpcHandler for EchoHandler {
    async fn handle_request(&mut self, method: &str, params: &Option<jsonrpcmsg::Params>,
                           response: JsonRpcRequestCx<jsonrpcmsg::Response>)
                           -> Result<Handled<JsonRpcRequestCx<jsonrpcmsg::Response>>, acp::Error> {
        if method == "echo" {
            let request: EchoRequest = scp::util::json_cast(params)?;
            response.cast().respond(EchoResponse {
                echo: request.message
            })?;
            Ok(Handled::Yes)
        } else {
            Ok(Handled::No(response))
        }
    }
}

// Run server
#[tokio::main]
async fn main() -> Result<(), acp::Error> {
    let stdin = tokio::io::stdin();
    let stdout = tokio::io::stdout();

    JsonRpcConnection::new(stdout, stdin)
        .add_handler(EchoHandler)
        .serve()
        .await
}
```

### Example: JSON-RPC Client

```rust
use scp::{JsonRpcConnection, JsonRpcCx};

#[tokio::main]
async fn main() -> Result<(), acp::Error> {
    let (stdin, stdout) = /* your streams */;

    JsonRpcConnection::new(stdout, stdin)
        .with_client(|cx: JsonRpcCx| async move {
            // Send a request
            let response = cx.send_request(EchoRequest {
                message: "hello".to_string()
            }).recv().await?;

            println!("Got echo: {}", response.echo);
            Ok(())
        })
        .await
}
```

The connection serves messages in the background while your client function runs, then cleanly shuts down when the client function returns.

## ACP Protocol Layer

The ACP layer builds on the JSON-RPC foundation to implement the Agent Client Protocol, which defines bidirectional communication between agents and editors.

### Core Types

**For implementing agents:**
- **`AcpAgent<CB>`**: Handler for agent-side messages (requests from editors).
- **`AcpAgentCallbacks`**: Trait you implement to handle requests agents receive.

**For implementing editors:**
- **`AcpEditor<CB>`**: Handler for editor-side messages (requests from agents).
- **`AcpEditorCallbacks`**: Trait you implement to handle requests editors receive.

**For proxies:** Implement both `AcpAgentCallbacks` and `AcpEditorCallbacks` to sit in the middle of the communication chain.

### ACP Protocol Methods

**Agent-side methods** (editors → agents):
- `initialize` - Protocol negotiation and capability exchange
- `authenticate` - Authentication flow
- `session/new` - Create a new agent session
- `session/load` - Load an existing session
- `session/prompt` - Send a user prompt to the agent
- `session/set_mode` - Change session mode
- `session/cancel` - Cancel an in-progress request (notification)

**Editor-side methods** (agents → editors):
- `session/request_permission` - Ask user for permission to execute tools
- `fs/read_text_file` - Read file contents
- `fs/write_text_file` - Write to files
- `terminal/create` - Start a terminal command
- `terminal/output` - Get terminal output
- `terminal/wait_for_exit` - Wait for command completion
- `terminal/kill` - Terminate running command
- `terminal/release` - Release terminal resources
- `session/update` - Stream progress updates (notification)

### Example: Minimal ACP Agent

```rust
use scp::acp::{AcpAgent, AcpAgentCallbacks};
use scp::{JsonRpcConnection, JsonRpcRequestCx};
use agent_client_protocol as acp;

// Implement the callbacks
struct MyAgent;

impl AcpAgentCallbacks for MyAgent {
    async fn initialize(&mut self, args: acp::InitializeRequest,
                       response: JsonRpcRequestCx<acp::InitializeResponse>)
                       -> Result<(), acp::Error> {
        // Advertise capabilities
        response.respond(acp::InitializeResponse {
            capabilities: acp::AgentCapabilities {
                streaming: Some(true),
                ..Default::default()
            },
            ..Default::default()
        })?;
        Ok(())
    }

    async fn prompt(&mut self, args: acp::PromptRequest,
                   response: JsonRpcRequestCx<acp::PromptResponse>)
                   -> Result<(), acp::Error> {
        // Get the prompt text from the request
        let prompt_text = args.prompt.text;

        // Process the prompt (simplified)
        let reply = format!("Echo: {}", prompt_text);

        // Send response
        response.respond(acp::PromptResponse {
            text: Some(reply),
            ..Default::default()
        })?;
        Ok(())
    }

    // Implement other required methods...
    async fn authenticate(&mut self, args: acp::AuthenticateRequest,
                         response: JsonRpcRequestCx<acp::AuthenticateResponse>)
                         -> Result<(), acp::Error> { todo!() }
    async fn session_cancel(&mut self, args: acp::CancelNotification,
                           cx: &JsonRpcCx) -> Result<(), acp::Error> { todo!() }
    async fn new_session(&mut self, args: acp::NewSessionRequest,
                        response: JsonRpcRequestCx<acp::NewSessionResponse>)
                        -> Result<(), acp::Error> { todo!() }
    async fn load_session(&mut self, args: acp::LoadSessionRequest,
                         response: JsonRpcRequestCx<acp::LoadSessionResponse>)
                         -> Result<(), acp::Error> { todo!() }
    async fn set_session_mode(&mut self, args: acp::SetSessionModeRequest,
                              response: JsonRpcRequestCx<acp::SetSessionModeResponse>)
                              -> Result<(), acp::Error> { todo!() }
}

#[tokio::main]
async fn main() -> Result<(), acp::Error> {
    let agent = MyAgent;
    let acp_handler = AcpAgent::new(agent);

    JsonRpcConnection::new(tokio::io::stdout(), tokio::io::stdin())
        .add_handler(acp_handler)
        .serve()
        .await
}
```

### Combining Multiple Handlers

You can chain multiple handlers to extend ACP with custom methods:

```rust
struct CustomHandler;

impl JsonRpcHandler for CustomHandler {
    async fn handle_request(&mut self, method: &str, /* ... */) {
        if method == "custom/my_extension" {
            // Handle your custom method
            Ok(Handled::Yes)
        } else {
            Ok(Handled::No(response))
        }
    }
}

// Chain handlers: try CustomHandler first, then AcpAgent
JsonRpcConnection::new(stdout, stdin)
    .add_handler(CustomHandler)
    .add_handler(AcpAgent::new(my_agent))
    .serve()
    .await
```

This pattern enables the proxy architecture: each proxy can add its own handler to the chain while forwarding unhandled messages downstream.

## Using the Library

### As a JSON-RPC Server

1. Define your request/response types implementing `serde::Serialize` and `serde::Deserialize`
2. Implement `JsonRpcRequest` trait for your request types
3. Create a handler struct implementing `JsonRpcHandler`
4. Build a `JsonRpcConnection` with your handler and call `.serve()`

### As an ACP Agent

1. Create a struct to hold your agent state
2. Implement `AcpAgentCallbacks` trait with your agent logic
3. Wrap it in `AcpAgent::new(your_agent)`
4. Add to a `JsonRpcConnection` and serve
5. Use `AcpAgentExt` trait methods to make requests to the editor:

```rust
use scp::acp::AcpAgentExt;  // Import the extension trait

async fn prompt(&mut self, args: PromptRequest, response: JsonRpcRequestCx<PromptResponse>) {
    let cx = response.json_rpc_cx();

    // Extension trait provides convenient methods
    let content = cx.read_text_file(ReadTextFileRequest {
        path: "src/main.rs".into(),
        ..Default::default()
    }).recv().await?;

    cx.session_update(SessionNotification { /* ... */ })?;
}
```

### As an ACP Editor

1. Create a struct to hold your editor state
2. Implement `AcpEditorCallbacks` trait to handle agent requests
3. Wrap it in `AcpEditor::new(your_editor)`
4. Add to a `JsonRpcConnection` and serve
5. Use `AcpEditorExt` trait methods to make requests to the agent:

```rust
use scp::acp::AcpEditorExt;  // Import the extension trait

async fn read_text_file(&mut self, args: ReadTextFileRequest, response: JsonRpcRequestCx<ReadTextFileResponse>) {
    let cx = response.json_rpc_cx();

    // Extension trait provides convenient methods
    let result = cx.prompt(PromptRequest { /* ... */ }).recv().await?;
}
```

### As an ACP Proxy

Proxies implement both callback traits:
1. Implement `AcpAgentCallbacks` to receive messages from upstream (editor)
2. Implement `AcpEditorCallbacks` to receive messages from downstream (agent)
3. Use `cx.send_request()` to forward and transform messages in both directions
4. Add custom handlers for proxy-specific extensions

## Type Safety Patterns

### Request/Response Correlation

The `JsonRpcRequest` trait ensures responses match requests at compile time:

```rust
impl JsonRpcRequest for MyRequest {
    type Response = MyResponse;  // Compiler enforces this pairing
    const METHOD: &'static str = "my_method";
}

// This works:
let response: MyResponse = cx.send_request(MyRequest { ... }).recv().await?;

// This would be a compile error:
let wrong: OtherResponse = cx.send_request(MyRequest { ... }).recv().await?;
```

### Response Context Casting

When handling generic `jsonrpcmsg::Response`, use `.cast()` to get typed context:

```rust
async fn handle_request(&mut self, method: &str, params: &Option<Params>,
                       response: JsonRpcRequestCx<jsonrpcmsg::Response>) {
    if method == "my_method" {
        let request: MyRequest = json_cast(params)?;

        // Cast to typed response context
        response.cast::<MyResponse>().respond(MyResponse { ... })?;
    }
}
```

## Error Handling

The library uses two error types:

- **`acp::Error`**: JSON-RPC protocol errors (method not found, invalid params, etc.)
- **`acp::Error`**: ACP protocol errors, automatically converted to JSON-RPC errors

Convert ACP errors using the utility function:

```rust
use scp::util::acp_to_jsonrpc_error;

let acp_err = acp::Error { code: 1000, message: "Agent busy".into(), data: None };
let jsonrpc_err = acp_to_jsonrpc_error(acp_err);
```

## Implementation Status

### Complete
- ✅ JSON-RPC 2.0 server and client implementation
- ✅ Actor-based message routing
- ✅ Handler chain pattern
- ✅ ACP agent-side support (handling requests from editors)
- ✅ ACP editor-side support (handling requests from agents)
- ✅ Type-safe request/response correlation

### In Progress
- 🚧 SCP-specific protocol extensions (`_scp/successor/*` messages)

### Planned
- ⏳ Orchestrator binary for managing proxy chains
- ⏳ Reference proxy implementations
- ⏳ Process lifecycle management

## Design Rationale

### Why actors instead of async/await everywhere?

The actor pattern separates concerns and prevents deadlocks that can occur when multiple async tasks try to write to the same transport. The outgoing actor ensures messages are serialized atomically, while the incoming actor can dispatch to handlers without blocking on I/O.

### Why trait-based handlers instead of closures?

Traits enable:
- Stateful handlers that can maintain connections or track sessions
- Handler composition via the chain pattern
- Clear separation between protocol layers
- Testable, modular components

### Why separate the JSON-RPC and ACP layers?

This design allows:
- Reusing the JSON-RPC implementation for non-ACP protocols
- Testing each layer independently
- Adding SCP extensions without modifying ACP support
- Building proxies that layer additional behavior

The JSON-RPC layer is protocol-agnostic and could be extracted as a standalone crate if needed.

## Examples Directory

See the `examples/` directory for complete working examples:
- `echo_server.rs` - Minimal JSON-RPC server
- `echo_client.rs` - JSON-RPC client making requests
- `acp_agent.rs` - Basic ACP agent implementation

## Dependencies

- `agent-client-protocol` - ACP protocol types and definitions
- `jsonrpcmsg` - JSON-RPC message types
- `tokio` - Async runtime
- `futures` - Async utilities and traits
- `serde` / `serde_json` - Serialization

## Contributing

When extending the library:

1. **Keep layers independent**: Changes to JSON-RPC shouldn't require ACP changes
2. **Maintain type safety**: Use traits to enforce compile-time guarantees
3. **Document actor interactions**: Changes to the actor system should document message flows
4. **Add tests**: Unit tests for protocol logic, integration tests for end-to-end flows