move databricks providers into goose-providers crate (#10162)
Some checks failed
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled

This commit is contained in:
Jack Amadeo 2026-07-02 09:18:57 -07:00 committed by GitHub
parent 3254eed181
commit c82c431c70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 385 additions and 210 deletions

1
Cargo.lock generated
View file

@ -5200,6 +5200,7 @@ dependencies = [
"tokio-util",
"tracing",
"url",
"urlencoding",
"utoipa 4.2.3",
"wiremock",
]

View file

@ -1,4 +1,5 @@
pub mod anthropic;
pub mod databricks;
pub mod ollama;
pub mod openai;
pub mod openai_responses;

View file

@ -1,16 +1,16 @@
use crate::conversation::message::{Message, MessageContent};
use crate::providers::formats::anthropic::{
use crate::formats::anthropic::{
adaptive_output_effort, model_supports_temperature, thinking_budget_tokens,
thinking_type_for_provider, ThinkingType,
};
use goose_providers::model::ModelConfig;
use crate::model::ModelConfig;
use anyhow::{anyhow, Error};
use goose_providers::formats::openai::{
use crate::formats::openai::{
extract_reasoning_effort, is_openai_responses_model, is_valid_function_name,
openai_reasoning_effort_for_thinking, sanitize_function_name,
};
use goose_providers::images::{convert_image, detect_image_path, load_image_file, ImageFormat};
use crate::images::{convert_image, detect_image_path, load_image_file, ImageFormat};
use anyhow::{anyhow, Error};
use rmcp::model::{
object, AnnotateAble, CallToolRequestParams, Content, ErrorCode, ErrorData, RawContent,
ResourceContents, Role, Tool,
@ -19,7 +19,7 @@ use serde::Serialize;
use serde_json::{json, Value};
use std::borrow::Cow;
pub(crate) const DATABRICKS_PROVIDER_NAME: &str = "databricks";
pub const DATABRICKS_PROVIDER_NAME: &str = "databricks";
#[derive(Serialize)]
struct DatabricksMessage {
@ -424,7 +424,7 @@ pub fn response_to_message(response: &Value) -> anyhow::Result<Message> {
};
content.push(MessageContent::tool_request(id, Err(error)));
} else {
match goose_providers::json::parse_tool_arguments(&arguments_str) {
match crate::json::parse_tool_arguments(&arguments_str) {
Some(params) if params.is_object() => {
content.push(MessageContent::tool_request(
id,
@ -448,7 +448,7 @@ pub fn response_to_message(response: &Value) -> anyhow::Result<Message> {
}
None => {
let message_text =
goose_providers::json::truncation_error_message(&arguments_str)
crate::json::truncation_error_message(&arguments_str)
.unwrap_or_else(|| {
format!(
"Could not interpret tool use parameters for id {id}"

View file

@ -48,6 +48,7 @@ tokio = { workspace = true }
tokio-stream = { workspace = true, features = ["io-util"] }
tokio-util = { workspace = true, features = ["compat"] }
url = { workspace = true }
urlencoding = { 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 }
# v0.10 matches the der/const-oid series used by sec1 v0.7 and pkcs1 v0.7; upgrading to v0.11 causes type mismatches across those crates.

View file

@ -1,39 +1,38 @@
use anyhow::Result;
use async_trait::async_trait;
use futures::future::BoxFuture;
use goose_providers::formats::openai::{
use crate::formats::openai::{
extract_reasoning_effort, is_openai_responses_model, openai_reasoning_effort_for_thinking,
};
use goose_providers::images::ImageFormat;
use crate::images::ImageFormat;
use anyhow::Result;
use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashSet;
use std::sync::LazyLock;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use super::api_client::{ApiClient, AuthMethod};
use super::base::{
ConfigKey, MessageStream, ModelInfo, Provider, ProviderDef, ProviderMetadata,
DEFAULT_PROVIDER_TIMEOUT_SECS,
use crate::api_client::{ApiClient, AuthMethod, TlsConfig};
use crate::base::{ConfigKey, MessageStream, ModelInfo, Provider, ProviderMetadata};
const DEFAULT_PROVIDER_TIMEOUT_SECS: u64 = 600;
use crate::conversation::message::Message;
use crate::databricks_auth::{
DatabricksAuth, DatabricksAuthProvider, DatabricksOauthTokenProvider, DatabricksRefreshHook,
DatabricksSessionIdProvider, DatabricksTokenResolver,
};
use super::databricks_auth::{DatabricksAuth, DatabricksAuthProvider};
use super::formats::databricks::{create_request_for_provider, DATABRICKS_PROVIDER_NAME};
use super::openai_compatible::{
use crate::errors::ProviderError;
use crate::formats::databricks::create_request_for_provider;
pub use crate::formats::databricks::DATABRICKS_PROVIDER_NAME;
use crate::formats::openai_responses::create_responses_request;
use crate::model::ModelConfig;
use crate::openai_compatible::{
handle_status, map_http_error_to_provider_error, sanitize_url, stream_openai_compat,
stream_responses_compat,
};
use super::retry::ProviderRetry;
use crate::config::ConfigError;
use crate::conversation::message::Message;
use crate::instance_id::get_instance_id;
use crate::providers::retry::{
use crate::request_log::{start_log, LoggerHandleExt};
use crate::retry::ProviderRetry;
use crate::retry::{
RetryConfig, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_INITIAL_RETRY_INTERVAL_MS,
DEFAULT_MAX_RETRIES, DEFAULT_MAX_RETRY_INTERVAL_MS,
};
use goose_providers::errors::ProviderError;
use goose_providers::formats::openai_responses::create_responses_request;
use goose_providers::model::ModelConfig;
use goose_providers::request_log::{start_log, LoggerHandleExt};
use rmcp::model::Tool;
use serde_json::json;
@ -73,7 +72,7 @@ pub const DATABRICKS_KNOWN_MODELS: &[&str] = &[
pub const DATABRICKS_DOC_URL: &str =
"https://docs.databricks.com/en/generative-ai/external-models/index.html";
#[derive(Debug, serde::Serialize)]
#[derive(serde::Serialize)]
pub struct DatabricksProvider {
#[serde(skip)]
api_client: ApiClient,
@ -89,39 +88,30 @@ pub struct DatabricksProvider {
token_cache: Arc<Mutex<Option<String>>>,
#[serde(skip)]
instance_id: Option<String>,
#[serde(skip)]
refresh_hook: Option<DatabricksRefreshHook>,
#[serde(skip)]
session_id_provider: Option<DatabricksSessionIdProvider>,
}
impl DatabricksProvider {
pub async fn cleanup() -> Result<()> {
super::oauth::cleanup_oauth_cache()
Ok(())
}
pub async fn from_env(
tls_config: Option<crate::providers::api_client::TlsConfig>,
#[allow(clippy::too_many_arguments)]
pub fn new(
host: String,
auth: DatabricksAuth,
retry_config: RetryConfig,
tls_config: Option<TlsConfig>,
oauth_token_provider: Option<DatabricksOauthTokenProvider>,
token_resolver: Option<DatabricksTokenResolver>,
request_builder: Option<crate::api_client::RequestBuilderDecorator>,
instance_id: Option<String>,
refresh_hook: Option<DatabricksRefreshHook>,
session_id_provider: Option<DatabricksSessionIdProvider>,
) -> Result<Self> {
let config = crate::config::Config::global();
let mut host: Result<String, ConfigError> = config.get_param("DATABRICKS_HOST");
if host.is_err() {
host = config.get_secret("DATABRICKS_HOST")
}
if host.is_err() {
return Err(ConfigError::NotFound(
"Did not find DATABRICKS_HOST in either config file or keyring".to_string(),
)
.into());
}
let host = host?;
let retry_config = Self::load_retry_config(config);
let auth = if let Ok(api_key) = config.get_secret("DATABRICKS_TOKEN") {
DatabricksAuth::token(api_key)
} else {
DatabricksAuth::oauth(host.clone())
};
let token_cache = Arc::new(Mutex::new(match &auth {
DatabricksAuth::Token(t) => Some(t.clone()),
_ => None,
@ -130,15 +120,19 @@ impl DatabricksProvider {
let auth_method = AuthMethod::Custom(Box::new(DatabricksAuthProvider {
auth: auth.clone(),
token_cache: token_cache.clone(),
oauth_token_provider,
token_resolver,
}));
let api_client = ApiClient::with_timeout_and_tls(
let mut api_client = ApiClient::with_timeout_and_tls(
host.clone(),
auth_method,
Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS),
tls_config.clone(),
)?
.with_request_builder(crate::session_context::session_id_request_builder());
tls_config,
)?;
if let Some(request_builder) = request_builder {
api_client = api_client.with_request_builder(request_builder);
}
Ok(Self {
api_client,
@ -148,33 +142,27 @@ impl DatabricksProvider {
retry_config,
name: DATABRICKS_PROVIDER_NAME.to_string(),
token_cache,
instance_id: Self::resolve_instance_id(),
instance_id,
refresh_hook,
session_id_provider,
})
}
fn load_retry_config(config: &crate::config::Config) -> RetryConfig {
let max_retries = config
.get_param("DATABRICKS_MAX_RETRIES")
.ok()
.and_then(|v: String| v.parse::<usize>().ok())
pub fn load_retry_config(get_param: impl Fn(&str) -> Option<String>) -> RetryConfig {
let max_retries = get_param("DATABRICKS_MAX_RETRIES")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(DEFAULT_MAX_RETRIES);
let initial_interval_ms = config
.get_param("DATABRICKS_INITIAL_RETRY_INTERVAL_MS")
.ok()
.and_then(|v: String| v.parse::<u64>().ok())
let initial_interval_ms = get_param("DATABRICKS_INITIAL_RETRY_INTERVAL_MS")
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(DEFAULT_INITIAL_RETRY_INTERVAL_MS);
let backoff_multiplier = config
.get_param("DATABRICKS_BACKOFF_MULTIPLIER")
.ok()
.and_then(|v: String| v.parse::<f64>().ok())
let backoff_multiplier = get_param("DATABRICKS_BACKOFF_MULTIPLIER")
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(DEFAULT_BACKOFF_MULTIPLIER);
let max_interval_ms = config
.get_param("DATABRICKS_MAX_RETRY_INTERVAL_MS")
.ok()
.and_then(|v: String| v.parse::<u64>().ok())
let max_interval_ms = get_param("DATABRICKS_MAX_RETRY_INTERVAL_MS")
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(DEFAULT_MAX_RETRY_INTERVAL_MS);
RetryConfig::new(
@ -185,17 +173,6 @@ impl DatabricksProvider {
)
}
fn resolve_instance_id() -> Option<String> {
let enabled = crate::config::Config::global()
.get_param::<bool>("GOOSE_DATABRICKS_CLIENT_REQUEST_ID")
.unwrap_or(false);
if enabled {
Some(get_instance_id().to_string())
} else {
None
}
}
fn is_claude_model(model_name: &str) -> bool {
model_name.to_lowercase().contains("claude")
}
@ -514,7 +491,7 @@ impl DatabricksProvider {
}
}
impl goose_providers::base::ProviderDescriptor for DatabricksProvider {
impl crate::base::ProviderDescriptor for DatabricksProvider {
fn metadata() -> ProviderMetadata {
ProviderMetadata::new(
DATABRICKS_PROVIDER_NAME,
@ -532,17 +509,6 @@ impl goose_providers::base::ProviderDescriptor for DatabricksProvider {
}
}
impl ProviderDef for DatabricksProvider {
type Provider = Self;
fn from_env(
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(tls_config))
}
}
#[async_trait]
impl Provider for DatabricksProvider {
fn get_name(&self) -> &str {
@ -554,7 +520,9 @@ impl Provider for DatabricksProvider {
}
async fn refresh_credentials(&self) -> Result<(), ProviderError> {
crate::config::Config::global().invalidate_secrets_cache();
if let Some(refresh_hook) = &self.refresh_hook {
refresh_hook();
}
*self.token_cache.lock().unwrap() = None;
tracing::info!("Invalidated secrets cache and token cache for credential refresh");
Ok(())
@ -567,7 +535,11 @@ impl Provider for DatabricksProvider {
messages: &[Message],
tools: &[Tool],
) -> Result<MessageStream, ProviderError> {
let session_id = crate::session_context::current_session_id().unwrap_or_default();
let session_id = self
.session_id_provider
.as_ref()
.and_then(|provider| provider())
.unwrap_or_default();
let (endpoint_name, _) = extract_reasoning_effort(&model_config.model_name);
let endpoint_info = self.resolve_endpoint_info_cached(&endpoint_name).await.ok();
let effective_model_name = endpoint_info

View file

@ -1,15 +1,23 @@
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use super::api_client::AuthProvider;
use super::oauth;
use crate::api_client::AuthProvider;
const DEFAULT_CLIENT_ID: &str = "databricks-cli";
const DEFAULT_REDIRECT_URL: &str = "http://localhost";
const DEFAULT_SCOPES: &[&str] = &["all-apis", "offline_access"];
pub type DatabricksOauthTokenFuture = Pin<Box<dyn Future<Output = Result<String>> + Send>>;
pub type DatabricksOauthTokenProvider =
Arc<dyn Fn(String, String, String, Vec<String>) -> DatabricksOauthTokenFuture + Send + Sync>;
pub type DatabricksTokenResolver = Arc<dyn Fn() -> Option<String> + Send + Sync>;
pub type DatabricksRefreshHook = Arc<dyn Fn() + Send + Sync>;
pub type DatabricksSessionIdProvider = Arc<dyn Fn() -> Option<String> + Send + Sync>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DatabricksAuth {
Token(String),
@ -36,9 +44,11 @@ impl DatabricksAuth {
}
}
pub(crate) struct DatabricksAuthProvider {
pub struct DatabricksAuthProvider {
pub auth: DatabricksAuth,
pub token_cache: Arc<Mutex<Option<String>>>,
pub oauth_token_provider: Option<DatabricksOauthTokenProvider>,
pub token_resolver: Option<DatabricksTokenResolver>,
}
#[async_trait]
@ -50,9 +60,11 @@ impl AuthProvider for DatabricksAuthProvider {
match cached {
Some(t) => t,
None => {
let fresh = crate::config::Config::global()
.get_secret::<String>("DATABRICKS_TOKEN")
.unwrap_or_else(|_| original.clone());
let fresh = self
.token_resolver
.as_ref()
.and_then(|resolve| resolve())
.unwrap_or_else(|| original.clone());
*self.token_cache.lock().unwrap() = Some(fresh.clone());
fresh
}
@ -63,8 +75,19 @@ impl AuthProvider for DatabricksAuthProvider {
client_id,
redirect_url,
scopes,
} => oauth::get_oauth_token_async(host, client_id, redirect_url, scopes).await?,
} => {
let Some(provider) = &self.oauth_token_provider else {
anyhow::bail!("Databricks OAuth token provider is not configured")
};
Ok(("Authorization".to_string(), format!("Bearer {}", token)))
provider(
host.clone(),
client_id.clone(),
redirect_url.clone(),
scopes.clone(),
)
.await?
}
};
Ok(("Authorization".to_string(), format!("Bearer {token}")))
}
}

View file

@ -1,11 +1,10 @@
use crate::formats::anthropic::{AnthropicFormatOptions, ANTHROPIC_PROVIDER_NAME};
use crate::formats::openai::{self, extract_reasoning_effort, is_openai_responses_model};
use crate::images::ImageFormat;
use anyhow::Result;
use async_stream::try_stream;
use async_trait::async_trait;
use futures::future::BoxFuture;
use futures::TryStreamExt;
use goose_providers::formats::anthropic::{AnthropicFormatOptions, ANTHROPIC_PROVIDER_NAME};
use goose_providers::formats::openai::{self, extract_reasoning_effort, is_openai_responses_model};
use goose_providers::images::ImageFormat;
use serde::Serialize;
use serde_json::Value;
use std::io;
@ -14,25 +13,25 @@ use std::time::Duration;
use tokio::pin;
use tokio_util::io::StreamReader;
use super::api_client::{ApiClient, AuthMethod};
use super::base::{
ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata,
DEFAULT_PROVIDER_TIMEOUT_SECS,
};
use super::databricks_auth::{DatabricksAuth, DatabricksAuthProvider};
use super::formats::anthropic;
use super::openai_compatible::{handle_status, stream_openai_compat, stream_responses_compat};
use super::retry::ProviderRetry;
use crate::config::ConfigError;
use crate::api_client::{ApiClient, AuthMethod, TlsConfig};
use crate::base::{ConfigKey, MessageStream, Provider, ProviderMetadata};
const DEFAULT_PROVIDER_TIMEOUT_SECS: u64 = 600;
use crate::conversation::message::Message;
use crate::providers::retry::{
use crate::databricks_auth::{
DatabricksAuth, DatabricksAuthProvider, DatabricksOauthTokenProvider, DatabricksRefreshHook,
DatabricksTokenResolver,
};
use crate::errors::ProviderError;
use crate::formats::anthropic;
use crate::formats::openai_responses;
use crate::model::ModelConfig;
use crate::openai_compatible::{handle_status, stream_openai_compat, stream_responses_compat};
use crate::request_log::{start_log, LoggerHandleExt};
use crate::retry::ProviderRetry;
use crate::retry::{
RetryConfig, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_INITIAL_RETRY_INTERVAL_MS,
DEFAULT_MAX_RETRIES, DEFAULT_MAX_RETRY_INTERVAL_MS,
};
use goose_providers::errors::ProviderError;
use goose_providers::formats::openai_responses;
use goose_providers::model::ModelConfig;
use goose_providers::request_log::{start_log, LoggerHandleExt};
use rmcp::model::Tool;
const DATABRICKS_V2_PROVIDER_NAME: &str = "databricks_v2";
@ -51,7 +50,7 @@ enum DatabricksV2Route {
MlflowChatCompletions,
}
#[derive(Debug, Serialize)]
#[derive(Serialize)]
pub struct DatabricksV2Provider {
#[serde(skip)]
api_client: ApiClient,
@ -61,47 +60,25 @@ pub struct DatabricksV2Provider {
name: String,
#[serde(skip)]
token_cache: Arc<Mutex<Option<String>>>,
#[serde(skip)]
refresh_hook: Option<DatabricksRefreshHook>,
}
impl DatabricksV2Provider {
pub async fn cleanup() -> Result<()> {
super::oauth::cleanup_oauth_cache()
Ok(())
}
pub async fn from_env(
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");
if host.is_err() {
host = config.get_secret("DATABRICKS_HOST")
}
if host.is_err() {
return Err(ConfigError::NotFound(
"Did not find DATABRICKS_HOST in either config file or keyring".to_string(),
)
.into());
}
let host = host?;
let retry_config = Self::load_retry_config(config);
let auth = if let Ok(api_key) = config.get_secret("DATABRICKS_TOKEN") {
DatabricksAuth::token(api_key)
} else {
DatabricksAuth::oauth(host.clone())
};
Self::new(host, auth, retry_config, tls_config)
}
fn new(
#[allow(clippy::too_many_arguments)]
pub fn new(
host: String,
auth: DatabricksAuth,
retry_config: RetryConfig,
tls_config: Option<crate::providers::api_client::TlsConfig>,
tls_config: Option<TlsConfig>,
oauth_token_provider: Option<DatabricksOauthTokenProvider>,
token_resolver: Option<DatabricksTokenResolver>,
request_builder: Option<crate::api_client::RequestBuilderDecorator>,
refresh_hook: Option<DatabricksRefreshHook>,
) -> Result<Self> {
let token_cache = Arc::new(Mutex::new(match &auth {
DatabricksAuth::Token(t) => Some(t.clone()),
@ -111,47 +88,44 @@ impl DatabricksV2Provider {
let auth_method = AuthMethod::Custom(Box::new(DatabricksAuthProvider {
auth: auth.clone(),
token_cache: token_cache.clone(),
oauth_token_provider,
token_resolver,
}));
let api_client = ApiClient::with_timeout_and_tls(
let mut api_client = ApiClient::with_timeout_and_tls(
host,
auth_method,
Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS),
tls_config,
)?
.with_request_builder(crate::session_context::session_id_request_builder());
)?;
if let Some(request_builder) = request_builder {
api_client = api_client.with_request_builder(request_builder);
}
Ok(Self {
api_client,
retry_config,
name: DATABRICKS_V2_PROVIDER_NAME.to_string(),
token_cache,
refresh_hook,
})
}
fn load_retry_config(config: &crate::config::Config) -> RetryConfig {
let max_retries = config
.get_param("DATABRICKS_MAX_RETRIES")
.ok()
.and_then(|v: String| v.parse::<usize>().ok())
pub fn load_retry_config(get_param: impl Fn(&str) -> Option<String>) -> RetryConfig {
let max_retries = get_param("DATABRICKS_MAX_RETRIES")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(DEFAULT_MAX_RETRIES);
let initial_interval_ms = config
.get_param("DATABRICKS_INITIAL_RETRY_INTERVAL_MS")
.ok()
.and_then(|v: String| v.parse::<u64>().ok())
let initial_interval_ms = get_param("DATABRICKS_INITIAL_RETRY_INTERVAL_MS")
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(DEFAULT_INITIAL_RETRY_INTERVAL_MS);
let backoff_multiplier = config
.get_param("DATABRICKS_BACKOFF_MULTIPLIER")
.ok()
.and_then(|v: String| v.parse::<f64>().ok())
let backoff_multiplier = get_param("DATABRICKS_BACKOFF_MULTIPLIER")
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(DEFAULT_BACKOFF_MULTIPLIER);
let max_interval_ms = config
.get_param("DATABRICKS_MAX_RETRY_INTERVAL_MS")
.ok()
.and_then(|v: String| v.parse::<u64>().ok())
let max_interval_ms = get_param("DATABRICKS_MAX_RETRY_INTERVAL_MS")
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(DEFAULT_MAX_RETRY_INTERVAL_MS);
RetryConfig::new(
@ -328,7 +302,7 @@ impl DatabricksV2Provider {
}
}
impl goose_providers::base::ProviderDescriptor for DatabricksV2Provider {
impl crate::base::ProviderDescriptor for DatabricksV2Provider {
fn metadata() -> ProviderMetadata {
ProviderMetadata::new(
DATABRICKS_V2_PROVIDER_NAME,
@ -345,17 +319,6 @@ impl goose_providers::base::ProviderDescriptor for DatabricksV2Provider {
}
}
impl ProviderDef for DatabricksV2Provider {
type Provider = Self;
fn from_env(
_extensions: Vec<crate::config::ExtensionConfig>,
tls_config: Option<crate::providers::api_client::TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(Self::from_env(tls_config))
}
}
#[async_trait]
impl Provider for DatabricksV2Provider {
fn get_name(&self) -> &str {
@ -367,7 +330,9 @@ impl Provider for DatabricksV2Provider {
}
async fn refresh_credentials(&self) -> Result<(), ProviderError> {
crate::config::Config::global().invalidate_secrets_cache();
if let Some(refresh_hook) = &self.refresh_hook {
refresh_hook();
}
*self.token_cache.lock().unwrap() = None;
Ok(())
}

View file

@ -1,5 +1,8 @@
pub mod anthropic;
pub mod api_client;
pub mod databricks;
pub mod databricks_auth;
pub mod databricks_v2;
pub use goose_provider_types::{
base, canonical, conversation, errors, formats, goose_mode, images, json, model, permission,
request_log, retry, thinking, utils,

View file

@ -4,8 +4,8 @@ use goose::agents::{Agent, AgentEvent, ExtensionConfig, SessionConfig};
use goose::config::{GooseMode, DEFAULT_EXTENSION_DESCRIPTION, DEFAULT_EXTENSION_TIMEOUT};
use goose::conversation::message::Message;
use goose::providers::create_with_named_model;
use goose::providers::databricks::DATABRICKS_DEFAULT_MODEL;
use goose::session::session_manager::SessionType;
use goose_providers::databricks::DATABRICKS_DEFAULT_MODEL;
use std::path::PathBuf;
#[tokio::main]

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use dotenvy::dotenv;
use goose::conversation::message::Message;
use goose::providers::create_with_named_model;
use goose::providers::databricks::DATABRICKS_DEFAULT_MODEL;
use goose_providers::databricks::DATABRICKS_DEFAULT_MODEL;
#[tokio::main]
async fn main() -> Result<()> {

View file

@ -4,8 +4,8 @@ use dotenvy::dotenv;
use goose::conversation::message::Message;
use goose::providers::anthropic::ANTHROPIC_DEFAULT_MODEL;
use goose::providers::create_with_named_model;
use goose::providers::databricks::DATABRICKS_DEFAULT_MODEL;
use goose::providers::openai::OPEN_AI_DEFAULT_MODEL;
use goose_providers::databricks::DATABRICKS_DEFAULT_MODEL;
use rmcp::model::{CallToolRequestParams, Content, Tool};
use rmcp::object;
use std::fs;

View file

@ -0,0 +1,111 @@
use anyhow::Result;
use futures::future::BoxFuture;
use goose_providers::api_client::TlsConfig;
use goose_providers::base::ProviderDescriptor;
use goose_providers::databricks::DatabricksProvider;
use goose_providers::databricks_auth::{
DatabricksAuth, DatabricksOauthTokenProvider, DatabricksRefreshHook,
DatabricksSessionIdProvider, DatabricksTokenResolver,
};
use std::sync::Arc;
use crate::config::{Config, ConfigError, ExtensionConfig};
use crate::providers::base::ProviderDef;
pub struct DatabricksProviderDef;
impl ProviderDescriptor for DatabricksProviderDef {
fn metadata() -> goose_providers::base::ProviderMetadata {
DatabricksProvider::metadata()
}
}
impl ProviderDef for DatabricksProviderDef {
type Provider = DatabricksProvider;
fn from_env(
_extensions: Vec<ExtensionConfig>,
tls_config: Option<TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(from_env(tls_config))
}
}
pub async fn from_env(tls_config: Option<TlsConfig>) -> Result<DatabricksProvider> {
let config = Config::global();
let host = load_host(config)?;
let retry_config = DatabricksProvider::load_retry_config(|key| config.get_param(key).ok());
let auth = if let Ok(api_key) = config.get_secret("DATABRICKS_TOKEN") {
DatabricksAuth::token(api_key)
} else {
DatabricksAuth::oauth(host.clone())
};
DatabricksProvider::new(
host,
auth,
retry_config,
tls_config,
Some(oauth_token_provider()),
Some(token_resolver()),
Some(crate::session_context::session_id_request_builder()),
resolve_instance_id(),
Some(refresh_hook()),
Some(session_id_provider()),
)
}
pub async fn cleanup() -> Result<()> {
crate::providers::oauth::cleanup_oauth_cache()
}
fn load_host(config: &Config) -> Result<String> {
let mut host: Result<String, ConfigError> = config.get_param("DATABRICKS_HOST");
if host.is_err() {
host = config.get_secret("DATABRICKS_HOST")
}
host.map_err(|_| {
ConfigError::NotFound(
"Did not find DATABRICKS_HOST in either config file or keyring".to_string(),
)
.into()
})
}
fn resolve_instance_id() -> Option<String> {
let enabled = Config::global()
.get_param::<bool>("GOOSE_DATABRICKS_CLIENT_REQUEST_ID")
.unwrap_or(false);
enabled.then(|| crate::instance_id::get_instance_id().to_string())
}
fn token_resolver() -> DatabricksTokenResolver {
Arc::new(|| {
Config::global()
.get_secret::<String>("DATABRICKS_TOKEN")
.ok()
})
}
fn refresh_hook() -> DatabricksRefreshHook {
Arc::new(|| Config::global().invalidate_secrets_cache())
}
fn session_id_provider() -> DatabricksSessionIdProvider {
Arc::new(crate::session_context::current_session_id)
}
fn oauth_token_provider() -> DatabricksOauthTokenProvider {
Arc::new(|host, client_id, redirect_url, scopes| {
Box::pin(async move {
crate::providers::oauth::get_oauth_token_async(
&host,
&client_id,
&redirect_url,
&scopes,
)
.await
})
})
}

View file

@ -0,0 +1,97 @@
use anyhow::Result;
use futures::future::BoxFuture;
use goose_providers::api_client::TlsConfig;
use goose_providers::base::ProviderDescriptor;
use goose_providers::databricks_auth::{
DatabricksAuth, DatabricksOauthTokenProvider, DatabricksRefreshHook, DatabricksTokenResolver,
};
use goose_providers::databricks_v2::DatabricksV2Provider;
use std::sync::Arc;
use crate::config::{Config, ConfigError, ExtensionConfig};
use crate::providers::base::ProviderDef;
pub struct DatabricksV2ProviderDef;
impl ProviderDescriptor for DatabricksV2ProviderDef {
fn metadata() -> goose_providers::base::ProviderMetadata {
DatabricksV2Provider::metadata()
}
}
impl ProviderDef for DatabricksV2ProviderDef {
type Provider = DatabricksV2Provider;
fn from_env(
_extensions: Vec<ExtensionConfig>,
tls_config: Option<TlsConfig>,
) -> BoxFuture<'static, Result<Self::Provider>> {
Box::pin(from_env(tls_config))
}
}
pub async fn from_env(tls_config: Option<TlsConfig>) -> Result<DatabricksV2Provider> {
let config = Config::global();
let host = load_host(config)?;
let retry_config = DatabricksV2Provider::load_retry_config(|key| config.get_param(key).ok());
let auth = if let Ok(api_key) = config.get_secret("DATABRICKS_TOKEN") {
DatabricksAuth::token(api_key)
} else {
DatabricksAuth::oauth(host.clone())
};
DatabricksV2Provider::new(
host,
auth,
retry_config,
tls_config,
Some(oauth_token_provider()),
Some(token_resolver()),
Some(crate::session_context::session_id_request_builder()),
Some(refresh_hook()),
)
}
pub async fn cleanup() -> Result<()> {
crate::providers::oauth::cleanup_oauth_cache()
}
fn load_host(config: &Config) -> Result<String> {
let mut host: Result<String, ConfigError> = config.get_param("DATABRICKS_HOST");
if host.is_err() {
host = config.get_secret("DATABRICKS_HOST")
}
host.map_err(|_| {
ConfigError::NotFound(
"Did not find DATABRICKS_HOST in either config file or keyring".to_string(),
)
.into()
})
}
fn token_resolver() -> DatabricksTokenResolver {
Arc::new(|| {
Config::global()
.get_secret::<String>("DATABRICKS_TOKEN")
.ok()
})
}
fn refresh_hook() -> DatabricksRefreshHook {
Arc::new(|| Config::global().invalidate_secrets_cache())
}
fn oauth_token_provider() -> DatabricksOauthTokenProvider {
Arc::new(|host, client_id, redirect_url, scopes| {
Box::pin(async move {
crate::providers::oauth::get_oauth_token_async(
&host,
&client_id,
&redirect_url,
&scopes,
)
.await
})
})
}

View file

@ -3,7 +3,9 @@ pub mod anthropic {
}
#[cfg(feature = "aws-providers")]
pub mod bedrock;
pub mod databricks;
pub mod databricks {
pub use goose_providers::formats::databricks::*;
}
pub mod gcpvertexai;
pub mod google;
pub mod openrouter;

View file

@ -19,8 +19,6 @@ use super::{
codex_acp::CodexAcpProvider,
copilot_acp::CopilotAcpProvider,
cursor_agent::CursorAgentProvider,
databricks::DatabricksProvider,
databricks_v2::DatabricksV2Provider,
gcpvertexai::GcpVertexAIProvider,
gemini_cli::GeminiCliProvider,
gemini_oauth::GeminiOAuthProvider,
@ -41,6 +39,8 @@ use super::{
use crate::config::ExtensionConfig;
use crate::providers::anthropic_def::AnthropicProviderDef;
use crate::providers::base::ProviderType;
use crate::providers::databricks_def::{self, DatabricksProviderDef};
use crate::providers::databricks_v2_def::{self, DatabricksV2ProviderDef};
use crate::providers::ollama_def::OllamaProviderDef;
use crate::providers::openai_def::OpenAiProviderDef;
use crate::{
@ -92,11 +92,11 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
);
registry.register::<CodexProvider>(true);
registry.register::<CursorAgentProvider>(false);
registry.register_with_inventory::<DatabricksProvider>(
registry.register_with_inventory::<DatabricksProviderDef>(
true,
Some(registrations::refresh_only()),
);
registry.register_with_inventory::<DatabricksV2Provider>(
registry.register_with_inventory::<DatabricksV2ProviderDef>(
false,
Some(registrations::refresh_only()),
);
@ -145,11 +145,11 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
);
registry.set_cleanup(
"databricks",
Arc::new(|| Box::pin(DatabricksProvider::cleanup())),
Arc::new(|| Box::pin(databricks_def::cleanup())),
);
registry.set_cleanup(
"databricks_v2",
Arc::new(|| Box::pin(DatabricksV2Provider::cleanup())),
Arc::new(|| Box::pin(databricks_v2_def::cleanup())),
);
registry.set_cleanup(
"kimi_code",

View file

@ -29,9 +29,8 @@ pub mod codex_acp;
pub mod copilot_acp;
pub mod cursor_agent;
pub mod custom_provider_config;
pub mod databricks;
pub mod databricks_auth;
pub mod databricks_v2;
pub mod databricks_def;
pub mod databricks_v2_def;
pub mod formats;
mod gcpauth;
pub mod gcpvertexai;

View file

@ -15,7 +15,6 @@ use goose::providers::bedrock::BEDROCK_DEFAULT_MODEL;
use goose::providers::claude_code::CLAUDE_CODE_DEFAULT_MODEL;
use goose::providers::codex::CODEX_DEFAULT_MODEL;
use goose::providers::create_with_named_model;
use goose::providers::databricks::DATABRICKS_DEFAULT_MODEL;
use goose::providers::google::GOOGLE_DEFAULT_MODEL;
use goose::providers::litellm::LITELLM_DEFAULT_MODEL;
use goose::providers::openai::OPEN_AI_DEFAULT_MODEL;
@ -24,6 +23,7 @@ use goose::providers::sagemaker_tgi::SAGEMAKER_TGI_DEFAULT_MODEL;
use goose::providers::snowflake::SNOWFLAKE_DEFAULT_MODEL;
use goose::providers::xai::XAI_DEFAULT_MODEL;
use goose::session::{SessionManager, SessionType};
use goose_providers::databricks::DATABRICKS_DEFAULT_MODEL;
use goose_providers::errors::ProviderError;
use goose_test_support::{
EnforceSessionId, ExpectedSessionId, IgnoreSessionId, McpFixture, FAKE_CODE,