mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 11:37:20 +08:00
456 lines
18 KiB
Go
456 lines
18 KiB
Go
package forwarder
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
|
|
"cursor/gen/agentv1"
|
|
execbridge "cursor/internal/backend/agent/bridge/exec"
|
|
runtimecore "cursor/internal/backend/agent/core"
|
|
)
|
|
|
|
const (
|
|
writeReadExecKind = "write_read"
|
|
writeWriteExecKind = "write_write"
|
|
writePostReadExecKind = "write_post_read"
|
|
)
|
|
|
|
type writeOperationArgs struct {
|
|
Path string `json:"path"`
|
|
Contents string `json:"contents"`
|
|
}
|
|
|
|
type pendingWritePayload struct {
|
|
VisibleArgs writeOperationArgs `json:"visible_args"`
|
|
ResolvedPath string `json:"resolved_path"`
|
|
BeforeContent string `json:"before_content,omitempty"`
|
|
AfterContent string `json:"after_content,omitempty"`
|
|
}
|
|
|
|
func isHiddenWriteExecKind(kind string) bool {
|
|
switch strings.TrimSpace(kind) {
|
|
case writeReadExecKind, writeWriteExecKind, writePostReadExecKind:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (service *Service) handleWriteToolInvocation(stream *ActiveStream, invocation runtimecore.ToolInvocation) error {
|
|
if stream == nil {
|
|
return fmt.Errorf("write stream is required")
|
|
}
|
|
|
|
writeArgs, err := decodeWriteOperationArgs(invocation.ArgsJSON)
|
|
if err != nil {
|
|
return newRecoverableToolInvocationError(err)
|
|
}
|
|
|
|
resolvedPath := strings.TrimSpace(writeArgs.Path)
|
|
writeArgs.Path = resolvedPath
|
|
|
|
visibleArgsJSON, err := writeArgs.MarshalJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
invocation.ArgsJSON = visibleArgsJSON
|
|
|
|
startedToolCall := buildStartedToolCall(invocation)
|
|
if startedToolCall != nil {
|
|
historyStartedToolCall := buildStartedWriteHistoryToolCall(writeArgs.Path)
|
|
toolCallPayload, err := protojson.Marshal(historyStartedToolCall)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = service.appendConversationEntries(stream, stream.ConversationID, []HistoryEntry{
|
|
newToolCallEntryWithProviderMetadata(stream.TurnSeq, stream.RequestID, invocation.CallID, invocation.ToolName, invocation.ReasoningContent, invocation.ReasoningSignature, invocation.ReasoningSignatureSource, invocation.ReasoningProviderItemID, invocation.ReasoningProviderStatus, invocation.ReasoningProviderSummary, invocation.ProviderItemID, invocation.ProviderCallID, invocation.ProviderStatus, toolCallPayload),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := service.broker.Publish(stream.RequestID, StreamEvent{
|
|
Message: buildToolCallStartedMessage(invocation.CallID, invocation.ModelCallID, startedToolCall),
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return service.startHiddenWriteRead(stream, invocation.CallID, invocation.ModelCallID, currentProviderPass(stream), invocation.ReasoningContent, invocation.ReasoningSignature, invocation.ReasoningSignatureSource, pendingWritePayload{
|
|
VisibleArgs: writeArgs,
|
|
ResolvedPath: resolvedPath,
|
|
})
|
|
}
|
|
|
|
func (service *Service) startHiddenWriteRead(stream *ActiveStream, toolCallID string, modelCallID string, providerPass int, reasoningContent string, reasoningSignature string, reasoningSignatureSource string, payload pendingWritePayload) error {
|
|
readArgsJSON, err := json.Marshal(map[string]any{
|
|
"path": strings.TrimSpace(payload.ResolvedPath),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serverMessage, pendingExec, err := service.execBridge.OpenExec(execbridge.OpenExecContext{
|
|
ConversationID: stream.ConversationID,
|
|
}, runtimecore.ToolInvocation{
|
|
CallID: strings.TrimSpace(toolCallID),
|
|
ToolName: "Read",
|
|
ArgsJSON: readArgsJSON,
|
|
ModelCallID: strings.TrimSpace(modelCallID),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pendingArgsJSON, err := payload.MarshalJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pendingExec.ModelCallID = strings.TrimSpace(modelCallID)
|
|
pendingExec.ProviderPass = providerPass
|
|
pendingExec.ToolCallID = strings.TrimSpace(toolCallID)
|
|
pendingExec.ReasoningContent = reasoningContent
|
|
pendingExec.ReasoningSignature = strings.TrimSpace(reasoningSignature)
|
|
pendingExec.ReasoningSignatureSource = strings.TrimSpace(reasoningSignatureSource)
|
|
pendingExec.ExecKind = writeReadExecKind
|
|
pendingExec.ArgsJSON = pendingArgsJSON
|
|
stream.mu.Lock()
|
|
stream.PendingExecs[pendingExec.ExecID] = pendingExec
|
|
stream.mu.Unlock()
|
|
if err := service.publishCheckpoint(stream.RequestID, stream.ConversationID); err != nil {
|
|
return err
|
|
}
|
|
return service.broker.Publish(stream.RequestID, StreamEvent{Message: serverMessage})
|
|
}
|
|
|
|
func (service *Service) startHiddenWriteExec(stream *ActiveStream, toolCallID string, modelCallID string, providerPass int, reasoningContent string, reasoningSignature string, reasoningSignatureSource string, payload pendingWritePayload, beforeContent string) error {
|
|
transportContents := prepareWriteContentsForClient(payload.ResolvedPath, payload.VisibleArgs.Contents)
|
|
writeArgsJSON, err := json.Marshal(map[string]any{
|
|
"path": strings.TrimSpace(payload.ResolvedPath),
|
|
"contents": transportContents,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serverMessage, pendingExec, err := service.execBridge.OpenExec(execbridge.OpenExecContext{
|
|
ConversationID: stream.ConversationID,
|
|
}, runtimecore.ToolInvocation{
|
|
CallID: strings.TrimSpace(toolCallID),
|
|
ToolName: "Write",
|
|
ArgsJSON: writeArgsJSON,
|
|
ModelCallID: strings.TrimSpace(modelCallID),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
payload.BeforeContent = beforeContent
|
|
payload.AfterContent = payload.VisibleArgs.Contents
|
|
pendingArgsJSON, err := payload.MarshalJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pendingExec.ModelCallID = strings.TrimSpace(modelCallID)
|
|
pendingExec.ProviderPass = providerPass
|
|
pendingExec.ToolCallID = strings.TrimSpace(toolCallID)
|
|
pendingExec.ReasoningContent = reasoningContent
|
|
pendingExec.ReasoningSignature = strings.TrimSpace(reasoningSignature)
|
|
pendingExec.ReasoningSignatureSource = strings.TrimSpace(reasoningSignatureSource)
|
|
pendingExec.ExecKind = writeWriteExecKind
|
|
pendingExec.ArgsJSON = pendingArgsJSON
|
|
stream.mu.Lock()
|
|
stream.PendingExecs[pendingExec.ExecID] = pendingExec
|
|
stream.mu.Unlock()
|
|
if err := service.publishCheckpoint(stream.RequestID, stream.ConversationID); err != nil {
|
|
return err
|
|
}
|
|
return service.broker.Publish(stream.RequestID, StreamEvent{Message: serverMessage})
|
|
}
|
|
|
|
func (service *Service) startHiddenWritePostRead(stream *ActiveStream, toolCallID string, modelCallID string, providerPass int, reasoningContent string, reasoningSignature string, reasoningSignatureSource string, payload pendingWritePayload) error {
|
|
readArgsJSON, err := json.Marshal(map[string]any{
|
|
"path": strings.TrimSpace(payload.ResolvedPath),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serverMessage, pendingExec, err := service.execBridge.OpenExec(execbridge.OpenExecContext{
|
|
ConversationID: stream.ConversationID,
|
|
}, runtimecore.ToolInvocation{
|
|
CallID: strings.TrimSpace(toolCallID),
|
|
ToolName: "Read",
|
|
ArgsJSON: readArgsJSON,
|
|
ModelCallID: strings.TrimSpace(modelCallID),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pendingArgsJSON, err := payload.MarshalJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pendingExec.ModelCallID = strings.TrimSpace(modelCallID)
|
|
pendingExec.ProviderPass = providerPass
|
|
pendingExec.ToolCallID = strings.TrimSpace(toolCallID)
|
|
pendingExec.ReasoningContent = reasoningContent
|
|
pendingExec.ReasoningSignature = strings.TrimSpace(reasoningSignature)
|
|
pendingExec.ReasoningSignatureSource = strings.TrimSpace(reasoningSignatureSource)
|
|
pendingExec.ExecKind = writePostReadExecKind
|
|
pendingExec.ArgsJSON = pendingArgsJSON
|
|
stream.mu.Lock()
|
|
stream.PendingExecs[pendingExec.ExecID] = pendingExec
|
|
stream.mu.Unlock()
|
|
if err := service.publishCheckpoint(stream.RequestID, stream.ConversationID); err != nil {
|
|
return err
|
|
}
|
|
return service.broker.Publish(stream.RequestID, StreamEvent{Message: serverMessage})
|
|
}
|
|
|
|
func (service *Service) handleHiddenWriteExecResult(stream *ActiveStream, pending runtimecore.PendingExec, message *agentv1.ExecClientMessage) error {
|
|
if stream == nil || message == nil {
|
|
return fmt.Errorf("write exec result requires stream and message")
|
|
}
|
|
payload, err := decodePendingWritePayload(pending.ArgsJSON)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch strings.TrimSpace(pending.ExecKind) {
|
|
case writeReadExecKind:
|
|
markExecCompleted(stream, pending)
|
|
readResult := message.GetReadResult()
|
|
if readResult == nil {
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, payload.VisibleArgs, buildEditErrorResult(payload.ResolvedPath, "write read result missing"))
|
|
}
|
|
switch readResult.GetResult().(type) {
|
|
case *agentv1.ReadResult_FileNotFound:
|
|
return service.startHiddenWriteExec(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, pending.ReasoningSignature, pending.ReasoningSignatureSource, payload, "")
|
|
default:
|
|
content, ok := extractReadContentForEdit(readResult)
|
|
if !ok {
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, payload.VisibleArgs, buildEditResultFromReadResult(payload.ResolvedPath, readResult))
|
|
}
|
|
return service.startHiddenWriteExec(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, pending.ReasoningSignature, pending.ReasoningSignatureSource, payload, content)
|
|
}
|
|
case writeWriteExecKind:
|
|
markExecCompleted(stream, pending)
|
|
writeResult := message.GetWriteResult()
|
|
if writeResult == nil {
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, payload.VisibleArgs, buildEditErrorResult(payload.ResolvedPath, "write result missing"))
|
|
}
|
|
switch item := writeResult.GetResult().(type) {
|
|
case *agentv1.WriteResult_Success:
|
|
payload.ResolvedPath = firstNonEmpty(strings.TrimSpace(item.Success.GetPath()), strings.TrimSpace(payload.ResolvedPath), strings.TrimSpace(payload.VisibleArgs.Path))
|
|
if item.Success.GetFileContentAfterWrite() != "" {
|
|
payload.AfterContent, _ = reconcilePostWriteObservedContent(payload.AfterContent, item.Success.GetFileContentAfterWrite())
|
|
}
|
|
payload.VisibleArgs.Path = payload.ResolvedPath
|
|
return service.startHiddenWritePostRead(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, pending.ReasoningSignature, pending.ReasoningSignatureSource, payload)
|
|
default:
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, payload.VisibleArgs, buildEditResultFromWriteResult(payload.ResolvedPath, writeResult))
|
|
}
|
|
case writePostReadExecKind:
|
|
markExecCompleted(stream, pending)
|
|
writeArgs := payload.VisibleArgs
|
|
writeArgs.Path = firstNonEmpty(strings.TrimSpace(payload.ResolvedPath), strings.TrimSpace(writeArgs.Path))
|
|
readResult := message.GetReadResult()
|
|
if readResult == nil {
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, writeArgs, buildSuccessfulWriteResult(writeArgs.Path, payload.BeforeContent, payload.AfterContent))
|
|
}
|
|
if content, ok := extractReadContentForEdit(readResult); ok {
|
|
if success := readResult.GetSuccess(); success != nil {
|
|
writeArgs.Path = firstNonEmpty(strings.TrimSpace(success.GetPath()), writeArgs.Path)
|
|
}
|
|
finalAfterContent, _ := reconcilePostWriteObservedContent(payload.AfterContent, content)
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, writeArgs, buildSuccessfulWriteResult(writeArgs.Path, payload.BeforeContent, finalAfterContent))
|
|
}
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, writeArgs, buildSuccessfulWriteResult(writeArgs.Path, payload.BeforeContent, payload.AfterContent))
|
|
default:
|
|
return fmt.Errorf("unsupported hidden write exec kind: %s", pending.ExecKind)
|
|
}
|
|
}
|
|
|
|
func (service *Service) handleHiddenWriteExecControl(stream *ActiveStream, pending runtimecore.PendingExec, message *agentv1.ExecClientControlMessage) error {
|
|
if stream == nil || message == nil {
|
|
return fmt.Errorf("write exec control requires stream and message")
|
|
}
|
|
if _, ok := message.GetMessage().(*agentv1.ExecClientControlMessage_Heartbeat); ok {
|
|
return nil
|
|
}
|
|
if _, ok := message.GetMessage().(*agentv1.ExecClientControlMessage_StreamClose); ok {
|
|
return nil
|
|
}
|
|
payload, err := decodePendingWritePayload(pending.ArgsJSON)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
markExecCompleted(stream, pending)
|
|
if strings.TrimSpace(pending.ExecKind) == writePostReadExecKind {
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, payload.VisibleArgs, buildSuccessfulWriteResult(payload.ResolvedPath, payload.BeforeContent, payload.AfterContent))
|
|
}
|
|
return service.finishWriteOperation(stream, pending.ToolCallID, pending.ModelCallID, pending.ProviderPass, pending.ReasoningContent, payload.VisibleArgs, buildEditErrorResult(payload.ResolvedPath, hiddenWriteControlError(message)))
|
|
}
|
|
|
|
func (service *Service) finishWriteOperation(stream *ActiveStream, toolCallID string, modelCallID string, providerPass int, reasoningContent string, writeArgs writeOperationArgs, result *agentv1.EditResult) error {
|
|
if stream == nil {
|
|
return nil
|
|
}
|
|
if result == nil {
|
|
result = buildEditErrorResult(writeArgs.Path, "write result missing")
|
|
}
|
|
writeArgs.Path = firstNonEmpty(resultPath(result), writeArgs.Path)
|
|
historyArgsJSON, err := writeArgs.MarshalJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
toolCall := buildCompletedWriteToolCall(writeArgs.Path, writeArgs.Contents, result)
|
|
historyToolCall := buildCompletedWriteHistoryToolCall(writeArgs.Path, result)
|
|
if err := service.appendToolResult(stream, strings.TrimSpace(toolCallID), "Write", historyArgsJSON, summarizeWriteHistoryResult(writeArgs.Path, result), reasoningContent, historyToolCall); err != nil {
|
|
return err
|
|
}
|
|
if err := service.publishToolCallCompleted(stream.RequestID, strings.TrimSpace(toolCallID), strings.TrimSpace(modelCallID), toolCall); err != nil {
|
|
return err
|
|
}
|
|
if err := service.syncSummaryCarryForward(stream.ConversationID, stream.RequestID, strings.TrimSpace(modelCallID)); err != nil {
|
|
return err
|
|
}
|
|
if err := service.publishCheckpoint(stream.RequestID, stream.ConversationID); err != nil {
|
|
return err
|
|
}
|
|
return service.reconcileStream(stream)
|
|
}
|
|
|
|
func decodeWriteOperationArgs(raw []byte) (writeOperationArgs, error) {
|
|
var args map[string]any
|
|
if err := json.Unmarshal(raw, &args); err != nil {
|
|
return writeOperationArgs{}, fmt.Errorf("decode write args failed: %w", err)
|
|
}
|
|
result := writeOperationArgs{
|
|
Path: firstNonEmpty(readStringAny(args["path"]), readStringAny(args["file_path"])),
|
|
Contents: firstNonEmpty(readStringAny(args["contents"]), readStringAny(args["content"]), readStringAny(args["stream_content"]), readStringAny(args["streamContent"])),
|
|
}
|
|
if strings.TrimSpace(result.Path) == "" {
|
|
return writeOperationArgs{}, fmt.Errorf("write path is required")
|
|
}
|
|
if !isAbsoluteToolPath(result.Path) {
|
|
return writeOperationArgs{}, fmt.Errorf("write path must be absolute")
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (args writeOperationArgs) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(map[string]any{
|
|
"path": strings.TrimSpace(args.Path),
|
|
"contents": args.Contents,
|
|
})
|
|
}
|
|
|
|
func (payload pendingWritePayload) MarshalJSON() ([]byte, error) {
|
|
type alias pendingWritePayload
|
|
return json.Marshal(alias(payload))
|
|
}
|
|
|
|
func decodePendingWritePayload(raw []byte) (pendingWritePayload, error) {
|
|
var payload pendingWritePayload
|
|
if err := json.Unmarshal(raw, &payload); err != nil {
|
|
return pendingWritePayload{}, fmt.Errorf("decode write pending payload failed: %w", err)
|
|
}
|
|
return payload, nil
|
|
}
|
|
|
|
func buildSuccessfulWriteResult(path string, beforeContent string, afterContent string) *agentv1.EditResult {
|
|
diffString, linesAdded, linesRemoved := computeEditDiff(beforeContent, afterContent)
|
|
return buildSuccessfulEditResult(path, beforeContent, afterContent, diffString, linesAdded, linesRemoved, "")
|
|
}
|
|
|
|
func buildCompletedWriteToolCall(path string, streamContent string, result *agentv1.EditResult) *agentv1.ToolCall {
|
|
return &agentv1.ToolCall{
|
|
Tool: &agentv1.ToolCall_EditToolCall{
|
|
EditToolCall: &agentv1.EditToolCall{
|
|
Args: &agentv1.EditArgs{
|
|
Path: strings.TrimSpace(path),
|
|
StreamContent: literalStringPtr(streamContent),
|
|
},
|
|
Result: result,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildStartedWriteHistoryToolCall(path string) *agentv1.ToolCall {
|
|
return &agentv1.ToolCall{
|
|
Tool: &agentv1.ToolCall_EditToolCall{
|
|
EditToolCall: &agentv1.EditToolCall{
|
|
Args: &agentv1.EditArgs{
|
|
Path: strings.TrimSpace(path),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildCompletedWriteHistoryToolCall(path string, result *agentv1.EditResult) *agentv1.ToolCall {
|
|
return buildCompletedEditToolCall(path, compactWriteHistoryEditResult(path, result))
|
|
}
|
|
|
|
func summarizeWriteHistoryResult(path string, result *agentv1.EditResult) string {
|
|
if result == nil {
|
|
return "write result missing"
|
|
}
|
|
success := result.GetSuccess()
|
|
if success == nil {
|
|
return summarizeEditResultWithoutPath(result)
|
|
}
|
|
encoded, err := json.Marshal(map[string]any{
|
|
"success": map[string]any{
|
|
"path": firstNonEmpty(success.GetPath(), path),
|
|
},
|
|
})
|
|
if err != nil {
|
|
return fmt.Sprintf(`{"success":{"path":%q}}`, firstNonEmpty(success.GetPath(), path))
|
|
}
|
|
return string(encoded)
|
|
}
|
|
|
|
func compactWriteHistoryEditResult(path string, result *agentv1.EditResult) *agentv1.EditResult {
|
|
if result == nil {
|
|
return buildEditErrorResult("", "write result missing")
|
|
}
|
|
success := result.GetSuccess()
|
|
if success == nil {
|
|
return editResultWithoutPath(result)
|
|
}
|
|
return &agentv1.EditResult{
|
|
Result: &agentv1.EditResult_Success{
|
|
Success: &agentv1.EditSuccess{
|
|
Path: firstNonEmpty(success.GetPath(), path),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func boundedWriteDiffString(diffString string) string {
|
|
return truncateProjectedReplayText("Write", diffString, projectedEditReplayLimit)
|
|
}
|
|
|
|
func resultPath(result *agentv1.EditResult) string {
|
|
if result == nil {
|
|
return ""
|
|
}
|
|
switch item := result.GetResult().(type) {
|
|
case *agentv1.EditResult_Success:
|
|
return strings.TrimSpace(item.Success.GetPath())
|
|
case *agentv1.EditResult_FileNotFound:
|
|
return strings.TrimSpace(item.FileNotFound.GetPath())
|
|
case *agentv1.EditResult_ReadPermissionDenied:
|
|
return strings.TrimSpace(item.ReadPermissionDenied.GetPath())
|
|
case *agentv1.EditResult_WritePermissionDenied:
|
|
return strings.TrimSpace(item.WritePermissionDenied.GetPath())
|
|
case *agentv1.EditResult_Rejected:
|
|
return strings.TrimSpace(item.Rejected.GetPath())
|
|
case *agentv1.EditResult_Error:
|
|
return strings.TrimSpace(item.Error.GetPath())
|
|
default:
|
|
return ""
|
|
}
|
|
}
|