0. 本章学习目标

理解 session API payload、HTTP handler、createUserMessage、part 解析、message 持久化,以及它们如何成为 agent loop 的上下文。

1. 一句话讲明白

用户输入与会话模块负责把外部请求变成内部事实:一个 session 下的 user message 和一组 message parts。来源:packages/opencode/src/session/prompt.ts:689-731packages/opencode/src/session/prompt.ts:1116-1230

2. 它在 OpenCode agent 中的位置

CLI/API 只提供输入,agent loop 只消费消息历史。session/message 层负责把文本、附件、agent mention、MCP resource、权限覆盖和模型选择整理成统一结构。

3. 生活类比

session 是项目档案,message 是每次沟通记录,part 是正文、附件、工具结果或系统补充。agent 每轮工作前读档案。

4. Java 开发者类比

  • Session.Info 类似会话 aggregate。
  • MessageV2.User / assistant 类似消息实体。
  • Part 是 message 的子实体集合。
  • SessionPrompt.prompt 是 Application Service。

5. 最小源码路径

  1. groups/session.ts:66-68
  2. groups/session.ts:312-324
  3. handlers/session.ts:279-290
  4. prompt.ts:689-731
  5. prompt.ts:788-1085
  6. prompt.ts:1116-1230

6. 用户输入到 agent 行动的整体链路

client.session.prompt(payload)
  -> PromptPayload
  -> sessionHandlers.prompt
  -> SessionPrompt.prompt
  -> createUserMessage
  -> resolvePart
  -> sessions.updateMessage/updatePart
  -> loop(sessionID)

7. 核心源码逐段讲解

7.1 Payload 从内部 schema 派生

export const PromptPayload = Schema.Struct(Struct.omit(SessionPrompt.PromptInput.fields, ["sessionID"]))

路径:packages/opencode/src/server/routes/instance/httpapi/groups/session.ts:66

7.2 Handler 补 sessionID

const message = yield* promptSvc.prompt({
  ...ctx.payload,
  sessionID: ctx.params.sessionID,
})

路径:packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:279-289

7.3 User message schema

export const User = Schema.Struct({
  role: Schema.Literal("user"),
  agent: Schema.String,
  model: Schema.Struct({ providerID: ProviderID, modelID: ModelID }),
  tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
})

路径:packages/opencode/src/session/message-v2.ts:327-350

7.4 Part union

export const Part = Schema.Union([
  TextPart, SubtaskPart, ReasoningPart, FilePart, ToolPart,
  StepStartPart, StepFinishPart, SnapshotPart, PatchPart,
  AgentPart, RetryPart, CompactionPart,
]).annotate({ discriminator: "type", identifier: "Part" })

路径:packages/opencode/src/session/message-v2.ts:352-365

7.5 创建消息并写入

const ag = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo()
const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID))
const info: MessageV2.User = { role: "user", sessionID: input.sessionID, agent: ag.name, model: { providerID: model.providerID, modelID: model.modelID, variant } }
yield* sessions.updateMessage(info)
for (const part of parts) yield* sessions.updatePart(part)

路径:packages/opencode/src/session/prompt.ts:689-731packages/opencode/src/session/prompt.ts:1116-1117

8. 关键 TypeScript 语法复习

  • Struct.omit:从内部 schema 派生 API payload。
  • object spread:{ ...ctx.payload, sessionID }
  • optional field:variant: Schema.optional(...)
  • discriminated union:Parttype 判别。
  • Effect.forEach 并发解析 parts。

9. 设计模式和架构思想

  • DTO 派生
  • Aggregate
  • Message/Part 子实体
  • Plugin hook
  • Controller 到 Application Service 的边界适配

10. 模块协作

Tool:file part 可调用 read tool;Provider:创建 message 时决定 model;Session:持久化 message/part;文件系统:file URL 被读取成 synthetic context。来源:packages/opencode/src/session/prompt.ts:867-1039

11. mini agent 对应代码

async function prompt(input) {
  const session = await sessions.get(input.sessionID)
  const user = createUserMessage(input, session)
  const parts = await resolveParts(input.parts)
  await sessions.saveMessage(user)
  await sessions.saveParts(user.id, parts)
  return input.noReply ? { info: user, parts } : runLoop(input.sessionID)
}

12. 费曼复述区

  1. 为什么 user message 里要保存 agent/model?
  2. part union 解决了什么问题?
  3. 为什么 file attachment 会在创建 user message 时被 read tool 读取?

13. 练习题

  • 解释为什么 PromptPayload omit sessionID
  • 列出 user message 的核心字段。
  • 实现一个支持 text/file 的 resolveParts

14. 源码追踪任务

  1. groups/session.ts:312-324 -> handler
  2. handler -> SessionPrompt.prompt
  3. createUserMessage -> model 选择
  4. file part -> read.execute

15. 面试式自测

  1. 为什么 message 和 part 分开?
  2. 为什么 prompt success 是 MessageV2.WithParts
  3. 插件修改消息的扩展点在哪里?

16. 下一步阅读建议

下一步读 “Agent 核心循环” 或 “Tool 调用系统”,理解这些 message parts 如何驱动行动。