重构: String→newtype 强类型 + 工程清理
- df-types: ExecutionId/ToolCallType 从 type 别名改为 newtype 结构体 - df-ai-core: 新增 ToolType newtype / MessageStatus 枚举, ChatMessage.status 从 Option<String> 改为 Option<MessageStatus> - provider: is_active() 改用 MessageStatus::Active 模式匹配 - context/chat/conversation: 构造站点更新为枚举变体 - .gitignore: 添加分析脚本排除项,清理根目录临时文件 - Batch.md: 更新最新提交与新增 Batch 37 记录
This commit is contained in:
@@ -95,14 +95,14 @@ impl ChatMessage {
|
||||
/// 无需每加一个状态就来这里改。当前取值 None/Some("active")/Some("truncated")
|
||||
/// 行为与旧反面排除完全等价(None=true / "active"=true / "truncated"=false)。
|
||||
pub fn is_active(&self) -> bool {
|
||||
matches!(self.status.as_deref(), None | Some("active"))
|
||||
matches!(self.status, None | Some(MessageStatus::Active))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolDefinition {
|
||||
pub fn function(name: impl Into<String>, description: impl Into<String>, parameters: serde_json::Value) -> Self {
|
||||
Self {
|
||||
tool_type: "function".into(),
|
||||
tool_type: ToolType::new("function"),
|
||||
function: ToolFunction { name: name.into(), description: description.into(), parameters },
|
||||
}
|
||||
}
|
||||
@@ -166,22 +166,22 @@ mod tests {
|
||||
|
||||
// "active"
|
||||
let mut m = ChatMessage::user("hi");
|
||||
m.status = Some("active".to_string());
|
||||
m.status = Some(MessageStatus::Active);
|
||||
assert!(m.is_active(), "Some(active) 应 active");
|
||||
|
||||
// "truncated" — 当前取值,与旧实现等价(false)
|
||||
let mut m = ChatMessage::user("hi");
|
||||
m.status = Some("truncated".to_string());
|
||||
m.status = Some(MessageStatus::Truncated);
|
||||
assert!(!m.is_active(), "truncated 应不 active");
|
||||
|
||||
// "archived_segment" — 阶段2 待引入,白名单自动隔离
|
||||
let mut m = ChatMessage::user("hi");
|
||||
m.status = Some("archived_segment".to_string());
|
||||
m.status = Some(MessageStatus::ArchivedSegment);
|
||||
assert!(!m.is_active(), "archived_segment 应不 active(白名单隔离)");
|
||||
|
||||
// "compressed" — 阶段2 待引入,白名单自动隔离
|
||||
let mut m = ChatMessage::user("hi");
|
||||
m.status = Some("compressed".to_string());
|
||||
m.status = Some(MessageStatus::Compressed);
|
||||
assert!(!m.is_active(), "compressed 应不 active(白名单隔离)");
|
||||
}
|
||||
|
||||
|
||||
@@ -103,11 +103,11 @@ pub struct ChatMessage {
|
||||
/// 生成该消息的 model(仅 assistant 消息有,消息级 model 追溯)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub model: Option<String>,
|
||||
/// 消息状态(UX-09 编辑重生成):None/"active" 正常可见;
|
||||
/// 消息状态(UX-09 编辑重生成):None 正常可见;
|
||||
/// "truncated" 软删(编辑某条 user 消息后其后续消息标记,保留 DB 可追溯但不进 LLM 上下文、前端视图过滤)
|
||||
/// 默认 None(向前兼容老 JSON 反序列化)。落库随 messages JSON 序列化,无需独立列。
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub status: Option<String>,
|
||||
pub status: Option<MessageStatus>,
|
||||
/// DeepSeek thinking 模式的推理内容(多轮需回传)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub reasoning_content: Option<String>,
|
||||
@@ -148,11 +148,91 @@ pub enum MessageRole {
|
||||
Tool,
|
||||
}
|
||||
|
||||
/// 消息状态枚举(IPC 边界序列化为小写 snake_case 字符串)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MessageStatus {
|
||||
/// 正常可见
|
||||
Active,
|
||||
/// 软删(编辑某条 user 消息后其后续消息标记)
|
||||
Truncated,
|
||||
/// 已压缩
|
||||
Compressed,
|
||||
/// 已归档段
|
||||
ArchivedSegment,
|
||||
}
|
||||
|
||||
impl MessageStatus {
|
||||
/// DB 存储用的小写 snake_case 字符串
|
||||
pub fn as_db_str(&self) -> &'static str {
|
||||
match self {
|
||||
MessageStatus::Active => "active",
|
||||
MessageStatus::Truncated => "truncated",
|
||||
MessageStatus::Compressed => "compressed",
|
||||
MessageStatus::ArchivedSegment => "archived_segment",
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 DB 字符串解析
|
||||
pub fn from_db_str(s: &str) -> Option<Self> {
|
||||
Some(match s {
|
||||
"active" => MessageStatus::Active,
|
||||
"truncated" => MessageStatus::Truncated,
|
||||
"compressed" => MessageStatus::Compressed,
|
||||
"archived_segment" => MessageStatus::ArchivedSegment,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ChatMessage 结构体
|
||||
/// 工具类型(IPC 边界透明序列化为字符串,如 "function")
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(transparent)]
|
||||
pub struct ToolType(String);
|
||||
|
||||
impl ToolType {
|
||||
/// 构造新工具类型
|
||||
pub fn new(s: impl Into<String>) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ToolType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ToolType {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ToolType {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for ToolType {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for ToolType {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.0 == other
|
||||
}
|
||||
}
|
||||
|
||||
/// 工具定义
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolDefinition {
|
||||
#[serde(rename = "type")]
|
||||
pub tool_type: String,
|
||||
pub tool_type: ToolType,
|
||||
pub function: ToolFunction,
|
||||
}
|
||||
|
||||
@@ -169,7 +249,7 @@ pub struct ToolFunction {
|
||||
pub struct ToolCall {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub call_type: String,
|
||||
pub call_type: ToolType,
|
||||
pub function: ToolCallFunction,
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ fn push_token_only_active() {
|
||||
let active_msg = ChatMessage::user("这条是 active 的");
|
||||
let active_tokens = TokenEstimator::default().estimate_message(&active_msg);
|
||||
let mut inactive_msg = ChatMessage::assistant("这条被截断了不该计 token");
|
||||
inactive_msg.status = Some("truncated".to_string());
|
||||
inactive_msg.status = Some(MessageStatus::Truncated);
|
||||
let inactive_tokens = TokenEstimator::default().estimate_message(&inactive_msg);
|
||||
|
||||
mgr.push(active_msg);
|
||||
@@ -210,11 +210,11 @@ fn push_token_only_active() {
|
||||
// 2) restore_from_messages 路径(调 push,token 同步仅 active)
|
||||
let mut mgr2 = ContextManager::new(cfg(100_000));
|
||||
let mut a = ChatMessage::user("active 一");
|
||||
a.status = Some("active".to_string());
|
||||
a.status = Some(MessageStatus::Active);
|
||||
let mut b = ChatMessage::user("archived 一");
|
||||
b.status = Some("archived_segment".to_string());
|
||||
b.status = Some(MessageStatus::ArchivedSegment);
|
||||
let mut c = ChatMessage::user("compressed 一");
|
||||
c.status = Some("compressed".to_string());
|
||||
c.status = Some(MessageStatus::Compressed);
|
||||
mgr2.restore_from_messages(vec![a, b, c]);
|
||||
|
||||
assert_eq!(mgr2.len(), 3, "restore 后全量保留三条");
|
||||
@@ -330,7 +330,7 @@ fn compress_old_messages_skips_already_inactive() {
|
||||
// 范围内含 truncated(已 !active)的消息:跳过,不返,不重复扣 token。
|
||||
let mut mgr = ContextManager::new(cfg(100_000));
|
||||
let mut truncated = ChatMessage::user("被截断");
|
||||
truncated.status = Some("truncated".to_string());
|
||||
truncated.status = Some(MessageStatus::Truncated);
|
||||
mgr.push(truncated);
|
||||
mgr.push(ChatMessage::user("active 一条"));
|
||||
let tokens_before = mgr.history_tokens();
|
||||
@@ -397,7 +397,7 @@ fn insert_at_adds_to_budget_when_active() {
|
||||
// 插入 !active 消息 → 不计入 token
|
||||
let tokens_before_inactive = mgr.history_tokens();
|
||||
let mut inactive = ChatMessage::user("x");
|
||||
inactive.status = Some("truncated".to_string());
|
||||
inactive.status = Some(MessageStatus::Truncated);
|
||||
mgr.insert_at(0, inactive);
|
||||
assert_eq!(
|
||||
mgr.history_tokens(),
|
||||
|
||||
@@ -31,7 +31,7 @@ pub use crate::context_helpers::{
|
||||
EvictionUnit, ContextConfig, MessageGroup, TokenEstimator, TrackedMessage,
|
||||
};
|
||||
|
||||
use crate::provider::{ChatMessage, MessageRole};
|
||||
use crate::provider::{ChatMessage, MessageRole, MessageStatus};
|
||||
|
||||
// ============================================================
|
||||
// 上下文管理器
|
||||
@@ -321,7 +321,7 @@ impl ContextManager {
|
||||
let mut count = 0usize;
|
||||
for t in self.messages[i + 1..].iter_mut() {
|
||||
if t.message.is_active() {
|
||||
t.message.status = Some("truncated".to_string());
|
||||
t.message.status = Some(MessageStatus::Truncated);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
@@ -513,7 +513,7 @@ impl ContextManager {
|
||||
for t in self.messages[..end].iter_mut() {
|
||||
if t.message.is_active() {
|
||||
newly_compressed.push(t.message.clone());
|
||||
t.message.status = Some("compressed".to_string());
|
||||
t.message.status = Some(MessageStatus::Compressed);
|
||||
self.history_tokens = self.history_tokens.saturating_sub(t.token_count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,68 @@ pub type ReleaseId = String;
|
||||
pub type BranchId = String;
|
||||
/// 插件 ID
|
||||
pub type PluginId = String;
|
||||
/// 执行 ID
|
||||
pub type ExecutionId = String;
|
||||
/// 执行 ID(IPC 边界透明序列化为字符串)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct ExecutionId(String);
|
||||
|
||||
impl ExecutionId {
|
||||
/// 构造新执行 ID
|
||||
pub fn new(s: impl Into<String>) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExecutionId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ExecutionId {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ExecutionId {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ExecutionId {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExecutionId> for String {
|
||||
fn from(id: ExecutionId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
// ExecutionId 与 &str 的比较(测试/匹配中大量使用)
|
||||
impl PartialEq<&str> for ExecutionId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for ExecutionId {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.0 == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for ExecutionId {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
/// 决策 ID
|
||||
pub type DecisionId = String;
|
||||
/// 节点类型(如 ai / human / ai_self_review)
|
||||
@@ -31,7 +91,47 @@ pub type NodeType = String;
|
||||
/// 任务链接类型(如 depends_on / blocks / relates_to)
|
||||
pub type LinkType = String;
|
||||
/// 工具调用类型(如 function)
|
||||
pub type ToolCallType = String;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct ToolCallType(String);
|
||||
|
||||
impl ToolCallType {
|
||||
/// 构造新工具调用类型
|
||||
pub fn new(s: impl Into<String>) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ToolCallType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ToolCallType {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ToolCallType {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ToolCallType {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToolCallType> for String {
|
||||
fn from(t: ToolCallType) -> Self {
|
||||
t.0
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 时间工具
|
||||
|
||||
Reference in New Issue
Block a user