fix(providers): restore dynamic model discovery and current Grok support (#10756)
Co-authored-by: Michael Neale <14976+michaelneale@users.noreply.github.com>
This commit is contained in:
@@ -472,10 +472,14 @@ pub trait Provider: Send + Sync {
|
||||
let mut models_with_dates: Vec<(String, Option<String>)> = all_models
|
||||
.iter()
|
||||
.filter_map(|model| {
|
||||
let canonical_id = map_to_canonical_model(provider_name, model, registry)?;
|
||||
|
||||
let (provider, model_name) = canonical_id.split_once('/')?;
|
||||
let canonical_model = registry.get(provider, model_name)?;
|
||||
let canonical_model = map_to_canonical_model(provider_name, model, registry)
|
||||
.and_then(|canonical_id| {
|
||||
let (provider, model_name) = canonical_id.split_once('/')?;
|
||||
registry.get(provider, model_name)
|
||||
});
|
||||
let Some(canonical_model) = canonical_model else {
|
||||
return Some((model.clone(), None));
|
||||
};
|
||||
|
||||
if !canonical_model
|
||||
.modalities
|
||||
@@ -595,6 +599,31 @@ mod tests {
|
||||
use super::*;
|
||||
use test_case::test_case;
|
||||
|
||||
struct ModelInventoryProvider {
|
||||
models: Vec<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Provider for ModelInventoryProvider {
|
||||
fn get_name(&self) -> &str {
|
||||
"xai"
|
||||
}
|
||||
|
||||
async fn stream(
|
||||
&self,
|
||||
_model_config: &ModelConfig,
|
||||
_system: &str,
|
||||
_messages: &[Message],
|
||||
_tools: &[Tool],
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
|
||||
Ok(self.models.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn content_from_str(s: String) -> MessageContentBlock {
|
||||
if let Some(img_data) = s.strip_prefix("*img:") {
|
||||
MessageContentBlock::image(format!("http://example.com/{}", img_data), "image/png")
|
||||
@@ -717,6 +746,23 @@ mod tests {
|
||||
assert_eq!(message.agent_visible_content().as_concat_text(), "private");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recommended_models_preserve_unknown_future_models() {
|
||||
let provider = ModelInventoryProvider {
|
||||
models: vec![
|
||||
"grok-4.5".to_string(),
|
||||
"grok-future-unlisted".to_string(),
|
||||
"grok-4.20-multi-agent".to_string(),
|
||||
],
|
||||
};
|
||||
|
||||
let models = provider.fetch_recommended_models(false).await.unwrap();
|
||||
|
||||
assert!(models.contains(&"grok-4.5".to_string()));
|
||||
assert!(models.contains(&"grok-future-unlisted".to_string()));
|
||||
assert!(!models.contains(&"grok-4.20-multi-agent".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_model_info_creation() {
|
||||
// Test direct ModelInfo creation
|
||||
|
||||
@@ -1410,12 +1410,17 @@ pub fn create_request_with_options(
|
||||
|
||||
let (model_name, legacy_reasoning_effort) = extract_reasoning_effort(&model_config.model_name);
|
||||
let is_reasoning_model = is_openai_responses_model(&model_name);
|
||||
let supports_xai_effort = supports_xai_reasoning_effort(&model_name);
|
||||
let reasoning_effort = if is_reasoning_model {
|
||||
model_config
|
||||
.thinking_effort()
|
||||
.map_or(legacy_reasoning_effort, |effort| {
|
||||
openai_reasoning_effort_for_thinking(&model_name, effort)
|
||||
})
|
||||
} else if supports_xai_effort {
|
||||
model_config
|
||||
.thinking_effort()
|
||||
.and_then(|effort| xai_reasoning_effort_for_thinking(&model_name, effort))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1446,7 +1451,7 @@ pub fn create_request_with_options(
|
||||
payload["tools"] = json!(tools_spec);
|
||||
}
|
||||
|
||||
if !is_reasoning_model {
|
||||
if !is_reasoning_model && !supports_xai_effort {
|
||||
if let Some(temp) = model_config.temperature {
|
||||
payload["temperature"] = json!(temp);
|
||||
}
|
||||
@@ -1531,6 +1536,50 @@ pub fn is_openai_responses_model(model_name: &str) -> bool {
|
||||
re.is_match(model_name)
|
||||
}
|
||||
|
||||
/// Returns whether an xAI Chat Completions model accepts `reasoning_effort`.
|
||||
pub fn supports_xai_reasoning_effort(model_name: &str) -> bool {
|
||||
let model_name = model_name.to_ascii_lowercase();
|
||||
|
||||
model_name.starts_with("grok-4.5")
|
||||
|| model_name.starts_with("grok-4.3")
|
||||
|| model_name.starts_with("grok-3-mini")
|
||||
}
|
||||
|
||||
/// Returns whether an xAI model performs server-side reasoning.
|
||||
pub fn is_xai_reasoning_model(model_name: &str) -> bool {
|
||||
let model_name = model_name.to_ascii_lowercase();
|
||||
|
||||
if model_name.contains("non-reasoning") || model_name.contains("non_reasoning") {
|
||||
return false;
|
||||
}
|
||||
|
||||
supports_xai_reasoning_effort(&model_name)
|
||||
|| model_name.starts_with("grok-4.20")
|
||||
|| model_name.starts_with("grok-4-0709")
|
||||
|| model_name.starts_with("grok-4-fast-reasoning")
|
||||
|| model_name.starts_with("grok-4-1-fast-reasoning")
|
||||
}
|
||||
|
||||
/// Maps Goose's effort levels to values accepted by xAI Chat Completions.
|
||||
pub fn xai_reasoning_effort_for_thinking(
|
||||
model_name: &str,
|
||||
effort: ThinkingEffort,
|
||||
) -> Option<String> {
|
||||
let model_name = model_name.to_ascii_lowercase();
|
||||
let supports_none = model_name.starts_with("grok-4.3");
|
||||
let supports_medium = !model_name.starts_with("grok-3-mini");
|
||||
|
||||
match effort {
|
||||
ThinkingEffort::Off if supports_none => Some("none".to_string()),
|
||||
ThinkingEffort::Off => Some("low".to_string()),
|
||||
ThinkingEffort::Low => Some("low".to_string()),
|
||||
ThinkingEffort::Medium if supports_medium => Some("medium".to_string()),
|
||||
ThinkingEffort::Medium | ThinkingEffort::High | ThinkingEffort::Max => {
|
||||
Some("high".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn openai_reasoning_effort_for_thinking(
|
||||
model_name: &str,
|
||||
effort: ThinkingEffort,
|
||||
@@ -4134,6 +4183,121 @@ data: [DONE]"#;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xai_reasoning_model_capabilities_are_model_specific() {
|
||||
for model in ["grok-4.5", "grok-4.3", "grok-3-mini"] {
|
||||
assert!(supports_xai_reasoning_effort(model), "{model}");
|
||||
assert!(is_xai_reasoning_model(model), "{model}");
|
||||
}
|
||||
|
||||
for model in [
|
||||
"grok-4.20",
|
||||
"grok-4.20-0309-reasoning",
|
||||
"grok-4.20-multi-agent",
|
||||
"grok-4-0709",
|
||||
"grok-4-fast-reasoning",
|
||||
] {
|
||||
assert!(!supports_xai_reasoning_effort(model), "{model}");
|
||||
assert!(is_xai_reasoning_model(model), "{model}");
|
||||
}
|
||||
|
||||
for model in [
|
||||
"grok-4.20-0309-non-reasoning",
|
||||
"grok-4-fast-non-reasoning",
|
||||
"grok-3",
|
||||
"grok-build-0.1",
|
||||
] {
|
||||
assert!(!supports_xai_reasoning_effort(model), "{model}");
|
||||
assert!(!is_xai_reasoning_model(model), "{model}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xai_reasoning_effort_uses_each_models_supported_levels() {
|
||||
assert_eq!(
|
||||
xai_reasoning_effort_for_thinking("grok-4.5", ThinkingEffort::Off),
|
||||
Some("low".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
xai_reasoning_effort_for_thinking("grok-4.5", ThinkingEffort::Medium),
|
||||
Some("medium".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
xai_reasoning_effort_for_thinking("grok-4.3", ThinkingEffort::Off),
|
||||
Some("none".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
xai_reasoning_effort_for_thinking("grok-3-mini", ThinkingEffort::Medium),
|
||||
Some("high".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
xai_reasoning_effort_for_thinking("grok-4.5", ThinkingEffort::Max),
|
||||
Some("high".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_applies_grok_4_5_reasoning_controls() {
|
||||
let model_config = ModelConfig::new("grok-4.5")
|
||||
.with_thinking_effort(ThinkingEffort::Medium)
|
||||
.with_temperature(Some(0.7));
|
||||
|
||||
let payload = create_request(
|
||||
&model_config,
|
||||
"system prompt",
|
||||
&[],
|
||||
&[],
|
||||
&ImageFormat::OpenAi,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(payload.get("reasoning_effort"), Some(&json!("medium")));
|
||||
assert!(payload.get("temperature").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_maps_xai_off_to_supported_effort() {
|
||||
for (model, expected) in [("grok-4.5", "low"), ("grok-4.3", "none")] {
|
||||
let model_config = ModelConfig::new(model).with_thinking_effort(ThinkingEffort::Off);
|
||||
let payload = create_request(
|
||||
&model_config,
|
||||
"system prompt",
|
||||
&[],
|
||||
&[],
|
||||
&ImageFormat::OpenAi,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
payload.get("reasoning_effort"),
|
||||
Some(&json!(expected)),
|
||||
"{model}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_keeps_grok_4_20_fixed_reasoning_parameters() {
|
||||
let model_config = ModelConfig::new("grok-4.20-0309-reasoning")
|
||||
.with_thinking_effort(ThinkingEffort::High)
|
||||
.with_temperature(Some(0.7));
|
||||
|
||||
let payload = create_request(
|
||||
&model_config,
|
||||
"system prompt",
|
||||
&[],
|
||||
&[],
|
||||
&ImageFormat::OpenAi,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(payload.get("reasoning_effort").is_none());
|
||||
assert_eq!(payload.get("temperature"), Some(&json!(0.7_f32)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_reasoning_effort_for_responses_models() {
|
||||
for (model, expected_name, expected_effort) in [
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::formats::openai::{extract_reasoning_effort, is_openai_responses_model};
|
||||
use crate::formats::openai::{
|
||||
extract_reasoning_effort, is_openai_responses_model, is_xai_reasoning_model,
|
||||
supports_xai_reasoning_effort,
|
||||
};
|
||||
use crate::thinking::ThinkingEffort;
|
||||
use serde::de::Deserializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -244,6 +247,7 @@ impl ModelConfig {
|
||||
self.is_openai_reasoning_model()
|
||||
|| self.model_name.to_lowercase().contains("claude")
|
||||
|| Self::is_gemini3_reasoning_model_name(&self.model_name)
|
||||
|| is_xai_reasoning_model(&self.model_name)
|
||||
}
|
||||
|
||||
fn is_gemini3_reasoning_model_name(model_name: &str) -> bool {
|
||||
@@ -260,7 +264,7 @@ impl ModelConfig {
|
||||
}
|
||||
|
||||
pub fn normalize_effort_suffix(&mut self) {
|
||||
if !self.is_openai_reasoning_model() {
|
||||
if !self.is_openai_reasoning_model() && !supports_xai_reasoning_effort(&self.model_name) {
|
||||
return;
|
||||
}
|
||||
let parts: Vec<&str> = self.model_name.split('-').collect();
|
||||
@@ -541,6 +545,21 @@ mod tests {
|
||||
assert_eq!(config.model_name, "claude-sonnet-4-high");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xai_reasoning_effort_suffix_is_normalized() {
|
||||
let _guard = env_lock::lock_env([
|
||||
("GOOSE_THINKING_EFFORT", None::<&str>),
|
||||
("GOOSE_MAX_TOKENS", None::<&str>),
|
||||
("GOOSE_TEMPERATURE", None::<&str>),
|
||||
("GOOSE_CONTEXT_LIMIT", None::<&str>),
|
||||
("GOOSE_TOOLSHIM", None::<&str>),
|
||||
("GOOSE_TOOLSHIM_OLLAMA_MODEL", None::<&str>),
|
||||
]);
|
||||
let config = ModelConfig::new("grok-4.5-high");
|
||||
assert_eq!(config.model_name, "grok-4.5");
|
||||
assert_eq!(config.thinking_effort(), Some(ThinkingEffort::High));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_aliases() {
|
||||
assert_eq!("off".parse::<ThinkingEffort>(), Ok(ThinkingEffort::Off));
|
||||
@@ -740,6 +759,9 @@ mod tests {
|
||||
assert!(ModelConfig::new("o3-mini").is_reasoning_model());
|
||||
assert!(ModelConfig::new("claude-sonnet-4").is_reasoning_model());
|
||||
assert!(ModelConfig::new("gemini-3-pro").is_reasoning_model());
|
||||
assert!(ModelConfig::new("grok-4.5").is_reasoning_model());
|
||||
assert!(ModelConfig::new("grok-4.20-0309-reasoning").is_reasoning_model());
|
||||
assert!(!ModelConfig::new("grok-4.20-0309-non-reasoning").is_reasoning_model());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -100,13 +100,19 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
|
||||
false,
|
||||
Some(registrations::refresh_only()),
|
||||
);
|
||||
registry.register::<GcpVertexAIProvider>(false);
|
||||
registry.register_with_inventory::<GcpVertexAIProvider>(
|
||||
false,
|
||||
Some(registrations::refresh_only()),
|
||||
);
|
||||
registry.register::<GeminiCliProvider>(false);
|
||||
registry.register_with_inventory::<GeminiOAuthProvider>(
|
||||
true,
|
||||
Some(registrations::gemini_oauth_inventory()),
|
||||
);
|
||||
registry.register::<GithubCopilotProvider>(false);
|
||||
registry.register_with_inventory::<GithubCopilotProvider>(
|
||||
false,
|
||||
Some(registrations::refresh_only()),
|
||||
);
|
||||
registry.register_with_inventory::<GoogleProviderDef>(
|
||||
true,
|
||||
Some(registrations::google_inventory()),
|
||||
@@ -115,7 +121,10 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
|
||||
true,
|
||||
Some(registrations::huggingface_inventory()),
|
||||
);
|
||||
registry.register::<KimiCodeProvider>(true);
|
||||
registry.register_with_inventory::<KimiCodeProvider>(
|
||||
true,
|
||||
Some(registrations::kimi_code_inventory()),
|
||||
);
|
||||
registry.register_with_inventory::<LiteLLMProvider>(
|
||||
false,
|
||||
Some(registrations::refresh_only().with_configured(|| {
|
||||
@@ -128,7 +137,8 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
|
||||
.is_ok()
|
||||
})),
|
||||
);
|
||||
registry.register::<NanoGptProvider>(true);
|
||||
registry
|
||||
.register_with_inventory::<NanoGptProvider>(true, Some(registrations::refresh_only()));
|
||||
registry.register_with_inventory::<OllamaProviderDef>(
|
||||
true,
|
||||
Some(registrations::ollama_inventory()),
|
||||
@@ -153,8 +163,9 @@ async fn init_registry() -> RwLock<ProviderRegistry> {
|
||||
#[cfg(feature = "aws-providers")]
|
||||
registry.register::<SageMakerTgiProvider>(false);
|
||||
registry.register::<SnowflakeProviderDef>(false);
|
||||
registry.register::<TetrateProvider>(true);
|
||||
registry.register::<XaiProvider>(false);
|
||||
registry
|
||||
.register_with_inventory::<TetrateProvider>(true, Some(registrations::refresh_only()));
|
||||
registry.register_with_inventory::<XaiProvider>(false, Some(registrations::refresh_only()));
|
||||
registry.register_with_inventory::<XaiOAuthProvider>(
|
||||
true,
|
||||
Some(registrations::xai_oauth_inventory()),
|
||||
@@ -498,6 +509,27 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_api_backed_model_providers_are_registered_for_refresh() {
|
||||
for provider_name in [
|
||||
"gcp_vertex_ai",
|
||||
"github_copilot",
|
||||
"kimi_code",
|
||||
"nano-gpt",
|
||||
"tetrate",
|
||||
"xai",
|
||||
"xai_oauth",
|
||||
] {
|
||||
let entry = get_from_registry(provider_name)
|
||||
.await
|
||||
.expect("dynamic model provider should be registered");
|
||||
assert!(
|
||||
entry.supports_inventory_refresh(),
|
||||
"{provider_name} must refresh its model inventory"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_litellm_configured_without_api_key() {
|
||||
let _guard = env_lock::lock_env([
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::providers::gemini_oauth::TokenCache as GeminiOAuthTokenCache;
|
||||
use crate::providers::google::{GOOGLE_API_HOST, GOOGLE_PROVIDER_NAME};
|
||||
use crate::providers::huggingface::HuggingFaceProvider;
|
||||
use crate::providers::huggingface_auth;
|
||||
use crate::providers::kimicode::KIMI_CONFIGURED_MARKER;
|
||||
use crate::providers::ollama::OLLAMA_PROVIDER_NAME;
|
||||
use crate::providers::openai::{OPEN_AI_DEFAULT_BASE_PATH, OPEN_AI_PROVIDER_NAME};
|
||||
use crate::providers::pi_acp::{PI_ACP_BINARY, PI_ACP_PROVIDER_NAME};
|
||||
@@ -147,6 +148,14 @@ pub fn refresh_only() -> InventoryRegistration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kimi_code_inventory() -> InventoryRegistration {
|
||||
refresh_only().with_configured(|| {
|
||||
Config::global()
|
||||
.get_param::<bool>(KIMI_CONFIGURED_MARKER)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn chatgpt_codex_inventory() -> InventoryRegistration {
|
||||
InventoryRegistration {
|
||||
supports_refresh: false,
|
||||
@@ -167,7 +176,7 @@ pub fn gemini_oauth_inventory() -> InventoryRegistration {
|
||||
|
||||
pub fn xai_oauth_inventory() -> InventoryRegistration {
|
||||
InventoryRegistration {
|
||||
supports_refresh: false,
|
||||
supports_refresh: true,
|
||||
identity: default_inventory_identity_resolver(),
|
||||
configured: None,
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const DEFAULT_TOKEN_LIFETIME_SECS: i64 = 3600;
|
||||
/// Marker key written to the user config when OAuth completes successfully.
|
||||
/// `check_provider_configured` (server) keys off this when an OAuth-flow
|
||||
/// provider has no required secret env var.
|
||||
const KIMI_CONFIGURED_MARKER: &str = "kimi_code_configured";
|
||||
pub(crate) const KIMI_CONFIGURED_MARKER: &str = "kimi_code_configured";
|
||||
|
||||
// ── Token persistence ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -6,8 +6,13 @@ use futures::future::BoxFuture;
|
||||
|
||||
const XAI_PROVIDER_NAME: &str = "xai";
|
||||
pub const XAI_API_HOST: &str = "https://api.x.ai/v1";
|
||||
pub const XAI_DEFAULT_MODEL: &str = "grok-code-fast-1";
|
||||
pub const XAI_DEFAULT_MODEL: &str = "grok-4.5";
|
||||
pub const XAI_KNOWN_MODELS: &[&str] = &[
|
||||
"grok-4.5",
|
||||
"grok-4.3",
|
||||
"grok-4.20-0309-reasoning",
|
||||
"grok-4.20-0309-non-reasoning",
|
||||
"grok-build-0.1",
|
||||
"grok-code-fast-1",
|
||||
"grok-4-0709",
|
||||
"grok-3",
|
||||
@@ -32,14 +37,23 @@ pub const XAI_DOC_URL: &str = "https://docs.x.ai/docs/overview";
|
||||
|
||||
pub struct XaiProvider;
|
||||
|
||||
pub fn xai_known_model_info() -> Vec<goose_providers::base::ModelInfo> {
|
||||
XAI_KNOWN_MODELS
|
||||
.iter()
|
||||
.map(|model_name| {
|
||||
goose_providers::base::model_info_for_provider_model(XAI_PROVIDER_NAME, model_name)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl goose_providers::base::ProviderDescriptor for XaiProvider {
|
||||
fn metadata() -> ProviderMetadata {
|
||||
ProviderMetadata::new(
|
||||
ProviderMetadata::with_models(
|
||||
XAI_PROVIDER_NAME,
|
||||
"xAI",
|
||||
"Grok models from xAI, including reasoning and multimodal capabilities",
|
||||
XAI_DEFAULT_MODEL,
|
||||
XAI_KNOWN_MODELS.to_vec(),
|
||||
xai_known_model_info(),
|
||||
XAI_DOC_URL,
|
||||
vec![
|
||||
ConfigKey::new("XAI_API_KEY", true, true, None, true),
|
||||
@@ -75,3 +89,29 @@ impl ProviderDef for XaiProvider {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use goose_providers::base::ProviderDescriptor;
|
||||
|
||||
#[test]
|
||||
fn current_xai_models_use_canonical_metadata() {
|
||||
let metadata = XaiProvider::metadata();
|
||||
|
||||
let grok_4_5 = metadata
|
||||
.known_models
|
||||
.iter()
|
||||
.find(|model| model.name == "grok-4.5")
|
||||
.expect("grok-4.5 should be a known xAI model");
|
||||
assert_eq!(grok_4_5.context_limit, 500_000);
|
||||
assert!(grok_4_5.reasoning);
|
||||
|
||||
let grok_4_20_non_reasoning = metadata
|
||||
.known_models
|
||||
.iter()
|
||||
.find(|model| model.name == "grok-4.20-0309-non-reasoning")
|
||||
.expect("grok-4.20 non-reasoning should be a known xAI model");
|
||||
assert!(!grok_4_20_non_reasoning.reasoning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::api_client::{ApiClient, AuthMethod, AuthProvider};
|
||||
use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata};
|
||||
use super::openai_compatible::OpenAiCompatibleProvider;
|
||||
use super::xai::{XAI_API_HOST, XAI_DEFAULT_MODEL, XAI_KNOWN_MODELS};
|
||||
use super::xai::{xai_known_model_info, XAI_API_HOST, XAI_DEFAULT_MODEL};
|
||||
use crate::config::paths::Paths;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::providers::private_file::write_private_file;
|
||||
@@ -755,12 +755,12 @@ impl Provider for XaiOAuthProvider {
|
||||
|
||||
impl goose_providers::base::ProviderDescriptor for XaiOAuthProvider {
|
||||
fn metadata() -> ProviderMetadata {
|
||||
ProviderMetadata::new(
|
||||
ProviderMetadata::with_models(
|
||||
XAI_OAUTH_PROVIDER_NAME,
|
||||
"xAI (SuperGrok Subscription)",
|
||||
"Use your xAI SuperGrok subscription via OAuth instead of an API key. Falls back to a device-code flow on headless / remote machines.",
|
||||
XAI_DEFAULT_MODEL,
|
||||
XAI_KNOWN_MODELS.to_vec(),
|
||||
xai_known_model_info(),
|
||||
XAI_OAUTH_DOC_URL,
|
||||
vec![
|
||||
ConfigKey::new_oauth("XAI_OAUTH_TOKEN", true, true, None, false),
|
||||
|
||||
@@ -175,13 +175,8 @@ fn acp_secret_mutations_and_inventory_refresh_invalidate_global_secret_cache() {
|
||||
assert_eq!(
|
||||
save_provider_config.get("refresh"),
|
||||
Some(&serde_json::json!({
|
||||
"started": [],
|
||||
"skipped": [
|
||||
{
|
||||
"providerId": "xai",
|
||||
"reason": "does_not_support_refresh",
|
||||
},
|
||||
],
|
||||
"started": ["xai"],
|
||||
"skipped": [],
|
||||
})),
|
||||
"provider config save should return the inventory refresh acknowledgement"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user