📰 来源: 博客园
本文是「深入 Open Agent SDK (Swift)」系列番外篇。
前七篇文章从各个子系统分析了 Open Agent SDK 的设计。但 SDK 写得好不好,最终得放到真实项目里验证。这篇文章记录我把 SDK 集成到一个开源 macOS 原生 Agent 应用——Motive——的完整过程:从理解原有架构到实现替换,以及一路上踩过的坑。
Motive 是一个 macOS 原生的 AI Agent 桌面应用,用 SwiftUI 写的。它的核心交互是:用户输入 prompt → Agent 在后台跑 Agent Loop(调工具、读文件、执行命令)→ 流式输出结果到 UI。
在集成 SDK 之前,Motive 的 Agent 后端长这样:
Motive App (SwiftUI)
└── OpenCodeBridge (actor)
├── OpenCodeServer — 启动外部 opencode 二进制进程 (opencode serve)
├── SSEClient — 通过 Server-Sent Events 接收流式事件
└── OpenCodeAPIClient — 通过 REST API 发送 prompt、回复权限请求
每次用户发 prompt,Motive 要:
opencode serve 进程(如果没在跑的话)POST /sessions 创建会话POST /sessions/{id}/prompt 发送 prompt这套架构能用,但有几个问题:
opencode CLI,Motive 还要处理二进制签名、路径查找SDK 的出现正好给了另一种可能——把 Agent Loop 直接跑在应用进程内。
目标:SDKBridge
我想做的替换:不启动外部进程,不经过 HTTP,直接在 Motive 进程内用 SDK 的 Agent.stream() 跑 Agent Loop。
Motive App (SwiftUI)
└── BackendBridge (enum wrapper)
├── .opencode → OpenCodeBridge (原有架构,保留)
└── .sdk → SDKBridge (新增,用 OpenAgentSDK)
└── Agent.stream() → 直接在进程内跑 Agent Loop
保留原有的 OpenCodeBridge 作为备选,让用户可以在设置中切换后端类型。这是一个务实的决定——万一 SDK 后端有问题,用户还能切回去。
第一步:BackendBridge 抽象层
原有的 OpenCodeBridge 是一个 actor,Motive 的 AppState 直接跟它交互。现在要加一个平行的 SDKBridge,需要一个分派层。
我用了一个 enum 而不是 protocol:
enum BackendBridge {
case opencode(OpenCodeBridge)
case sdk(SDKBridge)
func submitIntent(text: String, cwd: String, ...) async { ... }
func interrupt() async { ... }
func stop() async { ... }
// ...
}
为什么不用 protocol?因为 OpenCodeBridge 和 SDKBridge 的能力不完全一样。OpenCodeBridge 有权限请求(permission)、问题回复(question)等 SDK 后端不需要的概念。用 enum 可以在共享接口上做统一分派,同时保留各自特有的方法:
// OpenCode-only 方法,SDK 后端直接 no-op
func replyToQuestion(requestID: String, answers: [[String]], ...) async {
guard case .opencode(let bridge) = self else { return }
await bridge.replyToQuestion(requestID: requestID, answers: answers, ...)
}
对于 AppState 来说,大部分代码不需要改——它调 bridge.submitIntent(),至于底层是 HTTP 还是 SDK,它不关心。
第二步:SDKBridge 核心——361 行的 Actor
SDKBridge 是整个替换的核心。它是一个 actor,负责:
Configuration(API key、model、MCP servers 等)createAgent() 创建 AgentAgent.stream() 获取流式响应SDKMessage 映射成 Motive 已有的 OpenCodeEventactor SDKBridge {
struct Configuration: Sendable {
let apiKey: String
let model: String
let provider: String // "anthropic", "openai", etc.
let baseURL: String?
let debugMode: Bool
let projectDirectory: String
let mcpEntries: [String: MCPEntry]?
let env: [String: String]?
let skillDirectories: [String]?
}
struct MCPEntry: Sendable {
let command: String
let args: [String]?
let env: [String: String]?
}
}
MCPEntry 是中间类型——Motive 的配置系统有自己的 MCP 描述格式,在传入 SDK 之前转成
🔗 原文链接: 点击阅读原文
文章评论