当前位置: 首页 > news >正文

深入 SwiftWork(第 0 篇):用 SwiftUI 构建一个 Agent 可视化工作台

本文是「深入 SwiftWork」系列第 0 篇。

前面七篇文章加上番外篇,我们把 Open Agent SDK 的内部机制翻了个底朝天——Agent Loop、工具系统、MCP 集成、多 Agent 协作、会话持久化、多 LLM 支持。番外篇还把 SDK 塞进了一个 macOS 原生应用 Motive 里跑了跑。

但 Motive 只是一个替换后端的实验。真正的问题是:拿到 SDK 之后,怎么从零开始构建一个完整的 Agent 应用? SDK 给了你 Agent 的"大脑"——但用户看到的不是 Agent Loop,而是一个界面。Agent 在调工具、读文件、执行命令的时候,用户需要知道它在干什么、进展如何、结果是什么。

这就是 SwiftWork 要解决的问题——一个 macOS 原生的 Agent 可视化工作台。

SwiftWork 是什么

SwiftWork 是一个 macOS 原生的 AI Agent 桌面应用。它做的事情用一句话概括:让用户看到 Agent 正在做什么。

具体来说:

  • 用户在输入框输入 prompt
  • Agent 在后台跑 Agent Loop(调工具、读文件、执行命令)
  • 每一步执行都实时显示在时间线上——文本输出、工具调用、执行结果、错误信息,全部可视化

这不是一个终端里的 CLI 工具,也不是一个网页应用。它是用 SwiftUI 写的原生 macOS 应用,用 @Observable 做状态管理,用 SwiftData 做数据持久化,用 Apple 原生的渲染管线做 Markdown 和代码高亮。

为什么做 SwiftWork

做 SwiftWork 有两个动机。

第一,SDK 需要一个"展示应用"。 SDK 的 31 个示例项目覆盖了各种用法——流式输出、自定义工具、MCP 集成——但都是命令行工具。SDK 的能力需要一个 GUI 来完整展示,尤其是工具调用的可视化、事件流的实时渲染这些 CLI 做不好的事情。

第二,Agent 应用的可视化是一个被低估的问题。 当前的 Agent 应用(包括 Claude Code 自己)在终端里跑,用户看到的是滚动的文字流。但 Agent 在执行一个复杂任务时,可能调用十几次工具、读写多个文件、执行多条命令。终端里的线性输出很难让用户理解全局进展。SwiftWork 试图用事件时间线和工具卡片来改善这个问题。

架构总览

SwiftWork 采用事件驱动架构。整条数据流是一条单向管线:

SDK Agent Loop││  AsyncStream<SDKMessage>▼
AgentBridge (@Observable)││  EventMapper.map() → AgentEvent▼
AgentBridge.events: [AgentEvent]││  SwiftUI 自动响应 @Observable 变化▼
TimelineView → 各 EventView

四个角色:

组件 职责
Agent Loop SDK 提供,跑 Agent 的推理循环,产出 SDKMessage
AgentBridge 消费 AsyncStream<SDKMessage>,映射成 AgentEvent,管理生命周期
EventMapper 纯函数,SDKMessage → AgentEvent 的类型映射
TimelineView SwiftUI 视图,消费 AgentEvent 数组,渲染时间线

核心设计决策:视图不直接接触 SDK 类型。 AgentEvent 是 SwiftWork 自己定义的 UI 模型,跟 SDK 的 SDKMessage 完全解耦。视图只认 AgentEvent,不知道也不关心事件来自哪个 SDK 版本。

核心数据流

用户发一条消息,整条管线的运转过程:

1. 用户输入 → Agent 启动

// AgentBridge.swift
func sendMessage(_ text: String) {// 用户消息直接追加到事件列表let userEvent = AgentEvent(type: .userMessage, content: text, timestamp: .now)appendAndPersist(userEvent)isRunning = true// 在后台 Task 中消费 streamcurrentTask = Task { [weak self] inlet stream = agent.stream(text)for await message in stream {let event = EventMapper.map(message)self.appendAndPersist(event)}self.isRunning = false}
}

用户消息先追加到事件列表(即时显示),然后启动一个 Task 来消费 SDK 的 AsyncStream

2. SDKMessage → AgentEvent

EventMapper 是一个纯函数,把 SDK 的 18 种 SDKMessage 映射成 SwiftWork 的 AgentEventType

