feat(m174): 助手(AI)提供商配置真接线 + 「测试连接」
设置页新增第 6 组「助手」:提供商四态 / 端点 base_url / 模型名 / API 密钥(默认掩码, 点「显示」才明文)+ 保存 + 测试连接,四类失败(鉴权 / 模型不存在 / 不可达 / 超时)各自 一句文案 + 目标与耗时。 取舍三条: 1. 读写走 `core::setting` 的 schema API(`SettingStore::load` 现读 → `set` → `save`), 不经 `AppCommand::SetSetting`——那条指令是可序列化 DTO,`ai.api_key` 走它等于让明文进 事件流/日志。与 `SettingsBridge::persist` 写外观是同一条做法。 2. 「测试连接」不另写一套 HTTP:`unlucky_cli::commands::agent::probe` 现读设置文件、 交给 `RigProvider`(真 provider)发一次最小补全,带 8 秒时限;分类只看错误文本 (rig 只给 Display),四类各有一条真 TCP 假服务器用例钉住。 3. `ProviderKind` 补 `from_setting` / `setting_value`:serde 的 `rename_all = snake_case` 产出 `open_ai_compatible`,与 schema 的 `openai_compatible` 不是同一个词——这正是 M174 之前「agent 侧一次都没读过设置」的根因。 守卫(都做变异验证,见 mission 报告):密钥不出 `Debug`/`detail`/DTO;未配置时工厂构造 计数为 0;改完 base_url 后探测打到新地址;空/非法值被 schema 拦下且文件不动。 i18n:49 条中英各一(金表按 `Key::ALL` 位置插入);presenter 的 ai_group 补上 schema 允许 的 `disabled` 选项与一行「已配置 N 字符」的只读状态(仍然不显示值)。
This commit is contained in:
1 parent
82f3bbbff6
commit
9cb605f316
23 files changed
+2939
-73
No files matched your search
@@ -54,6 +54,35 @@ impl ProviderKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// 配置键 `ai.provider` 的取值 → 形态(M174)。
|
||||
///
|
||||
/// **不能用 serde**:`#[serde(rename_all = "snake_case")]` 把本枚举写成
|
||||
/// `open_ai_compatible`(见本文件末尾的用例),而 schema 的允许值表
|
||||
/// (`core::setting::keys::AI_PROVIDERS`)与设置页存的是 `openai_compatible`。两个词表必须在这里
|
||||
/// 显式对上,否则「设置里存的值」读不到——这正是 M174 之前 agent 侧一次都没读过设置的原因。
|
||||
/// 未知值给 `None`(由调用方回落默认,不猜)。
|
||||
#[must_use]
|
||||
pub fn from_setting(value: &str) -> Option<Self> {
|
||||
match value.trim() {
|
||||
"openai_compatible" => Some(Self::OpenAiCompatible),
|
||||
"anthropic" => Some(Self::Anthropic),
|
||||
"ollama" => Some(Self::Ollama),
|
||||
"disabled" => Some(Self::Disabled),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 本形态在 `ai.provider` 里的取值([`Self::from_setting`] 的逆;写回设置时用)。
|
||||
#[must_use]
|
||||
pub fn setting_value(self) -> &'static str {
|
||||
match self {
|
||||
Self::OpenAiCompatible => "openai_compatible",
|
||||
Self::Anthropic => "anthropic",
|
||||
Self::Ollama => "ollama",
|
||||
Self::Disabled => "disabled",
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置页展示名。
|
||||
#[must_use]
|
||||
pub fn display_name(self) -> &'static str {
|
||||
@@ -283,6 +312,37 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setting_vocabulary_round_trips() {
|
||||
// 设置里存的是 `openai_compatible`(schema 的 `AI_PROVIDERS`),不是 serde 的
|
||||
// `open_ai_compatible`:两个词表必须是这里这一对映射,否则「用户在设置页选的值」
|
||||
// agent 侧读出来是 None(M174 的根因)。
|
||||
for kind in [
|
||||
ProviderKind::OpenAiCompatible,
|
||||
ProviderKind::Anthropic,
|
||||
ProviderKind::Ollama,
|
||||
ProviderKind::Disabled,
|
||||
] {
|
||||
assert_eq!(ProviderKind::from_setting(kind.setting_value()), Some(kind));
|
||||
}
|
||||
assert_eq!(
|
||||
ProviderKind::from_setting(" ollama "),
|
||||
Some(ProviderKind::Ollama)
|
||||
);
|
||||
assert_eq!(ProviderKind::from_setting("open_ai_compatible"), None);
|
||||
assert_eq!(ProviderKind::from_setting(""), None);
|
||||
// schema 的允许值表与本枚举的取值表是**同一个集合**(一方加了另一方没加就红)。
|
||||
assert_eq!(
|
||||
unlucky_core::setting::keys::AI_PROVIDERS,
|
||||
[
|
||||
ProviderKind::OpenAiCompatible.setting_value(),
|
||||
ProviderKind::Anthropic.setting_value(),
|
||||
ProviderKind::Ollama.setting_value(),
|
||||
ProviderKind::Disabled.setting_value(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_requires_model_base_url_and_key() {
|
||||
assert!(!ProviderConfig::disabled().is_enabled());
|
||||
|
||||
Reference in new issue
Block a user