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

348 lines
8.9 KiB
Go

package runtimeprep
import (
"context"
"errors"
"fmt"
"sort"
"strings"
)
type CommandSourceKind string
const (
CommandSourceBuiltin CommandSourceKind = "builtin"
CommandSourceApp CommandSourceKind = "app"
)
type CommandSource struct {
Kind CommandSourceKind
AppID string
AppName string
}
type CommandTableColumn struct {
Key string
Label string
}
type CommandTableOutput struct {
Columns []CommandTableColumn
}
type CommandCapabilityOutput struct {
DefaultMode string
JSON bool
Table *CommandTableOutput
}
type CommandCapability struct {
ID string
Path []string
Summary string
Description string
Visibility string
InputSchema map[string]any
Output CommandCapabilityOutput
ExecutionMode string
Source CommandSource
}
type CommandContext struct {
Source string
WorkspaceID string
SkipCapabilityFilters bool
IncludeIntegrationCapabilities bool
}
type CommandCatalog interface {
Capabilities(context.Context, CommandContext) []CommandCapability
}
type CommandCapabilityProjection struct {
// AllowedIDs switches the projection to an exact capability allowlist when
// non-empty. It includes both public and promoted integration capabilities.
// Empty preserves the legacy public-by-default projection.
AllowedIDs []string
IncludeIntegrationIDs []string
ExcludeIDs []string
}
func resolveCommandCapabilities(
ctx context.Context,
catalog CommandCatalog,
workspaceID string,
cliName string,
projections ...*CommandCapabilityProjection,
) (*CommandResolver, error) {
if catalog == nil {
return nil, errors.New("agent runtime preparation requires a command catalog")
}
capabilities := catalog.Capabilities(ctx, CommandContext{
Source: "agent-runtime",
WorkspaceID: strings.TrimSpace(workspaceID),
SkipCapabilityFilters: true,
IncludeIntegrationCapabilities: projectionRequestsIntegration(
projections,
),
})
var projection *CommandCapabilityProjection
if len(projections) > 0 {
projection = projections[0]
}
projected, err := projectCommandCapabilities(capabilities, projection)
if err != nil {
return nil, err
}
return newCommandResolver(cliName, projected)
}
func projectionRequestsIntegration(
projections []*CommandCapabilityProjection,
) bool {
return len(projections) > 0 &&
projections[0] != nil &&
len(normalizedCommandCapabilityIDs(
projections[0].IncludeIntegrationIDs,
)) > 0
}
func commandVisibleToAgent(capability CommandCapability) bool {
visibility := strings.TrimSpace(capability.Visibility)
return visibility == "" || visibility == "public"
}
func projectCommandCapabilities(
capabilities []CommandCapability,
projection *CommandCapabilityProjection,
) ([]CommandCapability, error) {
if projection == nil {
return capabilities, nil
}
included := normalizedCommandCapabilityIDs(projection.IncludeIntegrationIDs)
excluded := normalizedCommandCapabilityIDs(projection.ExcludeIDs)
allowed := normalizedCommandCapabilityIDs(projection.AllowedIDs)
foundIncluded := make(map[string]struct{}, len(included))
foundAllowed := make(map[string]struct{}, len(allowed))
projected := make([]CommandCapability, 0, len(capabilities))
for _, capability := range capabilities {
id := strings.TrimSpace(capability.ID)
if len(allowed) > 0 {
if _, accepted := allowed[id]; !accepted {
continue
}
}
if _, denied := excluded[id]; denied {
continue
}
if _, requested := included[id]; requested {
capability.Visibility = "public"
foundIncluded[id] = struct{}{}
}
projected = append(projected, capability)
if _, required := allowed[id]; required {
foundAllowed[id] = struct{}{}
}
}
for id := range allowed {
if _, found := foundAllowed[id]; !found {
return nil, fmt.Errorf(
"agent command capability projection requires unavailable allowed command %q",
id,
)
}
}
for id := range included {
if _, found := foundIncluded[id]; !found {
return nil, fmt.Errorf(
"agent command capability projection requires unavailable command %q",
id,
)
}
}
return projected, nil
}
func normalizedCommandCapabilityIDs(values []string) map[string]struct{} {
normalized := make(map[string]struct{}, len(values))
for _, value := range values {
if value = strings.TrimSpace(value); value != "" {
normalized[value] = struct{}{}
}
}
return normalized
}
// Wait execution adds a wrapper-owned timeout input. It is part of the
// invocation contract even though app handlers do not receive it.
func executionFacingInputSchema(mode string, schema map[string]any) map[string]any {
if strings.TrimSpace(mode) != "wait" {
return schema
}
projected := cloneSchemaMap(schema)
projected["type"] = "object"
properties := cloneSchemaMap(mapSchemaValue(schema["properties"]))
if _, exists := properties["timeout-ms"]; !exists {
properties["timeout-ms"] = map[string]any{
"type": "integer",
"description": "Maximum total wait in milliseconds; omit to wait until the command reaches a stop point.",
}
}
projected["properties"] = properties
return projected
}
func cloneSchemaMap(source map[string]any) map[string]any {
if source == nil {
return nil
}
clone := make(map[string]any, len(source))
for key, value := range source {
clone[key] = cloneSchemaValue(value)
}
return clone
}
func cloneSchemaValue(value any) any {
switch typed := value.(type) {
case map[string]any:
return cloneSchemaMap(typed)
case []any:
clone := make([]any, len(typed))
for index, item := range typed {
clone[index] = cloneSchemaValue(item)
}
return clone
case []map[string]any:
clone := make([]map[string]any, len(typed))
for index, item := range typed {
clone[index] = cloneSchemaMap(item)
}
return clone
case []string:
return append([]string(nil), typed...)
default:
return value
}
}
func commandPath(path []string) string {
return strings.Join(normalizedCommandPath(path), " ")
}
func inputDetailsForCommand(_ string, schema map[string]any) string {
properties := mapSchemaValue(schema["properties"])
if len(properties) == 0 {
return ""
}
required := make(map[string]bool)
for _, name := range stringSliceSchemaValue(schema["required"]) {
required[strings.TrimSpace(name)] = true
}
names := make([]string, 0, len(properties))
for name := range properties {
if name = strings.TrimSpace(name); name != "" {
names = append(names, name)
}
}
sortCommandInputs(names, required)
parts := make([]string, 0, len(names))
for _, name := range names {
property := mapSchemaValue(properties[name])
detail := "--" + name
if typ := schemaTypeLabel(property); typ != "" {
detail += " <" + typ + ">"
}
qualifiers := []string{"optional"}
if required[name] {
qualifiers[0] = "required"
}
if enum := stringSliceSchemaValue(property["enum"]); len(enum) > 0 {
qualifiers = append(qualifiers, "values: "+strings.Join(enum, "|"))
}
if defaultValue, ok := property["default"]; ok {
if text := strings.TrimSpace(fmt.Sprint(defaultValue)); text != "" {
qualifiers = append(qualifiers, "default: "+text)
}
}
detail += " (" + strings.Join(qualifiers, "; ") + ")"
if description := strings.TrimSpace(asSchemaString(property["description"])); description != "" {
detail += " - " + description
}
parts = append(parts, detail)
}
return strings.Join(parts, "; ")
}
func sortCommandInputs(names []string, required map[string]bool) {
sort.SliceStable(names, func(left int, right int) bool {
if required[names[left]] != required[names[right]] {
return required[names[left]]
}
return names[left] < names[right]
})
}
func schemaTypeLabel(property map[string]any) string {
switch strings.TrimSpace(asSchemaString(property["type"])) {
case "integer", "number":
return "number"
case "boolean":
return "true|false"
case "array", "object":
return "json"
default:
return strings.TrimSpace(asSchemaString(property["type"]))
}
}
func mapSchemaValue(value any) map[string]any {
if typed, ok := value.(map[string]any); ok {
return typed
}
return nil
}
func asSchemaString(value any) string {
text, _ := value.(string)
return text
}
func schemaHasInput(schema map[string]any, name string) bool {
_, ok := mapSchemaValue(schema["properties"])[strings.TrimSpace(name)]
return ok
}
func stringSliceSchemaValue(value any) []string {
switch typed := value.(type) {
case []string:
return append([]string(nil), typed...)
case []any:
result := make([]string, 0, len(typed))
for _, item := range typed {
if text, ok := item.(string); ok {
result = append(result, text)
}
}
return result
default:
return nil
}
}
func normalizeCLICommandName(cliName string) string {
if cliName = strings.TrimSpace(cliName); cliName != "" {
return cliName
}
return "tutti"
}
func firstNonEmptyText(values ...string) string {
for _, value := range values {
if value = strings.TrimSpace(value); value != "" {
return value
}
}
return ""
}