Add unified thinking effort control across all providers (#9242)
Signed-off-by: jh-block <jhugo@block.xyz> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ use goose::config::permission::PermissionLevel;
|
||||
use goose::config::ExtensionEntry;
|
||||
use goose::conversation::Conversation;
|
||||
use goose::download_manager::{DownloadProgress, DownloadStatus};
|
||||
use goose::model::ModelConfig;
|
||||
use goose::model::{ModelConfig, ThinkingEffort};
|
||||
use goose::permission::permission_confirmation::{Permission, PrincipalType};
|
||||
use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata, ProviderType};
|
||||
use goose::session::{Session, SessionInsights, SessionType, SystemInfo};
|
||||
@@ -397,6 +397,7 @@ derive_utoipa!(IconTheme as IconThemeSchema);
|
||||
super::routes::config_management::read_all_config,
|
||||
super::routes::config_management::providers,
|
||||
super::routes::config_management::get_provider_models,
|
||||
super::routes::config_management::get_provider_model_info,
|
||||
super::routes::config_management::get_slash_commands,
|
||||
super::routes::config_management::upsert_permissions,
|
||||
super::routes::config_management::create_custom_provider,
|
||||
@@ -573,6 +574,8 @@ derive_utoipa!(IconTheme as IconThemeSchema);
|
||||
PrincipalType,
|
||||
ModelInfo,
|
||||
ModelConfig,
|
||||
ThinkingEffort,
|
||||
super::routes::config_management::ProviderModelInfoQuery,
|
||||
Session,
|
||||
goose::config::goose_mode::GooseMode,
|
||||
SessionInsights,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::routes::config_management::resolve_provider_model_info;
|
||||
use crate::routes::errors::ErrorResponse;
|
||||
use crate::routes::recipe_utils::{
|
||||
apply_recipe_to_agent, build_recipe_with_parameter_values, load_recipe_by_id, validate_recipe,
|
||||
@@ -595,7 +596,7 @@ async fn update_agent_provider(
|
||||
}
|
||||
};
|
||||
|
||||
let model_config = ModelConfig::new(&model)
|
||||
let mut model_config = ModelConfig::new(&model)
|
||||
.map_err(|e| {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
@@ -603,8 +604,15 @@ async fn update_agent_provider(
|
||||
)
|
||||
})?
|
||||
.with_canonical_limits(&payload.provider)
|
||||
.with_context_limit(payload.context_limit)
|
||||
.with_request_params(payload.request_params);
|
||||
.with_context_limit(payload.context_limit);
|
||||
|
||||
if let Some(request_params) = payload.request_params {
|
||||
model_config = model_config.with_merged_request_params(request_params);
|
||||
}
|
||||
let model_info = resolve_provider_model_info(&payload.provider, &model)
|
||||
.await
|
||||
.map_err(|e| (e.status, e.message))?;
|
||||
model_config.reasoning = Some(model_info.reasoning);
|
||||
|
||||
let extensions =
|
||||
EnabledExtensionsState::for_session(state.session_manager(), &payload.session_id, config)
|
||||
|
||||
@@ -13,7 +13,7 @@ use goose::config::ExtensionEntry;
|
||||
use goose::config::{Config, ConfigError};
|
||||
use goose::custom_requests::SourceType;
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::base::{ProviderMetadata, ProviderType};
|
||||
use goose::providers::base::{ModelInfo, ProviderMetadata, ProviderType};
|
||||
use goose::providers::canonical::maybe_get_canonical_model;
|
||||
use goose::providers::catalog::{
|
||||
get_provider_template, get_providers_by_format, ProviderCatalogEntry, ProviderFormat,
|
||||
@@ -418,7 +418,7 @@ pub async fn providers() -> Result<Json<Vec<ProviderDetails>>, ErrorResponse> {
|
||||
("name" = String, Path, description = "Provider name (e.g., openai)")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Models fetched successfully", body = [String]),
|
||||
(status = 200, description = "Models fetched successfully", body = [ModelInfo]),
|
||||
(status = 400, description = "Unknown provider, provider not configured, or authentication error"),
|
||||
(status = 429, description = "Rate limit exceeded"),
|
||||
(status = 500, description = "Internal server error")
|
||||
@@ -426,7 +426,7 @@ pub async fn providers() -> Result<Json<Vec<ProviderDetails>>, ErrorResponse> {
|
||||
)]
|
||||
pub async fn get_provider_models(
|
||||
Path(name): Path<String>,
|
||||
) -> Result<Json<Vec<String>>, ErrorResponse> {
|
||||
) -> Result<Json<Vec<ModelInfo>>, ErrorResponse> {
|
||||
let all = get_providers().await.into_iter().collect::<Vec<_>>();
|
||||
let Some((metadata, provider_type)) = all.into_iter().find(|(m, _)| m.name == name) else {
|
||||
return Err(ErrorResponse::bad_request(format!(
|
||||
@@ -444,7 +444,7 @@ pub async fn get_provider_models(
|
||||
let model_config = ModelConfig::new(&metadata.default_model)?.with_canonical_limits(&name);
|
||||
let provider = goose::providers::create(&name, model_config, Vec::new()).await?;
|
||||
|
||||
let models_result = provider.fetch_recommended_models().await;
|
||||
let models_result = provider.fetch_recommended_model_info().await;
|
||||
|
||||
match models_result {
|
||||
Ok(models) => Ok(Json(models)),
|
||||
@@ -452,6 +452,70 @@ pub async fn get_provider_models(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct ProviderModelInfoQuery {
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
pub async fn resolve_provider_model_info(
|
||||
name: &str,
|
||||
model: &str,
|
||||
) -> Result<ModelInfo, ErrorResponse> {
|
||||
let all = get_providers().await.into_iter().collect::<Vec<_>>();
|
||||
let Some((metadata, provider_type)) = all.into_iter().find(|(m, _)| m.name == name) else {
|
||||
return Err(ErrorResponse::bad_request(format!(
|
||||
"Unknown provider: {}",
|
||||
name
|
||||
)));
|
||||
};
|
||||
if !check_provider_configured(&metadata, provider_type) {
|
||||
return Err(ErrorResponse::bad_request(format!(
|
||||
"Provider '{}' is not configured",
|
||||
name
|
||||
)));
|
||||
}
|
||||
|
||||
let model_config = ModelConfig::new(model)?.with_canonical_limits(name);
|
||||
let provider = goose::providers::create(name, model_config.clone(), Vec::new()).await?;
|
||||
match provider.fetch_model_info(model).await {
|
||||
Ok(info) => Ok(info),
|
||||
Err(error) => {
|
||||
let mut info = ModelInfo::new(model, model_config.context_limit());
|
||||
info.reasoning = model_config.is_reasoning_model();
|
||||
tracing::debug!(
|
||||
provider = name,
|
||||
model,
|
||||
error = %error,
|
||||
"Falling back to local model metadata"
|
||||
);
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/config/providers/{name}/model-info",
|
||||
params(
|
||||
("name" = String, Path, description = "Provider name (e.g., openai)")
|
||||
),
|
||||
request_body = ProviderModelInfoQuery,
|
||||
responses(
|
||||
(status = 200, description = "Model metadata fetched successfully", body = ModelInfo),
|
||||
(status = 400, description = "Unknown provider, provider not configured, or authentication error"),
|
||||
(status = 429, description = "Rate limit exceeded"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn get_provider_model_info(
|
||||
Path(name): Path<String>,
|
||||
Json(query): Json<ProviderModelInfoQuery>,
|
||||
) -> Result<Json<ModelInfo>, ErrorResponse> {
|
||||
resolve_provider_model_info(&name, &query.model)
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::IntoParams)]
|
||||
pub struct SlashCommandsQuery {
|
||||
/// Optional working directory to discover local skills from
|
||||
@@ -523,6 +587,7 @@ pub struct ModelInfoData {
|
||||
pub model: String,
|
||||
pub context_limit: usize,
|
||||
pub max_output_tokens: Option<usize>,
|
||||
pub reasoning: bool,
|
||||
pub input_token_cost: Option<f64>,
|
||||
pub output_token_cost: Option<f64>,
|
||||
pub cache_read_token_cost: Option<f64>,
|
||||
@@ -560,6 +625,9 @@ pub async fn get_canonical_model_info(
|
||||
model: query.model.clone(),
|
||||
context_limit: canonical_model.limit.context,
|
||||
max_output_tokens: canonical_model.limit.output,
|
||||
reasoning: canonical_model
|
||||
.reasoning
|
||||
.unwrap_or_else(|| ModelConfig::new_or_fail(&query.model).is_reasoning_model()),
|
||||
// Costs are per million tokens - client handles division for display
|
||||
input_token_cost: canonical_model.cost.input,
|
||||
output_token_cost: canonical_model.cost.output,
|
||||
@@ -926,6 +994,10 @@ pub fn routes(state: Arc<AppState>) -> Router {
|
||||
.route("/config/extensions/{name}", delete(remove_extension))
|
||||
.route("/config/providers", get(providers))
|
||||
.route("/config/providers/{name}/models", get(get_provider_models))
|
||||
.route(
|
||||
"/config/providers/{name}/model-info",
|
||||
post(get_provider_model_info),
|
||||
)
|
||||
.route("/config/provider-catalog", get(get_provider_catalog))
|
||||
.route(
|
||||
"/config/provider-catalog/{id}",
|
||||
|
||||
Reference in New Issue
Block a user