The current session's context window is nearly full. This document
captures the project state, the key design decisions that future
maintainers should NOT re-derive, and the known minor issues that
were intentionally left for a follow-up session.
Contents:
- Project overview + current state (tag v0.1.1, branch main @ 310d507)
- Architecture diagram
- Critical code locations
- All 11 route endpoints in one table
- 5 key design decisions (SSE filter, no cell auto-inject, multi-session
model, no partial-render markdown, real interactive permission/question
UI) - written so the next session doesn't accidentally undo them
- How to run the tests
- Known minor issues / TODO list
Co-Authored-By: Claude <noreply@anthropic.com>
7.1 KiB
7.1 KiB
opencode_bridge 交接文档
项目概览
JupyterLab extension,把本地 OpenCode Serve 进程的能力桥接到 notebook 单元。
v0.1.1 (tag 在远端,当前 main 已经是 310d507)。
仓库
/Users/taochen/llm/notebook-extension/opencode-bridge
- 主分支:
main(已切回) - 远端:
http://101.43.40.124:3000/tao.chen/notebook-ai-extension.git - Tag:
v0.1.1(310d507)
架构
Frontend (JupyterLab TS) Server ext (Python tornado) OpenCode Serve
───────────────────────────────────── ───────────────────────────────── ─────────────
opencode_cell_actions.ts ─POST /opencode-bridge/edit──> EditHandler
per-cell AI 按钮 + inline panel _build_request_body(prompt) ──> /session/:id/prompt_async
(只发 prompt,不再自动注入 cell) ──> /session/:id/abort
opencode_inline_prompt.ts /session (list all)
1. SessionSelector 下拉(全 OpenCode sessions)
2. Provider + Model 选择(本地存储)
3. History 区域(流式 markdown 渲染)
4. "📋 插入单元格内容" 按钮(显式 opt-in)
5. 输入框 + send/cancel
SSE 订阅 GET /events?session= GlobalEventHandler ──proxy──> /global/event
→ 5 类事件路由:text/reasoning/tool/permission.asked/question.asked + session.idle
关键代码位置
| 文件 | 作用 |
|---|---|
src/components/opencode_cell_actions.ts |
cell 工具栏按钮 + 流编排(open/edit/SSE/permission/question/session 管理) |
src/components/opencode_inline_prompt.ts |
内嵌 prompt 面板(最复杂,~1300 行) |
src/api/opencode_client.ts |
8 个 session 管理 API + 4 个原有 API(edit/messages/permissions/questions/events) |
src/components/model_selection.ts |
(provider, model) localStorage 持久化 |
opencode_bridge/opencode_client.py |
HTTP 客户端,带 send_message_async + stream_global_events(SSE) + reply_permission/question + list_all_sessions |
opencode_bridge/routes.py |
11 个路由 |
opencode_bridge/session_manager.py |
1 notebook N sessions 模型(_active + _bound) |
opencode_bridge/config.py |
OpencodeConfig Configurable(env var → OpencodeConfig Traitlets) |
主要路由
| 端点 | 方法 | 用途 |
|---|---|---|
/opencode-bridge/edit |
POST | 异步发 prompt_async(只发 prompt + context.notebookPath) |
/opencode-bridge/events?session=X |
GET | SSE 代理 OpenCode /global/event |
/opencode-bridge/session-messages?notebook=X |
GET | 当前 session 的消息历史 |
/opencode-bridge/sessions/all |
GET | OpenCode 上所有 session(全局) |
/opencode-bridge/sessions/notebook?notebook=X |
GET/POST/PUT/DELETE | 绑定 + 列出 + 创建 + 删除 |
/opencode-bridge/sessions/active?notebook=X |
GET/PUT/DELETE | active session 操作 |
/opencode-bridge/permissions/:permId?session=X |
POST | 权限响应(once/always/reject) |
/opencode-bridge/questions/:qId/reply?session=X |
POST | 提问响应 |
关键设计决策(避免来回改)
1. SSE 事件路由(applyEvent)
- 客户端不对内容事件(text/reasoning/tool/idle)做 sessionID 过滤 — 服务端
?session=参数是唯一权威 - 只对
permission.asked/question.asked严格过滤,防止用户误答别 session 的提示 - 这条规则是 bug fix
310d507的核心,改之前会"切 session 后发消息无回复"
2. Cell 上下文不再自动注入
OpenCodeRequest.context收缩为{notebookPath}— 只告诉服务端哪个 notebook_build_request_body只发parts=[{text: prompt}]- 用户通过"📋 插入单元格内容"按钮显式 opt-in 把 cell 源码作为 markdown code block 插入
- 理由:LLM context 严格匹配用户意图,避免被前一个 cell 的 traceback 污染
- 这是
310d507的第二部分
3. 1 notebook N sessions
SessionManager._active(dict[notebook] → active sessionId) +_bound(dict[notebook] → [sessionId, ...])- 新方法:
create_new / bind_existing / set_active / unbind / unbind_active / delete_session / list_sessions_for_notebook / get_active release(notebook)删所有 bound sessions(不只是 active)delete_session(notebook, sid):先调 OpenCode delete,成功后再 unbind(失败时 retry 仍可能)
4. 流式 markdown 渲染(避免 partial-render flicker)
- 流式期间只
textContent += delta,不调marked.parse(否则 fence 没闭合时的部分渲染会闪 toolbar) session.idle时_resetStreamPointers触发一次完整marked.parse渲染- 终态与
setMessages路径(setMessages 重新打开时拉的)完全一致
5. 交互 UI 权限
permission.asked/question.asked事件渲染真实交互组件(按钮 / 输入框)- 点击通过后端路由代理回 OpenCode Serve
- 后端路由:
PUT /session/:sid/permissions/:permId+PUT /session/:sid/question/:qId/reply
测试
- 后端:73 pytest(主要看
test_routes.py+test_session_manager.py) - 前端:72 jest(
opencode_cell_actions.spec.ts是核心,900+ 行覆盖了 SessionSelector / 流式渲染 / permission/question 交互) - 命令:
source .venv/bin/activate && jlpm test && python -m pytest opencode_bridge/tests/
已知小问题 / 待优化
- 多 session 切换时,前端用 PUT
/sessions/notebook一个调用搞定(bind + set active 都做),不再用/sessions/active— 后者保留供"已 bound sessions 之间切换"用 cell_context.ts已删,如果新增"只想要 cellId"的功能,需要重建package.jsonversion 是0.1.1,跟远端 tag 一致(之前是用户手工 bump 上去的)UNIFIED_SYSTEM_PROMPT还在后端 routes.py 里,描述 LLM 的角色("你是一个代码助手...")。如果改 prompt,会同时影响所有 prompt
修改后的交互模式
- 用户在 cell 工具栏点 🪄 按钮
- 内嵌 prompt 出现:SessionSelector(列出 OpenCode 所有 session) + Provider/Model + history + "📋 插入单元格内容" + 输入框 + send/cancel
- 用户输入 prompt,可选按"📋 插入单元格内容"把 cell 源码作为 code block 追加
- send → 后端
prompt_async立即返回{ok, sessionId}+ 订阅 SSE - 流式事件实时渲染,
session.idle触发 markdown 渲染(终态正确) - permission/question 事件渲染交互 UI,点击代理回 OpenCode
- 切换 session → 关闭旧 SSE + bind 新 session(后端 PUT
/sessions/notebook)+ reload history
还没做过的事(如果新会话要继续)
- UI tests (Playwright) — 写了 spec 但没真跑
- 持久化 session 选择(目前只持久化 provider/model)
- cell 类型推断优化(目前用
model.type,真实 cell 应该有 metadata.language) - 后端 session TTL / cleanup(目前不删,除非用户显式 release)
- 真实 OpenCode Serve 接入测试(目前用 mock)