|
1 | 1 | pub(crate) mod fs_read_text_file; |
| 2 | +pub(crate) mod fs_write_text_file; |
2 | 3 | pub(crate) mod request_permission; |
3 | 4 | pub(crate) mod rpc_reply; |
4 | 5 | pub(crate) mod session_update; |
@@ -196,6 +197,18 @@ async fn dispatch_client_method< |
196 | 197 | ) |
197 | 198 | .await; |
198 | 199 | } |
| 200 | + ClientMethod::FsWriteTextFile => { |
| 201 | + fs_write_text_file::handle( |
| 202 | + &payload, |
| 203 | + ctx.client, |
| 204 | + reply.as_deref(), |
| 205 | + ctx.nats, |
| 206 | + parsed.session_id.as_str(), |
| 207 | + ctx.serializer, |
| 208 | + ctx.bridge.config.max_nats_payload_bytes(), |
| 209 | + ) |
| 210 | + .await; |
| 211 | + } |
199 | 212 | ClientMethod::SessionRequestPermission => { |
200 | 213 | request_permission::handle( |
201 | 214 | &payload, |
@@ -280,7 +293,7 @@ mod tests { |
280 | 293 | RequestPermissionOutcome, RequestPermissionRequest, RequestPermissionResponse, |
281 | 294 | SessionNotification, SessionUpdate, TerminalExitStatus, TerminalOutputRequest, |
282 | 295 | TerminalOutputResponse, ToolCallUpdate, ToolCallUpdateFields, WaitForTerminalExitRequest, |
283 | | - WaitForTerminalExitResponse, |
| 296 | + WaitForTerminalExitResponse, WriteTextFileRequest, WriteTextFileResponse, |
284 | 297 | }; |
285 | 298 | use async_trait::async_trait; |
286 | 299 | use std::cell::RefCell; |
@@ -351,6 +364,13 @@ mod tests { |
351 | 364 | Ok(ReadTextFileResponse::new("mock file content".to_string())) |
352 | 365 | } |
353 | 366 |
|
| 367 | + async fn write_text_file( |
| 368 | + &self, |
| 369 | + _: WriteTextFileRequest, |
| 370 | + ) -> agent_client_protocol::Result<WriteTextFileResponse> { |
| 371 | + Ok(WriteTextFileResponse::new()) |
| 372 | + } |
| 373 | + |
354 | 374 | async fn create_terminal( |
355 | 375 | &self, |
356 | 376 | _: CreateTerminalRequest, |
@@ -568,6 +588,87 @@ mod tests { |
568 | 588 | assert_eq!(nats.published_messages(), vec!["_INBOX.reply"]); |
569 | 589 | } |
570 | 590 |
|
| 591 | + #[tokio::test] |
| 592 | + async fn dispatch_client_method_dispatches_fs_write_text_file() { |
| 593 | + let nats = MockNatsClient::new(); |
| 594 | + let client = MockClient::new(); |
| 595 | + let session_id = AcpSessionId::new("sess-1").unwrap(); |
| 596 | + |
| 597 | + let envelope = Request { |
| 598 | + id: RequestId::Number(42), |
| 599 | + method: std::sync::Arc::from("fs/write_text_file"), |
| 600 | + params: Some(WriteTextFileRequest::new( |
| 601 | + agent_client_protocol::SessionId::from("sess-1"), |
| 602 | + "/tmp/test.txt".to_string(), |
| 603 | + "content".to_string(), |
| 604 | + )), |
| 605 | + }; |
| 606 | + let payload = bytes::Bytes::from(serde_json::to_vec(&envelope).unwrap()); |
| 607 | + |
| 608 | + let parsed = crate::nats::ParsedClientSubject { |
| 609 | + session_id, |
| 610 | + method: ClientMethod::FsWriteTextFile, |
| 611 | + }; |
| 612 | + |
| 613 | + let bridge = make_bridge(nats.clone()); |
| 614 | + let ctx = DispatchContext { |
| 615 | + nats: &nats, |
| 616 | + client: &client, |
| 617 | + bridge: &bridge, |
| 618 | + serializer: &StdJsonSerialize, |
| 619 | + }; |
| 620 | + dispatch_client_method( |
| 621 | + "acp.sess-1.client.fs.write_text_file", |
| 622 | + parsed, |
| 623 | + payload, |
| 624 | + Some("_INBOX.reply".to_string()), |
| 625 | + &ctx, |
| 626 | + ) |
| 627 | + .await; |
| 628 | + |
| 629 | + assert_eq!(nats.published_messages(), vec!["_INBOX.reply"]); |
| 630 | + let payloads = nats.published_payloads(); |
| 631 | + assert_eq!(payloads.len(), 1); |
| 632 | + let response: serde_json::Value = |
| 633 | + serde_json::from_slice(payloads[0].as_ref()).expect("response should be valid JSON"); |
| 634 | + assert_eq!(response["id"], 42, "response must be JSON-RPC envelope with matching id"); |
| 635 | + assert!(response.get("result").is_some(), "success response must have result field"); |
| 636 | + } |
| 637 | + |
| 638 | + #[tokio::test] |
| 639 | + async fn fs_write_text_file_round_trip() { |
| 640 | + let nats = MockNatsClient::new(); |
| 641 | + let client = MockClient::new(); |
| 642 | + let envelope = Request { |
| 643 | + id: RequestId::Number(1), |
| 644 | + method: std::sync::Arc::from("fs/write_text_file"), |
| 645 | + params: Some(WriteTextFileRequest::new( |
| 646 | + agent_client_protocol::SessionId::from("session-001"), |
| 647 | + "/tmp/test.txt".to_string(), |
| 648 | + "content".to_string(), |
| 649 | + )), |
| 650 | + }; |
| 651 | + let payload = serde_json::to_vec(&envelope).unwrap(); |
| 652 | + |
| 653 | + fs_write_text_file::handle( |
| 654 | + &payload, |
| 655 | + &client, |
| 656 | + Some("_INBOX.reply"), |
| 657 | + &nats, |
| 658 | + "session-001", |
| 659 | + &StdJsonSerialize, |
| 660 | + 1_048_576, |
| 661 | + ) |
| 662 | + .await; |
| 663 | + |
| 664 | + assert_eq!(nats.published_messages(), vec!["_INBOX.reply"]); |
| 665 | + let payloads = nats.published_payloads(); |
| 666 | + assert_eq!(payloads.len(), 1); |
| 667 | + let response: serde_json::Value = serde_json::from_slice(payloads[0].as_ref()).unwrap(); |
| 668 | + assert_eq!(response["id"], 1); |
| 669 | + assert!(response.get("result").is_some()); |
| 670 | + } |
| 671 | + |
571 | 672 | #[tokio::test] |
572 | 673 | async fn dispatch_client_method_dispatches_terminal_create() { |
573 | 674 | let nats = MockNatsClient::new(); |
@@ -2354,6 +2455,13 @@ mod tests { |
2354 | 2455 | Ok(ReadTextFileResponse::new("mock file content".to_string())) |
2355 | 2456 | } |
2356 | 2457 |
|
| 2458 | + async fn write_text_file( |
| 2459 | + &self, |
| 2460 | + _: WriteTextFileRequest, |
| 2461 | + ) -> agent_client_protocol::Result<WriteTextFileResponse> { |
| 2462 | + Ok(WriteTextFileResponse::new()) |
| 2463 | + } |
| 2464 | + |
2357 | 2465 | async fn create_terminal( |
2358 | 2466 | &self, |
2359 | 2467 | _: CreateTerminalRequest, |
@@ -2421,6 +2529,13 @@ mod tests { |
2421 | 2529 | Ok(ReadTextFileResponse::new("mock file content".to_string())) |
2422 | 2530 | } |
2423 | 2531 |
|
| 2532 | + async fn write_text_file( |
| 2533 | + &self, |
| 2534 | + _: WriteTextFileRequest, |
| 2535 | + ) -> agent_client_protocol::Result<WriteTextFileResponse> { |
| 2536 | + Ok(WriteTextFileResponse::new()) |
| 2537 | + } |
| 2538 | + |
2424 | 2539 | async fn create_terminal( |
2425 | 2540 | &self, |
2426 | 2541 | _: CreateTerminalRequest, |
@@ -2494,6 +2609,13 @@ mod tests { |
2494 | 2609 | Ok(ReadTextFileResponse::new("mock file content".to_string())) |
2495 | 2610 | } |
2496 | 2611 |
|
| 2612 | + async fn write_text_file( |
| 2613 | + &self, |
| 2614 | + _: WriteTextFileRequest, |
| 2615 | + ) -> agent_client_protocol::Result<WriteTextFileResponse> { |
| 2616 | + Ok(WriteTextFileResponse::new()) |
| 2617 | + } |
| 2618 | + |
2497 | 2619 | async fn create_terminal( |
2498 | 2620 | &self, |
2499 | 2621 | _: CreateTerminalRequest, |
@@ -2565,6 +2687,13 @@ mod tests { |
2565 | 2687 | Ok(ReadTextFileResponse::new("mock file content".to_string())) |
2566 | 2688 | } |
2567 | 2689 |
|
| 2690 | + async fn write_text_file( |
| 2691 | + &self, |
| 2692 | + _: WriteTextFileRequest, |
| 2693 | + ) -> agent_client_protocol::Result<WriteTextFileResponse> { |
| 2694 | + Ok(WriteTextFileResponse::new()) |
| 2695 | + } |
| 2696 | + |
2568 | 2697 | async fn create_terminal( |
2569 | 2698 | &self, |
2570 | 2699 | _: CreateTerminalRequest, |
@@ -3136,6 +3265,13 @@ mod tests { |
3136 | 3265 | Ok(ReadTextFileResponse::new("file contents".to_string())) |
3137 | 3266 | } |
3138 | 3267 |
|
| 3268 | + async fn write_text_file( |
| 3269 | + &self, |
| 3270 | + _: WriteTextFileRequest, |
| 3271 | + ) -> agent_client_protocol::Result<WriteTextFileResponse> { |
| 3272 | + Ok(WriteTextFileResponse::new()) |
| 3273 | + } |
| 3274 | + |
3139 | 3275 | async fn terminal_output( |
3140 | 3276 | &self, |
3141 | 3277 | _: TerminalOutputRequest, |
|
0 commit comments