修复(LLM目录契约): 响应枚举未知取值前向兼容
- LlmModelsResponse 内 agentMode / protocol 遇到未知 token 回退 Codex / 由 agentMode 推导默认协议,不再让旧客户端整份反序列化失败 - 输入校验仍走严格 parse;补未知 token 用例
This commit is contained in:
@@ -124,15 +124,44 @@ impl AgcModelProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// 响应 DTO 的前向兼容:未知执行器回退 `Codex`,避免后端新增取值时旧客户端整份
|
||||
/// [`LlmModelsResponse`] 反序列化失败。写入 / 输入校验仍走严格的 [`AgcAgentMode::parse`]。
|
||||
fn deserialize_forward_compat_agent_mode<'de, D>(deserializer: D) -> Result<AgcAgentMode, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let raw = Option::<String>::deserialize(deserializer)?;
|
||||
Ok(match raw.as_deref() {
|
||||
Some("cc") | Some("anthropic") => AgcAgentMode::Cc,
|
||||
_ => AgcAgentMode::Codex,
|
||||
})
|
||||
}
|
||||
|
||||
/// 响应 DTO 的前向兼容:未知协议回退 `None`,由 `agent_mode` 的默认协议补齐。
|
||||
fn deserialize_forward_compat_protocol<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<AgcModelProtocol>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let raw = Option::<String>::deserialize(deserializer)?;
|
||||
Ok(match raw.as_deref() {
|
||||
Some("openai_responses") => Some(AgcModelProtocol::OpenAiResponses),
|
||||
Some("openai_chat") => Some(AgcModelProtocol::OpenAiChat),
|
||||
Some("anthropic") => Some(AgcModelProtocol::Anthropic),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// 反序列化中间态:`protocol` 缺失时按 `agent_mode` 的默认协议补齐(见 `From` 实现)。
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct LlmModelSummaryWire {
|
||||
display_name: String,
|
||||
id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "deserialize_forward_compat_agent_mode")]
|
||||
agent_mode: AgcAgentMode,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "deserialize_forward_compat_protocol")]
|
||||
protocol: Option<AgcModelProtocol>,
|
||||
}
|
||||
|
||||
@@ -181,9 +210,9 @@ pub struct LlmModelSummary {
|
||||
struct LlmUnavailableModelWire {
|
||||
display_name: String,
|
||||
id: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "deserialize_forward_compat_agent_mode")]
|
||||
agent_mode: AgcAgentMode,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "deserialize_forward_compat_protocol")]
|
||||
protocol: Option<AgcModelProtocol>,
|
||||
reason: LlmModelUnavailableReason,
|
||||
}
|
||||
@@ -320,4 +349,23 @@ mod tests {
|
||||
assert_eq!(cc.protocol, AgcModelProtocol::Anthropic);
|
||||
assert_eq!(cc.reason, LlmModelUnavailableReason::Disabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_enum_tokens_fall_back_for_forward_compat() {
|
||||
// 后端将来新增执行器 / 协议时,旧客户端仍能解析整份响应,而不是 fail closed。
|
||||
let summary: LlmModelSummary = serde_json::from_str(
|
||||
r#"{"displayName":"未来模型","id":"model-x","agentMode":"future-mode","protocol":"future-proto"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(summary.agent_mode, AgcAgentMode::Codex);
|
||||
assert_eq!(summary.protocol, AgcModelProtocol::OpenAiResponses);
|
||||
|
||||
let unavailable: LlmUnavailableModel = serde_json::from_str(
|
||||
r#"{"displayName":"未来模型","id":"model-x","agentMode":"future-mode","protocol":"future-proto","reason":"future-reason"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(unavailable.agent_mode, AgcAgentMode::Codex);
|
||||
assert_eq!(unavailable.protocol, AgcModelProtocol::OpenAiResponses);
|
||||
assert_eq!(unavailable.reason, LlmModelUnavailableReason::Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user