Ctrl K
ComunidadGuías…

internal/approvalgate/target.go

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

Archivo original ↗Preguntar a KiBot
package approvalgate

import (
	"context"
	"encoding/json"
	"log/slog"
	"strings"

	"github.com/yeomyeonggeori/blueclaw/internal/mcpserver"
	"github.com/yeomyeonggeori/blueprotocol/agentcontract"
	"github.com/yeomyeonggeori/blueprotocol/holdrecord"
	"github.com/yeomyeonggeori/blueprotocol/toolcontract"
)

type ApprovalTarget = agentcontract.ApprovalTarget

type ApprovalTargetResolution struct {
	Target  ApprovalTarget
	Choices []holdrecord.Choice
	Failure toolcontract.ToolResult
}

type ApprovalTargetRequest struct {
	ToolName          string
	ToolInput         json.RawMessage
	RequesterPersonID string
	RequesterEmail    string
	Platform          string
	ConversationID    string
	ReplyTargetID     string
	Transport         map[string]any
}

type ApprovalTargetResolver interface {
	ResolveApprovalTarget(context.Context, ApprovalTargetRequest) (ApprovalTargetResolution, error)
}

func (gate *Gate) UseApprovalTargetResolver(approvalTargetResolver ApprovalTargetResolver) {
	gate.approvalTargetResolver = approvalTargetResolver
}

func (resolution ApprovalTargetResolution) namesNothingThatExists() bool {
	return !resolution.Target.IsResolved() && resolution.Failure.Failure != nil
}

func sendReachesOnlyTheRequester(approvalRequest mcpserver.ApprovalRequest, target ApprovalTarget) bool {
	if strings.TrimSpace(approvalRequest.SideEffectClass) != toolcontract.ToolSideEffectExternalSend || !target.IsResolved() {
		return false
	}
	for _, recipient := range append([]string{target.ID}, target.IDs...) {
		if strings.TrimSpace(recipient) != "" && !namesTheRequester(approvalRequest, recipient) {
			return false
		}
	}
	return true
}

func namesTheRequester(approvalRequest mcpserver.ApprovalRequest, recipient string) bool {
	trimmedRecipient := strings.TrimSpace(recipient)
	requesterEmail := strings.TrimSpace(approvalRequest.RequesterEmail)
	return trimmedRecipient == strings.TrimSpace(approvalRequest.RequesterPersonID) ||
		(requesterEmail != "" && strings.EqualFold(trimmedRecipient, requesterEmail))
}

func (gate *Gate) resolveApprovalTarget(ctx context.Context, approvalRequest mcpserver.ApprovalRequest) ApprovalTargetResolution {
	if target, isNamed := refusalTarget(ctx); isNamed {
		return ApprovalTargetResolution{Target: target}
	}
	if gate.approvalTargetResolver == nil {
		return ApprovalTargetResolution{}
	}
	transport, failure := CarriedWorkspaceFiles(ctx)
	if failure != nil {
		return ApprovalTargetResolution{Failure: *failure}
	}
	resolution, errorValue := gate.approvalTargetResolver.ResolveApprovalTarget(ctx, ApprovalTargetRequest{
		ToolName:          strings.TrimSpace(approvalRequest.ToolName),
		ToolInput:         approvalRequest.ToolInput,
		RequesterPersonID: approvalRequest.RequesterPersonID,
		RequesterEmail:    approvalRequest.RequesterEmail,
		Platform:          approvalRequest.Platform,
		ConversationID:    approvalRequest.ConversationID,
		ReplyTargetID:     approvalRequest.ReplyTargetID,
		Transport:         transport,
	})
	if errorValue == nil {
		return resolution
	}
	slog.Warn("approvalgate.target_resolution_unreachable",
		"toolName", strings.TrimSpace(approvalRequest.ToolName),
		"taskRunID", strings.TrimSpace(approvalRequest.TaskRunID),
		"reason", errorValue.Error())
	return ApprovalTargetResolution{}
}

func narrowedToolInput(toolInput json.RawMessage, target ApprovalTarget) json.RawMessage {
	if !target.IsResolved() {
		return nil
	}
	document := map[string]json.RawMessage{}
	if json.Unmarshal(toolInput, &document) != nil {
		return nil
	}
	identity, errorValue := json.Marshal(narrowedIdentity(target))
	if errorValue != nil {
		return nil
	}
	document[strings.TrimSpace(target.InputField)] = identity
	narrowedInput, errorValue := json.Marshal(document)
	if errorValue != nil {
		return nil
	}
	return narrowedInput
}

func narrowedIdentity(target ApprovalTarget) any {
	if len(target.IDs) > 0 {
		return target.IDs
	}
	return strings.TrimSpace(target.ID)
}