Files
Local-Cursor/internal/backend/agent/protocol/inbound.go
FuDie0915 db73cf5a39 chore: import Local-Cursor v1.1.8 source snapshot
Includes the current working directory changes. Historical commits and release binaries are excluded to limit Gitea storage.
Source base: d7515973fbfd6dd2001fa156c483455b8081c7cb
2026-11-10 01:24:28 +08:00

225 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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]) + "..."
}