fix: retire freefirst fast alias

This commit is contained in:
Agent Board
2026-07-13 09:37:03 +08:00
parent d6a25afa0a
commit d93319fe88
2 changed files with 47 additions and 14 deletions
+45 -12
View File
@@ -326,8 +326,9 @@ impl EffectiveAiRuntimePolicy {
requested_provider: Option<&str>,
requested_model_id: Option<&str>,
) -> Result<AiRuntimeResolvedModel, String> {
let default_ref = normalize_model_ref(None, &self.default_model)
.unwrap_or_else(|| normalize_model_ref(None, DEFAULT_PI_MODEL).expect("default model"));
let default_ref = canonical_ai_model_ref(None, &self.default_model).unwrap_or_else(|| {
canonical_ai_model_ref(None, DEFAULT_PI_MODEL).expect("default model")
});
let default_provider = default_ref
.split_once('/')
.map(|(provider, _)| provider.to_string())
@@ -340,7 +341,7 @@ impl EffectiveAiRuntimePolicy {
.filter(|value| !value.is_empty());
let model_ref = if let Some(model_id) = requested_model_id {
let normalized =
normalize_model_ref(requested_provider.or(Some(&default_provider)), model_id)
canonical_ai_model_ref(requested_provider.or(Some(&default_provider)), model_id)
.ok_or_else(|| "请求模型为空".to_string())?;
if let (Some(provider), Some((model_provider, _))) =
(requested_provider, normalized.split_once('/'))
@@ -358,7 +359,7 @@ impl EffectiveAiRuntimePolicy {
let allowed = self
.allowed_models
.iter()
.filter_map(|value| normalize_model_ref(Some(&default_provider), value))
.filter_map(|value| canonical_ai_model_ref(Some(&default_provider), value))
.any(|value| value == model_ref);
if !allowed {
return Err(format!("模型 {model_ref} 不在当前 AI 设置允许范围内"));
@@ -1180,6 +1181,7 @@ pub async fn admin_put_user_settings(
const DEFAULT_PI_MODEL: &str = "omniroute/gpt-5.4-mini";
const FREEFIRST_PI_MODEL: &str = "omniroute/freefirst";
const RETIRED_FREEFIRST_FAST_MODEL: &str = "omniroute/freefirst-fast";
const LIGHTRAG_PROVIDER: &str = "lightrag";
const LIGHTRAG_PROVIDER_DESCRIPTION: &str = "MNote 知识库默认提供者(LightRAG)";
const SOURCE_OF_TRUTH: &str = "directory_grants";
@@ -1820,17 +1822,20 @@ fn merge_effective_user_policy(global_policy: &Value, user_policy: &Value) -> Va
}
fn effective_default_model(model_policy: &Value) -> String {
model_policy
let configured = model_policy
.get("defaultModel")
.or_else(|| model_policy.get("default_model"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(DEFAULT_PI_MODEL)
.to_string()
.to_string();
canonical_ai_model_ref(None, &configured).unwrap_or(configured)
}
fn effective_models(model_policy: &Value, default_model: &str) -> Vec<ModelEntry> {
let default_model =
canonical_ai_model_ref(None, default_model).unwrap_or_else(|| DEFAULT_PI_MODEL.to_string());
let mut model_ids = model_policy
.get("allowedModels")
.or_else(|| model_policy.get("allowed_models"))
@@ -1838,14 +1843,18 @@ fn effective_models(model_policy: &Value, default_model: &str) -> Vec<ModelEntry
.into_iter()
.flatten()
.filter_map(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
.filter_map(|value| canonical_ai_model_ref(None, value))
.collect::<Vec<_>>();
if !model_ids.iter().any(|value| value == default_model) {
model_ids.insert(0, default_model.to_string());
if !model_ids.iter().any(|value| value == &default_model) {
model_ids.insert(0, default_model.clone());
}
model_ids
let mut deduped = Vec::with_capacity(model_ids.len());
for model_id in model_ids {
if !deduped.iter().any(|value| value == &model_id) {
deduped.push(model_id);
}
}
deduped
.into_iter()
.map(|id| {
let name = id.clone();
@@ -1880,6 +1889,14 @@ fn normalize_model_ref(default_provider: Option<&str>, value: &str) -> Option<St
Some(format!("{provider}/{trimmed}"))
}
fn canonical_ai_model_ref(default_provider: Option<&str>, value: &str) -> Option<String> {
let normalized = normalize_model_ref(default_provider, value)?;
if normalized == RETIRED_FREEFIRST_FAST_MODEL {
return Some(FREEFIRST_PI_MODEL.to_string());
}
Some(normalized)
}
fn pi_mcp_extension_bridge_enabled() -> bool {
std::env::var("MNOTE_PAGE_AI_PI_MCP_EXTENSION")
.ok()
@@ -2902,6 +2919,22 @@ mod tests {
assert!(models[0].is_default);
}
#[test]
fn effective_models_canonicalize_retired_freefirst_fast_alias() {
let policy = json!({
"defaultModel": "omniroute/freefirst-fast",
"allowedModels": ["omniroute/freefirst-fast", "omniroute/freefirst", "omniroute/gpt-5.4-mini"]
});
assert_eq!(effective_default_model(&policy), "omniroute/freefirst");
let models = effective_models(&policy, "omniroute/freefirst-fast");
let ids = models
.iter()
.map(|model| model.id.as_str())
.collect::<Vec<_>>();
assert_eq!(ids, vec!["omniroute/freefirst", "omniroute/gpt-5.4-mini"]);
assert!(models[0].is_default);
}
// ─── Admin body validation ────────────────────────────────────────────
#[test]
+2 -2
View File
@@ -105,7 +105,7 @@ async function main() {
await page.locator("#ai-admin-models.is-active").waitFor();
const modelPanel = page.locator("#ai-admin-models");
await modelPanel.locator('[data-field="allowedModels"]').fill(
"omniroute/freefirst, omniroute/freefirst-fast",
"omniroute/freefirst, omniroute/gpt-5.4-mini",
);
await modelPanel.locator('[data-field="secretRef"]').fill("env://OMNIROUTE_API_KEY");
await modelPanel.getByRole("button", { name: "保存配置" }).click();
@@ -250,7 +250,7 @@ async function main() {
});
assert.equal(effective.defaultModel, "omniroute/freefirst");
assert(
(effective.models || []).some((model) => model.id === "omniroute/freefirst-fast"),
(effective.models || []).some((model) => model.id === "omniroute/gpt-5.4-mini"),
"effective models 应包含管理员允许的模型",
);
assert(