hank9999 commited on
Commit ·
8ca3e6d
1
Parent(s): 7439977
feat: 新增会话ID保持
Browse files- src/anthropic/converter.rs +121 -2
- src/anthropic/types.rs +9 -0
src/anthropic/converter.rs
CHANGED
|
@@ -59,6 +59,26 @@ impl std::fmt::Display for ConversionError {
|
|
| 59 |
|
| 60 |
impl std::error::Error for ConversionError {}
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
/// 收集历史消息中使用的所有工具名称
|
| 64 |
fn collect_history_tool_names(history: &[Message]) -> Vec<String> {
|
|
@@ -97,7 +117,6 @@ fn create_placeholder_tool(name: &str) -> Tool {
|
|
| 97 |
}
|
| 98 |
}
|
| 99 |
|
| 100 |
-
|
| 101 |
/// 将 Anthropic 请求转换为 Kiro 请求
|
| 102 |
pub fn convert_request(req: &MessagesRequest) -> Result<ConversionResult, ConversionError> {
|
| 103 |
// 1. 映射模型
|
|
@@ -110,7 +129,13 @@ pub fn convert_request(req: &MessagesRequest) -> Result<ConversionResult, Conver
|
|
| 110 |
}
|
| 111 |
|
| 112 |
// 3. 生成会话 ID 和代理 ID
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
let agent_continuation_id = Uuid::new_v4().to_string();
|
| 115 |
|
| 116 |
// 4. 确定触发类型
|
|
@@ -584,6 +609,7 @@ mod tests {
|
|
| 584 |
tools: None,
|
| 585 |
tool_choice: None,
|
| 586 |
thinking: None,
|
|
|
|
| 587 |
};
|
| 588 |
assert_eq!(determine_chat_trigger_type(&req), "MANUAL");
|
| 589 |
}
|
|
@@ -669,6 +695,7 @@ mod tests {
|
|
| 669 |
tools: None, // 没有提供工具定义
|
| 670 |
tool_choice: None,
|
| 671 |
thinking: None,
|
|
|
|
| 672 |
};
|
| 673 |
|
| 674 |
let result = convert_request(&req).unwrap();
|
|
@@ -687,4 +714,96 @@ mod tests {
|
|
| 687 |
"tools 列表应包含 'read' 工具的占位符定义"
|
| 688 |
);
|
| 689 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
}
|
|
|
|
| 59 |
|
| 60 |
impl std::error::Error for ConversionError {}
|
| 61 |
|
| 62 |
+
/// 从 metadata.user_id 中提取 session UUID
|
| 63 |
+
///
|
| 64 |
+
/// user_id 格式: user_xxx_account__session_0b4445e1-f5be-49e1-87ce-62bbc28ad705
|
| 65 |
+
/// 提取 session_ 后面的 UUID 作为 conversationId
|
| 66 |
+
fn extract_session_id(user_id: &str) -> Option<String> {
|
| 67 |
+
// 查找 "session_" 后面的内容
|
| 68 |
+
if let Some(pos) = user_id.find("session_") {
|
| 69 |
+
let session_part = &user_id[pos + 8..]; // "session_" 长度为 8
|
| 70 |
+
// session_part 应该是 UUID 格式: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
| 71 |
+
// 验证是否是有效的 UUID 格式(36 字符,包含 4 个连字符)
|
| 72 |
+
if session_part.len() >= 36 {
|
| 73 |
+
let uuid_str = &session_part[..36];
|
| 74 |
+
// 简单验证 UUID 格式
|
| 75 |
+
if uuid_str.chars().filter(|c| *c == '-').count() == 4 {
|
| 76 |
+
return Some(uuid_str.to_string());
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
None
|
| 81 |
+
}
|
| 82 |
|
| 83 |
/// 收集历史消息中使用的所有工具名称
|
| 84 |
fn collect_history_tool_names(history: &[Message]) -> Vec<String> {
|
|
|
|
| 117 |
}
|
| 118 |
}
|
| 119 |
|
|
|
|
| 120 |
/// 将 Anthropic 请求转换为 Kiro 请求
|
| 121 |
pub fn convert_request(req: &MessagesRequest) -> Result<ConversionResult, ConversionError> {
|
| 122 |
// 1. 映射模型
|
|
|
|
| 129 |
}
|
| 130 |
|
| 131 |
// 3. 生成会话 ID 和代理 ID
|
| 132 |
+
// 优先从 metadata.user_id 中提取 session UUID 作为 conversationId
|
| 133 |
+
let conversation_id = req
|
| 134 |
+
.metadata
|
| 135 |
+
.as_ref()
|
| 136 |
+
.and_then(|m| m.user_id.as_ref())
|
| 137 |
+
.and_then(|user_id| extract_session_id(user_id))
|
| 138 |
+
.unwrap_or_else(|| Uuid::new_v4().to_string());
|
| 139 |
let agent_continuation_id = Uuid::new_v4().to_string();
|
| 140 |
|
| 141 |
// 4. 确定触发类型
|
|
|
|
| 609 |
tools: None,
|
| 610 |
tool_choice: None,
|
| 611 |
thinking: None,
|
| 612 |
+
metadata: None,
|
| 613 |
};
|
| 614 |
assert_eq!(determine_chat_trigger_type(&req), "MANUAL");
|
| 615 |
}
|
|
|
|
| 695 |
tools: None, // 没有提供工具定义
|
| 696 |
tool_choice: None,
|
| 697 |
thinking: None,
|
| 698 |
+
metadata: None,
|
| 699 |
};
|
| 700 |
|
| 701 |
let result = convert_request(&req).unwrap();
|
|
|
|
| 714 |
"tools 列表应包含 'read' 工具的占位符定义"
|
| 715 |
);
|
| 716 |
}
|
| 717 |
+
|
| 718 |
+
#[test]
|
| 719 |
+
fn test_extract_session_id_valid() {
|
| 720 |
+
// 测试有效的 user_id 格式
|
| 721 |
+
let user_id = "user_0dede55c6dcc4a11a30bbb5e7f22e6fdf86cdeba3820019cc27612af4e1243cd_account__session_8bb5523b-ec7c-4540-a9ca-beb6d79f1552";
|
| 722 |
+
let session_id = extract_session_id(user_id);
|
| 723 |
+
assert_eq!(
|
| 724 |
+
session_id,
|
| 725 |
+
Some("8bb5523b-ec7c-4540-a9ca-beb6d79f1552".to_string())
|
| 726 |
+
);
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
#[test]
|
| 730 |
+
fn test_extract_session_id_no_session() {
|
| 731 |
+
// 测试没有 session 的 user_id
|
| 732 |
+
let user_id = "user_0dede55c6dcc4a11a30bbb5e7f22e6fdf86cdeba3820019cc27612af4e1243cd";
|
| 733 |
+
let session_id = extract_session_id(user_id);
|
| 734 |
+
assert_eq!(session_id, None);
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
#[test]
|
| 738 |
+
fn test_extract_session_id_invalid_uuid() {
|
| 739 |
+
// 测试无效的 UUID 格式
|
| 740 |
+
let user_id = "user_xxx_session_invalid-uuid";
|
| 741 |
+
let session_id = extract_session_id(user_id);
|
| 742 |
+
assert_eq!(session_id, None);
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
#[test]
|
| 746 |
+
fn test_convert_request_with_session_metadata() {
|
| 747 |
+
use super::super::types::{Message as AnthropicMessage, Metadata};
|
| 748 |
+
|
| 749 |
+
// 测试带有 metadata 的请求,应该使用 session UUID 作为 conversationId
|
| 750 |
+
let req = MessagesRequest {
|
| 751 |
+
model: "claude-sonnet-4".to_string(),
|
| 752 |
+
max_tokens: 1024,
|
| 753 |
+
messages: vec![AnthropicMessage {
|
| 754 |
+
role: "user".to_string(),
|
| 755 |
+
content: serde_json::json!("Hello"),
|
| 756 |
+
}],
|
| 757 |
+
stream: false,
|
| 758 |
+
system: None,
|
| 759 |
+
tools: None,
|
| 760 |
+
tool_choice: None,
|
| 761 |
+
thinking: None,
|
| 762 |
+
metadata: Some(Metadata {
|
| 763 |
+
user_id: Some(
|
| 764 |
+
"user_0dede55c6dcc4a11a30bbb5e7f22e6fdf86cdeba3820019cc27612af4e1243cd_account__session_a0662283-7fd3-4399-a7eb-52b9a717ae88".to_string(),
|
| 765 |
+
),
|
| 766 |
+
}),
|
| 767 |
+
};
|
| 768 |
+
|
| 769 |
+
let result = convert_request(&req).unwrap();
|
| 770 |
+
assert_eq!(
|
| 771 |
+
result.conversation_state.conversation_id,
|
| 772 |
+
"a0662283-7fd3-4399-a7eb-52b9a717ae88"
|
| 773 |
+
);
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
#[test]
|
| 777 |
+
fn test_convert_request_without_metadata() {
|
| 778 |
+
use super::super::types::Message as AnthropicMessage;
|
| 779 |
+
|
| 780 |
+
// 测试没有 metadata 的请求,应该生成新的 UUID
|
| 781 |
+
let req = MessagesRequest {
|
| 782 |
+
model: "claude-sonnet-4".to_string(),
|
| 783 |
+
max_tokens: 1024,
|
| 784 |
+
messages: vec![AnthropicMessage {
|
| 785 |
+
role: "user".to_string(),
|
| 786 |
+
content: serde_json::json!("Hello"),
|
| 787 |
+
}],
|
| 788 |
+
stream: false,
|
| 789 |
+
system: None,
|
| 790 |
+
tools: None,
|
| 791 |
+
tool_choice: None,
|
| 792 |
+
thinking: None,
|
| 793 |
+
metadata: None,
|
| 794 |
+
};
|
| 795 |
+
|
| 796 |
+
let result = convert_request(&req).unwrap();
|
| 797 |
+
// 验证生成的是有效的 UUID 格式
|
| 798 |
+
assert_eq!(result.conversation_state.conversation_id.len(), 36);
|
| 799 |
+
assert_eq!(
|
| 800 |
+
result
|
| 801 |
+
.conversation_state
|
| 802 |
+
.conversation_id
|
| 803 |
+
.chars()
|
| 804 |
+
.filter(|c| *c == '-')
|
| 805 |
+
.count(),
|
| 806 |
+
4
|
| 807 |
+
);
|
| 808 |
+
}
|
| 809 |
}
|
src/anthropic/types.rs
CHANGED
|
@@ -86,6 +86,13 @@ where
|
|
| 86 |
Ok(value.min(MAX_BUDGET_TOKENS))
|
| 87 |
}
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
/// Messages 请求体
|
| 90 |
#[derive(Debug, Deserialize)]
|
| 91 |
pub struct MessagesRequest {
|
|
@@ -98,6 +105,8 @@ pub struct MessagesRequest {
|
|
| 98 |
pub tools: Option<Vec<Tool>>,
|
| 99 |
pub tool_choice: Option<serde_json::Value>,
|
| 100 |
pub thinking: Option<Thinking>,
|
|
|
|
|
|
|
| 101 |
}
|
| 102 |
|
| 103 |
/// 消息
|
|
|
|
| 86 |
Ok(value.min(MAX_BUDGET_TOKENS))
|
| 87 |
}
|
| 88 |
|
| 89 |
+
/// Claude Code 请求中的 metadata
|
| 90 |
+
#[derive(Debug, Clone, Deserialize)]
|
| 91 |
+
pub struct Metadata {
|
| 92 |
+
/// 用户 ID,格式如: user_xxx_account__session_0b4445e1-f5be-49e1-87ce-62bbc28ad705
|
| 93 |
+
pub user_id: Option<String>,
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
/// Messages 请求体
|
| 97 |
#[derive(Debug, Deserialize)]
|
| 98 |
pub struct MessagesRequest {
|
|
|
|
| 105 |
pub tools: Option<Vec<Tool>>,
|
| 106 |
pub tool_choice: Option<serde_json::Value>,
|
| 107 |
pub thinking: Option<Thinking>,
|
| 108 |
+
/// Claude Code 请求中的 metadata,包含 session 信息
|
| 109 |
+
pub metadata: Option<Metadata>,
|
| 110 |
}
|
| 111 |
|
| 112 |
/// 消息
|