pi coding agent 的原生桌面客户端 — 基于 Next.js 16 + React 19 + Electron 构建的双模式全栈 AI 编程助手
系统采用 Electron 主进程 → Next.js 服务端 → Pi SDK → 持久层四层架构。Web 模式下省略最外层 Electron 壳,直接从浏览器开始。服务端在进程内持有 AgentSession 实例,实现零延迟的事件推送。
主进程入口contextBridge系统托盘子进程就绪探测无界面服务适配GitHub Releases进程树管理SSE流式模型/工具/思维会话树文件浏览分支切换滚动缩略25+ 提供商技能管理统计栏多标签AgentSessionWrapper.jsonl 解析ToolCall 归一化级联重 parent文件锁安全根限制OAuth + API Key (5)38 条路由对话引擎文件管理模型注册模型运行时Skills 加载.jsonl 会话配置认证cwd从用户按下回车到 AI 响应完成,数据经历了浏览器 → API → AgentSession → SSE → 流式渲染的完整循环。AgentSession.prompt() 是异步的,实际内容通过 subscribe 回调推送到 SSE。
onSend(message, images)
/api/agent/[id],body: {type:"prompt", message:"..."}
rpc-manager.send(),查找或创建 AgentSessionWrapper
inner.prompt() — 异步执行,立即返回 {success:true}
content_block_delta 事件
data: {...}
streamReducer 增量更新流式消息,实时渲染
toolCall → 执行 → toolResult 事件成对返回
message_stop 事件到达,isStreaming = false,播放完成音效 🔔
会话浏览和交互对话走完全不同的路径,避免为只读操作创建重量级的 AgentSession。
侧边栏点击会话时触发,直接读取 .jsonl 文件,无需创建 AgentSession。
发送消息时触发,通过 rpc-manager 在进程内创建 AgentSessionWrapper。
全部手写,零 UI 组件库依赖。每个组件通过 CSS 变量实现暗色/亮色主题,使用 React 19 的最新特性构建。顶层 27 个组件文件 + 子组件目录 3 个(chat-input/、session-sidebar/、models-config/)。
顶层整体布局管理:侧边栏 + 聊天区 + 标签页。统一管理分支导航、系统提示词、会话统计等全局状态。
核心消息列表 + SSE 流式处理 + Fork 操作。委托 useAgentSession 处理全部 agent 交互。
输入多功能输入栏:模型选择、工具预设、Thinking Level、文件拖拽(@路径 / 图片附件)、Steer/FollowUp 模式。
列表消息列表容器,内置虚拟化滚动支持,按需挂载消息节点以保持长对话流畅。
渲染单条消息渲染:Markdown + 代码高亮 + Thinking 折叠 + 工具调用内联配对展示。
导航按 cwd 分组的会话树,支持 Fork 父子关系展示、最近 cwd 快捷入口、内嵌文件浏览器。
分支会话内分支切换器。线性链自动压缩,高亮活跃路径,点击叶节点切换分支。
缩略消息列表右侧的滚动缩略导航。用户消息蓝色、助手灰色,拖拽快速定位。
工具三档工具预设:PRESET_NONE(无工具)、PRESET_DEFAULT(4 个)、PRESET_FULL(7 个)。
模型25+ AI 提供商配置面板。品牌图标、API Key、OAuth 登录、默认模型设置。
技能Skills 管理:启用/禁用、搜索远程 Skills、安装。修改 SKILL.md frontmatter。
文件树懒加载目录浏览,20+ 文件类型图标。@ 引用:点击文件在输入框插入路径。
查看文件内容查看:代码高亮、图片展示、音频播放、内置 Myers diff 算法。
标签顶部标签栏:Chat 标签 + 多文件标签。活跃高亮,文件图标匹配。
统计token / cost / 上下文用量统计栏,从 session.contextUsage 与消息累加计算。
图标纯 SVG 单色文件图标,按扩展名匹配 20+ 类型。全部使用 currentColor。
工具FileViewer 的虚拟化算法模块(非 React 组件),按视口裁剪大文件渲染行。
子目录ChatInput 的子组件:AttachmentPreview 图片预览、ModelSelector 模型下拉、PresetSelector 工具预设下拉。
子目录SessionSidebar 的子组件:SidebarHeader、SessionTree / SessionTreeItem、PiAgentTitle、helpers。
子目录ModelsConfig 的子组件,按提供商分组的细粒度配置面板。
精选的现代前端技术栈,每个选择都有明确的目的。零外部状态管理库、零 UI 组件库 — 完全掌控每一行代码。
每个决策背后都有具体的技术挑战。以下是项目中最关键的 8 个设计决策及其成因。
Next.js 热重载会丢弃模块级变量。将 AgentSessionWrapper 注册表存储在 globalThis.__piSessions 中,确保 HMR 后实例不丢失。同理路径缓存 globalThis.__piSessionPathCache 和启动锁 globalThis.__piStartLocks。
Fork 在文件层创建新的 .jsonl,随后通过 startRpcSession() 预注册全新的 wrapper;只有新 wrapper 注册成功后才销毁旧 wrapper。失败时旧会话保持可用,并清理孤儿文件。
Pi SDK 存储格式 {id, name, arguments} 与前端类型 {toolCallId, toolName, input} 不一致。normalizeToolCalls() 在文件加载和 SSE 流两个路径都做了转换,确保前端始终使用统一字段名。
每个 AgentSessionWrapper 启动后开始计时,10 分钟无操作自动销毁,释放内存。每次 send() 或收到事件时重置计时器。当所有工具禁用时,直接设置 inner.agent.state.systemPrompt = "" 清空系统提示词。
Agent 事件是单向推送(服务端→浏览器),SSE 天然适合。30 秒心跳防止代理超时。页面刷新时若 isStreaming 为 true,自动重连 SSE。网络断连时 onerror 有 1 秒自动重连机制。
使用 View Transitions API 实现圆形擦除主题切换。禁用默认 cross-fade,由 Element.animate() 驱动 clip-path: circle() 从点击位置向外扩散。layout.tsx 内嵌脚本避免 FOUC。
file-paths.ts 统一反斜杠→正斜杠;npx.ts 绕过 npx.cmd 的 shell 限制(CVE-2024-27980);bin/pi-web.js 直接调用 next JS 入口避免路径空格问题。
Fork:创建独立 .jsonl 文件,侧边栏显示为子节点。In-session branch:同一文件内 navigate_tree,BranchNavigator 切换。两者互不干扰,分别服务于"另起炉灶"和"探索不同方向"的需求。
完整的目录结构(基于 CodeGraph 索引),按颜色区分各模块职责:青色=目录、紫色=API、绿色=库、琥珀=组件、玫红=Hooks、橙色=Electron、灰色=测试。