From 9889c1a1b75ca45d7e905ac4319bfd6721530393 Mon Sep 17 00:00:00 2001 From: Parsa Bakhtiari Date: Sun, 21 Jun 2026 16:10:26 +0330 Subject: [PATCH 1/6] add tools and ollama client --- .gitignore | 3 + mini-coding-agent-rs/Cargo.lock | 1638 ++++++++++++++++++++++++++++ mini-coding-agent-rs/Cargo.toml | 11 + mini-coding-agent-rs/src/main.rs | 122 +++ mini-coding-agent-rs/src/ollama.rs | 55 + mini-coding-agent-rs/src/tools.rs | 61 ++ 6 files changed, 1890 insertions(+) create mode 100644 mini-coding-agent-rs/Cargo.lock create mode 100644 mini-coding-agent-rs/Cargo.toml create mode 100644 mini-coding-agent-rs/src/main.rs create mode 100644 mini-coding-agent-rs/src/ollama.rs create mode 100644 mini-coding-agent-rs/src/tools.rs diff --git a/.gitignore b/.gitignore index 3d22bf9..031152e 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ tmp/ # OS files .DS_Store + +# Rust files +target diff --git a/mini-coding-agent-rs/Cargo.lock b/mini-coding-agent-rs/Cargo.lock new file mode 100644 index 0000000..c1bd2bf --- /dev/null +++ b/mini-coding-agent-rs/Cargo.lock @@ -0,0 +1,1638 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mini-coding-agent-rs" +version = "0.1.0" +dependencies = [ + "clap", + "reqwest", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/mini-coding-agent-rs/Cargo.toml b/mini-coding-agent-rs/Cargo.toml new file mode 100644 index 0000000..8b962bc --- /dev/null +++ b/mini-coding-agent-rs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "mini-coding-agent-rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +reqwest = { version = "0.12", features = ["json"] } +clap = { version = "4.5", features = ["derive"] } diff --git a/mini-coding-agent-rs/src/main.rs b/mini-coding-agent-rs/src/main.rs new file mode 100644 index 0000000..6f00015 --- /dev/null +++ b/mini-coding-agent-rs/src/main.rs @@ -0,0 +1,122 @@ +mod ollama; +mod tools; + +use ollama::OllamaClient; +use tools::ToolCall; + +use clap::Parser; +use serde::{Deserialize, Serialize}; +use std::io::{self, Write}; + +#[derive(Parser, Debug)] +#[command[author, version, about, long_about = None]] +struct Args { + #[arg(short, long, default_value = "minimax-m3:cloud")] + model: String, + + #[arg(short, long, default_value = "http://127.0.0.1:11434")] + server: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Message { + role: String, + content: String, +} + +// #[derive(Debug)] +// struct WorkspaceContext { +// repo_root: String, +// branch: String, +// } +// +// impl WorkspaceContext { +// fn new() -> Self { +// let root = Command::new("git") +// .args(["rev-parse", "--show-toplevel"]) +// .output() +// .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) +// .unwrap_or_else(|_| "main".to_string()); +// +// let branch = Command::new("git") +// .args(["branch", "--show-current"]) +// .output() +// .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) +// .unwrap_or_else(|_| "main".to_string()); +// +// Self { +// repo_root: root, +// branch, +// } +// } +// } + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = Args::parse(); + let client = OllamaClient::new(args.model, args.server); + + let mut history = vec![Message { + role: "system".to_string(), + content: "You are a coding assistant. + Available tools: + - list_files(path: string) + - read_file(path: string) + - write_file(path: string, content: string) + - run_shell(command: string) + + To use a tool, return ONLY: + {\"name\":\"tool_name\", \"args\":{...}}" + .to_string(), + }]; + + println!("Welcome to Mini-Coding-Agent!"); + println!("Type 'exit' to quit."); + + loop { + print!("\n> "); + io::stdout().flush()?; + let mut user_input = String::new(); + io::stdin().read_line(&mut user_input)?; + let user_input = user_input.trim(); + if user_input == "exit" { + break; + } + + history.push(Message { + role: "user".to_string(), + content: user_input.to_string(), + }); + + for _ in 0..5 { + let response = client.chat(history.clone()).await?; + history.push(response.clone()); + + let content = response.content.trim(); + + if content.contains("") && content.contains("") { + let start = content.find("").unwrap() + 6; + let end = content.find("").unwrap(); + let json_str = &content[start..end]; + + match serde_json::from_str::(json_str) { + Ok(tool_call) => { + let result = tool_call.run(); + println!("[Tool Result] \n{}", result); + history.push(Message { + role: "user".to_string(), + content: format!("Tool result: {}", result), + }); + } + Err(e) => { + println!("[ERORR] Could not pars tool: {}", e); + break; + } + } + } else { + println!("\nAgent: {}", content); + } + } + } + Ok(()) +} diff --git a/mini-coding-agent-rs/src/ollama.rs b/mini-coding-agent-rs/src/ollama.rs new file mode 100644 index 0000000..989e52c --- /dev/null +++ b/mini-coding-agent-rs/src/ollama.rs @@ -0,0 +1,55 @@ +use crate::Message; +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize)] +pub struct ChatRequest { + model: String, + messages: Vec, + stream: bool, +} + +#[derive(Deserialize)] +pub struct ChatResponse { + message: Message, +} + +pub struct OllamaClient { + model: String, + host: String, + http_client: Client, +} + +impl OllamaClient { + pub fn new(model: String, host: String) -> Self { + Self { + model, + host, + http_client: Client::new(), + } + } + + pub async fn chat( + &self, + messages: Vec, + ) -> Result> { + let url = format!("{}/api/chat", self.host); + + let request = ChatRequest { + model: self.model.clone(), + messages, + stream: false, + }; + + let res = self + .http_client + .post(url) + .json(&request) + .send() + .await? + .json::() + .await?; + + Ok(res.message) + } +} diff --git a/mini-coding-agent-rs/src/tools.rs b/mini-coding-agent-rs/src/tools.rs new file mode 100644 index 0000000..2fbb33d --- /dev/null +++ b/mini-coding-agent-rs/src/tools.rs @@ -0,0 +1,61 @@ +use serde::Deserialize; +use std::fs; +use std::process::Command; + +#[derive(Deserialize, Debug)] +#[serde(tag = "name", content = "args", rename_all = "snake_case")] +pub enum ToolCall { + ListFiles { path: String }, + ReadFile { path: String }, + WriteFile { path: String, content: String }, + RunShell { command: String }, +} + +impl ToolCall { + pub fn run(&self) -> String { + match self { + ToolCall::ListFiles { path } => match fs::read_dir(path) { + Ok(entries) => { + let names: Vec<_> = entries + .filter_map(|e| e.ok()) + .map(|e| e.file_name().to_string_lossy().to_string()) + .collect(); + names.join("\n") + } + Err(e) => format!("Error: {}", e), + }, + // Logic for ReadFile + ToolCall::ReadFile { path } => match fs::read_to_string(path) { + Ok(content) => content, + Err(e) => format!("Error reading file: {}", e), + }, + ToolCall::WriteFile { path, content } => match fs::write(path, content) { + Ok(_) => format!("Successfully wrote to {}", path), + Err(e) => format!("Error writing file: {}", e), + }, + ToolCall::RunShell { command } => { + println!("[System] Executing: {}", command); + + let output = if cfg!(target_os = "windows") { + Command::new("cmd").args(["/c", command]).output() + } else { + Command::new("sh").args(["-c", command]).output() + }; + + match output { + Ok(o) => { + let stdout = String::from_utf8_lossy(&o.stdout); + let stderr = String::from_utf8_lossy(&o.stderr); + format!( + "Exit Code: {}\nSTDOUT: {}\nSTDERR: {}", + o.status.code().unwrap_or(-1), + stdout, + stderr + ) + } + Err(e) => format!("Failed to execute command: {}", e), + } + } + } + } +} From 31c851204c99aa7cdaa8fae84f19d05b33389c30 Mon Sep 17 00:00:00 2001 From: Parsa Bakhtiari Date: Wed, 24 Jun 2026 16:07:21 +0330 Subject: [PATCH 2/6] add workspace, and some tools --- mini-coding-agent-rs/Cargo.lock | 128 ++++++++++++++++++- mini-coding-agent-rs/Cargo.toml | 2 + mini-coding-agent-rs/src/main.rs | 53 ++------ mini-coding-agent-rs/src/ollama.rs | 55 ++++++--- mini-coding-agent-rs/src/session.rs | 100 +++++++++++++++ mini-coding-agent-rs/src/tools.rs | 169 +++++++++++++++++++++----- mini-coding-agent-rs/src/workspace.rs | 134 ++++++++++++++++++++ 7 files changed, 546 insertions(+), 95 deletions(-) create mode 100644 mini-coding-agent-rs/src/session.rs create mode 100644 mini-coding-agent-rs/src/workspace.rs diff --git a/mini-coding-agent-rs/Cargo.lock b/mini-coding-agent-rs/Cargo.lock index c1bd2bf..f1ae91b 100644 --- a/mini-coding-agent-rs/Cargo.lock +++ b/mini-coding-agent-rs/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "1.0.0" @@ -58,6 +67,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "base64" version = "0.22.1" @@ -98,6 +113,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clap" version = "4.6.1" @@ -306,10 +334,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + [[package]] name = "h2" version = "0.4.14" @@ -457,6 +496,30 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -648,11 +711,13 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" name = "mini-coding-agent-rs" version = "0.1.0" dependencies = [ + "chrono", "clap", "reqwest", "serde", "serde_json", "tokio", + "uuid", ] [[package]] @@ -683,6 +748,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -812,6 +886,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1324,6 +1404,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -1419,6 +1510,41 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" diff --git a/mini-coding-agent-rs/Cargo.toml b/mini-coding-agent-rs/Cargo.toml index 8b962bc..0f897a3 100644 --- a/mini-coding-agent-rs/Cargo.toml +++ b/mini-coding-agent-rs/Cargo.toml @@ -9,3 +9,5 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" reqwest = { version = "0.12", features = ["json"] } clap = { version = "4.5", features = ["derive"] } +chrono = "0.4.45" +uuid = { version = "1.23.3", features = ["v4"] } diff --git a/mini-coding-agent-rs/src/main.rs b/mini-coding-agent-rs/src/main.rs index 6f00015..4a20617 100644 --- a/mini-coding-agent-rs/src/main.rs +++ b/mini-coding-agent-rs/src/main.rs @@ -1,4 +1,5 @@ mod ollama; +mod session; mod tools; use ollama::OllamaClient; @@ -8,6 +9,9 @@ use clap::Parser; use serde::{Deserialize, Serialize}; use std::io::{self, Write}; +mod workspace; +use workspace::WorkspaceContext; + #[derive(Parser, Debug)] #[command[author, version, about, long_about = None]] struct Args { @@ -54,7 +58,7 @@ pub struct Message { #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); - let client = OllamaClient::new(args.model, args.server); + // let client = OllamaClient::new(args.model, args.server); let mut history = vec![Message { role: "system".to_string(), @@ -73,50 +77,7 @@ async fn main() -> Result<(), Box> { println!("Welcome to Mini-Coding-Agent!"); println!("Type 'exit' to quit."); - loop { - print!("\n> "); - io::stdout().flush()?; - let mut user_input = String::new(); - io::stdin().read_line(&mut user_input)?; - let user_input = user_input.trim(); - if user_input == "exit" { - break; - } - - history.push(Message { - role: "user".to_string(), - content: user_input.to_string(), - }); - - for _ in 0..5 { - let response = client.chat(history.clone()).await?; - history.push(response.clone()); - - let content = response.content.trim(); - - if content.contains("") && content.contains("") { - let start = content.find("").unwrap() + 6; - let end = content.find("").unwrap(); - let json_str = &content[start..end]; - - match serde_json::from_str::(json_str) { - Ok(tool_call) => { - let result = tool_call.run(); - println!("[Tool Result] \n{}", result); - history.push(Message { - role: "user".to_string(), - content: format!("Tool result: {}", result), - }); - } - Err(e) => { - println!("[ERORR] Could not pars tool: {}", e); - break; - } - } - } else { - println!("\nAgent: {}", content); - } - } - } + let workspace = WorkspaceContext::build("."); + println!("{}", workspace.text()); Ok(()) } diff --git a/mini-coding-agent-rs/src/ollama.rs b/mini-coding-agent-rs/src/ollama.rs index 989e52c..be396b9 100644 --- a/mini-coding-agent-rs/src/ollama.rs +++ b/mini-coding-agent-rs/src/ollama.rs @@ -3,53 +3,76 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; #[derive(Serialize)] -pub struct ChatRequest { - model: String, - messages: Vec, - stream: bool, +pub struct GenerateOptions { + pub num_predict: usize, + pub temperature: f32, + pub top_p: f32, } +#[derive(Serialize)] +pub struct GenerateRequest { + pub model: String, + pub prompt: String, + pub stream: bool, + pub raw: bool, + pub options: GenerateOptions, +} #[derive(Deserialize)] -pub struct ChatResponse { - message: Message, +pub struct GenerateResponse { + pub response: String, + pub error: Option, } pub struct OllamaClient { model: String, host: String, http_client: Client, + temperature: f32, + top_p: f32, } impl OllamaClient { - pub fn new(model: String, host: String) -> Self { + pub fn new(model: String, host: String, temperature: f32, top_p: f32) -> Self { Self { model, host, http_client: Client::new(), + temperature, + top_p, } } - pub async fn chat( + pub async fn complete( &self, - messages: Vec, - ) -> Result> { - let url = format!("{}/api/chat", self.host); + prompt: &str, + max_new_tokens: usize, + ) -> Result> { + let url = format!("{}/api/generate", self.host); - let request = ChatRequest { + let request = GenerateRequest { model: self.model.clone(), - messages, + prompt: prompt.to_string(), stream: false, + raw: false, + options: GenerateOptions { + num_predict: max_new_tokens, + temperature: self.temperature, + top_p: self.top_p, + }, }; let res = self .http_client - .post(url) + .post(&url) .json(&request) .send() .await? - .json::() + .json::() .await?; - Ok(res.message) + if let Some(err) = res.error { + return Err(format!("Ollama error: {}", err).into()); + } + Ok(res.response) } } diff --git a/mini-coding-agent-rs/src/session.rs b/mini-coding-agent-rs/src/session.rs new file mode 100644 index 0000000..94977b9 --- /dev/null +++ b/mini-coding-agent-rs/src/session.rs @@ -0,0 +1,100 @@ +use chrono::Utc; +use clap::builder::Str; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::{Path, PathBuf}; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SessionMemory { + pub task: String, + pub files: Vec, + pub notes: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SessionItem { + pub role: String, + pub content: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub args: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub created_at: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Session { + pub id: String, + pub created_at: String, + pub workspace_root: String, + pub history: Vec, + pub memory: SessionMemory, +} + +impl Session { + pub fn new(workspace_root: String) -> Self { + let id = format!( + "{}-{}", + Utc::now().format("%Y%m%d-%H%M%S"), + &Uuid::new_v4().to_string()[..6] + ); + Self { + id, + created_at: Utc::now().to_rfc3339(), + workspace_root, + history: Vec::new(), + memory: SessionMemory { + task: String::new(), + files: Vec::new(), + notes: Vec::new(), + }, + } + } +} + +pub struct SessionStore { + root: PathBuf, +} + +impl SessionStore { + pub fn new>(workspace_root: P) -> Self { + let root = workspace_root + .as_ref() + .join(".mini-coding-agent") + .join("sessions"); + fs::create_dir_all(&root).unwrap_or_default(); + Self { root } + } + + fn path(&self, session_id: &str) -> PathBuf { + self.root.join(format!("{}.json", session_id)) + } + + pub fn save(&self, session: &Session) -> Result { + let path = self.path(&session.id); + let json = serde_json::to_string_pretty(session)?; + fs::write(&path, json)?; + Ok(path) + } + + pub fn load(&self, session_id: &str) -> Result { + let json = fs::read_to_string(self.path(session_id))?; + let session = serde_json::from_str(&json)?; + Ok(session) + } + + pub fn latest(&self) -> Option { + let mut entries: Vec<_> = fs::read_dir(&self.root) + .ok()? + .filter_map(Result::ok) + .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("json")) + .collect(); + entries.sort_by_key(|e| e.metadata().and_then(|m| m.modified()).ok()); + + entries + .last() + .map(|e| e.path().file_stem().unwrap().to_string_lossy().into_owned()) + } +} diff --git a/mini-coding-agent-rs/src/tools.rs b/mini-coding-agent-rs/src/tools.rs index 2fbb33d..2ffa1d9 100644 --- a/mini-coding-agent-rs/src/tools.rs +++ b/mini-coding-agent-rs/src/tools.rs @@ -1,61 +1,166 @@ +use clap::builder::Str; use serde::Deserialize; +use std::fmt::format; use std::fs; use std::process::Command; #[derive(Deserialize, Debug)] #[serde(tag = "name", content = "args", rename_all = "snake_case")] pub enum ToolCall { - ListFiles { path: String }, - ReadFile { path: String }, - WriteFile { path: String, content: String }, - RunShell { command: String }, + ListFiles { + path: Option, + }, + ReadFile { + path: String, + start: Option, + end: Option, + }, + WriteFile { + path: String, + content: String, + }, + PatchFile { + path: String, + old_text: String, + new_text: String, + }, + Search { + pattern: String, + path: Option, + }, + RunShell { + command: String, + timeout: Option, + }, } impl ToolCall { pub fn run(&self) -> String { match self { - ToolCall::ListFiles { path } => match fs::read_dir(path) { - Ok(entries) => { - let names: Vec<_> = entries - .filter_map(|e| e.ok()) - .map(|e| e.file_name().to_string_lossy().to_string()) - .collect(); - names.join("\n") + ToolCall::ListFiles { path } => { + let target_path = path.as_deref().unwrap_or("."); + match fs::read_dir(target_path) { + Ok(entries) => { + let names: Vec<_> = entries + .filter_map(|e| e.ok()) + .map(|e| e.file_name().to_string_lossy().to_string()) + .collect(); + names.join("\n") + } + Err(e) => format!("Error: {}", e), } - Err(e) => format!("Error: {}", e), - }, + } // Logic for ReadFile - ToolCall::ReadFile { path } => match fs::read_to_string(path) { - Ok(content) => content, + ToolCall::ReadFile { path, start, end } => match std::fs::read_to_string(path) { + Ok(content) => { + let lines: Vec<&str> = content.lines().collect(); + let start_idx = start.unwrap_or(1).saturating_sub(1); + let end_idx = end.unwrap_or(200).min(lines.len()); + if start_idx >= lines.len() || start_idx >= end_idx { + return "Error: Invalid line range.".to_string(); + } + + let selected_lines = &lines[start_idx..end_idx]; + let mut result = Vec::new(); + + for (i, line) in selected_lines.iter().enumerate() { + result.push(format!("{:>4}: {}", start_idx + i + 1, line)); + } + format!("# {}\n{}", path, result.join("\n")) + } Err(e) => format!("Error reading file: {}", e), }, + ToolCall::WriteFile { path, content } => match fs::write(path, content) { Ok(_) => format!("Successfully wrote to {}", path), Err(e) => format!("Error writing file: {}", e), }, - ToolCall::RunShell { command } => { - println!("[System] Executing: {}", command); - - let output = if cfg!(target_os = "windows") { - Command::new("cmd").args(["/c", command]).output() - } else { - Command::new("sh").args(["-c", command]).output() - }; - - match output { - Ok(o) => { - let stdout = String::from_utf8_lossy(&o.stdout); - let stderr = String::from_utf8_lossy(&o.stderr); + ToolCall::PatchFile { + path, + old_text, + new_text, + } => match std::fs::read_to_string(path) { + Ok(content) => { + let matches = content.matches(old_text).count(); + if matches == 1 { + let updated = content.replacen(old_text, new_text, 1); + match std::fs::write(path, updated) { + Ok(_) => format!("Successfully patched {}", path), + Err(e) => format!("Error writing patched file: {}", e), + } + } else { format!( - "Exit Code: {}\nSTDOUT: {}\nSTDERR: {}", - o.status.code().unwrap_or(-1), - stdout, - stderr + "Error: old_text must occur exactly once, found {} occurrences.", + matches ) } + } + Err(e) => format!("Error reading file: {}", e), + }, + ToolCall::Search { pattern, path } => { + let target_path = path.as_deref().unwrap_or("."); + match std::process::Command::new("rg").args(["-n", "--smart-case", "--max-count", "200", pattern, target_path]).output() { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + if stdout.trim().is_empty() { + if stderr.trim().is_empty() { + "(no matches)".to_string() + } else { + stderr.to_string() + } + } else { + stdout.to_string() + } + } + Err(_) => "Error: ripgrep ('rg) is not installed or failed to run. Please install ripgrep.".to_string(), + } + } + ToolCall::RunShell { command, timeout } => { + let timeout_secs = timeout.unwrap_or(20); + + println!( + "[System] Executing: {} (timeout: {}s)", + command, timeout_secs + ); + + let mut child = if cfg!(target_os = "windows") { + std::process::Command::new("cmd") + .args(["/c", &command]) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + } else { + std::process::Command::new("sh") + .args(["-c", &command]) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + }; + match child { + Ok(mut process) => match process.wait_with_output() { + Ok(o) => { + let stdout = String::from_utf8_lossy(&o.stdout); + let stderr = String::from_utf8_lossy(&o.stderr); + format!( + "Exit Code: {}\nSTDOUT: {}\nSTDERR: {}", + o.status.code().unwrap_or(-1), + stdout, + stderr + ) + } + Err(e) => format!("Failed to read command output: {}", e), + }, Err(e) => format!("Failed to execute command: {}", e), } } } } } + +#[derive(Debug, Clone, PartialEq)] +pub enum ApprovalPolicy { + Ask, + Auto, + Never, +} diff --git a/mini-coding-agent-rs/src/workspace.rs b/mini-coding-agent-rs/src/workspace.rs new file mode 100644 index 0000000..74b40dc --- /dev/null +++ b/mini-coding-agent-rs/src/workspace.rs @@ -0,0 +1,134 @@ +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +#[derive(Debug)] +pub struct WorkspaceContext { + pub cwd: String, + pub repo_root: String, + pub branch: String, + pub default_branch: String, + pub status: String, + pub recent_commits: Vec, + pub project_docs: HashMap, +} + +impl WorkspaceContext { + fn git_command(cwd: &Path, args: &[&str], fallback: &str) -> String { + match Command::new("git").args(args).current_dir(cwd).output() { + Ok(output) if output.status.success() => { + let stdout = String::from_utf8_lossy(&output.stdout); + let trimmed = stdout.trim(); + if trimmed.is_empty() { + fallback.to_string() + } else { + trimmed.to_string() + } + } + _ => fallback.to_string(), + } + } + + // Trucate Text + fn clip(text: &str, limit: usize) -> String { + if text.len() <= limit { + text.to_string() + } else { + format!( + "{}\n...[truncated {} chars]", + &text[..limit], + text.len() - limit + ) + } + } + + pub fn build(cwd_path: &str) -> Self { + let cwd = PathBuf::from(cwd_path) + .canonicalize() + .unwrap_or_else(|_| PathBuf::from(".")); + + let repo_root = Self::git_command( + &cwd, + &["rev-parse", "--show-toplevel"], + cwd.to_str().unwrap(), + ); + let branch = Self::git_command(&cwd, &["branch", "--show-current"], "-"); + let default_branch_raw = Self::git_command( + &cwd, + &["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], + "origin/main", + ); + let default_branch = default_branch_raw + .strip_prefix("origin/") + .unwrap_or(&default_branch_raw) + .to_string(); + + let status_raw = Self::git_command(&cwd, &["log", "--oneline", "-5"], ""); + let status = Self::clip(&status_raw, 1500); + + let commits_raw = Self::git_command(&cwd, &["log", "--oneline", "-5"], ""); + let recent_commits: Vec = commits_raw + .lines() + .filter(|l| !l.is_empty()) + .map(|l| l.to_string()) + .collect(); + + // Read specific project documentation if it exists + let mut project_docs = HashMap::new(); + let doc_names = ["AGENTS.md", "README.md", "Cargo.toml", "package.json"]; + + let root_path = PathBuf::from(&repo_root); + for base in &[root_path.clone(), cwd.clone()] { + for name in doc_names { + let path = base.join(name); + if path.exists() { + if let Ok(path_diff) = path.strip_prefix(&root_path) { + let key = path_diff.to_string_lossy().to_string(); + if !project_docs.contains_key(&key) { + if let Ok(content) = fs::read_to_string(&path) { + project_docs.insert(key, Self::clip(&content, 1200)); + } + } + } + } + } + } + + Self { + cwd: cwd.to_string_lossy().to_string(), + repo_root, + branch, + default_branch, + status, + recent_commits, + project_docs, + } + } + pub fn text(&self) -> String { + let commits = if self.recent_commits.is_empty() { + "- none".to_string() + } else { + self.recent_commits + .iter() + .map(|c| format!("- {}", c)) + .collect::>() + .join("\n") + }; + + let docs = if self.project_docs.is_empty() { + "- none".to_string() + } else { + self.project_docs + .iter() + .map(|(path, snippet)| format!("- {}\n{}", path, snippet)) + .collect::>() + .join("\n") + }; + + format!( + "Workspace:\n- cwd: {}\n- repo_root: {}\n- branch: {}\n- default_branch: {}\n-status:\n{}\n- recent_commits:\n{}\n- project_docs:\n{}", + self.cwd, self.repo_root, self.branch, self.default_branch, self.status, commits, docs + ) + } +} From c6f5f46c05045747cb24e25fc5f78bf0af961a3f Mon Sep 17 00:00:00 2001 From: Parsa Bakhtiari Date: Sat, 27 Jun 2026 17:45:06 +0330 Subject: [PATCH 3/6] finish the rs code --- mini-coding-agent-rs/Cargo.lock | 76 ++++---- mini-coding-agent-rs/Cargo.toml | 1 + mini-coding-agent-rs/src/agent.rs | 281 ++++++++++++++++++++++++++++ mini-coding-agent-rs/src/main.rs | 212 +++++++++++++++------ mini-coding-agent-rs/src/ollama.rs | 1 - mini-coding-agent-rs/src/session.rs | 1 - mini-coding-agent-rs/src/tools.rs | 3 - 7 files changed, 476 insertions(+), 99 deletions(-) create mode 100644 mini-coding-agent-rs/src/agent.rs diff --git a/mini-coding-agent-rs/Cargo.lock b/mini-coding-agent-rs/Cargo.lock index f1ae91b..6834706 100644 --- a/mini-coding-agent-rs/Cargo.lock +++ b/mini-coding-agent-rs/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -326,18 +335,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -346,7 +343,7 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", ] [[package]] @@ -713,6 +710,7 @@ version = "0.1.0" dependencies = [ "chrono", "clap", + "regex", "reqwest", "serde", "serde_json", @@ -880,12 +878,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -901,6 +893,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "reqwest" version = "0.12.28" @@ -1221,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1436,15 +1457,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.123" @@ -1662,12 +1674,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "writeable" version = "0.6.3" diff --git a/mini-coding-agent-rs/Cargo.toml b/mini-coding-agent-rs/Cargo.toml index 0f897a3..fc9dfd6 100644 --- a/mini-coding-agent-rs/Cargo.toml +++ b/mini-coding-agent-rs/Cargo.toml @@ -11,3 +11,4 @@ reqwest = { version = "0.12", features = ["json"] } clap = { version = "4.5", features = ["derive"] } chrono = "0.4.45" uuid = { version = "1.23.3", features = ["v4"] } +regex = "1.12.4" diff --git a/mini-coding-agent-rs/src/agent.rs b/mini-coding-agent-rs/src/agent.rs new file mode 100644 index 0000000..594ded8 --- /dev/null +++ b/mini-coding-agent-rs/src/agent.rs @@ -0,0 +1,281 @@ +use crate::ollama::OllamaClient; +use crate::session::{Session, SessionStore}; +use crate::tools::ApprovalPolicy; +use crate::workspace::WorkspaceContext; + +use regex::Regex; +use serde_json::Value; + +pub struct MiniAgent { + pub model_client: OllamaClient, + pub workspace: WorkspaceContext, + pub session_store: SessionStore, + pub session: Session, + pub approval_policy: ApprovalPolicy, + pub max_step: usize, + pub max_new_tokens: usize, +} + +impl MiniAgent { + pub fn new( + model_client: OllamaClient, + workspace: WorkspaceContext, + session_store: SessionStore, + session: Session, + approval_policy: ApprovalPolicy, + max_step: usize, + max_new_tokens: usize, + ) -> Self { + Self { + model_client, + workspace, + session_store, + session, + approval_policy, + max_step, + max_new_tokens, + } + } + + pub fn clip(text: &str, limit: usize) -> String { + if text.len() <= limit { + text.to_string() + } else { + format!( + "{}\n...[truncated {} chars]", + &text[..limit], + text.len() - limit + ) + } + } + + pub fn memory_text(&self) -> String { + let task = if self.session.memory.task.is_empty() { + "-" + } else { + &self.session.memory.task + }; + let files = if self.session.memory.files.is_empty() { + "-".to_string() + } else { + self.session.memory.files.join(", ") + }; + let notes = if self.session.memory.notes.is_empty() { + "- none".to_string() + } else { + self.session + .memory + .notes + .iter() + .map(|n| format!("- {}", n)) + .collect::>() + .join("\n") + }; + format!( + "Memory:\n- task: {}\n- files: {}\n- notes:\n{}", + task, files, notes + ) + } + + pub fn history_text(&self) -> String { + if self.session.history.is_empty() { + return "- empty".to_string(); + } + + let mut lines = Vec::new(); + let total_items = self.session.history.len(); + let recent_start = if total_items > 6 { total_items - 6 } else { 0 }; + + for (index, item) in self.session.history.iter().enumerate() { + let recent = index >= recent_start; + let limit = if recent { 900 } else { 220 }; + + if item.role == "tool" { + let name = item.name.as_deref().unwrap_or("unknown"); + let args_str = item + .args + .as_ref() + .map(|a| a.to_string()) + .unwrap_or_default(); + lines.push(format!("[tool:{}] {}", name, args_str)); + lines.push(Self::clip(&item.content, limit)); + } else { + lines.push(format!( + "[{}] {}", + item.role, + Self::clip(&item.content, limit) + )); + } + } + // limit the total history block to 12,000 characters + Self::clip(&lines.join("\n"), 12000) + } + + pub fn build_prefix(&self) -> String { + let rules = "- Use tools instead of guessing about the workspace. +- Return exactly one ... or one .... +- Tool calls must look like: + {\"name\":\"tool_name\",\"args\":{...}} +- For write_file and patch_file with multi-line text, prefer XML style: + ... +- Final answers must look like: + your answer +- Never invent tool results."; + let examples = "{\"name\":\"list_files\",\"args\":{\"path\":\".\"}}\nDone."; + + format!("You are Mini-Coding-Agent, a small local coding agent running through Ollama.\nRules:\n{}\n\nValid response examples:\n{}\n\n{}", rules, examples, self.workspace.text()) + } + + pub fn prompt(&self, user_message: &str) -> String { + format!( + "{}\n\n{}\n\nTranscript:\n{}\n\nCurrent user request:\n{}", + self.build_prefix(), + self.memory_text(), + self.history_text(), + user_message + ) + } + + pub fn extract<'a>(text: &'a str, tag: &str) -> Option<&'a str> { + let start_tag = format!("<{}>", tag); + let end_tag = format!("", tag); + let start = text.find(&start_tag)?; + let start_idx = start + start_tag.len(); + let end = text[start_idx..].find(&end_tag)?; + Some(&text[start_idx..start_idx + end].trim()) + } + + pub fn parse(raw: &str) -> (String, String) { + let raw = raw.trim(); + if let Some(tool_body) = Self::extract(raw, "tool") { + if serde_json::from_str::(tool_body).is_ok() { + return ("tool".to_string(), tool_body.to_string()); + } else { + return ("retry".to_string(), "Runtime notice: model returned malformed tool JSON. Reply with a valid call.".to_string()); + } + } + + let xml_re = Regex::new(r#"]*>(.*?)<\/tool>"#).unwrap(); + if let Some(caps) = xml_re.captures(raw) { + let name = caps.get(1).unwrap().as_str(); + let body = caps.get(2).unwrap().as_str(); + if name == "write_file" || name == "patch_file" { + if let Some(content) = Self::extract(body, "content") { + let json = serde_json::json!({ + "name": name, + "args": { + "path": Self::extract(body, "path").unwrap_or(""), + "content": content + + } + }); + return ("tool".to_string(), json.to_string()); + } + } + } + + if let Some(final_text) = Self::extract(raw, "final") { + if !final_text.is_empty() { + return ("final".to_string(), final_text.to_string()); + } + } + + if !raw.is_empty() && !raw.contains(" or .".to_string()) + } + + pub async fn ask(&mut self, user_message: &str) -> String { + use crate::session::SessionItem; + + if self.session.memory.task.is_empty() { + self.session.memory.task = Self::clip(user_message, 300); + } + + self.session.history.push(SessionItem { + role: "user".to_string(), + content: user_message.to_string(), + name: None, + args: None, + created_at: Some(chrono::Utc::now().to_rfc3339()), + }); + + let mut tool_steps = 0; + let mut attempts = 0; + let max_attempts = (self.max_step * 3).max(self.max_step + 4); + + while tool_steps < self.max_step && attempts < max_attempts { + attempts += 1; + let prompt_text = self.prompt(user_message); + + match self + .model_client + .complete(&prompt_text, self.max_new_tokens) + .await + { + Ok(raw_response) => { + let (kind, payload) = Self::parse(&raw_response); + if kind == "tool" { + tool_steps += 1; + match serde_json::from_str::(&payload) { + Ok(tool_call) => { + let result = tool_call.run(); + + self.session.history.push(SessionItem { + role: "tool".to_string(), + name: Some("executed".to_string()), + args: Some(serde_json::from_str(&payload).unwrap()), + content: result.clone(), + created_at: Some(chrono::Utc::now().to_rfc3339()), + }); + + let _ = self.session_store.save(&self.session); + continue; + } + Err(e) => { + self.session.history.push(SessionItem { + role: "assistant".to_string(), + content: format!( + "Runtime notice: Failed to parse tool call internally: {}", + e + ), + name: None, + args: None, + created_at: None, + }); + continue; + } + } + } + + if kind == "retry" { + self.session.history.push(SessionItem { + role: "assistant".to_string(), + content: payload, + name: None, + args: None, + created_at: None, + }); + continue; + } + + // It's final answer! + self.session.history.push(SessionItem { + role: "assistant".to_string(), + content: payload.clone(), + name: None, + args: None, + created_at: Some(chrono::Utc::now().to_rfc3339()), + }); + + let _ = self.session_store.save(&self.session); + return payload; + } + Err(e) => return format!("Error reaching Ollama: {}", e), + } + } + "Stopped afer reaching step limits whithout a final answer.".to_string() + } +} diff --git a/mini-coding-agent-rs/src/main.rs b/mini-coding-agent-rs/src/main.rs index 4a20617..a9bb780 100644 --- a/mini-coding-agent-rs/src/main.rs +++ b/mini-coding-agent-rs/src/main.rs @@ -1,83 +1,177 @@ +mod agent; mod ollama; mod session; mod tools; +mod workspace; +use agent::MiniAgent; use ollama::OllamaClient; -use tools::ToolCall; +use session::{Session, SessionStore}; +use tools::ApprovalPolicy; +use workspace::WorkspaceContext; use clap::Parser; -use serde::{Deserialize, Serialize}; use std::io::{self, Write}; -mod workspace; -use workspace::WorkspaceContext; - #[derive(Parser, Debug)] #[command[author, version, about, long_about = None]] struct Args { #[arg(short, long, default_value = "minimax-m3:cloud")] model: String, - #[arg(short, long, default_value = "http://127.0.0.1:11434")] - server: String, -} + #[arg(long, default_value = "http://127.0.0.1:11434")] + host: String, + + #[arg(long, default_value = ".")] + cwd: String, -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Message { - role: String, - content: String, + #[arg(long)] + resume: Option, + + #[arg(long, default_value = "ask")] + approval: String, + + #[arg(long, default_value_t = 6)] + max_steps: usize, + + #[arg(long, default_value_t = 512)] + max_new_tokens: usize, + + #[arg(long, default_value_t = 0.2)] + temperature: f32, + + #[arg(long, default_value_t = 0.9)] + top_p: f32, } -// #[derive(Debug)] -// struct WorkspaceContext { -// repo_root: String, -// branch: String, -// } -// -// impl WorkspaceContext { -// fn new() -> Self { -// let root = Command::new("git") -// .args(["rev-parse", "--show-toplevel"]) -// .output() -// .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) -// .unwrap_or_else(|_| "main".to_string()); -// -// let branch = Command::new("git") -// .args(["branch", "--show-current"]) -// .output() -// .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) -// .unwrap_or_else(|_| "main".to_string()); -// -// Self { -// repo_root: root, -// branch, -// } -// } -// } +fn build_welcome(agent: &MiniAgent, model: &str, host: &str) -> String { + let art = [ + r#"/\ /\"#, + r#"{ `---' }"#, + r#"{ O O }"#, + r#"~~> V <~~"#, + r#"\\ \|/ /"#, + r#"`-----'__"#, + ]; + + let mut output = String::new(); + output.push_str(&format!("+{}+\n", "=".repeat(76))); + for line in &art { + output.push_str(&format!("| {:^74} |\n", line)); + } + output.push_str(&format!("| {:^74} |\n", "MINI CODING AGENT RUST")); + output.push_str(&format!("+{}+\n", "-".repeat(76))); + + let cwd_display = if agent.workspace.cwd.len() > 60 { + format!( + "...{}", + &agent.workspace.cwd[agent.workspace.cwd.len() - 57..] + ) + } else { + agent.workspace.cwd.clone() + }; + + output.push_str(&format!("| WORKSPACE {:<63} |\n", cwd_display)); + output.push_str(&format!( + "| MODEL {:<20} BRANCH {:<33} |\n", + model, agent.workspace.branch + )); + output.push_str(&format!( + "| HOST {:<20} SESSION {:<33} |\n", + host, agent.session.id + )); + output.push_str(&format!("+{}+\n", "=".repeat(76))); + output +} #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); - // let client = OllamaClient::new(args.model, args.server); - - let mut history = vec![Message { - role: "system".to_string(), - content: "You are a coding assistant. - Available tools: - - list_files(path: string) - - read_file(path: string) - - write_file(path: string, content: string) - - run_shell(command: string) - - To use a tool, return ONLY: - {\"name\":\"tool_name\", \"args\":{...}}" - .to_string(), - }]; - - println!("Welcome to Mini-Coding-Agent!"); - println!("Type 'exit' to quit."); - - let workspace = WorkspaceContext::build("."); - println!("{}", workspace.text()); + + let approval_policy = match args.approval.to_lowercase().as_str() { + "auto" => ApprovalPolicy::Auto, + "never" => ApprovalPolicy::Never, + _ => ApprovalPolicy::Ask, + }; + + let workspace = WorkspaceContext::build(&args.cwd); + let store = SessionStore::new(&workspace.repo_root); + + let session_id = if let Some(r) = args.resume { + if r == "latest" { + store + .latest() + .unwrap_or_else(|| Session::new(workspace.repo_root.clone()).id) + } else { + r + } + } else { + Session::new(workspace.repo_root.clone()).id + }; + + let session = store + .load(&session_id) + .unwrap_or_else(|_| Session::new(workspace.repo_root.clone())); + + let client = OllamaClient::new( + args.model.clone(), + args.host.clone(), + args.temperature, + args.top_p, + ); + + let mut agent = MiniAgent::new( + client, + workspace, + store, + session, + approval_policy, + args.max_steps, + args.max_new_tokens, + ); + + println!("{}", build_welcome(&agent, &args.model, &args.host)); + + loop { + print!("\nmini-coding-agent-rs> "); + io::stdout().flush()?; + + let mut user_input = String::new(); + io::stdin().read_line(&mut user_input)?; + let user_input = user_input.trim(); + + if user_input.is_empty() { + continue; + } + + match user_input { + "/exit" | "/quit" => break, + "/help" => { + println!("Commands:"); + println!("/help Show this help message."); + println!("/memory Show the agent's distilled working memory."); + println!("/session Show the path to the saved session file."); + println!("/reset Clear the current session history and memory."); + println!("/exit Exit the agent."); + } + "/memory" => println!("{}", agent.memory_text()), + "/session" => { + let session_path = agent.session_store.save(&agent.session)?; + println!("{}", session_path.display()) + } + "/reset" => { + agent.session.history.clear(); + agent.session.memory.task.clear(); + agent.session.memory.files.clear(); + agent.session.memory.notes.clear(); + println!("session reset"); + } + _ => { + println!(); + let response = agent.ask(user_input).await; + println!("\n{}", response); + } + } + } Ok(()) } diff --git a/mini-coding-agent-rs/src/ollama.rs b/mini-coding-agent-rs/src/ollama.rs index be396b9..5e3d764 100644 --- a/mini-coding-agent-rs/src/ollama.rs +++ b/mini-coding-agent-rs/src/ollama.rs @@ -1,4 +1,3 @@ -use crate::Message; use reqwest::Client; use serde::{Deserialize, Serialize}; diff --git a/mini-coding-agent-rs/src/session.rs b/mini-coding-agent-rs/src/session.rs index 94977b9..b888f68 100644 --- a/mini-coding-agent-rs/src/session.rs +++ b/mini-coding-agent-rs/src/session.rs @@ -1,5 +1,4 @@ use chrono::Utc; -use clap::builder::Str; use serde::{Deserialize, Serialize}; use std::fs; use std::path::{Path, PathBuf}; diff --git a/mini-coding-agent-rs/src/tools.rs b/mini-coding-agent-rs/src/tools.rs index 2ffa1d9..9e48ef6 100644 --- a/mini-coding-agent-rs/src/tools.rs +++ b/mini-coding-agent-rs/src/tools.rs @@ -1,8 +1,5 @@ -use clap::builder::Str; use serde::Deserialize; -use std::fmt::format; use std::fs; -use std::process::Command; #[derive(Deserialize, Debug)] #[serde(tag = "name", content = "args", rename_all = "snake_case")] From d46e00864d2c21ebea5d9c51071f3b2116600ba5 Mon Sep 17 00:00:00 2001 From: Parsa Bakhtiari Date: Sun, 28 Jun 2026 09:11:42 +0330 Subject: [PATCH 4/6] fix/XML Parser Regex --- mini-coding-agent-rs/src/agent.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mini-coding-agent-rs/src/agent.rs b/mini-coding-agent-rs/src/agent.rs index 594ded8..8dc0448 100644 --- a/mini-coding-agent-rs/src/agent.rs +++ b/mini-coding-agent-rs/src/agent.rs @@ -155,16 +155,21 @@ impl MiniAgent { } } - let xml_re = Regex::new(r#"]*>(.*?)<\/tool>"#).unwrap(); + let xml_re = Regex::new( + r#"]*>(.*?)<\/tool>"#, + ) + .unwrap(); if let Some(caps) = xml_re.captures(raw) { let name = caps.get(1).unwrap().as_str(); - let body = caps.get(2).unwrap().as_str(); + let path = caps.get(2).map(|m| m.as_str()).unwrap_or(""); + let body = caps.get(3).unwrap().as_str(); + if name == "write_file" || name == "patch_file" { if let Some(content) = Self::extract(body, "content") { let json = serde_json::json!({ "name": name, "args": { - "path": Self::extract(body, "path").unwrap_or(""), + "path": path, "content": content } From 58a54e1434ca0498784355a5cd8fe039191e3b7e Mon Sep 17 00:00:00 2001 From: Parsa Bakhtiari Date: Sun, 28 Jun 2026 10:03:09 +0330 Subject: [PATCH 5/6] impl approval and wire to main.rs --- mini-coding-agent-rs/src/agent.rs | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/mini-coding-agent-rs/src/agent.rs b/mini-coding-agent-rs/src/agent.rs index 8dc0448..1fec9d1 100644 --- a/mini-coding-agent-rs/src/agent.rs +++ b/mini-coding-agent-rs/src/agent.rs @@ -1,3 +1,5 @@ +use std::io::Read; + use crate::ollama::OllamaClient; use crate::session::{Session, SessionStore}; use crate::tools::ApprovalPolicy; @@ -136,6 +138,24 @@ impl MiniAgent { ) } + pub fn approve(&self, payload: &str) -> bool { + match self.approval_policy { + ApprovalPolicy::Auto => true, + ApprovalPolicy::Never => false, + ApprovalPolicy::Ask => { + use std::io::{self, Write}; + print!("Approve tool execution: {}? [y/N] ", payload); + let _ = io::stdout().flush(); + let mut answer = String::new(); + if io::stdin().read_line(&mut answer).is_ok() { + let ans = answer.trim().to_lowercase(); + ans == "y" || ans == "yes" + } else { + false + } + } + } + } pub fn extract<'a>(text: &'a str, tag: &str) -> Option<&'a str> { let start_tag = format!("<{}>", tag); let end_tag = format!("", tag); @@ -226,6 +246,23 @@ impl MiniAgent { tool_steps += 1; match serde_json::from_str::(&payload) { Ok(tool_call) => { + // Check if the tool is risky + let is_risky = matches!( + tool_call, + crate::tools::ToolCall::WriteFile { .. } + | crate::tools::ToolCall::PatchFile { .. } + | crate::tools::ToolCall::RunShell { .. } + ); + + if is_risky && !self.approve(&payload) { + self.session.history.push(SessionItem { + role: "assistant".to_string(), + content: "Runtime notice: tool execution denied by user. Reply with a different tool or final answer.".to_string(), + name: None, args: None, created_at: None, + }); + continue; + } + let result = tool_call.run(); self.session.history.push(SessionItem { From 75c8075d29c65e11e2c51074b76df195f2470f1b Mon Sep 17 00:00:00 2001 From: Parsa Bakhtiari Date: Sun, 28 Jun 2026 10:11:14 +0330 Subject: [PATCH 6/6] add readme for Rust version --- mini-coding-agent-rs/README.md | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 mini-coding-agent-rs/README.md diff --git a/mini-coding-agent-rs/README.md b/mini-coding-agent-rs/README.md new file mode 100644 index 0000000..c41847e --- /dev/null +++ b/mini-coding-agent-rs/README.md @@ -0,0 +1,78 @@ +# Mini Coding Agent (Rust Edition) + +A blazingly fast, context-aware local coding assistant written in Rust. It communicates with local models via Ollama and seamlessly reads your workspace, interacts with your files, and executes shell commands safely on your behalf. + +## Features + +- **Context-Aware**: Automatically pulls your current branch, recent commits, Git status, and project documentation into its context before you even say hello. +- **Token Efficient**: Employs aggressive "context reduction" strategies to truncate verbose command outputs and gracefully prune older chat history to save tokens. +- **Sandboxed Execution**: Features an approval policy (`ask`, `auto`, `never`) for risky operations like editing files or running shell commands. +- **Robust Parsing**: Supports a dual JSON/XML parsing strategy and automatically asks the model to try again if it hallucinates malformed tool payloads. +- **Persistence**: Remembers your previous sessions and summaries via the `.mini-coding-agent/sessions` directory so you can pick up exactly where you left off. + +## Requirements + +- [Rust & Cargo](https://rustup.rs/) (edition 2021+) +- [Ollama](https://ollama.com/) running locally or accessible via URL. +- [Ripgrep (`rg`)](https://github.com/BurntSushi/ripgrep) installed on your system (optional, used for the `search` tool). + +## Installation + +To install the agent globally on your system, clone the repository, navigate to the `mini-coding-agent-rs` directory, and run: + +```bash +cargo install --path . +``` + +This will compile the optimized binary and place it in your `~/.cargo/bin` folder, making it accessible from anywhere in your terminal! + +## Usage + +Navigate to any coding project where you want assistance and run: + +```bash +mini-coding-agent-rs +``` + +### Command Line Arguments + +You can customize the agent's behavior at startup using CLI flags: + +- `--model `: The Ollama model to use (default: `qwen3.5:4b`) +- `--host `: The Ollama API endpoint (default: `http://127.0.0.1:11434`) +- `--cwd `: The workspace directory (default: `.`) +- `--approval `: Tool approval policy: `ask`, `auto`, or `never` (default: `ask`) +- `--resume `: Resume a specific session ID, or `latest` to pick up your last conversation. +- `--max-steps `: Maximum tool execution loops per turn (default: 6) +- `--max-new-tokens `: Maximum output tokens per step (default: 512) +- `--temperature `: Model temperature (default: 0.2) +- `--top-p `: Model top-p sampling (default: 0.9) + +**Example:** +```bash +mini-coding-agent-rs --model qwen2.5-coder:7b --approval auto --resume latest +``` + +### In-App Commands + +While running the REPL loop, you can use the following commands: + +- `/help` Show the help message. +- `/memory` Show the agent's distilled working memory summary. +- `/session` Show the path to the saved session file. +- `/reset` Clear the current session history and memory. +- `/exit` Exit the agent (or `/quit`). + +## Tools + +The agent is equipped with the following internal tools: +- `list_files(path)` +- `read_file(path, start, end)` +- `search(pattern, path)` +- `write_file(path, content)` +- `patch_file(path, old_text, new_text)` +- `run_shell(command)` + +## Contributing + +Pull requests and feature improvements are welcome! Ensure you run `cargo fmt` and `cargo check` before submitting.