// EventMapper.swift
static func map(_ message: SDKMessage) -> AgentEvent {switch message {case .assistant(let data):return AgentEvent(type: .assistant, content: data.text,metadata: ["model": data.model, "stopReason": data.stopReason], timestamp: .now)case .toolUse(let data):return AgentEvent(type: .toolUse, content: data.toolName,metadata: ["toolName": data.toolName, "toolUseId": data.toolUseId, "input": data.input],timestamp: .now)case .toolResult(let data):return AgentEvent(type: .toolResult, content: data.content,metadata: ["toolUseId": data.toolUseId, "isError": data.isError], timestamp: .now)// ... 18 种消息类型}
}

为什么要有这一层映射?因为 SDK 的类型是给 Agent 运行时用的,它包含很多 UI 不需要的细节。AgentEvent 只保留 UI 渲染需要的字段:类型、内容、元数据、时间戳。视图不需要知道 SDKMessage 的枚举定义,只需要处理 AgentEventType

3. 事件追加 + 持久化

每条事件都经过 appendAndPersist,同时更新内存数组和 SwiftData 数据库:

private func appendAndPersist(_ event: AgentEvent) {events.append(event)processToolContentMap(for: event)guard event.type != .partialMessage,let eventStore, let currentSession else { return }try eventStore.persist(event, session: currentSession, order: eventOrder)eventOrder += 1trimOldEvents()
}

注意 partialMessage 不持久化——它是流式文本的中间片段,累积完成后会生成一条完整的 .assistant 事件。

4. SwiftUI 自动渲染

AgentBridge 标记了 @Observable。当 events 数组变化时,TimelineView 自动重新渲染:

// TimelineView.swift
ForEach(virtualizedEvents) { event ineventView(for: event)
}

eventView 根据 event.type 分派到不同的视图组件——UserMessageViewAssistantMessageViewToolCardViewSystemEventView 等。

项目结构

SwiftWork/
├── App/
│   ├── SwiftWorkApp.swift            # @main 入口,注册 SwiftData 模型
│   └── ContentView.swift             # NavigationSplitView 根视图
├── Models/
│   ├── UI/                           # UI 模型层
│   │   ├── AgentEvent.swift          # 事件模型(SwiftUI 渲染用)
│   │   ├── AgentEventType.swift      # 18 种事件类型枚举
│   │   ├── ToolContent.swift         # 工具内容(配对 toolUse + toolResult)
│   │   ├── PermissionDecision.swift  # 权限决策
│   │   └── AppError.swift            # 错误模型
│   └── SwiftData/                    # 持久化模型层
│       ├── Session.swift             # 会话
│       ├── Event.swift               # 持久化事件
│       ├── AppConfiguration.swift    # 应用配置
│       └── PermissionRule.swift      # 权限规则
├── ViewModels/
│   ├── SessionViewModel.swift        # 会话 CRUD
│   └── SettingsViewModel.swift       # 设置管理
├── Views/
│   ├── Sidebar/                      # 会话列表
│   ├── Workspace/
│   │   ├── Timeline/
│   │   │   ├── TimelineView.swift    # 时间线主视图 + 虚拟化
│   │   │   ├── EventViews/           # 各事件类型视图 + ToolCardView
│   │   │   │   ├── ToolRenderers/    # 5 个内置工具渲染器
│   │   │   │   ├── StreamingTextView.swift
│   │   │   │   ├── MarkdownContentView.swift
│   │   │   │   └── ...
│   │   │   └── Inspector/            # 事件详情面板
│   │   └── InputBar/                 # 消息输入框
│   ├── Settings/                     # 设置界面
│   ├── Onboarding/                   # 首次启动引导
│   └── Permission/                   # 权限审批弹窗
├── SDKIntegration/
│   ├── AgentBridge.swift             # SDK ↔ ViewModel 桥接
│   ├── AgentBridge+ToolContentMap.swift  # 工具内容配对逻辑
│   ├── EventMapper.swift             # SDKMessage → AgentEvent
│   ├── ToolRenderable.swift          # 工具渲染协议
│   └── ToolRendererRegistry.swift    # 工具渲染注册表
├── Services/
│   ├── CodeHighlighter.swift         # Splash 代码高亮
│   ├── MarkdownRenderer.swift        # swift-markdown 渲染
│   ├── KeychainManager.swift         # API Key 安全存储
│   ├── EventStore.swift              # 事件持久化接口
│   ├── AppStateManager.swift         # 应用状态保存/恢复
│   └── TitleGenerator.swift          # 自动生成会话标题
└── Utils/└── Extensions/                   # 颜色、日期格式化等

结构上的核心分层:

  • Models/UI/Models/SwiftData/ 是两套独立的模型层。UI 模型(AgentEvent)是给 SwiftUI 渲染用的,SwiftData 模型(Event)是给持久化用的。两者之间有转换逻辑。
  • SDKIntegration/ 是 SDK 和 UI 之间的桥梁层。视图和 ViewModel 不直接 import OpenAgentSDK
  • Views/ 按功能分区,每个事件类型一个视图文件,工具渲染器有独立的子目录。

技术选型

组件 选择 原因
语言 Swift 6.1 严格并发 Agent SDK 要求,Sendable 保证线程安全
UI SwiftUI + @Observable macOS 14+ 支持,跟 Swift 并发配合好
持久化 SwiftData 跟 SwiftUI 深度集成,比 Core Data 简洁
Markdown swift-markdown (Apple) 原生 Apple 库,CommonMark 兼容
代码高亮 Splash (John Sundell) 轻量、支持 Swift/Python/JS/Bash
自动更新 Sparkle 2.x macOS 应用更新的标准方案
Agent SDK Open Agent SDK 自己写的 SDK,当然用自己的

系列预告

这篇文章给了一个全景图。接下来的文章会逐层拆开,看每个子系统怎么实现:

  • 第 1 篇:SDK 集成层——AgentBridge 如何消费 AsyncStream<SDKMessage>、映射事件、管理生命周期
  • 第 2 篇:事件时间线——18 种事件的可视化、流式文本、虚拟化
  • 第 3 篇:Tool Card 系统——ToolRenderable 协议和可扩展的工具渲染器
  • 第 4 篇:数据层与服务——SwiftData 持久化、状态恢复、Markdown 渲染、代码高亮

相关链接

  • SwiftWork:terryso/SwiftWork
  • Open Agent SDK:terryso/open-agent-sdk-swift
http://www.jsqmd.com/news/738124/

相关文章:

  • 从Word到LaTeX的终极转换指南:docx2tex完整解决方案
  • [具身智能-533]:常见的中间件软件有哪些?
  • DoL-Lyra终极整合包:5分钟打造个性化游戏美化体验
  • 微信小程序逆向工程深度解析:wxappUnpacker技术实战指南
  • 微信好友智能检测:3步找出谁删了你,轻松管理社交关系
  • 终极指南:如何用llamafile实现LLM单文件分发与前端运行的完整方案
  • 2026年必备:高效降低AI率工具推荐,免费降AI率方法轻松搞定论文降AI - 降AI实验室
  • 2026年必备降AI指南:免费工具+改写技巧,一键拯救高AI率论文 - 降AI实验室
  • 【低轨卫星星载C程序功耗优化权威指南】:20年航天嵌入式专家亲授7大不可绕过的硬件协同降耗铁律
  • 8大网盘直链解析终极指南:一键获取真实下载地址告别限速烦恼
  • java后端开发学习
  • Rusted PackFile Manager:全面战争MOD开发的现代化革命
  • 实用话费充值卡回收技巧 - 京顺回收
  • 别再写UDF了!用Fluent表达式搞定出口温度控制入口流速的完整流程(附案例文件)
  • 微信通讯录隐形清理指南:如何发现并管理那些单向删除你的好友?
  • 高效实现B站缓存视频合并的完整解决方案:智能处理离线视频与弹幕挂载
  • 别再瞎调了!STM32F411时钟配置避坑指南:从HSI切换到HSE的完整流程与仿真验证
  • Phi-4-mini-reasoning快速部署:基于Docker Compose的多服务协同部署模板
  • 如何轻松解锁QQ音乐加密文件:qmcdump让你的音乐真正自由
  • C语言农业物联网传感器驱动框架设计(工业级抗干扰驱动架构首次公开)
  • 开发 AI 应用时如何利用 Taotoken 统一管理多模型调用链路
  • Qwerty Learner终极架构揭秘:200+词库的本地存储与实时学习分析技术深度解析
  • 2026年3月有实力的遮阳棚厂家推荐,伸缩篷/景观棚/膜结构/体育看台/膜结构车棚/电动推拉棚,遮阳棚生产厂家怎么选择 - 品牌推荐师
  • Windows窗口管理的革命:Traymond如何通过系统托盘优化你的工作空间
  • Jetson Orin NX到手后必做的5件事:从输入法到远程SSH,保姆级配置清单
  • 微信好友关系智能检测:高效管理社交网络的终极方案
  • 初创团队如何利用 Taotoken 统一管理分散的 AI 模型调用
  • 终极网盘直链下载助手:一键获取八大平台真实下载链接的完整指南
  • 告别手动建模:用Python CPLEX高效求解供应链网络优化问题(附完整代码)
  • 突破性解决方案:三分钟搞定Adobe扩展安装难题