From 57f72394d410f97b12f5ace1c59fb9a128d83f45 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 25 Nov 2025 14:51:27 +0100 Subject: [PATCH 1/3] Move to workspace deps for main crate --- Cargo.lock | 300 +++++++++++++-------------- Cargo.toml | 14 +- src/agent-client-protocol/Cargo.toml | 42 ++-- src/agent-client-protocol/src/rpc.rs | 11 +- 4 files changed, 182 insertions(+), 185 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03ffac3..623af5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,6 @@ dependencies = [ "futures", "futures-util", "log", - "parking_lot", "piper", "pretty_assertions", "rustyline", @@ -40,9 +39,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -58,9 +57,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -73,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -88,22 +87,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -155,9 +154,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "boxfnonce" @@ -179,15 +178,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.45" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "shlex", @@ -195,9 +194,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -216,14 +215,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -231,9 +230,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -417,9 +416,9 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -451,7 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -510,9 +509,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fnv" @@ -632,9 +631,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -644,11 +643,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -663,7 +662,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.1", + "windows-core 0.62.2", ] [[package]] @@ -683,9 +682,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -693,9 +692,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" @@ -705,22 +704,22 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", @@ -755,9 +754,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "linux-raw-sys" @@ -767,11 +766,10 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -792,9 +790,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "migration-examples" @@ -811,13 +809,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "wasi", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -847,7 +845,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -873,9 +871,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking" @@ -885,9 +883,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -895,15 +893,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -977,9 +975,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1000,9 +998,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1054,27 +1052,27 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -1106,9 +1104,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rmcp" @@ -1156,7 +1154,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1167,9 +1165,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustyline" -version = "17.0.1" +version = "17.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6614df0b6d4cfb20d1d5e295332921793ce499af3ebc011bf1e393380e1e492" +checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" dependencies = [ "bitflags", "cfg-if", @@ -1328,9 +1326,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "chrono", "dyn-clone", @@ -1342,9 +1340,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" +checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" dependencies = [ "proc-macro2", "quote", @@ -1360,9 +1358,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1370,18 +1368,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1435,9 +1433,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -1456,12 +1454,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1472,9 +1470,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1575,7 +1573,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1602,9 +1600,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -1702,9 +1700,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -1714,9 +1712,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -1844,15 +1842,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.62.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", - "windows-result 0.4.0", - "windows-strings 0.5.0", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -1896,9 +1894,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" @@ -1921,11 +1919,11 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -1939,11 +1937,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -1961,16 +1959,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.2.1", ] [[package]] @@ -1991,19 +1989,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2023,9 +2021,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2035,9 +2033,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2047,9 +2045,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2059,9 +2057,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2071,9 +2069,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2083,9 +2081,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2095,9 +2093,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2107,9 +2105,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" @@ -2134,18 +2132,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 011eec5..79f3d93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ tokio-util = { version = "0.7", features = ["compat"] } agent-client-protocol-schema = "0.6.3" # Serialization -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = { version = "1", features = ["raw_value"] } schemars = { version = "1.0", features = ["derive"] } # UUID generation @@ -42,6 +42,8 @@ anyhow = "1.0" thiserror = "2.0" # Logging +env_logger = "0.11" +log = "0.4" tracing = "0.1" tracing-appender = "0.2" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } @@ -53,14 +55,22 @@ rmcp = { version = "0.8.3", features = ["server", "transport-io", "schemars"] } clap = { version = "4.5", features = ["derive"] } # Other dependencies +async-broadcast = "0.7" +async-trait = "0.1" boxfnonce = "0.1.1" chrono = "0.4" +derive_more = { version = "2", features = ["from"] } futures = "0.3.31" fxhash = "0.2.1" jsonrpcmsg = "0.1.2" + # Testing expect-test = "1.5" +futures-util = { version = "0.3", features = ["io"] } +piper = "0.2" +pretty_assertions = "1" +rustyline = "17" tokio-test = "0.4" [workspace.lints.rust] diff --git a/src/agent-client-protocol/Cargo.toml b/src/agent-client-protocol/Cargo.toml index f72c461..c9d12fd 100644 --- a/src/agent-client-protocol/Cargo.toml +++ b/src/agent-client-protocol/Cargo.toml @@ -17,34 +17,24 @@ include = ["/src/**/*.rs", "/README.md", "/LICENSE", "/Cargo.toml"] unstable = ["agent-client-protocol-schema/unstable"] [dependencies] -agent-client-protocol-schema = { version = "0.6.2" } -anyhow = "1" -async-broadcast = "0.7" -async-trait = "0.1" -derive_more = { version = "2", features = ["from"] } -futures = { version = "0.3" } -log = "0.4" -parking_lot = "0.12" -serde = { version = "1", features = ["derive", "rc"] } -serde_json = { version = "1", features = ["raw_value"] } +agent-client-protocol-schema.workspace = true +anyhow.workspace = true +async-broadcast.workspace = true +async-trait.workspace = true +derive_more.workspace = true +futures.workspace = true +log.workspace = true +serde.workspace = true +serde_json.workspace = true [dev-dependencies] -env_logger = "0.11" -futures-util = { version = "0.3", features = ["io"] } -piper = "0.2" -pretty_assertions = "1" -rustyline = "17" -tokio = { version = "1", features = [ - "macros", - "rt", - "time", - "io-std", - "io-util", - "net", - "process", - "sync", -] } -tokio-util = { version = "0.7", features = ["compat"] } +env_logger.workspace = true +futures-util.workspace = true +piper.workspace = true +pretty_assertions.workspace = true +rustyline.workspace = true +tokio.workspace = true +tokio-util.workspace = true [lints] workspace = true diff --git a/src/agent-client-protocol/src/rpc.rs b/src/agent-client-protocol/src/rpc.rs index ee85815..e252308 100644 --- a/src/agent-client-protocol/src/rpc.rs +++ b/src/agent-client-protocol/src/rpc.rs @@ -3,7 +3,7 @@ use std::{ collections::HashMap, rc::Rc, sync::{ - Arc, + Arc, Mutex, atomic::{AtomicI64, Ordering}, }, }; @@ -22,7 +22,6 @@ use futures::{ io::BufReader, select_biased, }; -use parking_lot::Mutex; use serde::{Deserialize, de::DeserializeOwned}; use serde_json::value::RawValue; @@ -74,7 +73,7 @@ where broadcast_tx, ) .await; - pending_responses.lock().clear(); + pending_responses.lock().unwrap().clear(); result } }; @@ -116,7 +115,7 @@ where let (tx, rx) = oneshot::channel(); let id = self.next_id.fetch_add(1, Ordering::SeqCst); let id = RequestId::Number(id); - self.pending_responses.lock().insert( + self.pending_responses.lock().unwrap().insert( id.clone(), PendingResponse { deserialize: |value| { @@ -139,7 +138,7 @@ where }) .is_err() { - self.pending_responses.lock().remove(&id); + self.pending_responses.lock().unwrap().remove(&id); } async move { let result = rx @@ -208,7 +207,7 @@ where broadcast.outgoing(&error_response); } } - } else if let Some(pending_response) = pending_responses.lock().remove(&id) { + } else if let Some(pending_response) = pending_responses.lock().unwrap().remove(&id) { // Response if let Some(result_value) = message.result { broadcast.incoming_response(id, Ok(Some(result_value))); From 36f65ff63af9bdb6be65a4fe5b9677bc0268fc2f Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 25 Nov 2025 23:26:54 +0100 Subject: [PATCH 2/3] Update to agent-client-protocol-schema 0.7 --- Cargo.lock | 4 +- Cargo.toml | 2 +- examples/migration/agent_legacy.rs | 29 +- examples/migration/agent_sacp.rs | 26 +- .../migration/agent_with_callbacks_sacp.rs | 30 +- examples/migration/client_legacy.rs | 41 +-- examples/migration/client_sacp.rs | 20 +- src/agent-client-protocol/src/lib.rs | 38 ++- src/agent-client-protocol/src/rpc.rs | 10 +- src/agent-client-protocol/src/rpc_tests.rs | 323 ++++-------------- .../src/stream_broadcast.rs | 14 +- src/elizacp/src/lib.rs | 48 +-- src/sacp-conductor/src/conductor.rs | 2 +- .../src/conductor/mcp_bridge.rs | 14 +- .../tests/initialization_sequence.rs | 43 +-- src/sacp-conductor/tests/mcp-integration.rs | 49 +-- .../tests/mcp_integration/agent.rs | 71 ++-- src/sacp-proxy/src/mcp_server.rs | 8 +- src/sacp-proxy/src/to_from_successor.rs | 3 +- src/sacp-test/src/test_client.rs | 35 +- src/sacp-tokio/src/acp_agent.rs | 43 ++- src/sacp/examples/simple_agent.rs | 10 +- src/sacp/examples/yolo_one_shot_client.rs | 53 ++- src/sacp/src/capabilities.rs | 61 +--- src/sacp/src/jsonrpc.rs | 2 +- src/sacp/src/jsonrpc/actors.rs | 7 +- src/sacp/src/jsonrpc/task_actor.rs | 2 +- src/sacp/src/schema/enum_impls.rs | 36 +- src/sacp/src/util.rs | 4 +- src/sacp/tests/jsonrpc_error_handling.rs | 6 +- src/yopo/src/main.rs | 73 ++-- 31 files changed, 329 insertions(+), 778 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 623af5b..9888d78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,9 +26,9 @@ dependencies = [ [[package]] name = "agent-client-protocol-schema" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d08d095e8069115774caa50392e9c818e3fb1c482ef4f3153d26b4595482f2" +checksum = "a7d6917e22780934009eb99c32ff5f6e1bdc84c36f2a89ee10aa8b8580b2946c" dependencies = [ "anyhow", "derive_more", diff --git a/Cargo.toml b/Cargo.toml index 79f3d93..be67b2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.48", features = ["full"] } tokio-util = { version = "0.7", features = ["compat"] } # Protocol -agent-client-protocol-schema = "0.6.3" +agent-client-protocol-schema = "0.7.0" # Serialization serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/examples/migration/agent_legacy.rs b/examples/migration/agent_legacy.rs index 47f07dc..9946b20 100644 --- a/examples/migration/agent_legacy.rs +++ b/examples/migration/agent_legacy.rs @@ -4,10 +4,10 @@ use agent_client_protocol::{Agent, AgentSideConnection, Client}; use agent_client_protocol_schema::{ - AgentCapabilities, AuthenticateRequest, AuthenticateResponse, CancelNotification, Error, - ExtNotification, ExtRequest, ExtResponse, InitializeRequest, InitializeResponse, - LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, - PromptResponse, ReadTextFileRequest, ReadTextFileResponse, Result, SetSessionModeRequest, + AuthenticateRequest, AuthenticateResponse, CancelNotification, Error, ExtNotification, + ExtRequest, ExtResponse, InitializeRequest, InitializeResponse, LoadSessionRequest, + LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, + ReadTextFileRequest, ReadTextFileResponse, Result, SetSessionModeRequest, SetSessionModeResponse, StopReason, WriteTextFileRequest, WriteTextFileResponse, }; use futures::FutureExt; @@ -23,13 +23,7 @@ struct MyAgent { impl Agent for MyAgent { async fn initialize(&self, args: InitializeRequest) -> Result { eprintln!("[{}] Initializing", self.name); - Ok(InitializeResponse { - protocol_version: args.protocol_version, - agent_capabilities: AgentCapabilities::default(), - auth_methods: Default::default(), - agent_info: Default::default(), - meta: Default::default(), - }) + Ok(InitializeResponse::new(args.protocol_version)) } async fn authenticate(&self, _args: AuthenticateRequest) -> Result { @@ -38,21 +32,12 @@ impl Agent for MyAgent { async fn new_session(&self, _args: NewSessionRequest) -> Result { eprintln!("[{}] Creating session", self.name); - Ok(NewSessionResponse { - session_id: "session-1".into(), - modes: None, - meta: Default::default(), - #[cfg(feature = "unstable")] - models: None, - }) + Ok(NewSessionResponse::new("session-1".into())) } async fn prompt(&self, _args: PromptRequest) -> Result { eprintln!("[{}] Processing prompt", self.name); - Ok(PromptResponse { - stop_reason: StopReason::EndTurn, - meta: Default::default(), - }) + Ok(PromptResponse::new(StopReason::EndTurn)) } async fn cancel(&self, _args: CancelNotification) -> Result<()> { diff --git a/examples/migration/agent_sacp.rs b/examples/migration/agent_sacp.rs index 16a9f4d..327f4de 100644 --- a/examples/migration/agent_sacp.rs +++ b/examples/migration/agent_sacp.rs @@ -3,9 +3,8 @@ //! This example shows the handler-based approach for building agents. use sacp::schema::{ - AgentCapabilities, InitializeRequest, InitializeResponse, NewSessionRequest, - NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, StopReason, - WriteTextFileRequest, + InitializeRequest, InitializeResponse, NewSessionRequest, NewSessionResponse, PromptRequest, + PromptResponse, ReadTextFileRequest, StopReason, WriteTextFileRequest, }; use sacp::{JrHandlerChain, MessageAndCx, UntypedMessage}; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; @@ -20,34 +19,19 @@ async fn main() -> Result<(), sacp::Error> { // ANCHOR: handler_initialize .on_receive_request(async move |req: InitializeRequest, cx| { eprintln!("[{}] Initializing", agent_name); - cx.respond(InitializeResponse { - protocol_version: req.protocol_version, - agent_capabilities: AgentCapabilities::default(), - auth_methods: Default::default(), - agent_info: Default::default(), - meta: Default::default(), - }) + cx.respond(InitializeResponse::new(req.protocol_version)) }) // ANCHOR_END: handler_initialize // ANCHOR: handler_new_session .on_receive_request(async move |_req: NewSessionRequest, cx| { eprintln!("[{}] Creating session", agent_name); - cx.respond(NewSessionResponse { - session_id: "session-1".into(), - modes: None, - #[cfg(feature = "unstable")] - models: None, - meta: Default::default(), - }) + cx.respond(NewSessionResponse::new("session-1".into())) }) // ANCHOR_END: handler_new_session // ANCHOR: handler_prompt .on_receive_request(async move |_req: PromptRequest, cx| { eprintln!("[{}] Processing prompt", agent_name); - cx.respond(PromptResponse { - stop_reason: StopReason::EndTurn, - meta: Default::default(), - }) + cx.respond(PromptResponse::new(StopReason::EndTurn)) }) // ANCHOR_END: handler_prompt // ANCHOR: handler_client_requests diff --git a/examples/migration/agent_with_callbacks_sacp.rs b/examples/migration/agent_with_callbacks_sacp.rs index b7c2ffb..c2f5668 100644 --- a/examples/migration/agent_with_callbacks_sacp.rs +++ b/examples/migration/agent_with_callbacks_sacp.rs @@ -4,8 +4,8 @@ //! and the importance of not blocking the message handler. use sacp::schema::{ - AgentCapabilities, InitializeRequest, InitializeResponse, NewSessionRequest, - NewSessionResponse, PromptRequest, PromptResponse, StopReason, + InitializeRequest, InitializeResponse, NewSessionRequest, NewSessionResponse, PromptRequest, + PromptResponse, StopReason, }; use sacp::{JrHandlerChain, MessageAndCx, UntypedMessage}; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; @@ -15,22 +15,10 @@ async fn main() -> Result<(), sacp::Error> { JrHandlerChain::new() .name("callback-agent") .on_receive_request(async move |req: InitializeRequest, cx| { - cx.respond(InitializeResponse { - protocol_version: req.protocol_version, - agent_capabilities: AgentCapabilities::default(), - auth_methods: Default::default(), - agent_info: Default::default(), - meta: Default::default(), - }) + cx.respond(InitializeResponse::new(req.protocol_version)) }) .on_receive_request(async move |_req: NewSessionRequest, cx| { - cx.respond(NewSessionResponse { - session_id: "session-1".into(), - modes: None, - #[cfg(feature = "unstable")] - models: None, - meta: Default::default(), - }) + cx.respond(NewSessionResponse::new("session-1".into())) }) // ANCHOR: blocking_risk .on_receive_request(async move |_req: PromptRequest, cx| { @@ -47,10 +35,7 @@ async fn main() -> Result<(), sacp::Error> { // cx.respond(response) // ✅ GOOD: Respond immediately - cx.respond(PromptResponse { - stop_reason: StopReason::EndTurn, - meta: Default::default(), - }) + cx.respond(PromptResponse::new(StopReason::EndTurn)) }) // ANCHOR_END: blocking_risk // ANCHOR: spawn_alternative @@ -66,10 +51,7 @@ async fn main() -> Result<(), sacp::Error> { }); // Respond immediately - cx.respond(PromptResponse { - stop_reason: StopReason::EndTurn, - meta: Default::default(), - }) + cx.respond(PromptResponse::new(StopReason::EndTurn)) }) // ANCHOR_END: spawn_alternative .on_receive_message( diff --git a/examples/migration/client_legacy.rs b/examples/migration/client_legacy.rs index 34b68b2..68e12c4 100644 --- a/examples/migration/client_legacy.rs +++ b/examples/migration/client_legacy.rs @@ -2,18 +2,16 @@ //! //! This example shows how to connect to an agent and send requests. -use agent_client_protocol::{Agent, Client, ClientSideConnection}; -use agent_client_protocol_schema::{ - CreateTerminalRequest, CreateTerminalResponse, Error, ExtNotification, ExtRequest, ExtResponse, - InitializeRequest, KillTerminalCommandRequest, KillTerminalCommandResponse, NewSessionRequest, - PromptRequest, ReadTextFileRequest, ReadTextFileResponse, ReleaseTerminalRequest, - ReleaseTerminalResponse, RequestPermissionRequest, RequestPermissionResponse, Result, - SessionNotification, TerminalOutputRequest, TerminalOutputResponse, VERSION, - WaitForTerminalExitRequest, WaitForTerminalExitResponse, WriteTextFileRequest, - WriteTextFileResponse, +use agent_client_protocol::{ + Agent, Client, ClientSideConnection, CreateTerminalRequest, CreateTerminalResponse, Error, + ExtNotification, ExtRequest, ExtResponse, InitializeRequest, KillTerminalCommandRequest, + KillTerminalCommandResponse, NewSessionRequest, PromptRequest, ReadTextFileRequest, + ReadTextFileResponse, ReleaseTerminalRequest, ReleaseTerminalResponse, + RequestPermissionRequest, RequestPermissionResponse, Result, SessionNotification, + TerminalOutputRequest, TerminalOutputResponse, V1, WaitForTerminalExitRequest, + WaitForTerminalExitResponse, WriteTextFileRequest, WriteTextFileResponse, }; use futures::FutureExt; -use std::path::PathBuf; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; // ANCHOR: client_impl @@ -127,35 +125,18 @@ async fn main() -> Result<()> { // Now we can send requests using the connection // ANCHOR: send_requests // Initialize the agent - let init_response = connection - .initialize(InitializeRequest { - protocol_version: VERSION, - client_capabilities: Default::default(), - client_info: Default::default(), - meta: None, - }) - .await?; + let init_response = connection.initialize(InitializeRequest::new(V1)).await?; eprintln!("Agent initialized: {:?}", init_response.agent_info); // Create a session - let session_response = connection - .new_session(NewSessionRequest { - mcp_servers: vec![], - cwd: PathBuf::from("/"), - meta: None, - }) - .await?; + let session_response = connection.new_session(NewSessionRequest::new("/")).await?; eprintln!("Session created: {}", session_response.session_id); // Send a prompt let prompt_response = connection - .prompt(PromptRequest { - session_id: session_response.session_id, - prompt: vec![], - meta: None, - }) + .prompt(PromptRequest::new(session_response.session_id, vec![])) .await?; eprintln!("Prompt completed: {:?}", prompt_response.stop_reason); diff --git a/examples/migration/client_sacp.rs b/examples/migration/client_sacp.rs index b191b58..387c08c 100644 --- a/examples/migration/client_sacp.rs +++ b/examples/migration/client_sacp.rs @@ -7,7 +7,6 @@ use sacp::schema::{ InitializeRequest, NewSessionRequest, PromptRequest, ReadTextFileRequest, RequestPermissionRequest, SessionNotification, VERSION, WriteTextFileRequest, }; -use std::path::PathBuf; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; // ANCHOR: setup @@ -49,12 +48,7 @@ async fn main() -> Result<(), sacp::Error> { // ANCHOR: send_requests // Initialize the agent let init_response = cx - .send_request(InitializeRequest { - protocol_version: VERSION, - client_capabilities: Default::default(), - client_info: Default::default(), - meta: None, - }) + .send_request(InitializeRequest::new(VERSION)) .block_task() .await?; @@ -62,11 +56,7 @@ async fn main() -> Result<(), sacp::Error> { // Create a session let session_response = cx - .send_request(NewSessionRequest { - mcp_servers: vec![], - cwd: PathBuf::from("/"), - meta: None, - }) + .send_request(NewSessionRequest::new("/")) .block_task() .await?; @@ -74,11 +64,7 @@ async fn main() -> Result<(), sacp::Error> { // Send a prompt let prompt_response = cx - .send_request(PromptRequest { - session_id: session_response.session_id, - prompt: vec![], - meta: None, - }) + .send_request(PromptRequest::new(session_response.session_id, vec![])) .block_task() .await?; diff --git a/src/agent-client-protocol/src/lib.rs b/src/agent-client-protocol/src/lib.rs index e47a0e1..18d7d12 100644 --- a/src/agent-client-protocol/src/lib.rs +++ b/src/agent-client-protocol/src/lib.rs @@ -220,10 +220,10 @@ impl Side for ClientSide { } _ => { if let Some(custom_method) = method.strip_prefix('_') { - Ok(AgentRequest::ExtMethodRequest(ExtRequest { - method: custom_method.into(), - params: params.to_owned().into(), - })) + Ok(AgentRequest::ExtMethodRequest(ExtRequest::new( + custom_method, + params.to_owned(), + ))) } else { Err(Error::method_not_found()) } @@ -240,10 +240,10 @@ impl Side for ClientSide { .map_err(Into::into), _ => { if let Some(custom_method) = method.strip_prefix('_') { - Ok(AgentNotification::ExtNotification(ExtNotification { - method: custom_method.into(), - params: RawValue::from_string(params.get().to_string())?.into(), - })) + Ok(AgentNotification::ExtNotification(ExtNotification::new( + custom_method, + RawValue::from_string(params.get().to_string())?, + ))) } else { Err(Error::method_not_found()) } @@ -291,6 +291,7 @@ impl MessageHandler for T { let response = self.ext_method(args).await?; Ok(ClientResponse::ExtMethodResponse(response)) } + _ => Err(Error::method_not_found()), } } @@ -302,6 +303,8 @@ impl MessageHandler for T { AgentNotification::ExtNotification(args) => { self.ext_notification(args).await?; } + // Ignore unknown notifications + _ => {} } Ok(()) } @@ -520,10 +523,10 @@ impl Side for AgentSide { .map_err(Into::into), _ => { if let Some(custom_method) = method.strip_prefix('_') { - Ok(ClientRequest::ExtMethodRequest(ExtRequest { - method: custom_method.into(), - params: params.to_owned().into(), - })) + Ok(ClientRequest::ExtMethodRequest(ExtRequest::new( + custom_method, + params.to_owned(), + ))) } else { Err(Error::method_not_found()) } @@ -540,10 +543,10 @@ impl Side for AgentSide { .map_err(Into::into), _ => { if let Some(custom_method) = method.strip_prefix('_') { - Ok(ClientNotification::ExtNotification(ExtNotification { - method: custom_method.into(), - params: RawValue::from_string(params.get().to_string())?.into(), - })) + Ok(ClientNotification::ExtNotification(ExtNotification::new( + custom_method, + RawValue::from_string(params.get().to_string())?, + ))) } else { Err(Error::method_not_found()) } @@ -588,6 +591,7 @@ impl MessageHandler for T { let response = self.ext_method(args).await?; Ok(AgentResponse::ExtMethodResponse(response)) } + _ => Err(Error::method_not_found()), } } @@ -599,6 +603,8 @@ impl MessageHandler for T { ClientNotification::ExtNotification(args) => { self.ext_notification(args).await?; } + // Ignore unknown notifications + _ => {} } Ok(()) } diff --git a/src/agent-client-protocol/src/rpc.rs b/src/agent-client-protocol/src/rpc.rs index e252308..63f0b60 100644 --- a/src/agent-client-protocol/src/rpc.rs +++ b/src/agent-client-protocol/src/rpc.rs @@ -104,7 +104,7 @@ where method: method.into(), params, }) - .map_err(|_| Error::internal_error().with_data("failed to send notification")) + .map_err(|_| Error::internal_error().data("failed to send notification")) } pub(crate) fn request( @@ -121,9 +121,7 @@ where deserialize: |value| { serde_json::from_str::(value.get()) .map(|out| Box::new(out) as _) - .map_err(|_| { - Error::internal_error().with_data("failed to deserialize response") - }) + .map_err(|_| Error::internal_error().data("failed to deserialize response")) }, respond: tx, }, @@ -143,9 +141,9 @@ where async move { let result = rx .await - .map_err(|_| Error::internal_error().with_data("server shut down unexpectedly"))?? + .map_err(|_| Error::internal_error().data("server shut down unexpectedly"))?? .downcast::() - .map_err(|_| Error::internal_error().with_data("failed to deserialize response"))?; + .map_err(|_| Error::internal_error().data("failed to deserialize response"))?; Ok(*result) } diff --git a/src/agent-client-protocol/src/rpc_tests.rs b/src/agent-client-protocol/src/rpc_tests.rs index 1d726ab..5b6bcee 100644 --- a/src/agent-client-protocol/src/rpc_tests.rs +++ b/src/agent-client-protocol/src/rpc_tests.rs @@ -49,10 +49,7 @@ impl Client for TestClient { let outcome = responses .pop() .unwrap_or(RequestPermissionOutcome::Cancelled); - Ok(RequestPermissionResponse { - outcome, - meta: None, - }) + Ok(RequestPermissionResponse::new(outcome)) } async fn write_text_file( @@ -72,10 +69,7 @@ impl Client for TestClient { .get(&arguments.path) .cloned() .unwrap_or_else(|| "default content".to_string()); - Ok(ReadTextFileResponse { - content, - meta: None, - }) + Ok(ReadTextFileResponse::new(content)) } async fn session_notification(&self, args: SessionNotification) -> Result<()> { @@ -161,17 +155,8 @@ impl TestAgent { #[async_trait::async_trait(?Send)] impl Agent for TestAgent { async fn initialize(&self, arguments: InitializeRequest) -> Result { - Ok(InitializeResponse { - protocol_version: arguments.protocol_version, - agent_capabilities: AgentCapabilities::default(), - agent_info: Some(Implementation { - name: "test-agent".into(), - title: Some("Test Agent".into()), - version: "0.0.0".into(), - }), - auth_methods: vec![], - meta: None, - }) + Ok(InitializeResponse::new(arguments.protocol_version) + .agent_info(Implementation::new("test-agent", "0.0.0").title("Test Agent"))) } async fn authenticate(&self, _arguments: AuthenticateRequest) -> Result { @@ -179,31 +164,20 @@ impl Agent for TestAgent { } async fn new_session(&self, _arguments: NewSessionRequest) -> Result { - let session_id = SessionId(Arc::from("test-session-123")); + let session_id = SessionId::new("test-session-123"); self.sessions.lock().unwrap().insert(session_id.clone()); - Ok(NewSessionResponse { - session_id, - modes: None, - #[cfg(feature = "unstable")] - models: None, - meta: None, - }) + Ok(NewSessionResponse::new(session_id)) } async fn load_session(&self, _: LoadSessionRequest) -> Result { - Ok(LoadSessionResponse { - modes: None, - #[cfg(feature = "unstable")] - models: None, - meta: None, - }) + Ok(LoadSessionResponse::new()) } async fn set_session_mode( &self, _arguments: SetSessionModeRequest, ) -> Result { - Ok(SetSessionModeResponse { meta: None }) + Ok(SetSessionModeResponse::new()) } async fn prompt(&self, arguments: PromptRequest) -> Result { @@ -211,10 +185,7 @@ impl Agent for TestAgent { .lock() .unwrap() .push((arguments.session_id, arguments.prompt)); - Ok(PromptResponse { - stop_reason: StopReason::EndTurn, - meta: None, - }) + Ok(PromptResponse::new(StopReason::EndTurn)) } async fn cancel(&self, args: CancelNotification) -> Result<()> { @@ -299,18 +270,12 @@ async fn test_initialize() { let (agent_conn, _client_conn) = create_connection_pair(&client, &agent); - let result = agent_conn - .initialize(InitializeRequest { - protocol_version: VERSION, - client_capabilities: ClientCapabilities::default(), - client_info: Some(Implementation { - name: "test-client".to_string(), - title: Some("Test Client".to_string()), - version: "0.0.0".to_string(), - }), - meta: None, - }) - .await; + let result = + agent_conn + .initialize(InitializeRequest::new(V1).client_info( + Implementation::new("test-client", "0.0.0").title("Test Client"), + )) + .await; assert!(result.is_ok()); let response = result.unwrap(); @@ -330,11 +295,7 @@ async fn test_basic_session_creation() { let (agent_conn, _client_conn) = create_connection_pair(&client, &agent); agent_conn - .new_session(NewSessionRequest { - mcp_servers: vec![], - cwd: std::path::PathBuf::from("/test"), - meta: None, - }) + .new_session(NewSessionRequest::new("/test")) .await .expect("new_session failed"); }) @@ -356,15 +317,9 @@ async fn test_bidirectional_file_operations() { let (_agent_conn, client_conn) = create_connection_pair(&client, &agent); // Test reading a file - let session_id = SessionId(Arc::from("test-session")); + let session_id = SessionId::new("test-session"); let read_result = client_conn - .read_text_file(ReadTextFileRequest { - session_id: session_id.clone(), - path: test_path.clone(), - line: None, - limit: None, - meta: None, - }) + .read_text_file(ReadTextFileRequest::new(session_id.clone(), &test_path)) .await .expect("read_text_file failed"); @@ -372,12 +327,11 @@ async fn test_bidirectional_file_operations() { // Test writing a file let write_result = client_conn - .write_text_file(WriteTextFileRequest { - session_id: session_id.clone(), - path: test_path.clone(), - content: "Updated content".to_string(), - meta: None, - }) + .write_text_file(WriteTextFileRequest::new( + session_id.clone(), + &test_path, + "Updated content", + )) .await; assert!(write_result.is_ok()); @@ -395,37 +349,21 @@ async fn test_session_notifications() { let (_agent_conn, client_conn) = create_connection_pair(&client, &agent); - let session_id = SessionId(Arc::from("test-session")); + let session_id = SessionId::new("test-session"); // Send various session updates client_conn - .session_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::UserMessageChunk(ContentChunk { - content: ContentBlock::Text(TextContent { - annotations: None, - text: "Hello from user".to_string(), - meta: None, - }), - meta: None, - }), - meta: None, - }) + .session_notification(SessionNotification::new( + session_id.clone(), + SessionUpdate::UserMessageChunk(ContentChunk::new("Hello from user".into())), + )) .await .expect("session_notification failed"); client_conn - .session_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::AgentMessageChunk(ContentChunk { - content: ContentBlock::Text(TextContent { - annotations: None, - text: "Hello from agent".to_string(), - meta: None, - }), - meta: None, - }), - meta: None, - }) + .session_notification(SessionNotification::new( + session_id.clone(), + SessionUpdate::AgentMessageChunk(ContentChunk::new("Hello from agent".into())), + )) .await .expect("session_notification failed"); @@ -449,13 +387,10 @@ async fn test_cancel_notification() { let (agent_conn, _client_conn) = create_connection_pair(&client, &agent); - let session_id = SessionId(Arc::from("test-session")); + let session_id = SessionId::new("test-session"); // Send cancel notification agent_conn - .cancel(CancelNotification { - session_id: session_id.clone(), - meta: None, - }) + .cancel(CancelNotification::new(session_id.clone())) .await .expect("cancel failed"); @@ -484,19 +419,14 @@ async fn test_concurrent_operations() { let (_agent_conn, client_conn) = create_connection_pair(&client, &agent); - let session_id = SessionId(Arc::from("test-session")); + let session_id = SessionId::new("test-session"); // Launch multiple concurrent read operations let mut read_futures = vec![]; for i in 0..5 { let path = std::path::PathBuf::from(format!("/test/file{i}.txt")); - let future = client_conn.read_text_file(ReadTextFileRequest { - session_id: session_id.clone(), - path, - line: None, - limit: None, - meta: None, - }); + let future = + client_conn.read_text_file(ReadTextFileRequest::new(session_id.clone(), path)); read_futures.push(future); } @@ -521,180 +451,84 @@ async fn test_full_conversation_flow() { let agent = TestAgent::new(); // Set up permission to approve the tool call - client.add_permission_response(RequestPermissionOutcome::Selected { - option_id: PermissionOptionId(Arc::from("allow-once")), - }); + client.add_permission_response(RequestPermissionOutcome::Selected(SelectedPermissionOutcome::new(PermissionOptionId::new("allow-once")))); let (agent_conn, client_conn) = create_connection_pair(&client, &agent); // 1. Start new session let new_session_result = agent_conn - .new_session(NewSessionRequest { - mcp_servers: vec![], - cwd: std::path::PathBuf::from("/test"), - meta: None, - }) + .new_session(NewSessionRequest::new("/test")) .await .expect("new_session failed"); let session_id = new_session_result.session_id; // 2. Send user message - let user_prompt = vec![ContentBlock::Text(TextContent { - annotations: None, - text: "Please analyze the file and summarize it".to_string(), - meta: None, - })]; + let user_prompt = vec!["Please analyze the file and summarize it".into()]; agent_conn - .prompt(PromptRequest { - session_id: session_id.clone(), - prompt: user_prompt, - meta: None, - }) + .prompt(PromptRequest::new(session_id.clone(), user_prompt)) .await .expect("prompt failed"); // 3. Agent starts responding client_conn - .session_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::AgentMessageChunk(ContentChunk { - content: ContentBlock::Text(TextContent { - annotations: None, - text: "I'll analyze the file for you. ".to_string(), - meta: None, - }), - meta: None, - }), - meta: None, - }) + .session_notification(SessionNotification::new(session_id.clone(), SessionUpdate::AgentMessageChunk(ContentChunk::new("I'll analyze the file for you. ".into())))) .await .expect("session_notification failed"); // 4. Agent creates a tool call - let tool_call_id = ToolCallId(Arc::from("read-file-001")); + let tool_call_id = ToolCallId::new("read-file-001"); client_conn - .session_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::ToolCall(ToolCall { - id: tool_call_id.clone(), - title: "Reading file".to_string(), - kind: ToolKind::Read, - status: ToolCallStatus::Pending, - content: vec![], - locations: vec![ToolCallLocation { - path: std::path::PathBuf::from("/test/data.txt"), - line: None, - meta: None, - }], - raw_input: None, - raw_output: None, - meta: None, - }), - meta: None, - }) + .session_notification(SessionNotification::new(session_id.clone(), SessionUpdate::ToolCall(ToolCall::new(tool_call_id.clone(), "Reading file").kind(ToolKind::Read).locations(vec![ToolCallLocation::new("/test/data.txt")])))) .await .expect("session_notification failed"); // 5. Agent requests permission for the tool call let permission_result = client_conn - .request_permission(RequestPermissionRequest { - session_id: session_id.clone(), - tool_call: ToolCallUpdate { - id: tool_call_id.clone(), - fields: ToolCallUpdateFields { - title: Some("Read /test/data.txt".to_string()), - locations: Some(vec![ToolCallLocation { - path: std::path::PathBuf::from("/test/data.txt"), - line: None, - meta: None, - }]), - ..Default::default() - }, - meta: None, - }, - options: vec![ - PermissionOption { - id: PermissionOptionId(Arc::from("allow-once")), - name: "Allow once".to_string(), - kind: PermissionOptionKind::AllowOnce, - meta: None, - }, - PermissionOption { - id: PermissionOptionId(Arc::from("reject-once")), - name: "Reject".to_string(), - kind: PermissionOptionKind::RejectOnce, - meta: None, - }, + .request_permission(RequestPermissionRequest::new(session_id.clone(), ToolCallUpdate::new(tool_call_id.clone(), ToolCallUpdateFields::new().title("Read /test/data.txt").locations(vec![ToolCallLocation::new("/test/data.txt")])), vec![ + PermissionOption::new(PermissionOptionId::new("allow-once"), "Allow once", PermissionOptionKind::AllowOnce), + PermissionOption::new( + PermissionOptionId::new("reject-once"), + "Reject", + PermissionOptionKind::RejectOnce, + ), ], - meta: None, - }) + )) .await .expect("request_permission failed"); // Verify permission was granted match permission_result.outcome { - RequestPermissionOutcome::Selected { option_id } => { + RequestPermissionOutcome::Selected(SelectedPermissionOutcome { option_id, .. }) => { assert_eq!(option_id.0.as_ref(), "allow-once"); } - RequestPermissionOutcome::Cancelled => panic!("Expected permission to be granted"), + _ => panic!("Expected permission to be granted"), } // 6. Update tool call status client_conn - .session_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::ToolCallUpdate(ToolCallUpdate { - id: tool_call_id.clone(), - fields: ToolCallUpdateFields { - status: Some(ToolCallStatus::InProgress), - ..Default::default() - }, - meta: None, - }), - meta: None, - }) + .session_notification(SessionNotification::new( + session_id.clone(), + SessionUpdate::ToolCallUpdate(ToolCallUpdate::new( + tool_call_id.clone(), + ToolCallUpdateFields::new().status(ToolCallStatus::InProgress), + )))) .await .expect("session_notification failed"); // 7. Tool call completes with content client_conn - .session_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::ToolCallUpdate(ToolCallUpdate { - id: tool_call_id.clone(), - fields: ToolCallUpdateFields { - status: Some(ToolCallStatus::Completed), - content: Some(vec![ToolCallContent::Content { - content: ContentBlock::Text(TextContent { - annotations: None, - text: "File contents: Lorem ipsum dolor sit amet".to_string(), - meta: None, - }), - }]), - ..Default::default() - }, - meta: None, - }), - meta: None, - }) + .session_notification(SessionNotification::new( + session_id.clone(), + SessionUpdate::ToolCallUpdate(ToolCallUpdate::new(tool_call_id.clone(), ToolCallUpdateFields::new().status(ToolCallStatus::Completed).content(vec!["File contents: Lorem ipsum dolor sit amet".into()]))))) .await .expect("session_notification failed"); // 8. Agent sends more text after tool completion client_conn - .session_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::AgentMessageChunk(ContentChunk { - content: ContentBlock::Text(TextContent { - annotations: None, - text: "Based on the file contents, here's my summary: The file contains placeholder text commonly used in the printing industry.".to_string(), - meta: None, - }), - meta: None, - }), - meta: None, - }) + .session_notification(SessionNotification::new( + session_id.clone(), + SessionUpdate::AgentMessageChunk(ContentChunk::new("Based on the file contents, here's my summary: The file contains placeholder text commonly used in the printing industry.".into())))) .await .expect("session_notification failed"); @@ -754,13 +588,10 @@ async fn test_extension_methods_and_notifications() { let agent_ref = agent.clone(); let (client_conn, agent_conn) = create_connection_pair(&client, &agent); - // Test agent calling client extension method + let params: Arc<_> = raw_json!({ "data": "test" }); let client_response = agent_conn - .ext_method(ExtRequest { - method: "example.com/ping".into(), - params: raw_json!({"data": "test"}), - }) + .ext_method(ExtRequest::new("example.com/ping", params)) .await .unwrap(); @@ -773,11 +604,9 @@ async fn test_extension_methods_and_notifications() { ); // Test client calling agent extension method + let params: Arc<_> = raw_json!({ "message": "hello" }); let agent_response = client_conn - .ext_method(ExtRequest { - method: "example.com/echo".into(), - params: raw_json!({"message": "hello"}), - }) + .ext_method(ExtRequest::new("example.com/echo", params)) .await .unwrap(); @@ -789,19 +618,15 @@ async fn test_extension_methods_and_notifications() { ); // Test extension notifications + let params: Arc<_> = raw_json!({ "info": "client notification" }); agent_conn - .ext_notification(ExtNotification { - method: "example.com/client/notify".into(), - params: raw_json!({"info": "client notification"}), - }) + .ext_notification(ExtNotification::new("example.com/client/notify", params)) .await .unwrap(); + let params: Arc<_> = raw_json!({ "info": "agent notification" }); client_conn - .ext_notification(ExtNotification { - method: "example.com/agent/notify".into(), - params: raw_json!({"info": "agent notification"}), - }) + .ext_notification(ExtNotification::new("example.com/agent/notify", params)) .await .unwrap(); diff --git a/src/agent-client-protocol/src/stream_broadcast.rs b/src/agent-client-protocol/src/stream_broadcast.rs index d69356f..521fa8f 100644 --- a/src/agent-client-protocol/src/stream_broadcast.rs +++ b/src/agent-client-protocol/src/stream_broadcast.rs @@ -105,7 +105,7 @@ impl StreamReceiver { self.0 .recv() .await - .map_err(|e| Error::internal_error().with_data(e.to_string())) + .map_err(|e| Error::internal_error().data(e.to_string())) } } @@ -136,6 +136,9 @@ impl StreamSender { result: match result { ResponseResult::Result(value) => Ok(serde_json::to_value(value).ok()), ResponseResult::Error(error) => Err(error.clone()), + _ => { + return; + } }, }, OutgoingMessage::Notification { method, params } => { @@ -144,6 +147,9 @@ impl StreamSender { params: serde_json::to_value(params).ok(), } } + _ => { + return; + } }, }; @@ -272,6 +278,9 @@ impl From> for StreamM result: match result { ResponseResult::Result(value) => Ok(serde_json::to_value(value).ok()), ResponseResult::Error(error) => Err(error), + _ => { + unimplemented!() + } }, }, OutgoingMessage::Notification { method, params } => { @@ -280,6 +289,9 @@ impl From> for StreamM params: serde_json::to_value(params).ok(), } } + _ => { + unimplemented!() + } }, } } diff --git a/src/elizacp/src/lib.rs b/src/elizacp/src/lib.rs index 1721a08..2fb7c5e 100644 --- a/src/elizacp/src/lib.rs +++ b/src/elizacp/src/lib.rs @@ -3,9 +3,9 @@ pub mod eliza; use anyhow::Result; use eliza::Eliza; use sacp::schema::{ - AgentCapabilities, ContentBlock, ContentChunk, InitializeRequest, InitializeResponse, - LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, - PromptResponse, SessionId, SessionNotification, SessionUpdate, StopReason, TextContent, + ContentBlock, ContentChunk, InitializeRequest, InitializeResponse, LoadSessionRequest, + LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, + SessionId, SessionNotification, SessionUpdate, StopReason, TextContent, }; use sacp::{Component, JrHandlerChain}; use std::collections::HashMap; @@ -51,16 +51,10 @@ impl ElizaAgent { tracing::debug!("New session request with cwd: {:?}", request.cwd); // Generate a new session ID - let session_id = SessionId(Arc::from(uuid::Uuid::new_v4().to_string())); + let session_id = SessionId::new(uuid::Uuid::new_v4().to_string()); self.create_session(&session_id); - let response = NewSessionResponse { - session_id, - modes: None, - #[cfg(feature = "unstable")] - models: None, - meta: None, - }; + let response = NewSessionResponse::new(session_id); request_cx.respond(response) } @@ -75,12 +69,7 @@ impl ElizaAgent { // For Eliza, we just create a fresh session self.create_session(&request.session_id); - let response = LoadSessionResponse { - modes: None, - #[cfg(feature = "unstable")] - models: None, - meta: None, - }; + let response = LoadSessionResponse::new(); request_cx.respond(response) } @@ -112,20 +101,13 @@ impl ElizaAgent { request_cx .connection_cx() - .send_notification(SessionNotification { - session_id: session_id.clone(), - update: SessionUpdate::AgentMessageChunk(ContentChunk { - content: response_text.into(), - meta: None, - }), - meta: None, - })?; + .send_notification(SessionNotification::new( + session_id.clone(), + SessionUpdate::AgentMessageChunk(ContentChunk::new(response_text.into())), + ))?; // Complete the request - request_cx.respond(PromptResponse { - stop_reason: StopReason::EndTurn, - meta: None, - }) + request_cx.respond(PromptResponse::new(StopReason::EndTurn)) } } @@ -154,13 +136,7 @@ pub async fn run_elizacp(transport: impl Component + 'static) -> Result<(), sacp async |initialize: InitializeRequest, request_cx| { tracing::debug!("Received initialize request"); - request_cx.respond(InitializeResponse { - protocol_version: initialize.protocol_version, - agent_capabilities: AgentCapabilities::default(), - auth_methods: Vec::default(), - agent_info: Option::default(), - meta: None, - }) + request_cx.respond(InitializeResponse::new(initialize.protocol_version)) } }) .on_receive_request({ diff --git a/src/sacp-conductor/src/conductor.rs b/src/sacp-conductor/src/conductor.rs index 067381b..bc0ec08 100644 --- a/src/sacp-conductor/src/conductor.rs +++ b/src/sacp-conductor/src/conductor.rs @@ -498,7 +498,7 @@ impl ConductorHandlerState { // If message is coming from the conductor's successor, // check whether we have proxy capability and error otherwise. if !self.proxy_mode.load(Ordering::Relaxed) { - return Err(sacp::Error::invalid_request().with_data("cannot accept successor message when not initialized with proxy capability")); + return Err(sacp::Error::invalid_request().data("cannot accept successor message when not initialized with proxy capability")); } // Message from conductor's successor goes to the last component (the conductor's successor's predecessor) diff --git a/src/sacp-conductor/src/conductor/mcp_bridge.rs b/src/sacp-conductor/src/conductor/mcp_bridge.rs index d5a3a2a..6908d88 100644 --- a/src/sacp-conductor/src/conductor/mcp_bridge.rs +++ b/src/sacp-conductor/src/conductor/mcp_bridge.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, net::SocketAddr}; use futures::{SinkExt, StreamExt as _, channel::mpsc}; use sacp; -use sacp::schema::McpServer; +use sacp::schema::{McpServer, McpServerHttp, McpServerStdio}; use sacp::{JrConnectionCx, JrHandlerChain, MessageAndCx}; use sacp_proxy::McpDisconnectNotification; use tokio::net::TcpStream; @@ -53,7 +53,10 @@ impl McpBridgeListeners { ) -> Result<(), sacp::Error> { use sacp::schema::McpServer; - let McpServer::Http { name, url, headers } = mcp_server else { + let McpServer::Http(McpServerHttp { + name, url, headers, .. + }) = mcp_server + else { return Ok(()); }; @@ -94,12 +97,7 @@ impl McpBridgeListeners { args.push("mcp".to_string()); args.push(tcp_port.tcp_port.to_string()); - let transformed = McpServer::Stdio { - name: name.clone(), - command, - args, - env: vec![], - }; + let transformed = McpServer::Stdio(McpServerStdio::new(name.clone(), command).args(args)); *mcp_server = transformed; Ok(()) diff --git a/src/sacp-conductor/tests/initialization_sequence.rs b/src/sacp-conductor/tests/initialization_sequence.rs index eb214d3..a27ef02 100644 --- a/src/sacp-conductor/tests/initialization_sequence.rs +++ b/src/sacp-conductor/tests/initialization_sequence.rs @@ -6,8 +6,7 @@ //! 3. Proxy components must accept the capability or initialization fails //! 4. Last component (agent) never receives proxy capability offer -use agent_client_protocol_schema::{ClientCapabilities, ProtocolVersion}; -use sacp::schema::{AgentCapabilities, InitializeRequest, InitializeResponse}; +use sacp::schema::{InitializeRequest, InitializeResponse, VERSION}; use sacp::{Component, JrHandlerChain, MetaCapabilityExt, Proxy}; use sacp_conductor::conductor::Conductor; use sacp_proxy::JrCxExt; @@ -97,13 +96,7 @@ impl Component for InitComponent { request_cx.respond(response) }) } else { - let response = InitializeResponse { - protocol_version: request.protocol_version, - agent_capabilities: AgentCapabilities::default(), - auth_methods: vec![], - meta: None, - agent_info: None, - }; + let response = InitializeResponse::new(request.protocol_version); request_cx.respond(response) } @@ -144,13 +137,7 @@ async fn test_single_component_no_proxy_offer() -> Result<(), sacp::Error> { let component1 = InitConfig::new(false); run_test_with_components(vec![InitComponent::new(&component1)], async |editor_cx| { - let init_response = recv(editor_cx.send_request(InitializeRequest { - protocol_version: ProtocolVersion::default(), - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - })) - .await; + let init_response = recv(editor_cx.send_request(InitializeRequest::new(VERSION))).await; assert!( init_response.is_ok(), @@ -178,13 +165,7 @@ async fn test_two_components() -> Result<(), sacp::Error> { InitComponent::new(&component2), ], async |editor_cx| { - let init_response = recv(editor_cx.send_request(InitializeRequest { - protocol_version: ProtocolVersion::default(), - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - })) - .await; + let init_response = recv(editor_cx.send_request(InitializeRequest::new(VERSION))).await; assert!( init_response.is_ok(), @@ -214,13 +195,7 @@ async fn test_proxy_component_must_respond_with_proxy() -> Result<(), sacp::Erro InitComponent::new(&component2), ], async |editor_cx| { - let init_response = recv(editor_cx.send_request(InitializeRequest { - protocol_version: ProtocolVersion::default(), - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - })) - .await; + let init_response = recv(editor_cx.send_request(InitializeRequest::new(VERSION))).await; // Should fail because component1 was offered proxy but didn't respond with it assert!( @@ -259,13 +234,7 @@ async fn test_proxy_component_must_strip_proxy_when_forwarding() -> Result<(), s InitComponent::new(&component2), ], async |editor_cx| { - let init_response = recv(editor_cx.send_request(InitializeRequest { - protocol_version: ProtocolVersion::default(), - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - })) - .await; + let init_response = recv(editor_cx.send_request(InitializeRequest::new(VERSION))).await; // Should fail because component1 forwarded request with proxy capability still attached assert!( diff --git a/src/sacp-conductor/tests/mcp-integration.rs b/src/sacp-conductor/tests/mcp-integration.rs index f5bb9e4..368ac9a 100644 --- a/src/sacp-conductor/tests/mcp-integration.rs +++ b/src/sacp-conductor/tests/mcp-integration.rs @@ -9,13 +9,11 @@ mod mcp_integration; use std::path::PathBuf; -use agent_client_protocol_schema::{ClientCapabilities, ProtocolVersion}; use expect_test::expect; use futures::{SinkExt, StreamExt, channel::mpsc}; use sacp::JrHandlerChain; use sacp::schema::{ - ContentBlock, InitializeRequest, NewSessionRequest, PromptRequest, SessionNotification, - TextContent, + InitializeRequest, NewSessionRequest, PromptRequest, SessionNotification, VERSION, }; use sacp_conductor::conductor::Conductor; @@ -80,13 +78,7 @@ async fn test_proxy_provides_mcp_tools() -> Result<(), sacp::Error> { ], async |editor_cx| { // Send initialization request - let init_response = recv(editor_cx.send_request(InitializeRequest { - protocol_version: ProtocolVersion::default(), - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - })) - .await; + let init_response = recv(editor_cx.send_request(InitializeRequest::new(VERSION))).await; assert!( init_response.is_ok(), @@ -94,12 +86,8 @@ async fn test_proxy_provides_mcp_tools() -> Result<(), sacp::Error> { ); // Send session/new request - let session_response = recv(editor_cx.send_request(NewSessionRequest { - cwd: PathBuf::default(), - mcp_servers: vec![], - meta: None, - })) - .await; + let session_response = + recv(editor_cx.send_request(NewSessionRequest::new(PathBuf::default()))).await; assert!( session_response.is_ok(), @@ -158,34 +146,19 @@ async fn test_agent_handles_prompt() -> Result<(), sacp::Error> { }) .with_client(transport, async |editor_cx| { // Initialize - recv(editor_cx.send_request(InitializeRequest { - protocol_version: ProtocolVersion::default(), - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - })) - .await?; + recv(editor_cx.send_request(InitializeRequest::new(VERSION))).await?; // Create session - let session = recv(editor_cx.send_request(NewSessionRequest { - cwd: PathBuf::default(), - mcp_servers: vec![], - meta: None, - })) - .await?; + let session = + recv(editor_cx.send_request(NewSessionRequest::new(PathBuf::default()))).await?; tracing::debug!(session_id = %session.session_id.0, "Session created"); // Send a prompt - let prompt_response = recv(editor_cx.send_request(PromptRequest { - session_id: session.session_id.clone(), - prompt: vec![ContentBlock::Text(TextContent { - annotations: None, - text: "Hello agent!".to_string(), - meta: None, - })], - meta: None, - })) + let prompt_response = recv(editor_cx.send_request(PromptRequest::new( + session.session_id.clone(), + vec!["Hello agent!".into()], + ))) .await?; // Log the response diff --git a/src/sacp-conductor/tests/mcp_integration/agent.rs b/src/sacp-conductor/tests/mcp_integration/agent.rs index 19b3b2d..6e78f52 100644 --- a/src/sacp-conductor/tests/mcp_integration/agent.rs +++ b/src/sacp-conductor/tests/mcp_integration/agent.rs @@ -2,9 +2,9 @@ use rmcp::ServiceExt; use sacp::schema::{ - AgentCapabilities, ContentBlock, ContentChunk, InitializeRequest, InitializeResponse, - McpServer, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, - SessionNotification, SessionUpdate, StopReason, TextContent, + ContentChunk, InitializeRequest, InitializeResponse, McpServer, McpServerStdio, + NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, SessionNotification, + SessionUpdate, StopReason, }; use sacp::{Component, JrHandlerChain, JrRequestCx}; use std::sync::Arc; @@ -31,13 +31,7 @@ impl Component for AgentComponent { .name("agent-component") .on_receive_request(async move |request: InitializeRequest, request_cx| { // Simple initialization response - let response = InitializeResponse { - protocol_version: request.protocol_version, - agent_capabilities: AgentCapabilities::default(), - auth_methods: vec![], - meta: None, - agent_info: None, - }; + let response = InitializeResponse::new(request.protocol_version); request_cx.respond(response) }) .on_receive_request({ @@ -54,12 +48,12 @@ impl Component for AgentComponent { ); // Verify the stdio configuration is correct - if let McpServer::Stdio { + if let McpServer::Stdio(McpServerStdio { name, command, args, .. - } = mcp_server + }) = mcp_server { assert_eq!(name, "test"); let conductor_command = conductor_command(); @@ -73,13 +67,7 @@ impl Component for AgentComponent { *state.mcp_servers.lock().await = request.mcp_servers; // Simple session response - let response = NewSessionResponse { - session_id: "test-session-123".into(), - modes: None, - #[cfg(feature = "unstable")] - models: None, - meta: None, - }; + let response = NewSessionResponse::new("test-session-123".into()); request_cx.respond(response) } }) @@ -117,25 +105,19 @@ impl AgentComponent { let connection_cx = request_cx.connection_cx(); // Send initial message - connection_cx.send_notification(SessionNotification { - session_id: request.session_id.clone(), - update: SessionUpdate::AgentMessageChunk(ContentChunk { - content: ContentBlock::Text(TextContent { - annotations: None, - text: "Hello. I will now use the MCP tool".to_string(), - meta: None, - }), - meta: None, - }), - meta: None, - })?; + connection_cx.send_notification(SessionNotification::new( + request.session_id.clone(), + SessionUpdate::AgentMessageChunk(ContentChunk::new( + "Hello. I will now use the MCP tool".into(), + )), + ))?; // Get MCP servers let mcp_servers = state.mcp_servers.lock().await; if let Some(mcp_server) = mcp_servers.first() - && let McpServer::Stdio { + && let McpServer::Stdio(McpServerStdio { command, args, env, .. - } = mcp_server + }) = mcp_server { tracing::debug!( command = ?command, @@ -175,18 +157,12 @@ impl AgentComponent { tracing::debug!("Tool call result: {:?}", tool_result); // Send the tool result as a message - connection_cx.send_notification(SessionNotification { - session_id: request.session_id.clone(), - update: SessionUpdate::AgentMessageChunk(ContentChunk { - content: ContentBlock::Text(TextContent { - annotations: None, - text: format!("MCP tool result: {tool_result:?}"), - meta: None, - }), - meta: None, - }), - meta: None, - })?; + connection_cx.send_notification(SessionNotification::new( + request.session_id.clone(), + SessionUpdate::AgentMessageChunk(ContentChunk::new( + format!("MCP tool result: {tool_result:?}").into(), + )), + ))?; // Clean up the client mcp_client @@ -195,10 +171,7 @@ impl AgentComponent { .map_err(sacp::Error::into_internal_error)?; } - let response = PromptResponse { - stop_reason: StopReason::EndTurn, - meta: None, - }; + let response = PromptResponse::new(StopReason::EndTurn); request_cx.respond(response) } diff --git a/src/sacp-proxy/src/mcp_server.rs b/src/sacp-proxy/src/mcp_server.rs index 8b83dd8..d548b5e 100644 --- a/src/sacp-proxy/src/mcp_server.rs +++ b/src/sacp-proxy/src/mcp_server.rs @@ -2,7 +2,7 @@ use futures::channel::mpsc; use futures::{SinkExt, StreamExt}; use fxhash::FxHashMap; -use sacp::schema::NewSessionRequest; +use sacp::schema::{McpServerHttp, NewSessionRequest}; use sacp::{ Channel, Component, DynComponent, Handled, JrConnectionCx, JrHandlerChain, JrMessage, JrMessageHandler, JrRequestCx, MessageAndCx, UntypedMessage, @@ -439,11 +439,7 @@ struct RegisteredMcpServer { impl RegisteredMcpServer { fn acp_mcp_server(&self) -> sacp::schema::McpServer { - sacp::schema::McpServer::Http { - name: self.name.clone(), - url: self.url.clone(), - headers: vec![], - } + sacp::schema::McpServer::Http(McpServerHttp::new(&self.name, &self.url)) } } diff --git a/src/sacp-proxy/src/to_from_successor.rs b/src/sacp-proxy/src/to_from_successor.rs index d1acc77..c36497a 100644 --- a/src/sacp-proxy/src/to_from_successor.rs +++ b/src/sacp-proxy/src/to_from_successor.rs @@ -591,8 +591,7 @@ impl ProxyHandler { if !request.has_meta_capability(Proxy) { request_cx.respond_with_error( - sacp::Error::invalid_params() - .with_data("this command requires the proxy capability"), + sacp::Error::invalid_params().data("this command requires the proxy capability"), )?; return Ok(()); } diff --git a/src/sacp-test/src/test_client.rs b/src/sacp-test/src/test_client.rs index 1af3404..0a4d3a2 100644 --- a/src/sacp-test/src/test_client.rs +++ b/src/sacp-test/src/test_client.rs @@ -6,9 +6,8 @@ use sacp::{ JrHandlerChain, schema::{ - ClientCapabilities, ContentBlock, InitializeRequest, InitializeResponse, NewSessionRequest, - NewSessionResponse, PromptRequest, PromptResponse, ProtocolVersion, SessionNotification, - SessionUpdate, TextContent, + ContentBlock, InitializeRequest, InitializeResponse, NewSessionRequest, NewSessionResponse, + PromptRequest, PromptResponse, SessionNotification, SessionUpdate, V1, }, }; @@ -55,39 +54,21 @@ where .with_client(async move |cx| { // Initialize let InitializeResponse { .. } = cx - .send_request(InitializeRequest { - protocol_version: ProtocolVersion::default(), - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - }) + .send_request(InitializeRequest::new(V1)) .block_task() .await?; // Create session let NewSessionResponse { session_id, .. } = cx - .send_request(NewSessionRequest { - meta: None, - mcp_servers: vec![], - cwd: std::env::current_dir().unwrap_or_default(), - }) + .send_request(NewSessionRequest::new( + std::env::current_dir().unwrap_or_default(), + )) .block_task() .await?; // Send prompt - let PromptResponse { - stop_reason, - meta: _, - } = cx - .send_request(PromptRequest { - session_id, - prompt: vec![ContentBlock::Text(TextContent { - text: prompt.to_string(), - annotations: None, - meta: None, - })], - meta: None, - }) + let PromptResponse { stop_reason, .. } = cx + .send_request(PromptRequest::new(session_id, vec![prompt.into()])) .block_task() .await?; diff --git a/src/sacp-tokio/src/acp_agent.rs b/src/sacp-tokio/src/acp_agent.rs index 85f9d18..718e589 100644 --- a/src/sacp-tokio/src/acp_agent.rs +++ b/src/sacp-tokio/src/acp_agent.rs @@ -106,12 +106,12 @@ impl AcpAgent { sacp::Error, > { match &self.server { - sacp::schema::McpServer::Stdio { + sacp::schema::McpServer::Stdio(sacp::schema::McpServerStdio { command, args, env, - name: _, - } => { + .. + }) => { let mut cmd = tokio::process::Command::new(command); cmd.args(args); for env_var in env { @@ -139,6 +139,7 @@ impl AcpAgent { sacp::schema::McpServer::Sse { .. } => Err(sacp::util::internal_error( "SSE transport not yet supported by AcpAgent", )), + _ => Err(sacp::util::internal_error("Unsupported MCP server type")), } } } @@ -216,12 +217,9 @@ fn parse_command_string(s: &str) -> Result { .to_string(); Ok(AcpAgent { - server: sacp::schema::McpServer::Stdio { - name, - command, - args, - env: vec![], - }, + server: sacp::schema::McpServer::Stdio( + sacp::schema::McpServerStdio::new(name, command).args(args), + ), }) } @@ -233,12 +231,13 @@ mod tests { fn test_parse_simple_command() { let agent = AcpAgent::from_str("python agent.py").unwrap(); match agent.server { - sacp::schema::McpServer::Stdio { + sacp::schema::McpServer::Stdio(sacp::schema::McpServerStdio { name, command, args, env, - } => { + .. + }) => { assert_eq!(name, "python"); assert_eq!(command, PathBuf::from("python")); assert_eq!(args, vec!["agent.py"]); @@ -252,12 +251,13 @@ mod tests { fn test_parse_command_with_args() { let agent = AcpAgent::from_str("node server.js --port 8080 --verbose").unwrap(); match agent.server { - sacp::schema::McpServer::Stdio { + sacp::schema::McpServer::Stdio(sacp::schema::McpServerStdio { name, command, args, env, - } => { + .. + }) => { assert_eq!(name, "node"); assert_eq!(command, PathBuf::from("node")); assert_eq!(args, vec!["server.js", "--port", "8080", "--verbose"]); @@ -271,12 +271,13 @@ mod tests { fn test_parse_command_with_quotes() { let agent = AcpAgent::from_str(r#"python "my agent.py" --name "Test Agent""#).unwrap(); match agent.server { - sacp::schema::McpServer::Stdio { + sacp::schema::McpServer::Stdio(sacp::schema::McpServerStdio { name, command, args, env, - } => { + .. + }) => { assert_eq!(name, "python"); assert_eq!(command, PathBuf::from("python")); assert_eq!(args, vec!["my agent.py", "--name", "Test Agent"]); @@ -297,12 +298,13 @@ mod tests { }"#; let agent = AcpAgent::from_str(json).unwrap(); match agent.server { - sacp::schema::McpServer::Stdio { + sacp::schema::McpServer::Stdio(sacp::schema::McpServerStdio { name, command, args, env, - } => { + .. + }) => { assert_eq!(name, "my-agent"); assert_eq!(command, PathBuf::from("/usr/bin/python")); assert_eq!(args, vec!["agent.py", "--verbose"]); @@ -322,7 +324,12 @@ mod tests { }"#; let agent = AcpAgent::from_str(json).unwrap(); match agent.server { - sacp::schema::McpServer::Http { name, url, headers } => { + sacp::schema::McpServer::Http(sacp::schema::McpServerHttp { + name, + url, + headers, + .. + }) => { assert_eq!(name, "remote-agent"); assert_eq!(url, "https://example.com/agent"); assert_eq!(headers, vec![]); diff --git a/src/sacp/examples/simple_agent.rs b/src/sacp/examples/simple_agent.rs index dcfc106..2fcc756 100644 --- a/src/sacp/examples/simple_agent.rs +++ b/src/sacp/examples/simple_agent.rs @@ -1,4 +1,4 @@ -use sacp::schema::{AgentCapabilities, InitializeRequest, InitializeResponse}; +use sacp::schema::{InitializeRequest, InitializeResponse}; use sacp::{JrHandlerChain, MessageAndCx, UntypedMessage}; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; @@ -8,13 +8,7 @@ async fn main() -> Result<(), sacp::Error> { .name("my-agent") // for debugging .on_receive_request(async move |initialize: InitializeRequest, request_cx| { // Respond to initialize successfully - request_cx.respond(InitializeResponse { - protocol_version: initialize.protocol_version, - agent_capabilities: AgentCapabilities::default(), - auth_methods: vec![], - agent_info: None, - meta: None, - }) + request_cx.respond(InitializeResponse::new(initialize.protocol_version)) }) .on_receive_message( async move |message: MessageAndCx| { diff --git a/src/sacp/examples/yolo_one_shot_client.rs b/src/sacp/examples/yolo_one_shot_client.rs index 5a385b7..58fead7 100644 --- a/src/sacp/examples/yolo_one_shot_client.rs +++ b/src/sacp/examples/yolo_one_shot_client.rs @@ -11,13 +11,12 @@ //! cargo run --example yolo_one_shot_client -- --command "python my_agent.py" "What is 2+2?" //! ``` -use agent_client_protocol_schema::ClientCapabilities; use clap::Parser; use sacp::JrHandlerChain; use sacp::schema::{ - ContentBlock, InitializeRequest, NewSessionRequest, PromptRequest, RequestPermissionOutcome, - RequestPermissionRequest, RequestPermissionResponse, SessionNotification, TextContent, - VERSION as PROTOCOL_VERSION, + InitializeRequest, NewSessionRequest, PromptRequest, RequestPermissionOutcome, + RequestPermissionRequest, RequestPermissionResponse, SelectedPermissionOutcome, + SessionNotification, VERSION as PROTOCOL_VERSION, }; use std::path::PathBuf; use tokio::process::Child; @@ -96,30 +95,23 @@ async fn main() -> Result<(), Box> { .on_receive_request(async move |request: RequestPermissionRequest, request_cx| { // YOLO: Auto-approve all permission requests by selecting the first option eprintln!("✅ Auto-approving permission request: {request:?}"); - let option_id = request.options.first().map(|opt| opt.id.clone()); - if let Some(id) = option_id { - request_cx.respond(RequestPermissionResponse { - outcome: RequestPermissionOutcome::Selected { option_id: id }, - meta: None, - }) + let option_id = request.options.first().map(|opt| opt.option_id.clone()); + if let Some(option_id) = option_id { + request_cx.respond(RequestPermissionResponse::new( + RequestPermissionOutcome::Selected(SelectedPermissionOutcome::new(option_id)), + )) } else { eprintln!("⚠️ No options provided in permission request, cancelling"); - request_cx.respond(RequestPermissionResponse { - outcome: RequestPermissionOutcome::Cancelled, - meta: None, - }) + request_cx.respond(RequestPermissionResponse::new( + RequestPermissionOutcome::Cancelled, + )) } }) .with_client(transport, |cx: sacp::JrConnectionCx| async move { // Initialize the agent eprintln!("🤝 Initializing agent..."); let init_response = cx - .send_request(InitializeRequest { - protocol_version: PROTOCOL_VERSION, - client_capabilities: ClientCapabilities::default(), - client_info: None, - meta: None, - }) + .send_request(InitializeRequest::new(PROTOCOL_VERSION)) .block_task() .await?; @@ -128,11 +120,9 @@ async fn main() -> Result<(), Box> { // Create a new session eprintln!("📝 Creating new session..."); let new_session_response = cx - .send_request(NewSessionRequest { - mcp_servers: vec![], - cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), - meta: None, - }) + .send_request(NewSessionRequest::new( + std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), + )) .block_task() .await?; @@ -142,15 +132,10 @@ async fn main() -> Result<(), Box> { // Send the prompt eprintln!("💬 Sending prompt: \"{}\"", cli.prompt); let prompt_response = cx - .send_request(PromptRequest { - session_id: session_id.clone(), - prompt: vec![ContentBlock::Text(TextContent { - text: cli.prompt.clone(), - annotations: None, - meta: None, - })], - meta: None, - }) + .send_request(PromptRequest::new( + session_id.clone(), + vec![cli.prompt.into()], + )) .block_task() .await?; diff --git a/src/sacp/src/capabilities.rs b/src/sacp/src/capabilities.rs index d703206..14d7153 100644 --- a/src/sacp/src/capabilities.rs +++ b/src/sacp/src/capabilities.rs @@ -152,17 +152,12 @@ impl MetaCapabilityExt for InitializeResponse { #[cfg(test)] mod tests { use super::*; - use crate::schema::{AgentCapabilities, ClientCapabilities, VERSION}; + use crate::schema::{ClientCapabilities, VERSION}; use serde_json::json; #[test] fn test_add_proxy_capability() { - let request = InitializeRequest { - protocol_version: VERSION, - client_capabilities: ClientCapabilities::default(), - meta: None, - client_info: None, - }; + let request = InitializeRequest::new(VERSION); let request = request.add_meta_capability(Proxy); @@ -175,21 +170,13 @@ mod tests { #[test] fn test_remove_proxy_capability() { - let client_capabilities = ClientCapabilities { - meta: Some(json!({ - "symposium": { - "proxy": true - } - })), - ..Default::default() - }; - - let request = InitializeRequest { - protocol_version: VERSION, - client_capabilities, - meta: None, - client_info: None, - }; + let client_capabilities = ClientCapabilities::new().meta(json!({ + "symposium": { + "proxy": true + } + })); + + let request = InitializeRequest::new(VERSION).client_capabilities(client_capabilities); let request = request.remove_meta_capability(Proxy); @@ -198,21 +185,13 @@ mod tests { #[test] fn test_has_proxy_capability() { - let client_capabilities = ClientCapabilities { - meta: Some(json!({ - "symposium": { - "proxy": true - } - })), - ..Default::default() - }; - - let request = InitializeRequest { - protocol_version: VERSION, - client_capabilities, - meta: None, - client_info: None, - }; + let client_capabilities = ClientCapabilities::new().meta(json!({ + "symposium": { + "proxy": true + } + })); + + let request = InitializeRequest::new(VERSION).client_capabilities(client_capabilities); assert!(request.has_meta_capability(Proxy)); assert!(!request.has_meta_capability(McpAcpTransport)); @@ -220,13 +199,7 @@ mod tests { #[test] fn test_response_capabilities() { - let response = InitializeResponse { - protocol_version: VERSION, - agent_capabilities: AgentCapabilities::default(), - auth_methods: vec![], - meta: None, - agent_info: None, - }; + let response = InitializeResponse::new(VERSION); let response = response.add_meta_capability(McpAcpTransport); diff --git a/src/sacp/src/jsonrpc.rs b/src/sacp/src/jsonrpc.rs index 33ca3ad..5ee5ef2 100644 --- a/src/sacp/src/jsonrpc.rs +++ b/src/sacp/src/jsonrpc.rs @@ -2087,7 +2087,7 @@ impl JrResponse { const COMMUNICATION_FAILURE: i32 = -32000; fn communication_failure(err: impl ToString) -> crate::Error { - crate::Error::new((COMMUNICATION_FAILURE, err.to_string())) + crate::Error::new(COMMUNICATION_FAILURE, err.to_string()) } // ============================================================================ diff --git a/src/sacp/src/jsonrpc/actors.rs b/src/sacp/src/jsonrpc/actors.rs index 84df9c3..98b6302 100644 --- a/src/sacp/src/jsonrpc/actors.rs +++ b/src/sacp/src/jsonrpc/actors.rs @@ -254,11 +254,8 @@ pub(super) async fn incoming_protocol_actor( .map_err(crate::Error::into_internal_error)?; } else if let Some(error) = response.error { // Convert jsonrpcmsg::Error to crate::Error - let acp_error = crate::Error { - code: error.code, - message: error.message, - data: error.data, - }; + let acp_error = + crate::Error::new(error.code, error.message).data(error.data); reply_tx .unbounded_send(ReplyMessage::Dispatch(id, Err(acp_error))) .map_err(crate::Error::into_internal_error)?; diff --git a/src/sacp/src/jsonrpc/task_actor.rs b/src/sacp/src/jsonrpc/task_actor.rs index 5347e44..18bbc57 100644 --- a/src/sacp/src/jsonrpc/task_actor.rs +++ b/src/sacp/src/jsonrpc/task_actor.rs @@ -23,7 +23,7 @@ impl Task { Ok(()) => Ok(()), Err(err) => { let data = err.data.clone(); - Err(err.with_data(serde_json::json! { + Err(err.data(serde_json::json! { { "spawned_at": format!("{}:{}:{}", location.file(), location.line(), location.column()), "data": data, diff --git a/src/sacp/src/schema/enum_impls.rs b/src/sacp/src/schema/enum_impls.rs index 7c1aa98..88daa26 100644 --- a/src/sacp/src/schema/enum_impls.rs +++ b/src/sacp/src/schema/enum_impls.rs @@ -34,6 +34,7 @@ impl JrMessage for ClientRequest { ClientRequest::ExtMethodRequest(ext) => &ext.method, #[cfg(feature = "unstable")] ClientRequest::SetSessionModelRequest(_) => "session/set_model", + _ => todo!("implement this method on ClientRequest"), } } @@ -49,10 +50,10 @@ impl JrMessage for ClientRequest { // Check for extension methods (prefixed with underscore) if let Some(custom_method) = method.strip_prefix('_') { json_cast(params).map(|ext_req: crate::schema::ExtRequest| { - ClientRequest::ExtMethodRequest(crate::schema::ExtRequest { - method: custom_method.to_string().into(), - params: ext_req.params, - }) + ClientRequest::ExtMethodRequest(crate::schema::ExtRequest::new( + custom_method, + ext_req.params, + )) }) } else { return None; @@ -86,6 +87,7 @@ impl JrMessage for ClientNotification { match self { ClientNotification::CancelNotification(_) => "session/cancel", ClientNotification::ExtNotification(ext) => &ext.method, + _ => todo!("implement this method on ClientNotification"), } } @@ -107,10 +109,10 @@ impl JrMessage for ClientNotification { // Check for extension notifications (prefixed with underscore) if let Some(custom_method) = method.strip_prefix('_') { json_cast(params).map(|ext_notif: crate::schema::ExtNotification| { - ClientNotification::ExtNotification(crate::schema::ExtNotification { - method: custom_method.to_string().into(), - params: ext_notif.params, - }) + ClientNotification::ExtNotification(crate::schema::ExtNotification::new( + custom_method, + ext_notif.params, + )) }) } else { return None; @@ -145,6 +147,7 @@ impl JrMessage for AgentRequest { AgentRequest::WaitForTerminalExitRequest(_) => "terminal/wait_for_exit", AgentRequest::KillTerminalCommandRequest(_) => "terminal/kill", AgentRequest::ExtMethodRequest(ext) => &ext.method, + _ => todo!("implement this method on AgentRequest"), } } @@ -166,10 +169,10 @@ impl JrMessage for AgentRequest { // Check for extension methods (prefixed with underscore) if let Some(custom_method) = method.strip_prefix('_') { json_cast(params).map(|ext_req: crate::schema::ExtRequest| { - AgentRequest::ExtMethodRequest(crate::schema::ExtRequest { - method: custom_method.to_string().into(), - params: ext_req.params, - }) + AgentRequest::ExtMethodRequest(crate::schema::ExtRequest::new( + custom_method, + ext_req.params, + )) }) } else { return None; @@ -203,6 +206,7 @@ impl JrMessage for AgentNotification { match self { AgentNotification::SessionNotification(_) => "session/update", AgentNotification::ExtNotification(ext) => &ext.method, + _ => todo!("implement this method on AgentNotification"), } } @@ -224,10 +228,10 @@ impl JrMessage for AgentNotification { // Check for extension notifications (prefixed with underscore) if let Some(custom_method) = method.strip_prefix('_') { json_cast(params).map(|ext_notif: crate::schema::ExtNotification| { - AgentNotification::ExtNotification(crate::schema::ExtNotification { - method: custom_method.to_string().into(), - params: ext_notif.params, - }) + AgentNotification::ExtNotification(crate::schema::ExtNotification::new( + custom_method, + ext_notif.params, + )) }) } else { return None; diff --git a/src/sacp/src/util.rs b/src/sacp/src/util.rs index d9f95ec..1667018 100644 --- a/src/sacp/src/util.rs +++ b/src/sacp/src/util.rs @@ -16,12 +16,12 @@ where /// Creates an internal error with the given message pub fn internal_error(message: impl ToString) -> crate::Error { - crate::Error::internal_error().with_data(message.to_string()) + crate::Error::internal_error().data(message.to_string()) } /// Creates a parse error with the given message pub fn parse_error(message: impl ToString) -> crate::Error { - crate::Error::parse_error().with_data(message.to_string()) + crate::Error::parse_error().data(message.to_string()) } pub(crate) fn instrumented_with_connection_name( diff --git a/src/sacp/tests/jsonrpc_error_handling.rs b/src/sacp/tests/jsonrpc_error_handling.rs index 934cea5..e818dbf 100644 --- a/src/sacp/tests/jsonrpc_error_handling.rs +++ b/src/sacp/tests/jsonrpc_error_handling.rs @@ -283,10 +283,10 @@ async fn test_handler_returns_error() { let server = JrHandlerChain::new().on_receive_request( async |_request: ErrorRequest, request_cx: JrRequestCx| { // Explicitly return an error - request_cx.respond_with_error(sacp::Error::new(( + request_cx.respond_with_error(sacp::Error::new( -32000, - "This is an intentional error".to_string(), - ))) + "This is an intentional error", + )) }, ); diff --git a/src/yopo/src/main.rs b/src/yopo/src/main.rs index 640611f..f3319e5 100644 --- a/src/yopo/src/main.rs +++ b/src/yopo/src/main.rs @@ -22,9 +22,9 @@ use sacp::JrHandlerChain; use sacp::schema::{ - ClientCapabilities, ContentBlock, InitializeRequest, NewSessionRequest, PromptRequest, - RequestPermissionOutcome, RequestPermissionRequest, RequestPermissionResponse, - SessionNotification, TextContent, VERSION as PROTOCOL_VERSION, + ContentBlock, InitializeRequest, NewSessionRequest, PromptRequest, RequestPermissionOutcome, + RequestPermissionRequest, RequestPermissionResponse, SelectedPermissionOutcome, + SessionNotification, TextContent, V1, }; use sacp_tokio::AcpAgent; use std::path::PathBuf; @@ -61,45 +61,27 @@ async fn main() -> Result<(), Box> { JrHandlerChain::new() .on_receive_notification(async move |notification: SessionNotification, _cx| { // Print session updates to stdout (so 2>/dev/null shows only agent output) - match notification.update { - sacp::schema::SessionUpdate::AgentMessageChunk(content_chunk) => { - match content_chunk.content { - ContentBlock::Text(TextContent { - annotations: _, - text, - meta: _, - }) => print!("{text}"), - ContentBlock::Image(_) - | ContentBlock::Audio(_) - | ContentBlock::ResourceLink(_) - | ContentBlock::Resource(_) => {} - } - } - sacp::schema::SessionUpdate::UserMessageChunk(_) - | sacp::schema::SessionUpdate::AgentThoughtChunk(_) - | sacp::schema::SessionUpdate::ToolCall(_) - | sacp::schema::SessionUpdate::ToolCallUpdate(_) - | sacp::schema::SessionUpdate::Plan(_) - | sacp::schema::SessionUpdate::AvailableCommandsUpdate(_) - | sacp::schema::SessionUpdate::CurrentModeUpdate(_) => {} + if let sacp::schema::SessionUpdate::AgentMessageChunk(content_chunk) = + notification.update + && let ContentBlock::Text(TextContent { text, .. }) = content_chunk.content + { + print!("{text}"); } Ok(()) }) .on_receive_request(async move |request: RequestPermissionRequest, request_cx| { // YOPO: Auto-approve all permission requests by selecting the first option eprintln!("✅ Auto-approving permission request: {request:?}"); - let option_id = request.options.first().map(|opt| opt.id.clone()); + let option_id = request.options.first().map(|opt| opt.option_id.clone()); if let Some(id) = option_id { - request_cx.respond(RequestPermissionResponse { - outcome: RequestPermissionOutcome::Selected { option_id: id }, - meta: None, - }) + request_cx.respond(RequestPermissionResponse::new( + RequestPermissionOutcome::Selected(SelectedPermissionOutcome::new(id)), + )) } else { eprintln!("⚠️ No options provided in permission request, cancelling"); - request_cx.respond(RequestPermissionResponse { - outcome: RequestPermissionOutcome::Cancelled, - meta: None, - }) + request_cx.respond(RequestPermissionResponse::new( + RequestPermissionOutcome::Cancelled, + )) } }) .connect_to(agent)? @@ -107,12 +89,7 @@ async fn main() -> Result<(), Box> { // Initialize the agent eprintln!("🤝 Initializing agent..."); let init_response = cx - .send_request(InitializeRequest { - protocol_version: PROTOCOL_VERSION, - client_capabilities: ClientCapabilities::default(), - client_info: None, - meta: None, - }) + .send_request(InitializeRequest::new(V1)) .block_task() .await?; @@ -121,11 +98,9 @@ async fn main() -> Result<(), Box> { // Create a new session eprintln!("📝 Creating new session..."); let new_session_response = cx - .send_request(NewSessionRequest { - mcp_servers: vec![], - cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), - meta: None, - }) + .send_request(NewSessionRequest::new( + std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), + )) .block_task() .await?; @@ -135,15 +110,7 @@ async fn main() -> Result<(), Box> { // Send the prompt eprintln!("💬 Sending prompt: \"{prompt}\""); let prompt_response = cx - .send_request(PromptRequest { - session_id: session_id.clone(), - prompt: vec![ContentBlock::Text(TextContent { - text: prompt.clone(), - annotations: None, - meta: None, - })], - meta: None, - }) + .send_request(PromptRequest::new(session_id.clone(), vec![prompt.into()])) .block_task() .await?; From 6363f59fb8846128105ce1f1df041e736e33fb0d Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Wed, 26 Nov 2025 11:54:13 +0100 Subject: [PATCH 3/3] Fix doctests --- .github/workflows/ci.yml | 2 +- src/sacp/src/lib.rs | 8 +------- src/sacp/src/util/typed.rs | 13 ++----------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c29ff0..e534498 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,4 +46,4 @@ jobs: run: cargo build --all-targets --all-features - name: Run tests - run: cargo test --all-features + run: cargo test --all-features --workspace diff --git a/src/sacp/src/lib.rs b/src/sacp/src/lib.rs index eff1fba..3c2e6f4 100644 --- a/src/sacp/src/lib.rs +++ b/src/sacp/src/lib.rs @@ -23,13 +23,7 @@ //! // Create one or more request handlers -- these are attempted in order. //! // You can do anything you want in here, but you should eventually //! // respond to the request with `request_cx.respond(...)`: -//! request_cx.respond(InitializeResponse { -//! protocol_version: initialize.protocol_version, -//! agent_capabilities: AgentCapabilities::default(), -//! auth_methods: Default::default(), -//! agent_info: Default::default(), -//! meta: Default::default(), -//! }) +//! request_cx.respond(InitializeResponse::new(initialize.protocol_version)) //! }) //! .on_receive_message(async move |message: MessageAndCx| { //! // You can also handle any kind of message: diff --git a/src/sacp/src/util/typed.rs b/src/sacp/src/util/typed.rs index ab7fbe8..bbe3402 100644 --- a/src/sacp/src/util/typed.rs +++ b/src/sacp/src/util/typed.rs @@ -30,22 +30,13 @@ use crate::{ /// MatchMessage::new(message) /// .if_request(|req: InitializeRequest, cx: sacp::JrRequestCx| async move { /// // Handle initialization -/// let response = InitializeResponse { -/// protocol_version: req.protocol_version, -/// agent_capabilities: Default::default(), -/// auth_methods: vec![], -/// meta: None, -/// agent_info: None, -/// }; +/// let response = InitializeResponse::new(req.protocol_version); /// cx.respond(response) /// }) /// .await /// .if_request(|req: PromptRequest, cx: sacp::JrRequestCx| async move { /// // Handle prompts -/// let response = PromptResponse { -/// stop_reason: sacp::schema::StopReason::EndTurn, -/// meta: None, -/// }; +/// let response = PromptResponse::new(sacp::schema::StopReason::EndTurn); /// cx.respond(response) /// }) /// .await