diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ad3fbd75ff28..1ea17ac027d1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -95,7 +95,7 @@ jobs:
# play nicely with hermit-managed rust
hermit uninstall rustup
export CARGO_INCREMENTAL=0
- cargo clippy --all-targets -- -D warnings
+ cargo clippy --workspace --all-targets --exclude v8 -- -D warnings
- name: Check for banned TLS crates
run: ./scripts/check-no-native-tls.sh
diff --git a/.gitignore b/.gitignore
index 3704de6ad1eb..15fb5c318367 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ run_cli.sh
tokenizer_files/
.DS_Store
.idea
+.vscode
*.log
tmp/
@@ -22,17 +23,24 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
+# UI
+node_modules
+./ui/desktop/out
+
+# Generated goose DLLs (built at build time, not checked in)
+ui/desktop/src/bin/goose_ffi.dll
+ui/desktop/src/bin/goose_llm.dll
+
# Hermit
.hermit/
# Claude
-.claude/
+.claude
debug_*.txt
# Docs
# Dependencies
-/node_modules
# Production
/build
@@ -46,9 +54,12 @@ benchmark-*
benchconf.json
scripts/fake.sh
do_not_version/
+/ui/desktop/src/bin/temporal
/temporal-service/temporal.db
+/ui/desktop/src/bin/temporal.db
/temporal.db
-
+/ui/desktop/src/bin/goose-scheduler-executor
+/ui/desktop/src/bin/goose
/.env
/working_dir
@@ -66,6 +77,3 @@ result
# Goose self-test artifacts
gooseselftest/
.tasks/
-
-**/tests/**/*.txt
-**/*tests*.new
diff --git a/Cargo.lock b/Cargo.lock
index 89ab7be37850..ffb1b5291878 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,6 +8,16 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
[[package]]
name = "aes"
version = "0.8.4"
@@ -27,12 +37,23 @@ checksum = "44bc1fef9c32f03bce2ab44af35b6f483bfd169bf55cc59beeb2e3b1a00ae4d1"
dependencies = [
"anyhow",
"derive_more",
- "schemars",
+ "schemars 1.2.1",
"serde",
"serde_json",
"strum",
]
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom 0.2.17",
+ "once_cell",
+ "version_check",
+]
+
[[package]]
name = "ahash"
version = "0.8.12"
@@ -187,6 +208,45 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+[[package]]
+name = "asn1-rs"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom 7.1.3",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror 2.0.18",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "assert-json-diff"
version = "2.0.2"
@@ -209,6 +269,17 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "async-recursion"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "async-stream"
version = "0.3.6"
@@ -305,12 +376,27 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "aws-lc-fips-sys"
+version = "0.13.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8bce4948d2520386c6d92a6ea2d472300257702242e5a1d01d6add52bd2e7c1"
+dependencies = [
+ "bindgen",
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+ "regex",
+]
+
[[package]]
name = "aws-lc-rs"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf"
dependencies = [
+ "aws-lc-fips-sys",
"aws-lc-sys",
"untrusted 0.7.1",
"zeroize",
@@ -322,6 +408,7 @@ version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e"
dependencies = [
+ "bindgen",
"cc",
"cmake",
"dunce",
@@ -827,6 +914,18 @@ dependencies = [
"tower-service",
]
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
[[package]]
name = "base64"
version = "0.21.7"
@@ -871,8 +970,8 @@ dependencies = [
"encoding_rs",
"flate2",
"globset",
- "indexmap",
- "itertools",
+ "indexmap 2.13.0",
+ "itertools 0.14.0",
"nu-ansi-term",
"once_cell",
"path_abs",
@@ -905,6 +1004,26 @@ dependencies = [
"serde",
]
+[[package]]
+name = "bindgen"
+version = "0.72.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
+dependencies = [
+ "bitflags 2.11.0",
+ "cexpr",
+ "clang-sys",
+ "itertools 0.13.0",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash 2.1.1",
+ "shlex",
+ "syn 2.0.117",
+]
+
[[package]]
name = "bit-set"
version = "0.5.3"
@@ -1140,6 +1259,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom 7.1.3",
+]
+
[[package]]
name = "cfb"
version = "0.10.0"
@@ -1196,6 +1324,18 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
+ "zeroize",
+]
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
]
[[package]]
@@ -1433,6 +1573,26 @@ dependencies = [
"tiny-keccak",
]
+[[package]]
+name = "const_format"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@@ -1650,6 +1810,18 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "crypto-common"
version = "0.1.7"
@@ -1660,6 +1832,21 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "crypto_secretbox"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1"
+dependencies = [
+ "aead",
+ "cipher",
+ "generic-array",
+ "poly1305",
+ "salsa20",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "ctor"
version = "0.2.9"
@@ -1670,6 +1857,43 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core 0.20.11",
+ "darling_macro 0.20.11",
+]
+
[[package]]
name = "darling"
version = "0.21.3"
@@ -1690,6 +1914,20 @@ dependencies = [
"darling_macro 0.23.0",
]
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.117",
+]
+
[[package]]
name = "darling_core"
version = "0.21.3"
@@ -1717,6 +1955,17 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core 0.20.11",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "darling_macro"
version = "0.21.3"
@@ -1799,6 +2048,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
+[[package]]
+name = "decoded-char"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3"
+
[[package]]
name = "der"
version = "0.7.10"
@@ -1806,10 +2061,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
+ "der_derive",
+ "flagset",
"pem-rfc7468",
"zeroize",
]
+[[package]]
+name = "der-parser"
+version = "10.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom 7.1.3",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
+[[package]]
+name = "der_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "deranged"
version = "0.5.8"
@@ -1817,6 +2099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
+ "serde_core",
]
[[package]]
@@ -1830,6 +2113,37 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "derive_builder"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+dependencies = [
+ "darling 0.20.11",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+dependencies = [
+ "derive_builder_core",
+ "syn 2.0.117",
+]
+
[[package]]
name = "derive_more"
version = "2.1.1"
@@ -1991,40 +2305,100 @@ dependencies = [
]
[[package]]
-name = "either"
-version = "1.15.0"
+name = "ecdsa"
+version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
- "serde",
+ "der",
+ "digest",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
]
[[package]]
-name = "email_address"
-version = "0.2.9"
+name = "ed25519"
+version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
- "serde",
+ "pkcs8",
+ "signature",
]
[[package]]
-name = "encode_unicode"
-version = "1.0.0"
+name = "ed25519-dalek"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "rand_core 0.6.4",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
[[package]]
-name = "encoding_rs"
-version = "0.8.35"
+name = "either"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
dependencies = [
- "cfg-if",
+ "serde",
]
[[package]]
-name = "endian-type"
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest",
+ "ff",
+ "generic-array",
+ "group",
+ "hkdf",
+ "pem-rfc7468",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "email_address"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[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 = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
@@ -2035,6 +2409,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcb71a32ab9582e2756554e84b24aee90d7187034049c35921ec3296b70c13ad"
+[[package]]
+name = "env_home"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -2183,6 +2563,22 @@ dependencies = [
"simd-adler32",
]
+[[package]]
+name = "ff"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+dependencies = [
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
[[package]]
name = "filetime"
version = "0.2.27"
@@ -2206,6 +2602,12 @@ version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
+[[package]]
+name = "flagset"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
+
[[package]]
name = "flate2"
version = "1.1.9"
@@ -2465,6 +2867,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
+ "zeroize",
]
[[package]]
@@ -2517,6 +2920,18 @@ dependencies = [
"wasip3",
]
+[[package]]
+name = "getset"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912"
+dependencies = [
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "gif"
version = "0.13.3"
@@ -2537,6 +2952,12 @@ dependencies = [
"weezl",
]
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
[[package]]
name = "globset"
version = "0.4.18"
@@ -2550,6 +2971,17 @@ dependencies = [
"regex-syntax",
]
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
[[package]]
name = "h2"
version = "0.3.27"
@@ -2562,7 +2994,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
- "indexmap",
+ "indexmap 2.13.0",
"slab",
"tokio",
"tokio-util",
@@ -2581,7 +3013,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.4.0",
- "indexmap",
+ "indexmap 2.13.0",
"slab",
"tokio",
"tokio-util",
@@ -2599,6 +3031,15 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash 0.7.8",
+]
+
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -2717,6 +3158,15 @@ dependencies = [
"itoa",
]
+[[package]]
+name = "http-auth"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "150fa4a9462ef926824cf4519c84ed652ca8f4fbae34cb8af045b5cbcaf98822"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "http-body"
version = "0.4.6"
@@ -3083,6 +3533,17 @@ dependencies = [
"quote",
]
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
[[package]]
name = "indexmap"
version = "2.13.0"
@@ -3180,6 +3641,24 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itertools"
version = "0.14.0"
@@ -3336,6 +3815,37 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "json-number"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479dfd2ad8e4b4ae076b031f72ef2f3791f65e2a0f51e5f3408dbf716c4c2f82"
+dependencies = [
+ "lexical",
+ "ryu-js",
+ "serde",
+ "smallvec",
+]
+
+[[package]]
+name = "json-syntax"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "044a68aba3f96d712f492b72be25e10f96201eaaca3207a7d6e68d6d5105fda9"
+dependencies = [
+ "decoded-char",
+ "hashbrown 0.12.3",
+ "indexmap 1.9.3",
+ "json-number",
+ "locspan",
+ "locspan-derive",
+ "ryu-js",
+ "serde",
+ "smallstr",
+ "smallvec",
+ "utf8-decode",
+]
+
[[package]]
name = "json5"
version = "0.4.1"
@@ -3363,7 +3873,7 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d"
dependencies = [
- "ahash",
+ "ahash 0.8.12",
"base64 0.22.1",
"bytecount",
"email_address",
@@ -3401,6 +3911,21 @@ dependencies = [
"simple_asn1",
]
+[[package]]
+name = "jwt"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
+dependencies = [
+ "base64 0.13.1",
+ "crypto-common",
+ "digest",
+ "hmac",
+ "serde",
+ "serde_json",
+ "sha2",
+]
+
[[package]]
name = "keyring"
version = "3.6.3"
@@ -3431,7 +3956,7 @@ name = "leaf"
version = "1.28.0"
dependencies = [
"agent-client-protocol-schema",
- "ahash",
+ "ahash 0.8.12",
"anyhow",
"async-stream",
"async-trait",
@@ -3455,7 +3980,7 @@ dependencies = [
"futures",
"ignore",
"include_dir",
- "indexmap",
+ "indexmap 2.13.0",
"indoc",
"insta",
"jsonschema",
@@ -3482,7 +4007,7 @@ dependencies = [
"reqwest 0.13.2",
"rmcp 1.2.0",
"sacp",
- "schemars",
+ "schemars 1.2.1",
"serde",
"serde_json",
"serde_urlencoded",
@@ -3523,7 +4048,7 @@ dependencies = [
"uuid",
"v_htmlescape",
"webbrowser",
- "which",
+ "which 8.0.2",
"winapi",
"wiremock",
"zip 0.6.6",
@@ -3549,7 +4074,7 @@ dependencies = [
"regex",
"rmcp 1.2.0",
"sacp",
- "schemars",
+ "schemars 1.2.1",
"serde",
"serde_json",
"strum",
@@ -3606,7 +4131,9 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
+ "sha2",
"shlex",
+ "sigstore-verification",
"strum",
"tar",
"tempfile",
@@ -3639,7 +4166,7 @@ dependencies = [
"once_cell",
"reqwest 0.13.2",
"rmcp 1.2.0",
- "schemars",
+ "schemars 1.2.1",
"serde",
"serde_json",
"shell-words",
@@ -3730,6 +4257,72 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
+[[package]]
+name = "lexical"
+version = "7.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc8a009b2ff1f419ccc62706f04fe0ca6e67b37460513964a3dfdb919bb37d6"
+dependencies = [
+ "lexical-core",
+]
+
+[[package]]
+name = "lexical-core"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594"
+dependencies = [
+ "lexical-parse-float",
+ "lexical-parse-integer",
+ "lexical-util",
+ "lexical-write-float",
+ "lexical-write-integer",
+]
+
+[[package]]
+name = "lexical-parse-float"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56"
+dependencies = [
+ "lexical-parse-integer",
+ "lexical-util",
+]
+
+[[package]]
+name = "lexical-parse-integer"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34"
+dependencies = [
+ "lexical-util",
+]
+
+[[package]]
+name = "lexical-util"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17"
+
+[[package]]
+name = "lexical-write-float"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361"
+dependencies = [
+ "lexical-util",
+ "lexical-write-integer",
+]
+
+[[package]]
+name = "lexical-write-integer"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df"
+dependencies = [
+ "lexical-util",
+]
+
[[package]]
name = "libc"
version = "0.2.183"
@@ -3746,6 +4339,16 @@ dependencies = [
"pkg-config",
]
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
+
[[package]]
name = "libm"
version = "0.2.16"
@@ -3802,6 +4405,24 @@ dependencies = [
"scopeguard",
]
+[[package]]
+name = "locspan"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33890449fcfac88e94352092944bf321f55e5deb4e289a6f51c87c55731200a0"
+
+[[package]]
+name = "locspan-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88991223b049a3d29ca1f60c05639581336a0f3ee4bf8a659dddecc11c4961a"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "log"
version = "0.4.29"
@@ -3821,12 +4442,12 @@ dependencies = [
"ecb",
"encoding_rs",
"flate2",
- "indexmap",
+ "indexmap 2.13.0",
"itoa",
"jiff",
"log",
"md-5",
- "nom",
+ "nom 8.0.0",
"nom_locate",
"rand 0.9.2",
"rangemap",
@@ -3923,6 +4544,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -3980,6 +4607,12 @@ dependencies = [
"pxfm",
]
+[[package]]
+name = "multimap"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
+
[[package]]
name = "nanoid"
version = "0.4.0"
@@ -4028,6 +4661,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
[[package]]
name = "nom"
version = "8.0.0"
@@ -4045,7 +4688,7 @@ checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
dependencies = [
"bytecount",
"memchr",
- "nom",
+ "nom 8.0.0",
]
[[package]]
@@ -4225,6 +4868,69 @@ dependencies = [
"objc2",
]
+[[package]]
+name = "oci-client"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b74df13319e08bc386d333d3dc289c774c88cc543cae31f5347db07b5ec2172"
+dependencies = [
+ "bytes",
+ "chrono",
+ "futures-util",
+ "http 1.4.0",
+ "http-auth",
+ "jwt",
+ "lazy_static",
+ "oci-spec",
+ "olpc-cjson",
+ "regex",
+ "reqwest 0.12.28",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "unicase",
+]
+
+[[package]]
+name = "oci-spec"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc3da52b83ce3258fbf29f66ac784b279453c2ac3c22c5805371b921ede0d308"
+dependencies = [
+ "const_format",
+ "derive_builder",
+ "getset",
+ "regex",
+ "serde",
+ "serde_json",
+ "strum",
+ "strum_macros",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "oid-registry"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
+dependencies = [
+ "asn1-rs",
+]
+
+[[package]]
+name = "olpc-cjson"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "696183c9b5fe81a7715d074fd632e8bd46f4ccc0231a3ed7fc580a80de5f7083"
+dependencies = [
+ "serde",
+ "serde_json",
+ "unicode-normalization",
+]
+
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -4259,6 +4965,12 @@ dependencies = [
"pkg-config",
]
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
[[package]]
name = "open"
version = "5.3.3"
@@ -4270,6 +4982,37 @@ dependencies = [
"pathdiff",
]
+[[package]]
+name = "openidconnect"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2"
+dependencies = [
+ "base64 0.21.7",
+ "chrono",
+ "dyn-clone",
+ "ed25519-dalek",
+ "hmac",
+ "http 1.4.0",
+ "itertools 0.10.5",
+ "log",
+ "oauth2",
+ "p256",
+ "p384",
+ "rand 0.8.5",
+ "rsa",
+ "serde",
+ "serde-value",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_plain",
+ "serde_with",
+ "sha2",
+ "subtle",
+ "thiserror 1.0.69",
+ "url",
+]
+
[[package]]
name = "openssl"
version = "0.10.76"
@@ -4370,7 +5113,7 @@ dependencies = [
"opentelemetry-http",
"opentelemetry-proto",
"opentelemetry_sdk",
- "prost",
+ "prost 0.14.3",
"reqwest 0.13.2",
"thiserror 2.0.18",
]
@@ -4382,7 +5125,7 @@ source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=345cd74a#
dependencies = [
"opentelemetry",
"opentelemetry_sdk",
- "prost",
+ "prost 0.14.3",
"tonic",
"tonic-prost",
]
@@ -4419,6 +5162,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "ordered-multimap"
version = "0.7.3"
@@ -4435,6 +5187,30 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
+[[package]]
+name = "p256"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
+[[package]]
+name = "p384"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
[[package]]
name = "parking"
version = "2.2.1"
@@ -4475,6 +5251,17 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "password-hash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
[[package]]
name = "pastey"
version = "0.2.1"
@@ -4504,10 +5291,20 @@ checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
- "password-hash",
+ "password-hash 0.4.2",
"sha2",
]
+[[package]]
+name = "pbkdf2"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+dependencies = [
+ "digest",
+ "hmac",
+]
+
[[package]]
name = "pem"
version = "3.0.6"
@@ -4576,6 +5373,16 @@ dependencies = [
"sha2",
]
+[[package]]
+name = "petgraph"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
+dependencies = [
+ "fixedbitset",
+ "indexmap 2.13.0",
+]
+
[[package]]
name = "pin-project"
version = "1.1.11"
@@ -4619,6 +5426,21 @@ dependencies = [
"spki",
]
+[[package]]
+name = "pkcs5"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
+dependencies = [
+ "aes",
+ "cbc",
+ "der",
+ "pbkdf2 0.12.2",
+ "scrypt",
+ "sha2",
+ "spki",
+]
+
[[package]]
name = "pkcs8"
version = "0.10.2"
@@ -4626,6 +5448,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
+ "pkcs5",
+ "rand_core 0.6.4",
"spki",
]
@@ -4648,7 +5472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
dependencies = [
"base64 0.22.1",
- "indexmap",
+ "indexmap 2.13.0",
"quick-xml 0.38.4",
"serde",
"time",
@@ -4680,6 +5504,17 @@ dependencies = [
"miniz_oxide",
]
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures 0.2.17",
+ "opaque-debug",
+ "universal-hash",
+]
+
[[package]]
name = "portable-atomic"
version = "1.13.1"
@@ -4755,6 +5590,15 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "primeorder"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
+dependencies = [
+ "elliptic-curve",
+]
+
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -4779,6 +5623,28 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -4795,13 +5661,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55"
dependencies = [
"futures",
- "indexmap",
+ "indexmap 2.13.0",
"nix 0.31.2",
"tokio",
"tracing",
"windows 0.62.2",
]
+[[package]]
+name = "prost"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
+dependencies = [
+ "bytes",
+ "prost-derive 0.13.5",
+]
+
[[package]]
name = "prost"
version = "0.14.3"
@@ -4809,7 +5685,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
dependencies = [
"bytes",
- "prost-derive",
+ "prost-derive 0.14.3",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
+dependencies = [
+ "heck",
+ "itertools 0.14.0",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost 0.13.5",
+ "prost-types",
+ "regex",
+ "syn 2.0.117",
+ "tempfile",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
+dependencies = [
+ "anyhow",
+ "itertools 0.14.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
]
[[package]]
@@ -4819,12 +5728,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
dependencies = [
"anyhow",
- "itertools",
+ "itertools 0.14.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "prost-reflect"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5edd582b62f5cde844716e66d92565d7faf7ab1445c8cebce6e00fba83ddb2"
+dependencies = [
+ "base64 0.22.1",
+ "once_cell",
+ "prost 0.13.5",
+ "prost-reflect-derive 0.14.0",
+ "prost-types",
+ "serde",
+ "serde-value",
+]
+
+[[package]]
+name = "prost-reflect"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37587d5a8a1b3dc9863403d084fc2254b91ab75a702207098837950767e2260b"
+dependencies = [
+ "prost 0.13.5",
+ "prost-reflect-derive 0.15.1",
+ "prost-types",
+]
+
+[[package]]
+name = "prost-reflect-build"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8db7191445b1dbee19df4f6b6294e5123aef52620b344a630bb845d302622a"
+dependencies = [
+ "prost-build",
+ "prost-reflect 0.15.3",
+]
+
+[[package]]
+name = "prost-reflect-derive"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4fce6b22f15cc8d8d400a2b98ad29202b33bd56c7d9ddd815bc803a807ecb65"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "prost-reflect-derive"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab076798900edeaf1499ed1c30097db86e6697c5d02660a63d72fe4ebdcfefd2"
+dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
+[[package]]
+name = "prost-types"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
+dependencies = [
+ "prost 0.13.5",
+]
+
[[package]]
name = "psl-types"
version = "2.0.11"
@@ -5168,7 +6144,7 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e"
dependencies = [
- "ahash",
+ "ahash 0.8.12",
"fluent-uri",
"once_cell",
"parking_lot",
@@ -5230,6 +6206,7 @@ dependencies = [
"hyper-util",
"js-sys",
"log",
+ "mime_guess",
"percent-encoding",
"pin-project-lite",
"quinn",
@@ -5241,12 +6218,14 @@ dependencies = [
"sync_wrapper",
"tokio",
"tokio-rustls 0.26.4",
+ "tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
+ "wasm-streams 0.4.2",
"web-sys",
"webpki-roots 1.0.6",
]
@@ -5295,10 +6274,20 @@ dependencies = [
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
- "wasm-streams",
+ "wasm-streams 0.5.0",
"web-sys",
]
+[[package]]
+name = "rfc6979"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+dependencies = [
+ "hmac",
+ "subtle",
+]
+
[[package]]
name = "rgb"
version = "0.8.53"
@@ -5335,7 +6324,7 @@ dependencies = [
"pastey",
"pin-project-lite",
"rmcp-macros 0.12.0",
- "schemars",
+ "schemars 1.2.1",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -5365,7 +6354,7 @@ dependencies = [
"rand 0.10.0",
"reqwest 0.13.2",
"rmcp-macros 1.2.0",
- "schemars",
+ "schemars 1.2.1",
"serde",
"serde_json",
"sse-stream",
@@ -5476,6 +6465,15 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom 7.1.3",
+]
+
[[package]]
name = "rustix"
version = "1.1.4"
@@ -5622,6 +6620,12 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+[[package]]
+name = "ryu-js"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f"
+
[[package]]
name = "sacp"
version = "10.1.0"
@@ -5637,7 +6641,7 @@ dependencies = [
"jsonrpcmsg",
"rmcp 0.12.0",
"sacp-derive",
- "schemars",
+ "schemars 1.2.1",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -5658,6 +6662,15 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "same-file"
version = "1.0.6"
@@ -5680,9 +6693,21 @@ dependencies = [
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
dependencies = [
- "windows-sys 0.61.2",
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
]
[[package]]
@@ -5717,6 +6742,18 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "scrypt"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+dependencies = [
+ "password-hash 0.5.0",
+ "pbkdf2 0.12.2",
+ "salsa20",
+ "sha2",
+]
+
[[package]]
name = "sct"
version = "0.7.1"
@@ -5733,6 +6770,20 @@ version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "security-framework"
version = "2.11.1"
@@ -5803,6 +6854,16 @@ dependencies = [
"typeid",
]
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
[[package]]
name = "serde_core"
version = "1.0.228"
@@ -5840,7 +6901,7 @@ version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
- "indexmap",
+ "indexmap 2.13.0",
"itoa",
"memchr",
"serde",
@@ -5859,6 +6920,26 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde_plain"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "serde_spanned"
version = "1.0.4"
@@ -5886,8 +6967,17 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9"
dependencies = [
+ "base64 0.22.1",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.13.0",
+ "schemars 0.9.0",
+ "schemars 1.2.1",
"serde_core",
+ "serde_json",
"serde_with_macros",
+ "time",
]
[[package]]
@@ -5908,7 +6998,7 @@ version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
- "indexmap",
+ "indexmap 2.13.0",
"itoa",
"ryu",
"serde",
@@ -6013,6 +7103,116 @@ dependencies = [
"rand_core 0.6.4",
]
+[[package]]
+name = "sigstore"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43427f0d642cfed11bd596608148ee4476dd75f938888aa13a9c4e176fe14225"
+dependencies = [
+ "async-trait",
+ "aws-lc-rs",
+ "base64 0.22.1",
+ "cfg-if",
+ "chrono",
+ "const-oid",
+ "crypto_secretbox",
+ "digest",
+ "ecdsa",
+ "ed25519",
+ "ed25519-dalek",
+ "elliptic-curve",
+ "futures",
+ "futures-util",
+ "hex",
+ "json-syntax",
+ "oci-client",
+ "olpc-cjson",
+ "openidconnect",
+ "p256",
+ "p384",
+ "pem",
+ "pkcs1",
+ "pkcs8",
+ "rand 0.8.5",
+ "regex",
+ "reqwest 0.12.28",
+ "ring",
+ "rsa",
+ "rustls-pki-types",
+ "rustls-webpki 0.103.9",
+ "scrypt",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serde_with",
+ "sha2",
+ "signature",
+ "sigstore_protobuf_specs",
+ "thiserror 2.0.18",
+ "tls_codec",
+ "tokio",
+ "tokio-util",
+ "tough",
+ "tracing",
+ "url",
+ "webbrowser",
+ "x509-cert",
+ "zeroize",
+]
+
+[[package]]
+name = "sigstore-protobuf-specs-derive"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80baa401f274093f7bb27d7a69d6139cbc11f1b97624e9a61a9b3ea32c776a35"
+dependencies = [
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "sigstore-verification"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db10507b7d2ff109e56bfd885ce7b489a16040996516020ecfb43ced017a7a47"
+dependencies = [
+ "async-trait",
+ "base64 0.22.1",
+ "ed25519-dalek",
+ "hex",
+ "log",
+ "p256",
+ "p384",
+ "reqwest 0.12.28",
+ "serde",
+ "serde_json",
+ "sha2",
+ "signature",
+ "sigstore",
+ "thiserror 2.0.18",
+ "tokio",
+ "x509-parser",
+]
+
+[[package]]
+name = "sigstore_protobuf_specs"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799e5ed827a6d8d2be7fc598515d061b59d85f496d7066152822a80f3250af74"
+dependencies = [
+ "anyhow",
+ "glob",
+ "prost 0.13.5",
+ "prost-build",
+ "prost-reflect 0.14.7",
+ "prost-reflect-build",
+ "prost-types",
+ "serde",
+ "serde_json",
+ "sigstore-protobuf-specs-derive",
+ "which 7.0.3",
+]
+
[[package]]
name = "simd-adler32"
version = "0.3.8"
@@ -6059,6 +7259,16 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+[[package]]
+name = "smallstr"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862077b1e764f04c251fe82a2ef562fd78d7cadaeb072ca7c2bcaf7217b1ff3b"
+dependencies = [
+ "serde",
+ "smallvec",
+]
+
[[package]]
name = "smallvec"
version = "1.15.1"
@@ -6074,6 +7284,29 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
+[[package]]
+name = "snafu"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2"
+dependencies = [
+ "futures-core",
+ "pin-project",
+ "snafu-derive",
+]
+
+[[package]]
+name = "snafu-derive"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "socket2"
version = "0.5.10"
@@ -6145,7 +7378,7 @@ dependencies = [
"futures-util",
"hashbrown 0.15.5",
"hashlink",
- "indexmap",
+ "indexmap 2.13.0",
"log",
"memchr",
"once_cell",
@@ -6389,6 +7622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
+ "quote",
"unicode-ident",
]
@@ -6739,6 +7973,27 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+[[package]]
+name = "tls_codec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
+dependencies = [
+ "tls_codec_derive",
+ "zeroize",
+]
+
+[[package]]
+name = "tls_codec_derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "tokio"
version = "1.50.0"
@@ -6850,7 +8105,7 @@ version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
- "indexmap",
+ "indexmap 2.13.0",
"serde_core",
"serde_spanned",
"toml_datetime",
@@ -6911,10 +8166,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309"
dependencies = [
"bytes",
- "prost",
+ "prost 0.14.3",
"tonic",
]
+[[package]]
+name = "tough"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88d0ee9525696569cc2af5d46f8a739028c0268895071e0386957195b0c9161"
+dependencies = [
+ "async-recursion",
+ "async-trait",
+ "aws-lc-rs",
+ "bytes",
+ "chrono",
+ "dyn-clone",
+ "futures",
+ "futures-core",
+ "globset",
+ "hex",
+ "log",
+ "olpc-cjson",
+ "pem",
+ "percent-encoding",
+ "reqwest 0.12.28",
+ "rustls 0.23.37",
+ "serde",
+ "serde_json",
+ "serde_plain",
+ "snafu",
+ "tempfile",
+ "tokio",
+ "tokio-util",
+ "typed-path 0.9.3",
+ "untrusted 0.7.1",
+ "url",
+ "walkdir",
+]
+
[[package]]
name = "tower"
version = "0.5.3"
@@ -7217,6 +8507,12 @@ dependencies = [
"utf-8",
]
+[[package]]
+name = "typed-path"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82205ffd44a9697e34fc145491aa47310f9871540bb7909eaa9365e0a9a46607"
+
[[package]]
name = "typed-path"
version = "0.12.3"
@@ -7248,7 +8544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "408c7e039c96ec1d517a1111ade7fadab889f32c096dac691a1e3b8018c3e39a"
dependencies = [
"aes",
- "ahash",
+ "ahash 0.8.12",
"base64 0.22.1",
"byteorder",
"cbc",
@@ -7333,6 +8629,16 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -7376,6 +8682,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+[[package]]
+name = "utf8-decode"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498"
+
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -7394,7 +8706,7 @@ version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
dependencies = [
- "indexmap",
+ "indexmap 2.13.0",
"serde",
"serde_json",
"utoipa-gen",
@@ -7590,11 +8902,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
- "indexmap",
+ "indexmap 2.13.0",
"wasm-encoder",
"wasmparser",
]
+[[package]]
+name = "wasm-streams"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "wasm-streams"
version = "0.5.0"
@@ -7616,7 +8941,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags 2.11.0",
"hashbrown 0.15.5",
- "indexmap",
+ "indexmap 2.13.0",
"semver",
]
@@ -7689,6 +9014,18 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+[[package]]
+name = "which"
+version = "7.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
+dependencies = [
+ "either",
+ "env_home",
+ "rustix",
+ "winsafe",
+]
+
[[package]]
name = "which"
version = "8.0.2"
@@ -8229,6 +9566,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "winsafe"
+version = "0.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
+
[[package]]
name = "wiremock"
version = "0.6.5"
@@ -8280,7 +9623,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
- "indexmap",
+ "indexmap 2.13.0",
"prettyplease",
"syn 2.0.117",
"wasm-metadata",
@@ -8311,7 +9654,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags 2.11.0",
- "indexmap",
+ "indexmap 2.13.0",
"log",
"serde",
"serde_derive",
@@ -8330,7 +9673,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
- "indexmap",
+ "indexmap 2.13.0",
"log",
"semver",
"serde",
@@ -8346,6 +9689,37 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+[[package]]
+name = "x509-cert"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
+dependencies = [
+ "const-oid",
+ "der",
+ "sha1",
+ "signature",
+ "spki",
+ "tls_codec",
+]
+
+[[package]]
+name = "x509-parser"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom 7.1.3",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror 2.0.18",
+ "time",
+]
+
[[package]]
name = "xattr"
version = "1.6.1"
@@ -8525,7 +9899,7 @@ dependencies = [
"crossbeam-utils",
"flate2",
"hmac",
- "pbkdf2",
+ "pbkdf2 0.11.0",
"sha1",
"time",
"zstd 0.11.2+zstd.1.5.2",
@@ -8542,7 +9916,7 @@ dependencies = [
"crossbeam-utils",
"displaydoc",
"flate2",
- "indexmap",
+ "indexmap 2.13.0",
"memchr",
"thiserror 2.0.18",
"zopfli",
@@ -8556,9 +9930,9 @@ checksum = "b680f2a0cd479b4cff6e1233c483fdead418106eae419dc60200ae9850f6d004"
dependencies = [
"crc32fast",
"flate2",
- "indexmap",
+ "indexmap 2.13.0",
"memchr",
- "typed-path",
+ "typed-path 0.12.3",
"zopfli",
]
diff --git a/README.md b/README.md
index cd2dc4e5c43b..f388136cd3b3 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,138 @@
-goose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.
+Leaf is a **pure CLI AI agent** forked from the [Goose](https://github.com/block/goose) project by Block. While Goose provides both desktop and CLI interfaces, Leaf focuses exclusively on command-line automation for developers who prefer terminal-based workflows.
-Whether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.
+## Key Differences from Goose
-Designed for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.
+- **Pure CLI**: No desktop UI or Electron components - just a fast, terminal-based experience
+- **Lightweight**: Removed V8 dependencies and UI components for a smaller footprint
+- **Agent Protocol (ACP)**: Full support for Agent Client Protocol for multi-agent workflows
+- **MCP Integration**: Seamless integration with Model Context Protocol (MCP) servers
+- **Multi-Model**: Works with any LLM (OpenAI, Anthropic, local models, etc.)
-[](https://youtu.be/D-DpDunrbpo)
+## What Leaf Can Do
-# Quick Links
-- [Quickstart](https://block.github.io/goose/docs/quickstart)
-- [Installation](https://block.github.io/goose/docs/getting-started/installation)
-- [Tutorials](https://block.github.io/goose/docs/category/tutorials)
-- [Documentation](https://block.github.io/goose/docs/category/getting-started)
-- [Governance](https://github.com/block/goose/blob/main/GOVERNANCE.md)
-- [Custom Distributions](https://github.com/block/goose/blob/main/CUSTOM_DISTROS.md) - build your own goose distro with preconfigured providers, extensions, and branding
+Leaf is your on-machine AI agent, capable of automating complex development tasks:
-## Need Help?
-- [Diagnostics & Reporting](https://block.github.io/goose/docs/troubleshooting/diagnostics-and-reporting)
-- [Known Issues](https://block.github.io/goose/docs/troubleshooting/known-issues)
+- **Code Generation**: Build projects from scratch, write and refactor code
+- **Debugging**: Analyze errors and fix issues autonomously
+- **Workflow Automation**: Execute complex multi-step engineering pipelines
+- **External Integrations**: Interact with APIs and external services via MCP
+- **Orchestration**: Delegate tasks to subagents with independent contexts
-# a little goose humor 🪿
+## Quick Start
-> Why did the developer choose goose as their AI agent?
->
-> Because it always helps them "migrate" their code to production! 🚀
+### Installation
-# goose around with us
-- [Discord](https://discord.gg/goose-oss)
-- [YouTube](https://www.youtube.com/@goose-oss)
-- [LinkedIn](https://www.linkedin.com/company/goose-oss)
-- [Twitter/X](https://x.com/goose_oss)
-- [Bluesky](https://bsky.app/profile/opensource.block.xyz)
-- [Nostr](https://njump.me/opensource@block.xyz)
+```bash
+# Build from source
+cargo build --release --package leaf-cli
+
+# The binary will be at:
+./target/release/leaf
+```
+
+### Setup
+
+```bash
+# Configure your provider
+leaf configure
+
+# Start an interactive session
+leaf session
+
+# Or run a recipe
+leaf run --recipe my-recipe.yaml
+```
+
+### Example Usage
+
+```bash
+# Ask Leaf to help with a task
+leaf session
+
+# Run with a specific provider
+leaf session --provider openai
+
+# Use recipes for repeatable workflows
+leaf run --recipe deploy.yaml
+```
+
+## Architecture
+
+```
+crates/
+├── leaf # Core agent logic
+├── leaf-acp # Agent Client Protocol implementation
+├── leaf-cli # Command-line interface
+├── leaf-mcp # Model Context Protocol extensions
+└── leaf-server # ACP server (leafd)
+```
+
+## Project Status
+
+Leaf is a community fork focused on CLI-only workflows. It maintains compatibility with:
+- **MCP Servers**: All standard Model Context Protocol servers
+- **Recipes**: YAML-based automation workflows
+- **Extensions**: Dynamic tool loading via MCP
+
+## Documentation
+
+- [Getting Started](#getting-started) - Installation and first steps
+- [Configuration](#configuration) - Setting up providers and extensions
+- [Recipes](#recipes) - Automation workflows
+- [MCP Extensions](#mcp-extensions) - Available tools and integrations
+
+## Development
+
+```bash
+# Setup
+source bin/activate-hermit
+
+# Build
+cargo build --release
+
+# Test
+cargo test --package leaf-cli
+
+# Lint
+cargo clippy --all-targets -- -D warnings
+cargo fmt
+```
+
+## Contributing
+
+We welcome contributions! Please:
+1. Fork the repository
+2. Create a feature branch
+3. Make your changes
+4. Run tests and linting
+5. Submit a PR
+
+## License
+
+Apache License 2.0 - see [LICENSE](LICENSE) file.
+
+## Acknowledgments
+
+Leaf is a fork of [Goose](https://github.com/block/goose) by Block. We're grateful for the solid foundation they built.
+
+---
+
+
+
+**Pure CLI. Maximum Control. No UI Overhead.**
+
+
diff --git a/crates/leaf-acp/src/transport.rs b/crates/leaf-acp/src/transport.rs
index 632f540bb8e4..130b08e92216 100644
--- a/crates/leaf-acp/src/transport.rs
+++ b/crates/leaf-acp/src/transport.rs
@@ -114,6 +114,7 @@ pub fn create_router(server: Arc) -> Router {
Router::new()
.route("/health", get(health))
+ .route("/status", get(health))
.route(
"/acp",
post(http::handle_post).with_state(http_state.clone()),
diff --git a/crates/leaf-acp/tests/test_data/openai_basic.txt b/crates/leaf-acp/tests/test_data/openai_basic.txt
new file mode 100644
index 000000000000..4c3d0c69a071
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_basic.txt
@@ -0,0 +1,9 @@
+data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":1766229303,"model":"gpt-5-nano","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":1766229303,"model":"gpt-5-nano","choices":[{"index":0,"delta":{"content":"2"},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":1766229303,"model":"gpt-5-nano","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":1766229303,"model":"gpt-5-nano","choices":[],"usage":{"prompt_tokens":100,"completion_tokens":10,"total_tokens":110}}
+
+data: [DONE]
diff --git a/crates/leaf-acp/tests/test_data/openai_builtin_execute.txt b/crates/leaf-acp/tests/test_data/openai_builtin_execute.txt
new file mode 100644
index 000000000000..bf6d35bd7935
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_builtin_execute.txt
@@ -0,0 +1,511 @@
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_HCUq7OYIqj233H77wpqAtSGP","type":"function","function":{"name":"code_execution__execute","arguments":""}}],"refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"XbIx"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"0WAOew1EJA"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"PdQalDVBc"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"SUSOxe2S"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"async"}}]},"finish_reason":null}],"usage":null,"obfuscation":"lGNpX0hf"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" function"}}]},"finish_reason":null}],"usage":null,"obfuscation":"1wCB"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" run"}}]},"finish_reason":null}],"usage":null,"obfuscation":"LNRcwubzP"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"()"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Aq7vRtDvlF4"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" {\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"lb2NCrdyI"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ErsVOWq6Z3Xu"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"qNMOJVTHLUp4"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" //"}}]},"finish_reason":null}],"usage":null,"obfuscation":"6n2KlzcsTp"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Step"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Syxh8KgO"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"LVrPhgFXXORS"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"1"}}]},"finish_reason":null}],"usage":null,"obfuscation":"HJcsbkgldODt"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"meGjHjoA1xft"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Get"}}]},"finish_reason":null}],"usage":null,"obfuscation":"6ue6xAtTS"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"tFmcmr9A"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" from"}}]},"finish_reason":null}],"usage":null,"obfuscation":"YlOG2Pln"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" MCP"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Uxh9l06s6"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" fixture"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Pzqu4"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"kOYakk7vCs"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"Ew0I4Yv7lsZu"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" const"}}]},"finish_reason":null}],"usage":null,"obfuscation":"BwnGXcl"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"DYK3JLN8"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7T6NXmfl7g"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ="}}]},"finish_reason":null}],"usage":null,"obfuscation":"rYrRL7FpIpl"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" await"}}]},"finish_reason":null}],"usage":null,"obfuscation":"aKhInCt"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Mc"}}]},"finish_reason":null}],"usage":null,"obfuscation":"TiUrBUJ3S8"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"p"}}]},"finish_reason":null}],"usage":null,"obfuscation":"rUBZNYrMu5QX"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Fixture"}}]},"finish_reason":null}],"usage":null,"obfuscation":"JAVgEJ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".get"}}]},"finish_reason":null}],"usage":null,"obfuscation":"9SzQLFQkS"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"35KBxpEwO"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"({"}}]},"finish_reason":null}],"usage":null,"obfuscation":"JrhE1JHlm54"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"});"}}]},"finish_reason":null}],"usage":null,"obfuscation":"wohXvQg7Ob"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"lVazpoueAIq"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"j5Kk4o8z8KVq"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"1GWCRFnUwLOm"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" let"}}]},"finish_reason":null}],"usage":null,"obfuscation":"QfhWFYeVO"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"y2sgZpV8"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ="}}]},"finish_reason":null}],"usage":null,"obfuscation":"CSJpY7ykRgo"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ''"}}]},"finish_reason":null}],"usage":null,"obfuscation":"d8X4iNvkfT"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":";\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"SHsYhZpXT5"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"eaM94gaV3SjW"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"oBdwnsKEn6Pe"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" if"}}]},"finish_reason":null}],"usage":null,"obfuscation":"MOdNd1VIdT"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ("}}]},"finish_reason":null}],"usage":null,"obfuscation":"LaNZf51HyNY"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"zpRHAvyvj"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"RiV1yS7ugy"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" !="}}]},"finish_reason":null}],"usage":null,"obfuscation":"4UaTQI4oY6"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" null"}}]},"finish_reason":null}],"usage":null,"obfuscation":"CksUZb6e"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":")"}}]},"finish_reason":null}],"usage":null,"obfuscation":"du3S3fRcNGFJ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" {\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"peNFo8PaO"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"y3HGwbdgGGN2"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"2yRkpk4hHq"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" if"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Fqx4l55lFk"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ("}}]},"finish_reason":null}],"usage":null,"obfuscation":"R3Nn65bbShk"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"typeof"}}]},"finish_reason":null}],"usage":null,"obfuscation":"KNuxb26"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"y3VY7fiz"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"GzFgdFnlEk"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ==="}}]},"finish_reason":null}],"usage":null,"obfuscation":"QZYQNItUp"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" '"}}]},"finish_reason":null}],"usage":null,"obfuscation":"BxFluNL3LiS"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"string"}}]},"finish_reason":null}],"usage":null,"obfuscation":"EYS4Szt"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"')"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ld3QawCCj8j"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" {\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"AhXpCgGSW"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"HR4oVYvrKIp0"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"GRH0AMvt"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"b22NAPWU"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ="}}]},"finish_reason":null}],"usage":null,"obfuscation":"jXZz9g9tus1"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"EzPoaNwG"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"dNp3Z4YURy"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":";\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"SFijuwxEh0"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ta4OC28eSsDQ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"KmW5WLpYfl"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" }"}}]},"finish_reason":null}],"usage":null,"obfuscation":"67HBmLCOU18"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" else"}}]},"finish_reason":null}],"usage":null,"obfuscation":"SWcwdCbt"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" if"}}]},"finish_reason":null}],"usage":null,"obfuscation":"H6DUToAyQ4"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ("}}]},"finish_reason":null}],"usage":null,"obfuscation":"quSQs2d3Leu"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"typeof"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Yth9o7r"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"4VQXKZnb"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Z3wtnBjMxE"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ==="}}]},"finish_reason":null}],"usage":null,"obfuscation":"0TPK99588"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" '"}}]},"finish_reason":null}],"usage":null,"obfuscation":"WY5WPQzxzYm"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"object"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7uUOdgI"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"')"}}]},"finish_reason":null}],"usage":null,"obfuscation":"HvcmQAC33mi"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" {\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"gZ29HXtRa"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"2BVcAHxoKTBJ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"8ZgPj7ct"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ibY235FW"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ="}}]},"finish_reason":null}],"usage":null,"obfuscation":"ZioVR8c4ry5"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"uw5DCPUr"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"easTNJ4HTi"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"2f996DsH"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ??"}}]},"finish_reason":null}],"usage":null,"obfuscation":"antE6IcDfK"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Tb6mqsJT"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"F9SNOWaJ4s"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".content"}}]},"finish_reason":null}],"usage":null,"obfuscation":"AcXac"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ??"}}]},"finish_reason":null}],"usage":null,"obfuscation":"1AJwttqKUC"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"vKazIIc2"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"digOEQ8L8K"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".output"}}]},"finish_reason":null}],"usage":null,"obfuscation":"buFsGY"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ??"}}]},"finish_reason":null}],"usage":null,"obfuscation":"0teoOOb2Ps"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ''"}}]},"finish_reason":null}],"usage":null,"obfuscation":"d8wkQgBHJX"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":";\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7ZG1t8y03p"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"pyBBgSFqywgy"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"j9yOeuhITh"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" }\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"KPPU04Hw7"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"5LX55vkRR8kW"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"PxYDQQzmC8oa"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" }\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ZhtYKzY50"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"lTheErkcOz2I"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"1NujUoinnFdC"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" if"}}]},"finish_reason":null}],"usage":null,"obfuscation":"LnpOMGO1gA"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" (!"}}]},"finish_reason":null}],"usage":null,"obfuscation":"PG0Ukg0lIG"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"seyEOZxMx"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":")"}}]},"finish_reason":null}],"usage":null,"obfuscation":"8HgFZ5FIEiyd"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" {\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"pe3d5DOxz"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"pO4Zjn4nYjUN"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"PwrHo1elqK"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"j5wyQvGu"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ="}}]},"finish_reason":null}],"usage":null,"obfuscation":"ZbcOhXhUldx"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" '//"}}]},"finish_reason":null}],"usage":null,"obfuscation":"quGPXmBjM"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" no"}}]},"finish_reason":null}],"usage":null,"obfuscation":"IAwIEo6A6a"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"h5ERm6Sh"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" returned"}}]},"finish_reason":null}],"usage":null,"obfuscation":"WOaC"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" by"}}]},"finish_reason":null}],"usage":null,"obfuscation":"5gcyoClkTY"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Mc"}}]},"finish_reason":null}],"usage":null,"obfuscation":"0zteWyELKW"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"p"}}]},"finish_reason":null}],"usage":null,"obfuscation":"B5cHXJmChBms"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Fixture"}}]},"finish_reason":null}],"usage":null,"obfuscation":"0Xe1QR"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".get"}}]},"finish_reason":null}],"usage":null,"obfuscation":"2vHYUwQDp"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"9JwUpRtnt"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"()"}}]},"finish_reason":null}],"usage":null,"obfuscation":"1zjaqiXZl2f"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"';"}}]},"finish_reason":null}],"usage":null,"obfuscation":"bYFdw3hFiAw"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"3GhBmzN37pV"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"e5RqErjJCYzD"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"vkgozuoVy8n6"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" }\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"4JNc8Okf2"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"eEpcildyEGUi"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"kZJv2yzDAT"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"NJaDVEBJleAZ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" //"}}]},"finish_reason":null}],"usage":null,"obfuscation":"AkzTH2plV3"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Step"}}]},"finish_reason":null}],"usage":null,"obfuscation":"VGDFoeiS"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"NdzacxSwRwkc"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"2"}}]},"finish_reason":null}],"usage":null,"obfuscation":"dO0VtCqWvSue"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"3EmacbMeJWDm"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"UB5FQGQ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" the"}}]},"finish_reason":null}],"usage":null,"obfuscation":"4KMEtFkPe"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" retrieved"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7mp"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"xFHKGtd8"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" to"}}]},"finish_reason":null}],"usage":null,"obfuscation":"XWtWyabOHo"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" /"}}]},"finish_reason":null}],"usage":null,"obfuscation":"GI413xIkvDP"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tmp"}}]},"finish_reason":null}],"usage":null,"obfuscation":"8Hwwo1OP5F"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/result"}}]},"finish_reason":null}],"usage":null,"obfuscation":"vr2pwB"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".txt"}}]},"finish_reason":null}],"usage":null,"obfuscation":"WcJiQyK6D"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"cR0x5ORkfO"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"g4ZejTLDefCP"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" const"}}]},"finish_reason":null}],"usage":null,"obfuscation":"LKWKME4"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"i4nxJ43"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"hVAkN7PbyW"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" ="}}]},"finish_reason":null}],"usage":null,"obfuscation":"ilkxXcex9UU"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" await"}}]},"finish_reason":null}],"usage":null,"obfuscation":"cYjPDlD"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Developer"}}]},"finish_reason":null}],"usage":null,"obfuscation":"G6t"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"."}}]},"finish_reason":null}],"usage":null,"obfuscation":"OOxdzNJq"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"MiMZRWA"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"({"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7sQdVn1KZH3"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"L6lFRm1PNqG"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Su0Qw704RLRe"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"rkfEW2QLpO"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" path"}}]},"finish_reason":null}],"usage":null,"obfuscation":"jGD2QcOd"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ne7pCHdniACu"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" '/"}}]},"finish_reason":null}],"usage":null,"obfuscation":"A7rPtw8xwm"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tmp"}}]},"finish_reason":null}],"usage":null,"obfuscation":"scv4frvmOL"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/result"}}]},"finish_reason":null}],"usage":null,"obfuscation":"2eELSy"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".txt"}}]},"finish_reason":null}],"usage":null,"obfuscation":"1KSuOUume"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"',"}}]},"finish_reason":null}],"usage":null,"obfuscation":"2bfPK90ZMWR"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"GSiHQg8iM2E"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"56PdyUx8eBvQ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"XurvUHlgwc"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" // command"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ZsYLy"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"PFlue8D49Rzx"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" '"}}]},"finish_reason":null}],"usage":null,"obfuscation":"JUShQEjjAxm"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"MB5hRzt5"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"',"}}]},"finish_reason":null}],"usage":null,"obfuscation":"unqKUoz76aS"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"P39riGC0HKA"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"sJxnV6swTye6"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"xVJI6wFQLA"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" content"}}]},"finish_reason":null}],"usage":null,"obfuscation":"aYkuCMJQ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":""}}]},"finish_reason":null}],"usage":null,"obfuscation":"DQ5IKXUC"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"YaxTVILdGh6I"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"tTzYfOHr"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":",\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"EKsCaMI5g9"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"kKCqlBzwhCq8"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"KLo6QPM5VKk0"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" });"}}]},"finish_reason":null}],"usage":null,"obfuscation":"DNqh4g1Na"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"u1iriNCPeXk"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"PRv5bSbXOVAK"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"zBm488IA2h"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null,"obfuscation":"5BmN6XMBvJK8"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" return"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Jzvgzr"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" {"}}]},"finish_reason":null}],"usage":null,"obfuscation":"dqpuXK9vmsI"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"0QRLI8kv"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":","}}]},"finish_reason":null}],"usage":null,"obfuscation":"1GL4TASp6ipg"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Czgd0iL"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Res"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Bv304pNXQg"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" }"}}]},"finish_reason":null}],"usage":null,"obfuscation":"fZGuBvkzNzz"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":";\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ManURNiZBB"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"hN2gPjbJkn1b"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"}\\"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Hke7vUJzSM"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"n"}}]},"finish_reason":null}],"usage":null,"obfuscation":"naUuxvnxWvXs"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"HYQQNm55"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tool"}}]},"finish_reason":null}],"usage":null,"obfuscation":"lmbBNO1kI"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_graph"}}]},"finish_reason":null}],"usage":null,"obfuscation":"qdrnpGB"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":["}}]},"finish_reason":null}],"usage":null,"obfuscation":"OYQjmFkz5"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"7yVjHnHF8g"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tool"}}]},"finish_reason":null}],"usage":null,"obfuscation":"QEEhae2TJ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"kEukkQGI"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"M"}}]},"finish_reason":null}],"usage":null,"obfuscation":"LYxfiGayx20v"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"cp"}}]},"finish_reason":null}],"usage":null,"obfuscation":"zEf2SuFI3cH"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Fixture"}}]},"finish_reason":null}],"usage":null,"obfuscation":"S2GG1i"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".get"}}]},"finish_reason":null}],"usage":null,"obfuscation":"likS7Y45F"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"TwySl39OJ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"2p2DjdVv"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"description"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Zk"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"ZBeDt6sM"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Get"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7bjcsaItXQ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"LtDIcbNq"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"A1kFK8sA"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"depends"}}]},"finish_reason":null}],"usage":null,"obfuscation":"AyIA9i"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_on"}}]},"finish_reason":null}],"usage":null,"obfuscation":"9F4q7ARQQd"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":[]"}}]},"finish_reason":null}],"usage":null,"obfuscation":"jAVbSgQF"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"},{\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"XUbEQMwJ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tool"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Eehl1Y6Xt"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"T3iH8KKU"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Developer"}}]},"finish_reason":null}],"usage":null,"obfuscation":"jzRU"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"."}}]},"finish_reason":null}],"usage":null,"obfuscation":"zeeCDR1q"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"8YZ1VtI"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"R15EQTSl"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"description"}}]},"finish_reason":null}],"usage":null,"obfuscation":"v8"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"uCS5hMDz"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"b4P9og82"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" code"}}]},"finish_reason":null}],"usage":null,"obfuscation":"CkksRy3s"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" to"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7z4KadqiCw"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" /"}}]},"finish_reason":null}],"usage":null,"obfuscation":"XYeWQ4H9N5X"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tmp"}}]},"finish_reason":null}],"usage":null,"obfuscation":"qADuBhZQgw"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/result"}}]},"finish_reason":null}],"usage":null,"obfuscation":"0tqRXZ"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".txt"}}]},"finish_reason":null}],"usage":null,"obfuscation":"59jHzUi0h"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"QRyYho94"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"depends"}}]},"finish_reason":null}],"usage":null,"obfuscation":"61NYkx"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_on"}}]},"finish_reason":null}],"usage":null,"obfuscation":"WHWBsd5Jkg"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":["}}]},"finish_reason":null}],"usage":null,"obfuscation":"xyMuvvQ1H"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"0"}}]},"finish_reason":null}],"usage":null,"obfuscation":"NkL223rTveiY"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"]}"}}]},"finish_reason":null}],"usage":null,"obfuscation":"5FvupEDfMHe"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"]}"}}]},"finish_reason":null}],"usage":null,"obfuscation":"uDYSO0oHbmz"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"9sRJO4xoviH"}
+
+data: {"id":"chatcmpl-D64NZp69RkEyXdUoDaCBj7fSYll8J","object":"chat.completion.chunk","created":1770339173,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":5657,"completion_tokens":1488,"total_tokens":7145,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":1216,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"6YeROQTGvStbj"}
+
+data: [DONE]
diff --git a/crates/leaf-acp/tests/test_data/openai_builtin_final.txt b/crates/leaf-acp/tests/test_data/openai_builtin_final.txt
new file mode 100644
index 000000000000..45e52a3113ee
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_builtin_final.txt
@@ -0,0 +1,167 @@
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"aQKzd"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"##"},"finish_reason":null}],"usage":null,"obfuscation":"0Wf0H"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Done"},"finish_reason":null}],"usage":null,"obfuscation":"AQ"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n\n"},"finish_reason":null}],"usage":null,"obfuscation":"0hF"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"zu5Ssj"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Retrieved"},"finish_reason":null}],"usage":null,"obfuscation":"qz8LIGnHhsu2z"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" code"},"finish_reason":null}],"usage":null,"obfuscation":"rd"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" using"},"finish_reason":null}],"usage":null,"obfuscation":"I"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Mc"},"finish_reason":null}],"usage":null,"obfuscation":"4nTh"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"p"},"finish_reason":null}],"usage":null,"obfuscation":"8GcBxs"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Fixture"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".get"},"finish_reason":null}],"usage":null,"obfuscation":"l1G"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Code"},"finish_reason":null}],"usage":null,"obfuscation":"tiq"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null}],"usage":null,"obfuscation":"gu51g"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"vovzo6"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Saved"},"finish_reason":null}],"usage":null,"obfuscation":"Y"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" the"},"finish_reason":null}],"usage":null,"obfuscation":"EhL"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" code"},"finish_reason":null}],"usage":null,"obfuscation":"7L"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" to"},"finish_reason":null}],"usage":null,"obfuscation":"MeaR"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" /"},"finish_reason":null}],"usage":null,"obfuscation":"Bzpfd"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"tmp"},"finish_reason":null}],"usage":null,"obfuscation":"hmov"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"/result"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".txt"},"finish_reason":null}],"usage":null,"obfuscation":"omu"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" using"},"finish_reason":null}],"usage":null,"obfuscation":"P"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Developer"},"finish_reason":null}],"usage":null,"obfuscation":"ehHehaK2t0Ddx"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".text"},"finish_reason":null}],"usage":null,"obfuscation":"Io"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Editor"},"finish_reason":null}],"usage":null,"obfuscation":"4"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" ("},"finish_reason":null}],"usage":null,"obfuscation":"ZyNgq"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"write"},"finish_reason":null}],"usage":null,"obfuscation":"79"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":")\n\n"},"finish_reason":null}],"usage":null,"obfuscation":"yx"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"###"},"finish_reason":null}],"usage":null,"obfuscation":"AnhY"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" File"},"finish_reason":null}],"usage":null,"obfuscation":"fR"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" content"},"finish_reason":null}],"usage":null,"obfuscation":"toRedYiWXrOWCt6"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null}],"usage":null,"obfuscation":"wNYeR"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"``"},"finish_reason":null}],"usage":null,"obfuscation":"xmJag"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"`\n"},"finish_reason":null}],"usage":null,"obfuscation":"ZqCC"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"test"},"finish_reason":null}],"usage":null,"obfuscation":"Zom"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"25fVgE"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"uuid"},"finish_reason":null}],"usage":null,"obfuscation":"sNs"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"WKf475"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"123"},"finish_reason":null}],"usage":null,"obfuscation":"pUWP"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"45"},"finish_reason":null}],"usage":null,"obfuscation":"UJWHi"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"naAmnL"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"678"},"finish_reason":null}],"usage":null,"obfuscation":"EZiD"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"90"},"finish_reason":null}],"usage":null,"obfuscation":"pmDEn"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null}],"usage":null,"obfuscation":"ExdUo"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"``"},"finish_reason":null}],"usage":null,"obfuscation":"GVkML"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"`\n\n"},"finish_reason":null}],"usage":null,"obfuscation":"Rm"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"###"},"finish_reason":null}],"usage":null,"obfuscation":"KHkH"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Write"},"finish_reason":null}],"usage":null,"obfuscation":"N"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" result"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null}],"usage":null,"obfuscation":"N9gVJ"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"3m12KG"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Successfully"},"finish_reason":null}],"usage":null,"obfuscation":"zWAzMcKJ1h"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" wrote"},"finish_reason":null}],"usage":null,"obfuscation":"g"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" to"},"finish_reason":null}],"usage":null,"obfuscation":"npd5"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" /"},"finish_reason":null}],"usage":null,"obfuscation":"Di2lF"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"tmp"},"finish_reason":null}],"usage":null,"obfuscation":"bFN2"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"/result"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".txt"},"finish_reason":null}],"usage":null,"obfuscation":"jAM"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n\n"},"finish_reason":null}],"usage":null,"obfuscation":"mBj"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"If"},"finish_reason":null}],"usage":null,"obfuscation":"EwyTz"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" you"},"finish_reason":null}],"usage":null,"obfuscation":"MdZ"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" want"},"finish_reason":null}],"usage":null,"obfuscation":"lr"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" to"},"finish_reason":null}],"usage":null,"obfuscation":"3UYH"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" view"},"finish_reason":null}],"usage":null,"obfuscation":"fT"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" or"},"finish_reason":null}],"usage":null,"obfuscation":"aA1v"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" edit"},"finish_reason":null}],"usage":null,"obfuscation":"lx"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" the"},"finish_reason":null}],"usage":null,"obfuscation":"CFk"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" file"},"finish_reason":null}],"usage":null,"obfuscation":"Pk"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" further"},"finish_reason":null}],"usage":null,"obfuscation":"xZV6orYDINsvpSm"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":","},"finish_reason":null}],"usage":null,"obfuscation":"rIgbQL"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" I"},"finish_reason":null}],"usage":null,"obfuscation":"PushO"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" can"},"finish_reason":null}],"usage":null,"obfuscation":"NfT"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" open"},"finish_reason":null}],"usage":null,"obfuscation":"vt"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" it"},"finish_reason":null}],"usage":null,"obfuscation":"YdGd"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" or"},"finish_reason":null}],"usage":null,"obfuscation":"Nr1q"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" perform"},"finish_reason":null}],"usage":null,"obfuscation":"odfH1BqmSysXoQX"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" additional"},"finish_reason":null}],"usage":null,"obfuscation":"85PS7edSCbfj"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" edits"},"finish_reason":null}],"usage":null,"obfuscation":"u"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}],"usage":null,"obfuscation":"PyvcSe"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"7"}
+
+data: {"id":"chatcmpl-D64NkPzFlFdteIXaDZiWzbRazyX73","object":"chat.completion.chunk","created":1770339184,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":6014,"completion_tokens":601,"total_tokens":6615,"prompt_tokens_details":{"cached_tokens":4736,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":512,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"wCzJI4gC3nyF"}
+
+data: [DONE]
diff --git a/crates/leaf-acp/tests/test_data/openai_builtin_search.txt b/crates/leaf-acp/tests/test_data/openai_builtin_search.txt
new file mode 100644
index 000000000000..7a47e4f4d2bc
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_builtin_search.txt
@@ -0,0 +1,39 @@
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null},"finish_reason":null}],"obfuscation":"bj"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_mhfEtsDu6HJHTgoyc3rUDTNh","type":"function","function":{"name":"code_execution__list_functions","arguments":""}}]},"finish_reason":null}],"obfuscation":"iPDqUVdKwkKN3F"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{}"}}]},"finish_reason":null}],"obfuscation":"1sK4gwd8S5s"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"call_AwLicLqHJBNM33BX8pjZeB9H","type":"function","function":{"name":"code_execution__get_function_details","arguments":""}}]},"finish_reason":null}],"obfuscation":"Nb9YXNVD"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\"fu"}}]},"finish_reason":null}],"obfuscation":"WfhGNuZ8"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"nctio"}}]},"finish_reason":null}],"obfuscation":"onePKs8g"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"ns\": ["}}]},"finish_reason":null}],"obfuscation":"1i6Ns7"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\"Mcp"}}]},"finish_reason":null}],"obfuscation":"SxKZ9zQ8"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"Fixtu"}}]},"finish_reason":null}],"obfuscation":"oRNvv26j"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"re.get"}}]},"finish_reason":null}],"obfuscation":"9jh4BM5"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"Code"}}]},"finish_reason":null}],"obfuscation":"X4583CKJj"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\", \"D"}}]},"finish_reason":null}],"obfuscation":"pLCir4"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"evelop"}}]},"finish_reason":null}],"obfuscation":"YdjzlvJ"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"er.w"}}]},"finish_reason":null}],"obfuscation":"Kv1vRc0to"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"rite"}}]},"finish_reason":null}],"obfuscation":"4sRF9L7t"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\"]"}}]},"finish_reason":null}],"obfuscation":"SmXF9J"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"}"}}]},"finish_reason":null}],"obfuscation":"kO5yFNBeMAXW"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"RNhcx0FLJsf"}
+
+data: {"id":"chatcmpl-D64NHpAses8hYgIt8xQfDCmg3PoHQ","object":"chat.completion.chunk","created":1770339155,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":2919,"completion_tokens":2626,"total_tokens":5545,"prompt_tokens_details":{"cached_tokens":2560,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":2560,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"KT48p4Oy6v"}
+
+data: [DONE]
diff --git a/crates/leaf-acp/tests/test_data/openai_fs_read_tool_call.txt b/crates/leaf-acp/tests/test_data/openai_fs_read_tool_call.txt
new file mode 100644
index 000000000000..b9c05e967f09
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_fs_read_tool_call.txt
@@ -0,0 +1,31 @@
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_ihWQp56Fq7txY7HHZJiNwzdy","type":"function","function":{"name":"read","arguments":""}}],"refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"arnej9G"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"M6pPn6sMuZ"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"path"}}]},"finish_reason":null}],"usage":null,"obfuscation":"7mHQMw0OR"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"UktUfC37dd"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"/"}}]},"finish_reason":null}],"usage":null,"obfuscation":"kkR18xflju"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tmp"}}]},"finish_reason":null}],"usage":null,"obfuscation":"WRyfb1apwr"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/test"}}]},"finish_reason":null}],"usage":null,"obfuscation":"8Q5olmtW"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_ac"}}]},"finish_reason":null}],"usage":null,"obfuscation":"3eLyGhC3LQ"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"p"}}]},"finish_reason":null}],"usage":null,"obfuscation":"NXUnZPCSiGnn"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_read"}}]},"finish_reason":null}],"usage":null,"obfuscation":"mCuJG2UV"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".txt"}}]},"finish_reason":null}],"usage":null,"obfuscation":"KjZS3z8R5"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"finish_reason":null}],"usage":null,"obfuscation":"Pe4UZ56QwD"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"5hgvg2ilskM"}
+
+data: {"id":"chatcmpl-DFS7Z5WU3Km0oi1oFGiFSCLRPlriu","object":"chat.completion.chunk","created":1772575389,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":7010,"completion_tokens":156,"total_tokens":7166,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":128,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"bxKDPI1dGxmTdwd"}
+
+data: [DONE]
+
+
diff --git a/crates/leaf-acp/tests/test_data/openai_fs_read_tool_result.txt b/crates/leaf-acp/tests/test_data/openai_fs_read_tool_result.txt
new file mode 100644
index 000000000000..a56e349bf714
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_fs_read_tool_result.txt
@@ -0,0 +1,21 @@
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"5Gvpc"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"test"},"finish_reason":null}],"usage":null,"obfuscation":"8x6"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-read"},"finish_reason":null}],"usage":null,"obfuscation":"PM"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-content"},"finish_reason":null}],"usage":null,"obfuscation":"UxQl2cnPsJJsU4e"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"OIwMhu"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"123"},"finish_reason":null}],"usage":null,"obfuscation":"wsSJ"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"45"},"finish_reason":null}],"usage":null,"obfuscation":"2A6Mf"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"R"}
+
+data: {"id":"chatcmpl-DFS7cNHbE2luDLKMezfuTconLbkjx","object":"chat.completion.chunk","created":1772575392,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":7061,"completion_tokens":143,"total_tokens":7204,"prompt_tokens_details":{"cached_tokens":6784,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":128,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"GPZ33iiMhEfX"}
+
+data: [DONE]
+
+
diff --git a/crates/leaf-acp/tests/test_data/openai_fs_write_tool_call.txt b/crates/leaf-acp/tests/test_data/openai_fs_write_tool_call.txt
new file mode 100644
index 000000000000..bdeef703be6c
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_fs_write_tool_call.txt
@@ -0,0 +1,57 @@
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_AmzdRa1JlDxMgwoFQ3W9Y6bf","type":"function","function":{"name":"write","arguments":""}}],"refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"RgiRu2"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{"}}]},"finish_reason":null}],"usage":null,"obfuscation":"c0QxkX9w4RWN"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \""}}]},"finish_reason":null}],"usage":null,"obfuscation":"guqW44RQ65"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"path"}}]},"finish_reason":null}],"usage":null,"obfuscation":"U4psai0wZ"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"VmwxuO12qp"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \"/"}}]},"finish_reason":null}],"usage":null,"obfuscation":"u6VUDV4LA"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"tmp"}}]},"finish_reason":null}],"usage":null,"obfuscation":"oyo2Q6TTwc"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"/test"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ra2IFNjY"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_ac"}}]},"finish_reason":null}],"usage":null,"obfuscation":"67j2bDOpM3"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"p"}}]},"finish_reason":null}],"usage":null,"obfuscation":"tqT1vbnWapFk"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"_write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"35yX5yg"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":".txt"}}]},"finish_reason":null}],"usage":null,"obfuscation":"2OmZyuL8W"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\","}}]},"finish_reason":null}],"usage":null,"obfuscation":"D4bVfN7m5M"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \""}}]},"finish_reason":null}],"usage":null,"obfuscation":"TtPSCtq6VV"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"content"}}]},"finish_reason":null}],"usage":null,"obfuscation":"EqSkfr"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":"}}]},"finish_reason":null}],"usage":null,"obfuscation":"ubKsA67ljm"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \""}}]},"finish_reason":null}],"usage":null,"obfuscation":"H6lzd69n6V"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"test"}}]},"finish_reason":null}],"usage":null,"obfuscation":"v0IFOBPju"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"-write"}}]},"finish_reason":null}],"usage":null,"obfuscation":"jzyuAat"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"-content"}}]},"finish_reason":null}],"usage":null,"obfuscation":"6p3tY"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"-"}}]},"finish_reason":null}],"usage":null,"obfuscation":"R4PFCfWGWwQO"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"678"}}]},"finish_reason":null}],"usage":null,"obfuscation":"BHRNUazkpJ"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"90"}}]},"finish_reason":null}],"usage":null,"obfuscation":"085WWflBbh1"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"zu9irR3Bf58"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" }"}}]},"finish_reason":null}],"usage":null,"obfuscation":"m4lW5kBYPNx"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"Ptpd2etx9L8"}
+
+data: {"id":"chatcmpl-DFS9ySulK0wZhiU0Qbcdrr7vqTth2","object":"chat.completion.chunk","created":1772575538,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":7013,"completion_tokens":233,"total_tokens":7246,"prompt_tokens_details":{"cached_tokens":6784,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":192,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"K1gClbYIVju8"}
+
+data: [DONE]
+
+
diff --git a/crates/leaf-acp/tests/test_data/openai_fs_write_tool_result.txt b/crates/leaf-acp/tests/test_data/openai_fs_write_tool_result.txt
new file mode 100644
index 000000000000..df360031e68c
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_fs_write_tool_result.txt
@@ -0,0 +1,41 @@
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"LlUCg"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Written"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" to"},"finish_reason":null}],"usage":null,"obfuscation":"eOkP"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" /"},"finish_reason":null}],"usage":null,"obfuscation":"Wna6a"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"tmp"},"finish_reason":null}],"usage":null,"obfuscation":"r1VT"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"/test"},"finish_reason":null}],"usage":null,"obfuscation":"KI"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"_ac"},"finish_reason":null}],"usage":null,"obfuscation":"OV52"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"p"},"finish_reason":null}],"usage":null,"obfuscation":"lN8FEz"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"_write"},"finish_reason":null}],"usage":null,"obfuscation":"w"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".txt"},"finish_reason":null}],"usage":null,"obfuscation":"mhj"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":":"},"finish_reason":null}],"usage":null,"obfuscation":"MLXIAD"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" test"},"finish_reason":null}],"usage":null,"obfuscation":"fu"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-write"},"finish_reason":null}],"usage":null,"obfuscation":"P"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-content"},"finish_reason":null}],"usage":null,"obfuscation":"UnYjMuzV1tsglmv"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"dXz2oN"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"678"},"finish_reason":null}],"usage":null,"obfuscation":"cBoM"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"90"},"finish_reason":null}],"usage":null,"obfuscation":"nCBwP"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"j"}
+
+data: {"id":"chatcmpl-DFSA1sjiFQp5MUkdJvhadSGA4nAoX","object":"chat.completion.chunk","created":1772575541,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":7080,"completion_tokens":19,"total_tokens":7099,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"e7"}
+
+data: [DONE]
+
+
diff --git a/crates/leaf-acp/tests/test_data/openai_image_attachment.txt b/crates/leaf-acp/tests/test_data/openai_image_attachment.txt
new file mode 100644
index 000000000000..add7a87aa3de
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_image_attachment.txt
@@ -0,0 +1,238 @@
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"7E7t8"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Here"},"finish_reason":null}],"usage":null,"obfuscation":"C7q"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"’s"},"finish_reason":null}],"usage":null,"obfuscation":"2YLin"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" what"},"finish_reason":null}],"usage":null,"obfuscation":"X0"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" I"},"finish_reason":null}],"usage":null,"obfuscation":"cWHjE"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" can"},"finish_reason":null}],"usage":null,"obfuscation":"sSB"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" derive"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" from"},"finish_reason":null}],"usage":null,"obfuscation":"wh"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" the"},"finish_reason":null}],"usage":null,"obfuscation":"pdc"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" image"},"finish_reason":null}],"usage":null,"obfuscation":"6"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":":\n\n"},"finish_reason":null}],"usage":null,"obfuscation":"sf"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"8yQy1S"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" OCR"},"finish_reason":null}],"usage":null,"obfuscation":"8A1"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"/text"},"finish_reason":null}],"usage":null,"obfuscation":"9R"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" transcription"},"finish_reason":null}],"usage":null,"obfuscation":"Ehc21BJqv"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":":\n"},"finish_reason":null}],"usage":null,"obfuscation":"9ab4"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" "},"finish_reason":null}],"usage":null,"obfuscation":"u2EwQi"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Hello"},"finish_reason":null}],"usage":null,"obfuscation":"5"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Goose"},"finish_reason":null}],"usage":null,"obfuscation":"b"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"!\n"},"finish_reason":null}],"usage":null,"obfuscation":"EsVy"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" "},"finish_reason":null}],"usage":null,"obfuscation":"Va8f84"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" This"},"finish_reason":null}],"usage":null,"obfuscation":"IR"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" is"},"finish_reason":null}],"usage":null,"obfuscation":"eaRL"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" a"},"finish_reason":null}],"usage":null,"obfuscation":"cdTOW"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" test"},"finish_reason":null}],"usage":null,"obfuscation":"WN"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" image"},"finish_reason":null}],"usage":null,"obfuscation":"l"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".\n\n"},"finish_reason":null}],"usage":null,"obfuscation":"lx"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"ABF429"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Observ"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"ations"},"finish_reason":null}],"usage":null,"obfuscation":"J"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":":\n"},"finish_reason":null}],"usage":null,"obfuscation":"iWhB"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" "},"finish_reason":null}],"usage":null,"obfuscation":"IFJGlV"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" -"},"finish_reason":null}],"usage":null,"obfuscation":"C8bxY"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" The"},"finish_reason":null}],"usage":null,"obfuscation":"UNZ"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" text"},"finish_reason":null}],"usage":null,"obfuscation":"aO"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" appears"},"finish_reason":null}],"usage":null,"obfuscation":"fMW5Ik2fIuCxYIi"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" in"},"finish_reason":null}],"usage":null,"obfuscation":"6Csr"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" two"},"finish_reason":null}],"usage":null,"obfuscation":"1OA"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" lines"},"finish_reason":null}],"usage":null,"obfuscation":"6"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" on"},"finish_reason":null}],"usage":null,"obfuscation":"MfK8"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" a"},"finish_reason":null}],"usage":null,"obfuscation":"Xn4NP"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" white"},"finish_reason":null}],"usage":null,"obfuscation":"Z"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" background"},"finish_reason":null}],"usage":null,"obfuscation":"xJbYCha0CxG5"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" with"},"finish_reason":null}],"usage":null,"obfuscation":"pN"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" black"},"finish_reason":null}],"usage":null,"obfuscation":"7"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" sans"},"finish_reason":null}],"usage":null,"obfuscation":"ni"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-serif"},"finish_reason":null}],"usage":null,"obfuscation":"X"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" style"},"finish_reason":null}],"usage":null,"obfuscation":"W"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".\n"},"finish_reason":null}],"usage":null,"obfuscation":"TPOZ"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" "},"finish_reason":null}],"usage":null,"obfuscation":"SpMYza"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" -"},"finish_reason":null}],"usage":null,"obfuscation":"CSuHP"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Lik"},"finish_reason":null}],"usage":null,"obfuscation":"Wgi"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"ely"},"finish_reason":null}],"usage":null,"obfuscation":"dhDR"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" centered"},"finish_reason":null}],"usage":null,"obfuscation":"ai50dSkUIc0bS6"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" alignment"},"finish_reason":null}],"usage":null,"obfuscation":"8sTNdzGIIYmdW"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":","},"finish_reason":null}],"usage":null,"obfuscation":"Fby3dv"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" two"},"finish_reason":null}],"usage":null,"obfuscation":"Lpl"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-line"},"finish_reason":null}],"usage":null,"obfuscation":"xf"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" layout"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":".\n\n"},"finish_reason":null}],"usage":null,"obfuscation":"CM"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"What"},"finish_reason":null}],"usage":null,"obfuscation":"zy9"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" would"},"finish_reason":null}],"usage":null,"obfuscation":"d"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" you"},"finish_reason":null}],"usage":null,"obfuscation":"qyo"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" like"},"finish_reason":null}],"usage":null,"obfuscation":"tG"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" me"},"finish_reason":null}],"usage":null,"obfuscation":"KJ3J"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" to"},"finish_reason":null}],"usage":null,"obfuscation":"jTJN"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" do"},"finish_reason":null}],"usage":null,"obfuscation":"Kvuu"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" next"},"finish_reason":null}],"usage":null,"obfuscation":"Q4"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"?\n"},"finish_reason":null}],"usage":null,"obfuscation":"MKya"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"FGV2M3"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Translate"},"finish_reason":null}],"usage":null,"obfuscation":"ojudhthRN0wje"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" the"},"finish_reason":null}],"usage":null,"obfuscation":"j1d"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" text"},"finish_reason":null}],"usage":null,"obfuscation":"IN"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null}],"usage":null,"obfuscation":"5GPdt"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"GdygSH"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Extract"},"finish_reason":null}],"usage":null,"obfuscation":"CocHxzrTqsaRFbq"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" more"},"finish_reason":null}],"usage":null,"obfuscation":"LN"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" metadata"},"finish_reason":null}],"usage":null,"obfuscation":"52g08uQUy8fzv2"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" ("},"finish_reason":null}],"usage":null,"obfuscation":"AmD59"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"font"},"finish_reason":null}],"usage":null,"obfuscation":"Qvr"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":","},"finish_reason":null}],"usage":null,"obfuscation":"DKTZR4"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" size"},"finish_reason":null}],"usage":null,"obfuscation":"sa"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":","},"finish_reason":null}],"usage":null,"obfuscation":"kQknkh"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" contrast"},"finish_reason":null}],"usage":null,"obfuscation":"CzLtt2a3ZKZRsk"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":")\n"},"finish_reason":null}],"usage":null,"obfuscation":"10ts"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"BkGfhE"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Generate"},"finish_reason":null}],"usage":null,"obfuscation":"kRp8kkudiLXyPr"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" alt"},"finish_reason":null}],"usage":null,"obfuscation":"naE"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" text"},"finish_reason":null}],"usage":null,"obfuscation":"WO"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" or"},"finish_reason":null}],"usage":null,"obfuscation":"wjCo"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" accessibility"},"finish_reason":null}],"usage":null,"obfuscation":"HgWk0MkQP"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" description"},"finish_reason":null}],"usage":null,"obfuscation":"2RkUmSr6mK5"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n"},"finish_reason":null}],"usage":null,"obfuscation":"qCmgA"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"dDYudm"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Create"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" a"},"finish_reason":null}],"usage":null,"obfuscation":"Uco4R"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" caption"},"finish_reason":null}],"usage":null,"obfuscation":"cAPhTd1DoHVetWW"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" or"},"finish_reason":null}],"usage":null,"obfuscation":"qK8N"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" use"},"finish_reason":null}],"usage":null,"obfuscation":"wGO"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" it"},"finish_reason":null}],"usage":null,"obfuscation":"DKP3"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" in"},"finish_reason":null}],"usage":null,"obfuscation":"0qux"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" code"},"finish_reason":null}],"usage":null,"obfuscation":"FI"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" ("},"finish_reason":null}],"usage":null,"obfuscation":"BXnPm"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"HTML"},"finish_reason":null}],"usage":null,"obfuscation":"lal"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"/C"},"finish_reason":null}],"usage":null,"obfuscation":"2bBrS"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"SS"},"finish_reason":null}],"usage":null,"obfuscation":"kM3Jx"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":")\n"},"finish_reason":null}],"usage":null,"obfuscation":"H7Et"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"EbOoyr"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Run"},"finish_reason":null}],"usage":null,"obfuscation":"h74"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" OCR"},"finish_reason":null}],"usage":null,"obfuscation":"3vs"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" on"},"finish_reason":null}],"usage":null,"obfuscation":"N3y0"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" similar"},"finish_reason":null}],"usage":null,"obfuscation":"mMnr4ffD7zygIYv"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" images"},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" for"},"finish_reason":null}],"usage":null,"obfuscation":"TJp"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" batch"},"finish_reason":null}],"usage":null,"obfuscation":"6"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" processing"},"finish_reason":null}],"usage":null,"obfuscation":"ls6HOZgAV1ez"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"j"}
+
+data: {"id":"chatcmpl-DHk8aNO2gKKqlrPxXg3cHSzIBitLf","object":"chat.completion.chunk","created":1773121300,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":2768,"completion_tokens":572,"total_tokens":3340,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":448,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"EvMBUZOQw2ps4DX"}
+
+data: [DONE]
+
diff --git a/crates/leaf-acp/tests/test_data/openai_image_tool_call.txt b/crates/leaf-acp/tests/test_data/openai_image_tool_call.txt
new file mode 100644
index 000000000000..491f78285829
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_image_tool_call.txt
@@ -0,0 +1,9 @@
+data: {"id":"chatcmpl-D64jlQjeSdFB1flOZAoyVx3Kr3jBM","object":"chat.completion.chunk","created":1770340549,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_WQz9ddBYGE2NUstbYIx4jLzP","type":"function","function":{"name":"mcp-fixture__get_image","arguments":""}}],"refusal":null},"finish_reason":null}],"usage":null,"obfuscation":""}
+
+data: {"id":"chatcmpl-D64jlQjeSdFB1flOZAoyVx3Kr3jBM","object":"chat.completion.chunk","created":1770340549,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{}"}}]},"finish_reason":null}],"usage":null,"obfuscation":"S3q07Q7rhA5"}
+
+data: {"id":"chatcmpl-D64jlQjeSdFB1flOZAoyVx3Kr3jBM","object":"chat.completion.chunk","created":1770340549,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"sqTkwgxzEue"}
+
+data: {"id":"chatcmpl-D64jlQjeSdFB1flOZAoyVx3Kr3jBM","object":"chat.completion.chunk","created":1770340549,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":5254,"completion_tokens":345,"total_tokens":5599,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":320,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"XUJZvwg1ZvdZQCh"}
+
+data: [DONE]
diff --git a/crates/leaf-acp/tests/test_data/openai_image_tool_result.txt b/crates/leaf-acp/tests/test_data/openai_image_tool_result.txt
new file mode 100644
index 000000000000..55126be16644
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_image_tool_result.txt
@@ -0,0 +1,25 @@
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"5exqq"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}],"usage":null,"obfuscation":"yI"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" Goose"},"finish_reason":null}],"usage":null,"obfuscation":"6"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"!\n"},"finish_reason":null}],"usage":null,"obfuscation":"ocAA"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"This"},"finish_reason":null}],"usage":null,"obfuscation":"DJx"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" is"},"finish_reason":null}],"usage":null,"obfuscation":"CQGZ"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" a"},"finish_reason":null}],"usage":null,"obfuscation":"NJygF"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" test"},"finish_reason":null}],"usage":null,"obfuscation":"kV"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":" image"},"finish_reason":null}],"usage":null,"obfuscation":"S"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}],"usage":null,"obfuscation":"Cv0h11"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"N"}
+
+data: {"id":"chatcmpl-D64jowotwkWXsd3RqhveZzRGUuOVu","object":"chat.completion.chunk","created":1770340552,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":5448,"completion_tokens":466,"total_tokens":5914,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":448,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"evt64Mp0NnoCT9R"}
+
+data: [DONE]
diff --git a/crates/leaf-acp/tests/test_data/openai_shell_tool_call.txt b/crates/leaf-acp/tests/test_data/openai_shell_tool_call.txt
new file mode 100644
index 000000000000..f677072b2321
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_shell_tool_call.txt
@@ -0,0 +1,30 @@
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_JOtrY1Ec2KAB2a7gI6H6CWfs","type":"function","function":{"name":"shell","arguments":""}}],"refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"QvrA4B"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"fvwZUJVGPB"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"command"}}]},"finish_reason":null}],"usage":null,"obfuscation":"bHHxtU"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}],"usage":null,"obfuscation":"GgtVJCU4"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"echo"}}]},"finish_reason":null}],"usage":null,"obfuscation":"DRTSpxLvb"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" test"}}]},"finish_reason":null}],"usage":null,"obfuscation":"IwvMxMcs"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"-shell"}}]},"finish_reason":null}],"usage":null,"obfuscation":"y6LBASX"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"-content"}}]},"finish_reason":null}],"usage":null,"obfuscation":"4bIL9"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"-"}}]},"finish_reason":null}],"usage":null,"obfuscation":"cOYkwBZ6YQk9"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"987"}}]},"finish_reason":null}],"usage":null,"obfuscation":"2QASfrMS6w"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"65"}}]},"finish_reason":null}],"usage":null,"obfuscation":"IKmTYztxyp2"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"finish_reason":null}],"usage":null,"obfuscation":"tVHIYFZOIs"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"91dJqR0sRAl"}
+
+data: {"id":"chatcmpl-DJqmdyg4XAGu7vN41PpMz4U8CMQhI","object":"chat.completion.chunk","created":1773623503,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":3392,"completion_tokens":284,"total_tokens":3676,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":256,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"aOmtxq2YavJWo1s"}
+
+data: [DONE]
+
diff --git a/crates/leaf-acp/tests/test_data/openai_shell_tool_result.txt b/crates/leaf-acp/tests/test_data/openai_shell_tool_result.txt
new file mode 100644
index 000000000000..82e7f2ea8ac0
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_shell_tool_result.txt
@@ -0,0 +1,21 @@
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"k3ql1"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"test"},"finish_reason":null}],"usage":null,"obfuscation":"fi2"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-shell"},"finish_reason":null}],"usage":null,"obfuscation":"l"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-content"},"finish_reason":null}],"usage":null,"obfuscation":"lV6xovjxNlG3ZDv"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"EzE9fO"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"987"},"finish_reason":null}],"usage":null,"obfuscation":"Ji6A"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"65"},"finish_reason":null}],"usage":null,"obfuscation":"lI6rL"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"x"}
+
+data: {"id":"chatcmpl-DJqmhw9icV2gtFchMIfmzQZxB4sAt","object":"chat.completion.chunk","created":1773623507,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":3443,"completion_tokens":9,"total_tokens":3452,"prompt_tokens_details":{"cached_tokens":3200,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":""}
+
+data: [DONE]
+
+
diff --git a/crates/leaf-acp/tests/test_data/openai_tool_call.txt b/crates/leaf-acp/tests/test_data/openai_tool_call.txt
new file mode 100644
index 000000000000..c62eaf9b9238
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_tool_call.txt
@@ -0,0 +1,10 @@
+data: {"id":"chatcmpl-CqqCVVtD16yj37EZocLFkGNMhHZFS","object":"chat.completion.chunk","created":1766709751,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_eLXEeL8ZQBgXACKp78eNmyNp","type":"function","function":{"name":"mcp-fixture__get_code","arguments":""}}],"refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"FobexttCIQY"}
+
+data: {"id":"chatcmpl-CqqCVVtD16yj37EZocLFkGNMhHZFS","object":"chat.completion.chunk","created":1766709751,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{}"}}]},"finish_reason":null}],"usage":null,"obfuscation":"01EkRUrgMxo"}
+
+data: {"id":"chatcmpl-CqqCVVtD16yj37EZocLFkGNMhHZFS","object":"chat.completion.chunk","created":1766709751,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":null,"obfuscation":"k965c2jCwUF"}
+
+data: {"id":"chatcmpl-CqqCVVtD16yj37EZocLFkGNMhHZFS","object":"chat.completion.chunk","created":1766709751,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":2320,"completion_tokens":149,"total_tokens":2469,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":128,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"7Wxwg9X1OBwbjfE"}
+
+data: [DONE]
+
diff --git a/crates/leaf-acp/tests/test_data/openai_tool_result.txt b/crates/leaf-acp/tests/test_data/openai_tool_result.txt
new file mode 100644
index 000000000000..d940cc4d9834
--- /dev/null
+++ b/crates/leaf-acp/tests/test_data/openai_tool_result.txt
@@ -0,0 +1,26 @@
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}],"usage":null,"obfuscation":"p0yzG"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"test"},"finish_reason":null}],"usage":null,"obfuscation":"ASB"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"b2XPf4"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"uuid"},"finish_reason":null}],"usage":null,"obfuscation":"88h"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"MunLmd"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"123"},"finish_reason":null}],"usage":null,"obfuscation":"iyKy"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"45"},"finish_reason":null}],"usage":null,"obfuscation":"MGSUp"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"-"},"finish_reason":null}],"usage":null,"obfuscation":"swF2Pu"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"678"},"finish_reason":null}],"usage":null,"obfuscation":"TPtP"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"90"},"finish_reason":null}],"usage":null,"obfuscation":"1UrvC"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":null,"obfuscation":"e"}
+
+data: {"id":"chatcmpl-CqqCXO3JYXwnwTystVUj2AuyW9xgV","object":"chat.completion.chunk","created":1766709753,"model":"gpt-5-nano-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":2357,"completion_tokens":274,"total_tokens":2631,"prompt_tokens_details":{"cached_tokens":2048,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":256,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"qFAKS6Oew9eV"}
+
+data: [DONE]
+
diff --git a/crates/leaf-cli/Cargo.toml b/crates/leaf-cli/Cargo.toml
index 4664bda922ad..59023270a1f9 100644
--- a/crates/leaf-cli/Cargo.toml
+++ b/crates/leaf-cli/Cargo.toml
@@ -61,6 +61,8 @@ url = { workspace = true }
urlencoding = { workspace = true }
clap_complete = "4.5.62"
comfy-table = "7.2.2"
+sha2 = "0.10"
+sigstore-verification = { version = "0.1", default-features = false, features = ["rustls"] }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["wincred"] }
diff --git a/crates/leaf-cli/src/commands/configure.rs b/crates/leaf-cli/src/commands/configure.rs
index 5ace5dac62cd..d1c66d8d51f8 100644
--- a/crates/leaf-cli/src/commands/configure.rs
+++ b/crates/leaf-cli/src/commands/configure.rs
@@ -447,9 +447,7 @@ fn select_model_from_list(
// Sort models naturally
let mut sorted_models = models.to_vec();
- sorted_models.sort_by(|a, b| {
- a.to_lowercase().cmp(&b.to_lowercase())
- });
+ sorted_models.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
if sorted_models.len() > MAX_MODELS {
// Use models from API directly, not filtered through known_models
@@ -470,9 +468,7 @@ fn select_model_from_list(
}
}
- recommended_models.sort_by(|a, b| {
- a.to_lowercase().cmp(&b.to_lowercase())
- });
+ recommended_models.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
if !recommended_models.is_empty() {
let mut model_items: Vec<(String, String, &str)> = recommended_models
@@ -510,8 +506,10 @@ fn select_model_from_list(
Ok(interactive_model_search(&sorted_models)?)
}
} else {
- let mut model_items: Vec<(String, String, &str)> =
- sorted_models.iter().map(|m| (m.clone(), m.clone(), "")).collect();
+ let mut model_items: Vec<(String, String, &str)> = sorted_models
+ .iter()
+ .map(|m| (m.clone(), m.clone(), ""))
+ .collect();
model_items.push((
UNLISTED_MODEL_KEY.to_string(),
diff --git a/crates/leaf-cli/src/commands/session.rs b/crates/leaf-cli/src/commands/session.rs
index 145669043e16..c4851ed3762f 100644
--- a/crates/leaf-cli/src/commands/session.rs
+++ b/crates/leaf-cli/src/commands/session.rs
@@ -89,25 +89,18 @@ pub async fn handle_session_remove(
regex_string: Option,
) -> Result<()> {
let session_manager = SessionManager::instance();
- let all_sessions = match session_manager.list_sessions().await {
- Ok(sessions) => sessions,
- Err(e) => {
- tracing::error!("Failed to retrieve sessions: {:?}", e);
- return Err(anyhow::anyhow!("Failed to retrieve sessions"));
- }
- };
let matched_sessions: Vec;
if let Some(id_val) = session_id {
- if let Some(session) = all_sessions.iter().find(|s| s.id == id_val) {
- matched_sessions = vec![session.clone()];
- } else {
- return Err(anyhow::anyhow!("Session ID '{}' not found.", id_val));
+ match session_manager.get_session(&id_val, false).await {
+ Ok(session) => matched_sessions = vec![session],
+ Err(_) => return Err(anyhow::anyhow!("Session ID '{}' not found.", id_val)),
}
} else if let Some(name_val) = name {
- if let Some(session) = all_sessions.iter().find(|s| s.name == name_val) {
- matched_sessions = vec![session.clone()];
+ let all_sessions = session_manager.list_all_sessions().await?;
+ if let Some(session) = all_sessions.into_iter().find(|s| s.name == name_val) {
+ matched_sessions = vec![session];
} else {
return Err(anyhow::anyhow!(
"Session with name '{}' not found.",
@@ -118,7 +111,8 @@ pub async fn handle_session_remove(
let session_regex = Regex::new(®ex_val)
.with_context(|| format!("Invalid regex pattern '{}'", regex_val))?;
- matched_sessions = all_sessions
+ let visible_sessions = session_manager.list_sessions().await?;
+ matched_sessions = visible_sessions
.into_iter()
.filter(|session| session_regex.is_match(&session.id))
.collect();
@@ -128,10 +122,11 @@ pub async fn handle_session_remove(
return Ok(());
}
} else {
- if all_sessions.is_empty() {
+ let visible_sessions = session_manager.list_sessions().await?;
+ if visible_sessions.is_empty() {
return Err(anyhow::anyhow!("No sessions found."));
}
- matched_sessions = prompt_interactive_session_removal(&all_sessions)?;
+ matched_sessions = prompt_interactive_session_removal(&visible_sessions)?;
}
if matched_sessions.is_empty() {
diff --git a/crates/leaf-cli/src/commands/update.rs b/crates/leaf-cli/src/commands/update.rs
index a22de096e1ac..27736863841d 100644
--- a/crates/leaf-cli/src/commands/update.rs
+++ b/crates/leaf-cli/src/commands/update.rs
@@ -1,6 +1,8 @@
use anyhow::{bail, Context, Result};
+use sha2::{Digest, Sha256};
use std::env;
use std::fs;
+use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -40,10 +42,90 @@ fn binary_name() -> &'static str {
}
}
+// ---------------------------------------------------------------------------
+// Sigstore / SLSA provenance verification
+// ---------------------------------------------------------------------------
+
+/// Compute the SHA-256 hex digest of a byte slice.
+fn sha256_hex(data: &[u8]) -> String {
+ let mut hasher = Sha256::new();
+ hasher.update(data);
+ format!("{:x}", hasher.finalize())
+}
+
+/// Verify the downloaded archive against GitHub's Sigstore SLSA provenance.
+///
+/// Uses the attestations published by `actions/attest-build-provenance` in the
+/// release/canary/nightly workflows (added in PR #7097). Verification fetches
+/// the attestation bundle from GitHub's attestation API and validates the
+/// Sigstore signature chain, Rekor transparency log inclusion, and artifact
+/// digest match.
+/// Returns `Ok(true)` when the attestation is fully verified, `Ok(false)` when
+/// verification was skipped with a warning (no attestation, transient error),
+/// and `Err` when verification actively fails (invalid signature, digest
+/// mismatch, etc.).
+async fn verify_provenance(archive_data: &[u8], tag: &str) -> Result {
+ let digest = sha256_hex(archive_data);
+ println!("Archive SHA-256: {digest}");
+
+ let mut tmp_file = tempfile::NamedTempFile::new()
+ .context("Failed to create temp file for provenance verification")?;
+ tmp_file
+ .write_all(archive_data)
+ .context("Failed to write archive to temp file")?;
+ tmp_file.flush().context("Failed to flush temp file")?;
+
+ let workflow = match tag {
+ "canary" => "canary.yml",
+ _ => "release.yml",
+ };
+
+ let token = std::env::var("GITHUB_TOKEN")
+ .ok()
+ .or_else(|| std::env::var("GH_TOKEN").ok());
+
+ println!("Verifying SLSA provenance via Sigstore...");
+
+ match sigstore_verification::verify_github_attestation(
+ tmp_file.path(),
+ "block",
+ "goose",
+ token.as_deref(),
+ Some(workflow),
+ )
+ .await
+ {
+ Ok(_) => {
+ println!("Sigstore provenance verification passed.");
+ Ok(true)
+ }
+ Err(sigstore_verification::AttestationError::NoAttestations) => {
+ eprintln!(
+ "Warning: No Sigstore attestation found for this build. \
+ This may be expected for canary or nightly builds."
+ );
+ Ok(false)
+ }
+ Err(sigstore_verification::AttestationError::Verification(msg)) => Err(anyhow::anyhow!(
+ "Sigstore verification failed: {}\n\nAborting update due to security check failure.",
+ msg
+ )),
+ Err(e) => {
+ eprintln!(
+ "Warning: Sigstore provenance check could not complete: {e}\n\
+ This may be expected for releases published before provenance \
+ attestations were enabled."
+ );
+ Ok(false)
+ }
+ }
+}
+
/// Update the goose binary to the latest release.
///
-/// Downloads the platform-appropriate archive from GitHub releases,
-/// extracts it, and replaces the current binary in-place.
+/// Downloads the platform-appropriate archive from GitHub releases, verifies
+/// its SLSA provenance via Sigstore, extracts it with path-traversal
+/// hardening, and replaces the current binary in-place.
pub async fn update(canary: bool, reconfigure: bool) -> Result<()> {
#[cfg(feature = "disable-update")]
{
@@ -78,7 +160,10 @@ pub async fn update(canary: bool, reconfigure: bool) -> Result<()> {
println!("Downloaded {} bytes.", bytes.len());
- // --- Extract to temp dir ------------------------------------------------
+ // --- Verify SLSA provenance via Sigstore --------------------------------
+ let provenance_verified = verify_provenance(&bytes, tag).await?;
+
+ // --- Extract to temp dir (hardened against path traversal) --------------
let tmp_dir = tempfile::tempdir().context("Failed to create temp directory")?;
#[cfg(target_os = "windows")]
@@ -103,7 +188,11 @@ pub async fn update(canary: bool, reconfigure: bool) -> Result<()> {
#[cfg(target_os = "windows")]
copy_dlls(&extracted_binary, ¤t_exe)?;
- println!("goose updated successfully!");
+ if provenance_verified {
+ println!("goose updated successfully (verified with Sigstore SLSA provenance).");
+ } else {
+ println!("goose updated successfully.");
+ }
// --- Reconfigure if requested -------------------------------------------
if reconfigure {
@@ -125,27 +214,94 @@ pub async fn update(canary: bool, reconfigure: bool) -> Result<()> {
// Archive extraction
// ---------------------------------------------------------------------------
-/// Extract a .zip archive (Windows).
+/// Extract a .zip archive with path-traversal hardening (Windows).
+///
+/// Iterates entries individually and uses `enclosed_name()` to reject any
+/// path that escapes the destination directory (zip-slip protection).
#[cfg(target_os = "windows")]
fn extract_zip(data: &[u8], dest: &Path) -> Result<()> {
use std::io::Cursor;
let cursor = Cursor::new(data);
let mut archive = zip::ZipArchive::new(cursor).context("Failed to open zip archive")?;
- archive
- .extract(dest)
- .context("Failed to extract zip archive")?;
+
+ for i in 0..archive.len() {
+ let mut entry = archive
+ .by_index(i)
+ .with_context(|| format!("Failed to read zip entry at index {i}"))?;
+
+ let safe_path = match entry.enclosed_name() {
+ Some(p) => p.to_owned(),
+ None => bail!("Zip entry has unsafe path: {}", entry.name()),
+ };
+
+ let target = dest.join(&safe_path);
+
+ if entry.is_dir() {
+ fs::create_dir_all(&target)?;
+ } else {
+ if let Some(parent) = target.parent() {
+ fs::create_dir_all(parent)?;
+ }
+ let mut out = fs::File::create(&target)?;
+ std::io::copy(&mut entry, &mut out)?;
+ }
+ }
+
Ok(())
}
-/// Extract a .tar.bz2 archive (macOS / Linux).
+/// Validate that an archive entry path is safe (no absolute paths, no `..`).
+fn validate_entry_path(path: &Path) -> Result<()> {
+ if path.is_absolute() {
+ bail!("Tar entry has absolute path: {}", path.display());
+ }
+ for component in path.components() {
+ if matches!(component, std::path::Component::ParentDir) {
+ bail!("Tar entry contains path traversal: {}", path.display());
+ }
+ }
+ Ok(())
+}
+
+/// Extract a .tar.bz2 archive with path-traversal hardening (macOS / Linux).
+///
+/// Iterates entries individually, rejecting any entry whose path is absolute
+/// or contains `..` components (tar-slip protection).
#[cfg(not(target_os = "windows"))]
fn extract_tar_bz2(data: &[u8], dest: &Path) -> Result<()> {
use bzip2::read::BzDecoder;
let decoder = BzDecoder::new(data);
let mut archive = tar::Archive::new(decoder);
- archive
- .unpack(dest)
- .context("Failed to extract tar.bz2 archive")?;
+
+ for entry in archive.entries().context("Failed to read tar entries")? {
+ let mut entry = entry.context("Failed to read tar entry")?;
+ let path = entry
+ .path()
+ .context("Failed to read entry path")?
+ .into_owned();
+
+ validate_entry_path(&path)?;
+
+ // Block symlinks and hardlinks whose targets escape the destination directory.
+ // Use entry.link_name() (not entry.header().link_name()) so GNU/PAX extended
+ // metadata (linkpath) is resolved; the header field alone may be truncated.
+ let link_target_opt = entry
+ .link_name()
+ .context("Failed to read link name from tar entry")?;
+ if let Some(link_target) = link_target_opt {
+ validate_entry_path(&link_target)?;
+ }
+
+ let target = dest.join(&path);
+ if let Some(parent) = target.parent() {
+ fs::create_dir_all(parent)?;
+ }
+
+ entry
+ .unpack(&target)
+ .with_context(|| format!("Failed to extract: {}", path.display()))?;
+ }
+
Ok(())
}
@@ -457,4 +613,143 @@ mod tests {
// DLL should be in goose-package too
assert!(tmp.path().join("goose-package/libtest.dll").exists());
}
+
+ // -----------------------------------------------------------------------
+ // SHA-256 digest tests
+ // -----------------------------------------------------------------------
+
+ #[test]
+ fn test_sha256_hex_known_value() {
+ let digest = sha256_hex(b"hello world");
+ assert_eq!(
+ digest,
+ "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
+ );
+ }
+
+ #[test]
+ fn test_sha256_hex_empty() {
+ let digest = sha256_hex(b"");
+ assert_eq!(
+ digest,
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ );
+ }
+
+ // -----------------------------------------------------------------------
+ // Path validation and extraction hardening tests
+ // -----------------------------------------------------------------------
+
+ #[test]
+ fn test_validate_entry_path_accepts_safe_paths() {
+ assert!(validate_entry_path(Path::new("goose")).is_ok());
+ assert!(validate_entry_path(Path::new("goose-package/goose")).is_ok());
+ assert!(validate_entry_path(Path::new("subdir/nested/file.txt")).is_ok());
+ }
+
+ #[test]
+ fn test_validate_entry_path_rejects_absolute() {
+ let result = validate_entry_path(Path::new("/etc/malicious"));
+ assert!(result.is_err());
+ assert!(result.unwrap_err().to_string().contains("absolute path"));
+ }
+
+ #[test]
+ fn test_validate_entry_path_rejects_traversal() {
+ let result = validate_entry_path(Path::new("../../escape.txt"));
+ assert!(result.is_err());
+ assert!(result.unwrap_err().to_string().contains("path traversal"));
+ }
+
+ #[test]
+ fn test_validate_entry_path_rejects_nested_traversal() {
+ let result = validate_entry_path(Path::new("safe/../../escape"));
+ assert!(result.is_err());
+ assert!(result.unwrap_err().to_string().contains("path traversal"));
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ #[test]
+ fn test_extract_tar_bz2_safe_archive() {
+ use bzip2::write::BzEncoder;
+ use bzip2::Compression;
+
+ let tmp = tempdir().unwrap();
+
+ let mut builder_buf = Vec::new();
+ {
+ let encoder = BzEncoder::new(&mut builder_buf, Compression::default());
+ let mut builder = tar::Builder::new(encoder);
+
+ let data = b"goose binary content";
+ let mut header = tar::Header::new_gnu();
+ header.set_size(data.len() as u64);
+ header.set_mode(0o755);
+ header.set_cksum();
+ builder
+ .append_data(&mut header, "goose-package/goose", &data[..])
+ .unwrap();
+ builder.into_inner().unwrap().finish().unwrap();
+ }
+
+ extract_tar_bz2(&builder_buf, tmp.path()).unwrap();
+
+ let extracted = tmp.path().join("goose-package/goose");
+ assert!(extracted.exists());
+ assert_eq!(
+ fs::read_to_string(extracted).unwrap(),
+ "goose binary content"
+ );
+ }
+
+ // -----------------------------------------------------------------------
+ // Sigstore provenance verification test
+ // -----------------------------------------------------------------------
+
+ #[tokio::test]
+ async fn test_verify_provenance_warns_on_missing_attestation() {
+ let result = verify_provenance(b"not a real archive", "stable").await;
+ // Network failures and missing attestations are soft warnings: Ok(false), not hard errors.
+ assert_eq!(
+ result.ok(),
+ Some(false),
+ "verify_provenance should return Ok(false) when attestations cannot be fetched"
+ );
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ #[test]
+ fn test_extract_tar_bz2_blocks_symlink_escape() {
+ use bzip2::write::BzEncoder;
+ use bzip2::Compression;
+
+ let tmp = tempdir().unwrap();
+
+ let mut builder_buf = Vec::new();
+ {
+ let encoder = BzEncoder::new(&mut builder_buf, Compression::default());
+ let mut builder = tar::Builder::new(encoder);
+
+ let mut header = tar::Header::new_gnu();
+ header.set_size(0);
+ header.set_mode(0o777);
+ header.set_cksum();
+ // Symlink whose target escapes the destination directory.
+ builder
+ .append_link(&mut header, "evil_link", "../../etc/passwd")
+ .unwrap();
+ builder.into_inner().unwrap().finish().unwrap();
+ }
+
+ let result = extract_tar_bz2(&builder_buf, tmp.path());
+ assert!(
+ result.is_err(),
+ "extraction should fail when a symlink target escapes the destination"
+ );
+ let err_msg = result.unwrap_err().to_string();
+ assert!(
+ err_msg.contains("path traversal"),
+ "error should mention path traversal, got: {err_msg}"
+ );
+ }
}
diff --git a/crates/leaf-cli/src/logging.rs b/crates/leaf-cli/src/logging.rs
index b126cc49573c..758614be3d6e 100644
--- a/crates/leaf-cli/src/logging.rs
+++ b/crates/leaf-cli/src/logging.rs
@@ -67,14 +67,12 @@ fn setup_logging_internal(name: Option<&str>, force: bool, verbose: bool) -> Res
EnvFilter::try_from_default_env().unwrap_or_else(|_| default_env_filter());
// Start building the subscriber
- let mut layers = vec![
- file_layer.with_filter(env_filter).boxed(),
- ];
+ let mut layers = vec![file_layer.with_filter(env_filter).boxed()];
// Console logging layer when verbose mode is enabled
if verbose {
- let console_filter = EnvFilter::try_from_default_env()
- .unwrap_or_else(|_| EnvFilter::new("debug"));
+ let console_filter =
+ EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
let console_layer = fmt::layer()
.with_target(true)
.with_level(true)
diff --git a/crates/leaf-cli/src/scenario_tests/scenarios.rs b/crates/leaf-cli/src/scenario_tests/scenarios.rs
index 6b476e3f0a8b..d1c7695742cc 100644
--- a/crates/leaf-cli/src/scenario_tests/scenarios.rs
+++ b/crates/leaf-cli/src/scenario_tests/scenarios.rs
@@ -17,7 +17,7 @@ mod tests {
|result| {
assert!(result.error.is_none());
assert!(
- result.last_message()?.to_lowercase().contains("leaf"),
+ result.last_message()?.to_lowercase().contains("goose"),
"Response should contain 'goose': {}",
result.last_message()?
);
diff --git a/crates/leaf-cli/src/session/builder.rs b/crates/leaf-cli/src/session/builder.rs
index 6de5dfbed7f6..a975f80efd7f 100644
--- a/crates/leaf-cli/src/session/builder.rs
+++ b/crates/leaf-cli/src/session/builder.rs
@@ -187,8 +187,7 @@ async fn offer_extension_debugging_help(
// Create a debugging prompt with context about the extension failure
let debug_prompt = format!(
"I'm having trouble starting an extension called '{}'. Here's the error I encountered:\n\n{}\n\nCan you help me diagnose what might be wrong and suggest how to fix it? Please consider common issues like:\n- Missing dependencies or tools\n- Configuration problems\n- Network connectivity (for remote extensions)\n- Permission issues\n- Path or environment variable problems",
- extension_name,
- error_message
+ extension_name, error_message
);
// Create a minimal agent for debugging
diff --git a/crates/leaf-cli/src/session/mod.rs b/crates/leaf-cli/src/session/mod.rs
index 47f4c96b87f2..9c8f4550b397 100644
--- a/crates/leaf-cli/src/session/mod.rs
+++ b/crates/leaf-cli/src/session/mod.rs
@@ -207,7 +207,9 @@ pub async fn classify_planner_response(
message_text: String,
provider: Arc,
) -> Result {
- let prompt = format!("The text below is the output from an AI model which can either provide a plan or list of clarifying questions. Based on the text below, decide if the output is a \"plan\" or \"clarifying questions\".\n---\n{message_text}");
+ let prompt = format!(
+ "The text below is the output from an AI model which can either provide a plan or list of clarifying questions. Based on the text below, decide if the output is a \"plan\" or \"clarifying questions\".\n---\n{message_text}"
+ );
let message = Message::user().with_text(&prompt);
let model_config = provider.get_model_config();
@@ -628,6 +630,7 @@ impl CliSession {
let _provider = self.agent.provider().await?;
+ println!();
output::run_status_hook("thinking");
output::show_thinking();
let start_time = Instant::now();
@@ -1751,7 +1754,9 @@ fn display_log_notification(
let _ = progress_bars.hide();
}
if !is_json_mode {
- print!("{}", formatted_message);
+ for line in formatted_message.lines() {
+ println!(" {}", console::style(line).dim());
+ }
std::io::stdout().flush().unwrap();
}
} else if ntype == "shell_output" {
@@ -1766,7 +1771,7 @@ fn display_log_notification(
let _ = progress_bars.hide();
}
if !is_json_mode {
- println!("{}", formatted_message);
+ println!(" {}", console::style(formatted_message).dim());
}
}
}
diff --git a/crates/leaf-cli/src/session/output.rs b/crates/leaf-cli/src/session/output.rs
index 70f236f0eb60..ec911f0945af 100644
--- a/crates/leaf-cli/src/session/output.rs
+++ b/crates/leaf-cli/src/session/output.rs
@@ -235,7 +235,7 @@ pub fn render_message(message: &Message, debug: bool) {
},
MessageContent::Text(text) => print_markdown(&text.text, theme),
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
- MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug),
+ MessageContent::ToolResponse(resp) => render_tool_response(resp, debug),
MessageContent::Image(image) => {
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
}
@@ -295,7 +295,7 @@ pub fn render_message_streaming(
}
MessageContent::ToolResponse(resp) => {
flush_markdown_buffer(buffer, theme);
- render_tool_response(resp, theme, debug);
+ render_tool_response(resp, debug);
}
MessageContent::ActionRequired(action) => {
flush_markdown_buffer(buffer, theme);
@@ -480,7 +480,7 @@ fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) {
Ok(call) => match call.name.to_string().as_str() {
name if is_shell_tool_name(name) => render_shell_request(call, debug),
name if is_file_tool_name(name) => render_text_editor_request(call, debug),
- "execute" | "execute_code" => render_execute_code_request(call, debug),
+ "execute_typescript" | "execute_code" => render_execute_code_request(call, debug),
"delegate" => render_delegate_request(call, debug),
"subagent" => render_delegate_request(call, debug),
"todo__write" => render_todo_request(call, debug),
@@ -491,7 +491,7 @@ fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) {
}
}
-fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {
+fn render_tool_response(resp: &ToolResponse, debug: bool) {
let config = Config::global();
match &resp.tool_result {
@@ -519,11 +519,52 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {
if debug {
println!("{:#?}", content);
} else if let Some(text) = content.as_text() {
- print_markdown(&text.text, theme);
+ print_tool_output(&text.text);
}
}
}
- Err(e) => print_markdown(&e.to_string(), theme),
+ Err(e) => {
+ println!(" {}", style(e.to_string()).red().dim());
+ }
+ }
+}
+
+fn print_tool_output(text: &str) {
+ if text.is_empty() {
+ return;
+ }
+ if !std::io::stdout().is_terminal() {
+ print!("{}", text);
+ return;
+ }
+ let max_lines = if get_show_full_tool_output() {
+ usize::MAX
+ } else {
+ 20
+ };
+ let lines: Vec<&str> = text.lines().collect();
+ if lines.len() <= max_lines {
+ for line in &lines {
+ println!(" {}", style(line).dim());
+ }
+ } else {
+ let head = max_lines / 2;
+ let tail = max_lines - head;
+ for line in &lines[..head] {
+ println!(" {}", style(line).dim());
+ }
+ println!(
+ " {}",
+ style(format!(
+ "... ({} lines hidden, /toggle to show all)",
+ lines.len() - head - tail
+ ))
+ .dim()
+ .italic()
+ );
+ for line in &lines[lines.len() - tail..] {
+ println!(" {}", style(line).dim());
+ }
}
}
@@ -822,7 +863,7 @@ pub fn render_subagent_tool_call(
arguments: Option<&JsonObject>,
debug: bool,
) {
- if tool_name == "code_execution__execute_code" {
+ if tool_name == "code_execution__execute_typescript" {
let tool_graph = arguments
.and_then(|args| args.get("tool_graph"))
.and_then(Value::as_array)
@@ -851,7 +892,7 @@ fn render_subagent_tool_graph(subagent_id: &str, tool_graph: &[Value]) {
" {} {} {} {} tool call{}",
style("▸").dim(),
style(format!("[subagent:{}]", short_id)).dim(),
- style("execute_code").dim(),
+ style("execute_typescript").dim(),
style(count).dim(),
plural,
);
@@ -904,6 +945,7 @@ fn print_tool_header(call: &CallToolRequestParams) {
)
};
println!();
+ println!(" {}", style("─".repeat(40)).dim());
println!("{}", tool_header);
}
diff --git a/crates/leaf-cli/src/session/task_execution_display/mod.rs b/crates/leaf-cli/src/session/task_execution_display/mod.rs
index 92d3330f3603..5301723275fb 100644
--- a/crates/leaf-cli/src/session/task_execution_display/mod.rs
+++ b/crates/leaf-cli/src/session/task_execution_display/mod.rs
@@ -9,10 +9,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(test)]
mod tests;
-const CLEAR_SCREEN: &str = "\x1b[2J\x1b[H";
-const MOVE_TO_PROGRESS_LINE: &str = "\x1b[4;1H";
-const CLEAR_TO_EOL: &str = "\x1b[K";
-const CLEAR_BELOW: &str = "\x1b[J";
pub const TASK_EXECUTION_NOTIFICATION_TYPE: &str = "task_execution";
static INITIAL_SHOWN: AtomicBool = AtomicBool::new(false);
@@ -85,18 +81,14 @@ fn format_tasks_update_from_event(event: &TaskExecutionNotificationEvent) -> Str
let mut display = String::new();
if !INITIAL_SHOWN.swap(true, Ordering::SeqCst) {
- display.push_str(CLEAR_SCREEN);
display.push_str("🎯 Task Execution Dashboard\n");
display.push_str("═══════════════════════════\n\n");
- } else {
- display.push_str(MOVE_TO_PROGRESS_LINE);
}
display.push_str(&format!(
- "📊 Progress: {} total | ⏳ {} pending | 🏃 {} running | ✅ {} completed | ❌ {} failed",
+ "📊 Progress: {} total | ⏳ {} pending | 🏃 {} running | ✅ {} completed | ❌ {} failed\n\n",
stats.total, stats.pending, stats.running, stats.completed, stats.failed
));
- display.push_str(&format!("{}\n\n", CLEAR_TO_EOL));
let mut sorted_tasks = tasks.clone();
sorted_tasks.sort_by(|a, b| a.id.cmp(&b.id));
@@ -105,7 +97,6 @@ fn format_tasks_update_from_event(event: &TaskExecutionNotificationEvent) -> Str
display.push_str(&format_task_display(&task));
}
- display.push_str(CLEAR_BELOW);
display
} else {
String::new()
@@ -155,25 +146,22 @@ fn format_task_display(task: &TaskInfo) -> String {
};
task_display.push_str(&format!(
- "{} {} ({}){}\n",
- status_icon, task.task_name, task.task_type, CLEAR_TO_EOL
+ "{} {} ({})\n",
+ status_icon, task.task_name, task.task_type
));
if !task.task_metadata.is_empty() {
- task_display.push_str(&format!(
- " 📋 Parameters: {}{}\n",
- task.task_metadata, CLEAR_TO_EOL
- ));
+ task_display.push_str(&format!(" 📋 Parameters: {}\n", task.task_metadata));
}
if let Some(duration_secs) = task.duration_secs {
- task_display.push_str(&format!(" ⏱️ {:.1}s{}\n", duration_secs, CLEAR_TO_EOL));
+ task_display.push_str(&format!(" ⏱️ {:.1}s\n", duration_secs));
}
if matches!(task.status, TaskStatus::Running) && !task.current_output.trim().is_empty() {
let processed_output = process_output_for_display(&task.current_output);
if !processed_output.is_empty() {
- task_display.push_str(&format!(" 💬 {}{}\n", processed_output, CLEAR_TO_EOL));
+ task_display.push_str(&format!(" 💬 {}\n", processed_output));
}
}
@@ -181,7 +169,7 @@ fn format_task_display(task: &TaskInfo) -> String {
if let Some(result_data) = &task.result_data {
let result_preview = format_result_data_for_display(result_data);
if !result_preview.is_empty() {
- task_display.push_str(&format!(" 📄 {}{}\n", result_preview, CLEAR_TO_EOL));
+ task_display.push_str(&format!(" 📄 {}\n", result_preview));
}
}
}
@@ -189,14 +177,10 @@ fn format_task_display(task: &TaskInfo) -> String {
if matches!(task.status, TaskStatus::Failed) {
if let Some(error) = &task.error {
let error_preview = safe_truncate(error, 80);
- task_display.push_str(&format!(
- " ⚠️ {}{}\n",
- error_preview.replace('\n', " "),
- CLEAR_TO_EOL
- ));
+ task_display.push_str(&format!(" ⚠️ {}\n", error_preview.replace('\n', " ")));
}
}
- task_display.push_str(&format!("{}\n", CLEAR_TO_EOL));
+ task_display.push('\n');
task_display
}
diff --git a/crates/leaf-cli/src/session/task_execution_display/tests.rs b/crates/leaf-cli/src/session/task_execution_display/tests.rs
index 722f3df63499..c044693f2cd6 100644
--- a/crates/leaf-cli/src/session/task_execution_display/tests.rs
+++ b/crates/leaf-cli/src/session/task_execution_display/tests.rs
@@ -145,7 +145,7 @@ fn test_format_tasks_update_from_event() {
let result2 = format_tasks_update_from_event(&event);
assert!(!result2.contains("🎯 Task Execution Dashboard"));
- assert!(result2.contains(MOVE_TO_PROGRESS_LINE));
+ assert!(result2.contains("📊 Progress:"));
}
#[test]
diff --git a/crates/leaf-server/src/routes/session_events.rs b/crates/leaf-server/src/routes/session_events.rs
index 814e69bfdf15..694735fef967 100644
--- a/crates/leaf-server/src/routes/session_events.rs
+++ b/crates/leaf-server/src/routes/session_events.rs
@@ -1,5 +1,6 @@
use crate::routes::errors::ErrorResponse;
use crate::routes::reply::{get_token_state, track_tool_telemetry, MessageEvent};
+use crate::session_event_bus::RequestGuard;
use crate::state::AppState;
use axum::{
extract::{DefaultBodyLimit, Path, State},
@@ -320,7 +321,13 @@ pub async fn session_reply(
}
let bus = state.get_or_create_event_bus(&session_id).await;
- let cancel_token = bus.register_request(request_id.clone()).await;
+
+ let cancel_token = bus
+ .try_register_request(request_id.clone())
+ .await
+ .map_err(|_| {
+ ErrorResponse::bad_request("Session already has an active request. Cancel it first.")
+ })?;
let user_message = request.user_message;
let override_conversation = request.override_conversation;
@@ -332,6 +339,8 @@ pub async fn session_reply(
let task_bus = bus.clone();
drop(tokio::spawn(async move {
+ let mut _guard = RequestGuard::new(task_bus.clone(), task_request_id.clone());
+
let publish = |rid: Option, event: MessageEvent| {
let bus = task_bus.clone();
async move {
@@ -350,7 +359,6 @@ pub async fn session_reply(
},
)
.await;
- task_bus.cleanup_request(&task_request_id).await;
return;
}
};
@@ -370,7 +378,6 @@ pub async fn session_reply(
},
)
.await;
- task_bus.cleanup_request(&task_request_id).await;
return;
}
};
@@ -420,7 +427,6 @@ pub async fn session_reply(
},
)
.await;
- task_bus.cleanup_request(&task_request_id).await;
return;
}
};
@@ -562,6 +568,7 @@ pub async fn session_reply(
)
.await;
+ _guard.disarm();
task_bus.cleanup_request(&task_request_id).await;
}));
diff --git a/crates/leaf-server/src/session_event_bus.rs b/crates/leaf-server/src/session_event_bus.rs
index e7a6dddc07c6..a24114c2918a 100644
--- a/crates/leaf-server/src/session_event_bus.rs
+++ b/crates/leaf-server/src/session_event_bus.rs
@@ -116,7 +116,7 @@ impl SessionEventBus {
requests.keys().cloned().collect()
}
- /// Register a new request and return its cancellation token.
+ #[cfg(test)]
pub async fn register_request(&self, request_id: String) -> CancellationToken {
let token = CancellationToken::new();
let mut requests = self.active_requests.lock().await;
@@ -124,6 +124,20 @@ impl SessionEventBus {
token
}
+ /// Atomically check no requests are active and register one. Returns Err if busy.
+ pub async fn try_register_request(
+ &self,
+ request_id: String,
+ ) -> Result {
+ let mut requests = self.active_requests.lock().await;
+ if !requests.is_empty() {
+ return Err("Session already has an active request".into());
+ }
+ let token = CancellationToken::new();
+ requests.insert(request_id, token.clone());
+ Ok(token)
+ }
+
/// Cancel a specific request by request_id.
pub async fn cancel_request(&self, request_id: &str) -> bool {
let requests = self.active_requests.lock().await;
@@ -156,6 +170,38 @@ impl Default for SessionEventBus {
}
}
+pub struct RequestGuard {
+ bus: std::sync::Arc,
+ request_id: String,
+ disarmed: bool,
+}
+
+impl RequestGuard {
+ pub fn new(bus: std::sync::Arc, request_id: String) -> Self {
+ Self {
+ bus,
+ request_id,
+ disarmed: false,
+ }
+ }
+
+ pub fn disarm(&mut self) {
+ self.disarmed = true;
+ }
+}
+
+impl Drop for RequestGuard {
+ fn drop(&mut self) {
+ if !self.disarmed {
+ let bus = self.bus.clone();
+ let request_id = self.request_id.clone();
+ tokio::spawn(async move {
+ bus.cleanup_request(&request_id).await;
+ });
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/leaf/src/agents/agent.rs b/crates/leaf/src/agents/agent.rs
index b5e6398c9e24..e3328aa84e89 100644
--- a/crates/leaf/src/agents/agent.rs
+++ b/crates/leaf/src/agents/agent.rs
@@ -1533,6 +1533,11 @@ impl Agent {
Err(e) => {
crate::posthog::emit_error("compaction_failed", &e.to_string());
error!("Compaction failed: {}", e);
+ yield AgentEvent::Message(
+ Message::assistant().with_text(
+ format!("Ran into this error trying to compact: {e}.\n\nPlease try again or create a new session")
+ )
+ );
break;
}
}
diff --git a/crates/leaf/src/agents/extension_manager.rs b/crates/leaf/src/agents/extension_manager.rs
index 32fd9e497170..1698d9cba2a2 100644
--- a/crates/leaf/src/agents/extension_manager.rs
+++ b/crates/leaf/src/agents/extension_manager.rs
@@ -222,6 +222,12 @@ pub fn is_first_class_extension(name: &str) -> bool {
.is_some_and(|def| def.unprefixed_tools)
}
+pub fn is_hidden_extension(name: &str) -> bool {
+ PLATFORM_EXTENSIONS
+ .get(name_to_key(name).as_str())
+ .is_some_and(|def| def.hidden)
+}
+
/// Result of resolving a tool call to its owning extension
struct ResolvedTool {
extension_name: String,
@@ -1543,10 +1549,10 @@ impl ExtensionManager {
pub async fn search_available_extensions(&self) -> Result, ErrorData> {
let mut output_parts = vec![];
- // First get disabled extensions from current config
+ // First get disabled extensions from current config (skip hidden ones)
let mut disabled_extensions: Vec = vec![];
for extension in get_all_extensions() {
- if !extension.enabled {
+ if !extension.enabled && !is_hidden_extension(&extension.config.name()) {
let config = extension.config.clone();
let description = match &config {
ExtensionConfig::Builtin {
@@ -1571,9 +1577,15 @@ impl ExtensionManager {
}
}
- // Get currently enabled extensions that can be disabled
- let enabled_extensions: Vec =
- self.extensions.lock().await.keys().cloned().collect();
+ // Get currently enabled extensions that can be disabled (skip hidden ones)
+ let enabled_extensions: Vec = self
+ .extensions
+ .lock()
+ .await
+ .keys()
+ .filter(|name| !is_hidden_extension(name))
+ .cloned()
+ .collect();
// Build output string
if !disabled_extensions.is_empty() {
diff --git a/crates/leaf/src/agents/platform_extensions/code_execution.rs b/crates/leaf/src/agents/platform_extensions/code_execution.rs
index 22e0f43ae76c..26012035678b 100644
--- a/crates/leaf/src/agents/platform_extensions/code_execution.rs
+++ b/crates/leaf/src/agents/platform_extensions/code_execution.rs
@@ -4,11 +4,13 @@ use crate::agents::mcp_client::{Error, McpClientTrait};
use crate::agents::tool_execution::ToolCallContext;
use anyhow::Result;
use async_trait::async_trait;
-use indoc::indoc;
-use pctx_code_mode::config::ToolDisclosure;
-use pctx_code_mode::model::{CallbackConfig, ExecuteInput, GetFunctionDetailsInput};
-use pctx_code_mode::registry::{CallbackFn, PctxRegistry};
-use pctx_code_mode::CodeMode;
+use pctx_code_mode::{
+ config::ToolDisclosure,
+ descriptions::{tools as tool_descriptions, workflow::get_workflow_description},
+ model::{CallbackConfig, ExecuteBashInput, ExecuteInput, GetFunctionDetailsInput},
+ registry::{CallbackFn, PctxRegistry},
+ CodeMode,
+};
use rmcp::model::{
CallToolRequestParams, CallToolResult, Content, Implementation, InitializeResult, JsonObject,
ListToolsResult, RawContent, Role, ServerCapabilities, Tool as McpTool, ToolAnnotations,
@@ -29,6 +31,7 @@ pub static EXTENSION_NAME: &str = "code_execution";
pub struct CodeExecutionClient {
info: InitializeResult,
context: PlatformExtensionContext,
+ disclosure: ToolDisclosure,
state: RwLock>,
}
@@ -54,32 +57,18 @@ pub struct ExecuteWithToolGraph {
}
impl CodeExecutionClient {
- pub fn new(context: PlatformExtensionContext) -> Result {
+ pub fn new(context: PlatformExtensionContext, disclosure: ToolDisclosure) -> Result {
let info = InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
.with_server_info(
Implementation::new(EXTENSION_NAME.to_string(), "1.0.0".to_string())
.with_title("Code Mode"),
)
- .with_instructions(indoc! {r#"
- BATCH MULTIPLE TOOL CALLS INTO ONE execute CALL.
-
- This extension exists to reduce round-trips. When a task requires multiple tool calls:
- - WRONG: Multiple execute calls, each with one tool
- - RIGHT: One execute call with a script that calls all needed tools
-
- IMPORTANT: All tool calls are ASYNC. Use await for each call.
-
- Workflow:
- 1. Use the list_functions and get_function_details tools to discover tools and signatures
- 2. Write ONE script that calls ALL tools needed for the task, no need to import anything,
- all the namespaces returned by list_functions and get_function_details will be available
- 3. Chain results: use output from one tool as input to the next
- 4. Only return and console.log data you need, tools could have very large responses.
- "#}.to_string());
+ .with_instructions(get_workflow_description(disclosure));
Ok(Self {
info,
context,
+ disclosure,
state: RwLock::new(None),
})
}
@@ -95,19 +84,20 @@ impl CodeExecutionClient {
.get_prefixed_tools_excluding(session_id, EXTENSION_NAME)
.await
.ok()?;
+
let mut cfgs = vec![];
for tool in tools {
- let full_name = tool.name.to_string();
- let (namespace, name) = if let Some((server, tool_name)) = full_name.split_once("__") {
- (server.to_string(), tool_name.to_string())
+ let (name, namespace) = if let Some((prefix, tool_name)) = tool.name.split_once("__") {
+ (tool_name.to_string(), Some(prefix.to_string()))
} else if let Some(owner) = get_tool_owner(&tool) {
- (owner, full_name)
+ (tool.name.to_string(), Some(owner))
} else {
- continue;
+ (tool.name.to_string(), None)
};
+
cfgs.push(CallbackConfig {
name,
- namespace: Some(namespace),
+ namespace,
description: tool.description.as_ref().map(|d| d.to_string()),
input_schema: Some(json!(tool.input_schema)),
output_schema: tool.output_schema.as_ref().map(|s| json!(s)),
@@ -146,10 +136,11 @@ impl CodeExecutionClient {
let state = CodeModeState::new(cfgs)?;
let code_mode = state.code_mode.clone();
*guard = Some(state);
+
Ok(code_mode)
}
- /// Build a CallbackRegistry with all tool callbacks registered
+ /// Build a PctxRegistry with all tool callbacks registered
fn build_callback_registry(
&self,
session_id: &str,
@@ -164,7 +155,14 @@ impl CodeExecutionClient {
let registry = PctxRegistry::default();
for cfg in code_mode.callbacks() {
- let full_name = format!("{}__{}", cfg.namespace.as_deref().unwrap_or(""), &cfg.name);
+ let full_name = format!(
+ "{}{}",
+ cfg.namespace
+ .clone()
+ .map(|n| format!("{n}__"))
+ .unwrap_or_default(),
+ &cfg.name
+ );
let callback = create_tool_callback(session_id.to_string(), full_name, manager.clone());
registry
.add_callback(&cfg.id(), callback)
@@ -200,8 +198,43 @@ impl CodeExecutionClient {
Ok(vec![Content::text(output.code)])
}
- /// Handle the execute tool call
- async fn handle_execute(
+ /// Handle the execute bash tool call
+ async fn handle_execute_bash(
+ &self,
+ session_id: &str,
+ arguments: Option,
+ ) -> Result, String> {
+ let input: ExecuteBashInput = arguments
+ .map(|args| serde_json::from_value(Value::Object(args)))
+ .transpose()
+ .map_err(|e| format!("Failed to parse arguments: {e}"))?
+ .ok_or("Missing arguments for execute_bash")?;
+ let command = input.command;
+ let code_mode = self.get_code_mode(session_id).await?;
+
+ // Deno runtime is not Send, so we need to run it in a blocking task
+ // with its own tokio runtime
+ let output = tokio::task::spawn_blocking(move || {
+ let rt = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .map_err(|e| format!("Failed to create runtime: {e}"))?;
+
+ rt.block_on(async move {
+ code_mode
+ .execute_bash(&command)
+ .await
+ .map_err(|e| format!("Typescript execution error: {e}"))
+ })
+ })
+ .await
+ .map_err(|e| format!("Typescript execution task failed: {e}"))??;
+
+ Ok(vec![Content::text(output.markdown())])
+ }
+
+ /// Handle the execute typescript tool call
+ async fn handle_execute_typescript(
&self,
session_id: &str,
arguments: Option,
@@ -210,11 +243,12 @@ impl CodeExecutionClient {
.map(|args| serde_json::from_value(Value::Object(args)))
.transpose()
.map_err(|e| format!("Failed to parse arguments: {e}"))?
- .ok_or("Missing arguments for execute")?;
+ .ok_or("Missing arguments for execute_typescript")?;
let code_mode = self.get_code_mode(session_id).await?;
let registry = self.build_callback_registry(session_id, &code_mode)?;
let code = args.input.code.clone();
+ let disclosure = self.disclosure;
// Deno runtime is not Send, so we need to run it in a blocking task
// with its own tokio runtime
@@ -226,13 +260,13 @@ impl CodeExecutionClient {
rt.block_on(async move {
code_mode
- .execute_typescript(&code, ToolDisclosure::default(), Some(registry))
+ .execute_typescript(&code, disclosure, Some(registry))
.await
- .map_err(|e| format!("Execution error: {e}"))
+ .map_err(|e| format!("Typescript execution error: {e}"))
})
})
.await
- .map_err(|e| format!("Execution task failed: {e}"))??;
+ .map_err(|e| format!("Typescript execution task failed: {e}"))??;
Ok(vec![Content::text(output.markdown())])
}
@@ -316,97 +350,79 @@ impl McpClientTrait for CodeExecutionClient {
}))
.expect("valid schema");
- Ok(ListToolsResult {
- tools: vec![
- McpTool::new(
- "list_functions".to_string(),
- indoc! {r#"
- List all available functions across all namespaces.
-
- This will not return function input and output types.
- After determining which functions are needed use
- get_function_details to get input and output type
- information about specific functions.
- "#}
- .to_string(),
- empty_schema,
- )
- .annotate(ToolAnnotations::from_raw(
- Some("List functions".to_string()),
- Some(true),
- Some(false),
- Some(true),
- Some(false),
- )),
- McpTool::new(
- "get_function_details".to_string(),
- indoc! {r#"
- Get detailed type information for specific functions.
-
- Provide a list of function identifiers in the format "Namespace.functionName"
- (e.g., "Developer.shell", "Github.createIssue").
-
- Returns full TypeScript interface definitions with parameter types,
- return types, and descriptions for the requested functions.
- "#}
- .to_string(),
- schema::(),
- )
- .annotate(ToolAnnotations::from_raw(
- Some("Get function details".to_string()),
- Some(true),
- Some(false),
- Some(true),
- Some(false),
- )),
- McpTool::new(
- "execute".to_string(),
- indoc! {r#"
- Execute TypeScript code that calls available functions.
-
- SYNTAX - TypeScript with async run() function:
- ```typescript
- async function run() {
- // Access functions via Namespace.functionName({ params }) — always camelCase
- const files = await Developer.shell({ command: "ls -la" });
- const readme = await Developer.shell({ command: "cat ./README.md" });
- return { files, readme };
- }
- ```
-
- TOOL_GRAPH: Always provide tool_graph to describe the execution flow for the UI.
- Each node has: tool (Namespace.functionName), description (what it does), depends_on (indices of dependencies).
- Example for chained operations:
- [
- {"tool": "Developer.shell", "description": "list files", "depends_on": []},
- {"tool": "Developer.shell", "description": "read README.md", "depends_on": []},
- {"tool": "Developer.write", "description": "write output.txt", "depends_on": [0, 1]}
- ]
-
- KEY RULES:
- - Code MUST define an async function named `run()`
- - All function calls are async - use `await`
- - Function names are always camelCase (e.g., Developer.shell, Github.listIssues, Github.createIssue)
- - Return value from `run()` is the result, all `console.log()` output will be returned as well.
- - Only functions from `list_functions()` and `console` methods are available — no `fetch()`, `fs`, or other Node/Deno APIs
- - Variables don't persist between `execute()` calls - return or log anything you need later
- - Code runs in an isolated sandbox with restricted network access
-
- HANDLING RETURN VALUES:
- - If a function returns `any`, do NOT assume its shape - log it first: `console.log(JSON.stringify(result))`
- - Many functions return wrapper objects, not raw arrays - check the response structure before calling .filter(), .map(), etc.
- - Always inspect unfamiliar return values with console.log() before processing them
-
- TOKEN USAGE WARNING: This tool could return LARGE responses if your code returns big objects.
- To minimize tokens:
- - Filter/map/reduce data IN YOUR CODE before returning
- - Only return specific fields you need (e.g., return {id: result.id, count: items.length})
- - Use console.log() for intermediate results instead of returning everything
- - Avoid returning full API responses - extract just what you need
-
- BEFORE CALLING: Use list_functions or get_function_details to check available functions and their parameters.
- "#}
- .to_string(),
+ let tools = match self.disclosure {
+ ToolDisclosure::Catalog => {
+ vec![
+ McpTool::new(
+ "list_functions".to_string(),
+ tool_descriptions::LIST_FUNCTIONS.to_string(),
+ empty_schema,
+ )
+ .annotate(ToolAnnotations::from_raw(
+ Some("List functions".to_string()),
+ Some(true),
+ Some(false),
+ Some(true),
+ Some(false),
+ )),
+ McpTool::new(
+ "get_function_details".to_string(),
+ tool_descriptions::GET_FUNCTION_DETAILS.to_string(),
+ schema::(),
+ )
+ .annotate(ToolAnnotations::from_raw(
+ Some("Get function details".to_string()),
+ Some(true),
+ Some(false),
+ Some(true),
+ Some(false),
+ )),
+ McpTool::new(
+ "execute_typescript".to_string(),
+ tool_descriptions::EXECUTE_TYPESCRIPT_CATALOG.to_string(),
+ schema::(),
+ )
+ .annotate(ToolAnnotations::from_raw(
+ Some("Execute TypeScript".to_string()),
+ Some(false),
+ Some(true),
+ Some(false),
+ Some(true),
+ )),
+ ]
+ }
+ ToolDisclosure::Filesystem => {
+ vec![
+ McpTool::new(
+ "execute_bash".to_string(),
+ tool_descriptions::EXECUTE_BASH.to_string(),
+ schema::(),
+ )
+ .annotate(ToolAnnotations::from_raw(
+ Some("Get function details".to_string()),
+ Some(true),
+ Some(false),
+ Some(true),
+ Some(false),
+ )),
+ McpTool::new(
+ "execute_typescript".to_string(),
+ tool_descriptions::EXECUTE_TYPESCRIPT_FILESYSTEM.to_string(),
+ schema::(),
+ )
+ .annotate(ToolAnnotations::from_raw(
+ Some("Execute TypeScript".to_string()),
+ Some(false),
+ Some(true),
+ Some(false),
+ Some(true),
+ )),
+ ]
+ }
+ ToolDisclosure::Sidecar => {
+ vec![McpTool::new(
+ "execute_typescript".to_string(),
+ tool_descriptions::EXECUTE_TYPESCRIPT_SIDECAR.to_string(),
schema::(),
)
.annotate(ToolAnnotations::from_raw(
@@ -415,10 +431,14 @@ impl McpClientTrait for CodeExecutionClient {
Some(true),
Some(false),
Some(true),
- )),
- ],
- next_cursor: None,
+ ))]
+ }
+ };
+
+ Ok(ListToolsResult {
meta: None,
+ next_cursor: None,
+ tools,
})
}
@@ -436,7 +456,8 @@ impl McpClientTrait for CodeExecutionClient {
self.handle_get_function_details(session_id, arguments)
.await
}
- "execute" => self.handle_execute(session_id, arguments).await,
+ "execute_bash" => self.handle_execute_bash(session_id, arguments).await,
+ "execute_typescript" => self.handle_execute_typescript(session_id, arguments).await,
_ => Err(format!("Unknown tool: {name}")),
};
@@ -454,28 +475,48 @@ impl McpClientTrait for CodeExecutionClient {
async fn get_moim(&self, session_id: &str) -> Option {
let code_mode = self.get_code_mode(session_id).await.ok()?;
- let available: Vec<_> = code_mode
- .list_functions()
- .functions
- .iter()
- .map(|f| format!("{}.{}", &f.namespace, &f.name))
- .collect();
+
+ let disclosure_style_moim = match self.disclosure {
+ ToolDisclosure::Catalog => {
+ let available_fns: Vec<_> = code_mode
+ .list_functions()
+ .functions
+ .iter()
+ .map(|f| format!("{}.{}", &f.namespace, &f.name))
+ .collect();
+ format!("Available functions: {}
+
+ Use the list_functions & get_function_details tools to see tool signatures and input/output types before calling execute_typescript.", available_fns.join(", "))
+ }
+ ToolDisclosure::Filesystem => {
+ let available_filepaths: Vec<_> = code_mode
+ .virtual_fs().keys().map(String::from).collect();
+ format!("Use execute_bash to search and read the tool signatures and input/output types before calling execute_typescript. The available files are: {}", available_filepaths.join(", "))
+ },
+ ToolDisclosure::Sidecar => "Prioritize calling tools with the execute_typescript tool, especially when multiple tools can be called in one script.".into(),
+ };
Some(format!(
indoc::indoc! {r#"
- ALWAYS batch multiple tool operations into ONE execute call.
- - WRONG: Separate execute calls for read file, then write file
- - RIGHT: One execute with an async run() function that reads AND writes
-
- Available namespaces: {}
+ ALWAYS batch multiple tool operations into ONE execute_typescript call.
+ - WRONG: Separate execute_typescript calls for read file, then write file
+ - RIGHT: One execute_typescript with an async run() function that reads AND writes AND logs/returns as little information as needed for the next step.
- Use the list_functions & get_function_details tools to see tool signatures and input/output types before calling unfamiliar tools.
+ {}
"#},
- available.join(", ")
+ disclosure_style_moim
))
}
}
+pub fn get_tool_disclosure() -> ToolDisclosure {
+ let config = crate::config::Config::global();
+ let tool_disclosure_str: String = config
+ .get_param("CODE_MODE_TOOL_DISCLOSURE")
+ .unwrap_or_else(|_| "catalog".to_string());
+ serde_json::from_value(serde_json::json!(tool_disclosure_str)).unwrap_or_default()
+}
+
struct CodeModeState {
code_mode: CodeMode,
hash: u64,
diff --git a/crates/leaf/src/agents/platform_extensions/mod.rs b/crates/leaf/src/agents/platform_extensions/mod.rs
index 23a484c17c9f..9813f9a097ab 100644
--- a/crates/leaf/src/agents/platform_extensions/mod.rs
+++ b/crates/leaf/src/agents/platform_extensions/mod.rs
@@ -3,6 +3,7 @@ pub mod apps;
pub mod chatrecall;
pub mod developer;
pub mod ext_manager;
+pub mod orchestrator;
pub mod summarize;
pub mod summon;
pub mod todo;
@@ -35,6 +36,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
"Analyze code structure with tree-sitter: directory overviews, file details, symbol call graphs",
default_enabled: true,
unprefixed_tools: true,
+ hidden: false,
client_factory: |ctx| Box::new(analyze::AnalyzeClient::new(ctx).unwrap()),
},
);
@@ -48,6 +50,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
"Enable a todo list for goose so it can keep track of what it is doing",
default_enabled: true,
unprefixed_tools: false,
+ hidden: false,
client_factory: |ctx| Box::new(todo::TodoClient::new(ctx).unwrap()),
},
);
@@ -61,6 +64,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
"Create and manage custom Goose apps through chat. Apps are HTML/CSS/JavaScript and run in sandboxed windows.",
default_enabled: true,
unprefixed_tools: false,
+ hidden: false,
client_factory: |ctx| Box::new(apps::AppsManagerClient::new(ctx).unwrap()),
},
);
@@ -74,6 +78,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
"Search past conversations and load session summaries for contextual memory",
default_enabled: false,
unprefixed_tools: false,
+ hidden: false,
client_factory: |ctx| Box::new(chatrecall::ChatRecallClient::new(ctx).unwrap()),
},
);
@@ -87,6 +92,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
"Enable extension management tools for discovering, enabling, and disabling extensions",
default_enabled: true,
unprefixed_tools: false,
+ hidden: false,
client_factory: |ctx| Box::new(ext_manager::ExtensionManagerClient::new(ctx).unwrap()),
},
);
@@ -99,6 +105,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
description: "Load knowledge and delegate tasks to subagents",
default_enabled: true,
unprefixed_tools: true,
+ hidden: false,
client_factory: |ctx| Box::new(summon::SummonClient::new(ctx).unwrap()),
},
);
@@ -111,6 +118,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
description: "Load files/directories and get an LLM summary in a single call",
default_enabled: false,
unprefixed_tools: false,
+ hidden: false,
client_factory: |ctx| Box::new(summarize::SummarizeClient::new(ctx).unwrap()),
},
);
@@ -123,10 +131,25 @@ pub static PLATFORM_EXTENSIONS: Lazy
description: "Write and edit files, and execute shell commands",
default_enabled: true,
unprefixed_tools: true,
+ hidden: false,
client_factory: |ctx| Box::new(developer::DeveloperClient::new(ctx).unwrap()),
},
);
+ map.insert(
+ orchestrator::EXTENSION_NAME,
+ PlatformExtensionDef {
+ name: orchestrator::EXTENSION_NAME,
+ display_name: "Orchestrator",
+ description:
+ "Manage agent sessions: list, view, start, send messages, interrupt, and stop agents",
+ default_enabled: false,
+ unprefixed_tools: false,
+ hidden: true,
+ client_factory: |ctx| Box::new(orchestrator::OrchestratorClient::new(ctx).unwrap()),
+ },
+ );
+
map.insert(
tom::EXTENSION_NAME,
PlatformExtensionDef {
@@ -136,6 +159,7 @@ pub static PLATFORM_EXTENSIONS: Lazy
"Inject custom context into every turn via GOOSE_MOIM_MESSAGE_TEXT and GOOSE_MOIM_MESSAGE_FILE environment variables",
default_enabled: true,
unprefixed_tools: false,
+ hidden: false,
client_factory: |ctx| Box::new(tom::TomClient::new(ctx).unwrap()),
},
);
@@ -193,5 +217,7 @@ pub struct PlatformExtensionDef {
pub default_enabled: bool,
/// If true, tools are exposed without extension prefix for intuitive first-class use.
pub unprefixed_tools: bool,
+ /// If true, the extension is not shown in the UI or discoverable via search_available_extensions.
+ pub hidden: bool,
pub client_factory: fn(PlatformExtensionContext) -> Box,
}
diff --git a/crates/leaf/src/agents/platform_extensions/orchestrator.rs b/crates/leaf/src/agents/platform_extensions/orchestrator.rs
new file mode 100644
index 000000000000..57c3af3e98b9
--- /dev/null
+++ b/crates/leaf/src/agents/platform_extensions/orchestrator.rs
@@ -0,0 +1,635 @@
+use crate::agents::extension::PlatformExtensionContext;
+use crate::agents::mcp_client::{Error, McpClientTrait};
+use crate::agents::tool_execution::ToolCallContext;
+use crate::agents::{AgentEvent, SessionConfig};
+use crate::config::LeafMode;
+use crate::context_mgmt::format_message_for_compacting;
+use crate::conversation::message::Message;
+use crate::execution::manager::AgentManager;
+use crate::providers::base::Provider;
+use crate::session::session_manager::SessionType;
+use anyhow::Result;
+use async_trait::async_trait;
+use futures::StreamExt;
+use rmcp::model::{
+ CallToolResult, Content, Implementation, InitializeResult, JsonObject, ListToolsResult,
+ ServerCapabilities, Tool,
+};
+use schemars::{schema_for, JsonSchema};
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
+use std::sync::Arc;
+use tokio_util::sync::CancellationToken;
+
+pub static EXTENSION_NAME: &str = "orchestrator";
+
+struct CancelTokenGuard {
+ manager: Arc,
+ session_id: String,
+ disarmed: bool,
+}
+
+impl CancelTokenGuard {
+ fn new(manager: Arc, session_id: String) -> Self {
+ Self {
+ manager,
+ session_id,
+ disarmed: false,
+ }
+ }
+
+ fn disarm(&mut self) {
+ self.disarmed = true;
+ }
+}
+
+impl Drop for CancelTokenGuard {
+ fn drop(&mut self) {
+ if !self.disarmed {
+ let manager = self.manager.clone();
+ let session_id = self.session_id.clone();
+ tokio::spawn(async move {
+ manager.unregister_cancel_token(&session_id).await;
+ });
+ }
+ }
+}
+
+const DEFAULT_LIST_LIMIT: usize = 10;
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+struct ListSessionsParams {
+ /// Filter by session type: "user", "sub_agent", "scheduled", "hidden", "terminal", "gateway".
+ /// If omitted, returns all session types.
+ session_type: Option,
+ /// Maximum number of sessions to return (most recent first). Defaults to 10.
+ last_n: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+struct ViewSessionParams {
+ /// The session ID to inspect
+ session_id: String,
+ /// How to view the conversation: "first_last" returns the first and last message,
+ /// "summarize" calls the LLM to produce a summary. If omitted, returns first and last.
+ mode: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+struct StartAgentParams {
+ /// Working directory for the new agent session
+ working_dir: String,
+ /// Human-readable name for the session
+ name: Option,
+ // TODO: add a "model_tier" parameter (e.g. "fast" vs "normal") to let the orchestrator
+ // choose between a fast/cheap model and the default one. For now we inherit the
+ // orchestrator's own provider and model.
+}
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+struct SendMessageParams {
+ /// The session ID of the agent to send a message to
+ session_id: String,
+ /// The message text to send
+ message: String,
+}
+
+#[derive(Debug, Serialize, Deserialize, JsonSchema)]
+struct InterruptAgentParams {
+ /// The session ID of the agent to interrupt
+ session_id: String,
+}
+
+pub struct OrchestratorClient {
+ info: InitializeResult,
+ context: PlatformExtensionContext,
+}
+
+impl OrchestratorClient {
+ pub fn new(context: PlatformExtensionContext) -> Result {
+ let info = InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
+ .with_server_info(
+ Implementation::new(EXTENSION_NAME, "1.0.0").with_title("Orchestrator"),
+ )
+ .with_instructions(
+ "Manage agent sessions: list, view, start, send messages, and interrupt agents.",
+ );
+
+ Ok(Self { info, context })
+ }
+
+ async fn get_agent_manager(&self) -> Result, String> {
+ AgentManager::instance()
+ .await
+ .map_err(|e| format!("Failed to get agent manager: {}", e))
+ }
+
+ async fn get_provider(&self) -> Result, String> {
+ let extension_manager = self
+ .context
+ .extension_manager
+ .as_ref()
+ .and_then(|weak| weak.upgrade())
+ .ok_or("Extension manager not available")?;
+
+ let provider_guard = extension_manager.get_provider().lock().await;
+ provider_guard
+ .as_ref()
+ .cloned()
+ .ok_or_else(|| "Provider not available".to_string())
+ }
+
+ async fn handle_list_sessions(
+ &self,
+ arguments: Option,
+ ) -> Result {
+ let type_filter = arguments
+ .as_ref()
+ .and_then(|args| args.get("session_type"))
+ .and_then(|v| v.as_str());
+
+ let limit = arguments
+ .as_ref()
+ .and_then(|args| args.get("last_n"))
+ .and_then(|v| v.as_u64())
+ .map(|v| v as usize)
+ .unwrap_or(DEFAULT_LIST_LIMIT);
+
+ let manager = self.get_agent_manager().await?;
+
+ let mut sessions = if let Some(type_str) = type_filter {
+ let session_type: SessionType = type_str
+ .parse()
+ .map_err(|e| format!("Invalid session type '{}': {}", type_str, e))?;
+ self.context
+ .session_manager
+ .list_sessions_by_types(&[session_type])
+ .await
+ .map_err(|e| format!("Failed to list sessions: {}", e))?
+ } else {
+ self.context
+ .session_manager
+ .list_sessions()
+ .await
+ .map_err(|e| format!("Failed to list sessions: {}", e))?
+ };
+
+ // Most recent first
+ sessions.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
+ let total = sessions.len();
+ sessions.truncate(limit);
+
+ if sessions.is_empty() {
+ return Ok(CallToolResult::success(vec![Content::text(
+ "No sessions found.",
+ )]));
+ }
+
+ let active_ids = manager.list_active_session_ids().await;
+
+ let mut lines = vec![format!(
+ "Showing {} of {} session(s):\n",
+ sessions.len(),
+ total
+ )];
+ for session in &sessions {
+ let is_loaded = active_ids.contains(&session.id);
+ let is_busy = if is_loaded {
+ manager.is_session_busy(&session.id).await
+ } else {
+ false
+ };
+
+ let status = if is_busy {
+ "🔄 busy"
+ } else if is_loaded {
+ "✓ loaded"
+ } else {
+ "○ idle"
+ };
+
+ lines.push(format!(
+ "- **{}** ({})\n Type: {} | Status: {} | Messages: {} | Updated: {}",
+ session.name,
+ session.id,
+ session.session_type,
+ status,
+ session.message_count,
+ session.updated_at.format("%Y-%m-%d %H:%M"),
+ ));
+ }
+
+ Ok(CallToolResult::success(vec![Content::text(
+ lines.join("\n"),
+ )]))
+ }
+
+ async fn handle_view_session(
+ &self,
+ session_id_for_llm: &str,
+ arguments: Option,
+ ) -> Result {
+ let args = arguments.ok_or("Missing arguments")?;
+ let session_id = extract_string(&args, "session_id")?;
+ let mode = args
+ .get("mode")
+ .and_then(|v| v.as_str())
+ .unwrap_or("first_last");
+
+ let session = self
+ .context
+ .session_manager
+ .get_session(&session_id, true)
+ .await
+ .map_err(|e| format!("Session '{}' not found: {}", session_id, e))?;
+
+ let manager = self.get_agent_manager().await?;
+ let is_busy = manager.is_session_busy(&session_id).await;
+
+ let mut output = vec![format!(
+ "# Session: {} ({})\n\nType: {} | Status: {} | Working dir: {}\nMessages: {} | Updated: {}\n",
+ session.name,
+ session.id,
+ session.session_type,
+ if is_busy { "🔄 busy" } else { "idle" },
+ session.working_dir.display(),
+ session.message_count,
+ session.updated_at.format("%Y-%m-%d %H:%M"),
+ )];
+
+ match mode {
+ "first_last" => {
+ if let Some(conversation) = &session.conversation {
+ let messages = conversation.messages();
+ if messages.is_empty() {
+ output.push("No messages in this session.".to_string());
+ } else {
+ output.push("## First message\n".to_string());
+ output.push(format_message_for_compacting(&messages[0]));
+
+ if messages.len() > 1 {
+ output.push(format!("\n*({} messages omitted)*\n", messages.len() - 2));
+ output.push("## Last message\n".to_string());
+ output
+ .push(format_message_for_compacting(&messages[messages.len() - 1]));
+ }
+ }
+ } else {
+ output.push("No messages in this session.".to_string());
+ }
+ }
+ "summarize" => {
+ if let Some(conversation) = &session.conversation {
+ let messages = conversation.messages();
+ if messages.is_empty() {
+ output.push("No messages to summarize.".to_string());
+ } else {
+ let summary = self
+ .summarize_conversation(session_id_for_llm, messages)
+ .await?;
+ output.push(format!("## Summary\n\n{}", summary));
+ }
+ } else {
+ output.push("No messages to summarize.".to_string());
+ }
+ }
+ other => {
+ return Err(format!(
+ "Unknown mode '{}'. Use 'first_last' or 'summarize'.",
+ other
+ ));
+ }
+ }
+
+ Ok(CallToolResult::success(vec![Content::text(
+ output.join("\n"),
+ )]))
+ }
+
+ async fn summarize_conversation(
+ &self,
+ session_id: &str,
+ messages: &[Message],
+ ) -> Result {
+ let provider = self.get_provider().await?;
+
+ let conversation_text = messages
+ .iter()
+ .filter(|m| m.is_agent_visible())
+ .map(format_message_for_compacting)
+ .collect::>()
+ .join("\n");
+
+ let system =
+ "You are a helpful assistant. Summarize the following conversation concisely, \
+ capturing the key topics, decisions, and current state. Be brief.";
+
+ let user_message = Message::user().with_text(format!(
+ "Summarize this conversation ({} messages):\n\n{}",
+ messages.len(),
+ conversation_text
+ ));
+
+ let (response, _usage) = provider
+ .complete_fast(session_id, system, &[user_message], &[])
+ .await
+ .map_err(|e| format!("LLM summarization failed: {}", e))?;
+
+ Ok(response
+ .content
+ .iter()
+ .filter_map(|c| {
+ if let crate::conversation::message::MessageContent::Text(t) = c {
+ Some(t.text.clone())
+ } else {
+ None
+ }
+ })
+ .collect::>()
+ .join("\n"))
+ }
+
+ async fn handle_start_agent(
+ &self,
+ arguments: Option,
+ ) -> Result {
+ let args = arguments.ok_or("Missing arguments")?;
+ let working_dir = extract_string(&args, "working_dir")?;
+ let name = args
+ .get("name")
+ .and_then(|v| v.as_str())
+ .unwrap_or("Orchestrated Agent")
+ .to_string();
+
+ let raw_path = PathBuf::from(&working_dir);
+ let path = if raw_path.is_absolute() {
+ raw_path
+ } else {
+ let base = self
+ .context
+ .session
+ .as_ref()
+ .map(|s| s.working_dir.clone())
+ .unwrap_or_else(|| PathBuf::from("."));
+ base.join(&raw_path)
+ };
+
+ let path = path
+ .canonicalize()
+ .map_err(|e| format!("Invalid working directory '{}': {}", working_dir, e))?;
+
+ if !path.is_dir() {
+ return Err(format!("'{}' is not a directory", working_dir));
+ }
+
+ let mode = LeafMode::default();
+
+ let session = self
+ .context
+ .session_manager
+ .create_session(path, name.clone(), SessionType::User, mode)
+ .await
+ .map_err(|e| format!("Failed to create session: {}", e))?;
+
+ let manager = self.get_agent_manager().await?;
+ let agent = manager
+ .get_or_create_agent(session.id.clone())
+ .await
+ .map_err(|e| format!("Failed to create agent: {}", e))?;
+
+ // Inherit the orchestrator's provider and model
+ let provider = self.get_provider().await?;
+ agent
+ .update_provider(provider, &session.id)
+ .await
+ .map_err(|e| format!("Failed to set provider on new agent: {}", e))?;
+
+ Ok(CallToolResult::success(vec![Content::text(format!(
+ "Started agent session '{}' with ID: {}\n\nUse send_message with this session_id to interact with it.",
+ name, session.id
+ ))]))
+ }
+
+ async fn handle_send_message(
+ &self,
+ parent_session_id: &str,
+ parent_cancel: &CancellationToken,
+ arguments: Option,
+ ) -> Result {
+ let args = arguments.ok_or("Missing arguments")?;
+ let session_id = extract_string(&args, "session_id")?;
+ let message_text = extract_string(&args, "message")?;
+
+ if session_id == parent_session_id {
+ return Err("Cannot send a message to the orchestrator's own session".into());
+ }
+
+ let manager = self.get_agent_manager().await?;
+
+ let agent = manager
+ .get_or_create_agent(session_id.clone())
+ .await
+ .map_err(|e| format!("Failed to get agent for session '{}': {}", session_id, e))?;
+
+ if agent.provider().await.is_err() {
+ if let Ok(provider) = self.get_provider().await {
+ agent
+ .update_provider(provider, &session_id)
+ .await
+ .map_err(|e| format!("Failed to set provider: {}", e))?;
+ }
+ }
+
+ let cancel_token = CancellationToken::new();
+ manager
+ .try_register_cancel_token(&session_id, cancel_token.clone())
+ .await
+ .map_err(|_| {
+ format!(
+ "Session '{}' is currently busy. Use interrupt_agent first, or wait.",
+ session_id
+ )
+ })?;
+
+ let mut guard = CancelTokenGuard::new(manager.clone(), session_id.clone());
+
+ let user_message = Message::user().with_text(&message_text);
+ let session_config = SessionConfig {
+ id: session_id.clone(),
+ schedule_id: None,
+ max_turns: None,
+ retry_config: None,
+ };
+
+ let mut stream = agent
+ .reply(user_message, session_config, Some(cancel_token.clone()))
+ .await
+ .map_err(|e| format!("Failed to start reply: {}", e))?;
+
+ let mut response_parts: Vec = Vec::new();
+ let mut cancelled = false;
+
+ loop {
+ tokio::select! {
+ _ = parent_cancel.cancelled() => {
+ cancel_token.cancel();
+ cancelled = true;
+ break;
+ }
+ event = stream.next() => {
+ match event {
+ Some(Ok(AgentEvent::Message(msg))) => {
+ let text = msg.as_concat_text();
+ if !text.is_empty() {
+ response_parts.push(text);
+ }
+ }
+ Some(Ok(_)) => {}
+ Some(Err(e)) => {
+ response_parts.push(format!("Error during agent processing: {}", e));
+ break;
+ }
+ None => break,
+ }
+ }
+ }
+ }
+
+ drop(stream);
+ guard.disarm();
+ manager.unregister_cancel_token(&session_id).await;
+
+ if cancelled {
+ return Err("Cancelled by parent session".into());
+ }
+
+ if response_parts.is_empty() {
+ Ok(CallToolResult::success(vec![Content::text(
+ "Agent completed without producing text output.",
+ )]))
+ } else {
+ Ok(CallToolResult::success(vec![Content::text(format!(
+ "## Response from session {}\n\n{}",
+ session_id,
+ response_parts.join("\n\n")
+ ))]))
+ }
+ }
+
+ async fn handle_interrupt_agent(
+ &self,
+ arguments: Option,
+ ) -> Result {
+ let args = arguments.ok_or("Missing arguments")?;
+ let session_id = extract_string(&args, "session_id")?;
+
+ let manager = self.get_agent_manager().await?;
+
+ manager
+ .cancel_session(&session_id)
+ .await
+ .map_err(|e| format!("Failed to interrupt session '{}': {}", session_id, e))?;
+
+ Ok(CallToolResult::success(vec![Content::text(format!(
+ "Interrupted agent session '{}'.",
+ session_id
+ ))]))
+ }
+}
+
+#[async_trait]
+impl McpClientTrait for OrchestratorClient {
+ async fn list_tools(
+ &self,
+ _session_id: &str,
+ _next_cursor: Option,
+ _cancel_token: CancellationToken,
+ ) -> Result {
+ let tools = vec![
+ Tool::new(
+ "list_sessions".to_string(),
+ "List agent sessions with their status (loaded, busy, idle). Returns the most recent 10 by default. Optionally filter by session type."
+ .to_string(),
+ schema::(),
+ ),
+ Tool::new(
+ "view_session".to_string(),
+ "View a session's details and conversation. Mode 'first_last' (default) returns the first and last message. Mode 'summarize' calls the LLM to produce a conversation summary."
+ .to_string(),
+ schema::(),
+ ),
+ Tool::new(
+ "start_agent".to_string(),
+ "Start a new agent session with its own working directory. Inherits the current provider and model. Returns a session_id for future interaction."
+ .to_string(),
+ schema::(),
+ ),
+ Tool::new(
+ "send_message".to_string(),
+ "Send a message to an existing agent session and get the response. Returns an error if the agent is currently busy."
+ .to_string(),
+ schema::(),
+ ),
+ Tool::new(
+ "interrupt_agent".to_string(),
+ "Interrupt a busy agent by cancelling its current operation."
+ .to_string(),
+ schema::(),
+ ),
+ ];
+
+ Ok(ListToolsResult {
+ tools,
+ next_cursor: None,
+ meta: None,
+ })
+ }
+
+ async fn call_tool(
+ &self,
+ ctx: &ToolCallContext,
+ name: &str,
+ arguments: Option,
+ cancel_token: CancellationToken,
+ ) -> Result {
+ let result = match name {
+ "list_sessions" => self.handle_list_sessions(arguments).await,
+ "view_session" => self.handle_view_session(&ctx.session_id, arguments).await,
+ "start_agent" => self.handle_start_agent(arguments).await,
+ "send_message" => {
+ self.handle_send_message(&ctx.session_id, &cancel_token, arguments)
+ .await
+ }
+ "interrupt_agent" => self.handle_interrupt_agent(arguments).await,
+ _ => Err(format!("Unknown tool: {}", name)),
+ };
+
+ match result {
+ Ok(result) => Ok(result),
+ Err(error) => Ok(CallToolResult::error(vec![Content::text(format!(
+ "Error: {}",
+ error
+ ))])),
+ }
+ }
+
+ fn get_info(&self) -> Option<&InitializeResult> {
+ Some(&self.info)
+ }
+}
+
+fn schema() -> JsonObject {
+ let mut obj = serde_json::to_value(schema_for!(T))
+ .map(|v| v.as_object().unwrap().clone())
+ .expect("valid schema");
+ obj.entry("properties")
+ .or_insert_with(|| serde_json::json!({}));
+ obj
+}
+
+fn extract_string(args: &JsonObject, key: &str) -> Result {
+ args.get(key)
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string())
+ .ok_or_else(|| format!("Missing or invalid '{}'", key))
+}
diff --git a/crates/leaf/src/agents/reply_parts.rs b/crates/leaf/src/agents/reply_parts.rs
index 8e1235132ec4..f830917fef47 100644
--- a/crates/leaf/src/agents/reply_parts.rs
+++ b/crates/leaf/src/agents/reply_parts.rs
@@ -145,17 +145,6 @@ impl Agent {
tools.push(frontend_tool.tool.clone());
}
- let code_execution_active = false;
- if code_execution_active {
- tools.retain(|tool| {
- if let Some(owner) = crate::agents::extension_manager::get_tool_owner(tool) {
- crate::agents::extension_manager::is_first_class_extension(&owner)
- } else {
- false
- }
- });
- }
-
// Stable tool ordering is important for multi session prompt caching.
tools.sort_by(|a, b| a.name.cmp(&b.name));
@@ -181,7 +170,6 @@ impl Agent {
.with_extensions(extensions_info.into_iter())
.with_frontend_instructions(self.frontend_instructions.lock().await.clone())
.with_extension_and_tool_counts(extension_count, tool_count)
- .with_code_execution_mode(code_execution_active)
.with_hints(working_dir)
.with_leaf_mode(leaf_mode)
.build();
diff --git a/crates/leaf/src/context_mgmt/mod.rs b/crates/leaf/src/context_mgmt/mod.rs
index 7a32203189d6..fd7c785744d7 100644
--- a/crates/leaf/src/context_mgmt/mod.rs
+++ b/crates/leaf/src/context_mgmt/mod.rs
@@ -342,7 +342,7 @@ async fn do_compact(
))
}
-fn format_message_for_compacting(msg: &Message) -> String {
+pub fn format_message_for_compacting(msg: &Message) -> String {
let content_parts: Vec = msg
.content
.iter()
diff --git a/crates/leaf/src/execution/manager.rs b/crates/leaf/src/execution/manager.rs
index 577d427ae329..258ceb3223fe 100644
--- a/crates/leaf/src/execution/manager.rs
+++ b/crates/leaf/src/execution/manager.rs
@@ -7,9 +7,11 @@ use crate::scheduler_trait::SchedulerTrait;
use crate::session::SessionManager;
use anyhow::Result;
use lru::LruCache;
+use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::sync::Arc;
use tokio::sync::{OnceCell, RwLock};
+use tokio_util::sync::CancellationToken;
use tracing::{debug, info};
const DEFAULT_MAX_SESSION: usize = 100;
@@ -22,6 +24,7 @@ pub struct AgentManager {
session_manager: Arc,
default_provider: Arc>>>,
default_mode: LeafMode,
+ cancel_tokens: Arc>>,
}
impl AgentManager {
@@ -42,6 +45,7 @@ impl AgentManager {
session_manager,
default_provider: Arc::new(RwLock::new(None)),
default_mode,
+ cancel_tokens: Arc::new(RwLock::new(HashMap::new())),
};
Ok(manager)
@@ -151,6 +155,9 @@ impl AgentManager {
}
pub async fn remove_session(&self, session_id: &str) -> Result<()> {
+ if let Some(token) = self.cancel_tokens.write().await.remove(session_id) {
+ token.cancel();
+ }
let mut sessions = self.sessions.write().await;
sessions
.pop(session_id)
@@ -166,6 +173,51 @@ impl AgentManager {
pub async fn session_count(&self) -> usize {
self.sessions.read().await.len()
}
+
+ /// Atomically check if busy and register a cancel token. Returns Err if already busy.
+ pub async fn try_register_cancel_token(
+ &self,
+ session_id: &str,
+ token: CancellationToken,
+ ) -> Result<()> {
+ let mut tokens = self.cancel_tokens.write().await;
+ if tokens.contains_key(session_id) {
+ anyhow::bail!("Session '{}' is currently busy", session_id);
+ }
+ tokens.insert(session_id.to_string(), token);
+ Ok(())
+ }
+
+ /// Remove the cancellation token for a session (called when reply finishes)
+ pub async fn unregister_cancel_token(&self, session_id: &str) {
+ self.cancel_tokens.write().await.remove(session_id);
+ }
+
+ /// Cancel a running agent by triggering its cancellation token
+ pub async fn cancel_session(&self, session_id: &str) -> Result<()> {
+ let tokens = self.cancel_tokens.read().await;
+ let token = tokens
+ .get(session_id)
+ .ok_or_else(|| anyhow::anyhow!("No active operation for session {}", session_id))?;
+ token.cancel();
+ Ok(())
+ }
+
+ /// Check if a session has an active reply in progress
+ pub async fn is_session_busy(&self, session_id: &str) -> bool {
+ let tokens = self.cancel_tokens.read().await;
+ tokens.contains_key(session_id)
+ }
+
+ /// List session IDs that currently have active agents loaded
+ pub async fn list_active_session_ids(&self) -> Vec {
+ self.sessions
+ .read()
+ .await
+ .iter()
+ .map(|(id, _)| id.clone())
+ .collect()
+ }
}
#[cfg(test)]
diff --git a/crates/leaf/src/gateway/handler.rs b/crates/leaf/src/gateway/handler.rs
index 3a3618c4fd38..5d7464f68f9f 100644
--- a/crates/leaf/src/gateway/handler.rs
+++ b/crates/leaf/src/gateway/handler.rs
@@ -64,8 +64,7 @@ impl GatewayHandler {
.send_message(
&message.user,
OutgoingMessage::Text {
- body: "Welcome! Enter your pairing code to connect to leaf."
- .into(),
+ body: "Welcome! Enter your pairing code to connect to leaf.".into(),
},
)
.await?;
diff --git a/crates/leaf/src/recipe/template_recipe.rs b/crates/leaf/src/recipe/template_recipe.rs
index f127c4c66077..7897ddd87ed2 100644
--- a/crates/leaf/src/recipe/template_recipe.rs
+++ b/crates/leaf/src/recipe/template_recipe.rs
@@ -147,12 +147,10 @@ fn get_env_with_template_variables(
undefined_behavior: UndefinedBehavior,
) -> Result<(Environment<'_>, HashSet)> {
let env = add_template_in_env(content, recipe_dir, undefined_behavior)?;
- let template = env.get_template(CURRENT_TEMPLATE_NAME).unwrap();
- let state = template.eval_to_state(())?;
- let mut template_variables = HashSet::new();
- for (_, template) in state.env().templates() {
- template_variables.extend(template.undeclared_variables(true));
- }
+ let template_variables = {
+ let template = env.get_template(CURRENT_TEMPLATE_NAME).unwrap();
+ template.undeclared_variables(true)
+ };
Ok((env, template_variables))
}
diff --git a/crates/leaf/src/session/session_manager.rs b/crates/leaf/src/session/session_manager.rs
index bdc8e2920e06..858c059de3e2 100644
--- a/crates/leaf/src/session/session_manager.rs
+++ b/crates/leaf/src/session/session_manager.rs
@@ -311,7 +311,11 @@ impl SessionManager {
}
pub async fn list_sessions_by_types(&self, types: &[SessionType]) -> Result> {
- self.storage.list_sessions_by_types(types).await
+ self.storage.list_sessions_by_types(Some(types)).await
+ }
+
+ pub async fn list_all_sessions(&self) -> Result> {
+ self.storage.list_sessions_by_types(None).await
}
pub async fn delete_session(&self, id: &str) -> Result<()> {
@@ -1239,12 +1243,19 @@ impl SessionStorage {
Self::replace_conversation_inner(pool, session_id, conversation).await
}
- async fn list_sessions_by_types(&self, types: &[SessionType]) -> Result> {
- if types.is_empty() {
- return Ok(Vec::new());
- }
+ async fn list_sessions_by_types(&self, types: Option<&[SessionType]>) -> Result> {
+ let (where_clause, binds): (String, Vec) = match types {
+ Some(t) if !t.is_empty() => {
+ let placeholders: String = t.iter().map(|_| "?").collect::>().join(", ");
+ (
+ format!("WHERE s.session_type IN ({})", placeholders),
+ t.iter().map(|t| t.to_string()).collect(),
+ )
+ }
+ Some(_) => return Ok(Vec::new()),
+ None => (String::new(), Vec::new()),
+ };
- let placeholders: String = types.iter().map(|_| "?").collect::>().join(", ");
let query = format!(
r#"
SELECT s.id, s.working_dir, s.name, s.description, s.user_set_name, s.session_type, s.created_at, s.updated_at, s.extension_data,
@@ -1255,16 +1266,16 @@ impl SessionStorage {
COUNT(m.id) as message_count
FROM sessions s
INNER JOIN messages m ON s.id = m.session_id
- WHERE s.session_type IN ({})
+ {}
GROUP BY s.id
ORDER BY s.updated_at DESC
"#,
- placeholders
+ where_clause
);
let mut q = sqlx::query_as::<_, Session>(&query);
- for t in types {
- q = q.bind(t.to_string());
+ for b in &binds {
+ q = q.bind(b);
}
let pool = self.pool().await?;
@@ -1272,7 +1283,7 @@ impl SessionStorage {
}
async fn list_sessions(&self) -> Result> {
- self.list_sessions_by_types(&[SessionType::User, SessionType::Scheduled])
+ self.list_sessions_by_types(Some(&[SessionType::User, SessionType::Scheduled]))
.await
}
diff --git a/npm/goose-acp-server-darwin-arm64/package.json b/npm/goose-acp-server-darwin-arm64/package.json
index 4c659f6def43..68c6c2f8bdca 100644
--- a/npm/goose-acp-server-darwin-arm64/package.json
+++ b/npm/goose-acp-server-darwin-arm64/package.json
@@ -14,6 +14,6 @@
"arm64"
],
"files": [
- "bin/goose-acp-server"
+ "bin/goose"
]
}
diff --git a/npm/goose-acp-server-darwin-x64/package.json b/npm/goose-acp-server-darwin-x64/package.json
index f477ffa09fe7..9d04dcf08da8 100644
--- a/npm/goose-acp-server-darwin-x64/package.json
+++ b/npm/goose-acp-server-darwin-x64/package.json
@@ -14,6 +14,6 @@
"x64"
],
"files": [
- "bin/goose-acp-server"
+ "bin/goose"
]
}
diff --git a/npm/goose-acp-server-linux-arm64/package.json b/npm/goose-acp-server-linux-arm64/package.json
index 363570a1c11c..071b308551bf 100644
--- a/npm/goose-acp-server-linux-arm64/package.json
+++ b/npm/goose-acp-server-linux-arm64/package.json
@@ -14,6 +14,6 @@
"arm64"
],
"files": [
- "bin/goose-acp-server"
+ "bin/goose"
]
}
diff --git a/npm/goose-acp-server-linux-x64/package.json b/npm/goose-acp-server-linux-x64/package.json
index a6473be39630..e055534c4804 100644
--- a/npm/goose-acp-server-linux-x64/package.json
+++ b/npm/goose-acp-server-linux-x64/package.json
@@ -14,6 +14,6 @@
"x64"
],
"files": [
- "bin/goose-acp-server"
+ "bin/goose"
]
}
diff --git a/npm/goose-acp-server-win32-x64/package.json b/npm/goose-acp-server-win32-x64/package.json
index 9aea8862310a..e67f8547d3b1 100644
--- a/npm/goose-acp-server-win32-x64/package.json
+++ b/npm/goose-acp-server-win32-x64/package.json
@@ -14,6 +14,6 @@
"x64"
],
"files": [
- "bin/goose-acp-server.exe"
+ "bin/goose.exe"
]
}
diff --git a/scripts/test_providers_code_exec.sh b/scripts/test_providers_code_exec.sh
index 19243d6622fc..c9d720d202a0 100755
--- a/scripts/test_providers_code_exec.sh
+++ b/scripts/test_providers_code_exec.sh
@@ -27,10 +27,11 @@ run_test() {
cd "$testdir" && "$GOOSE_BIN" run --text "$prompt" --with-builtin "$BUILTINS" 2>&1
) > "$output_file" 2>&1
- # Matches: "execute | code_execution", "get_function_details | code_execution",
+ # Matches: "execute_typescript | code_execution", "get_function_details | code_execution",
# "tool call | execute", "tool calls | execute" (old format)
# "▸ execute N tool call" (new format with tool_graph)
- if grep -qE "(execute \| code_execution)|(get_function_details \| code_execution)|(tool calls? \| execute)|(▸.*execute.*tool call)" "$output_file"; then
+ # "▸ execute_typescript" (plain tool name in output)
+ if grep -qE "(execute_typescript \| code_execution)|(get_function_details \| code_execution)|(tool calls? \| execute)|(▸.*execute.*tool call)|(▸ execute_typescript)" "$output_file"; then
echo "success|code_execution tool called" > "$result_file"
else
echo "failure|no code_execution tool calls found" > "$result_file"