基于 Agent Client Protocol (ACP) 的通用前端组件库,帮你把任意 ACP 兼容的 Agent 包装成开箱即用的桌面/Web 工作台------也可以作为依赖库,快速构建你自己的 Agent 应用。
📦 项目地址:github.com/zvzuola/acp... · MIT License
如果你正在寻找一个能同时满足「我要一个现成的 Agent 工作台」和「我要把它当 SDK 集成进我自己的应用」两种诉求的方案,acp-components 正是为这两种用法设计的。它不是一个只能跑 demo 的玩具,而是一个分层清晰、可插拔、可扩展的组件体系:
@acp-components/core------ 框架无关的 TypeScript 数据层:负责传输、状态管理、业务逻辑,零 React 依赖。你可以用它配 Vue / Svelte / Solid / 原生 JS。@acp-components/react------ React UI 层:上下文 Provider + 25+ 组件 + hooks,负责渲染与交互。
下面我们通过示例应用来快速感受它「开箱即用」的部分,再看它「作为依赖库」时有多灵活。
一、开箱即用:两种宿主示例
仓库自带两个示例应用,分别覆盖 Web 与 桌面(Tauri) 两种宿主形态,它们同时也是你集成时的参考实现。
1. Web Demo(浏览器版 Agent 工作台)
Web demo 用 Vite 驱动,通过一个 WebSocket ↔ stdio 桥接服务器 把本地 Agent 进程暴露给浏览器。这套桥接架构很有代表性:浏览器无法直接 spawn 子进程,于是用 dev:server 起一个 WebSocket 服务,背后用 stdio 拉起 opencode(或 Codex / Claude)的 ACP 子命令,前端用 websocket 传输连进去。
bash
# Terminal 1 --- 桥接服务器(WebSocket ↔ stdio 代理)
pnpm dev:server # 默认 opencode
# 或换 Codex / Claude
pnpm dev:server-codex
pnpm dev:server-claude
# Terminal 2 --- Vite 前端
pnpm dev # http://localhost:5173
启动后你将看到一个完整的工作台 Shell:左侧侧边栏(导航 + 会话列表 + 底部菜单),右侧主区域随导航切换 Sessions / Skills / New Session / Settings 等视图。


2. Tauri Desktop(原生桌面版)
Tauri 版则直接用 stdio 传输 拉起本地 Agent 进程,省去了桥接服务器------这是真正意义上的「本地优先」桌面应用。它用 Tauri 的 plugin-fs / plugin-dialog 实现原生文件读写与目录选择,Monaco 做代码预览。
bash
pnpm dev:tauri # 桌面开发模式
pnpm build:tauri # 生产构建

