160 lines
7.0 KiB
Swift
160 lines
7.0 KiB
Swift
import AgentKit
|
|
import Foundation
|
|
import TermindApplication
|
|
|
|
/// What Termind's own agent tools are built from.
|
|
///
|
|
/// The runtime's `AgentBuiltInToolConfiguration` covers the tools it ships;
|
|
/// this covers ours, which are all about hosts — listing them, running commands
|
|
/// on them, moving files to and from them. Tool types stay narrowly injected:
|
|
/// the environment is unpacked here and each definition receives only what it
|
|
/// uses.
|
|
nonisolated public struct TermindAgentToolEnvironment: Sendable {
|
|
public var metrics: any MetricsSnapshotReading
|
|
public var hosts: AgentHostResolver
|
|
public var connections: AgentConnectionPool
|
|
public var authentication: AgentSSHAuthenticationRegistry
|
|
public var background: AgentBackgroundCommands
|
|
/// Shared with the runtime's scratch group. `scratch_pull` and
|
|
/// `scratch_push` are ours rather than the runtime's precisely because they
|
|
/// are the two ends that touch a host, and they must stage into the same
|
|
/// directory `scratch_replace` reads.
|
|
public var workspace: AgentScratchWorkspace?
|
|
|
|
public init(
|
|
hosts: AgentHostResolver,
|
|
metrics: any MetricsSnapshotReading,
|
|
connections: AgentConnectionPool,
|
|
authentication suppliedAuthentication: AgentSSHAuthenticationRegistry? = nil,
|
|
background: AgentBackgroundCommands = AgentBackgroundCommands(),
|
|
workspace: AgentScratchWorkspace? = nil
|
|
) {
|
|
self.metrics = metrics
|
|
self.hosts = hosts
|
|
self.connections = connections
|
|
authentication =
|
|
suppliedAuthentication ?? AgentSSHAuthenticationRegistry(resolver: hosts)
|
|
self.background = background
|
|
self.workspace = workspace
|
|
}
|
|
}
|
|
|
|
/// Termind's own agent tool catalog, registered alongside the runtime's.
|
|
///
|
|
/// It exists for the same reason the runtime's does: one registration point per
|
|
/// type, supplying both the executable definition and the presenter a persisted
|
|
/// card is redrawn from years later.
|
|
nonisolated public enum TermindAgentTools {
|
|
public typealias Registration = AgentToolTypeRegistration<TermindAgentToolEnvironment>
|
|
|
|
public static func tools(in environment: TermindAgentToolEnvironment) -> [AnyAgentTool] {
|
|
definitions.compactMap { $0.tool(in: environment) }
|
|
}
|
|
|
|
/// Every presenter that can draw a card in this app, ours after the
|
|
/// runtime's. Built once: the map is immutable and the lookup happens on
|
|
/// every historical card the transcript draws.
|
|
public static let presenterRegistry = AgentToolPresenterRegistry(
|
|
AgentToolCatalog.builtInPresenters + definitions.map(\.presenter)
|
|
)
|
|
|
|
public static func presenter(id: String) -> AgentToolDetailPresenter? {
|
|
presenterRegistry.presenter(id: id)
|
|
}
|
|
|
|
public static func presenter(for descriptor: AgentToolDescriptor) -> AgentToolDetailPresenter? {
|
|
if let id = descriptor.presentation?.presenterID { return presenter(id: id) }
|
|
return legacyPresenter(forToolNamed: descriptor.name)
|
|
}
|
|
|
|
/// Cards written before presenter IDs were persisted carry only a tool name.
|
|
///
|
|
/// Every built-in of that era used the `builtin.` namespace, so the name is
|
|
/// enough — but only for the names that existed then, which is why this is a
|
|
/// closed list rather than a prefix rule. A tool added later has always
|
|
/// recorded its ID.
|
|
public static func legacyPresenter(forToolNamed name: String) -> AgentToolDetailPresenter? {
|
|
guard legacyToolNames.contains(name) else { return nil }
|
|
return presenter(id: "builtin.\(name)")
|
|
}
|
|
|
|
private static let definitions: [Registration] = [
|
|
.init(GetMetricsTool.self) {
|
|
GetMetricsTool(hosts: $0.hosts, metrics: $0.metrics)
|
|
},
|
|
.init(ListHostsTool.self) {
|
|
ListHostsTool(hosts: $0.hosts)
|
|
},
|
|
.init(SSHExecTool.self, availableIn: [.acting]) {
|
|
SSHExecTool(
|
|
authentication: $0.authentication, pool: $0.connections,
|
|
background: $0.background
|
|
)
|
|
},
|
|
.init(PlanningSSHExecTool.self, availableIn: [.planning]) {
|
|
PlanningSSHExecTool(
|
|
authentication: $0.authentication, pool: $0.connections,
|
|
background: $0.background
|
|
)
|
|
},
|
|
.init(SSHWaitTool.self) { SSHWaitTool(background: $0.background) },
|
|
.init(TerminalSuggestTool.self) { _ in
|
|
TerminalSuggestTool()
|
|
},
|
|
.init(SFTPListTool.self) {
|
|
SFTPListTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPStatTool.self) {
|
|
SFTPStatTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPReadTool.self) {
|
|
SFTPReadTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPWriteTool.self, availableIn: [.acting]) {
|
|
SFTPWriteTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPMkdirTool.self, availableIn: [.acting]) {
|
|
SFTPMkdirTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPMoveTool.self, availableIn: [.acting]) {
|
|
SFTPMoveTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPCopyTool.self, availableIn: [.acting]) {
|
|
SFTPCopyTool(hosts: $0.hosts, authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPChmodTool.self, availableIn: [.acting]) {
|
|
SFTPChmodTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(SFTPDeleteTool.self, availableIn: [.acting]) {
|
|
SFTPDeleteTool(authentication: $0.authentication, connections: $0.connections)
|
|
},
|
|
.init(ScratchPullTool.self) { environment in
|
|
environment.workspace.map {
|
|
ScratchPullTool(
|
|
workspace: $0, pool: environment.connections,
|
|
authentication: environment.authentication
|
|
)
|
|
}
|
|
},
|
|
.init(ScratchPushTool.self, availableIn: [.acting]) { environment in
|
|
environment.workspace.map {
|
|
ScratchPushTool(
|
|
workspace: $0, pool: environment.connections,
|
|
authentication: environment.authentication
|
|
)
|
|
}
|
|
},
|
|
]
|
|
|
|
/// Names understood by releases that predate persisted presenter IDs. Both
|
|
/// catalogs' tools appear here, because at that point they were one list.
|
|
private static let legacyToolNames: Set<String> = [
|
|
"list_hosts", "ssh_exec", "ssh_wait", "terminal_suggest",
|
|
"sftp_list", "sftp_stat", "sftp_read", "sftp_write", "sftp_mkdir",
|
|
"sftp_move", "sftp_chmod", "sftp_delete", "fetch", "web_search",
|
|
"request_user_input", "request_user_secret", "scratch_list", "scratch_read",
|
|
"scratch_write", "scratch_edit", "scratch_diff", "scratch_delete",
|
|
"scratch_pull", "scratch_push", "present_plan", "manage_tasks", "load_skill",
|
|
]
|
|
}
|