项目背景 链接到标题

用 Electron 封装 PI Coding Agent SDK,做一个轻量桌面 AI 聊天客户端。目标是:

  • 提供 GUI 界面替代终端交互
  • 跨平台(macOS + Windows)
  • 集成 PI SDK 的 tool calling、流式回复能力

源码:MarshalW/electron-pi

架构 链接到标题

Renderer (index.html)
    ↕ contextBridge (preload.js)
Main Process (main.js)
    ↕ @earendil-works/pi-coding-agent
AgentSession (Tool calling, LLM 通信)

三进程架构:

  • Renderer:纯前端 HTML/CSS/JS,显示聊天界面
  • Preload:contextBridge 暴露 window.pi.* API
  • Main:Electron 主进程,管理窗口、PI SDK、IPC 通道

IPC 通道设计:

Channel 方向 说明
config:get Renderer → Main 读取配置
config:save Renderer → Main 保存配置
session:create Renderer → Main 创建 AgentSession
session:send Renderer → Main 发送 prompt
session:abort Renderer → Main 中止回复
pi:event Main → Renderer 推送流式事件

PI SDK 集成 链接到标题

模型解析 链接到标题

createAgentSession 需要完整的模型对象(含 baseUrlapi 等字段),不能只传 { provider, model }

const modelRegistry = ModelRegistry.inMemory(authStorage)
const model = modelRegistry.find(config.provider, config.model)
if (!model) throw new Error(`模型 ${config.model} 未找到`)

const result = await createAgentSession({
  model,
  tools: ['read', 'bash', 'edit', 'write', 'grep', 'find'],
  resourceLoader: new DefaultResourceLoader({
    cwd: expandHome(config.cwd),
    agentDir: getAgentDir(),
  }),
  sessionManager: SessionManager.inMemory(),
  authStorage,
})

Auth 注入 链接到标题

process.env.DEEPSEEK_API_KEY = config.apiKey
const authStorage = new AuthStorage(new InMemoryAuthStorageBackend())
authStorage.setRuntimeApiKey(config.provider, config.apiKey)

事件订阅 链接到标题

piSession.subscribe((event) => sendToRenderer('pi:event', event))

踩坑记录 链接到标题

1. 模型字段名错误 链接到标题

误传 { provider, model } 而非 { provider, id },导致 model.baseUrlundefined,SDK 内部 baseUrl.includes() 崩溃。修复:用 ModelRegistry.find() 解析完整模型对象。

2. SDK 错误处理 bug 链接到标题

error-body.jserror.message.includes(body) 未做空值保护,原始 API 错误被掩盖,UI 显示空气泡。临时修补:加 error.message && 判断。

3. 消息气泡渲染 链接到标题

SDK 在一个 turn 内发送两次 message_start/end(assistant)——第一次含 thinking + toolCall,第二次含最终回答。方案:不在 message_end 关闭气泡,改在 agent_end 关闭。工具调用和结果合并到同一气泡,可折叠。

4. Portable 单文件 ICU 错误 链接到标题

Windows portable 单文件 exe 报 Invalid file descriptor to ICU data received。修复:在 electron-builder.yml 中配置 asarUnpack 将 ICU 数据文件解压到 asar 外部:

asarUnpack:
  - "**/icudtl*"
  - "**/*.dat"

UI 设计 链接到标题

  • 初次配置页 vs 模态对话框重新配置
  • 消息气泡:用户蓝色、助手白色
  • 工具调用块:可折叠,显示工具名 + 参数预览 + 耗时 + 结果
  • Markdown 渲染:本地引入 marked.js
  • 智能滚动:用户到底部时跟随,向上翻看时不强制滚动

跨平台构建 链接到标题

macOS 链接到标题

npm run build:mac
# 输出 release/ElectronPi-1.0.0.dmg

无开发者签名时配置 identity: null,首次打开需右键 → 打开,或 xattr -cr /Applications/ElectronPi.app

Windows 链接到标题

# electron-builder.yml
win:
  target:
    - portable
asarUnpack:
  - "**/icudtl*"
  - "**/*.dat"
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npx electron-builder --win

Portable 单文件 .exe,无需安装。无签名时 SmartScreen 拦截,点"更多信息 → 仍要运行"。

总结 链接到标题

  • 技术栈:Electron 43 + PI SDK 0.80 + marked.js
  • 核心代码不到 500 行
  • 适合作为 PI SDK 集成的参考实现