Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

486 lines
13 KiB
Go

package cli
import (
"context"
"errors"
"fmt"
"strings"
)
var (
ErrCommandNotFound = errors.New("cli command not found")
ErrInvalidCommand = errors.New("invalid cli command")
ErrInvalidInput = errors.New("invalid cli command input")
ErrServiceUnavailable = errors.New("cli service unavailable")
ErrWorkspaceOperation = errors.New("cli workspace operation failed")
)
type InvokeError struct {
Kind error
ReasonCode string
Reason string
Err error
}
func (e *InvokeError) Error() string {
if e == nil {
return ""
}
message := strings.TrimSpace(e.Reason)
if e.Err != nil {
if message != "" {
message += ": "
}
message += e.Err.Error()
}
if message == "" && e.Kind != nil {
message = e.Kind.Error()
}
return message
}
func (e *InvokeError) Unwrap() error {
if e == nil {
return nil
}
if e.Kind == nil {
return e.Err
}
if e.Err == nil {
return e.Kind
}
return errors.Join(e.Kind, e.Err)
}
func ServiceUnavailableError(reason string, err error) error {
return &InvokeError{Kind: ErrServiceUnavailable, Reason: strings.TrimSpace(reason), Err: err}
}
func WorkspaceOperationError(reason string, err error) error {
return &InvokeError{Kind: ErrWorkspaceOperation, Reason: strings.TrimSpace(reason), Err: err}
}
func InvalidInputReasonError(reasonCode string, message string, err error) error {
return &InvokeError{
Kind: ErrInvalidInput,
ReasonCode: strings.TrimSpace(reasonCode),
Reason: strings.TrimSpace(message),
Err: err,
}
}
func InvokeErrorReason(err error) string {
var invokeErr *InvokeError
if errors.As(err, &invokeErr) {
if invokeErr.ReasonCode != "" {
return invokeErr.ReasonCode
}
return invokeErr.Reason
}
return ""
}
type Registry struct {
commands map[string]Command
order []string
commandProviderID map[string]string
providerFilters map[string]CapabilityFilterProvider
AppCommands DynamicCommandRegistry
AgentSessionCapabilities AgentSessionCapabilityResolver
}
type Provider interface {
AppID() string
Commands() []Command
}
type CapabilityFilterProvider interface {
FilterCapabilities(context.Context, InvokeContext, []Capability) []Capability
}
type AgentSessionCapabilityProjection struct {
AllowedIDs []string
IncludeIntegrationIDs []string
ExcludeIDs []string
}
type AgentSessionCapabilityResolver interface {
ResolveAgentSessionCapabilityProjection(
context.Context,
string,
string,
) (AgentSessionCapabilityProjection, error)
}
type DynamicCommandRegistry interface {
Capabilities(context.Context, InvokeContext) []Capability
Invoke(context.Context, InvokeRequest) (CommandOutput, error)
}
func NewRegistryFromProviders(providers ...Provider) (*Registry, error) {
registry := newRegistry()
for _, provider := range providers {
if provider == nil {
return nil, fmt.Errorf("%w: provider is nil", ErrInvalidCommand)
}
providerID := strings.TrimSpace(provider.AppID())
if providerID == "" {
return nil, fmt.Errorf("%w: provider app id is required", ErrInvalidCommand)
}
if filter, ok := provider.(CapabilityFilterProvider); ok {
registry.providerFilters[providerID] = filter
}
for _, command := range provider.Commands() {
if err := registry.register(command, providerID); err != nil {
return nil, err
}
}
}
return registry, nil
}
func newRegistry() *Registry {
return &Registry{
commands: map[string]Command{},
order: []string{},
commandProviderID: map[string]string{},
providerFilters: map[string]CapabilityFilterProvider{},
}
}
func (r *Registry) Register(command Command) error {
return r.register(command, "")
}
func (r *Registry) register(command Command, providerID string) error {
if r == nil {
return fmt.Errorf("%w: registry is nil", ErrInvalidCommand)
}
id := strings.TrimSpace(command.Capability.ID)
if id == "" {
return fmt.Errorf("%w: id is required", ErrInvalidCommand)
}
if len(command.Capability.Path) == 0 {
return fmt.Errorf("%w: path is required", ErrInvalidCommand)
}
if strings.TrimSpace(command.Capability.Summary) == "" {
return fmt.Errorf("%w: summary is required", ErrInvalidCommand)
}
if command.Handler == nil {
return fmt.Errorf("%w: handler is required", ErrInvalidCommand)
}
command.Capability.ID = id
if command.Capability.Source.Kind == "" {
command.Capability.Source.Kind = CapabilitySourceBuiltin
}
if _, exists := r.commands[id]; exists {
return fmt.Errorf("%w: duplicate id %q", ErrInvalidCommand, id)
}
r.commands[id] = command
r.order = append(r.order, id)
if providerID != "" {
r.commandProviderID[id] = providerID
}
return nil
}
func (r *Registry) Capabilities(ctx context.Context, invokeContext InvokeContext) []Capability {
if r == nil || len(r.commands) == 0 {
if r != nil && r.AppCommands != nil {
return r.AppCommands.Capabilities(ctx, invokeContext)
}
return []Capability{}
}
var allowedByProvider map[string]map[string]struct{}
if !invokeContext.SkipCapabilityFilters {
allowedByProvider = r.filteredCapabilityIDsByProvider(ctx, invokeContext)
}
projection, projected, err := r.resolveAgentSessionProjection(ctx, invokeContext)
if err != nil {
return []Capability{}
}
if projected {
invokeContext.IncludeIntegrationCapabilities = true
}
result := make([]Capability, 0, len(r.commands))
for _, id := range r.order {
if command, ok := r.commands[id]; ok {
if !r.capabilityVisible(id, allowedByProvider) {
continue
}
if !capabilityDiscoverable(command.Capability, invokeContext) {
continue
}
if projected && !projection.allows(command.Capability) {
continue
}
result = append(result, command.Capability)
}
}
if r.AppCommands != nil {
appCapabilities := r.AppCommands.Capabilities(ctx, invokeContext)
if projected {
appCapabilities = projection.filter(appCapabilities)
}
result = append(result, appCapabilities...)
}
return result
}
func (r *Registry) filteredCapabilityIDsByProvider(ctx context.Context, invokeContext InvokeContext) map[string]map[string]struct{} {
if r == nil || len(r.providerFilters) == 0 {
return nil
}
capabilitiesByProvider := map[string][]Capability{}
for _, id := range r.order {
providerID := r.commandProviderID[id]
if providerID == "" || r.providerFilters[providerID] == nil {
continue
}
command, ok := r.commands[id]
if !ok {
continue
}
capabilitiesByProvider[providerID] = append(capabilitiesByProvider[providerID], command.Capability)
}
if len(capabilitiesByProvider) == 0 {
return nil
}
allowedByProvider := map[string]map[string]struct{}{}
for providerID, capabilities := range capabilitiesByProvider {
filter := r.providerFilters[providerID]
allowed := map[string]struct{}{}
for _, capability := range filter.FilterCapabilities(ctx, invokeContext, append([]Capability(nil), capabilities...)) {
id := strings.TrimSpace(capability.ID)
if id != "" {
allowed[id] = struct{}{}
}
}
allowedByProvider[providerID] = allowed
}
return allowedByProvider
}
func (r *Registry) capabilityVisible(id string, allowedByProvider map[string]map[string]struct{}) bool {
if len(allowedByProvider) == 0 {
return true
}
providerID := r.commandProviderID[id]
if providerID == "" {
return true
}
allowed, ok := allowedByProvider[providerID]
if !ok {
return true
}
_, visible := allowed[id]
return visible
}
func capabilityDiscoverable(capability Capability, invokeContext InvokeContext) bool {
if invokeContext.IncludeIntegrationCapabilities {
return true
}
return NormalizeCapabilityVisibility(capability.Visibility) != CapabilityVisibilityIntegration
}
func NormalizeCapabilityVisibility(visibility CapabilityVisibility) CapabilityVisibility {
switch CapabilityVisibility(strings.TrimSpace(string(visibility))) {
case "", CapabilityVisibilityPublic:
return CapabilityVisibilityPublic
case CapabilityVisibilityIntegration:
return CapabilityVisibilityIntegration
default:
return visibility
}
}
func (r *Registry) Invoke(ctx context.Context, request InvokeRequest) (CommandOutput, error) {
if r == nil {
return CommandOutput{}, ErrCommandNotFound
}
commandID := strings.TrimSpace(request.CommandID)
command, ok := r.commands[commandID]
if !ok {
if r.AppCommands != nil {
capability, err := r.authorizeDynamicCommand(ctx, request.Context, commandID)
if err != nil {
return CommandOutput{}, err
}
if err := validateCapabilityInput(capability.InputSchema, request.Input); err != nil {
return CommandOutput{}, err
}
return r.AppCommands.Invoke(ctx, request)
}
return CommandOutput{}, ErrCommandNotFound
}
if err := r.authorizeProviderCapability(ctx, request.Context, commandID, command.Capability); err != nil {
return CommandOutput{}, err
}
if err := r.authorizeAgentSessionCapability(
ctx, request.Context, command.Capability,
); err != nil {
return CommandOutput{}, err
}
if err := validateCapabilityInput(command.Capability.InputSchema, request.Input); err != nil {
return CommandOutput{}, err
}
if request.OutputMode == "" {
request.OutputMode = command.Capability.Output.DefaultMode
}
if request.Context.Source == "" {
request.Context.Source = "cli"
}
return command.Handler(ctx, request)
}
func (r *Registry) authorizeProviderCapability(
ctx context.Context,
invokeContext InvokeContext,
commandID string,
_ Capability,
) error {
providerID := r.commandProviderID[commandID]
filter := r.providerFilters[providerID]
if providerID == "" || filter == nil {
return nil
}
// SkipCapabilityFilters is a discovery-only host escape hatch. Invocation
// always re-evaluates the provider policy with the escape hatch disabled.
invokeContext.SkipCapabilityFilters = false
allowedByProvider := r.filteredCapabilityIDsByProvider(ctx, invokeContext)
if r.capabilityVisible(commandID, allowedByProvider) {
return nil
}
return ErrCommandNotFound
}
func (r *Registry) resolveAgentSessionProjection(
ctx context.Context,
invokeContext InvokeContext,
) (agentSessionCapabilityProjection, bool, error) {
workspaceID := strings.TrimSpace(invokeContext.WorkspaceID)
sessionID := strings.TrimSpace(invokeContext.AgentSessionID)
if sessionID == "" {
return agentSessionCapabilityProjection{}, false, nil
}
if workspaceID == "" || r.AgentSessionCapabilities == nil {
return agentSessionCapabilityProjection{}, true, ServiceUnavailableError(
"agent_session_capability_projection_unavailable",
errors.New("agent session capability resolver is unavailable"),
)
}
projection, err := r.AgentSessionCapabilities.ResolveAgentSessionCapabilityProjection(
ctx, workspaceID, sessionID,
)
if err != nil {
return agentSessionCapabilityProjection{}, true, ServiceUnavailableError(
"agent_session_capability_projection_unavailable", err,
)
}
return newAgentSessionCapabilityProjection(projection), true, nil
}
func (r *Registry) authorizeAgentSessionCapability(
ctx context.Context,
invokeContext InvokeContext,
capability Capability,
) error {
projection, projected, err := r.resolveAgentSessionProjection(ctx, invokeContext)
if err != nil {
return err
}
if projected && !projection.allows(capability) {
return ErrCommandNotFound
}
return nil
}
func (r *Registry) authorizeDynamicCommand(
ctx context.Context,
invokeContext InvokeContext,
commandID string,
) (Capability, error) {
projection, projected, err := r.resolveAgentSessionProjection(ctx, invokeContext)
if err != nil {
return Capability{}, err
}
if projected {
invokeContext.IncludeIntegrationCapabilities = true
}
invokeContext.SkipCapabilityFilters = false
for _, capability := range r.AppCommands.Capabilities(ctx, invokeContext) {
if strings.TrimSpace(capability.ID) == commandID {
if !projected || projection.allows(capability) {
return capability, nil
}
break
}
}
return Capability{}, ErrCommandNotFound
}
type agentSessionCapabilityProjection struct {
allowedIDs map[string]struct{}
includedIntegrationIDs map[string]struct{}
excludedIDs map[string]struct{}
}
func newAgentSessionCapabilityProjection(
projection AgentSessionCapabilityProjection,
) agentSessionCapabilityProjection {
result := agentSessionCapabilityProjection{
allowedIDs: make(map[string]struct{}),
includedIntegrationIDs: make(map[string]struct{}),
excludedIDs: make(map[string]struct{}),
}
for _, id := range projection.AllowedIDs {
if id = strings.TrimSpace(id); id != "" {
result.allowedIDs[id] = struct{}{}
}
}
for _, id := range projection.IncludeIntegrationIDs {
if id = strings.TrimSpace(id); id != "" {
result.includedIntegrationIDs[id] = struct{}{}
}
}
for _, id := range projection.ExcludeIDs {
if id = strings.TrimSpace(id); id != "" {
result.excludedIDs[id] = struct{}{}
}
}
return result
}
func (projection agentSessionCapabilityProjection) filter(
capabilities []Capability,
) []Capability {
result := make([]Capability, 0, len(capabilities))
for _, capability := range capabilities {
if projection.allows(capability) {
result = append(result, capability)
}
}
return result
}
func (projection agentSessionCapabilityProjection) allows(
capability Capability,
) bool {
id := strings.TrimSpace(capability.ID)
if len(projection.allowedIDs) > 0 {
if _, allowed := projection.allowedIDs[id]; !allowed {
return false
}
}
if _, excluded := projection.excludedIDs[id]; excluded {
return false
}
if NormalizeCapabilityVisibility(capability.Visibility) !=
CapabilityVisibilityIntegration {
return true
}
_, included := projection.includedIntegrationIDs[id]
return included
}