move ApiClient to goose-providers, lifting out TlsConfig (#9869)

This commit is contained in:
Jack Amadeo
2026-06-18 20:51:09 -04:00
committed by GitHub
parent 0f1846a439
commit 5dcd3ff34c
57 changed files with 449 additions and 312 deletions
Generated
+31 -60
View File
@@ -2232,8 +2232,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "961b955a666e25ee5a1091d219128d6e6401e3dab84efb1a2bf6b4035d797b39"
dependencies = [
"crmf",
"der 0.7.10",
"spki 0.7.3",
"der",
"spki",
"x509-cert",
]
@@ -2244,8 +2244,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730"
dependencies = [
"const-oid 0.9.6",
"der 0.7.10",
"spki 0.7.3",
"der",
"spki",
"x509-cert",
]
@@ -2580,8 +2580,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36fe21b96d5b87f5de4b5b7202ec41c00110ac817ce6728fe75fb2fe5962ed92"
dependencies = [
"cms",
"der 0.7.10",
"spki 0.7.3",
"der",
"spki",
"x509-cert",
]
@@ -3350,16 +3350,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "der"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b"
dependencies = [
"const-oid 0.10.2",
"zeroize",
]
[[package]]
name = "der-parser"
version = "10.0.0"
@@ -3701,12 +3691,12 @@ version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der 0.7.10",
"der",
"digest 0.10.7",
"elliptic-curve",
"rfc6979",
"signature",
"spki 0.7.3",
"spki",
]
[[package]]
@@ -3724,7 +3714,7 @@ version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8 0.10.2",
"pkcs8",
"signature",
]
@@ -3765,7 +3755,7 @@ dependencies = [
"group",
"hkdf",
"pem-rfc7468",
"pkcs8 0.10.2",
"pkcs8",
"rand_core 0.6.4",
"sec1",
"subtle",
@@ -4819,9 +4809,6 @@ dependencies = [
"opentelemetry_sdk 0.32.1",
"pastey",
"pctx_code_mode",
"pem",
"pkcs1",
"pkcs8 0.11.0",
"process-wrap",
"pulldown-cmark",
"rand 0.8.6",
@@ -4832,7 +4819,6 @@ dependencies = [
"rubato",
"rustls",
"schemars 1.2.1",
"sec1",
"serde",
"serde_json",
"serde_urlencoded",
@@ -4990,10 +4976,14 @@ dependencies = [
"env-lock",
"futures",
"once_cell",
"pem",
"pkcs1",
"pkcs8",
"rand 0.8.6",
"regex",
"reqwest 0.13.4",
"rmcp",
"sec1",
"serde",
"serde_json",
"strum 0.28.0",
@@ -5004,6 +4994,7 @@ dependencies = [
"tokio-stream",
"tracing",
"unicode-normalization",
"url",
"utoipa 4.2.3",
"uuid",
]
@@ -8097,9 +8088,9 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der 0.7.10",
"pkcs8 0.10.2",
"spki 0.7.3",
"der",
"pkcs8",
"spki",
]
[[package]]
@@ -8108,18 +8099,8 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der 0.7.10",
"spki 0.7.3",
]
[[package]]
name = "pkcs8"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
dependencies = [
"der 0.8.0",
"spki 0.8.0",
"der",
"spki",
]
[[package]]
@@ -9045,10 +9026,10 @@ dependencies = [
"num-integer",
"num-traits",
"pkcs1",
"pkcs8 0.10.2",
"pkcs8",
"rand_core 0.6.4",
"signature",
"spki 0.7.3",
"spki",
"subtle",
"zeroize",
]
@@ -9419,9 +9400,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der 0.7.10",
"der",
"generic-array",
"pkcs8 0.10.2",
"pkcs8",
"subtle",
"zeroize",
]
@@ -9835,14 +9816,14 @@ dependencies = [
"aws-lc-rs",
"base64 0.22.1",
"const-oid 0.9.6",
"der 0.7.10",
"der",
"digest 0.10.7",
"pem",
"rand_core 0.9.5",
"sha2 0.10.9",
"signature",
"sigstore-types",
"spki 0.7.3",
"spki",
"thiserror 2.0.18",
"tracing",
"x509-cert",
@@ -9908,7 +9889,7 @@ dependencies = [
"cmpv2",
"cms",
"const-oid 0.9.6",
"der 0.7.10",
"der",
"hex",
"jiff",
"rand 0.9.4",
@@ -10123,17 +10104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der 0.7.10",
]
[[package]]
name = "spki"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
dependencies = [
"base64ct",
"der 0.8.0",
"der",
]
[[package]]
@@ -13667,10 +13638,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
dependencies = [
"const-oid 0.9.6",
"der 0.7.10",
"der",
"sha1",
"signature",
"spki 0.7.3",
"spki",
"tls_codec",
]
@@ -13700,7 +13671,7 @@ checksum = "f5ceece934a21607055b7ac5c25adb56a2ff559804b10705dc674d1d838c15e1"
dependencies = [
"cmpv2",
"cms",
"der 0.7.10",
"der",
]
[[package]]
+2
View File
@@ -103,12 +103,14 @@ rustls-tls = [
"sigstore-verify?/rustls",
"goose/rustls-tls",
"goose-mcp/rustls-tls",
"goose-providers/rustls-tls",
]
native-tls = [
"reqwest/native-tls",
"sigstore-verify?/native-tls",
"goose/native-tls",
"goose-mcp/native-tls",
"goose-providers/native-tls",
]
[dev-dependencies]
+20
View File
@@ -11,6 +11,21 @@ description.workspace = true
[lints]
workspace = true
[features]
default = []
rustls-tls = [
"reqwest/rustls",
"rmcp/reqwest",
]
native-tls = [
"dep:pem",
"dep:pkcs1",
"dep:pkcs8",
"dep:sec1",
"reqwest/native-tls",
"rmcp/reqwest-native-tls",
]
[dependencies]
anyhow = { workspace = true }
async-stream = { workspace = true }
@@ -32,6 +47,11 @@ async-trait = { workspace = true }
strum = { workspace = true }
tokio = { workspace = true }
rand = { workspace = true }
url = { workspace = true }
pem = { version = "3.0.2", default-features = false, features = ["std"], optional = true }
pkcs1 = { version = "0.7.5", default-features = false, features = ["pkcs8", "std"], optional = true }
pkcs8 = { version = "0.10.2", default-features = false, features = ["alloc", "std"], optional = true }
sec1 = { version = "0.7", default-features = false, features = ["der", "pkcs8", "std"], optional = true }
[dev-dependencies]
test-case = { workspace = true }
@@ -1,5 +1,3 @@
use crate::providers::base::DEFAULT_PROVIDER_TIMEOUT_SECS;
use crate::session_context::SESSION_ID_HEADER;
use anyhow::Result;
use async_trait::async_trait;
use reqwest::{
@@ -15,6 +13,9 @@ use std::fs::read_to_string;
use std::path::PathBuf;
use std::time::Duration;
const DEFAULT_PROVIDER_TIMEOUT_SECS: u64 = 600;
const SESSION_ID_HEADER: &str = "agent-session-id";
pub struct ApiClient {
client: Client,
host: String,
@@ -28,12 +29,7 @@ pub struct ApiClient {
pub enum AuthMethod {
NoAuth,
BearerToken(String),
ApiKey {
header_name: String,
key: String,
},
#[allow(dead_code)]
OAuth(OAuthConfig),
ApiKey { header_name: String, key: String },
Custom(Box<dyn AuthProvider>),
}
@@ -57,48 +53,6 @@ impl TlsConfig {
}
}
pub fn from_config() -> Result<Option<Self>> {
let config = crate::config::Config::global();
let mut tls_config = TlsConfig::new();
let mut has_tls_config = false;
let client_cert_path = config.get_param::<String>("GOOSE_CLIENT_CERT_PATH").ok();
let client_key_path = config.get_param::<String>("GOOSE_CLIENT_KEY_PATH").ok();
// Validate that both cert and key are provided if either is provided
match (client_cert_path, client_key_path) {
(Some(cert_path), Some(key_path)) => {
tls_config = tls_config.with_client_cert_and_key(
std::path::PathBuf::from(cert_path),
std::path::PathBuf::from(key_path),
);
has_tls_config = true;
}
(Some(_), None) => {
return Err(anyhow::anyhow!(
"Client certificate provided (GOOSE_CLIENT_CERT_PATH) but no private key (GOOSE_CLIENT_KEY_PATH)"
));
}
(None, Some(_)) => {
return Err(anyhow::anyhow!(
"Client private key provided (GOOSE_CLIENT_KEY_PATH) but no certificate (GOOSE_CLIENT_CERT_PATH)"
));
}
(None, None) => {}
}
if let Ok(ca_cert_path) = config.get_param::<String>("GOOSE_CA_CERT_PATH") {
tls_config = tls_config.with_ca_cert(std::path::PathBuf::from(ca_cert_path));
has_tls_config = true;
}
if has_tls_config {
Ok(Some(tls_config))
} else {
Ok(None)
}
}
pub fn with_client_cert_and_key(mut self, cert_path: PathBuf, key_path: PathBuf) -> Self {
self.client_identity = Some(TlsCertKeyPair {
cert_path,
@@ -234,13 +188,6 @@ fn convert_key_to_pkcs8_pem(key_pem_str: &str) -> Result<String> {
}
}
pub struct OAuthConfig {
pub host: String,
pub client_id: String,
pub redirect_url: String,
pub scopes: Vec<String>,
}
#[async_trait]
pub trait AuthProvider: Send + Sync {
async fn get_auth_header(&self) -> Result<(String, String)>;
@@ -261,7 +208,6 @@ impl fmt::Debug for AuthMethod {
.field("header_name", header_name)
.field("key", &"[hidden]")
.finish(),
AuthMethod::OAuth(_) => f.debug_tuple("OAuth").field(&"[config]").finish(),
AuthMethod::Custom(_) => f.debug_tuple("Custom").field(&"[provider]").finish(),
}
}
@@ -283,19 +229,27 @@ pub struct ApiRequestBuilder<'a> {
}
impl ApiClient {
pub fn new(host: String, auth: AuthMethod) -> Result<Self> {
Self::with_timeout(
pub fn new_with_tls(
host: String,
auth: AuthMethod,
tls_config: Option<TlsConfig>,
) -> Result<Self> {
Self::with_timeout_and_tls(
host,
auth,
Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS),
tls_config,
)
}
pub fn with_timeout(host: String, auth: AuthMethod, timeout: Duration) -> Result<Self> {
pub fn with_timeout_and_tls(
host: String,
auth: AuthMethod,
timeout: Duration,
tls_config: Option<TlsConfig>,
) -> Result<Self> {
let mut client_builder = Client::builder().timeout(timeout);
// Configure TLS if needed
let tls_config = TlsConfig::from_config()?;
if let Some(ref config) = tls_config {
client_builder = Self::configure_tls(client_builder, config)?;
}
@@ -445,16 +399,6 @@ impl ApiClient {
Ok(url)
}
async fn get_oauth_token(&self, config: &OAuthConfig) -> Result<String> {
super::oauth::get_oauth_token_async(
&config.host,
&config.client_id,
&config.redirect_url,
&config.scopes,
)
.await
}
}
impl<'a> ApiRequestBuilder<'a> {
@@ -518,10 +462,6 @@ impl<'a> ApiRequestBuilder<'a> {
request.header("Authorization", format!("Bearer {}", token))
}
AuthMethod::ApiKey { header_name, key } => request.header(header_name.as_str(), key),
AuthMethod::OAuth(config) => {
let token = self.client.get_oauth_token(config).await?;
request.header("Authorization", format!("Bearer {}", token))
}
AuthMethod::Custom(provider) => {
let (header_name, header_value) = provider.get_auth_header().await?;
request.header(header_name, header_value)
@@ -680,9 +620,10 @@ mod tests {
) {
let runtime = tokio::runtime::Runtime::new().unwrap();
runtime.block_on(async {
let client = ApiClient::new(
let client = ApiClient::new_with_tls(
"http://localhost:8080".to_string(),
AuthMethod::BearerToken("test-token".to_string()),
None,
)
.unwrap();
+1
View File
@@ -1,3 +1,4 @@
pub mod api_client;
pub mod base;
pub mod canonical;
pub mod conversation;
+2
View File
@@ -41,6 +41,7 @@ rustls-tls = [
"dep:aws-lc-rs",
"goose/rustls-tls",
"goose-mcp/rustls-tls",
"goose-providers/rustls-tls",
]
native-tls = [
"reqwest/native-tls",
@@ -49,6 +50,7 @@ native-tls = [
"dep:openssl",
"goose/native-tls",
"goose-mcp/native-tls",
"goose-providers/native-tls",
]
[dependencies]
+2 -8
View File
@@ -52,12 +52,9 @@ rustls-tls = [
"oauth2/reqwest",
"oauth2/rustls-tls",
"hf-hub/rustls-tls",
"goose-providers/rustls-tls",
]
native-tls = [
"dep:pem",
"dep:pkcs1",
"dep:pkcs8",
"dep:sec1",
"reqwest/native-tls",
"rmcp/reqwest-native-tls",
"smithy-transport-reqwest?/native-tls",
@@ -65,6 +62,7 @@ native-tls = [
"jsonwebtoken/rust_crypto",
"oauth2/reqwest",
"oauth2/native-tls",
"goose-providers/native-tls",
]
system-keyring = ["dep:keyring"]
portable-default = ["rustls-tls", "aws-providers", "telemetry", "otel"]
@@ -196,10 +194,6 @@ pulldown-cmark = { version = "0.13", default-features = false }
encoding_rs = { version = "0.8.35", default-features = false }
pastey = { version = "0.2", default-features = false }
shell-words = { workspace = true }
pem = { version = "3.0.2", default-features = false, features = ["std"], optional = true }
pkcs1 = { version = "0.7.5", default-features = false, features = ["pkcs8", "std"], optional = true }
pkcs8 = { version = "0.11.0", default-features = false, features = ["alloc", "std"], optional = true }
sec1 = { version = "0.7", default-features = false, features = ["der", "pkcs8", "std"], optional = true }
goose-acp-macros = { path = "../goose-acp-macros", default-features = false }
tower-http = { workspace = true, features = ["cors"] }
http-body-util = { version = "0.1.2", default-features = false }
@@ -585,10 +585,10 @@ pub fn register_declarative_provider(
&config,
provider_type,
config.dynamic_models.unwrap_or(false),
move |model| {
move |model, tls_config| {
let mut cfg = captured.clone();
resolve_config(&mut cfg)?;
HuggingFaceProvider::from_custom_config(model, cfg)
HuggingFaceProvider::from_custom_config(model, cfg, tls_config)
},
move || {
let mut cfg = identity_config.clone();
@@ -608,10 +608,10 @@ pub fn register_declarative_provider(
&config,
provider_type,
config.dynamic_models.unwrap_or(false),
move |model| {
move |model, tls_config| {
let mut cfg = captured.clone();
resolve_config(&mut cfg)?;
OpenAiProvider::from_custom_config(model, cfg)
OpenAiProvider::from_custom_config(model, cfg, tls_config)
},
move || {
let mut cfg = identity_config.clone();
@@ -628,10 +628,10 @@ pub fn register_declarative_provider(
&config,
provider_type,
config.dynamic_models.unwrap_or(false),
move |model| {
move |model, tls_config| {
let mut cfg = captured.clone();
resolve_config(&mut cfg)?;
OllamaProvider::from_custom_config(model, cfg)
OllamaProvider::from_custom_config(model, cfg, tls_config)
},
move || {
let mut cfg = identity_config.clone();
@@ -647,10 +647,10 @@ pub fn register_declarative_provider(
&config,
provider_type,
config.dynamic_models.unwrap_or(false),
move |model| {
move |model, tls_config| {
let mut cfg = captured.clone();
resolve_config(&mut cfg)?;
AnthropicProvider::from_custom_config(model, cfg)
AnthropicProvider::from_custom_config(model, cfg, tls_config)
},
move || {
let mut cfg = identity_config.clone();
+1
View File
@@ -10,6 +10,7 @@ pub mod search_path;
pub mod signup_nanogpt;
pub mod signup_openrouter;
pub mod signup_tetrate;
pub mod tls;
pub use crate::agents::ExtensionConfig;
pub use base::{merge_config_values, Config, ConfigError};
@@ -23,8 +23,13 @@ struct PollResponse {
}
fn build_client() -> Result<ApiClient> {
ApiClient::new(NANOGPT_CLI_LOGIN_HOST.to_string(), AuthMethod::NoAuth)?
.with_header("x-client", "goose")
let tls_config = crate::config::tls::provider_tls_config_from_config(Config::global())?;
ApiClient::new_with_tls(
NANOGPT_CLI_LOGIN_HOST.to_string(),
AuthMethod::NoAuth,
tls_config,
)?
.with_header("x-client", "goose")
}
async fn poll_for_token(client: &ApiClient, device_code: &str) -> Result<String> {
+38
View File
@@ -0,0 +1,38 @@
use crate::config::Config;
use crate::providers::api_client::TlsConfig;
use anyhow::{bail, Result};
use std::path::PathBuf;
pub fn provider_tls_config_from_config(config: &Config) -> Result<Option<TlsConfig>> {
let mut tls_config = TlsConfig::new();
let mut has_tls_config = false;
let client_cert_path = config.get_param::<String>("GOOSE_CLIENT_CERT_PATH").ok();
let client_key_path = config.get_param::<String>("GOOSE_CLIENT_KEY_PATH").ok();
match (client_cert_path, client_key_path) {
(Some(cert_path), Some(key_path)) => {
tls_config = tls_config
.with_client_cert_and_key(PathBuf::from(cert_path), PathBuf::from(key_path));
has_tls_config = true;
}
(Some(_), None) => {
bail!(
"Client certificate provided (GOOSE_CLIENT_CERT_PATH) but no private key (GOOSE_CLIENT_KEY_PATH)"
);
}
(None, Some(_)) => {
bail!(
"Client private key provided (GOOSE_CLIENT_KEY_PATH) but no certificate (GOOSE_CLIENT_CERT_PATH)"
);
}
(None, None) => {}
}
if let Ok(ca_cert_path) = config.get_param::<String>("GOOSE_CA_CERT_PATH") {
tls_config = tls_config.with_ca_cert(PathBuf::from(ca_cert_path));
has_tls_config = true;
}
Ok(has_tls_config.then_some(tls_config))
}
+7 -4
View File
@@ -1,3 +1,4 @@
use crate::config::tls::provider_tls_config_from_config;
use crate::config::Config;
#[cfg(feature = "local-inference")]
use crate::dictation::whisper::LOCAL_WHISPER_MODEL_CONFIG_KEY;
@@ -251,10 +252,12 @@ fn build_api_client(provider: DictationProvider) -> Result<(ApiClient, String)>
DictationProvider::Local => anyhow::bail!("Local provider should not use API client"),
};
let mut client = ApiClient::with_timeout(base_url, auth, REQUEST_TIMEOUT).map_err(|e| {
tracing::error!("Failed to create API client: {}", e);
e
})?;
let tls = provider_tls_config_from_config(config)?;
let mut client = ApiClient::with_timeout_and_tls(base_url, auth, REQUEST_TIMEOUT, tls)
.map_err(|e| {
tracing::error!("Failed to create API client: {}", e);
e
})?;
if !query_params.is_empty() {
client = client.with_query(query_params);
}
+3 -1
View File
@@ -43,14 +43,16 @@ impl ProviderDef for AmpAcpProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Self::from_env_with_working_dir(model, extensions, current_working_dir())
Self::from_env_with_working_dir(model, extensions, current_working_dir(), tls_config)
}
fn from_env_with_working_dir(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
working_dir: PathBuf,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Box::pin(async move {
let config = Config::global();
+18 -10
View File
@@ -63,7 +63,10 @@ pub struct AnthropicProvider {
}
impl AnthropicProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let model = crate::model_config::with_configured_fast_model(
model,
ANTHROPIC_PROVIDER_NAME,
@@ -81,8 +84,8 @@ impl AnthropicProvider {
key: api_key,
};
let api_client =
ApiClient::new(host, auth)?.with_header("anthropic-version", ANTHROPIC_API_VERSION)?;
let api_client = ApiClient::new_with_tls(host, auth, tls_config)?
.with_header("anthropic-version", ANTHROPIC_API_VERSION)?;
Ok(Self {
api_client,
@@ -99,6 +102,7 @@ impl AnthropicProvider {
pub fn from_custom_config(
model: ModelConfig,
config: DeclarativeProviderConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let custom_models = if !config.models.is_empty() {
Some(
@@ -132,7 +136,7 @@ impl AnthropicProvider {
let format_options = Self::format_options_for_provider(config.preserves_thinking);
let mut api_client = ApiClient::new(config.base_url, auth)?
let mut api_client = ApiClient::new_with_tls(config.base_url, auth, tls_config)?
.with_header("anthropic-version", ANTHROPIC_API_VERSION)?;
if let Some(headers) = &config.headers {
@@ -267,8 +271,9 @@ impl ProviderDef for AnthropicProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
@@ -379,7 +384,7 @@ mod tests {
header_name: "x-api-key".to_string(),
key: "test-key".to_string(),
};
let api_client = ApiClient::new(server_uri.to_string(), auth)
let api_client = ApiClient::new_with_tls(server_uri.to_string(), auth, None)
.unwrap()
.with_header("anthropic-version", ANTHROPIC_API_VERSION)
.unwrap();
@@ -444,10 +449,13 @@ mod tests {
#[test]
fn from_custom_config_rejects_static_only_without_models() {
let config = base_declarative_config(vec![], Some(false));
let err =
AnthropicProvider::from_custom_config(ModelConfig::new_or_fail("claude-test"), config)
.err()
.expect("expected construction error for dynamic_models: false with empty models");
let err = AnthropicProvider::from_custom_config(
ModelConfig::new_or_fail("claude-test"),
config,
None,
)
.err()
.expect("expected construction error for dynamic_models: false with empty models");
let msg = err.to_string();
assert!(
msg.contains("dynamic_models: false"),
+3 -1
View File
@@ -39,6 +39,7 @@ impl ProviderDef for AvianProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<OpenAiCompatibleProvider>> {
Box::pin(async move {
let config = crate::config::Config::global();
@@ -47,7 +48,8 @@ impl ProviderDef for AvianProvider {
.get_param("AVIAN_HOST")
.unwrap_or_else(|_| AVIAN_API_HOST.to_string());
let api_client = ApiClient::new(host, AuthMethod::BearerToken(api_key))?;
let api_client =
ApiClient::new_with_tls(host, AuthMethod::BearerToken(api_key), tls_config)?;
Ok(OpenAiCompatibleProvider::new(
AVIAN_PROVIDER_NAME.to_string(),
+6 -1
View File
@@ -74,6 +74,7 @@ impl ProviderDef for AzureProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(async move {
let config = crate::config::Config::global();
@@ -105,7 +106,11 @@ impl ProviderDef for AzureProvider {
let auth_provider = AzureAuthProvider { auth };
let host = format!("{}/openai", endpoint.trim_end_matches('/'));
let mut api_client = ApiClient::new(host, AuthMethod::Custom(Box::new(auth_provider)))?;
let mut api_client = ApiClient::new_with_tls(
host,
AuthMethod::Custom(Box::new(auth_provider)),
tls_config,
)?;
if let Some(version) = api_version {
api_client = api_client.with_query(vec![("api-version".to_string(), version)]);
}
+4 -1
View File
@@ -1,3 +1,4 @@
use super::api_client::TlsConfig;
use anyhow::Result;
use futures::future::BoxFuture;
pub use goose_providers::conversation::token_usage::{
@@ -245,6 +246,7 @@ pub trait ProviderDef: Send + Sync {
fn from_env(
model: ModelConfig,
extensions: Vec<ExtensionConfig>,
tls_config: Option<TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>>
where
Self: Sized;
@@ -253,13 +255,14 @@ pub trait ProviderDef: Send + Sync {
model: ModelConfig,
extensions: Vec<ExtensionConfig>,
_working_dir: PathBuf,
tls_config: Option<TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>>
where
Self: Sized,
{
// ACP subprocess providers must override this so session cwd is preserved.
// Non-subprocess providers can rely on the default because cwd is irrelevant.
Self::from_env(model, extensions)
Self::from_env(model, extensions, tls_config)
}
}
+6 -2
View File
@@ -79,7 +79,10 @@ struct ConverseRequestParts {
}
impl BedrockProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
// Attempt to load config and secrets to get AWS_ prefixed keys
@@ -710,8 +713,9 @@ impl ProviderDef for BedrockProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+6 -2
View File
@@ -894,7 +894,10 @@ impl ChatGptCodexProvider {
Ok(())
}
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let auth_provider = Arc::new(ChatGptCodexAuthProvider::new(
ChatGptCodexAuthState::instance(),
));
@@ -976,8 +979,9 @@ impl ProviderDef for ChatGptCodexProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+3 -1
View File
@@ -41,14 +41,16 @@ impl ProviderDef for ClaudeAcpProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Self::from_env_with_working_dir(model, extensions, current_working_dir())
Self::from_env_with_working_dir(model, extensions, current_working_dir(), tls_config)
}
fn from_env_with_working_dir(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
working_dir: PathBuf,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Box::pin(async move {
let config = Config::global();
@@ -604,6 +604,7 @@ impl ProviderDef for ClaudeCodeProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<ExtensionConfig>,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(async move {
let config = crate::config::Config::global();
+1
View File
@@ -637,6 +637,7 @@ impl ProviderDef for CodexProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<ExtensionConfig>,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(async move {
let config = Config::global();
+3 -1
View File
@@ -40,14 +40,16 @@ impl ProviderDef for CodexAcpProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Self::from_env_with_working_dir(model, extensions, current_working_dir())
Self::from_env_with_working_dir(model, extensions, current_working_dir(), tls_config)
}
fn from_env_with_working_dir(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
working_dir: PathBuf,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Box::pin(async move {
let config = Config::global();
+3 -1
View File
@@ -44,14 +44,16 @@ impl ProviderDef for CopilotAcpProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Self::from_env_with_working_dir(model, extensions, current_working_dir())
Self::from_env_with_working_dir(model, extensions, current_working_dir(), tls_config)
}
fn from_env_with_working_dir(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
working_dir: PathBuf,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Box::pin(async move {
let config = Config::global();
+6 -2
View File
@@ -37,7 +37,10 @@ pub struct CursorAgentProvider {
}
impl CursorAgentProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let command: String = config.get_cursor_agent_command().unwrap_or_default().into();
let resolved_command = SearchPaths::builder().with_npm().resolve(&command)?;
@@ -297,8 +300,9 @@ impl ProviderDef for CursorAgentProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+8 -30
View File
@@ -97,7 +97,10 @@ impl DatabricksProvider {
super::oauth::cleanup_oauth_cache()
}
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let mut host: Result<String, ConfigError> = config.get_param("DATABRICKS_HOST");
@@ -131,10 +134,11 @@ impl DatabricksProvider {
token_cache: token_cache.clone(),
}));
let api_client = ApiClient::with_timeout(
let api_client = ApiClient::with_timeout_and_tls(
host.clone(),
auth_method,
Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS),
tls_config.clone(),
)?;
let mut provider = Self {
@@ -189,33 +193,6 @@ impl DatabricksProvider {
)
}
pub fn from_params(host: String, api_key: String, model: ModelConfig) -> Result<Self> {
let token_cache = Arc::new(Mutex::new(Some(api_key.clone())));
let auth = DatabricksAuth::token(api_key);
let auth_method = AuthMethod::Custom(Box::new(DatabricksAuthProvider {
auth: auth.clone(),
token_cache: token_cache.clone(),
}));
let api_client = ApiClient::with_timeout(
host.clone(),
auth_method,
Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS),
)?;
Ok(Self {
api_client,
host,
auth,
model,
image_format: ImageFormat::OpenAi,
retry_config: RetryConfig::default(),
name: DATABRICKS_PROVIDER_NAME.to_string(),
token_cache,
instance_id: Self::resolve_instance_id(),
})
}
fn resolve_instance_id() -> Option<String> {
let enabled = crate::config::Config::global()
.get_param::<bool>("GOOSE_DATABRICKS_CLIENT_REQUEST_ID")
@@ -569,8 +546,9 @@ impl ProviderDef for DatabricksProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+10 -13
View File
@@ -67,7 +67,10 @@ impl DatabricksV2Provider {
super::oauth::cleanup_oauth_cache()
}
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let mut host: Result<String, ConfigError> = config.get_param("DATABRICKS_HOST");
@@ -91,16 +94,7 @@ impl DatabricksV2Provider {
DatabricksAuth::oauth(host.clone())
};
Self::new(host, auth, model, retry_config)
}
pub fn from_params(host: String, api_key: String, model: ModelConfig) -> Result<Self> {
Self::new(
host,
DatabricksAuth::token(api_key),
model,
RetryConfig::default(),
)
Self::new(host, auth, model, retry_config, tls_config)
}
fn new(
@@ -108,6 +102,7 @@ impl DatabricksV2Provider {
auth: DatabricksAuth,
model: ModelConfig,
retry_config: RetryConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let token_cache = Arc::new(Mutex::new(match &auth {
DatabricksAuth::Token(t) => Some(t.clone()),
@@ -119,10 +114,11 @@ impl DatabricksV2Provider {
token_cache: token_cache.clone(),
}));
let api_client = ApiClient::with_timeout(
let api_client = ApiClient::with_timeout_and_tls(
host,
auth_method,
Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS),
tls_config,
)?;
Ok(Self {
@@ -358,8 +354,9 @@ impl ProviderDef for DatabricksV2Provider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+6 -2
View File
@@ -165,7 +165,10 @@ impl GcpVertexAIProvider {
///
/// # Arguments
/// * `model` - Configuration for the model to be used
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let project_id = config.get_param("GCP_PROJECT_ID")?;
let location = Self::determine_location(config)?;
@@ -580,8 +583,9 @@ impl ProviderDef for GcpVertexAIProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+6 -2
View File
@@ -47,7 +47,10 @@ pub struct GeminiCliProvider {
}
impl GeminiCliProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = Config::global();
let command: String = config.get_gemini_cli_command().unwrap_or_default().into();
let resolved_command = SearchPaths::builder().with_npm().resolve(&command)?;
@@ -174,8 +177,9 @@ impl ProviderDef for GeminiCliProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+6 -2
View File
@@ -837,7 +837,10 @@ pub struct GeminiOAuthProvider {
}
impl GeminiOAuthProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let model = crate::model_config::with_configured_fast_model(
model,
GEMINI_OAUTH_PROVIDER_NAME,
@@ -956,8 +959,9 @@ impl ProviderDef for GeminiOAuthProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+11 -3
View File
@@ -202,6 +202,8 @@ pub struct GithubCopilotProvider {
client_id: String,
#[serde(skip)]
name: String,
#[serde(skip)]
tls_config: Option<crate::providers::api_client::TlsConfig>,
}
impl GithubCopilotProvider {
@@ -229,7 +231,10 @@ impl GithubCopilotProvider {
})
}
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = Config::global();
let host = normalize_host(
&config
@@ -254,6 +259,7 @@ impl GithubCopilotProvider {
urls,
client_id,
name: GITHUB_COPILOT_PROVIDER_NAME.to_string(),
tls_config,
})
}
@@ -273,7 +279,8 @@ impl GithubCopilotProvider {
}
let initiator = if is_user_initiated { "user" } else { "agent" };
headers.insert("X-Initiator", initiator.parse().unwrap());
let api_client = ApiClient::new(endpoint.clone(), auth)?.with_headers(headers)?;
let api_client = ApiClient::new_with_tls(endpoint.clone(), auth, self.tls_config.clone())?
.with_headers(headers)?;
api_client
.response_post(session_id, path, payload)
@@ -546,8 +553,9 @@ impl ProviderDef for GithubCopilotProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+8 -4
View File
@@ -66,7 +66,10 @@ pub struct GoogleProvider {
}
impl GoogleProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let model = crate::model_config::with_configured_fast_model(
model,
GOOGLE_PROVIDER_NAME,
@@ -84,8 +87,8 @@ impl GoogleProvider {
key: api_key,
};
let api_client =
ApiClient::new(host, auth)?.with_header("Content-Type", "application/json")?;
let api_client = ApiClient::new_with_tls(host, auth, tls_config)?
.with_header("Content-Type", "application/json")?;
Ok(Self {
api_client,
@@ -136,8 +139,9 @@ impl ProviderDef for GoogleProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+12 -5
View File
@@ -74,6 +74,7 @@ impl HuggingFaceProvider {
pub fn from_custom_config(
model: ModelConfig,
config: DeclarativeProviderConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let custom_models = static_model_names(&config);
if config.dynamic_models == Some(false) && custom_models.is_none() {
@@ -91,10 +92,11 @@ impl HuggingFaceProvider {
let timeout_secs = config
.timeout_seconds
.unwrap_or(DEFAULT_PROVIDER_TIMEOUT_SECS);
let mut api_client = ApiClient::with_timeout(
let mut api_client = ApiClient::with_timeout_and_tls(
host,
auth_method,
std::time::Duration::from_secs(timeout_secs),
tls_config,
)?
.with_query(query_params);
@@ -206,6 +208,7 @@ impl ProviderDef for HuggingFaceProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(async move {
let config = Config::global();
@@ -214,7 +217,7 @@ impl ProviderDef for HuggingFaceProvider {
let host: String = config
.get_param("HF_HOST")
.unwrap_or_else(|_| HUGGINGFACE_API_HOST.to_string());
let api_client = ApiClient::new(host, auth_method)?;
let api_client = ApiClient::new_with_tls(host, auth_method, tls_config)?;
Ok(Self {
inner: OpenAiCompatibleProvider::new(
@@ -442,9 +445,12 @@ mod tests {
ModelInfo::new("static-b".to_string(), 128000),
];
let provider =
HuggingFaceProvider::from_custom_config(ModelConfig::new("static-a").unwrap(), config)
.unwrap();
let provider = HuggingFaceProvider::from_custom_config(
ModelConfig::new("static-a").unwrap(),
config,
None,
)
.unwrap();
assert_eq!(
provider.fetch_supported_models().await.unwrap(),
@@ -461,6 +467,7 @@ mod tests {
let error = match HuggingFaceProvider::from_custom_config(
ModelConfig::new("model").unwrap(),
config,
None,
) {
Ok(_) => panic!("expected dynamic_models: false without static models to fail"),
Err(error) => error,
+4 -1
View File
@@ -54,7 +54,10 @@ use tokio::sync::OnceCell;
static REGISTRY: OnceCell<RwLock<ProviderRegistry>> = OnceCell::const_new();
async fn init_registry() -> RwLock<ProviderRegistry> {
let mut registry = ProviderRegistry::new().with_providers(|registry| {
let tls_config =
crate::config::tls::provider_tls_config_from_config(crate::config::Config::global())
.expect("failed to load provider TLS config");
let mut registry = ProviderRegistry::new(tls_config).with_providers(|registry| {
use super::inventory::registrations;
registry.register_with_inventory::<AmpAcpProvider>(
+6 -2
View File
@@ -162,7 +162,10 @@ impl KimiCodeProvider {
TokenCache::new().clear().await
}
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let model = crate::model_config::with_configured_fast_model(
model,
KIMI_CODE_PROVIDER_NAME,
@@ -372,8 +375,9 @@ impl ProviderDef for KimiCodeProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+12 -4
View File
@@ -37,7 +37,10 @@ pub struct LiteLLMProvider {
}
impl LiteLLMProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let secrets = config
.get_secrets("LITELLM_API_KEY", &["LITELLM_CUSTOM_HEADERS"])
@@ -63,8 +66,12 @@ impl LiteLLMProvider {
AuthMethod::BearerToken(api_key)
};
let mut api_client =
ApiClient::with_timeout(host, auth, std::time::Duration::from_secs(timeout_secs))?;
let mut api_client = ApiClient::with_timeout_and_tls(
host,
auth,
std::time::Duration::from_secs(timeout_secs),
tls_config,
)?;
if let Some(headers) = custom_headers {
let mut header_map = reqwest::header::HeaderMap::new();
@@ -184,8 +191,9 @@ impl ProviderDef for LiteLLMProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
@@ -532,6 +532,7 @@ impl ProviderDef for LocalInferenceProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<ExtensionConfig>,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>>
where
Self: Sized,
+3 -1
View File
@@ -1,7 +1,9 @@
mod acp_tooling;
pub mod amp_acp;
pub mod anthropic;
pub mod api_client;
pub mod api_client {
pub use goose_providers::api_client::*;
}
pub mod avian;
pub mod azure;
pub mod azureauth;
+20 -8
View File
@@ -30,16 +30,24 @@ pub struct NanoGptProvider {
}
impl NanoGptProvider {
fn build_client(host: &str, api_key: &str) -> Result<ApiClient> {
ApiClient::new(
fn build_client(
host: &str,
api_key: &str,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<ApiClient> {
ApiClient::new_with_tls(
host.to_string(),
AuthMethod::BearerToken(api_key.to_string()),
tls_config,
)?
.with_header("x-client", "goose")
}
async fn check_subscription(api_key: &str) -> bool {
let client = match Self::build_client(NANOGPT_SUBSCRIPTION_HOST, api_key) {
async fn check_subscription(
api_key: &str,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> bool {
let client = match Self::build_client(NANOGPT_SUBSCRIPTION_HOST, api_key, tls_config) {
Ok(c) => c,
Err(_) => return false,
};
@@ -55,11 +63,14 @@ impl NanoGptProvider {
}
}
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let api_key: String = config.get_secret(NANOGPT_API_KEY)?;
let is_subscription = Self::check_subscription(&api_key).await;
let is_subscription = Self::check_subscription(&api_key, tls_config.clone()).await;
let host = if is_subscription {
tracing::debug!("NanoGPT subscription active, using subscription endpoint");
NANOGPT_SUBSCRIPTION_HOST.to_string()
@@ -68,7 +79,7 @@ impl NanoGptProvider {
NANOGPT_API_HOST.to_string()
};
let api_client = Self::build_client(&host, &api_key)?;
let api_client = Self::build_client(&host, &api_key, tls_config)?;
Ok(Self {
api_client,
@@ -96,8 +107,9 @@ impl ProviderDef for NanoGptProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+19 -6
View File
@@ -130,7 +130,10 @@ pub(crate) fn ollama_host_configured(config: &crate::config::Config) -> bool {
}
impl OllamaProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let host: String = config
.get_param("OLLAMA_HOST")
@@ -158,8 +161,12 @@ impl OllamaProvider {
.map_err(|_| anyhow::anyhow!("Failed to set default port"))?;
}
let api_client =
ApiClient::with_timeout(base_url.to_string(), AuthMethod::NoAuth, timeout)?;
let api_client = ApiClient::with_timeout_and_tls(
base_url.to_string(),
AuthMethod::NoAuth,
timeout,
tls_config,
)?;
Ok(Self {
api_client,
@@ -173,6 +180,7 @@ impl OllamaProvider {
pub fn from_custom_config(
model: ModelConfig,
config: DeclarativeProviderConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let timeout = Duration::from_secs(config.timeout_seconds.unwrap_or(OLLAMA_TIMEOUT));
@@ -196,8 +204,12 @@ impl OllamaProvider {
.map_err(|_| anyhow::anyhow!("Failed to set default port"))?;
}
let mut api_client =
ApiClient::with_timeout(base_url.to_string(), AuthMethod::NoAuth, timeout)?;
let mut api_client = ApiClient::with_timeout_and_tls(
base_url.to_string(),
AuthMethod::NoAuth,
timeout,
tls_config,
)?;
if let Some(headers) = &config.headers {
let mut header_map = reqwest::header::HeaderMap::new();
@@ -261,8 +273,9 @@ impl ProviderDef for OllamaProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+31 -13
View File
@@ -155,7 +155,10 @@ pub struct OpenAiProvider {
}
impl OpenAiProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
// Resolve host and base_path.
@@ -269,10 +272,11 @@ impl OpenAiProvider {
Some(key) if !key.is_empty() => AuthMethod::BearerToken(key),
_ => AuthMethod::NoAuth,
};
let mut api_client = ApiClient::with_timeout(
let mut api_client = ApiClient::with_timeout_and_tls(
parsed.host,
auth,
std::time::Duration::from_secs(timeout_secs),
tls_config,
)?;
if !parsed.query_params.is_empty() {
@@ -404,6 +408,7 @@ impl OpenAiProvider {
pub fn from_custom_config(
model: ModelConfig,
config: DeclarativeProviderConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let custom_models = if !config.models.is_empty() {
Some(
@@ -456,8 +461,12 @@ impl OpenAiProvider {
Some(key) if !key.is_empty() => AuthMethod::BearerToken(key),
_ => AuthMethod::NoAuth,
};
let mut api_client =
ApiClient::with_timeout(host, auth, std::time::Duration::from_secs(timeout_secs))?;
let mut api_client = ApiClient::with_timeout_and_tls(
host,
auth,
std::time::Duration::from_secs(timeout_secs),
tls_config,
)?;
// Add custom headers if present
if let Some(headers) = &config.headers {
@@ -770,8 +779,9 @@ impl ProviderDef for OpenAiProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
@@ -944,7 +954,12 @@ mod tests {
fn make_provider(name: &str) -> OpenAiProvider {
OpenAiProvider {
api_client: ApiClient::new("http://localhost".to_string(), AuthMethod::NoAuth).unwrap(),
api_client: ApiClient::new_with_tls(
"http://localhost".to_string(),
AuthMethod::NoAuth,
None,
)
.unwrap(),
base_path: "v1/chat/completions".to_string(),
organization: None,
project: None,
@@ -1276,7 +1291,8 @@ mod tests {
dynamic_models: Option<bool>,
) -> OpenAiProvider {
OpenAiProvider {
api_client: ApiClient::new(server_uri.to_string(), AuthMethod::NoAuth).unwrap(),
api_client: ApiClient::new_with_tls(server_uri.to_string(), AuthMethod::NoAuth, None)
.unwrap(),
base_path: "v1/chat/completions".to_string(),
organization: None,
project: None,
@@ -1358,7 +1374,8 @@ mod tests {
config.base_url = "localhost:1234".to_string();
let provider =
OpenAiProvider::from_custom_config(ModelConfig::new_or_fail("m1"), config).unwrap();
OpenAiProvider::from_custom_config(ModelConfig::new_or_fail("m1"), config, None)
.unwrap();
assert_eq!(provider.api_client.host(), "http://localhost:1234");
assert_eq!(provider.base_path, "v1/chat/completions");
@@ -1386,11 +1403,12 @@ mod tests {
#[test]
fn from_custom_config_rejects_static_only_without_models() {
let config = base_declarative_config(vec![], Some(false));
let err =
OpenAiProvider::from_custom_config(ModelConfig::new_or_fail("test-model"), config)
.expect_err(
"expected construction error for dynamic_models: false with empty models",
);
let err = OpenAiProvider::from_custom_config(
ModelConfig::new_or_fail("test-model"),
config,
None,
)
.expect_err("expected construction error for dynamic_models: false with empty models");
let msg = err.to_string();
assert!(
msg.contains("dynamic_models: false"),
@@ -306,9 +306,10 @@ mod tests {
fn build_request_respects_non_streaming_mode() {
let provider = OpenAiCompatibleProvider::new(
"test".to_string(),
ApiClient::new(
ApiClient::new_with_tls(
"http://localhost".to_string(),
super::super::api_client::AuthMethod::NoAuth,
None,
)
.unwrap(),
ModelConfig::new_or_fail("test-model"),
+7 -3
View File
@@ -47,7 +47,10 @@ pub struct OpenRouterProvider {
}
impl OpenRouterProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let model = crate::model_config::with_configured_fast_model(
model,
OPENROUTER_PROVIDER_NAME,
@@ -61,7 +64,7 @@ impl OpenRouterProvider {
.unwrap_or_else(|_| "https://openrouter.ai".to_string());
let auth = AuthMethod::BearerToken(api_key);
let api_client = ApiClient::new(host, auth)?
let api_client = ApiClient::new_with_tls(host, auth, tls_config)?
.with_header("HTTP-Referer", "https://goose-docs.ai")?
.with_header("X-Title", "goose")?;
@@ -183,8 +186,9 @@ impl ProviderDef for OpenRouterProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+3 -1
View File
@@ -42,14 +42,16 @@ impl ProviderDef for PiAcpProvider {
fn from_env(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Self::from_env_with_working_dir(model, extensions, current_working_dir())
Self::from_env_with_working_dir(model, extensions, current_working_dir(), tls_config)
}
fn from_env_with_working_dir(
model: ModelConfig,
extensions: Vec<crate::config::ExtensionConfig>,
working_dir: PathBuf,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<AcpProvider>> {
Box::pin(async move {
let config = Config::global();
+33 -14
View File
@@ -1,3 +1,4 @@
use super::api_client::TlsConfig;
use super::base::{ConfigKey, ModelInfo, Provider, ProviderDef, ProviderMetadata, ProviderType};
use super::inventory::{InventoryIdentityInput, InventoryRegistration, InventoryResolvers};
use crate::config::{DeclarativeProviderConfig, ExtensionConfig};
@@ -13,6 +14,7 @@ pub type ProviderConstructor = Arc<
ModelConfig,
Vec<ExtensionConfig>,
Option<PathBuf>,
Option<TlsConfig>,
) -> BoxFuture<'static, Result<Arc<dyn Provider>>>
+ Send
+ Sync,
@@ -29,6 +31,7 @@ pub struct ProviderEntry {
pub(crate) cleanup: Option<ProviderCleanup>,
provider_type: ProviderType,
supports_inventory_refresh: bool,
tls_config: Option<TlsConfig>,
}
impl ProviderEntry {
@@ -78,7 +81,7 @@ impl ProviderEntry {
&self.metadata.default_model,
)?;
let model_config = self.normalize_model_config(model_config)?;
(self.constructor)(model_config, extensions, None).await
(self.constructor)(model_config, extensions, None, self.tls_config.clone()).await
}
pub async fn create(
@@ -87,7 +90,7 @@ impl ProviderEntry {
extensions: Vec<ExtensionConfig>,
) -> Result<Arc<dyn Provider>> {
let model = self.normalize_model_config(model)?;
(self.constructor)(model, extensions, None).await
(self.constructor)(model, extensions, None, self.tls_config.clone()).await
}
pub async fn create_with_working_dir(
@@ -97,19 +100,27 @@ impl ProviderEntry {
working_dir: PathBuf,
) -> Result<Arc<dyn Provider>> {
let model = self.normalize_model_config(model)?;
(self.constructor)(model, extensions, Some(working_dir)).await
(self.constructor)(
model,
extensions,
Some(working_dir),
self.tls_config.clone(),
)
.await
}
}
#[derive(Default)]
pub struct ProviderRegistry {
pub(crate) entries: HashMap<String, ProviderEntry>,
tls_config: Option<TlsConfig>,
}
impl ProviderRegistry {
pub fn new() -> Self {
pub fn new(tls_config: Option<TlsConfig>) -> Self {
Self {
entries: HashMap::new(),
tls_config,
}
}
@@ -136,13 +147,19 @@ impl ProviderRegistry {
name,
ProviderEntry {
metadata,
constructor: Arc::new(|model, extensions, working_dir| {
constructor: Arc::new(|model, extensions, working_dir, tls_config| {
Box::pin(async move {
let provider = match working_dir {
Some(working_dir) => {
F::from_env_with_working_dir(model, extensions, working_dir).await?
F::from_env_with_working_dir(
model,
extensions,
working_dir,
tls_config,
)
.await?
}
None => F::from_env(model, extensions).await?,
None => F::from_env(model, extensions, tls_config).await?,
};
Ok(Arc::new(provider) as Arc<dyn Provider>)
})
@@ -156,6 +173,7 @@ impl ProviderRegistry {
ProviderType::Builtin
},
supports_inventory_refresh: inventory.supports_refresh,
tls_config: self.tls_config.clone(),
},
);
}
@@ -169,7 +187,7 @@ impl ProviderRegistry {
inventory_identity: G,
) where
P: ProviderDef + 'static,
F: Fn(ModelConfig) -> Result<P::Provider> + Send + Sync + 'static,
F: Fn(ModelConfig, Option<TlsConfig>) -> Result<P::Provider> + Send + Sync + 'static,
G: Fn() -> Result<InventoryIdentityInput> + Send + Sync + 'static,
{
self.register_with_name_impl::<P, F, G>(
@@ -192,7 +210,7 @@ impl ProviderRegistry {
inventory_configured: H,
) where
P: ProviderDef + 'static,
F: Fn(ModelConfig) -> Result<P::Provider> + Send + Sync + 'static,
F: Fn(ModelConfig, Option<TlsConfig>) -> Result<P::Provider> + Send + Sync + 'static,
G: Fn() -> Result<InventoryIdentityInput> + Send + Sync + 'static,
H: Fn() -> bool + Send + Sync + 'static,
{
@@ -216,7 +234,7 @@ impl ProviderRegistry {
inventory_configured: Option<super::inventory::InventoryConfiguredResolver>,
) where
P: ProviderDef + 'static,
F: Fn(ModelConfig) -> Result<P::Provider> + Send + Sync + 'static,
F: Fn(ModelConfig, Option<TlsConfig>) -> Result<P::Provider> + Send + Sync + 'static,
G: Fn() -> Result<InventoryIdentityInput> + Send + Sync + 'static,
{
let base_metadata = P::metadata();
@@ -311,8 +329,8 @@ impl ProviderRegistry {
config.name.clone(),
ProviderEntry {
metadata: custom_metadata,
constructor: Arc::new(move |model, _extensions, _working_dir| {
let result = constructor(model);
constructor: Arc::new(move |model, _extensions, _working_dir, tls_config| {
let result = constructor(model, tls_config);
Box::pin(async move {
let provider = result?;
Ok(Arc::new(provider) as Arc<dyn Provider>)
@@ -323,6 +341,7 @@ impl ProviderRegistry {
cleanup: None,
provider_type,
supports_inventory_refresh,
tls_config: self.tls_config.clone(),
},
);
}
@@ -400,12 +419,12 @@ mod tests {
#[test]
fn register_with_name_can_override_inventory_configured() {
let mut registry = ProviderRegistry::new();
let mut registry = ProviderRegistry::new(None);
registry.register_with_name_and_inventory_configured::<OpenAiProvider, _, _, _>(
&test_config(),
ProviderType::Declarative,
false,
|_| unreachable!("constructor is not used by this test"),
|_, _| unreachable!("constructor is not used by this test"),
|| Ok(InventoryIdentityInput::new("custom_hf", "huggingface")),
|| false,
);
+6 -2
View File
@@ -40,7 +40,10 @@ pub struct SageMakerTgiProvider {
}
impl SageMakerTgiProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
// Get SageMaker endpoint name (just the name, not full URL)
@@ -297,8 +300,9 @@ impl ProviderDef for SageMakerTgiProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+8 -3
View File
@@ -59,7 +59,10 @@ pub struct SnowflakeProvider {
}
impl SnowflakeProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let mut host: Result<String, ConfigError> = config.get_param("SNOWFLAKE_HOST");
if host.is_err() {
@@ -103,7 +106,8 @@ impl SnowflakeProvider {
};
let auth = AuthMethod::BearerToken(token?);
let api_client = ApiClient::new(base_url, auth)?.with_header("User-Agent", "goose")?;
let api_client = ApiClient::new_with_tls(base_url, auth, tls_config)?
.with_header("User-Agent", "goose")?;
Ok(Self {
api_client,
@@ -320,8 +324,9 @@ impl ProviderDef for SnowflakeProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
@@ -156,6 +156,7 @@ impl ProviderDef for TestProvider {
fn from_env(
_model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
_tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(async { Err(anyhow!("TestProvider must be constructed explicitly")) })
}
+7 -3
View File
@@ -47,7 +47,10 @@ pub struct TetrateProvider {
}
impl TetrateProvider {
pub async fn from_env(model: ModelConfig) -> Result<Self> {
pub async fn from_env(
model: ModelConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> Result<Self> {
let config = crate::config::Config::global();
let api_key: String = config.get_secret("TETRATE_API_KEY")?;
let host: String = config
@@ -55,7 +58,7 @@ impl TetrateProvider {
.unwrap_or_else(|_| "https://api.router.tetrate.ai".to_string());
let auth = AuthMethod::BearerToken(api_key);
let api_client = ApiClient::new(host, auth)?
let api_client = ApiClient::new_with_tls(host, auth, tls_config)?
.with_header("HTTP-Referer", "https://goose-docs.ai")?
.with_header("X-Title", "goose")?;
@@ -116,8 +119,9 @@ impl ProviderDef for TetrateProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(model))
Box::pin(Self::from_env(model, tls_config))
}
}
+3 -1
View File
@@ -54,6 +54,7 @@ impl ProviderDef for XaiProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<OpenAiCompatibleProvider>> {
Box::pin(async move {
let config = crate::config::Config::global();
@@ -62,7 +63,8 @@ impl ProviderDef for XaiProvider {
.get_param("XAI_HOST")
.unwrap_or_else(|_| XAI_API_HOST.to_string());
let api_client = ApiClient::new(host, AuthMethod::BearerToken(api_key))?;
let api_client =
ApiClient::new_with_tls(host, AuthMethod::BearerToken(api_key), tls_config)?;
Ok(OpenAiCompatibleProvider::new(
XAI_PROVIDER_NAME.to_string(),
+3 -1
View File
@@ -781,6 +781,7 @@ impl ProviderDef for XaiOAuthProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(async move {
let config = crate::config::Config::global();
@@ -790,9 +791,10 @@ impl ProviderDef for XaiOAuthProvider {
let auth_provider = Arc::new(XaiOAuthAuthProvider::new(XaiAuthState::instance()));
let auth_for_client = Arc::clone(&auth_provider);
let api_client = ApiClient::new(
let api_client = ApiClient::new_with_tls(
host,
AuthMethod::Custom(Box::new(SharedAuthProvider(auth_for_client))),
tls_config,
)?;
let inner = OpenAiCompatibleProvider::new(
+2 -1
View File
@@ -199,9 +199,10 @@ pub async fn spawn_acp_server_in_process(
move |_provider_name, model_config, _extensions, _working_dir| {
let base_url = base_url.clone();
Box::pin(async move {
let api_client = ApiClient::new(
let api_client = ApiClient::new_with_tls(
base_url,
ApiAuthMethod::BearerToken("test-key".to_string()),
None,
)
.unwrap();
let provider: Arc<dyn Provider> =
+4
View File
@@ -381,6 +381,7 @@ mod tests {
fn from_env(
_model: ModelConfig,
_extensions: Vec<goose::config::ExtensionConfig>,
_tls_config: Option<goose::providers::api_client::TlsConfig>,
) -> futures::future::BoxFuture<'static, anyhow::Result<Self>> {
Box::pin(async { Ok(Self::new()) })
}
@@ -551,6 +552,7 @@ mod tests {
fn from_env(
_model: ModelConfig,
_extensions: Vec<goose::config::ExtensionConfig>,
_tls_config: Option<goose::providers::api_client::TlsConfig>,
) -> futures::future::BoxFuture<'static, anyhow::Result<Self>> {
Box::pin(async { Ok(Self::new()) })
}
@@ -903,6 +905,7 @@ mod tests {
fn from_env(
_model: ModelConfig,
_extensions: Vec<goose::config::ExtensionConfig>,
_tls_config: Option<goose::providers::api_client::TlsConfig>,
) -> futures::future::BoxFuture<'static, anyhow::Result<Self>> {
unimplemented!()
}
@@ -1173,6 +1176,7 @@ mod tests {
fn from_env(
_model: ModelConfig,
_extensions: Vec<goose::config::ExtensionConfig>,
_tls_config: Option<goose::providers::api_client::TlsConfig>,
) -> futures::future::BoxFuture<'static, anyhow::Result<Self>> {
Box::pin(async { Ok(Self::new()) })
}
+1
View File
@@ -199,6 +199,7 @@ impl ProviderDef for MockCompactionProvider {
fn from_env(
_model: ModelConfig,
_extensions: Vec<goose::config::ExtensionConfig>,
_tls_config: Option<goose::providers::api_client::TlsConfig>,
) -> futures::future::BoxFuture<'static, anyhow::Result<Self>> {
Box::pin(async { Ok(Self::new()) })
}
@@ -61,6 +61,7 @@ impl ProviderDef for MockProvider {
fn from_env(
model: ModelConfig,
_extensions: Vec<goose::config::ExtensionConfig>,
_tls_config: Option<goose::providers::api_client::TlsConfig>,
) -> futures::future::BoxFuture<'static, anyhow::Result<Self>> {
Box::pin(async move { Ok(Self::new(model)) })
}
@@ -36,9 +36,10 @@ impl HeaderCapture {
}
fn create_test_provider(mock_server_url: &str) -> Box<dyn Provider> {
let api_client = ApiClient::new(
let api_client = ApiClient::new_with_tls(
mock_server_url.to_string(),
AuthMethod::BearerToken("test-key".to_string()),
None,
)
.unwrap();
let model = ModelConfig::new_or_fail("gpt-5-nano");
+2 -2
View File
@@ -17,7 +17,7 @@ mod tetrate_streaming_tests {
// Create a test provider with the default model
let model_config =
ModelConfig::new("claude-3-5-sonnet-latest")?.with_canonical_limits("tetrate");
TetrateProvider::from_env(model_config).await
TetrateProvider::from_env(model_config, None).await
}
#[tokio::test]
@@ -248,7 +248,7 @@ mod tetrate_streaming_tests {
let model_config =
ModelConfig::new("claude-3-5-sonnet-latest")?.with_canonical_limits("tetrate");
let provider = TetrateProvider::from_env(model_config).await?;
let provider = TetrateProvider::from_env(model_config, None).await?;
let messages = vec![Message::user().with_text("Hello")];
let model_config = provider.get_model_config();