Opencode 核心设计-Session会话机制

Session 会话机制详解

本系列文章皆基于开源 Vibecoding 工具 Opencode 源码进行详细拆解。

源码链接:github.com/anomalyco/o...


1. 什么是 Session?

一句话概括:Session 管理一次 AI 对话的生命周期,包含用户输入、LLM 调用、工具执行、压缩处理等完整过程。

当你运行 opencode run "帮我写个 hello world" 时,一个 Session 就被创建了,直到对话结束。

Session 解决的问题

问题 说明
上下文记忆 AI 需要记住之前的对话内容
状态管理 记录文件修改、命令执行结果
历史追溯 支持回顾、分享、继续会话

2. 核心概念:三层结构

Session 由三层组成:

scss 复制代码
Session (会话)
  └── Message (消息) - user/assistant/system 角色
        └── Part[] (部分) - text/tool/reasoning 等类型

2.1 Session

一个独立的对话上下文,有自己的:

  • ID 和标题
  • 工作目录
  • 权限配置
  • 时间戳

2.2 Message

一次对话中的一条消息,可以是:

  • user - 用户的输入
  • assistant - AI 的回复
  • system - 系统消息

2.3 Part

Message 中的具体内容,一条 Message 可以包含多个 Part:

Part 类型 说明 示例
text 普通文本 AI 的回复
reasoning 思考过程 Claude/DeepSeek 的推理
tool 工具调用 调用 bash、read 等
file 文件附件 用户上传的文件
step-start 步骤开始 开始新步骤
step-finish 步骤结束 包含 token 统计
compaction 压缩标记 历史压缩标记

关系图

