Files
Termind/Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift
T

69 lines
3.5 KiB
Swift

import AgentKit
import AgentKitScrubber
import Foundation
import TermindApplication
import TermindDomain
@MainActor
public enum AssistantToolRegistry {
public static func make(
conversation: ConversationModel, model: AIModel, provider: ModelProvider,
skills skillCatalog: AgentSkillCatalog, channel: AgentEventChannel,
ai: AIConfigurationModel, hosts: AgentHostResolver,
metrics: any MetricsSnapshotReading, connections: AgentConnectionPool,
authentication: AgentSSHAuthenticationRegistry, background: AgentBackgroundCommands
) -> AgentToolRegistry {
let native =
ai.webSearch.isEnabled && ai.webSearch.prefersBuiltIn && provider.supportsNativeWebSearch(model: model)
let localSearch: ScrubberWebClient? = ai.webSearch.isEnabled && !native ? ScrubberWebClient() : nil
var registry = AgentToolRegistry([AnyAgentTool]())
if model.abilities.contains(.toolCall) {
// One workspace per conversation, not per run: a file staged before the
// user interrupted, or before they typed a follow-up, is still there for
// the run that answers them. Isolation comes from the scope directory
// being keyed by conversation, so one chat can never read what another
// pulled off a host.
let scratch = AgentScratchWorkspace(
conversationID: conversation.id,
applicationName: TermindAgentPolicy.scratchDirectoryName
)
// One client for both, rather than two default-constructed ones: `fetch`
// and `scratch_fetch` read the same web the same way and differ only in
// where the page ends up.
let web = ScrubberWebClient()
// Both write through the one ordered channel the rest of the run
// uses, rather than reaching into the conversation directly: a tool
// that touched `@Observable` state from a task group would be
// mutating the UI from off the main actor.
let servers = ai.usableMCPServers.map { server in
(server: server, bearerToken: ai.secret(for: server.credentialRef))
}
// Local definitions are registered once per catalog — the runtime's
// built-in groups, then ours; discovered MCP definitions join the
// same resolved registry after both, so neither a Termind name nor a
// remote name can shadow a built-in.
registry = AgentToolCatalog.registry(
builtIn: AgentBuiltInToolConfiguration(
workspace: scratch,
web: web,
search: localSearch,
secretConsumerTools: [TermindAgentPolicy.sshExecToolName],
tasks: AgentTaskList { channel.emit(.tasksUpdated($0)) },
plans: AgentPlanRecorder { channel.emit(.planPresented($0)) },
skills: skillCatalog,
mcpServers: servers,
mcpClient: MCPClient(clientInfo: TermindAgentPolicy.mcpClientInfo)
),
additional: TermindAgentTools.tools(
in: TermindAgentToolEnvironment(
hosts: hosts, metrics: metrics, connections: connections,
authentication: authentication,
background: background,
workspace: scratch
))
)
}
return registry
}
}