本文是 Agent SDK 中会话存储 Store(会话存储)模块的方法参考。您可以借助 Store 持久化并管理智能体运行过程中的 Conversation(会话)与 Message(消息):新建会话、按条件检索会话列表、向会话追加消息、读取与更新消息,直到删除或清空。本文先给出一段可直接运行的最短示例,随后按方法逐一说明用途、参数、返回与使用要点,最后介绍如何把 Store 接入主流 Agent 框架以及如何处理错误。
Store 通过 @iga-pages/agent/store 子路径提供会话与消息的持久化能力。它将会话与消息的读写收敛为一组方法,您无需管理底层存储细节,即可管理会话生命周期和消息记录。
Store 本身不调用模型。
Store 中的会话状态按当前项目身份进行隔离:不同项目的会话与消息相互独立、互不可见,您在某一项目下创建的会话与消息不会出现在其他项目的查询结果中。项目身份无需您在业务代码中传入,SDK 会使用当前应用的运行时身份访问对应项目中的资源。
在调用 Store 的任何方法之前,请确认您已完成以下准备:
@iga-pages/agent,并可通过子路径 @iga-pages/agent/store 引入 Store。安装步骤请参见安装与环境准备。下面这段代码是使用 Store 的最短闭环:创建一个会话、向它追加一条用户消息、再读取历史。您可以把它作为集成 Store 的起点,确认环境已就绪后再展开更复杂的会话管理。
import { store } from "@iga-pages/agent/store"; export async function createExample() { // 1. 新建一个会话,可顺带写入标题等元数据 const conversation = await store.createConversation({ metadata: { title: "我的第一段对话" }, }); const { conversationId } = conversation; // 2. 向该会话追加一条消息,正文结构由您的应用自定义 await store.appendMessage({ conversationId, role: "user", payload: { text: "你好" }, }); // 3. 读取历史消息 const page = await store.listMessages({ conversationId }); return { conversationId, messages: page.items }; }
要点说明:
store 导入后可直接使用,无需自行创建实例。conversationId,后续请求复用它即可续接同一段会话;每次调用 createConversation() 都会新建一个独立会话。listMessages() 返回的是一页消息,正文在每条消息的 payload 中;会话较长时通过游标翻页(见下文 listMessages)。在查阅方法之前,先了解 Store 读写的两类核心对象及其字段。以下定义展示接口返回的数据结构。
Conversation(会话)保存一段会话的基本信息,不包含消息列表:
interface Conversation { // 会话 ID conversationId: string; // 会话附加信息,如标题、标签;未设置时为 {} metadata: JsonObject; // 服务端生成的创建时间和最近更新时间 createdAt: string; updatedAt: string; }
创建会话时可以指定 conversationId,也可以省略,由 SDK 生成。
Message(消息)保存会话中的一条消息,通过 conversationId 关联所属会话:
type JsonPrimitive = string | number | boolean | null; // 任意 JSON 值,数组和对象可以递归嵌套 type JsonValue = JsonPrimitive | JsonValue[] | JsonObject; // 键为字符串,值为任意 JSON 值 interface JsonObject { [key: string]: JsonValue; } interface Message { // 当前项目内唯一的消息 ID,不限定为 UUID messageId: string; // 消息所属会话的 ID conversationId: string; // 服务端分配的会话内递增序号,以十进制字符串返回; seq: string; // 角色,如 user、assistant 或自定义字符串;未设置时为 null role: string | null; // 消息类型,未设置时为 null type: string | null; // 消息正文,JSON 结构由应用定义 payload: JsonValue; // 仅属于这条消息的附加信息,与会话 metadata 独立;未设置时为 {} metadata: JsonObject; // 服务端生成的创建时间和最近更新时间 createdAt: string; updatedAt: string; }
seq 是十进制字符串,比较时用 BigInt(seq),不要按字符串排序;消息删除后序号可能存在空缺。type 由应用自定义,不从 payload 推断。分页读取统一返回 Page<T>,其结构为 { items: T[]; nextCursor: string | null };nextCursor 为 null 时表示没有下一页,非空时原样传回同一列表方法的 cursor 参数继续翻页。
以下方法用于创建、读取、列出、更新和删除会话。conversationId 须为非空字符串,最多 256 UTF-8 字节。
创建一个新会话。只想保存消息时也可以跳过本方法,直接用 appendMessage/appendMessages——会话不存在时会随消息一起创建。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 否 | 省略时由 SDK 生成 UUID;指定一个已存在的 ID 会报 Conflict。 |
metadata | JsonObject | 否 | 初始会话元数据,默认 {}。 |
整个参数对象也可省略,即创建一个空会话。
返回值
| 类型 | 说明 |
|---|---|
Promise<Conversation> | 新建的会话。 |
示例
const conversation = await store.createConversation({ metadata: { title: "新对话" }, });
使用说明
conversationId 便于让业务侧 ID 与会话对齐;同一 ID 已存在时本方法不会读取或覆盖已有会话,而是抛出 Conflict。metadata 用来携带标题、标签等业务信息,不影响消息存储。按标识读取单个会话的基本信息。本方法只返回会话本身,不含消息列表;消息需另用 listMessages 读取。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 是 | 会话 ID。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<Conversation \| null> | 会话信息;不存在时返回 null。 |
示例
const conversation = await store.getConversation({ conversationId });
使用说明
null 而非抛错,适合用来判断某个 ID 是否已建会话。分页列举当前项目下的会话。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
limit | number | 否 | 每页条数,1~100 的整数,默认 50。 |
cursor | string | 否 | 上一页返回的 nextCursor,第一页不传。 |
整个参数对象可省略。
返回值
| 类型 | 说明 |
|---|---|
Promise<Page<Conversation>> | 当前页会话与下一页游标。 |
示例
const page = await store.listConversations({ limit: 20 }); if (page.nextCursor) { const nextPage = await store.listConversations({ limit: 20, cursor: page.nextCursor, }); }
使用说明
updatedAt 降序,同一时间再按 conversationId 降序。nextCursor 为 null 表示没有下一页。更新会话的元数据,会话不存在时抛出 StoreError(code 为 NotFound)。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 是 | 会话 ID。 |
metadata | JsonObject | 是 | 需要修改的会话元数据。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<Conversation> | 更新后的会话。 |
示例
const conversation = await store.updateConversation({ conversationId, metadata: { title: "新的标题", temporaryLabel: null }, });
使用说明
metadata 按顶层字段合并:未传的字段保留,顶层值为 null 时删除对应键,嵌套对象与数组则整体替换。更新失败时,不保留部分变更。{} 不修改数据也不更新时间,但仍会检查会话是否存在。永久删除会话及其全部消息。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 是 | 会话 ID。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<void> | 无返回数据。 |
示例
await store.deleteConversation({ conversationId });
使用说明
以下方法用于追加、读取、列出、更新、删除和清空消息。messageId 在当前项目内唯一,单条消息的读取、更新与删除都无需再传会话 ID。
向指定会话追加一条消息;会话不存在时自动创建。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 是 | 消息所属会话的 ID。 |
payload | JsonValue | 是 | 消息正文,JSON 结构由应用定义。 |
role | string | null | 否 | 消息角色,默认 null。 |
type | string | null | 否 | 应用自定义的消息类型,默认 null;不从 payload 推断。 |
messageId | string | 否 | 消息 ID,省略时自动生成 UUID。 |
metadata | JsonObject | 否 | 消息元数据,默认 {}。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<Message> | 保存后的消息,含 ID、序号与时间。 |
示例
const message = await store.appendMessage({ conversationId, role: "user", payload: { text: "你好" }, });
使用说明
conversationId 对应的会话不存在,appendMessage() 会在写入首条消息时一并创建会话,初始 metadata 为 {}。写入失败时,不会只创建会话。messageId、会话与内容:相同记录仍在则直接返回,内容不一致则抛 Conflict;省略 ID 再次调用则生成一条新消息。向指定会话一次性批量追加多条消息,整批成功或整批失败。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 是 | 会话 ID。 |
messages | AppendMessageItemInput[] | 是 | 1~100 条消息;每条字段与 appendMessage 相同,但不含 conversationId。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<Message[]> | 保存后的消息数组,顺序与输入一致。 |
示例
const messages = await store.appendMessages({ conversationId, messages: [ { role: "user", payload: { text: "你好" } }, { role: "assistant", payload: { text: "有什么可以帮你?" } }, ], });
使用说明
messageId 不能重复,也不能混用“已有 ID”和“新 ID”。按标识读取单条消息,无需同时提供会话 ID。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
messageId | string | 是 | 消息 ID。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<Message \| null> | 消息详情;不存在时返回 null。 |
示例
const message = await store.getMessage({ messageId });
使用说明
messageId 在当前项目内唯一,因此单条消息的读取、更新、删除都无需再传会话 ID。分页读取指定会话的消息,默认返回最早的 50 条。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 是 | 会话 ID。 |
order | "asc" | "desc" | 否 | 默认 asc(最早在前);desc 为最新在前。 |
limit | number | 否 | 每页条数,1~100 的整数,默认 50。 |
cursor | string | 否 | 上一页返回的 nextCursor,第一页不传。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<Page<Message>> | 当前页消息与下一页游标。 |
示例
const page = await store.listMessages({ conversationId, order: "desc", limit: 20, }); if (page.nextCursor) { const nextPage = await store.listMessages({ conversationId, order: "desc", limit: 20, cursor: page.nextCursor, }); }
使用说明
order: "desc";翻页时保持 conversationId 与 order 不变。更新单条消息,role、type、payload、metadata 四者至少提供一个。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
messageId | string | 是 | 要更新的消息 ID。 |
role | string | null | 否 | 替换角色,null 清空角色。 |
type | string | null | 否 | 替换消息类型,null 清空,省略则保留;不修改 payload。 |
payload | JsonValue | 否 | 整体替换消息正文。 |
metadata | JsonObject | 否 | 整体替换元数据,{} 清空。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<Message> | 更新后的消息。 |
示例
const message = await store.updateMessage({ messageId, payload: { text: "修改后的正文" }, metadata: {}, });
使用说明
StoreError(code 为 NotFound),没有可更新字段时 code 为 BadRequest。payload 与 metadata 都是整体替换,不做对象合并——这一点与 updateConversation 的顶层合并规则不同。永久删除一条消息,保留所属会话。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
messageId | string | 是 | 消息 ID。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<void> | 无返回数据。 |
示例
await store.deleteMessage({ messageId });
使用说明
清空会话下的全部消息,同时保留会话、metadata、创建时间与序号计数。清空失败时,不删除任何消息。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversationId | string | 是 | 会话 ID。 |
返回值
| 类型 | 说明 |
|---|---|
Promise<void> | 无返回数据。 |
示例
await store.clearMessages({ conversationId });
使用说明
clearMessages 会清空 Store 中该会话的通用消息,包括 OpenAI Session 使用的会话历史;不会删除 LangGraph 检查点、Claude SessionStore 会话记录、Sandbox 文件或 Blob 对象。这些资源需要通过各自生命周期或 API 单独管理。updatedAt;清空完成后再追加,并重新开始分页。除了直接调用上面的方法,Store 还提供了几个适配器,让主流 Agent 框架把会话历史或执行状态直接落到 Store 上。您按自己使用的框架选择其一即可,无需全部接入。三个适配器的创建函数都会同步返回实例,实际的读写由框架在运行时触发;模型凭证仍由您的应用自行配置。
下表帮您快速判断该用哪一个:
| 适配器入口 | 保存什么 | 续用标识 | 通用 listMessages() 能否读取 |
|---|---|---|---|
@iga-pages/agent/store/openai 的 createSession(conversationId) | OpenAI Agents 历史条目 | conversationId | 能,payload 为框架条目格式。 |
@iga-pages/agent/store/langgraph 的 createCheckpointer(serde?) | LangGraph/DeepAgents 执行检查点 | 非空字符串 thread_id(最多 256 UTF-8 字节) | 不能。 |
@iga-pages/agent/store/claude 的 createSessionStore() | Claude 会话记录 | Claude session ID 与稳定项目键 | 不能。 |
说明
以下仅演示本模块的适配出口如何接入框架。将 OpenAI Agents、LangGraph、DeepAgents、Claude Agent SDK 等框架从零搭建,并把 Store、Tools、Sandbox 等模块组合成一个完整应用的端到端示例,请参见框架适配。
接入 OpenAI Agents 时,把 createSession(conversationId) 作为 session 传给 run(),框架就会在同一 conversationId 下自动加载并续写历史:
import { createSession } from "@iga-pages/agent/store/openai"; await run(agent, input, { session: createSession(conversationId), });
首次使用一个新的会话 ID(非空字符串,最多 256 UTF-8 字节),后续调用传入相同 ID 即可接续。适配器读取该会话的全部消息,请不要在同一会话里混入其他格式的业务记录,也不要重复保存框架已写入的消息。
安装 @langchain/langgraph-checkpoint@^1.1.4。LangGraph 与 DeepAgents 复用 @iga-pages/agent/store/langgraph 的 createCheckpointer(serde?)。将返回的 Checkpointer 传给框架编译入口,并跨请求复用同一个 thread_id(非空字符串,最多 256 UTF-8 字节)即可恢复执行状态。
Checkpointer 保存执行检查点,不会额外创建通用会话,也不会将状态写入通用消息。因此,无法通过 listMessages() 读取这类数据。
清理某个线程的检查点时,调用 checkpointer.deleteThread(conversationId)。
安装 @anthropic-ai/claude-agent-sdk@>=0.2.113 <0.4。Claude Agent SDK 使用 @iga-pages/agent/store/claude 的 createSessionStore()。将返回的 SessionStore 作为 sessionStore 传给 query() 后,首轮保存返回的 Claude session ID,下一轮以 resume 传入即可恢复。
SessionStore 使用 Claude session ID 与项目键定位会话。请通过 CLAUDE_CODE_PROJECT_DIR_NAME 为同一项目提供稳定值。
Claude session ID 与业务 conversationId 的关联由应用自行管理。SessionStore 只保存会话记录,不保存工作目录和文件。
Store 的方法失败时抛出 StoreError。它带有一个 code 字段供程序分支判断,toJSON() 返回以下结构,便于打日志或上报:
{ code: "Conflict", status: 409, message: "duplicate key value violates unique constraint", error: { code: "23505", message: "duplicate key value violates unique constraint", details: "...", hint: null } }
code 表示错误类型,message 为错误说明,status 为可选状态码;没有 HTTP 响应时,status 可能缺失或为 0。数据库错误还包含 error,可通过 error.code 判断具体原因,例如 23505 表示记录重复。外层 code 不重写内层上游返回的错误细节(如数据库 SQLSTATE 或 PostgREST 错误);需要更细的原因时读取 error 内的字段。
HTTP 失败通常映射为 NotFound、BadRequest、Conflict、TooManyRequests 等分类;本地或网络失败还可能返回 NetworkError、TimeoutError、AbortError 或 UnknownError。
getConversation() 读取不存在的会话时正常返回 null,不抛出 StoreError。
import { store, StoreError } from "@iga-pages/agent/store"; try { await store.createConversation({ conversationId }); } catch (err) { if (err instanceof StoreError && err.code === "Conflict") { console.log("记录已存在"); } else { throw err; } }
处理 Store 错误时,先用 instanceof StoreError 判断,再读取其字段。
Blob 与 Sandbox 会抛出各自的异常,Tools 的一次执行失败通常返回 status: "error";其他模块的返回与错误处理方式见对应参考文档。
是否重试取决于操作语义。读取操作和复用相同 ID 与内容的幂等写入可在短暂网络失败后退避重试;追加消息时应显式复用原 messageId,避免生成重复消息。
更新等非幂等操作应先确认结果,不能盲目重试。clearMessages() 的重试会再次清空届时的消息,须遵循本节的并发警告。
BadRequest、Conflict 等请求错误应先修正入参或消解冲突;Unauthorized、Forbidden 应检查项目登录与授权。