mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
add provider setup infrastructure (#130)
* feat: add provider setup infrastructure Add the backend commands, config services, shared types, and frontend API layer needed for provider setup and credential management. * fix: harden provider setup commands and config deletion Resolve PR review issues by deriving provider behavior from backend definitions, preventing renderer-supplied agent commands, and clearing OAuth cache artifacts when provider config is removed. * fix: expose provider defs within backend Restore backend compilation by making provider definitions visible to sibling modules that derive secure config behavior from the shared provider metadata. * refactor: streamline checkAgentAuth function signature Simplify the function signature of checkAgentAuth in agentSetup.ts by removing unnecessary line breaks, enhancing code readability.
This commit is contained in:
Generated
+509
-14
@@ -25,6 +25,17 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol"
|
||||
version = "0.10.4"
|
||||
@@ -358,6 +369,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
@@ -565,6 +585,15 @@ dependencies = [
|
||||
"toml 0.9.12+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.58"
|
||||
@@ -628,6 +657,16 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -672,6 +711,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
@@ -695,7 +744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@@ -708,7 +757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -845,6 +894,35 @@ version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus-secret-service"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"block-padding",
|
||||
"cbc",
|
||||
"dbus",
|
||||
"fastrand",
|
||||
"hkdf",
|
||||
"num",
|
||||
"once_cell",
|
||||
"sha2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
@@ -899,6 +977,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1112,6 +1191,17 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "etcetera"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"home",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
@@ -1625,7 +1715,9 @@ dependencies = [
|
||||
"chrono",
|
||||
"dirs",
|
||||
"doctor",
|
||||
"etcetera",
|
||||
"futures",
|
||||
"keyring",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1744,6 +1836,33 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.29.1"
|
||||
@@ -2034,6 +2153,16 @@ dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
@@ -2187,6 +2316,23 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dbus-secret-service",
|
||||
"linux-keyutils",
|
||||
"log",
|
||||
"secret-service",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework 3.7.0",
|
||||
"windows-sys 0.60.2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.8-speedreader"
|
||||
@@ -2235,6 +2381,15 @@ version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
@@ -2254,6 +2409,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-keyutils"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
@@ -2441,6 +2606,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2449,12 +2615,76 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -3526,6 +3756,61 @@ version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "secret-service"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"futures-util",
|
||||
"generic-array",
|
||||
"hkdf",
|
||||
"num",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"sha2",
|
||||
"zbus 4.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.24.0"
|
||||
@@ -3891,6 +4176,12 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.9"
|
||||
@@ -3967,6 +4258,12 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@@ -4041,7 +4338,7 @@ checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dispatch2",
|
||||
@@ -4307,7 +4604,7 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"url",
|
||||
"windows",
|
||||
"zbus",
|
||||
"zbus 5.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5488,6 +5785,24 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -5530,6 +5845,21 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -5587,6 +5917,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5605,6 +5941,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5623,6 +5965,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5653,6 +6001,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5671,6 +6025,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5689,6 +6049,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5707,6 +6073,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5924,6 +6296,16 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xdg-home"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
@@ -5947,6 +6329,38 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
"zbus_macros 4.4.0",
|
||||
"zbus_names 3.0.0",
|
||||
"zvariant 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.14.0"
|
||||
@@ -5977,9 +6391,22 @@ dependencies = [
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
"winnow 0.7.15",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zbus_macros 5.14.0",
|
||||
"zbus_names 4.3.1",
|
||||
"zvariant 5.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zvariant_utils 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5992,9 +6419,20 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
"zbus_names 4.3.1",
|
||||
"zvariant 5.10.0",
|
||||
"zvariant_utils 3.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6005,7 +6443,7 @@ checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow 0.7.15",
|
||||
"zvariant",
|
||||
"zvariant 5.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6049,6 +6487,26 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
@@ -6088,6 +6546,19 @@ version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.10.0"
|
||||
@@ -6098,8 +6569,21 @@ dependencies = [
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"winnow 0.7.15",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
"zvariant_derive 5.10.0",
|
||||
"zvariant_utils 3.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zvariant_utils 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6112,7 +6596,18 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zvariant_utils",
|
||||
"zvariant_utils 3.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -30,6 +30,7 @@ tokio = { version = "1.50.0", features = ["full"] }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
serde_yaml = "0.9"
|
||||
etcetera = "0.8"
|
||||
futures = "0.3"
|
||||
tokio-stream = "0.1"
|
||||
tokio-util = { version = "0.7", features = ["compat", "rt"] }
|
||||
@@ -39,5 +40,14 @@ tokio-tungstenite = "0.21.0"
|
||||
acp-client = { git = "https://github.com/block/builderbot", rev = "6cb674a704f3a9e402a76e9aeed734144d6cec11" }
|
||||
doctor = { git = "https://github.com/block/builderbot", rev = "bfce29264181236a2e5b4fd903ace6291f6a7594" }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
keyring = { version = "3", features = ["apple-native"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
keyring = { version = "3", features = ["windows-native"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
keyring = { version = "3", features = ["linux-native-sync-persistent", "crypto-rust"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
struct AgentCommandDef {
|
||||
id: &'static str,
|
||||
binary_name: &'static str,
|
||||
install_command: Option<&'static str>,
|
||||
auth_command: Option<&'static str>,
|
||||
auth_status_command: Option<&'static str>,
|
||||
}
|
||||
|
||||
const AGENT_COMMAND_DEFS: &[AgentCommandDef] = &[
|
||||
AgentCommandDef {
|
||||
id: "claude-acp",
|
||||
binary_name: "claude-agent-acp",
|
||||
install_command: Some(
|
||||
"npm install -g @anthropic-ai/claude-code @zed-industries/claude-agent-acp",
|
||||
),
|
||||
auth_command: Some("claude auth login"),
|
||||
auth_status_command: Some("claude auth status"),
|
||||
},
|
||||
AgentCommandDef {
|
||||
id: "codex-acp",
|
||||
binary_name: "codex-acp",
|
||||
install_command: Some("npm install -g @openai/codex @zed-industries/codex-acp"),
|
||||
auth_command: Some("codex login"),
|
||||
auth_status_command: Some("codex login status"),
|
||||
},
|
||||
AgentCommandDef {
|
||||
id: "copilot-acp",
|
||||
binary_name: "copilot",
|
||||
install_command: Some("npm install -g @github/copilot"),
|
||||
auth_command: Some("copilot login"),
|
||||
auth_status_command: None,
|
||||
},
|
||||
AgentCommandDef {
|
||||
id: "amp-acp",
|
||||
binary_name: "amp-acp",
|
||||
install_command: Some("npm install -g @sourcegraph/amp@latest amp-acp"),
|
||||
auth_command: Some("amp login"),
|
||||
auth_status_command: Some("amp usage"),
|
||||
},
|
||||
AgentCommandDef {
|
||||
id: "cursor-agent",
|
||||
binary_name: "cursor-agent",
|
||||
install_command: Some("curl -fsSL https://cursor.com/install | bash"),
|
||||
auth_command: Some("cursor-agent login"),
|
||||
auth_status_command: Some("cursor-agent status"),
|
||||
},
|
||||
AgentCommandDef {
|
||||
id: "pi-acp",
|
||||
binary_name: "pi-acp",
|
||||
install_command: None,
|
||||
auth_command: None,
|
||||
auth_status_command: None,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AgentSetupOutput {
|
||||
provider_id: String,
|
||||
line: String,
|
||||
}
|
||||
|
||||
fn find_agent_command_def(provider_id: &str) -> Option<&'static AgentCommandDef> {
|
||||
AGENT_COMMAND_DEFS.iter().find(|def| def.id == provider_id)
|
||||
}
|
||||
|
||||
fn get_agent_command_def(provider_id: &str) -> Result<&'static AgentCommandDef, String> {
|
||||
find_agent_command_def(provider_id)
|
||||
.ok_or_else(|| format!("Unknown agent provider '{provider_id}'"))
|
||||
}
|
||||
|
||||
fn build_extended_path() -> String {
|
||||
let mut paths: Vec<PathBuf> = Vec::new();
|
||||
|
||||
if let Ok(system_path) = std::env::var("PATH") {
|
||||
paths.extend(std::env::split_paths(&system_path).filter(|p| {
|
||||
!p.to_string_lossy().contains(".hermit") && !p.join("activate-hermit").exists()
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
paths.push(home.join(".local/bin"));
|
||||
paths.push(home.join(".npm-global/bin"));
|
||||
}
|
||||
|
||||
paths.push(PathBuf::from("/usr/local/bin"));
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
paths.push(PathBuf::from("/opt/homebrew/bin"));
|
||||
paths.push(PathBuf::from("/opt/local/bin"));
|
||||
}
|
||||
|
||||
if cfg!(windows) {
|
||||
if let Some(appdata) = dirs::data_dir() {
|
||||
paths.push(appdata.join("npm"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
let nvm_dir = home.join(".nvm/versions/node");
|
||||
if nvm_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(&nvm_dir) {
|
||||
let mut versions: Vec<_> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
|
||||
.collect();
|
||||
versions.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
|
||||
if let Some(latest) = versions.first() {
|
||||
paths.push(latest.path().join("bin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fnm_dir = home.join(".local/share/fnm/node-versions");
|
||||
if fnm_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(&fnm_dir) {
|
||||
let mut versions: Vec<_> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
|
||||
.collect();
|
||||
versions.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
|
||||
if let Some(latest) = versions.first() {
|
||||
paths.push(latest.path().join("installation/bin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
paths.retain(|p| seen.insert(p.clone()));
|
||||
|
||||
std::env::join_paths(paths)
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_agent_installed(provider_id: String) -> Result<bool, String> {
|
||||
let def = get_agent_command_def(&provider_id)?;
|
||||
let extended_path = build_extended_path();
|
||||
|
||||
let (cmd, flag) = if cfg!(target_os = "windows") {
|
||||
("where", "/Q")
|
||||
} else {
|
||||
("which", "")
|
||||
};
|
||||
|
||||
let mut command = std::process::Command::new(cmd);
|
||||
if !flag.is_empty() {
|
||||
command.arg(flag);
|
||||
}
|
||||
command.arg(def.binary_name);
|
||||
command.env("PATH", &extended_path);
|
||||
|
||||
Ok(command
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_agent_auth(provider_id: String) -> Result<bool, String> {
|
||||
let def = get_agent_command_def(&provider_id)?;
|
||||
let Some(auth_status_command) = def.auth_status_command else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let extended_path = build_extended_path();
|
||||
|
||||
let shell = if cfg!(target_os = "windows") {
|
||||
"cmd"
|
||||
} else {
|
||||
"sh"
|
||||
};
|
||||
let flag = if cfg!(target_os = "windows") {
|
||||
"/C"
|
||||
} else {
|
||||
"-c"
|
||||
};
|
||||
|
||||
std::process::Command::new(shell)
|
||||
.arg(flag)
|
||||
.arg(auth_status_command)
|
||||
.env("PATH", &extended_path)
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.map_err(|e| format!("Failed to check auth status: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_agent(app_handle: AppHandle, provider_id: String) -> Result<(), String> {
|
||||
let def = get_agent_command_def(&provider_id)?;
|
||||
let install_command = def
|
||||
.install_command
|
||||
.ok_or_else(|| format!("Agent provider '{provider_id}' does not support install"))?;
|
||||
run_shell_command(&app_handle, &provider_id, install_command).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn authenticate_agent(app_handle: AppHandle, provider_id: String) -> Result<(), String> {
|
||||
let def = get_agent_command_def(&provider_id)?;
|
||||
let auth_command = def
|
||||
.auth_command
|
||||
.ok_or_else(|| format!("Agent provider '{provider_id}' does not support auth"))?;
|
||||
run_shell_command(&app_handle, &provider_id, auth_command).await
|
||||
}
|
||||
|
||||
fn strip_npm_config_env(cmd: &mut tokio::process::Command) {
|
||||
for (key, _) in std::env::vars() {
|
||||
if key.starts_with("npm_config") || key.starts_with("NPM_CONFIG") {
|
||||
cmd.env_remove(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_shell_command(
|
||||
app_handle: &AppHandle,
|
||||
provider_id: &str,
|
||||
command: &str,
|
||||
) -> Result<(), String> {
|
||||
let extended_path = build_extended_path();
|
||||
|
||||
let shell = if cfg!(target_os = "windows") {
|
||||
"cmd"
|
||||
} else {
|
||||
"sh"
|
||||
};
|
||||
let flag = if cfg!(target_os = "windows") {
|
||||
"/C"
|
||||
} else {
|
||||
"-c"
|
||||
};
|
||||
|
||||
let mut child_cmd = tokio::process::Command::new(shell);
|
||||
child_cmd
|
||||
.arg(flag)
|
||||
.arg(command)
|
||||
.env("PATH", &extended_path)
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
strip_npm_config_env(&mut child_cmd);
|
||||
|
||||
let mut child = child_cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start command: {e}"))?;
|
||||
|
||||
let stdout = child.stdout.take();
|
||||
let stderr = child.stderr.take();
|
||||
|
||||
let provider_id_owned = provider_id.to_string();
|
||||
let app_for_stdout = app_handle.clone();
|
||||
|
||||
let stdout_task = tokio::spawn(async move {
|
||||
if let Some(stdout) = stdout {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let _ = app_for_stdout.emit(
|
||||
"agent-setup:output",
|
||||
AgentSetupOutput {
|
||||
provider_id: provider_id_owned.clone(),
|
||||
line,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let provider_id_owned2 = provider_id.to_string();
|
||||
let app_for_stderr = app_handle.clone();
|
||||
|
||||
let stderr_task = tokio::spawn(async move {
|
||||
if let Some(stderr) = stderr {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let _ = app_for_stderr.emit(
|
||||
"agent-setup:output",
|
||||
AgentSetupOutput {
|
||||
provider_id: provider_id_owned2.clone(),
|
||||
line,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _ = tokio::join!(stdout_task, stderr_task);
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to wait for command: {e}"))?;
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
Err(format!("Command exited with code {code}"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use tauri::State;
|
||||
|
||||
use crate::services::goose_config::{FieldValue, GooseConfig, ProviderStatus};
|
||||
use crate::services::provider_defs::find_config_key;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_provider_config(
|
||||
config: State<'_, GooseConfig>,
|
||||
provider_id: String,
|
||||
) -> Result<Vec<FieldValue>, String> {
|
||||
config.get_provider_field_values(&provider_id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn save_provider_field(
|
||||
config: State<'_, GooseConfig>,
|
||||
key: String,
|
||||
value: String,
|
||||
) -> Result<(), String> {
|
||||
let config_key =
|
||||
find_config_key(&key).ok_or_else(|| format!("Unknown provider config key '{key}'"))?;
|
||||
let trimmed_value = value.trim();
|
||||
if trimmed_value.is_empty() {
|
||||
return Err("Field value cannot be empty".to_string());
|
||||
}
|
||||
|
||||
if config_key.is_secret {
|
||||
config.set_secret(&key, trimmed_value)
|
||||
} else {
|
||||
config.set_param(&key, trimmed_value)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_provider_config(
|
||||
config: State<'_, GooseConfig>,
|
||||
provider_id: String,
|
||||
) -> Result<(), String> {
|
||||
config.delete_all_provider_fields(&provider_id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn check_all_provider_status(config: State<'_, GooseConfig>) -> Vec<ProviderStatus> {
|
||||
config.check_all_provider_status()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn restart_app(app: tauri::AppHandle) {
|
||||
app.restart();
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
pub mod acp;
|
||||
pub mod agent_setup;
|
||||
pub mod agents;
|
||||
pub mod credentials;
|
||||
pub mod doctor;
|
||||
pub mod git;
|
||||
pub mod model_setup;
|
||||
pub mod projects;
|
||||
pub mod skills;
|
||||
pub mod system;
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
use crate::services::acp::resolve_goose_binary;
|
||||
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ModelSetupOutput {
|
||||
provider_id: String,
|
||||
line: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn authenticate_model_provider(
|
||||
app_handle: AppHandle,
|
||||
provider_id: String,
|
||||
provider_label: String,
|
||||
) -> Result<(), String> {
|
||||
if cfg!(target_os = "windows") {
|
||||
return Err("Native Goose sign-in is not supported on Windows yet".to_string());
|
||||
}
|
||||
|
||||
let goose_binary = resolve_goose_binary()?;
|
||||
let quoted_label = shell_quote(&provider_label);
|
||||
let quoted_binary = shell_quote(&goose_binary.to_string_lossy());
|
||||
|
||||
let command = if cfg!(target_os = "linux") {
|
||||
format!(
|
||||
"printf '\\n%s\\n' {quoted_label} | script -qf /dev/null -c '{quoted_binary} configure'",
|
||||
)
|
||||
} else {
|
||||
format!("printf '\\n%s\\n' {quoted_label} | script -q /dev/null {quoted_binary} configure",)
|
||||
};
|
||||
|
||||
run_shell_command(&app_handle, &provider_id, &command).await
|
||||
}
|
||||
|
||||
fn shell_quote(value: &str) -> String {
|
||||
format!("'{}'", value.replace('\'', "'\"'\"'"))
|
||||
}
|
||||
|
||||
fn strip_ansi(value: &str) -> String {
|
||||
let bytes = value.as_bytes();
|
||||
let mut index = 0;
|
||||
let mut output = String::new();
|
||||
|
||||
while index < bytes.len() {
|
||||
if bytes[index] == 0x1b {
|
||||
index += 1;
|
||||
if index < bytes.len() && bytes[index] == b'[' {
|
||||
index += 1;
|
||||
while index < bytes.len() {
|
||||
let byte = bytes[index];
|
||||
index += 1;
|
||||
if (0x40..=0x7e).contains(&byte) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if bytes[index].is_ascii_control() {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
output.push(bytes[index] as char);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn normalize_output_line(line: &str) -> Option<String> {
|
||||
let cleaned = strip_ansi(line);
|
||||
let trimmed = cleaned
|
||||
.trim()
|
||||
.trim_start_matches(['│', '┌', '└', '◆', '◇', '●', '○'])
|
||||
.trim();
|
||||
|
||||
if trimmed.is_empty()
|
||||
|| trimmed == "Configure Providers"
|
||||
|| trimmed == "What would you like to configure?"
|
||||
|| trimmed == "Which model provider should we use?"
|
||||
|| trimmed == "This will update your existing config files"
|
||||
|| trimmed.starts_with("if you prefer, you can edit them directly at")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
|
||||
fn is_relevant_output(line: &str) -> bool {
|
||||
line.starts_with("Configuring ")
|
||||
|| line.starts_with("Please visit ")
|
||||
|| line.starts_with("Open ")
|
||||
|| line.starts_with("Opening ")
|
||||
|| line.starts_with("Waiting ")
|
||||
|| line.starts_with("Authentication")
|
||||
|| line.starts_with("Authenticated")
|
||||
|| line.starts_with("Saved ")
|
||||
|| line.contains("oauth")
|
||||
|| line.contains("OAuth")
|
||||
|| line.contains("device code")
|
||||
|| line.contains("login/device")
|
||||
|| line.contains("browser")
|
||||
}
|
||||
|
||||
async fn run_shell_command(
|
||||
app_handle: &AppHandle,
|
||||
provider_id: &str,
|
||||
command: &str,
|
||||
) -> Result<(), String> {
|
||||
let shell = if cfg!(target_os = "windows") {
|
||||
"cmd"
|
||||
} else {
|
||||
"sh"
|
||||
};
|
||||
let flag = if cfg!(target_os = "windows") {
|
||||
"/C"
|
||||
} else {
|
||||
"-c"
|
||||
};
|
||||
|
||||
let _ = app_handle.emit(
|
||||
"model-setup:output",
|
||||
ModelSetupOutput {
|
||||
provider_id: provider_id.to_string(),
|
||||
line: "Starting Goose sign-in...".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
let mut child = tokio::process::Command::new(shell)
|
||||
.arg(flag)
|
||||
.arg(command)
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start sign-in flow: {e}"))?;
|
||||
|
||||
let stdout = child.stdout.take();
|
||||
let stderr = child.stderr.take();
|
||||
|
||||
let provider_id_stdout = provider_id.to_string();
|
||||
let app_stdout = app_handle.clone();
|
||||
|
||||
let stdout_task = tokio::spawn(async move {
|
||||
let mut has_relevant_output = false;
|
||||
if let Some(stdout) = stdout {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let Some(line) = normalize_output_line(&line) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !has_relevant_output && is_relevant_output(&line) {
|
||||
has_relevant_output = true;
|
||||
}
|
||||
|
||||
if has_relevant_output {
|
||||
let _ = app_stdout.emit(
|
||||
"model-setup:output",
|
||||
ModelSetupOutput {
|
||||
provider_id: provider_id_stdout.clone(),
|
||||
line,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let provider_id_stderr = provider_id.to_string();
|
||||
let app_stderr = app_handle.clone();
|
||||
|
||||
let stderr_task = tokio::spawn(async move {
|
||||
let mut has_relevant_output = false;
|
||||
if let Some(stderr) = stderr {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let Some(line) = normalize_output_line(&line) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !has_relevant_output && is_relevant_output(&line) {
|
||||
has_relevant_output = true;
|
||||
}
|
||||
|
||||
if has_relevant_output {
|
||||
let _ = app_stderr.emit(
|
||||
"model-setup:output",
|
||||
ModelSetupOutput {
|
||||
provider_id: provider_id_stderr.clone(),
|
||||
line,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _ = tokio::join!(stdout_task, stderr_task);
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to wait for sign-in flow: {e}"))?;
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
Err(format!("Goose sign-in exited with code {code}"))
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod types;
|
||||
use std::sync::Arc;
|
||||
|
||||
use services::acp::AcpSessionRegistry;
|
||||
use services::goose_config::GooseConfig;
|
||||
use services::personas::PersonaStore;
|
||||
use tauri_plugin_window_state::StateFlags;
|
||||
|
||||
@@ -30,6 +31,7 @@ pub fn run() {
|
||||
.build(),
|
||||
)
|
||||
.manage(PersonaStore::new())
|
||||
.manage(GooseConfig::new())
|
||||
.manage(acp_registry)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::agents::list_personas,
|
||||
@@ -67,6 +69,16 @@ pub fn run() {
|
||||
commands::doctor::run_doctor,
|
||||
commands::doctor::run_doctor_fix,
|
||||
commands::git::get_git_state,
|
||||
commands::credentials::get_provider_config,
|
||||
commands::credentials::save_provider_field,
|
||||
commands::credentials::delete_provider_config,
|
||||
commands::credentials::check_all_provider_status,
|
||||
commands::credentials::restart_app,
|
||||
commands::model_setup::authenticate_model_provider,
|
||||
commands::agent_setup::check_agent_installed,
|
||||
commands::agent_setup::check_agent_auth,
|
||||
commands::agent_setup::install_agent,
|
||||
commands::agent_setup::authenticate_agent,
|
||||
commands::system::get_home_dir,
|
||||
commands::system::path_exists,
|
||||
])
|
||||
|
||||
@@ -158,7 +158,7 @@ fn default_serve_working_dir() -> PathBuf {
|
||||
// Binary resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn resolve_goose_binary() -> Result<PathBuf, String> {
|
||||
pub(crate) fn resolve_goose_binary() -> Result<PathBuf, String> {
|
||||
let binary_path = if let Ok(override_path) = std::env::var("GOOSE_BIN") {
|
||||
let path = PathBuf::from(&override_path);
|
||||
if !path.exists() {
|
||||
|
||||
@@ -4,6 +4,7 @@ mod payloads;
|
||||
mod registry;
|
||||
mod writer;
|
||||
|
||||
pub(crate) use goose_serve::resolve_goose_binary;
|
||||
pub use manager::{AcpSessionInfo, GooseAcpManager};
|
||||
pub use registry::{AcpRunningSession, AcpSessionRegistry};
|
||||
pub use writer::TauriMessageWriter;
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::provider_defs::{find_provider_def, PROVIDER_CONFIG_DEFS};
|
||||
|
||||
const KEYRING_SERVICE: &str = "goose";
|
||||
const KEYRING_USERNAME: &str = "secrets";
|
||||
const CONFIG_YAML_NAME: &str = "config.yaml";
|
||||
const SECRETS_YAML_NAME: &str = "secrets.yaml";
|
||||
const SECRET_MASK_PREFIX_LEN: usize = 4;
|
||||
const SECRET_MASK_SUFFIX_LEN: usize = 3;
|
||||
const SECRET_MASK_FALLBACK: &str = "***";
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProviderStatus {
|
||||
pub provider_id: String,
|
||||
pub is_configured: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FieldValue {
|
||||
pub key: String,
|
||||
pub value: Option<String>,
|
||||
pub is_set: bool,
|
||||
pub is_secret: bool,
|
||||
pub required: bool,
|
||||
}
|
||||
|
||||
pub struct GooseConfig {
|
||||
config_dir: PathBuf,
|
||||
guard: Mutex<()>,
|
||||
}
|
||||
|
||||
impl GooseConfig {
|
||||
pub fn new() -> Self {
|
||||
let config_dir = Self::resolve_config_dir();
|
||||
log::info!("GooseConfig using config dir: {}", config_dir.display());
|
||||
Self {
|
||||
config_dir,
|
||||
guard: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_config_dir() -> PathBuf {
|
||||
if let Ok(root) = std::env::var("GOOSE_PATH_ROOT") {
|
||||
return PathBuf::from(root).join("config");
|
||||
}
|
||||
|
||||
let strategy = choose_app_strategy(AppStrategyArgs {
|
||||
top_level_domain: "Block".to_string(),
|
||||
author: "Block".to_string(),
|
||||
app_name: "goose".to_string(),
|
||||
})
|
||||
.expect("goose requires a home dir");
|
||||
|
||||
strategy.config_dir()
|
||||
}
|
||||
|
||||
fn read_config_map(&self) -> serde_yaml::Mapping {
|
||||
let config_path = self.config_dir.join(CONFIG_YAML_NAME);
|
||||
match std::fs::read_to_string(&config_path) {
|
||||
Ok(contents) => {
|
||||
serde_yaml::from_str::<serde_yaml::Mapping>(&contents).unwrap_or_default()
|
||||
}
|
||||
Err(_) => serde_yaml::Mapping::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config_map(&self, config: &serde_yaml::Mapping) -> Result<(), String> {
|
||||
let path = self.config_dir.join(CONFIG_YAML_NAME);
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
|
||||
let yaml = serde_yaml::to_string(config)
|
||||
.map_err(|e| format!("Failed to serialize config: {e}"))?;
|
||||
|
||||
std::fs::write(&path, yaml).map_err(|e| format!("Failed to write config file: {e}"))
|
||||
}
|
||||
|
||||
fn get_secret(&self, key: &str) -> Option<String> {
|
||||
let env_key = key.to_uppercase();
|
||||
if let Ok(value) = std::env::var(&env_key) {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
let secrets = self.all_secrets();
|
||||
secrets
|
||||
.get(key)
|
||||
.and_then(|value| value.as_str().map(|secret| secret.to_string()))
|
||||
}
|
||||
|
||||
fn mask_secret_value(value: &str) -> String {
|
||||
let prefix: String = value.chars().take(SECRET_MASK_PREFIX_LEN).collect();
|
||||
let suffix_chars: Vec<char> = value.chars().rev().take(SECRET_MASK_SUFFIX_LEN).collect();
|
||||
let suffix: String = suffix_chars.into_iter().rev().collect();
|
||||
|
||||
if prefix.is_empty()
|
||||
|| suffix.is_empty()
|
||||
|| value.chars().count() <= SECRET_MASK_PREFIX_LEN + SECRET_MASK_SUFFIX_LEN
|
||||
{
|
||||
return SECRET_MASK_FALLBACK.to_string();
|
||||
}
|
||||
|
||||
format!("{prefix}...{suffix}")
|
||||
}
|
||||
|
||||
fn has_param(&self, key: &str) -> bool {
|
||||
self.get_param(key).is_some()
|
||||
}
|
||||
|
||||
fn has_secret(&self, key: &str) -> bool {
|
||||
self.get_secret(key).is_some()
|
||||
}
|
||||
|
||||
pub fn get_param(&self, key: &str) -> Option<String> {
|
||||
let env_key = key.to_uppercase();
|
||||
if let Ok(value) = std::env::var(&env_key) {
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
let config = self.read_config_map();
|
||||
let yaml_key = serde_yaml::Value::String(key.to_string());
|
||||
config
|
||||
.get(&yaml_key)
|
||||
.and_then(|value| value.as_str().map(|param| param.to_string()))
|
||||
}
|
||||
|
||||
pub fn get_secret_masked(&self, key: &str) -> Option<String> {
|
||||
self.get_secret(key)
|
||||
.map(|value| Self::mask_secret_value(&value))
|
||||
}
|
||||
|
||||
pub fn set_param(&self, key: &str, value: &str) -> Result<(), String> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut config = self.read_config_map();
|
||||
config.insert(
|
||||
serde_yaml::Value::String(key.to_string()),
|
||||
serde_yaml::Value::String(value.to_string()),
|
||||
);
|
||||
|
||||
self.write_config_map(&config)
|
||||
}
|
||||
|
||||
pub fn delete_param(&self, key: &str) -> Result<bool, String> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut config = self.read_config_map();
|
||||
let yaml_key = serde_yaml::Value::String(key.to_string());
|
||||
let removed = config.remove(&yaml_key).is_some();
|
||||
|
||||
if removed {
|
||||
self.write_config_map(&config)?;
|
||||
}
|
||||
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
pub fn set_secret(&self, key: &str, value: &str) -> Result<(), String> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut secrets = self.all_secrets();
|
||||
secrets.insert(key.to_string(), Value::String(value.to_string()));
|
||||
|
||||
if self.is_keyring_disabled() {
|
||||
self.write_secrets_to_file(&secrets)
|
||||
} else {
|
||||
self.write_secrets_to_keyring(&secrets)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_secret(&self, key: &str) -> Result<bool, String> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut secrets = self.all_secrets();
|
||||
let removed = secrets.remove(key).is_some();
|
||||
|
||||
if removed {
|
||||
if self.is_keyring_disabled() {
|
||||
self.write_secrets_to_file(&secrets)?;
|
||||
} else {
|
||||
self.write_secrets_to_keyring(&secrets)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
fn has_oauth_cache(&self, cache_path: &str) -> bool {
|
||||
let full_path = self.config_dir.join(cache_path);
|
||||
if full_path.is_dir() {
|
||||
std::fs::read_dir(&full_path)
|
||||
.map(|mut entries| entries.any(|_| true))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
full_path.exists()
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_oauth_cache(&self, cache_path: &str) -> Result<bool, String> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
let full_path = self.config_dir.join(cache_path);
|
||||
|
||||
if full_path.is_dir() {
|
||||
std::fs::remove_dir_all(&full_path)
|
||||
.map_err(|e| format!("Failed to remove OAuth cache directory: {e}"))?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if full_path.exists() {
|
||||
std::fs::remove_file(&full_path)
|
||||
.map_err(|e| format!("Failed to remove OAuth cache file: {e}"))?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn check_provider_status(&self, provider_id: &str) -> ProviderStatus {
|
||||
if provider_id == "databricks" {
|
||||
let has_host = self.has_param("DATABRICKS_HOST");
|
||||
let has_token = self.has_secret("DATABRICKS_TOKEN");
|
||||
let has_oauth = self.has_oauth_cache("databricks/oauth");
|
||||
|
||||
return ProviderStatus {
|
||||
provider_id: provider_id.to_string(),
|
||||
is_configured: has_host && (has_token || has_oauth),
|
||||
};
|
||||
}
|
||||
|
||||
let def = match find_provider_def(provider_id) {
|
||||
Some(def) => def,
|
||||
None => {
|
||||
return ProviderStatus {
|
||||
provider_id: provider_id.to_string(),
|
||||
is_configured: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let has_oauth = def
|
||||
.oauth_cache_path
|
||||
.map(|p| self.has_oauth_cache(p))
|
||||
.unwrap_or(false);
|
||||
|
||||
if has_oauth {
|
||||
return ProviderStatus {
|
||||
provider_id: provider_id.to_string(),
|
||||
is_configured: true,
|
||||
};
|
||||
}
|
||||
|
||||
let all_required_present = def.keys.iter().all(|k| {
|
||||
if !k.required {
|
||||
return true;
|
||||
}
|
||||
if k.is_secret {
|
||||
self.has_secret(k.name)
|
||||
} else {
|
||||
self.has_param(k.name)
|
||||
}
|
||||
});
|
||||
|
||||
let has_any_key = def.keys.iter().any(|k| {
|
||||
if k.is_secret {
|
||||
self.has_secret(k.name)
|
||||
} else {
|
||||
self.has_param(k.name)
|
||||
}
|
||||
});
|
||||
|
||||
let is_configured = if def.keys.is_empty() {
|
||||
false
|
||||
} else {
|
||||
all_required_present && has_any_key
|
||||
};
|
||||
|
||||
ProviderStatus {
|
||||
provider_id: provider_id.to_string(),
|
||||
is_configured,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_all_provider_status(&self) -> Vec<ProviderStatus> {
|
||||
PROVIDER_CONFIG_DEFS
|
||||
.iter()
|
||||
.map(|def| self.check_provider_status(def.id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_provider_field_values(&self, provider_id: &str) -> Result<Vec<FieldValue>, String> {
|
||||
let def = find_provider_def(provider_id)
|
||||
.ok_or_else(|| format!("Unknown provider '{provider_id}'"))?;
|
||||
|
||||
Ok(def
|
||||
.keys
|
||||
.iter()
|
||||
.map(|config_key| {
|
||||
let value = if config_key.is_secret {
|
||||
self.get_secret_masked(config_key.name)
|
||||
} else {
|
||||
self.get_param(config_key.name)
|
||||
};
|
||||
|
||||
FieldValue {
|
||||
key: config_key.name.to_string(),
|
||||
is_set: value.is_some(),
|
||||
value,
|
||||
is_secret: config_key.is_secret,
|
||||
required: config_key.required,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn delete_all_provider_fields(&self, provider_id: &str) -> Result<(), String> {
|
||||
let def = find_provider_def(provider_id)
|
||||
.ok_or_else(|| format!("Unknown provider '{provider_id}'"))?;
|
||||
|
||||
for config_key in def.keys {
|
||||
if config_key.is_secret {
|
||||
self.delete_secret(config_key.name)?;
|
||||
} else {
|
||||
self.delete_param(config_key.name)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(oauth_cache_path) = def.oauth_cache_path {
|
||||
self.delete_oauth_cache(oauth_cache_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_keyring_disabled(&self) -> bool {
|
||||
if std::env::var("GOOSE_DISABLE_KEYRING").is_ok() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let config_path = self.config_dir.join(CONFIG_YAML_NAME);
|
||||
if let Ok(contents) = std::fs::read_to_string(&config_path) {
|
||||
if let Ok(yaml) = serde_yaml::from_str::<serde_yaml::Mapping>(&contents) {
|
||||
let key = serde_yaml::Value::String("GOOSE_DISABLE_KEYRING".to_string());
|
||||
if let Some(val) = yaml.get(&key) {
|
||||
return val.as_bool().unwrap_or(false)
|
||||
|| val
|
||||
.as_str()
|
||||
.map(|s| s == "true" || s == "1")
|
||||
.unwrap_or(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn all_secrets(&self) -> HashMap<String, Value> {
|
||||
if self.is_keyring_disabled() {
|
||||
return self.read_secrets_from_file();
|
||||
}
|
||||
|
||||
match self.read_secrets_from_keyring() {
|
||||
Ok(secrets) => secrets,
|
||||
Err(e) => {
|
||||
log::warn!("Keyring read failed, falling back to secrets file: {e}");
|
||||
self.read_secrets_from_file()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_secrets_from_keyring(&self) -> Result<HashMap<String, Value>, String> {
|
||||
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_USERNAME)
|
||||
.map_err(|e| format!("Failed to access keyring: {e}"))?;
|
||||
|
||||
match entry.get_password() {
|
||||
Ok(json_str) => serde_json::from_str(&json_str)
|
||||
.map_err(|e| format!("Failed to parse keyring JSON: {e}")),
|
||||
Err(keyring::Error::NoEntry) => Ok(HashMap::new()),
|
||||
Err(e) => Err(format!("Failed to read keyring: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_secrets_from_file(&self) -> HashMap<String, Value> {
|
||||
let path = self.config_dir.join(SECRETS_YAML_NAME);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(contents) => serde_yaml::from_str::<HashMap<String, String>>(&contents)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, Value::String(v)))
|
||||
.collect(),
|
||||
Err(_) => HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_secrets_to_keyring(&self, secrets: &HashMap<String, Value>) -> Result<(), String> {
|
||||
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_USERNAME)
|
||||
.map_err(|e| format!("Failed to access keyring: {e}"))?;
|
||||
|
||||
let json_str = serde_json::to_string(secrets)
|
||||
.map_err(|e| format!("Failed to serialize secrets: {e}"))?;
|
||||
|
||||
entry
|
||||
.set_password(&json_str)
|
||||
.map_err(|e| format!("Failed to write to keyring: {e}"))
|
||||
}
|
||||
|
||||
fn write_secrets_to_file(&self, secrets: &HashMap<String, Value>) -> Result<(), String> {
|
||||
let path = self.config_dir.join(SECRETS_YAML_NAME);
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
|
||||
let yaml_map: HashMap<String, String> = secrets
|
||||
.iter()
|
||||
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
|
||||
.collect();
|
||||
|
||||
let yaml = serde_yaml::to_string(&yaml_map)
|
||||
.map_err(|e| format!("Failed to serialize secrets: {e}"))?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.mode(0o600)
|
||||
.open(&path)
|
||||
.map_err(|e| format!("Failed to open secrets file: {e}"))?;
|
||||
|
||||
file.write_all(yaml.as_bytes())
|
||||
.map_err(|e| format!("Failed to write secrets file: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
std::fs::write(&path, yaml).map_err(|e| format!("Failed to write secrets file: {e}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod acp;
|
||||
pub mod goose_config;
|
||||
pub mod personas;
|
||||
pub(crate) mod provider_defs;
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
pub(crate) struct ConfigKey {
|
||||
pub name: &'static str,
|
||||
pub is_secret: bool,
|
||||
pub required: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct ProviderConfigDef {
|
||||
pub id: &'static str,
|
||||
pub keys: &'static [ConfigKey],
|
||||
pub oauth_cache_path: Option<&'static str>,
|
||||
}
|
||||
|
||||
const fn key(name: &'static str, is_secret: bool, required: bool) -> ConfigKey {
|
||||
ConfigKey {
|
||||
name,
|
||||
is_secret,
|
||||
required,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static PROVIDER_CONFIG_DEFS: &[ProviderConfigDef] = &[
|
||||
ProviderConfigDef {
|
||||
id: "anthropic",
|
||||
keys: &[key("ANTHROPIC_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "openai",
|
||||
keys: &[key("OPENAI_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "google",
|
||||
keys: &[key("GOOGLE_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "openrouter",
|
||||
keys: &[key("OPENROUTER_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "xai",
|
||||
keys: &[key("XAI_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "nanogpt",
|
||||
keys: &[key("NANOGPT_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "venice",
|
||||
keys: &[key("VENICE_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "tetrate",
|
||||
keys: &[key("TETRATE_API_KEY", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "databricks",
|
||||
keys: &[
|
||||
key("DATABRICKS_HOST", false, true),
|
||||
key("DATABRICKS_TOKEN", true, false),
|
||||
],
|
||||
oauth_cache_path: Some("databricks/oauth"),
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "snowflake",
|
||||
keys: &[
|
||||
key("SNOWFLAKE_HOST", false, true),
|
||||
key("SNOWFLAKE_TOKEN", true, true),
|
||||
],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "litellm",
|
||||
keys: &[
|
||||
key("LITELLM_HOST", false, true),
|
||||
key("LITELLM_API_KEY", true, false),
|
||||
],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "azure",
|
||||
keys: &[
|
||||
key("AZURE_OPENAI_ENDPOINT", false, true),
|
||||
key("AZURE_OPENAI_DEPLOYMENT_NAME", false, true),
|
||||
key("AZURE_OPENAI_API_KEY", true, false),
|
||||
],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "bedrock",
|
||||
keys: &[key("AWS_REGION", false, false)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "gcp_vertex_ai",
|
||||
keys: &[
|
||||
key("GCP_PROJECT_ID", false, true),
|
||||
key("GCP_LOCATION", false, true),
|
||||
],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "chatgpt_codex",
|
||||
keys: &[key("CHATGPT_CODEX_TOKEN", true, true)],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "github_copilot",
|
||||
keys: &[],
|
||||
oauth_cache_path: Some("githubcopilot/info.json"),
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "ollama",
|
||||
keys: &[],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
ProviderConfigDef {
|
||||
id: "local_inference",
|
||||
keys: &[],
|
||||
oauth_cache_path: None,
|
||||
},
|
||||
];
|
||||
|
||||
pub(crate) fn find_config_key(key_name: &str) -> Option<&'static ConfigKey> {
|
||||
PROVIDER_CONFIG_DEFS
|
||||
.iter()
|
||||
.flat_map(|def| def.keys.iter())
|
||||
.find(|key| key.name == key_name)
|
||||
}
|
||||
|
||||
pub(crate) fn find_provider_def(provider_id: &str) -> Option<&'static ProviderConfigDef> {
|
||||
PROVIDER_CONFIG_DEFS.iter().find(|d| d.id == provider_id)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
interface AgentSetupOutput {
|
||||
providerId: string;
|
||||
line: string;
|
||||
}
|
||||
|
||||
export async function checkAgentInstalled(
|
||||
providerId: string,
|
||||
): Promise<boolean> {
|
||||
return invoke("check_agent_installed", { providerId });
|
||||
}
|
||||
|
||||
export async function checkAgentAuth(providerId: string): Promise<boolean> {
|
||||
return invoke("check_agent_auth", { providerId });
|
||||
}
|
||||
|
||||
export async function installAgent(providerId: string): Promise<void> {
|
||||
return invoke("install_agent", { providerId });
|
||||
}
|
||||
|
||||
export async function authenticateAgent(providerId: string): Promise<void> {
|
||||
return invoke("authenticate_agent", { providerId });
|
||||
}
|
||||
|
||||
export function onAgentSetupOutput(
|
||||
providerId: string,
|
||||
callback: (line: string) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
return listen<AgentSetupOutput>("agent-setup:output", (event) => {
|
||||
if (event.payload.providerId === providerId) {
|
||||
callback(event.payload.line);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { ProviderFieldValue } from "@/shared/types/providers";
|
||||
|
||||
export interface ProviderStatus {
|
||||
providerId: string;
|
||||
isConfigured: boolean;
|
||||
}
|
||||
|
||||
export async function getProviderConfig(
|
||||
providerId: string,
|
||||
): Promise<ProviderFieldValue[]> {
|
||||
return invoke("get_provider_config", { providerId });
|
||||
}
|
||||
|
||||
export async function saveProviderField(
|
||||
key: string,
|
||||
value: string,
|
||||
): Promise<void> {
|
||||
return invoke("save_provider_field", { key, value });
|
||||
}
|
||||
|
||||
export async function deleteProviderConfig(providerId: string): Promise<void> {
|
||||
return invoke("delete_provider_config", { providerId });
|
||||
}
|
||||
|
||||
export async function checkAllProviderStatus(): Promise<ProviderStatus[]> {
|
||||
return invoke("check_all_provider_status");
|
||||
}
|
||||
|
||||
export async function restartApp(): Promise<void> {
|
||||
return invoke("restart_app");
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
interface ModelSetupOutput {
|
||||
providerId: string;
|
||||
line: string;
|
||||
}
|
||||
|
||||
export async function authenticateModelProvider(
|
||||
providerId: string,
|
||||
providerLabel: string,
|
||||
): Promise<void> {
|
||||
return invoke("authenticate_model_provider", { providerId, providerLabel });
|
||||
}
|
||||
|
||||
export function onModelSetupOutput(
|
||||
providerId: string,
|
||||
callback: (line: string) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
return listen<ModelSetupOutput>("model-setup:output", (event) => {
|
||||
if (event.payload.providerId === providerId) {
|
||||
callback(event.payload.line);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import {
|
||||
getProviderConfig,
|
||||
saveProviderField,
|
||||
deleteProviderConfig,
|
||||
type ProviderStatus,
|
||||
checkAllProviderStatus,
|
||||
restartApp,
|
||||
} from "@/features/providers/api/credentials";
|
||||
import type { ProviderFieldValue } from "@/shared/types/providers";
|
||||
|
||||
interface UseCredentialsReturn {
|
||||
configuredIds: Set<string>;
|
||||
loading: boolean;
|
||||
saving: boolean;
|
||||
needsRestart: boolean;
|
||||
getConfig: (providerId: string) => Promise<ProviderFieldValue[]>;
|
||||
save: (key: string, value: string) => Promise<void>;
|
||||
remove: (providerId: string) => Promise<void>;
|
||||
restart: () => Promise<void>;
|
||||
completeNativeSetup: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useCredentials(): UseCredentialsReturn {
|
||||
const [statuses, setStatuses] = useState<ProviderStatus[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [needsRestart, setNeedsRestart] = useState(false);
|
||||
|
||||
const refreshStatuses = useCallback(async () => {
|
||||
const nextStatuses = await checkAllProviderStatus();
|
||||
setStatuses(nextStatuses);
|
||||
return nextStatuses;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshStatuses()
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
}, [refreshStatuses]);
|
||||
|
||||
const configuredIds = useMemo(
|
||||
() =>
|
||||
new Set(statuses.filter((s) => s.isConfigured).map((s) => s.providerId)),
|
||||
[statuses],
|
||||
);
|
||||
|
||||
const getConfig = useCallback(async (providerId: string) => {
|
||||
return getProviderConfig(providerId);
|
||||
}, []);
|
||||
|
||||
const save = useCallback(
|
||||
async (key: string, value: string) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await saveProviderField(key, value);
|
||||
await refreshStatuses();
|
||||
setNeedsRestart(true);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
},
|
||||
[refreshStatuses],
|
||||
);
|
||||
|
||||
const remove = useCallback(
|
||||
async (providerId: string) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await deleteProviderConfig(providerId);
|
||||
await refreshStatuses();
|
||||
setNeedsRestart(true);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
},
|
||||
[refreshStatuses],
|
||||
);
|
||||
|
||||
const restart = useCallback(async () => {
|
||||
await restartApp();
|
||||
}, []);
|
||||
|
||||
const completeNativeSetup = useCallback(async () => {
|
||||
await refreshStatuses();
|
||||
setNeedsRestart(true);
|
||||
}, [refreshStatuses]);
|
||||
|
||||
return {
|
||||
configuredIds,
|
||||
loading,
|
||||
saving,
|
||||
needsRestart,
|
||||
getConfig,
|
||||
save,
|
||||
remove,
|
||||
restart,
|
||||
completeNativeSetup,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
import type { ProviderCatalogEntry } from "@/shared/types/providers";
|
||||
|
||||
export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
||||
// ── Agent providers ──────────────────────────────────────────────
|
||||
{
|
||||
id: "goose",
|
||||
displayName: "Goose",
|
||||
category: "agent",
|
||||
description: "Block's open-source coding agent",
|
||||
setupMethod: "none",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "claude-acp",
|
||||
displayName: "Claude Code",
|
||||
category: "agent",
|
||||
description: "Anthropic's agentic coding tool",
|
||||
setupMethod: "cli_auth",
|
||||
binaryName: "claude-agent-acp",
|
||||
installCommand:
|
||||
"npm install -g @anthropic-ai/claude-code @zed-industries/claude-agent-acp",
|
||||
authCommand: "claude auth login",
|
||||
authStatusCommand: "claude auth status",
|
||||
docsUrl: "https://docs.anthropic.com/en/docs/claude-code",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "codex-acp",
|
||||
displayName: "Codex",
|
||||
category: "agent",
|
||||
description: "OpenAI's coding agent",
|
||||
setupMethod: "cli_auth",
|
||||
binaryName: "codex-acp",
|
||||
installCommand: "npm install -g @openai/codex @zed-industries/codex-acp",
|
||||
authCommand: "codex login",
|
||||
authStatusCommand: "codex login status",
|
||||
docsUrl: "https://github.com/openai/codex",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "copilot-acp",
|
||||
displayName: "GitHub Copilot",
|
||||
category: "agent",
|
||||
description: "GitHub's AI pair programmer",
|
||||
setupMethod: "cli_auth",
|
||||
binaryName: "copilot",
|
||||
installCommand: "npm install -g @github/copilot",
|
||||
authCommand: "copilot login",
|
||||
docsUrl: "https://docs.github.com/en/copilot/github-copilot-in-the-cli",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "amp-acp",
|
||||
displayName: "Amp",
|
||||
category: "agent",
|
||||
description: "Sourcegraph's coding agent",
|
||||
setupMethod: "cli_auth",
|
||||
binaryName: "amp-acp",
|
||||
installCommand: "npm install -g @sourcegraph/amp@latest amp-acp",
|
||||
authCommand: "amp login",
|
||||
authStatusCommand: "amp usage",
|
||||
docsUrl: "https://ampcode.com",
|
||||
tier: "standard",
|
||||
},
|
||||
{
|
||||
id: "cursor-agent",
|
||||
displayName: "Cursor Agent",
|
||||
category: "agent",
|
||||
description: "Cursor's AI agent",
|
||||
setupMethod: "cli_auth",
|
||||
binaryName: "cursor-agent",
|
||||
installCommand: "curl -fsSL https://cursor.com/install | bash",
|
||||
authCommand: "cursor-agent login",
|
||||
authStatusCommand: "cursor-agent status",
|
||||
docsUrl: "https://docs.cursor.com/en/cli/overview",
|
||||
tier: "standard",
|
||||
},
|
||||
{
|
||||
id: "pi-acp",
|
||||
displayName: "Pi",
|
||||
category: "agent",
|
||||
description: "Open-source AI coding agent",
|
||||
setupMethod: "cli_auth",
|
||||
binaryName: "pi-acp",
|
||||
docsUrl: "https://github.com/badlogic/pi-mono",
|
||||
tier: "standard",
|
||||
showOnlyWhenInstalled: true,
|
||||
},
|
||||
|
||||
// ── Model providers (power Goose) ────────────────────────────────
|
||||
{
|
||||
id: "anthropic",
|
||||
displayName: "Anthropic",
|
||||
category: "model",
|
||||
description: "Claude models",
|
||||
setupMethod: "single_api_key",
|
||||
envVar: "ANTHROPIC_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "ANTHROPIC_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
docsUrl: "https://console.anthropic.com/settings/keys",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "google",
|
||||
displayName: "Google Gemini",
|
||||
category: "model",
|
||||
description: "Gemini models",
|
||||
setupMethod: "single_api_key",
|
||||
envVar: "GOOGLE_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "GOOGLE_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
docsUrl: "https://aistudio.google.com/apikey",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "chatgpt_codex",
|
||||
displayName: "ChatGPT Codex",
|
||||
category: "model",
|
||||
description: "OpenAI via ChatGPT subscription",
|
||||
setupMethod: "oauth_device_code",
|
||||
nativeConnectQuery: "ChatGPT Codex",
|
||||
docsUrl: "https://chatgpt.com",
|
||||
tier: "standard",
|
||||
},
|
||||
{
|
||||
id: "openai",
|
||||
displayName: "OpenAI",
|
||||
category: "model",
|
||||
description: "GPT and o-series models",
|
||||
setupMethod: "config_fields",
|
||||
envVar: "OPENAI_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "OPENAI_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
docsUrl: "https://platform.openai.com/api-keys",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "ollama",
|
||||
displayName: "Ollama",
|
||||
category: "model",
|
||||
description: "Run models locally",
|
||||
setupMethod: "local",
|
||||
docsUrl: "https://ollama.com",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "openrouter",
|
||||
displayName: "OpenRouter",
|
||||
category: "model",
|
||||
description: "Unified API for many models",
|
||||
setupMethod: "single_api_key",
|
||||
envVar: "OPENROUTER_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "OPENROUTER_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
docsUrl: "https://openrouter.ai/keys",
|
||||
tier: "promoted",
|
||||
},
|
||||
{
|
||||
id: "databricks",
|
||||
displayName: "Databricks",
|
||||
category: "model",
|
||||
description: "Databricks Foundation Models",
|
||||
setupMethod: "host_with_oauth_fallback",
|
||||
fields: [
|
||||
{
|
||||
key: "DATABRICKS_HOST",
|
||||
label: "Host URL",
|
||||
secret: false,
|
||||
required: true,
|
||||
placeholder: "https://dbc-...cloud.databricks.com",
|
||||
},
|
||||
{
|
||||
key: "DATABRICKS_TOKEN",
|
||||
label: "Access Token",
|
||||
secret: true,
|
||||
required: false,
|
||||
placeholder: "Paste your access token",
|
||||
},
|
||||
],
|
||||
tier: "standard",
|
||||
},
|
||||
{
|
||||
id: "github_copilot",
|
||||
displayName: "GitHub Copilot Models",
|
||||
category: "model",
|
||||
description: "Models via GitHub Copilot subscription",
|
||||
setupMethod: "oauth_device_code",
|
||||
nativeConnectQuery: "GitHub Copilot",
|
||||
tier: "standard",
|
||||
},
|
||||
{
|
||||
id: "xai",
|
||||
displayName: "xAI",
|
||||
category: "model",
|
||||
description: "Grok models",
|
||||
setupMethod: "single_api_key",
|
||||
envVar: "XAI_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "XAI_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
tier: "standard",
|
||||
},
|
||||
{
|
||||
id: "azure",
|
||||
displayName: "Azure OpenAI",
|
||||
category: "model",
|
||||
description: "OpenAI models on Azure",
|
||||
setupMethod: "config_fields",
|
||||
fields: [
|
||||
{
|
||||
key: "AZURE_OPENAI_ENDPOINT",
|
||||
label: "Endpoint",
|
||||
secret: false,
|
||||
required: true,
|
||||
placeholder: "https://your-resource.openai.azure.com",
|
||||
},
|
||||
{
|
||||
key: "AZURE_OPENAI_DEPLOYMENT_NAME",
|
||||
label: "Deployment",
|
||||
secret: false,
|
||||
required: true,
|
||||
placeholder: "gpt-4o",
|
||||
},
|
||||
{
|
||||
key: "AZURE_OPENAI_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: false,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "bedrock",
|
||||
displayName: "AWS Bedrock",
|
||||
category: "model",
|
||||
description: "Models on AWS",
|
||||
setupMethod: "cloud_credentials",
|
||||
fields: [
|
||||
{
|
||||
key: "AWS_REGION",
|
||||
label: "AWS Region",
|
||||
secret: false,
|
||||
required: false,
|
||||
placeholder: "us-west-2",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "gcp_vertex_ai",
|
||||
displayName: "GCP Vertex AI",
|
||||
category: "model",
|
||||
description: "Models on Google Cloud",
|
||||
setupMethod: "cloud_credentials",
|
||||
fields: [
|
||||
{
|
||||
key: "GCP_PROJECT_ID",
|
||||
label: "Project ID",
|
||||
secret: false,
|
||||
required: true,
|
||||
placeholder: "my-gcp-project",
|
||||
},
|
||||
{
|
||||
key: "GCP_LOCATION",
|
||||
label: "Location",
|
||||
secret: false,
|
||||
required: true,
|
||||
placeholder: "us-central1",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "litellm",
|
||||
displayName: "LiteLLM",
|
||||
category: "model",
|
||||
description: "LiteLLM proxy gateway",
|
||||
setupMethod: "config_fields",
|
||||
envVar: "LITELLM_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "LITELLM_HOST",
|
||||
label: "Host URL",
|
||||
secret: false,
|
||||
required: true,
|
||||
placeholder: "https://your-proxy.example.com",
|
||||
},
|
||||
{
|
||||
key: "LITELLM_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: false,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "nanogpt",
|
||||
displayName: "NanoGPT",
|
||||
category: "model",
|
||||
description: "NanoGPT inference",
|
||||
setupMethod: "single_api_key",
|
||||
envVar: "NANOGPT_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "NANOGPT_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "tetrate",
|
||||
displayName: "Tetrate",
|
||||
category: "model",
|
||||
description: "Tetrate AI gateway",
|
||||
setupMethod: "single_api_key",
|
||||
fields: [
|
||||
{
|
||||
key: "TETRATE_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "venice",
|
||||
displayName: "Venice",
|
||||
category: "model",
|
||||
description: "Venice AI",
|
||||
setupMethod: "single_api_key",
|
||||
envVar: "VENICE_API_KEY",
|
||||
fields: [
|
||||
{
|
||||
key: "VENICE_API_KEY",
|
||||
label: "API Key",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your API key",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "snowflake",
|
||||
displayName: "Snowflake",
|
||||
category: "model",
|
||||
description: "Snowflake Cortex",
|
||||
setupMethod: "config_fields",
|
||||
fields: [
|
||||
{
|
||||
key: "SNOWFLAKE_HOST",
|
||||
label: "Host URL",
|
||||
secret: false,
|
||||
required: true,
|
||||
placeholder: "https://your-account.snowflakecomputing.com",
|
||||
},
|
||||
{
|
||||
key: "SNOWFLAKE_TOKEN",
|
||||
label: "Access Token",
|
||||
secret: true,
|
||||
required: true,
|
||||
placeholder: "Paste your access token",
|
||||
},
|
||||
],
|
||||
tier: "advanced",
|
||||
},
|
||||
{
|
||||
id: "local_inference",
|
||||
displayName: "Local Inference",
|
||||
category: "model",
|
||||
description: "Custom local model server",
|
||||
setupMethod: "local",
|
||||
tier: "advanced",
|
||||
},
|
||||
];
|
||||
|
||||
export function getCatalogEntry(
|
||||
providerId: string,
|
||||
): ProviderCatalogEntry | undefined {
|
||||
return PROVIDER_CATALOG.find((p) => p.id === providerId);
|
||||
}
|
||||
|
||||
export function getAgentProviders(): ProviderCatalogEntry[] {
|
||||
return PROVIDER_CATALOG.filter((p) => p.category === "agent");
|
||||
}
|
||||
|
||||
export function getModelProviders(): ProviderCatalogEntry[] {
|
||||
return PROVIDER_CATALOG.filter((p) => p.category === "model");
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
export type ProviderCategory = "agent" | "model";
|
||||
|
||||
export type ProviderSetupMethod =
|
||||
| "none"
|
||||
| "single_api_key"
|
||||
| "config_fields"
|
||||
| "host_with_oauth_fallback"
|
||||
| "oauth_browser"
|
||||
| "oauth_device_code"
|
||||
| "cloud_credentials"
|
||||
| "local"
|
||||
| "cli_auth";
|
||||
|
||||
export type ProviderTier = "promoted" | "standard" | "advanced";
|
||||
|
||||
export interface ProviderField {
|
||||
key: string;
|
||||
label: string;
|
||||
secret: boolean;
|
||||
required: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface ProviderFieldValue {
|
||||
key: string;
|
||||
value: string | null;
|
||||
isSet: boolean;
|
||||
isSecret: boolean;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface ProviderCatalogEntry {
|
||||
id: string;
|
||||
displayName: string;
|
||||
category: ProviderCategory;
|
||||
description: string;
|
||||
setupMethod: ProviderSetupMethod;
|
||||
nativeConnectQuery?: string;
|
||||
envVar?: string;
|
||||
fields?: ProviderField[];
|
||||
binaryName?: string;
|
||||
installCommand?: string;
|
||||
authCommand?: string;
|
||||
authStatusCommand?: string;
|
||||
docsUrl?: string;
|
||||
tier: ProviderTier;
|
||||
showOnlyWhenInstalled?: boolean;
|
||||
}
|
||||
|
||||
export type ProviderSetupStatus =
|
||||
| "built_in"
|
||||
| "connected"
|
||||
| "not_installed"
|
||||
| "not_configured"
|
||||
| "installing"
|
||||
| "authenticating"
|
||||
| "error";
|
||||
|
||||
export interface ProviderDisplayInfo extends ProviderCatalogEntry {
|
||||
status: ProviderSetupStatus;
|
||||
}
|
||||
Reference in New Issue
Block a user