修复(LLM目录契约): protocol 缺省改为按 agent_mode 推导

- LlmModelSummary/LlmUnavailableModel 反序列化缺失 protocol 时取 agent_mode 的默认协议,不再一律 openai_responses
- 补 cc / anthropic 别名 / 显式 protocol 用例;同步生成绑定注释
This commit is contained in:
2026-10-05 12:18:05 +08:00
parent 7579213bea
commit c7150b590c
3 changed files with 106 additions and 5 deletions
+1 -1
View File
@@ -11,6 +11,6 @@ export type LlmModelSummary = { displayName: string, id: string,
*/
agentMode: AgcAgentMode,
/**
* 上游线协议;旧服务端缺字段时按 `openai_responses` 兼容。
* 上游线协议;旧服务端缺字段时按 `agent_mode` 的默认协议兼容。
*/
protocol: AgcModelProtocol, };
@@ -6,4 +6,8 @@ import type { LlmModelUnavailableReason } from "./LlmModelUnavailableReason";
/**
* 目录里存在、但当前账号不可用的一条模型。字段与 [`LlmModelSummary`] 对齐,另带原因。
*/
export type LlmUnavailableModel = { displayName: string, id: string, agentMode: AgcAgentMode, protocol: AgcModelProtocol, reason: LlmModelUnavailableReason, };
export type LlmUnavailableModel = { displayName: string, id: string, agentMode: AgcAgentMode,
/**
* 上游线协议;旧服务端缺字段时按 `agent_mode` 的默认协议兼容。
*/
protocol: AgcModelProtocol, reason: LlmModelUnavailableReason, };
@@ -124,9 +124,35 @@ impl AgcModelProtocol {
}
}
/// 反序列化中间态:`protocol` 缺失时按 `agent_mode` 的默认协议补齐(见 `From` 实现)。
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LlmModelSummaryWire {
display_name: String,
id: String,
#[serde(default)]
agent_mode: AgcAgentMode,
#[serde(default)]
protocol: Option<AgcModelProtocol>,
}
impl From<LlmModelSummaryWire> for LlmModelSummary {
fn from(wire: LlmModelSummaryWire) -> Self {
let protocol = wire
.protocol
.unwrap_or_else(|| wire.agent_mode.default_protocol());
Self {
display_name: wire.display_name,
id: wire.id,
agent_mode: wire.agent_mode,
protocol,
}
}
}
/// 本档可用的一条模型摘要。不包含上游真实模型名。
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "camelCase", from = "LlmModelSummaryWire")]
#[cfg_attr(feature = "ts-bindings", derive(ts_rs::TS))]
#[cfg_attr(
feature = "ts-bindings",
@@ -144,14 +170,42 @@ pub struct LlmModelSummary {
/// 执行器;旧服务端缺字段时按 `codex` 兼容。
#[serde(default)]
pub agent_mode: AgcAgentMode,
/// 上游线协议;旧服务端缺字段时按 `openai_responses` 兼容。
/// 上游线协议;旧服务端缺字段时按 `agent_mode` 的默认协议兼容。
#[serde(default)]
pub protocol: AgcModelProtocol,
}
/// 反序列化中间态:与 [`LlmModelSummaryWire`] 同规则,额外携带 `reason`。
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LlmUnavailableModelWire {
display_name: String,
id: String,
#[serde(default)]
agent_mode: AgcAgentMode,
#[serde(default)]
protocol: Option<AgcModelProtocol>,
reason: LlmModelUnavailableReason,
}
impl From<LlmUnavailableModelWire> for LlmUnavailableModel {
fn from(wire: LlmUnavailableModelWire) -> Self {
let protocol = wire
.protocol
.unwrap_or_else(|| wire.agent_mode.default_protocol());
Self {
display_name: wire.display_name,
id: wire.id,
agent_mode: wire.agent_mode,
protocol,
reason: wire.reason,
}
}
}
/// 目录里存在、但当前账号不可用的一条模型。字段与 [`LlmModelSummary`] 对齐,另带原因。
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "camelCase", from = "LlmUnavailableModelWire")]
#[cfg_attr(feature = "ts-bindings", derive(ts_rs::TS))]
#[cfg_attr(
feature = "ts-bindings",
@@ -168,6 +222,7 @@ pub struct LlmUnavailableModel {
pub id: String,
#[serde(default)]
pub agent_mode: AgcAgentMode,
/// 上游线协议;旧服务端缺字段时按 `agent_mode` 的默认协议兼容。
#[serde(default)]
pub protocol: AgcModelProtocol,
pub reason: LlmModelUnavailableReason,
@@ -224,3 +279,45 @@ pub struct LlmModelsResponse {
#[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
pub revision: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_protocol_follows_agent_mode() {
let cc: LlmModelSummary =
serde_json::from_str(r#"{"displayName":"Claude","id":"model-a","agentMode":"cc"}"#)
.unwrap();
assert_eq!(cc.protocol, AgcModelProtocol::Anthropic);
let legacy_alias: LlmModelSummary = serde_json::from_str(
r#"{"displayName":"Claude","id":"model-a","agentMode":"anthropic"}"#,
)
.unwrap();
assert_eq!(legacy_alias.agent_mode, AgcAgentMode::Cc);
assert_eq!(legacy_alias.protocol, AgcModelProtocol::Anthropic);
let codex: LlmModelSummary =
serde_json::from_str(r#"{"displayName":"GPT","id":"model-b"}"#).unwrap();
assert_eq!(codex.agent_mode, AgcAgentMode::Codex);
assert_eq!(codex.protocol, AgcModelProtocol::OpenAiResponses);
// 显式 protocol 优先,不被 agent_mode 覆盖。
let explicit: LlmModelSummary = serde_json::from_str(
r#"{"displayName":"GPT","id":"model-c","agentMode":"codex","protocol":"openai_chat"}"#,
)
.unwrap();
assert_eq!(explicit.protocol, AgcModelProtocol::OpenAiChat);
}
#[test]
fn unavailable_model_missing_protocol_follows_agent_mode() {
let cc: LlmUnavailableModel = serde_json::from_str(
r#"{"displayName":"Claude","id":"model-a","agentMode":"cc","reason":"disabled"}"#,
)
.unwrap();
assert_eq!(cc.protocol, AgcModelProtocol::Anthropic);
assert_eq!(cc.reason, LlmModelUnavailableReason::Disabled);
}
}