Remove Grok OAuth and legacy BYOK support
This commit is contained in:
@@ -138,458 +138,3 @@ fn llm_info_round_trip_serializes_and_deserializes() {
|
||||
|
||||
assert_eq!(info, round_tripped);
|
||||
}
|
||||
|
||||
// -- build_custom_llm_infos / display label tests --
|
||||
|
||||
fn endpoint(
|
||||
name: &str,
|
||||
url: &str,
|
||||
api_key: &str,
|
||||
models: Vec<CustomEndpointModel>,
|
||||
) -> CustomEndpoint {
|
||||
CustomEndpoint {
|
||||
name: name.into(),
|
||||
url: url.into(),
|
||||
api_key: api_key.into(),
|
||||
models,
|
||||
}
|
||||
}
|
||||
|
||||
fn model(name: &str, alias: Option<&str>, config_key: &str) -> CustomEndpointModel {
|
||||
CustomEndpointModel {
|
||||
name: name.into(),
|
||||
alias: alias.map(|s| s.into()),
|
||||
config_key: config_key.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_llm_infos_built_from_endpoints() {
|
||||
let keys = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"My Endpoint",
|
||||
"https://x.io",
|
||||
"k",
|
||||
vec![
|
||||
model("gpt-4", Some("fast"), "uuid-1"),
|
||||
model("llama", None, "uuid-2"),
|
||||
],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let infos = build_custom_llm_infos(&keys);
|
||||
assert_eq!(infos.len(), 2);
|
||||
assert_eq!(infos[0].display_name, "fast");
|
||||
assert_eq!(infos[0].id.as_str(), "uuid-1");
|
||||
assert_eq!(
|
||||
infos[0].description.as_deref(),
|
||||
Some("Custom · My Endpoint")
|
||||
);
|
||||
assert_eq!(infos[1].display_name, "llama");
|
||||
assert_eq!(infos[1].id.as_str(), "uuid-2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_llm_display_name_uses_alias_when_present() {
|
||||
let keys = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"ep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![model("raw-name", Some("My Alias"), "uuid-a")],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let infos = build_custom_llm_infos(&keys);
|
||||
assert_eq!(infos[0].display_name, "My Alias");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_llm_display_name_falls_back_to_name_when_alias_missing() {
|
||||
let keys = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"ep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![model("raw-name", None, "uuid-a")],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let infos = build_custom_llm_infos(&keys);
|
||||
assert_eq!(infos[0].display_name, "raw-name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_endpoint_usage_display_label_resolves_alias_name_and_generic_fallback() {
|
||||
let keys = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"ep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![
|
||||
model("raw-alias", Some("Alias"), "uuid-alias"),
|
||||
model("raw-name", None, "uuid-name"),
|
||||
model("raw~name", None, "uuid-tilde-name"),
|
||||
],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let preferences = LLMPreferences {
|
||||
models_by_feature: ModelsByFeature::default(),
|
||||
last_update: None,
|
||||
base_llm_for_terminal_view: HashMap::new(),
|
||||
custom_llms: build_custom_llm_infos(&keys),
|
||||
custom_model_routers: Vec::new(),
|
||||
openai_provider_routing: HashMap::new(),
|
||||
fetched_openai_models: Vec::new(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
preferences.custom_endpoint_usage_display_label("uuid-alias"),
|
||||
"Alias"
|
||||
);
|
||||
assert_eq!(
|
||||
preferences.custom_endpoint_usage_display_label("uuid-name"),
|
||||
"raw-name"
|
||||
);
|
||||
assert_eq!(
|
||||
preferences.custom_endpoint_usage_display_label("uuid-tilde-name"),
|
||||
"raw~name"
|
||||
);
|
||||
assert_eq!(
|
||||
preferences.custom_endpoint_usage_display_label("unknown"),
|
||||
CUSTOM_ENDPOINT_USAGE_FALLBACK_LABEL
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn empty_llm_preferences_for_provider_tests() -> LLMPreferences {
|
||||
LLMPreferences {
|
||||
models_by_feature: ModelsByFeature::default(),
|
||||
last_update: None,
|
||||
base_llm_for_terminal_view: HashMap::new(),
|
||||
custom_llms: Vec::new(),
|
||||
custom_model_routers: Vec::new(),
|
||||
openai_provider_routing: HashMap::new(),
|
||||
fetched_openai_models: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn openai_model(
|
||||
model_id: &str,
|
||||
display_name: &str,
|
||||
context_size: u32,
|
||||
max_input_tokens: Option<u32>,
|
||||
max_output_tokens: Option<u32>,
|
||||
vision_supported: bool,
|
||||
) -> OpenAIModelConfig {
|
||||
OpenAIModelConfig {
|
||||
model_id: model_id.to_string(),
|
||||
display_name: display_name.to_string(),
|
||||
vision_supported,
|
||||
context_size,
|
||||
max_input_tokens,
|
||||
max_output_tokens,
|
||||
provider: Some("openai".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn openai_model_config_accepts_legacy_and_endpoint_field_names() {
|
||||
let model: OpenAIModelConfig = toml::from_str(
|
||||
r#"
|
||||
model_id = "provider/custom-model"
|
||||
display_name = "Custom Model"
|
||||
vision_support = true
|
||||
token_size = 123456
|
||||
max_input_tokens = 111111
|
||||
max_tokens = 8192
|
||||
provider = "openai"
|
||||
"#,
|
||||
)
|
||||
.expect("model config should parse");
|
||||
|
||||
assert_eq!(model.model_id, "provider/custom-model");
|
||||
assert!(model.vision_supported);
|
||||
assert_eq!(model.context_size, 123_456);
|
||||
assert_eq!(model.max_input_tokens, Some(111_111));
|
||||
assert_eq!(model.max_output_tokens, Some(8_192));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn inject_openai_models_uses_persisted_model_metadata_and_routing() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_settings_for_tests(&mut app);
|
||||
let mut preferences = empty_llm_preferences_for_provider_tests();
|
||||
|
||||
app.update(|ctx| {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
settings.openai_enabled.set_value(true, ctx).unwrap();
|
||||
settings
|
||||
.openai_base_url
|
||||
.set_value("https://litellm.example/v1".to_string(), ctx)
|
||||
.unwrap();
|
||||
settings
|
||||
.openai_api_key
|
||||
.set_value("test-key".to_string(), ctx)
|
||||
.unwrap();
|
||||
settings
|
||||
.openai_models
|
||||
.set_value(
|
||||
vec![openai_model(
|
||||
"provider/custom-model",
|
||||
"Custom Model",
|
||||
200_000,
|
||||
Some(128_000),
|
||||
Some(8_192),
|
||||
true,
|
||||
)],
|
||||
ctx,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
preferences.inject_openai_models(ctx);
|
||||
|
||||
let model = preferences
|
||||
.models_by_feature
|
||||
.agent_mode
|
||||
.choices
|
||||
.iter()
|
||||
.find(|model| model.id.as_str() == "provider/custom-model")
|
||||
.expect("configured model should be injected");
|
||||
assert_eq!(model.provider, LLMProvider::LiteLLM);
|
||||
assert_eq!(model.description.as_deref(), Some("LiteLLM"));
|
||||
assert!(model.vision_supported);
|
||||
assert_eq!(model.context_window.default_max, 128_000);
|
||||
assert_eq!(model.context_window.max, 128_000);
|
||||
|
||||
let client_config = preferences
|
||||
.openai_client_config_for_model("provider/custom-model")
|
||||
.expect("configured model should have routing");
|
||||
assert_eq!(client_config.base_url, "https://litellm.example/v1");
|
||||
assert_eq!(client_config.api_key.as_deref(), Some("test-key"));
|
||||
assert_eq!(client_config.max_input_tokens, Some(128_000));
|
||||
assert_eq!(client_config.max_output_tokens, Some(8_192));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn inject_openai_models_uses_multi_provider_models() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_settings_for_tests(&mut app);
|
||||
let mut preferences = empty_llm_preferences_for_provider_tests();
|
||||
|
||||
app.update(|ctx| {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
settings.openai_enabled.set_value(true, ctx).unwrap();
|
||||
settings
|
||||
.openai_providers
|
||||
.set_value(
|
||||
vec![OpenAIProviderConfig {
|
||||
name: "Ollama".to_string(),
|
||||
base_url: "http://localhost:11434/v1".to_string(),
|
||||
api_key: None,
|
||||
models: vec![openai_model(
|
||||
"llama3.2",
|
||||
"Llama 3.2",
|
||||
64_000,
|
||||
None,
|
||||
Some(4_096),
|
||||
false,
|
||||
)],
|
||||
}],
|
||||
ctx,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
preferences.inject_openai_models(ctx);
|
||||
|
||||
let model = preferences
|
||||
.models_by_feature
|
||||
.agent_mode
|
||||
.choices
|
||||
.iter()
|
||||
.find(|model| model.id.as_str() == "llama3.2")
|
||||
.expect("provider model should be injected");
|
||||
assert_eq!(model.description.as_deref(), Some("Ollama"));
|
||||
assert!(!model.vision_supported);
|
||||
assert_eq!(model.context_window.default_max, 64_000);
|
||||
|
||||
let client_config = preferences
|
||||
.openai_client_config_for_model("llama3.2")
|
||||
.expect("provider model should have routing");
|
||||
assert_eq!(client_config.base_url, "http://localhost:11434/v1");
|
||||
assert_eq!(client_config.max_input_tokens, Some(64_000));
|
||||
assert_eq!(client_config.max_output_tokens, Some(4_096));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_llm_infos_skip_endpoints_with_empty_api_key() {
|
||||
let keys = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![
|
||||
endpoint("bad", "https://a.io", "", vec![model("m", None, "uuid-x")]),
|
||||
endpoint(
|
||||
"good",
|
||||
"https://b.io",
|
||||
"k",
|
||||
vec![model("m", None, "uuid-y")],
|
||||
),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let infos = build_custom_llm_infos(&keys);
|
||||
assert_eq!(infos.len(), 1);
|
||||
assert_eq!(infos[0].id.as_str(), "uuid-y");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_llm_infos_skip_models_without_config_key() {
|
||||
let keys = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"ep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![
|
||||
model("unconfigured", None, ""),
|
||||
model("ready", None, "uuid-a"),
|
||||
],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let infos = build_custom_llm_infos(&keys);
|
||||
assert_eq!(infos.len(), 1);
|
||||
assert_eq!(infos[0].display_name, "ready");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_model_row_purges_from_custom_llms() {
|
||||
let before = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"ep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![model("a", None, "uuid-a"), model("b", None, "uuid-b")],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(build_custom_llm_infos(&before).len(), 2);
|
||||
|
||||
let after = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"ep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![model("b", None, "uuid-b")],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let infos = build_custom_llm_infos(&after);
|
||||
assert_eq!(infos.len(), 1);
|
||||
assert_eq!(infos[0].id.as_str(), "uuid-b");
|
||||
assert!(infos.iter().all(|i| i.id.as_str() != "uuid-a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_endpoint_purges_all_its_models_from_custom_llms() {
|
||||
let before = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![
|
||||
endpoint(
|
||||
"keep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![model("k1", None, "uuid-k1")],
|
||||
),
|
||||
endpoint(
|
||||
"goner",
|
||||
"https://b.io",
|
||||
"k",
|
||||
vec![model("g1", None, "uuid-g1"), model("g2", None, "uuid-g2")],
|
||||
),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(build_custom_llm_infos(&before).len(), 3);
|
||||
|
||||
let after = ai::api_keys::ApiKeys {
|
||||
custom_endpoints: vec![endpoint(
|
||||
"keep",
|
||||
"https://a.io",
|
||||
"k",
|
||||
vec![model("k1", None, "uuid-k1")],
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let infos = build_custom_llm_infos(&after);
|
||||
assert_eq!(infos.len(), 1);
|
||||
assert_eq!(infos[0].id.as_str(), "uuid-k1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reconcile_preserves_custom_models_saved_on_execution_profile() {
|
||||
App::test((), |mut app| async move {
|
||||
let _custom_inference_flag = FeatureFlag::CustomInferenceEndpoints.override_enabled(true);
|
||||
|
||||
initialize_settings_for_tests(&mut app);
|
||||
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
|
||||
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
|
||||
app.add_singleton_model(AuthManager::new_for_test);
|
||||
app.add_singleton_model(|_| NetworkStatus::new());
|
||||
app.add_singleton_model(UserWorkspaces::default_mock);
|
||||
app.add_singleton_model(CloudModel::mock);
|
||||
app.add_singleton_model(TeamTesterStatus::mock);
|
||||
app.add_singleton_model(SyncQueue::mock);
|
||||
app.add_singleton_model(UpdateManager::mock);
|
||||
app.add_singleton_model(|_| TemplatableMCPServerManager::default());
|
||||
|
||||
let profiles_model = app.add_singleton_model(|ctx| {
|
||||
AIExecutionProfilesModel::new(&LaunchMode::new_for_unit_test(), ctx)
|
||||
});
|
||||
let llm_preferences = app.add_singleton_model(LLMPreferences::new);
|
||||
|
||||
let custom_model_id = LLMId::from("custom-model-config-key");
|
||||
ApiKeyManager::handle(&app).update(&mut app, |api_key_manager, ctx| {
|
||||
api_key_manager.add_custom_endpoint(
|
||||
"local".to_string(),
|
||||
"https://example.com/v1".to_string(),
|
||||
"test-key".to_string(),
|
||||
vec![(
|
||||
"custom-model".to_string(),
|
||||
Some("Custom Model".to_string()),
|
||||
Some(custom_model_id.to_string()),
|
||||
)],
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
|
||||
let default_profile_id =
|
||||
profiles_model.read(&app, |profiles, _| profiles.default_profile_id());
|
||||
profiles_model.update(&mut app, |profiles, ctx| {
|
||||
profiles.set_base_model(default_profile_id, Some(custom_model_id.clone()), ctx);
|
||||
profiles.set_coding_model(default_profile_id, Some(custom_model_id.clone()), ctx);
|
||||
profiles.set_cli_agent_model(default_profile_id, Some(custom_model_id.clone()), ctx);
|
||||
});
|
||||
|
||||
llm_preferences.update(&mut app, |preferences, ctx| {
|
||||
preferences.update_feature_model_choices(Ok(ModelsByFeature::default()), ctx);
|
||||
});
|
||||
|
||||
profiles_model.read(&app, |profiles, ctx| {
|
||||
let profile = profiles.default_profile(ctx);
|
||||
assert_eq!(profile.data().base_model.as_ref(), Some(&custom_model_id));
|
||||
assert_eq!(profile.data().coding_model.as_ref(), Some(&custom_model_id));
|
||||
assert_eq!(
|
||||
profile.data().cli_agent_model.as_ref(),
|
||||
Some(&custom_model_id)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user