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

331 lines
14 KiB
Go

package agentruntime
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/tutti-os/tutti/packages/agent/daemon/providerregistry"
)
func newMigratedProviderAdapters(
transport ProcessTransport,
host HostMetadata,
commandResolver ProviderCommandResolver,
commandNetworkAccessPolicy CommandNetworkAccessPolicy,
) []Adapter {
descriptors := providerregistry.Migrated()
adapters := make([]Adapter, 0, len(descriptors))
for _, descriptor := range descriptors {
if err := providerregistry.Validate(descriptor); err != nil {
panic(fmt.Sprintf("invalid migrated provider descriptor: %v", err))
}
options := providerAdapterOptions{}
if descriptor.Runtime.Kind == providerregistry.RuntimeKindCodexAppServer &&
commandNetworkAccessPolicy != nil {
options.commandNetworkAccess = commandNetworkAccessPolicy(descriptor.Identity.ID)
}
adapter := newAdapterFromProviderDescriptor(descriptor, transport, host, commandResolver, options)
if adapter == nil {
panic(fmt.Sprintf("provider %q has unsupported runtime kind %q", descriptor.Identity.ID, descriptor.Runtime.Kind))
}
if provider := strings.TrimSpace(adapter.Provider()); provider != descriptor.Identity.ID {
panic(fmt.Sprintf("provider descriptor %q constructed adapter for %q", descriptor.Identity.ID, provider))
}
adapters = append(adapters, adapter)
}
return adapters
}
type providerAdapterOptions struct {
commandNetworkAccess bool
}
func newAdapterFromProviderDescriptor(
descriptor providerregistry.ProviderDescriptor,
transport ProcessTransport,
host HostMetadata,
commandResolver ProviderCommandResolver,
options providerAdapterOptions,
) Adapter {
switch descriptor.Runtime.Kind {
case providerregistry.RuntimeKindCodexAppServer:
return newAppServerAdapter(
transport,
host,
appServerAdapterConfig{
provider: descriptor.Identity.ID,
runtimeName: descriptor.Runtime.Name,
displayName: descriptor.Identity.DisplayName,
command: append([]string(nil), descriptor.Runtime.Command...),
clientInfoName: descriptor.Runtime.ClientInfoName,
authRequiredMessage: descriptor.Runtime.AuthRequiredMessage,
skillRootsStrategy: descriptor.Runtime.AppServerSkillRoots,
rateLimits: providerDescriptorHasCapability(descriptor, CapabilityRateLimits),
nativeSessionFork: descriptor.Runtime.NativeSessionFork,
sessionForkUserAgentBrand: descriptor.Runtime.AppServerFork.UserAgentBrand,
sessionForkThroughTurnMinVersion: descriptor.Runtime.AppServerFork.ThroughTurnMinVersion,
commandNetworkAccess: options.commandNetworkAccess,
},
commandResolver,
)
case providerregistry.RuntimeKindStandardACP:
switch descriptor.Runtime.StandardACP.AdapterStrategy {
case "", providerregistry.StandardACPAdapterStrategyGeneric:
return newStandardACPAdapterFromProviderDescriptor(descriptor, transport, host, commandResolver)
case providerregistry.StandardACPAdapterStrategyCursor:
return newCursorAdapterFromProviderDescriptor(descriptor, transport, host, cursorACPCommandResolver)
case providerregistry.StandardACPAdapterStrategyNexight:
return newNexightAdapterFromProviderDescriptor(descriptor, transport, host, commandResolver)
case providerregistry.StandardACPAdapterStrategyOpenClaw:
return newOpenClawAdapterFromProviderDescriptor(descriptor, transport, host, commandResolver)
case providerregistry.StandardACPAdapterStrategyOpenCode:
return newOpenCodeAdapterFromProviderDescriptor(descriptor, transport, host, commandResolver)
default:
return nil
}
case providerregistry.RuntimeKindClaudeSDK:
return NewClaudeCodeSDKAdapter(transport)
default:
return nil
}
}
func providerDescriptorHasCapability(descriptor providerregistry.ProviderDescriptor, capability string) bool {
for _, candidate := range descriptor.ComposerProfile.Capabilities {
if candidate == capability {
return true
}
}
return false
}
func newStandardACPAdapterFromProviderDescriptor(
descriptor providerregistry.ProviderDescriptor,
transport ProcessTransport,
host HostMetadata,
commandResolver ProviderCommandResolver,
) *standardACPAdapter {
modeIDs := make(map[string]string, len(descriptor.Runtime.StandardACP.PermissionModes))
for _, mode := range descriptor.Runtime.StandardACP.PermissionModes {
modeIDs[strings.TrimSpace(mode.InputID)] = strings.TrimSpace(mode.RuntimeID)
}
settingsEnvironment := descriptor.Runtime.StandardACP.SettingsEnvironment
standardACP := descriptor.Runtime.StandardACP
defaultRuntimeModeID := strings.TrimSpace(descriptor.Runtime.StandardACP.DefaultPermissionModeRuntimeID)
return &standardACPAdapter{
config: standardACPConfig{
provider: descriptor.Identity.ID,
adapterName: descriptor.Runtime.Name,
command: append([]string(nil), descriptor.Runtime.Command...),
defaultTitle: descriptor.Identity.DisplayName,
defaultTitleAliases: append([]string{descriptor.Identity.DisplayName, descriptor.Identity.ID}, descriptor.Identity.Aliases...),
authRequiredMessage: descriptor.Runtime.AuthRequiredMessage,
permissionModeID: func(mode string) string {
if runtimeModeID := modeIDs[strings.TrimSpace(mode)]; runtimeModeID != "" {
return runtimeModeID
}
return defaultRuntimeModeID
},
initializeParams: func() map[string]any { return defaultACPInitializeParams(host) },
env: func(session Session) []string {
env := standardACPEnv(session, host)
if value := runtimeSettingsEnvironmentValue(settingsEnvironment, session); value != "" {
env = append(env, settingsEnvironment.Variable+"="+value)
}
return env
},
commandResolver: commandResolver,
planModeRuntimeID: strings.TrimSpace(standardACP.PlanModeRuntimeID),
planModeDisabledRuntimeID: strings.TrimSpace(
standardACP.PlanModeDisabledRuntimeID,
),
projectCurrentMode: standardACP.ProjectCurrentMode,
startupDiagnostics: standardACP.StartupDiagnostics,
},
transport: transport,
host: host,
sessions: make(map[string]*standardACPSession),
inputUnits: providerInputUnitTrackerForTransport(transport),
}
}
type StandardACPAdapterConfig struct {
Provider string
Name string
DisplayName string
Command []string
AuthMessage string
ToolAliases map[string]string
ModelConfigOptionID string
ModelDescriptionFormat string
PermissionConfigOptionID string
ReasoningConfigOptionID string
RestrictConfigOptions bool
PermissionModes map[string]string
AutomaticPermissionDecisions map[string]string
PlanModeRuntimeID string
PlanModeDisabledRuntimeID string
PlanModeUsesLaunchPermission bool
LaunchPermission *StandardACPLaunchPermissionSetting
SetModelReasoningEffortMeta bool
Capabilities []string
AgentTargetID string
InstallationID string
ExecutableIdentity *ExecutableIdentity
Env []string
// StartupTimeout bounds initialize/session-new calls for callers that own a
// provider-specific cold-start policy. Zero keeps the normal ACP timeout.
StartupTimeout time.Duration
}
// NewStandardACPAdapter creates the generic, data-driven ACP adapter used by
// verified Agent Extension installations. It intentionally exposes no hooks
// for loading extension code into the daemon.
func NewStandardACPAdapter(config StandardACPAdapterConfig, transport ProcessTransport, host HostMetadata) (Adapter, error) {
provider := strings.TrimSpace(config.Provider)
if provider == "" || len(config.Command) == 0 || strings.TrimSpace(config.Command[0]) == "" {
return nil, fmt.Errorf("standard ACP provider and command are required")
}
modelDescriptionFormat := strings.TrimSpace(config.ModelDescriptionFormat)
if modelDescriptionFormat != "" && modelDescriptionFormat != StandardACPModelDescriptionMetadataFormatCreditConsumptionMultiplierV1 {
return nil, errors.New("standard ACP model description metadata format is unsupported")
}
launchPermission, err := validateStandardACPLaunchPermissionSetting(config.Command, config.LaunchPermission)
if err != nil {
return nil, err
}
if config.PlanModeUsesLaunchPermission {
planRuntimeID := strings.TrimSpace(config.PlanModeRuntimeID)
if launchPermission == nil || !standardACPLaunchSettingValuePattern.MatchString(planRuntimeID) {
return nil, errors.New("standard ACP launch-permission Plan mode declaration is invalid")
}
for _, permissionRuntimeID := range launchPermission.Values {
if planRuntimeID == permissionRuntimeID {
return nil, errors.New("standard ACP launch-permission Plan mode must use a distinct runtime value")
}
}
}
host = normalizeHostMetadata(host)
startupTimeout := config.StartupTimeout
if startupTimeout <= 0 {
startupTimeout = acpStartCallTimeout
}
permissionModes := cloneStandardACPToolAliases(config.PermissionModes)
adapter := &standardACPAdapter{
config: standardACPConfig{
provider: provider,
deferApprovalUntilToolInput: standardACPProviderBehaviorFor(provider).deferApprovalUntilToolInput,
adapterName: strings.TrimSpace(config.Name),
command: append([]string(nil), config.Command...),
defaultTitle: strings.TrimSpace(config.DisplayName),
defaultTitleAliases: []string{strings.TrimSpace(config.DisplayName), provider},
authRequiredMessage: strings.TrimSpace(config.AuthMessage),
toolAliases: cloneStandardACPToolAliases(config.ToolAliases),
modelConfigOptionID: strings.TrimSpace(config.ModelConfigOptionID),
modelDescriptionFormat: modelDescriptionFormat,
permissionConfigOptionID: strings.TrimSpace(config.PermissionConfigOptionID),
reasoningConfigOptionID: strings.TrimSpace(config.ReasoningConfigOptionID),
restrictConfigOptions: config.RestrictConfigOptions,
capabilities: append([]string(nil), config.Capabilities...),
agentTargetID: strings.TrimSpace(config.AgentTargetID),
installationID: strings.TrimSpace(config.InstallationID),
executableIdentity: cloneExecutableIdentity(config.ExecutableIdentity),
startupTimeout: startupTimeout,
permissionModeID: func(input string) string { return permissionModes[strings.ToLower(strings.TrimSpace(input))] },
planModeRuntimeID: strings.TrimSpace(config.PlanModeRuntimeID),
planModeDisabledRuntimeID: strings.TrimSpace(config.PlanModeDisabledRuntimeID),
planModeUsesLaunchPermission: config.PlanModeUsesLaunchPermission,
failOnSetModeError: strings.TrimSpace(config.PlanModeRuntimeID) != "" && !config.PlanModeUsesLaunchPermission,
launchPermission: launchPermission,
setModelReasoningEffortMeta: config.SetModelReasoningEffortMeta,
initializeParams: func() map[string]any { return defaultACPInitializeParams(host) },
env: func(session Session) []string {
return append(standardACPEnv(session, host), config.Env...)
},
},
transport: transport, host: host, sessions: make(map[string]*standardACPSession),
inputUnits: providerInputUnitTrackerForTransport(transport),
}
if decisions := automaticPermissionDecisionFromMap(config.AutomaticPermissionDecisions); decisions != nil {
adapter.config.automaticPermissionDecision = decisions
}
return adapter, nil
}
type standardACPProviderBehavior struct {
deferApprovalUntilToolInput bool
}
// standardACPProviderBehaviorFor owns trusted protocol differences for
// externalized Agent Extensions. Extension manifests cannot opt into these
// behaviors themselves.
func standardACPProviderBehaviorFor(provider string) standardACPProviderBehavior {
switch strings.TrimSpace(provider) {
case "acp:kimi-code":
return standardACPProviderBehavior{deferApprovalUntilToolInput: true}
default:
return standardACPProviderBehavior{}
}
}
func automaticPermissionDecisionFromMap(input map[string]string) func(string) string {
decisions := make(map[string]string, len(input))
for id, decision := range input {
normalizedID := strings.ToLower(strings.TrimSpace(id))
normalizedDecision := strings.TrimSpace(decision)
if normalizedID != "" && (normalizedDecision == "approved" || normalizedDecision == "denied") {
decisions[normalizedID] = normalizedDecision
}
}
if len(decisions) == 0 {
return nil
}
return func(permissionModeID string) string {
return decisions[strings.ToLower(strings.TrimSpace(permissionModeID))]
}
}
func cloneStandardACPToolAliases(input map[string]string) map[string]string {
result := make(map[string]string, len(input))
for key, value := range input {
result[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value)
}
return result
}
func runtimeSettingsEnvironmentValue(
descriptor providerregistry.RuntimeSettingsEnvironmentDescriptor,
session Session,
) string {
settings := session.SettingsValue()
values := make(map[string]string, len(descriptor.JSONFields))
for _, field := range descriptor.JSONFields {
var value string
if field.Setting == providerregistry.RuntimeSettingFieldModel {
value = strings.TrimSpace(settings.Model)
}
if value != "" {
values[field.JSONKey] = value
}
}
if len(values) == 0 {
return ""
}
data, err := json.Marshal(values)
if err != nil {
return ""
}
return string(data)
}
func migratedProviderComposerProfile(provider string) (providerregistry.ComposerProfileDescriptor, bool) {
descriptor, ok := providerregistry.Find(provider)
if !ok {
return providerregistry.ComposerProfileDescriptor{}, false
}
return descriptor.ComposerProfile, true
}