Part 是消息的基本组成单元,使用联合类型支持多种类型的内容。
Part 是 MessageV2 的核心内容容器。每个 Message 可以包含多个 Part,每个 Part 代表一种特定类型的内容(文本、文件、工具调用等)。这种设计允许灵活的消息内容组合和流式更新。
packages/opencode/src/session/message-v2.ts:329-347
所有 Part 都包含以下基础属性:
| 属性名 | 类型 | 必填 | 说明 |
|---|
id | string | 是 | Part唯一标识符 |
sessionID | string | 是 | 所属会话ID |
messageID | string | 是 | 所属消息ID |
type | string | 是 | Part类型标识 |
纯文本内容。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "text" | 是 | 类型标识 |
text | string | 是 | 文本内容 |
synthetic | boolean | 否 | 是否为合成内容 |
ignored | boolean | 否 | 是否被忽略 |
time | object | 否 | 时间信息 |
time.start | number | - | 开始时间 |
time.end | number | - | 结束时间 |
metadata | Record<string, any> | 否 | 元数据 |
表示触发的子任务(例如使用 Task 工具)。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "subtask" | 是 | 类型标识 |
prompt | string | 是 | 提示词 |
description | string | 是 | 任务描述 |
agent | string | 是 | 执行的Agent |
model | object | 否 | 模型配置 |
model.providerID | string | - | 提供商ID |
model.modelID | string | - | 模型ID |
command | string | 否 | 命令 |
AI 模型的推理内容(如 Chain of Thought)。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "reasoning" | 是 | 类型标识 |
text | string | 是 | 推理内容 |
metadata | Record<string, any> | 否 | 元数据 |
time | object | 是 | 时间信息 |
time.start | number | - | 开始时间 |
time.end | number | - | 结束时间 |
文件或图像内容。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "file" | 是 | 类型标识 |
mime | string | 是 | MIME类型 |
filename | string | 否 | 文件名 |
url | string | 是 | 文件URL |
source | FileSource | 否 | 文件来源信息 |
FileSource 类型:
| 类型 | 说明 |
|---|
FileSource | 文件来源(包含 path) |
SymbolSource | 符号来源(包含 path, range, name, kind) |
ResourceSource | 资源来源(包含 clientName, uri) |
工具调用及执行状态。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "tool" | 是 | 类型标识 |
callID | string | 是 | 调用ID |
tool | string | 是 | 工具名称 |
state | ToolState | 是 | 工具状态 |
metadata | Record<string, any> | 否 | 元数据 |
ToolState 联合类型:
| 属性名 | 类型 | 必填 | 说明 |
|---|
status | "pending" | 是 | 状态标识 |
input | Record<string, any> | 是 | 输入参数 |
raw | string | 是 | 原始输入 |
| 属性名 | 类型 | 必填 | 说明 |
|---|
status | "running" | 是 | 状态标识 |
input | Record<string, any> | 是 | 输入参数 |
title | string | 否 | 显示标题 |
metadata | Record<string, any> | 否 | 元数据 |
time.start | number | 是 | 开始时间 |
| 属性名 | 类型 | 必填 | 说明 |
|---|
status | "completed" | 是 | 状态标识 |
input | Record<string, any> | 是 | 输入参数 |
output | string | 是 | 输出结果 |
title | string | 是 | 显示标题 |
metadata | Record<string, any> | 是 | 元数据 |
time.start | number | 是 | 开始时间 |
time.end | number | 是 | 结束时间 |
time.compacted | number | 否 | 压缩时间 |
attachments | FilePart[] | 否 | 附件列表 |
| 属性名 | 类型 | 必填 | 说明 |
|---|
status | "error" | 是 | 状态标识 |
input | Record<string, any> | 是 | 输入参数 |
error | string | 是 | 错误信息 |
metadata | Record<string, any> | 否 | 元数据 |
time.start | number | 是 | 开始时间 |
time.end | number | 是 | 结束时间 |
表示一个新的推理步骤开始。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "step-start" | 是 | 类型标识 |
snapshot | string | 否 | 快照ID |
表示推理步骤完成。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "step-finish" | 是 | 类型标识 |
reason | string | 是 | 完成原因 |
snapshot | string | 否 | 快照ID |
cost | number | 是 | 成本 |
tokens | object | 是 | Token统计 |
tokens.input | number | - | 输入Token数 |
tokens.output | number | - | 输出Token数 |
tokens.reasoning | number | - | 推理Token数 |
tokens.cache.read | number | - | 缓存读取数 |
tokens.cache.write | number | - | 缓存写入数 |
代码快照引用。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "snapshot" | 是 | 类型标识 |
snapshot | string | 是 | 快照ID |
Git 补丁信息。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "patch" | 是 | 类型标识 |
hash | string | 是 | 补丁哈希 |
files | string[] | 是 | 影响的文件列表 |
Agent 切换标记。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "agent" | 是 | 类型标识 |
name | string | 是 | Agent名称 |
source | object | 否 | 来源信息 |
source.value | string | - | 值 |
source.start | number | - | 开始位置 |
source.end | number | - | 结束位置 |
重试操作记录。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "retry" | 是 | 类型标识 |
attempt | number | 是 | 尝试次数 |
error | APIError | 是 | 错误信息 |
time.created | number | 是 | 创建时间 |
历史消息压缩标记。
| 属性名 | 类型 | 必填 | 说明 |
|---|
type | "compaction" | 是 | 类型标识 |
auto | boolean | 是 | 是否自动压缩 |
export type Part =
| TextPart
| SubtaskPart
| ReasoningPart
| FilePart
| ToolPart
| StepStartPart
| StepFinishPart
| SnapshotPart
| PatchPart
| AgentPart
| RetryPart
| CompactionPart
;["part", messageID, partID]
const textPart: MessageV2.TextPart = {
id: Identifier.ascending("part"),
sessionID: session.id,
messageID: message.id,
type: "text",
text: "",
time: { start: Date.now() },
}
await Session.updatePart(textPart)
await Session.updatePart({
part: { ...textPart, text: "Hello " },
delta: "Hello ",
})
await Session.updatePart({
part: { ...textPart, text: "Hello World" },
delta: "World",
})
const filePart: MessageV2.FilePart = {
id: Identifier.ascending("part"),
sessionID: session.id,
messageID: message.id,
type: "file",
mime: "image/png",
filename: "screenshot.png",
url: "https://cdn.example.com/screenshot.png",
source: {
type: "file",
path: "/path/to/screenshot.png",
text: {
value: "",
start: 0,
end: 100,
},
},
}
await Session.updatePart(filePart)
const toolPart: MessageV2.ToolPart = {
id: Identifier.ascending("part"),
sessionID: session.id,
messageID: message.id,
type: "tool",
callID: Identifier.ascending("tool"),
tool: "bash",
state: {
status: "pending",
input: { command: "npm test" },
raw: '{"command": "npm test"}',
},
}
await Session.updatePart(toolPart)
toolPart.state = {
status: "running",
input: toolPart.state.input,
title: "Running npm test",
metadata: {},
time: { start: Date.now() },
}
await Session.updatePart(toolPart)
toolPart.state = {
status: "completed",
input: toolPart.state.input,
output: "✓ All tests passed",
title: "npm test",
metadata: {},
time: {
start: toolPart.state.time.start,
end: Date.now(),
},
attachments: [],
}
await Session.updatePart(toolPart)
const parts = await MessageV2.parts(messageID)
for (const part of parts) {
switch (part.type) {
case "text":
console.log(`Text: ${part.text}`)
break
case "tool":
console.log(`Tool: ${part.tool} (${part.state.status})`)
break
case "file":
console.log(`File: ${part.filename}`)
break
}
}
await Session.removePart({
sessionID: session.id,
messageID: message.id,
partID: part.id,
})
| 版本 | 变更内容 | 日期 |
|---|
| v2 | 引入 MessageV2 Part 架构 | - |
| v2.1 | 添加 CompactionPart | - |
| v2.2 | 添加 SubtaskPart | - |