mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 11:37:20 +08:00
v0.3.8
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
// Package protocol 负责协议层解码、摘要与上行消息种类判断。
|
||||
package protocol
|
||||
@@ -0,0 +1,224 @@
|
||||
// inbound.go 实现上行协议的解码、摘要与命令类型识别。
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"cursor/gen/agentv1"
|
||||
"cursor/gen/aiserverv1"
|
||||
"cursor/internal/backend/agent/core"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ReadAppendRequestID 从 BidiAppendRequest 中读取 request_id 文本。
|
||||
func ReadAppendRequestID(input *aiserverv1.BidiAppendRequest) string {
|
||||
if input == nil {
|
||||
return ""
|
||||
}
|
||||
return ReadBidiRequestID(input.GetRequestId())
|
||||
}
|
||||
|
||||
// ReadBidiRequestID 从 BidiRequestId 结构中提取并去除首尾空白。
|
||||
func ReadBidiRequestID(input *aiserverv1.BidiRequestId) string {
|
||||
if input == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(input.GetRequestId())
|
||||
}
|
||||
|
||||
// NormalizeRequestID 规范化请求标识并去除首尾空白。
|
||||
func NormalizeRequestID(requestID string) string {
|
||||
return strings.TrimSpace(requestID)
|
||||
}
|
||||
|
||||
// DecodeAgentClientMessage 解析 hex 文本为 AgentClientMessage,并返回消息类型标签。
|
||||
func DecodeAgentClientMessage(hexData string) (*agentv1.AgentClientMessage, string, error) {
|
||||
trimmed := strings.TrimSpace(hexData)
|
||||
if trimmed == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
payload, err := hex.DecodeString(trimmed)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("bidi append data is not valid hex: %w", err)
|
||||
}
|
||||
clientMessage := &agentv1.AgentClientMessage{}
|
||||
if err := proto.Unmarshal(payload, clientMessage); err != nil {
|
||||
return nil, "", fmt.Errorf("decode agent client message failed: %w", err)
|
||||
}
|
||||
return clientMessage, detectClientMessageKind(clientMessage), nil
|
||||
}
|
||||
|
||||
// MapClientMessageToCommandKind 将上行协议消息映射为运行时命令类型。
|
||||
func MapClientMessageToCommandKind(message *agentv1.AgentClientMessage, clientKind string) (runtimecore.CommandKind, error) {
|
||||
switch strings.TrimSpace(clientKind) {
|
||||
case "run_request":
|
||||
return runtimecore.CommandKindRunRequested, nil
|
||||
case "prewarm_request":
|
||||
return runtimecore.CommandKindPrewarmRequested, nil
|
||||
case "conversation_action":
|
||||
if message == nil || message.GetConversationAction() == nil {
|
||||
return "", fmt.Errorf("conversation_action payload is required")
|
||||
}
|
||||
switch message.GetConversationAction().GetAction().(type) {
|
||||
case *agentv1.ConversationAction_CancelAction:
|
||||
return runtimecore.CommandKindCancelRequested, nil
|
||||
case *agentv1.ConversationAction_UserMessageAction,
|
||||
*agentv1.ConversationAction_ResumeAction,
|
||||
*agentv1.ConversationAction_SummarizeAction,
|
||||
*agentv1.ConversationAction_ShellCommandAction,
|
||||
*agentv1.ConversationAction_StartPlanAction,
|
||||
*agentv1.ConversationAction_ExecutePlanAction,
|
||||
*agentv1.ConversationAction_AsyncAskQuestionCompletionAction,
|
||||
*agentv1.ConversationAction_CancelSubagentAction,
|
||||
*agentv1.ConversationAction_BackgroundShellAction,
|
||||
*agentv1.ConversationAction_BackgroundTaskCompletionAction:
|
||||
return runtimecore.CommandKindConversationActionRecordOnly, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported conversation_action payload")
|
||||
}
|
||||
case "exec_client_message":
|
||||
return runtimecore.CommandKindExecClientMessage, nil
|
||||
case "interaction_response":
|
||||
return runtimecore.CommandKindInteractionResponse, nil
|
||||
case "exec_client_control_message":
|
||||
return runtimecore.CommandKindExecClientControlMessage, nil
|
||||
case "client_heartbeat":
|
||||
return runtimecore.CommandKindClientHeartbeat, nil
|
||||
case "kv_client_message":
|
||||
return runtimecore.CommandKindKVClientMessage, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported client message kind: %s", clientKind)
|
||||
}
|
||||
}
|
||||
|
||||
// IsResumeRunRequest 判断当前消息是否为带 `resume_action` 的 `run_request`。
|
||||
func IsResumeRunRequest(message *agentv1.AgentClientMessage) bool {
|
||||
if message == nil || message.GetRunRequest() == nil {
|
||||
return false
|
||||
}
|
||||
action := message.GetRunRequest().GetAction()
|
||||
if action == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := action.GetAction().(*agentv1.ConversationAction_ResumeAction)
|
||||
return ok
|
||||
}
|
||||
|
||||
// BuildClientHistoryEntry 将消息类型与负载摘要拼接成会话历史记录文本。
|
||||
func BuildClientHistoryEntry(kind string, message *agentv1.AgentClientMessage) string {
|
||||
normalizedKind := strings.TrimSpace(kind)
|
||||
if normalizedKind == "" {
|
||||
normalizedKind = "unknown"
|
||||
}
|
||||
|
||||
payload := extractClientMessagePayload(message)
|
||||
summary := summarizePayload(payload)
|
||||
if summary == "" {
|
||||
if normalizedKind == "unknown" {
|
||||
return ""
|
||||
}
|
||||
return normalizedKind
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", normalizedKind, summary)
|
||||
}
|
||||
|
||||
// detectClientMessageKind 判断 oneof message 当前承载的消息分支类型。
|
||||
func detectClientMessageKind(message *agentv1.AgentClientMessage) string {
|
||||
if message == nil || message.GetMessage() == nil {
|
||||
return ""
|
||||
}
|
||||
switch message.GetMessage().(type) {
|
||||
case *agentv1.AgentClientMessage_RunRequest:
|
||||
return "run_request"
|
||||
case *agentv1.AgentClientMessage_PrewarmRequest:
|
||||
return "prewarm_request"
|
||||
case *agentv1.AgentClientMessage_ConversationAction:
|
||||
return "conversation_action"
|
||||
case *agentv1.AgentClientMessage_ExecClientMessage:
|
||||
return "exec_client_message"
|
||||
case *agentv1.AgentClientMessage_InteractionResponse:
|
||||
return "interaction_response"
|
||||
case *agentv1.AgentClientMessage_ExecClientControlMessage:
|
||||
return "exec_client_control_message"
|
||||
case *agentv1.AgentClientMessage_ClientHeartbeat:
|
||||
return "client_heartbeat"
|
||||
case *agentv1.AgentClientMessage_KvClientMessage:
|
||||
return "kv_client_message"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// extractClientMessagePayload 从 oneof 分支中提取原始 bytes 负载。
|
||||
func extractClientMessagePayload(message *agentv1.AgentClientMessage) []byte {
|
||||
if message == nil || message.GetMessage() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch item := message.GetMessage().(type) {
|
||||
case *agentv1.AgentClientMessage_RunRequest:
|
||||
return marshalProtoMessage(item.RunRequest)
|
||||
case *agentv1.AgentClientMessage_PrewarmRequest:
|
||||
return marshalProtoMessage(item.PrewarmRequest)
|
||||
case *agentv1.AgentClientMessage_ConversationAction:
|
||||
return marshalProtoMessage(item.ConversationAction)
|
||||
case *agentv1.AgentClientMessage_ExecClientMessage:
|
||||
return marshalProtoMessage(item.ExecClientMessage)
|
||||
case *agentv1.AgentClientMessage_InteractionResponse:
|
||||
return marshalProtoMessage(item.InteractionResponse)
|
||||
case *agentv1.AgentClientMessage_ExecClientControlMessage:
|
||||
return marshalProtoMessage(item.ExecClientControlMessage)
|
||||
case *agentv1.AgentClientMessage_ClientHeartbeat:
|
||||
return marshalProtoMessage(item.ClientHeartbeat)
|
||||
case *agentv1.AgentClientMessage_KvClientMessage:
|
||||
return marshalProtoMessage(item.KvClientMessage)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// marshalProtoMessage 将 proto 消息重新编码为 bytes,用于调试摘要展示。
|
||||
func marshalProtoMessage(message proto.Message) []byte {
|
||||
if message == nil {
|
||||
return nil
|
||||
}
|
||||
payload, err := proto.Marshal(message)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
// summarizePayload 生成可读摘要:优先文本,无法直接读时回退为 hex 片段。
|
||||
func summarizePayload(payload []byte) string {
|
||||
if len(payload) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if utf8.Valid(payload) {
|
||||
text := strings.TrimSpace(string(payload))
|
||||
text = strings.ReplaceAll(text, "\n", " ")
|
||||
text = strings.ReplaceAll(text, "\r", " ")
|
||||
text = strings.TrimSpace(text)
|
||||
if text != "" {
|
||||
return truncateText(text, 120)
|
||||
}
|
||||
}
|
||||
return "hex:" + truncateText(hex.EncodeToString(payload), 120)
|
||||
}
|
||||
|
||||
// truncateText 按 rune 数截断文本,避免在多字节字符中间截断导致乱码。
|
||||
func truncateText(text string, maxRunes int) string {
|
||||
if maxRunes <= 0 {
|
||||
return ""
|
||||
}
|
||||
runes := []rune(text)
|
||||
if len(runes) <= maxRunes {
|
||||
return text
|
||||
}
|
||||
return string(runes[:maxRunes]) + "..."
|
||||
}
|
||||
Reference in New Issue
Block a user