Integrate AgentKit 0.18.1 with native authorization on macOS and iOS, device-local OAuth credentials, V43 configuration persistence, and Assistant authorization prompts. Add HTTPS callback metadata, keep native callbacks safe across actor boundaries, and distinguish authorization setup failures from cancellation. Extend the external MCP server to negotiate 2025-03-26.
74 lines
3.7 KiB
Swift
74 lines
3.7 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,
|
|
panes: AgentPaneAccess? = nil,
|
|
chatModels: (any AgentChatModelCatalog)? = nil,
|
|
chats: (any AgentChatCoordinating)? = nil,
|
|
chatSource: AgentChatSourceContext? = nil
|
|
) -> 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,
|
|
chatModels: chatModels, chats: chats, chatSource: chatSource,
|
|
mcpServers: servers,
|
|
mcpClient: ai.mcpAuthorization.client
|
|
),
|
|
additional: TermindAgentTools.tools(
|
|
in: TermindAgentToolEnvironment(
|
|
hosts: hosts, metrics: metrics, connections: connections,
|
|
authentication: authentication,
|
|
background: background,
|
|
workspace: scratch, panes: panes
|
|
))
|
|
)
|
|
}
|
|
return registry
|
|
}
|
|
}
|