erDiagram SESSION ||--o{ MESSAGE : "contains" MESSAGE ||--o{ PART : "contains"

3. 数据结构

3.1 Session

typescript 复制代码
{
  id: SessionID,           // 唯一标识
  project_id: ProjectID,   // 所属项目
  parent_id: SessionID,   // 父会话(fork 用)
  directory: string,       // 工作目录
  title: string,          // 会话标题
  permission: Ruleset,    // 权限配置
  time: { created, updated, compacting, archived }
}

3.2 Message

typescript 复制代码
{
  id: MessageID,
  role: "user" | "assistant" | "system",
  parentID?: MessageID,   // 父消息(对话树)
  agent?: string,         // 使用的 Agent
  modelID?: string,       // 使用的模型
  summary?: true,         // 是否为摘要消息
}

3.3 Part

typescript 复制代码
// 文本类型
{ type: "text", text: "hello" }

// 工具类型
{ type: "tool", tool: "bash", state: { status, input, output } }

// 工具状态
type ToolState = 
  | { status: "pending", input }
  | { status: "running", input, time: { start } }
  | { status: "completed", input, output }
  | { status: "error", input, error }

4. 完整生命周期

4.1 流程概览

flowchart TD Start([用户输入]) --> CreateSession CreateSession --> BuildPrompt[构建系统提示词] BuildPrompt --> GetTools[获取可用工具] GetTools --> CallLLM[调用 LLM] CallLLM --> HasToolCall{AI 调用工具?} HasToolCall -->|是| ExecuteTool[执行工具] ExecuteTool --> ToolResult[返回结果] ToolResult --> CallLLM HasToolCall -->|否| Save[保存到数据库] Save --> CheckOverflow{token 超限?} CheckOverflow -->|是| Compress[触发压缩] Compress --> CallLLM CheckOverflow -->|否| End([完成])

4.2 详细步骤

步骤 操作 源码位置
1 创建/获取 Session session/index.ts
2 创建 User Message session/index.ts:685
3 构建系统提示词 session/system.ts
4 获取可用工具 session/llm.ts
5 调用 LLM session/llm.ts
6 处理工具调用 session/processor.ts
7 保存到数据库 session/index.ts:754
8 检查是否压缩 session/compaction.ts

5. 压缩机制(重点)

5.1 本质

压缩只作用于工具输出,文本消息永远不压缩。

内容类型 压缩后
用户文字问题 ✅ 一直发送
AI 文字回复 ✅ 一直发送
AI 推理过程 ✅ 一直发送
工具输出 ⚠️ 替换为 "[Old tool result content cleared]"

5.2 触发条件

typescript 复制代码
// tokens 超过模型可用空间时触发
const usable = context - reserved  // 保留 ~20K 给输出
return count >= usable

5.3 压缩流程

ini 复制代码
token 超限
    ↓
1. 调用 LLM 生成摘要
   (Goal/Instructions/Discoveries/Accomplished)
2. 保存摘要为新消息 (summary=true)
3. 给旧工具输出打标记 (compacted=true)
4. 重新发送消息
    ↓
空间够? → ✅ 继续聊
空间不够?
    ↓
渐进式压缩(replay)→ 只保留最近一个用户消息
    ↓
空间够? → ✅ 继续聊
空间不够?
    ↓
媒体剥离(图片→文字)
    ↓
空间够? → ✅ 继续聊
空间不够? → ❌ 报错停止

5.4 可以多次触发

轮次 操作
第4轮 触发压缩 → 工具输出被标记,生成摘要
第5-10轮 正常聊
第11轮 触发压缩 → 新的工具输出被标记
... 继续压缩,直到上限

5.5 渐进式压缩

当普通压缩不够时,会尝试更激进方案:

typescript 复制代码
// compaction.ts:113-129
// 只保留最后一个用户消息,之前的全删掉
messages = messages.slice(0, lastUserIndex)

5.6 媒体剥离

图片/PDF 转成文字:

typescript 复制代码
// 1MB 图片 → 30 字符
"[Attached image/png: screenshot.png]"

5.7 什么时候彻底不能聊?

  • 所有工具输出都已压缩
  • 纯文本消息本身就超过上下文限制

5.8 举例说明

压缩前

arduino 复制代码
消息1 (user): "帮我重构 user.ts"
消息2 (assistant): [text: "好的"]
消息3 (assistant): [tool: read, output: "500行代码"]
消息4 (assistant): [text: "我读取了文件"]
消息5 (user): "改成箭头函数"
消息6 (assistant): [tool: edit, output: "已修改"]
消息7 (assistant): [text: "完成了"]

压缩后

arduino 复制代码
消息1 (user): "帮我重构 user.ts"
消息2 (assistant): [text: "好的"]
消息3 (assistant): [tool: read, output: "[Old tool result content cleared]"]  ← 被替换
消息4 (assistant): [text: "我读取了文件"]
消息5 (user): "改成箭头函数"
消息6 (assistant): [tool: edit, output: "[Old tool result content cleared]"]  ← 被替换
消息7 (assistant): [text: "完成了"]
消息8 (assistant): [summary: true, text: "## Goal\n用户想重构...\n## Accomplished\n..."]  ← 新增摘要

6. 数据库存储

6.1 表结构

typescript 复制代码
// Session 表
const SessionTable = sqliteTable("session", {
  id: text().$type<SessionID>().primaryKey(),
  project_id: text().notNull(),
  // ...
})

// Message 表
const MessageTable = sqliteTable("message", {
  id: text().$type<MessageID>().primaryKey(),
  session_id: text().$type<SessionID>().notNull(),
  data: text({ mode: "json" }).notNull(),  // role, agent 等
})

// Part 表
const PartTable = sqliteTable("part", {
  id: text().$type<PartID>().primaryKey(),
  message_id: text().$type<MessageID>().notNull(),
  session_id: text().$type<SessionID>().notNull(),
  data: text({ mode: "json" }).notNull(),  // type, text, state 等
})

6.2 存储示例

Message 表

id session_id data
msg_001 sess_001 {"role":"assistant","agent":"build"}

Part 表

id message_id data
part_001 msg_001 {"type":"text","text":"好的"}
part_002 msg_001 {"type":"tool","tool":"read","state":{...}}

7. 关键特性

7.1 Fork(分叉)

从任意历史点创建分支:

bash 复制代码
opencode run -c --fork "尝试另一种方案"

7.2 分享

生成公开链接:

typescript 复制代码
await Session.share(sessionID)
// 返回 share.opencode.ai/xxx

7.3 权限控制

每个会话可独立配置权限:

json 复制代码
{
  "permission": {
    "bash": "allow",
    "write": "ask",
    "rm": "deny"
  }
}

8. 总结

核心要点

  1. 三层结构:Session → Message → Part
  2. 文本不压缩:只有工具输出会被压缩
  3. 可多次触发:直到没有工具可压缩
  4. 渐进式压缩:从轻量到激进

一句话总结

Session 管理一次 AI 对话的生命周期,Message 包含多 Part,压缩只作用于工具输出保留文本,直到没有工具可压缩且文本仍超限时会话结束。


相关源码

文件 作用
session/index.ts Session 核心逻辑
session/message-v2.ts Message 和 Part 定义
session/llm.ts LLM 调用
session/processor.ts 消息处理器
session/compaction.ts 压缩机制
session/session.sql.ts 数据库 Schema
相关推荐
chaors1 小时前
Langchain入门到精通0x06:RAG
人工智能·langchain·ai编程
CodeSam1 小时前
手把手教你安装 OpenClaw:让 AI 助手住进你的终端
openai·ai编程
Mintopia1 小时前
诗词如何影响人:从认知机制到可落地的文本分析技术路线
前端·代码规范
WaywardOne1 小时前
iOS必看!Deepseek给的Runtime实现原理,通俗易懂~
前端·面试
小码哥_常1 小时前
惊!Kotlin集合,你可能只用了40%?
前端
Wect1 小时前
LeetCode 52. N 皇后 II:回溯算法高效求解
前端·算法·typescript
毛骗导演1 小时前
万字解析 OpenClaw 源码架构-跨平台应用之 iOS 应用
前端·ios·架构
刀断青1 小时前
Flutter 开发之第一个Flutter应用
前端
gyx_这个杀手不太冷静1 小时前
OpenCode 进阶使用指南(第五章:最佳实践)
前端·ai编程