diff --git a/Cargo.lock b/Cargo.lock index e4bd294..61c450b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -105,7 +105,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -130,7 +130,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "wl-clipboard-rs", "x11rb", ] @@ -920,7 +920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2040,7 +2040,7 @@ dependencies = [ [[package]] name = "lash-core" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "arc-swap", "async-trait", @@ -2107,7 +2107,7 @@ dependencies = [ [[package]] name = "lash-http-transport" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "bytes", @@ -2120,7 +2120,7 @@ dependencies = [ [[package]] name = "lash-lashlang-runtime" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "chrono", @@ -2140,7 +2140,7 @@ dependencies = [ [[package]] name = "lash-llm-tools" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "jsonschema", @@ -2153,7 +2153,7 @@ dependencies = [ [[package]] name = "lash-llm-transport" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "bytes", @@ -2167,7 +2167,7 @@ dependencies = [ [[package]] name = "lash-plugin-mcp" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "base64 0.22.1", @@ -2186,7 +2186,7 @@ dependencies = [ [[package]] name = "lash-plugin-observational-memory" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "chrono", @@ -2198,7 +2198,7 @@ dependencies = [ [[package]] name = "lash-plugin-plan-mode" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "lash-core", @@ -2213,7 +2213,7 @@ dependencies = [ [[package]] name = "lash-plugin-process-controls" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "lash-core", @@ -2225,7 +2225,7 @@ dependencies = [ [[package]] name = "lash-plugin-tool-output-budget" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "lash-core", "serde", @@ -2236,7 +2236,7 @@ dependencies = [ [[package]] name = "lash-protocol-rlm" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "jsonschema", @@ -2254,7 +2254,7 @@ dependencies = [ [[package]] name = "lash-protocol-standard" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "lash-core", @@ -2265,7 +2265,7 @@ dependencies = [ [[package]] name = "lash-provider-anthropic" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "base64 0.22.1", @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "lash-provider-auth" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "base64 0.22.1", @@ -2296,7 +2296,7 @@ dependencies = [ [[package]] name = "lash-provider-google" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "base64 0.22.1", @@ -2314,7 +2314,7 @@ dependencies = [ [[package]] name = "lash-provider-openai" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "base64 0.22.1", @@ -2335,7 +2335,7 @@ dependencies = [ [[package]] name = "lash-remote-protocol" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "base64 0.22.1", "lash-core", @@ -2348,7 +2348,7 @@ dependencies = [ [[package]] name = "lash-rlm-types" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "lash-sansio", "serde", @@ -2358,7 +2358,7 @@ dependencies = [ [[package]] name = "lash-runtime" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "futures-util", @@ -2382,7 +2382,7 @@ dependencies = [ [[package]] name = "lash-sansio" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "schemars", "serde", @@ -2410,7 +2410,7 @@ dependencies = [ [[package]] name = "lash-sqlite-store" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "flate2", @@ -2430,7 +2430,7 @@ dependencies = [ [[package]] name = "lash-standard-plugins" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "lash-core", @@ -2444,7 +2444,7 @@ dependencies = [ [[package]] name = "lash-subagents" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "lash-core", @@ -2460,7 +2460,7 @@ dependencies = [ [[package]] name = "lash-tool-support" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "ignore", @@ -2476,7 +2476,7 @@ dependencies = [ [[package]] name = "lash-tools" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "globset", @@ -2498,7 +2498,7 @@ dependencies = [ [[package]] name = "lash-trace" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "chrono", "serde", @@ -2530,7 +2530,7 @@ dependencies = [ [[package]] name = "lashlang" version = "0.0.0-dev" -source = "git+https://github.com/SamGalanakis/lash?rev=6c31efb5c4beedb117c3b1e1df3b8439b1cc2935#6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" +source = "git+https://github.com/SamGalanakis/lash?rev=a4ba0f53c9043858af9c50001ec9629bbb43b3a7#a4ba0f53c9043858af9c50001ec9629bbb43b3a7" dependencies = [ "async-trait", "compact_str", @@ -3179,7 +3179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3516,7 +3516,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3918,7 +3918,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3977,7 +3977,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4338,7 +4338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4457,7 +4457,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5326,7 +5326,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e8c431c..556fe99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,26 +49,26 @@ uuid = "1" # The CLI tracks one reviewed Lash revision. Dependency updates are explicit, # testable compatibility changes rather than an implicit lockstep workspace. -lash = { package = "lash-runtime", git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-core = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-lashlang-runtime = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-llm-tools = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-plugin-mcp = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-plugin-plan-mode = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-plugin-process-controls = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-protocol-rlm = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-protocol-standard = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-provider-anthropic = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-provider-auth = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-provider-google = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-provider-openai = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-rlm-types = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-sqlite-store = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-standard-plugins = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-subagents = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-tool-support = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lash-trace = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } -lashlang = { git = "https://github.com/SamGalanakis/lash", rev = "6c31efb5c4beedb117c3b1e1df3b8439b1cc2935" } +lash = { package = "lash-runtime", git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-core = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-lashlang-runtime = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-llm-tools = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-plugin-mcp = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-plugin-plan-mode = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-plugin-process-controls = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-protocol-rlm = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-protocol-standard = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-provider-anthropic = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-provider-auth = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-provider-google = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-provider-openai = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-rlm-types = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-sqlite-store = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-standard-plugins = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-subagents = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-tool-support = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lash-trace = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } +lashlang = { git = "https://github.com/SamGalanakis/lash", rev = "a4ba0f53c9043858af9c50001ec9629bbb43b3a7" } lash-autoresearch = { path = "crates/lash-autoresearch" } lash-cli-perf = { path = "crates/lash-cli-perf" } diff --git a/crates/lash-cli/src/app/runtime_events.rs b/crates/lash-cli/src/app/runtime_events.rs index 7b13fd3..4c3b659 100644 --- a/crates/lash-cli/src/app/runtime_events.rs +++ b/crates/lash-cli/src/app/runtime_events.rs @@ -1,7 +1,7 @@ use super::*; use crate::assistant_text::push_assistant_text_block; #[cfg(test)] -use lash_core::SessionEvent; +use lash_core::SessionStreamEvent; use lash_core::{AcceptedInjectedTurnInput, PluginRuntimeEvent, TurnActivity, TurnEvent}; fn runtime_status_from_plugin_event( @@ -459,12 +459,12 @@ impl App { } #[cfg(test)] - pub fn handle_session_event(&mut self, event: SessionEvent) { - if matches!(event, SessionEvent::Done) { + pub fn handle_session_event(&mut self, event: SessionStreamEvent) { + if matches!(event, SessionStreamEvent::Done) { self.finalize_live_markdown(); self.stop_turn(); self.scroll_to_bottom(); - } else if let SessionEvent::Message { text, kind } = &event + } else if let SessionStreamEvent::Message { text, kind } = &event && kind == "tool_output" { let current_status = self.live.turn.as_ref().map(|turn| turn.run_state); @@ -483,14 +483,18 @@ impl App { } #[cfg(test)] -fn test_session_event_to_turn_activity(event: SessionEvent) -> Option { +fn test_session_event_to_turn_activity(event: SessionStreamEvent) -> Option { let turn_event = match event { - SessionEvent::LlmRequest { + SessionStreamEvent::LlmRequest { protocol_iteration, .. } => TurnEvent::ModelRequestStarted { protocol_iteration }, - SessionEvent::TextDelta { content } => TurnEvent::AssistantProseDelta { text: content }, - SessionEvent::ReasoningDelta { content } => TurnEvent::ReasoningDelta { text: content }, - SessionEvent::ToolCallStart { + SessionStreamEvent::TextDelta { content } => { + TurnEvent::AssistantProseDelta { text: content } + } + SessionStreamEvent::ReasoningDelta { content } => { + TurnEvent::ReasoningDelta { text: content } + } + SessionStreamEvent::ToolCallStart { call_id, name, args, @@ -501,7 +505,7 @@ fn test_session_event_to_turn_activity(event: SessionEvent) -> Option Option { + SessionStreamEvent::Message { text, kind } if kind == "lashlang_code" => { TurnEvent::CodeBlockStarted { language: "lashlang".to_string(), code: text, graph_key: None, } } - SessionEvent::TokenUsage { + SessionStreamEvent::TokenUsage { protocol_iteration, usage, cumulative, @@ -532,7 +536,7 @@ fn test_session_event_to_turn_activity(event: SessionEvent) -> Option Option TurnEvent::Error { message }, - SessionEvent::PluginEvent { plugin_id, event } => { + SessionStreamEvent::Error { message, .. } => TurnEvent::Error { message }, + SessionStreamEvent::PluginEvent { plugin_id, event } => { TurnEvent::PluginRuntime { plugin_id, event } } - SessionEvent::InjectedTurnInputAccepted { inputs, checkpoint } => { + SessionStreamEvent::InjectedTurnInputAccepted { inputs, checkpoint } => { TurnEvent::QueuedInputAccepted { checkpoint, inputs } } - SessionEvent::InjectedMessagesCommitted { + SessionStreamEvent::InjectedMessagesCommitted { messages, checkpoint, } => TurnEvent::QueuedMessagesCommitted { messages, checkpoint, }, - SessionEvent::Done - | SessionEvent::ChildTokenUsage { .. } - | SessionEvent::TurnOutcome { .. } - | SessionEvent::LlmResponse { .. } - | SessionEvent::Message { .. } => return None, + SessionStreamEvent::Done + | SessionStreamEvent::ChildTokenUsage { .. } + | SessionStreamEvent::TurnOutcome { .. } + | SessionStreamEvent::LlmResponse { .. } + | SessionStreamEvent::Message { .. } => return None, }; Some(TurnActivity::independent(turn_event)) } diff --git a/crates/lash-cli/src/app/tests.rs b/crates/lash-cli/src/app/tests.rs index 06b3a7e..e7016ac 100644 --- a/crates/lash-cli/src/app/tests.rs +++ b/crates/lash-cli/src/app/tests.rs @@ -1,7 +1,7 @@ use super::*; use crate::editor::LARGE_PASTE_CHAR_THRESHOLD; use async_trait::async_trait; -use lash_core::{Part, PruneState, SessionEvent, TurnActivity, TurnEvent}; +use lash_core::{Part, PruneState, SessionStreamEvent, TurnActivity, TurnEvent}; use lash_tui_extensions::{ SlashCommandSpec, TuiExtension, TuiExtensionContext, TuiExtensions, TuiHostEffect, }; @@ -56,28 +56,28 @@ fn part(id: &str, kind: PartKind, content: &str) -> Part { } } -fn conversation_event(message: Message) -> lash_core::SessionEventRecord { - lash_core::SessionEventRecord::Conversation(lash_core::ConversationRecord::from_message( +fn conversation_event(message: Message) -> lash_core::SessionHistoryRecord { + lash_core::SessionHistoryRecord::Conversation(lash_core::ConversationRecord::from_message( message, )) } -fn events_from_messages(messages: &[Message]) -> Vec { +fn events_from_messages(messages: &[Message]) -> Vec { messages.iter().cloned().map(conversation_event).collect() } fn test_read_view( - events: &[lash_core::SessionEventRecord], + events: &[lash_core::SessionHistoryRecord], messages: &[Message], _tool_calls: &[ToolCallRecord], ) -> lash_core::SessionReadView { let mut graph = lash_core::SessionGraph::default(); for event in events { match event { - lash_core::SessionEventRecord::Conversation(record) => { + lash_core::SessionHistoryRecord::Conversation(record) => { graph.append_message(record.to_message()); } - lash_core::SessionEventRecord::Protocol(event) => { + lash_core::SessionHistoryRecord::Protocol(event) => { graph.append_protocol_event(event.clone()); } } @@ -85,7 +85,7 @@ fn test_read_view( let event_message_ids = events .iter() .filter_map(|event| match event { - lash_core::SessionEventRecord::Conversation(record) => Some(record.id.as_str()), + lash_core::SessionHistoryRecord::Conversation(record) => Some(record.id.as_str()), _ => None, }) .collect::>(); @@ -103,7 +103,7 @@ fn test_read_view( } fn timeline_items_from_test_read_view( - events: &[lash_core::SessionEventRecord], + events: &[lash_core::SessionHistoryRecord], messages: &[Message], tool_calls: &[ToolCallRecord], ui_state: &UiProjectionState, @@ -115,7 +115,7 @@ fn timeline_items_from_test_read_view( } fn timeline_from_test_read_view( - events: &[lash_core::SessionEventRecord], + events: &[lash_core::SessionHistoryRecord], messages: &[Message], tool_calls: &[ToolCallRecord], ui_state: &UiProjectionState, @@ -125,7 +125,7 @@ fn timeline_from_test_read_view( } fn interrupted_blocks_from_test_read_view( - events: &[lash_core::SessionEventRecord], + events: &[lash_core::SessionHistoryRecord], messages: &[Message], tool_calls: &[ToolCallRecord], ui_state: &UiProjectionState, diff --git a/crates/lash-cli/src/app/tests/projection.rs b/crates/lash-cli/src/app/tests/projection.rs index 8c81f93..5c15b17 100644 --- a/crates/lash-cli/src/app/tests/projection.rs +++ b/crates/lash-cli/src/app/tests/projection.rs @@ -8,7 +8,7 @@ fn finish_turn_from_read_view_rebuilds_current_turn_from_authoritative_state() { let turn = PreparedTurn::new("What exists now?".into(), Vec::new()); app.push_prepared_user_input(&turn); app.start_turn(); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "I looked at the actual librarian prompt".into(), }); @@ -51,7 +51,7 @@ fn finish_turn_from_read_view_preserves_local_system_messages_inside_active_turn app.timeline.push(UiTimelineItem::SystemMessage( "Session info\nruntime: rlm".into(), )); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "Runtime answer".into(), }); @@ -119,10 +119,10 @@ fn finish_turn_from_read_view_does_not_duplicate_assistant_text_after_tool_activ let turn = PreparedTurn::new("Fix it".into(), Vec::new()); app.push_prepared_user_input(&turn); app.start_turn(); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "I found and fixed the 500.".into(), }); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { name: "update_plan".into(), args: serde_json::json!({ "plan": [ @@ -173,13 +173,13 @@ fn finish_turn_from_read_view_uses_authoritative_reasoning_and_text() { let turn = PreparedTurn::new("Write a poem".into(), Vec::new()); app.push_prepared_user_input(&turn); app.start_turn(); - app.handle_session_event(SessionEvent::ReasoningDelta { + app.handle_session_event(SessionStreamEvent::ReasoningDelta { content: "Crafting a cool poem".into(), }); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "Neon rain on midnight street.".into(), }); - app.handle_session_event(SessionEvent::ReasoningDelta { + app.handle_session_event(SessionStreamEvent::ReasoningDelta { content: "I see the user wants a cool poem.".into(), }); @@ -234,13 +234,13 @@ fn live_reasoning_and_prose_deltas_are_committed_chronologically() { app.push_prepared_user_input(&turn); app.start_turn(); - app.handle_session_event(SessionEvent::ReasoningDelta { + app.handle_session_event(SessionStreamEvent::ReasoningDelta { content: "first reasoning".into(), }); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "visible prose".into(), }); - app.handle_session_event(SessionEvent::ReasoningDelta { + app.handle_session_event(SessionStreamEvent::ReasoningDelta { content: "second reasoning".into(), }); app.finalize_live_markdown(); @@ -367,7 +367,7 @@ fn rlm_trajectory_reasoning_projects_as_assistant_reasoning() { }; let events = vec![ conversation_event(assistant.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -402,7 +402,7 @@ fn rlm_trajectory_projects_reasoning_prose_code_and_error_in_order() { }; let events = vec![ conversation_event(assistant.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -455,7 +455,7 @@ fn rlm_trajectory_final_output_projects_as_assistant_text() { }; let events = vec![ conversation_event(assistant.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -492,7 +492,7 @@ fn rlm_trajectory_null_final_output_keeps_prose_and_suppresses_final_value() { }; let events = vec![ conversation_event(assistant.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -533,7 +533,7 @@ fn rlm_final_answer_projects_after_reasoning_and_lashlang_code() { let events = vec![ conversation_event(user.clone()), conversation_event(assistant_reasoning.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), conversation_event(assistant.clone()), @@ -590,7 +590,7 @@ fn finish_turn_replaces_live_final_value_with_projection() { }; let events = vec![ conversation_event(user.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -652,7 +652,7 @@ fn rlm_trajectory_projects_reasoning_and_code_without_hidden_tool_calls() { }; let events = vec![ conversation_event(assistant.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -678,7 +678,7 @@ fn rlm_trajectory_final_output_does_not_inline_hidden_tool_call_projection() { error: None, final_output: Some(serde_json::json!("Mon May 11 01:51:25 PM CEST 2026")), }; - let events = vec![lash_core::SessionEventRecord::Protocol( + let events = vec![lash_core::SessionHistoryRecord::Protocol( lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), ), @@ -711,7 +711,7 @@ fn rlm_activity_journal_projects_tool_rows_after_matching_lashlang_block() { }; let events = vec![ conversation_event(user.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -817,7 +817,7 @@ fn finish_turn_from_read_view_preserves_live_lashlang_tool_activity_from_cli_jou }; let events = vec![ conversation_event(user.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(entry), )), ]; @@ -868,11 +868,11 @@ fn rlm_trajectory_steps_project_chronologically_without_hidden_tool_results() { let second_reasoning = assistant_reasoning_text_message("a-second", "Then check files.", ""); let events = vec![ conversation_event(first_reasoning.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(first), )), conversation_event(second_reasoning.clone()), - lash_core::SessionEventRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( + lash_core::SessionHistoryRecord::Protocol(lash_protocol_rlm::rlm_protocol_event( lash_rlm_types::RlmProtocolEvent::RlmTrajectoryEntry(second), )), conversation_event(assistant.clone()), @@ -903,7 +903,7 @@ fn finish_turn_from_read_view_uses_authoritative_transcript_even_when_streamed_t let turn = PreparedTurn::new("Shorten it".into(), Vec::new()); app.push_prepared_user_input(&turn); app.start_turn(); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "Visible streamed text".into(), }); @@ -935,7 +935,7 @@ fn finish_interrupted_turn_from_read_view_preserves_local_system_messages() { let turn = PreparedTurn::new("Keep working".into(), Vec::new()); app.push_prepared_user_input(&turn); app.start_turn(); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "Partial answer".into(), }); @@ -983,7 +983,7 @@ fn cancel_then_queued_turn_uses_read_view_without_preview_duplication() { let cancelled_turn = PreparedTurn::new("Cancel this".into(), Vec::new()); app.push_prepared_user_input(&cancelled_turn); app.start_turn(); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "Cancelled live preview".into(), }); diff --git a/crates/lash-cli/src/app/tests/render.rs b/crates/lash-cli/src/app/tests/render.rs index 5b3e19b..15d61ca 100644 --- a/crates/lash-cli/src/app/tests/render.rs +++ b/crates/lash-cli/src/app/tests/render.rs @@ -227,7 +227,7 @@ fn stop_turn_marks_idle_redraw_dirty() { #[test] fn text_delta_accumulates_raw() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "\n\nfirst\n".into(), }); assert_eq!( @@ -235,7 +235,7 @@ fn text_delta_accumulates_raw() { Some("first") ); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "\n\n\nsecond\n".into(), }); assert_eq!( @@ -247,10 +247,10 @@ fn text_delta_accumulates_raw() { #[test] fn text_delta_code_fence_preserved() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "text\n\n```python\n".into(), }); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "# comment\n".into(), }); // The newline between ```python and # comment must be preserved @@ -267,7 +267,7 @@ fn text_delta_stays_in_live_assistant_until_committed() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "Draft answer".into(), }); @@ -281,16 +281,16 @@ fn text_delta_stays_in_live_assistant_until_committed() { #[test] fn text_delta_updates_live_token_estimate() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 0, tool_list: String::new(), }); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "abcd".into(), }); assert_eq!(app.usage.live_output_tokens_estimate, 1); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "efgh".into(), }); assert_eq!(app.usage.live_output_tokens_estimate, 2); @@ -299,12 +299,12 @@ fn text_delta_updates_live_token_estimate() { #[test] fn first_text_delta_switches_thinking_to_responding() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 0, tool_list: String::new(), }); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "hello".into(), }); assert_eq!( @@ -323,7 +323,7 @@ fn first_text_delta_switches_thinking_to_responding() { #[test] fn llm_request_sets_plain_thinking_status() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 0, tool_list: String::new(), @@ -344,17 +344,17 @@ fn llm_request_sets_plain_thinking_status() { #[test] fn llm_request_flushes_intermediate_stream_text() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "Let me continue testing.".into(), }); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc1".into()), name: "read_file".into(), args: serde_json::json!({"path":"src/main.rs"}), output: lash_core::ToolCallOutput::success(serde_json::json!("ok")), duration_ms: 1, }); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 1, message_count: 0, tool_list: String::new(), @@ -371,10 +371,10 @@ fn tool_call_flushes_intermediate_stream_text_immediately() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.timeline.truncate(0); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "I’m checking the rendering path first.".into(), }); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc2".into()), name: "read_file".into(), args: serde_json::json!({"path":"crates/lash-cli/src/app/mod.rs"}), @@ -399,7 +399,7 @@ fn tool_call_started_renders_running_activity_until_completion() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.timeline.truncate(0); - app.handle_session_event(SessionEvent::ToolCallStart { + app.handle_session_event(SessionStreamEvent::ToolCallStart { call_id: Some("tc-running".into()), name: "read_file".into(), args: serde_json::json!({"path":"README.md"}), @@ -418,7 +418,7 @@ fn tool_call_started_renders_running_activity_until_completion() { ), } - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc-running".into()), name: "read_file".into(), args: serde_json::json!({"path":"README.md"}), @@ -443,11 +443,11 @@ fn tool_call_started_renders_running_activity_until_completion() { #[test] fn token_usage_resets_live_token_estimate() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "abcdefgh".into(), }); assert!(app.usage.live_output_tokens_estimate > 0); - app.handle_session_event(SessionEvent::TokenUsage { + app.handle_session_event(SessionStreamEvent::TokenUsage { protocol_iteration: 0, usage: TokenUsage { input_tokens: 10, @@ -472,12 +472,12 @@ fn token_usage_resets_live_token_estimate() { #[test] fn input_only_streamed_usage_keeps_live_output_estimate() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "abcdefgh".into(), }); let live_estimate = app.usage.live_output_tokens_estimate; assert!(live_estimate > 0); - app.handle_session_event(SessionEvent::TokenUsage { + app.handle_session_event(SessionStreamEvent::TokenUsage { protocol_iteration: 0, usage: TokenUsage { input_tokens: 10, @@ -503,7 +503,7 @@ fn input_only_streamed_usage_keeps_live_output_estimate() { fn tool_output_renders_during_generic_running_turn() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "started git status --short\n".into(), kind: "tool_output".into(), }); @@ -519,13 +519,13 @@ fn tool_output_carriage_return_rewrites_partial_line() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "Compiling alpha".into(), kind: "tool_output".into(), }); assert_eq!(app.live.tool_output.partial, "Compiling alpha"); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "\rCompiling beta".into(), kind: "tool_output".into(), }); @@ -539,7 +539,7 @@ fn tool_output_crlf_commits_current_line() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "started cargo check\r\n".into(), kind: "tool_output".into(), }); @@ -556,7 +556,7 @@ fn tool_output_strips_ansi_escape_sequences_from_live_preview() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "\u{1b}[33mwarning\u{1b}[0m: check this\n".into(), kind: "tool_output".into(), }); @@ -573,11 +573,11 @@ fn tool_output_strips_osc_escape_sequences_from_live_preview() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "\u{1b}]11;?\u{1b}\\".into(), kind: "tool_output".into(), }); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "done\n".into(), kind: "tool_output".into(), }); @@ -591,7 +591,7 @@ fn tool_output_tabs_collapse_to_single_spaces_in_live_preview() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "hash\trefs/tags/v0.2.29\n".into(), kind: "tool_output".into(), }); @@ -610,7 +610,7 @@ fn tool_output_does_not_change_total_content_height() { app.start_turn(); let baseline = app.total_content_height(32, 8); - app.handle_session_event(SessionEvent::Message { + app.handle_session_event(SessionStreamEvent::Message { text: "started git status --short\n".into(), kind: "tool_output".into(), }); @@ -627,7 +627,7 @@ fn update_plan_panel_lights_up_plan_dock() { "dock starts empty before any update_plan event" ); - app.handle_session_event(SessionEvent::PluginEvent { + app.handle_session_event(SessionStreamEvent::PluginEvent { plugin_id: "update_plan".into(), event: lash_core::PluginRuntimeEvent::Custom { name: "update_plan.snapshot".into(), @@ -662,7 +662,7 @@ fn rlm_budget_warning_uses_status_not_user_message() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.timeline = vec![UiTimelineItem::AssistantText("working".into())].into(); - app.handle_session_event(SessionEvent::PluginEvent { + app.handle_session_event(SessionStreamEvent::PluginEvent { plugin_id: "rlm_protocol".into(), event: lash_core::PluginRuntimeEvent::Status { key: "rlm_context_budget_warning".into(), @@ -689,7 +689,7 @@ fn rlm_budget_warning_uses_status_not_user_message() { fn plan_protocol_state_events_upsert_and_clear_blocks() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::PluginEvent { + app.handle_session_event(SessionStreamEvent::PluginEvent { plugin_id: "plan_mode".into(), event: lash_core::PluginRuntimeEvent::Custom { name: "plan_mode.state".into(), @@ -713,7 +713,7 @@ fn plan_protocol_state_events_upsert_and_clear_blocks() { .is_some_and(|turn| turn.has_visible_output) ); - app.handle_session_event(SessionEvent::PluginEvent { + app.handle_session_event(SessionStreamEvent::PluginEvent { plugin_id: "plan_mode".into(), event: lash_core::PluginRuntimeEvent::Custom { name: "plan_mode.state".into(), @@ -736,7 +736,7 @@ fn cancelled_error_renders_as_system_message() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); app.note_manual_interrupt_requested(); - app.handle_session_event(SessionEvent::Error { + app.handle_session_event(SessionStreamEvent::Error { message: "LLM error: cancelled".into(), envelope: Some(lash_core::session_model::ErrorEnvelope { kind: "llm_provider".into(), @@ -761,7 +761,7 @@ fn cancelled_error_renders_as_system_message() { fn cancelled_error_without_manual_request_still_stops_immediately() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - app.handle_session_event(SessionEvent::Error { + app.handle_session_event(SessionStreamEvent::Error { message: "LLM error: cancelled".into(), envelope: Some(lash_core::session_model::ErrorEnvelope { kind: "llm_provider".into(), @@ -786,7 +786,7 @@ fn cancelled_error_without_manual_request_still_stops_immediately() { fn repeated_cancelled_errors_do_not_duplicate_system_message() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.start_turn(); - let cancelled = SessionEvent::Error { + let cancelled = SessionStreamEvent::Error { message: "LLM error: cancelled".into(), envelope: Some(lash_core::session_model::ErrorEnvelope { kind: "llm_provider".into(), @@ -1014,7 +1014,7 @@ fn scroll_down_to_bottom_reenables_tail_follow_instead_of_contextual_anchor() { app.start_turn(); app.follow_mode = FollowOutputMode::PinnedTurnStart; - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: (0..20) .map(|idx| format!("line {idx}")) .collect::>() @@ -1055,7 +1055,7 @@ fn text_delta_does_not_force_scroll_when_follow_output_is_manual() { app.follow_mode = FollowOutputMode::Manual; app.scroll_offset = 3; - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "streamed output".into(), }); @@ -1111,7 +1111,7 @@ fn text_delta_reveals_message_start_before_switching_to_tail_follow() { app.start_turn(); app.follow_mode = FollowOutputMode::PinnedTurnStart; - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: (0..20) .map(|idx| format!("line {idx}")) .collect::>() @@ -1167,14 +1167,14 @@ fn handle_tool_call_merges_contiguous_exploration_activity() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.timeline.truncate(0); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc3".into()), name: "grep".into(), args: serde_json::json!({"query": "ctx"}), output: lash_core::ToolCallOutput::success(serde_json::json!("match")), duration_ms: 10, }); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc4".into()), name: "read_file".into(), args: serde_json::json!({"path": "crates/lash-cli/src/render/mod.rs"}), @@ -1214,7 +1214,7 @@ fn handle_tool_call_merges_contiguous_edit_activity() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); app.timeline.truncate(0); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc5".into()), name: "edit".into(), args: serde_json::json!({"path": "a.rs"}), @@ -1230,7 +1230,7 @@ fn handle_tool_call_merges_contiguous_edit_activity() { })), duration_ms: 7, }); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc6".into()), name: "edit".into(), args: serde_json::json!({"path": "b.rs"}), @@ -1268,7 +1268,7 @@ fn live_batch_tool_children_render_from_events_without_parent_batch_block() { app.timeline.truncate(0); // The batch container renders nothing on its own. - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("batch-0".into()), name: "batch".into(), args: serde_json::json!({ "tool_calls": [] }), @@ -1277,14 +1277,14 @@ fn live_batch_tool_children_render_from_events_without_parent_batch_block() { }); // Its children surface as their own tool events and render directly, with // no batch arg/result re-parsing. - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("batch-0:00".into()), name: "read_file".into(), args: serde_json::json!({ "path": "README.md" }), output: lash_core::ToolCallOutput::success(serde_json::json!("README body")), duration_ms: 8, }); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("batch-0:01".into()), name: "grep".into(), args: serde_json::json!({ "query": "OpenAI", "path": "README.md" }), diff --git a/crates/lash-cli/src/app/tests/runtime_state.rs b/crates/lash-cli/src/app/tests/runtime_state.rs index e325e7b..ad71e68 100644 --- a/crates/lash-cli/src/app/tests/runtime_state.rs +++ b/crates/lash-cli/src/app/tests/runtime_state.rs @@ -128,7 +128,7 @@ fn plan_exit_tool_call_consumes_pending_prompt_response() { .all(|block| !matches!(block, UiTimelineItem::UserInput(_))) ); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: Some("tc-plan-exit".into()), name: "plan_exit".into(), args: serde_json::json!({}), @@ -180,12 +180,12 @@ fn plan_exit_fresh_context_tool_does_not_queue_ui_turn_or_switch() { #[test] fn non_manual_error_sets_transient_status() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 0, tool_list: String::new(), }); - app.handle_session_event(SessionEvent::Error { + app.handle_session_event(SessionStreamEvent::Error { message: "LLM error: Claude request failed with 500".into(), envelope: Some(lash_core::session_model::ErrorEnvelope { kind: "llm_provider".into(), @@ -197,7 +197,7 @@ fn non_manual_error_sets_transient_status() { provider_failure_kind: None, }), }); - app.handle_session_event(SessionEvent::Done); + app.handle_session_event(SessionStreamEvent::Done); assert_eq!( app.live.turn.as_ref().map(|turn| turn.run_state), @@ -215,12 +215,12 @@ fn non_manual_error_sets_transient_status() { #[test] fn retry_status_stays_visible_when_retry_request_starts() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 0, tool_list: String::new(), }); - app.handle_session_event(SessionEvent::RetryStatus { + app.handle_session_event(SessionStreamEvent::RetryStatus { wait_seconds: 2, attempt: 2, max_attempts: 4, @@ -240,7 +240,7 @@ fn retry_status_stays_visible_when_retry_request_starts() { Some("in 2s · attempt 2/4 · Codex returned non-SSE body but it could not be read") ); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 0, tool_list: String::new(), @@ -262,16 +262,16 @@ fn retry_status_stays_visible_when_retry_request_starts() { #[test] fn transient_status_expires_on_tick() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::LlmRequest { + app.handle_session_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 0, tool_list: String::new(), }); - app.handle_session_event(SessionEvent::Error { + app.handle_session_event(SessionStreamEvent::Error { message: "runtime error".into(), envelope: None, }); - app.handle_session_event(SessionEvent::Done); + app.handle_session_event(SessionStreamEvent::Done); if let Some(turn) = app.live.turn.as_mut() { turn.transient_until = Some(std::time::Instant::now() - std::time::Duration::from_secs(1)); @@ -374,7 +374,7 @@ fn accepted_injected_turn_input_renders_matching_draft_presentation() { let turn = PreparedTurn::new("follow up".into(), Vec::new()); app.cache_draft_presentation(turn.clone()); - app.handle_session_event(SessionEvent::InjectedTurnInputAccepted { + app.handle_session_event(SessionStreamEvent::InjectedTurnInputAccepted { inputs: vec![lash_core::AcceptedInjectedTurnInput { id: None, message: PluginMessage::text(MessageRole::User, "follow up"), @@ -400,7 +400,7 @@ fn accepted_injected_turn_input_matches_by_runtime_content_even_when_display_tex turn.input_metadata.effective_text = turn.effective_text.clone(); app.cache_draft_presentation(turn.clone()); - app.handle_session_event(SessionEvent::InjectedTurnInputAccepted { + app.handle_session_event(SessionStreamEvent::InjectedTurnInputAccepted { inputs: vec![lash_core::AcceptedInjectedTurnInput { id: None, message: PluginMessage::text( @@ -427,7 +427,7 @@ fn accepted_injected_turn_input_prefers_draft_id_over_content_match() { app.cache_draft_presentation(first.clone()); app.cache_draft_presentation(accepted.clone()); - app.handle_session_event(SessionEvent::InjectedTurnInputAccepted { + app.handle_session_event(SessionStreamEvent::InjectedTurnInputAccepted { inputs: vec![lash_core::AcceptedInjectedTurnInput { id: Some(accepted_id), message: PluginMessage::text(MessageRole::User, "runtime accepted content"), @@ -500,7 +500,7 @@ fn turn_submission_route_blocks_when_runtime_is_switching() { fn accepted_injected_turn_input_without_pending_match_still_renders_once() { let mut app = App::new("test-model".into(), "test".into(), "test-session-id".into()); - app.handle_session_event(SessionEvent::InjectedTurnInputAccepted { + app.handle_session_event(SessionStreamEvent::InjectedTurnInputAccepted { inputs: vec![lash_core::AcceptedInjectedTurnInput { id: None, message: PluginMessage { @@ -514,7 +514,7 @@ fn accepted_injected_turn_input_without_pending_match_still_renders_once() { checkpoint: lash_core::CheckpointKind::AfterWork, }); - app.handle_session_event(SessionEvent::InjectedMessagesCommitted { + app.handle_session_event(SessionStreamEvent::InjectedMessagesCommitted { messages: vec![PluginMessage { role: MessageRole::User, content: "runtime content".into(), @@ -545,7 +545,7 @@ fn accepted_injected_turn_input_removes_matching_draft_without_popping_wrong_one Vec::new(), )); - app.handle_session_event(SessionEvent::InjectedTurnInputAccepted { + app.handle_session_event(SessionStreamEvent::InjectedTurnInputAccepted { inputs: vec![lash_core::AcceptedInjectedTurnInput { id: None, message: PluginMessage::text(MessageRole::User, "uhh do not switch nvm"), @@ -571,10 +571,10 @@ fn injected_messages_committed_do_not_duplicate_user_input_after_assistant_work( // Assistant streams some prose, then runs a tool — this pushes an // AssistantText and an Activity block in front of the original UserInput. - app.handle_session_event(SessionEvent::TextDelta { + app.handle_session_event(SessionStreamEvent::TextDelta { content: "You're right.".into(), }); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { name: "fs_read".into(), args: serde_json::json!({"path": "crates/lash/src/plugin.rs"}), output: lash_core::ToolCallOutput::success(serde_json::json!({"output": "..."})), @@ -583,7 +583,7 @@ fn injected_messages_committed_do_not_duplicate_user_input_after_assistant_work( }); // Now the late commit arrives. - app.handle_session_event(SessionEvent::InjectedMessagesCommitted { + app.handle_session_event(SessionStreamEvent::InjectedMessagesCommitted { messages: vec![PluginMessage::text( MessageRole::User, "Why are you still dillydallying", @@ -614,7 +614,7 @@ fn injected_messages_committed_do_not_duplicate_existing_visible_user_input() { app.push_prepared_user_input(&turn); app.cache_draft_presentation(turn.clone()); - app.handle_session_event(SessionEvent::InjectedMessagesCommitted { + app.handle_session_event(SessionStreamEvent::InjectedMessagesCommitted { messages: vec![PluginMessage::text( MessageRole::User, "(I want future migrations to work though!)", @@ -1180,7 +1180,7 @@ fn option_prompt_response_falls_back_to_user_block_without_inline_panel() { let response = app.take_prompt_response(); assert_eq!(response.as_deref(), Some("1. red")); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: None, name: "search_tools".into(), args: serde_json::json!({ "query": "queue" }), @@ -1220,7 +1220,7 @@ fn option_prompt_response_is_rendered_inline_by_question_panel_artifact() { Some("2. blue\n\nNote: ship the blue path") ); - app.handle_session_event(SessionEvent::ToolCall { + app.handle_session_event(SessionStreamEvent::ToolCall { call_id: None, name: "ask".into(), args: serde_json::json!({ diff --git a/crates/lash-cli/src/capability_catalog.rs b/crates/lash-cli/src/capability_catalog.rs index 6db0bf4..76820a9 100644 --- a/crates/lash-cli/src/capability_catalog.rs +++ b/crates/lash-cli/src/capability_catalog.rs @@ -15,7 +15,8 @@ //! anywhere else in the CLI. use lash_core::{ - ModelCapability, ReasoningCapability, ReasoningDisableEncoding, ReasoningEncoding, + CacheControlDialect, ModelCapability, ReasoningCapability, ReasoningDisableEncoding, + ReasoningEncoding, }; /// How a rule matches a model id. `full` is the lowercased model id as @@ -78,6 +79,7 @@ struct Row { aliases: &'static [(&'static str, &'static str)], encoding: Encoding, disable: Option, + cache_control: Option, } impl Row { @@ -111,6 +113,8 @@ impl Row { }), mandatory: false, }), + cache_control: self.cache_control, + stream_termination: None, } } } @@ -131,6 +135,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Native), + cache_control: None, }, Row { kind: "anthropic", @@ -140,6 +145,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Native), + cache_control: None, }, Row { kind: "anthropic", @@ -149,6 +155,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Native), + cache_control: None, }, // Budget fallback disables by explicitly omitting the thinking block. Row { @@ -159,6 +166,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Budget(&[("low", 1024), ("medium", 4096), ("high", 12288)]), disable: Some(DisableEncoding::Omit), + cache_control: None, }, // ── google_oauth ── Row { @@ -169,6 +177,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Budget(&[("high", 16000), ("max", 24576)]), disable: Some(DisableEncoding::Budget(0)), + cache_control: None, }, // gemini-3.1 must precede gemini-3 so `gemini-3.1-pro-preview` matches here. Row { @@ -179,6 +188,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: None, + cache_control: None, }, Row { kind: "google_oauth", @@ -188,6 +198,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: None, + cache_control: None, }, // ── openai (direct Responses API) ── // Prefixes match the prefix-stripped id. gpt-5.2/5.3/5.4 drop `minimal` @@ -200,6 +211,7 @@ const ROWS: &[Row] = &[ aliases: &[("minimal", "low")], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, Row { kind: "openai", @@ -209,6 +221,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, Row { kind: "openai", @@ -218,6 +231,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, // ── openai-compatible (OpenRouter base URL) ── Row { @@ -228,6 +242,7 @@ const ROWS: &[Row] = &[ aliases: &[("minimal", "low")], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, Row { kind: "openai-compatible", @@ -237,6 +252,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, Row { kind: "openai-compatible", @@ -246,6 +262,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: Some(CacheControlDialect::Anthropic), }, Row { kind: "openai-compatible", @@ -255,6 +272,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: Some(CacheControlDialect::Gemini), }, // ── codex (OAuth route; only gpt-5* models) ── // Exact gpt-5.5 first, then codex variants, then plain gpt-5 variants. @@ -267,6 +285,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, Row { kind: "codex", @@ -279,6 +298,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, Row { kind: "codex", @@ -288,6 +308,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, // gpt-5.2/5.3/5.4 drop `minimal` and clamp it to `low`. Row { @@ -301,6 +322,7 @@ const ROWS: &[Row] = &[ aliases: &[("minimal", "low")], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, Row { kind: "codex", @@ -310,6 +332,7 @@ const ROWS: &[Row] = &[ aliases: &[], encoding: Encoding::Effort, disable: Some(DisableEncoding::Effort("none")), + cache_control: None, }, ]; @@ -322,7 +345,7 @@ fn builtin_capability_override(_kind: &str, _model: &str) -> Option ModelCapability { if let Some(override_capability) = builtin_capability_override(kind, model) { return override_capability; @@ -453,9 +476,21 @@ mod tests { fn openai_compatible_claude_defaults_high() { let cap = capability_for("openai-compatible", "anthropic/claude-sonnet-4.6"); assert_eq!(default_effort(&cap).as_deref(), Some("high")); + assert_eq!( + cap.cache_control, + Some(CacheControlDialect::Anthropic), + "OpenRouter Claude routes use Anthropic cache-control placement" + ); // gpt substring wins earlier only when present; claude row here. let gpt = capability_for("openai-compatible", "openai/gpt-4o"); assert_eq!(default_effort(&gpt).as_deref(), Some("medium")); + assert_eq!(gpt.cache_control, None); + } + + #[test] + fn openai_compatible_gemini_uses_gemini_cache_control() { + let cap = capability_for("openai-compatible", "google/gemini-3-pro"); + assert_eq!(cap.cache_control, Some(CacheControlDialect::Gemini)); } #[test] diff --git a/crates/lash-cli/src/render/tests.rs b/crates/lash-cli/src/render/tests.rs index 70c1ead..36b3ed8 100644 --- a/crates/lash-cli/src/render/tests.rs +++ b/crates/lash-cli/src/render/tests.rs @@ -18,7 +18,7 @@ use std::sync::Arc; use std::sync::mpsc; fn timeline_items_from_test_read_view( - events: &[lash_core::SessionEventRecord], + events: &[lash_core::SessionHistoryRecord], messages: &[lash_core::Message], _tool_calls: &[lash_core::ToolCallRecord], ui_state: &crate::app::UiProjectionState, @@ -26,10 +26,10 @@ fn timeline_items_from_test_read_view( let mut graph = lash_core::SessionGraph::default(); for event in events { match event { - lash_core::SessionEventRecord::Conversation(record) => { + lash_core::SessionHistoryRecord::Conversation(record) => { graph.append_message(record.to_message()); } - lash_core::SessionEventRecord::Protocol(event) => { + lash_core::SessionHistoryRecord::Protocol(event) => { graph.append_protocol_event(event.clone()); } } @@ -37,7 +37,7 @@ fn timeline_items_from_test_read_view( let event_message_ids = events .iter() .filter_map(|event| match event { - lash_core::SessionEventRecord::Conversation(record) => Some(record.id.as_str()), + lash_core::SessionHistoryRecord::Conversation(record) => Some(record.id.as_str()), _ => None, }) .collect::>(); @@ -1408,7 +1408,7 @@ fn live_reasoning_compacts_after_activity_appends_below_it() { "live reasoning should expand while it is the tail; got {live_text:?}", ); - app.handle_session_event(lash_core::SessionEvent::ToolCall { + app.handle_session_event(lash_core::SessionStreamEvent::ToolCall { call_id: None, name: "read_file".into(), args: serde_json::json!({ "path": "crates/lash/src/provider.rs" }), @@ -1452,7 +1452,7 @@ fn committed_reasoning_compacts_while_live_assistant_streams() { )] .into(); - app.handle_session_event(lash_core::SessionEvent::TextDelta { + app.handle_session_event(lash_core::SessionStreamEvent::TextDelta { content: "Answer is streaming.".into(), }); diff --git a/crates/lash-cli/src/startup/plugins.rs b/crates/lash-cli/src/startup/plugins.rs index fb0f9a7..5a9086c 100644 --- a/crates/lash-cli/src/startup/plugins.rs +++ b/crates/lash-cli/src/startup/plugins.rs @@ -136,7 +136,7 @@ pub(super) async fn apply_autonomous_tool_policy( session: &lash::LashSession, ) -> anyhow::Result<()> { let mut snapshot = session.admin().tools().state().await?; - retain_autonomous_tools(&mut snapshot); + apply_autonomous_tool_membership(&mut snapshot); session .admin() .tools() @@ -146,8 +146,18 @@ pub(super) async fn apply_autonomous_tool_policy( Ok(()) } -fn retain_autonomous_tools(snapshot: &mut ToolState) { - snapshot.retain(|_, entry| autonomous_tool_allowed(&entry.manifest().name)); +fn apply_autonomous_tool_membership(snapshot: &mut ToolState) { + let disallowed = snapshot + .iter() + .filter(|(_, entry)| !autonomous_tool_allowed(&entry.manifest().name)) + .map(|(id, _)| id.clone()) + .collect::>(); + + for id in disallowed { + snapshot + .set_membership(&id, false) + .expect("tool id collected from the same snapshot"); + } } pub(crate) fn cli_prompt_config(autonomous: bool, _execution_mode: &ExecutionMode) -> PromptLayer { @@ -381,11 +391,16 @@ mod tests { ToolRegistry::from_tool_provider(Arc::new(CatalogFixtureProvider)).unwrap(); let mut snapshot = tool_registry.export_state(); - retain_autonomous_tools(&mut snapshot); - assert!(snapshot.contains(&lash_core::ToolId::from("tool:read_file"))); - assert!(!snapshot.contains(&lash_core::ToolId::from("tool:ask"))); - assert!(!snapshot.contains(&lash_core::ToolId::from("tool:plan_exit"))); - assert!(!snapshot.contains(&lash_core::ToolId::from("tool:showcase"))); + apply_autonomous_tool_membership(&mut snapshot); + let is_member = |id| { + snapshot + .get(&lash_core::ToolId::from(id)) + .is_some_and(lash_core::ToolStateEntry::is_member) + }; + assert!(is_member("tool:read_file")); + assert!(!is_member("tool:ask")); + assert!(!is_member("tool:plan_exit")); + assert!(!is_member("tool:showcase")); } #[test] diff --git a/crates/lash-cli/src/test_support.rs b/crates/lash-cli/src/test_support.rs index 2519aec..91c6d47 100644 --- a/crates/lash-cli/src/test_support.rs +++ b/crates/lash-cli/src/test_support.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::OnceLock; -use lash_core::{SessionEvent, TurnEvent}; +use lash_core::{SessionStreamEvent, TurnEvent}; use lash_tui::ScreenSnapshot; use lash_tui_extensions::TuiExtensions; use tokio::sync::Mutex; @@ -123,7 +123,7 @@ impl UiHarness { turn } - pub(crate) fn dispatch_event(&mut self, event: SessionEvent) -> &mut Self { + pub(crate) fn dispatch_event(&mut self, event: SessionStreamEvent) -> &mut Self { let effects = test_session_event_to_turn_event(&event) .map(|event| self.ui_extensions.effects_for_turn_event(&event)) .unwrap_or_default(); @@ -172,14 +172,14 @@ impl UiHarness { } } -fn test_session_event_to_turn_event(event: &SessionEvent) -> Option { +fn test_session_event_to_turn_event(event: &SessionStreamEvent) -> Option { match event { - SessionEvent::LlmRequest { + SessionStreamEvent::LlmRequest { protocol_iteration, .. } => Some(TurnEvent::ModelRequestStarted { protocol_iteration: *protocol_iteration, }), - SessionEvent::ToolCall { + SessionStreamEvent::ToolCall { call_id, name, args, @@ -194,7 +194,7 @@ fn test_session_event_to_turn_event(event: &SessionEvent) -> Option { graph_key: None, parent_call_id: None, }), - SessionEvent::PluginEvent { plugin_id, event } => Some(TurnEvent::PluginRuntime { + SessionStreamEvent::PluginEvent { plugin_id, event } => Some(TurnEvent::PluginRuntime { plugin_id: plugin_id.clone(), event: event.clone(), }), @@ -205,7 +205,7 @@ fn test_session_event_to_turn_event(event: &SessionEvent) -> Option { #[cfg(test)] mod tests { use super::*; - use lash_core::{PluginRuntimeEvent, SessionEvent}; + use lash_core::{PluginRuntimeEvent, SessionStreamEvent}; use serde_json::json; fn line_index_containing(lines: &[String], needle: &str) -> Option { @@ -217,12 +217,12 @@ mod tests { let mut harness = UiHarness::new(50, 14); harness.user_turn("write a poem"); harness.start_turn(); - harness.dispatch_event(SessionEvent::LlmRequest { + harness.dispatch_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 1, tool_list: "read_file, shell".to_string(), }); - harness.dispatch_event(SessionEvent::TextDelta { + harness.dispatch_event(SessionStreamEvent::TextDelta { content: "A short line.\nAnother line.".to_string(), }); @@ -265,7 +265,7 @@ mod tests { fn ui_harness_renders_inline_ask_panel_end_to_end() { let mut harness = UiHarness::new(68, 20); harness.user_turn("ask me"); - harness.dispatch_event(SessionEvent::ToolCall { + harness.dispatch_event(SessionStreamEvent::ToolCall { call_id: Some("call-1".into()), name: "ask".into(), args: json!({ @@ -333,7 +333,7 @@ mod tests { harness .app .cache_draft_presentation(PreparedTurn::new("follow up now".into(), Vec::new())); - harness.dispatch_event(SessionEvent::InjectedTurnInputAccepted { + harness.dispatch_event(SessionStreamEvent::InjectedTurnInputAccepted { inputs: vec![lash_core::AcceptedInjectedTurnInput { id: None, message: lash_core::PluginMessage::text( @@ -343,7 +343,7 @@ mod tests { }], checkpoint: lash_core::CheckpointKind::AfterWork, }); - harness.dispatch_event(SessionEvent::TextDelta { + harness.dispatch_event(SessionStreamEvent::TextDelta { content: "I saw the follow up.".into(), }); @@ -362,7 +362,7 @@ mod tests { #[test] fn ui_harness_renders_plugin_panel_and_mode_indicator_end_to_end() { let mut harness = UiHarness::new(72, 20); - harness.dispatch_event(SessionEvent::PluginEvent { + harness.dispatch_event(SessionStreamEvent::PluginEvent { plugin_id: "plan_mode".into(), event: PluginRuntimeEvent::Custom { name: "plan_mode.state".into(), @@ -396,19 +396,19 @@ mod tests { let mut harness = UiHarness::new(36, 12); harness.user_turn("write lines"); harness.start_turn(); - harness.dispatch_event(SessionEvent::LlmRequest { + harness.dispatch_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 1, tool_list: "read_file".into(), }); - harness.dispatch_event(SessionEvent::TextDelta { + harness.dispatch_event(SessionStreamEvent::TextDelta { content: (0..10) .map(|idx| format!("line {idx}")) .collect::>() .join("\n"), }); harness.render(); - harness.dispatch_event(SessionEvent::TextDelta { + harness.dispatch_event(SessionStreamEvent::TextDelta { content: (10..18) .map(|idx| format!("\nline {idx}")) .collect::(), @@ -433,7 +433,7 @@ mod tests { .cloned() .expect("older visible line after scrolling"); - harness.dispatch_event(SessionEvent::TextDelta { + harness.dispatch_event(SessionStreamEvent::TextDelta { content: "\nline 18\nline 19".into(), }); let paused = harness.render().visible_lines_trimmed(); @@ -454,7 +454,7 @@ mod tests { let mut harness = UiHarness::new(44, 12); harness.user_turn("write a very long answer please"); harness.start_turn(); - harness.dispatch_event(SessionEvent::LlmRequest { + harness.dispatch_event(SessionStreamEvent::LlmRequest { protocol_iteration: 0, message_count: 1, tool_list: String::new(), @@ -467,7 +467,7 @@ mod tests { .any(|line| line.contains("write a very long answer please")) ); - harness.dispatch_event(SessionEvent::TextDelta { + harness.dispatch_event(SessionStreamEvent::TextDelta { content: "first line\nsecond line\nthird line\nfourth line\nfifth line\nsixth line" .into(), }); @@ -481,7 +481,7 @@ mod tests { "prompt context should stay visible above first output" ); - harness.dispatch_event(SessionEvent::TextDelta { + harness.dispatch_event(SessionStreamEvent::TextDelta { content: "\nseventh line\neighth line\nninth line\ntenth line".into(), }); let tailed = harness.render().visible_lines_trimmed();