Message (MessageV2)
MessageV2 是当前使用的消息架构,支持多种角色和丰富的元数据。
概述
Message 表示会话中的一次对话交换,分为 User 和 Assistant 两种角色。Message 本身只包含元数据,实际内容通过 Part 数组承载。这种设计允许灵活的消息内容组合(文本、文件、工具调用等)。
定义位置
packages/opencode/src/session/message-v2.ts:1-740
核心特性
- 灵活的内容结构:通过 Part 数组支持多种内容类型
- 独立的元数据:Message 存储会话级别信息,Part 存储详细内容
- 流式更新:支持 Part 的增量和增量更新
- 模型转换:toModelMessages() 自动转换为 LLM 格式
- 错误处理:支持多种错误类型和重试机制
User Message
用户消息结构,表示用户的输入。
| 属性名 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 消息唯一标识符 |
sessionID | string | 是 | 所属会话ID |
role | "user" | 是 | 角色固定为 "user" |
time | object | 是 | 时间信息 |
time.created | number | - | 创建时间(Unix时间戳) |
summary | object | 否 | 摘要信息 |
summary.title | string | - | 摘要标题 |
summary.body | string | - | 摘要内容 |
summary.diffs | FileDiff[] | - | 文件差异列表 |
agent | string | 是 | 使用的Agent名称 |
model | object | 是 | 模型配置 |
model.providerID | string | - | 提供商ID |
model.modelID | string | - | 模型ID |
system | string | 否 | 系统提示词 |
tools | Record<string, boolean> | 否 | 工具配置 |
variant | string | 否 | 变体标识 |
Assistant Message
AI 助手消息结构,表示 AI 的响应。
| 属性名 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 消息唯一标识符 |
sessionID | string | 是 | 所属会话ID |
role | "assistant" | 是 | 角色固定为 "assistant" |
time | object | 是 | 时间信息 |
time.created | number | - | 创建时间(Unix时间戳) |
time.completed | number | 否 | 完成时间(Unix时间戳) |
error | Error | 否 | 错误信息 |
parentID | string | 是 | 父消息ID(对应的用户消息) |
modelID | string | 是 | 使用的模型ID |
providerID | string | 是 | 提供商ID |
mode | string | 是 | 模式(已标记为废弃) |
agent | string | 是 | Agent名称 |
path | object | 是 | 路径信息 |
path.cwd | string | - | 当前工作目录 |
path.root | string | - | 项目根目录 |
summary | boolean | 否 | 是否为摘要消息 |
cost | number | 是 | 产生的成本(美元) |
tokens | object | 是 | Token使用统计 |
tokens.input | number | - | 输入Token数 |
tokens.output | number | - | 输出Token数 |
tokens.reasoning | number | - | 推理Token数 |
tokens.cache | object | - | 缓存Token统计 |
tokens.cache.read | number | - | 读取缓存Token数 |
tokens.cache.write | number | - | 写入缓存Token数 |
finish | string | 否 | 完成原因 |
TypeScript 类型定义
export type User = {
id: string
sessionID: string
role: "user"
time: {
created: number
}
summary?: {
title?: string
body?: string
diffs: FileDiff[]
}
agent: string
model: {
providerID: string
modelID: string
}
system?: string
tools?: Record<string, boolean>
variant?: string
}
export type Assistant = {
id: string
sessionID: string
role: "assistant"
time: {
created: number
completed?: number
}
error?: AuthError | UnknownError | OutputLengthError | AbortedError | APIError
parentID: string
modelID: string
providerID: string
mode: string
agent: string
path: {
cwd: string
root: string
}
summary?: boolean
cost: number
tokens: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
}
finish?: string
}
export type Message = User | Assistant
系统操作
创建消息
const messageID = Identifier.ascending("message")
const userMessage: MessageV2.User = {
id: messageID,
sessionID: session.id,
role: "user",
time: { created: Date.now() },
agent: "fixer",
model: { providerID: "anthropic", modelID: "claude-3-5-sonnet" },
}
await MessageV2.updateMessage(userMessage)
更新消息
await MessageV2.updateMessage(messageID, (draft) => {
draft.time.completed = Date.now()
draft.finish = "stop"
})
删除消息
await MessageV2.removeMessage({
sessionID: session.id,
messageID: messageID,
})
获取消息
const message = await MessageV2.get({
sessionID: session.id,
messageID: messageID,
})
流式读取消息
for await (const msg of MessageV2.stream(session.id)) {
console.log(\`Message \${msg.info.id}: \${msg.info.role}\`)
for (const part of msg.parts) {
console.log(\` - Part: \${part.type}\`)
}
}
转换为 LLM 格式
const messages = await MessageV2.messages({ sessionID: session.id })
const modelMessages = MessageV2.toModelMessages(messages, model)
过滤已压缩消息
const messages = await MessageV2.messages({ sessionID: session.id })
const filtered = await MessageV2.filterCompacted(MessageV2.stream(session.id))
错误类型
Assistant Message 可能包含以下错误类型:
| 错误类型 | 说明 |
|---|---|
AuthError | 提供商认证失败 |
UnknownError | 未知错误 |
OutputLengthError | 输出长度超出限制 |
AbortedError | 操作被中止 |
APIError | API调用错误(可重试或不可重试) |
Part 类型关联
每个 Message 包含 Part 数组,Part 类型详见 Part 文档。
| Part 类型 | 说明 | 文档 |
|---|---|---|
TextPart | 文本内容 | Part |
SubtaskPart | 子任务 | Part |
ReasoningPart | 推理内容 | Part |
FilePart | 文件内容 | Part |
ToolPart | 工具调用 | Part |
StepStartPart | 步骤开始 | Part |
StepFinishPart | 步骤完成 | Part |
SnapshotPart | 快照引用 | Part |
PatchPart | 补丁信息 | Part |
AgentPart | Agent 切换 | Part |
RetryPart | 重试操作 | Part |
CompactionPart | 压缩标记 | Part |
消息创建流程
存储键结构
;["message", sessionID, messageID]
;["part", messageID, partID]
典型使用场景
场景 1:创建带摘要的用户消息
const userMessage: MessageV2.User = {
id: messageID,
sessionID: session.id,
role: "user",
time: { created: Date.now() },
agent: "fixer",
model: { providerID: "anthropic", modelID: "claude-3-5-sonnet" },
summary: {
title: "修复登录 bug",
body: "用户报告登录失败,需要修复认证逻辑",
},
}
await MessageV2.updateMessage(userMessage)
场景 2:创建助手消息并跟踪 Token
const assistantMessage: MessageV2.Assistant = {
id: assistantMessageID,
sessionID: session.id,
role: "assistant",
time: { created: Date.now() },
parentID: userMessageID,
modelID: "claude-3-5-sonnet",
providerID: "anthropic",
agent: "fixer",
path: {
cwd: "/home/user/project",
root: "/home/user/project",
},
cost: 0.0023,
tokens: {
input: 1200,
output: 500,
reasoning: 100,
cache: {
read: 50,
write: 20,
},
},
}
await MessageV2.updateMessage(assistantMessage)
场景 3:流式读取和过滤
// 读取所有消息
const allMessages = []
for await (const msg of MessageV2.stream(session.id)) {
allMessages.push(msg)
}
// 过滤已压缩的消息
const filteredMessages = []
for await (const msg of MessageV2.filterCompacted(MessageV2.stream(session.id))) {
filteredMessages.push(msg)
}
console.log(\`Total messages: \${allMessages.length}\`)
console.log(\`Filtered messages: \${filteredMessages.length}\`)
对象关系
相关文档
变更历史
| 版本 | 变更内容 | 日期 |
|---|---|---|
| v2 | 引入MessageV2 架构,支持更细粒度的Part类型 | - |