关键差异:
{ type: 'stdio' }配置需要宿主提供platform.process.createStdioTransport能力(即真的能 spawn 子进程)。浏览器无法 spawn,所以 Web demo 走websocket连桥接服务器;桌面端走stdio直连。同一个组件库,两种传输策略,同一套 UI。
二、当前支持的能力一览
示例应用的「能用」背后,是组件库一整套能力。下面按使用场景梳理:
多 Agent · 多工作区 · 多会话
- 多 Agent :可同时连接多个 ACP Agent,各自独立的传输、能力与会话管理。用户运行时添加的 Agent 会持久化到
storage('agents'),下次启动自动恢复;内置(宿主提供的)Agent 不可被用户移除。 - 多工作区(Workspace) :按工作目录(cwd)组织会话,工作区列表由
<PlatformWorkspacesAuto>自动持久化到storage('workspaces')。 - 多会话 + 分屏:可在可调整大小的分栏中并排运行/查看多个会话,每个会话状态完全隔离(消息、流式、工具调用、权限)。逐会话的 chunk 批处理保证多路并发流互不干扰。
传输层:Stdio / HTTP / WebSocket / Custom
每个 Agent 独立配置传输,按宿主能力挑选:
rust
// Stdio --- 直接 spawn Agent 进程(Electron / Tauri / Node 桌面)
{ id: 'desktop', name: 'Desktop', transport: { type: 'stdio', command: 'opencode', args: ['acp'] } }
// HTTP --- 通过 HTTP POST 连接
{ id: 'http', name: 'HTTP', transport: { type: 'http', url: 'http://localhost:8080/acp' } }
// WebSocket --- 连接桥接服务器(浏览器环境)
{ id: 'ws', name: 'WebSocket', transport: { type: 'websocket', url: 'ws://127.0.0.1:3100' } }
// Custom --- 自己实现 AcpTransport(Tauri IPC / Electron IPC / iframe postMessage...)
{ id: 'custom', name: 'Custom', transport: { type: 'custom', transport: myTransport } }
会话视图与会话面板
SessionView 是核心工作面:左侧 SessionPanes(可分屏聊天列),右侧 SessionPanel 自带 Files 标签页 (FileTree + FileViewer 两栏),并支持宿主注入额外标签页。
- FileTree :按工作区的懒加载文件树,支持展开/折叠、刷新、打开时定位。可选实时监听(
watchFileTree)。 - FileViewer :面板内置 Monaco 文件查看器,语法高亮 +
revealLine行定位。
聊天 · 流式 · 工具调用 · 计划
- ChatView :把消息按用户/Agent 轮次分组,内联渲染 plan、usage 进度条、config 面板与权限提示。
- 流式 UX:实时内容与思考流式(逐会话批处理),带动画指示器、实时工具调用状态、token 用量追踪。
- ToolCallCard:展示工具调用名称、状态、输入/输出、文件位置与 diff。
- PlanView / ThoughtView / DiffView:计划条目、思考链、文件改动 diff 各自的可视化。
权限与认证
- 权限流程 :基于 Promise 的逐会话权限流,
PermissionPrompt在ChatView内联渲染,批准或拒绝工具调用请求;会话关闭或 Agent 断开时自动拒绝。 - 认证 :内置
LoginDialog,支持 env_var 与 terminal 两种认证方式,env var 表单输入,5 分钟超时。
Skills 目录 · 设置 · 命令面板
- SkillView :通过
listSkills()实时拉取每个已连接 Agent 的技能目录,按作用域(用户级 vs 项目 cwd 级)分组,带搜索。 - SettingsView :全页设置(Appearance 主题 + Agents 管理),可通过追加
SETTINGS_SECTIONS扩展。 - CommandPalette:可用命令的斜杠命令面板。
主题与国际化
- 主题 :暗色 / 亮色内置主题,全部走 CSS 自定义属性(
--acp-*),运行时useSettings().setTheme()切换;支持自定义主题覆盖变量。 - i18n :内置 en-US / zh-CN,i18next 自动从宿主
Platform.system.getLocale()检测(Web:navigator.language,桌面: 系统语言)。
三、作为依赖库:构建你自己的 Agent 应用
示例应用之外,acp-components 的真正价值在于「当依赖库用」。最小可用配置只需三个 Provider 套一层 WorkbenchShell:
javascript
import {
I18nProvider, PlatformProvider, AcpProvider,
WorkbenchShell, LoginDialog, useAcpStore,
} from '@acp-components/react';
import { createWebPlatform } from './webPlatform'; // 你自己的宿主 Platform 工厂
function AppInner() {
const activeSessionId = useAcpStore((s) => s.activeSessionId);
return (
<>
<WorkbenchShell sessionId={activeSessionId} />
<LoginDialog />
</>
);
}
function App() {
return (
<PlatformProvider platform={createWebPlatform()}>
<I18nProvider>
<AcpProvider
agents={[
{ id: 'main', name: 'Main Agent',
transport: { type: 'websocket', url: 'ws://127.0.0.1:3100' } },
]}
theme="dark"
>
<AppInner />
</AcpProvider>
</I18nProvider>
</PlatformProvider>
);
}
多 Agent 集成示例
同时连接不同模式、不同用途的多个 Agent:
bash
<AcpProvider
agents={[
{ id: 'craft', name: 'Craft Agent', transport: { type: 'websocket', url: 'ws://127.0.0.1:3100' } },
{ id: 'ask', name: 'Ask Agent', transport: { type: 'stdio', command: 'opencode', args: ['acp', '--mode', 'ask'] } },
]}
theme="dark"
>
<App />
</AcpProvider>
框架无关的核心层
@acp-components/core 零 React 依赖,可直接配 Vue / Svelte:
php
import { acpStore, sessionStore, fileTreeStore, skillStore, createAcpProvider, sendPrompt } from '@acp-components/core';
const provider = createAcpProvider(
{ agents: [{ id: 'main', name: 'Main', transport: { type: 'stdio', command: 'opencode', args: ['acp'] } }] },
stdioFactory, // 宿主的 spawn 能力;无此能力传 null
);
provider.subscribe(() => { if (provider.ready) console.log('All agents connected!'); });
// 直接读 vanilla store
acpStore.getState().workspaces;
skillStore.getState();
// 命令式动作
const client = provider.getClient('main');
await sendPrompt(client!, sessionId, blocks);
// 动态增删 Agent(运行时添加的会持久化)
await provider.addAgent({ id: 'analyze', name: 'Analyze', transport: { type: 'websocket', url: 'ws://...' } });
await provider.removeAgent('analyze');
provider.destroy();
扩展点
| 扩展 | 做法 |
|---|---|
| 自定义传输 | 实现 AcpTransport 接口,按 { type: 'custom', transport: instance } 传入。真实场景:Tauri IPC、Electron IPC、Chrome 扩展消息、iframe postMessage。 |
| 自定义 Platform | 实现 Platform 接口(fs? / dialogs? / openExternalEditor? / updater? / system?,storage 必填),按切片/方法是否存在表达能力,调用处用 ?. 守卫。参考 createWebPlatform() / createTauriPlatform()。 |
| 扩展工作台 | 向 WorkbenchShell 注入 navItems(主区视图)与向 SessionView 注入 tabs(侧面板标签页),例如注入一个 Terminal 视图。 |
| 动态 Agent 管理 | useAcpContext() 的 addAgent / removeAgent 带持久化;内置 Agent 不可移除。 |
能力(capability)与数据(data)正交:
Platform拥有「原生能力」(如fs.readDirectory),而「要 spawn 哪个 Agent」(command/args/env)只是AgentConfig.transport上的纯数据------两者分离,互不耦合。
四、两种用法,怎么选?
| 场景 | 推荐 |
|---|---|
| 想要一个现成的多 Agent 工作台,支持接入各种 ACP Agent | 直接用 examples/tauri(桌面)或 examples/demo(Web)作为起点 |
| 已有 React 应用,想集成 Agent 工作台 UI | @acp-components/react 作为依赖,按需引入组件 |
| 非 React 技术栈(Vue/Svelte/原生),只要协议与状态层 | @acp-components/core 单独使用 |
| 自研传输(IPC / postMessage / 自定义协议) | 实现 AcpTransport,走 { type: 'custom' } |
| 自研宿主能力(文件、对话框、更新器) | 实现 Platform 接口,注入 <PlatformProvider> |
五、技术栈与状态
| 层 | 技术 |
|---|---|
| 协议 | @agentclientprotocol/sdk |
| 状态 | Zustand v5(vanilla store,无 React 依赖) |
| UI | React 18 / 19 |
| 代码编辑器 | Monaco(可选 peer dep,仅 FileViewer 用) |
| i18n | i18next + react-i18next |
| Markdown | react-markdown + remark-gfm |
| 样式 | SCSS Modules + CSS 自定义属性 |
| 构建 | Vite 6(库模式,ESM/CJS 双产物) |
| 测试 | Vitest + @testing-library/react + jsdom |
| 包管理 | pnpm workspace monorepo |
安装
sql
pnpm add @acp-components/core @acp-components/react
Peer 依赖:react (^18 || ^19)、react-dom (^18 || ^19)。monaco-editor 为可选 peer dep------仅当你用内置 FileViewer 时才需要。
六、下一步
- 想跑起来看看:克隆仓库后按
README.md的 Web Demo / Tauri Desktop 步骤启动。 - 想当依赖用:从上面「最小可用配置」开始,按你的宿主实现
Platform。 - 想扩展:从「扩展点」表格里挑一个方向------自定义传输、自定义 Platform、注入工作台视图。
ACP 的目标是让 Agent 与客户端解耦,acp-components 的目标是让前端不再重复造轮子。如果你在构建 Agent 工作台,欢迎试试------也欢迎 issue / PR。
📦 项目地址:github.com/zvzuola/acp... · MIT License