Ctrl K
ComunidadGuías…

internal/approvalgate/redeemed_call.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package approvalgate

import (
	"encoding/json"
	"errors"
	"strings"
	"sync"

	"github.com/yeomyeonggeori/blueprotocol/agentcontract"
	"github.com/yeomyeonggeori/blueprotocol/holdrecord"
	"github.com/yeomyeonggeori/blueprotocol/taskstate"
	"github.com/yeomyeonggeori/blueprotocol/toolcontract"
)

const TaskEventApprovalHoldRedeemed = "approval.hold_redeemed"

type HeldCallRedemption struct {
	TaskRunID         string          `json:"taskRunID"`
	HoldID            string          `json:"holdID"`
	RequesterPersonID string          `json:"requesterPersonID"`
	ToolName          string          `json:"toolName"`
	ToolInput         json.RawMessage `json:"toolInput"`
}

var (
	ErrHoldBelongsToNoRun      = errors.New("no run holds this call")
	ErrHoldIsAnotherPersons    = errors.New("the run that holds this call was asked for by someone else")
	ErrHoldIsNotInThisRun      = errors.New("this run holds no call by that id")
	ErrHoldWasNotApproved      = errors.New("the person who asked never approved this call")
	ErrHoldWasRedeemedAlready  = errors.New("this approval was already carried out once")
	ErrHoldApprovedAnotherCall = errors.New("this approval was for a different call")
	redeemingOneHoldAtATime    sync.Mutex
)

func RedeemHeldCall(taskRunStore taskstate.TaskRunStore, redemption HeldCallRedemption) error {
	redeemingOneHoldAtATime.Lock()
	defer redeemingOneHoldAtATime.Unlock()
	taskRunID := strings.TrimSpace(redemption.TaskRunID)
	taskRun, isFound := taskRunStore.FindTaskRun(taskRunID)
	if !isFound || taskRunID == "" {
		return ErrHoldBelongsToNoRun
	}
	if taskRun.RequesterPersonID == "" || taskRun.RequesterPersonID != strings.TrimSpace(redemption.RequesterPersonID) {
		return ErrHoldIsAnotherPersons
	}
	taskEvents := taskRunStore.ListTaskEvent(taskRunID)
	if errorValue := redeemableHold(taskEvents, redemption); errorValue != nil {
		return errorValue
	}
	taskRunStore.AppendTaskEvent(taskRunID, TaskEventApprovalHoldRedeemed, marshalEventBody(map[string]any{
		"holdID":    redemption.HoldID,
		"toolName":  toolcontract.CanonicalToolName(redemption.ToolName),
		"toolInput": json.RawMessage(agentcontract.CanonicalToolInput(redemption.ToolInput)),
	}))
	return nil
}

func redeemableHold(taskEvents []agentcontract.TaskEvent, redemption HeldCallRedemption) error {
	held, isHeld := holdByID(holdrecord.Holds(taskEvents), redemption.HoldID)
	if !isHeld {
		return ErrHoldIsNotInThisRun
	}
	if !wasApproved(taskEvents, held.ID) {
		return ErrHoldWasNotApproved
	}
	if wasRedeemed(taskEvents, held.ID) {
		return ErrHoldWasRedeemedAlready
	}
	if !isTheApprovedCall(held, redemption) {
		return ErrHoldApprovedAnotherCall
	}
	return nil
}

func holdByID(holds []holdrecord.Hold, holdID string) (holdrecord.Hold, bool) {
	trimmedHoldID := strings.TrimSpace(holdID)
	for _, held := range holds {
		if trimmedHoldID != "" && held.ID == trimmedHoldID {
			return held, true
		}
	}
	return holdrecord.Hold{}, false
}

func wasApproved(taskEvents []agentcontract.TaskEvent, holdID string) bool {
	for _, taskEvent := range taskEvents {
		if taskEvent.Name != agentcontract.TaskEventApprovalDecided {
			continue
		}
		var decided struct {
			HoldID   string `json:"holdID"`
			Decision string `json:"decision"`
		}
		if json.Unmarshal([]byte(taskEvent.Body), &decided) == nil && decided.HoldID == holdID {
			return decided.Decision == holdrecord.DecisionApprove
		}
	}
	return false
}

func wasRedeemed(taskEvents []agentcontract.TaskEvent, holdID string) bool {
	for _, taskEvent := range taskEvents {
		var redeemed struct {
			HoldID string `json:"holdID"`
		}
		if taskEvent.Name == TaskEventApprovalHoldRedeemed && json.Unmarshal([]byte(taskEvent.Body), &redeemed) == nil && redeemed.HoldID == holdID {
			return true
		}
	}
	return false
}

func isTheApprovedCall(held holdrecord.Hold, redemption HeldCallRedemption) bool {
	if toolcontract.CanonicalToolName(held.Call.ToolName) != toolcontract.CanonicalToolName(redemption.ToolName) {
		return false
	}
	return agentcontract.CanonicalToolInput(held.Call.ApprovedInput()) == agentcontract.CanonicalToolInput(redemption.ToolInput)
}