mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
230 lines
6.9 KiB
Go
230 lines
6.9 KiB
Go
package agentcapabilities
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ToolCallParams is the shared request/response tool-call parameter shape used
|
|
// by native Pulse Assistant execution and external-agent adapters.
|
|
type ToolCallParams struct {
|
|
Name string `json:"name"`
|
|
Arguments map[string]any `json:"arguments,omitempty"`
|
|
}
|
|
|
|
// ToolCallKind classifies a Pulse Intelligence tool call for safety gates and
|
|
// workflow transitions. Native Assistant currently uses it for FSM decisions,
|
|
// while the vocabulary lives in the shared core so external-agent adapters and
|
|
// future surfaces do not grow separate read/write heuristics.
|
|
type ToolCallKind int
|
|
|
|
const (
|
|
// ToolCallKindResolve covers discovery/query tools that establish resource
|
|
// context and also count as safe verification reads.
|
|
ToolCallKindResolve ToolCallKind = iota
|
|
|
|
// ToolCallKindRead covers read-only tools such as logs, metrics, status,
|
|
// and config inspection.
|
|
ToolCallKindRead
|
|
|
|
// ToolCallKindWrite covers tools that can change Pulse state or target-side
|
|
// infrastructure state.
|
|
ToolCallKindWrite
|
|
|
|
// ToolCallKindUserInput covers interactive tools that ask the user for
|
|
// missing information and do not advance infrastructure workflow state.
|
|
ToolCallKindUserInput
|
|
)
|
|
|
|
func (k ToolCallKind) String() string {
|
|
switch k {
|
|
case ToolCallKindResolve:
|
|
return "resolve"
|
|
case ToolCallKindRead:
|
|
return "read"
|
|
case ToolCallKindWrite:
|
|
return "write"
|
|
case ToolCallKindUserInput:
|
|
return "user_input"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// NormalizeToolCallParams returns the canonical tool-call parameter shape
|
|
// shared by native Assistant execution and external-agent adapters.
|
|
func NormalizeToolCallParams(params ToolCallParams) ToolCallParams {
|
|
params.Name = strings.TrimSpace(params.Name)
|
|
params.Arguments = CloneToolArguments(params.Arguments)
|
|
if params.Arguments == nil {
|
|
params.Arguments = map[string]any{}
|
|
}
|
|
return params
|
|
}
|
|
|
|
// ValidateToolCallParams checks the normalized tool-call contract before a
|
|
// surface attempts capability lookup or execution.
|
|
func ValidateToolCallParams(params ToolCallParams) error {
|
|
if strings.TrimSpace(params.Name) == "" {
|
|
return fmt.Errorf("tool name is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PrepareToolRegistryExecution normalizes and validates the direct in-process
|
|
// registry entrypoint used by native Assistant execution. It returns a
|
|
// ready-to-return shared error result when the tool-call params contract is
|
|
// invalid, so registry surfaces do not duplicate failure text or envelope
|
|
// construction.
|
|
func PrepareToolRegistryExecution(name string, args map[string]any) (ToolCallParams, ToolResult, bool) {
|
|
params := NormalizeToolCallParams(ToolCallParams{
|
|
Name: name,
|
|
Arguments: args,
|
|
})
|
|
if err := ValidateToolCallParams(params); err != nil {
|
|
return params, NewInvalidToolCallParamsResult(err), false
|
|
}
|
|
return params, ToolResult{}, true
|
|
}
|
|
|
|
// ClassifyToolCall classifies a provider/registry tool call for safety gates
|
|
// and workflow state transitions. Unknown tools default to write so newly
|
|
// introduced tools cannot bypass governed-action checks accidentally.
|
|
func ClassifyToolCall(toolName string, args map[string]interface{}) ToolCallKind {
|
|
action, _ := args["action"].(string)
|
|
actionLower := strings.ToLower(action)
|
|
operation, _ := args["operation"].(string)
|
|
operationLower := strings.ToLower(operation)
|
|
|
|
switch strings.TrimSpace(toolName) {
|
|
case PulseQuestionToolName:
|
|
return ToolCallKindUserInput
|
|
|
|
case PulseQueryToolName, PulseDiscoveryToolName:
|
|
return ToolCallKindResolve
|
|
|
|
case PulseMetricsToolName, PulseStorageToolName, PulsePMGToolName, PulseSummarizeToolName:
|
|
return ToolCallKindRead
|
|
|
|
case PulseAlertsToolName:
|
|
switch actionLower {
|
|
case "resolve", "dismiss":
|
|
return ToolCallKindWrite
|
|
default:
|
|
return ToolCallKindRead
|
|
}
|
|
|
|
case PulseKubernetesToolName:
|
|
switch actionLower {
|
|
case "scale", "restart", "delete_pod", "exec":
|
|
return ToolCallKindWrite
|
|
default:
|
|
return ToolCallKindRead
|
|
}
|
|
|
|
case PulseKnowledgeToolName:
|
|
switch actionLower {
|
|
case "remember", "note", "save":
|
|
return ToolCallKindWrite
|
|
default:
|
|
return ToolCallKindRead
|
|
}
|
|
|
|
case PulseReadToolName, LegacyAssistantFetchURLToolName:
|
|
return ToolCallKindRead
|
|
|
|
case PulseControlToolName:
|
|
return ToolCallKindWrite
|
|
|
|
case PulseDockerToolName:
|
|
switch actionLower {
|
|
case "control", "update", "check_updates", "trigger_update":
|
|
return ToolCallKindWrite
|
|
default:
|
|
return ToolCallKindRead
|
|
}
|
|
|
|
case PulseFileEditToolName:
|
|
switch actionLower {
|
|
case "read":
|
|
return ToolCallKindRead
|
|
case "write", "append":
|
|
return ToolCallKindWrite
|
|
default:
|
|
return ToolCallKindRead
|
|
}
|
|
|
|
case PulseRunCommandToolName, PulseControlGuestToolName, PulseControlDockerToolName,
|
|
LegacyAssistantRunCommandToolName, LegacyAssistantSetResourceURLToolName:
|
|
return ToolCallKindWrite
|
|
|
|
case PulseSearchResourcesToolName, PulseGetResourceToolName, PulseGetTopologyToolName,
|
|
PulseListInfrastructureToolName, PulseGetConnectionHealthToolName:
|
|
return ToolCallKindResolve
|
|
|
|
case PulseGetDockerLogsToolName, PulseGetPerformanceMetricsToolName,
|
|
PulseGetTemperaturesToolName, PulseGetBaselinesToolName, PulseGetPatternsToolName:
|
|
return ToolCallKindRead
|
|
|
|
case PatrolGetFindingsToolName:
|
|
return ToolCallKindRead
|
|
case PatrolReportFindingToolName, PatrolResolveFindingToolName:
|
|
return ToolCallKindWrite
|
|
}
|
|
|
|
if toolCallActionIsWrite(actionLower) || toolCallActionIsWrite(operationLower) {
|
|
return ToolCallKindWrite
|
|
}
|
|
|
|
if toolCallActionIsRead(actionLower) || toolCallActionIsRead(operationLower) {
|
|
return ToolCallKindRead
|
|
}
|
|
|
|
return ToolCallKindWrite
|
|
}
|
|
|
|
func toolCallActionIsWrite(action string) bool {
|
|
switch action {
|
|
case "start", "stop", "restart", "delete",
|
|
"shutdown", "reboot", "write", "append",
|
|
"update", "trigger", "resolve", "dismiss",
|
|
"control":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func toolCallActionIsRead(action string) bool {
|
|
switch action {
|
|
case "get", "list", "search", "query",
|
|
"read", "logs", "status", "health",
|
|
"describe", "inspect", "show":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// NewInvalidToolCallParamsResult returns the stable shared error result for a
|
|
// registry tool-call request that fails the shared params contract.
|
|
func NewInvalidToolCallParamsResult(err error) ToolResult {
|
|
if err == nil {
|
|
err = errors.New("tool call params are invalid")
|
|
}
|
|
return NewToolErrorResult(fmt.Errorf("invalid tools/call params: %w", err))
|
|
}
|
|
|
|
// NewUnknownToolResult returns the stable shared error result for a registry
|
|
// tool-call request that names no registered tool.
|
|
func NewUnknownToolResult(name string) ToolResult {
|
|
return NewToolErrorResult(fmt.Errorf("unknown tool: %s", strings.TrimSpace(name)))
|
|
}
|
|
|
|
// NewControlToolsDisabledToolResult returns the stable shared text result used
|
|
// when a direct registry execution attempts a control tool at read-only control
|
|
// level.
|
|
func NewControlToolsDisabledToolResult() ToolResult {
|
|
return NewToolTextResult(ControlToolsDisabledMessage)
|
|
}
|