8.9 KiB
Bright OneTalk 会话读取 API
状态:目标接口契约。接口只读取 Bright 已持久化的 direct 会话、消息和联系人资料;不读取 Mind 数据库、不代理插件,也不使用同步锚点作为页面游标。
通用授权、CORS 与读取范围
所有接口路径中的 channelAccountId 都必须与当前 Mind Session 授权出的账号完全一致。scope 永远来自该授权结果,不信任页面提交的用户、工作区或设备字段。
当浏览器带 Origin 时,Bright 只接受精确的 Mind 页面 Origin;成功的跨域响应包含:
Access-Control-Allow-Origin: <configured Mind Origin>
Access-Control-Allow-Credentials: true
Vary: Origin
OPTIONS 只允许 GET、OPTIONS 和 Content-Type。若浏览器的 Access-Control-Request-Headers 包含任一其它 header(比较不区分大小写)则返回 403;不允许的 Origin 同样返回 403,且不会调用读取服务或授权读取。
type Scope = {
workspaceId: string;
mindUserId: string;
channelAccountId: string;
};
type Plugin = {
status: "online" | "offline";
};
plugin.status 只表示当前插件连接;插件离线不影响对已持久化 Bright 数据的读取。
会话列表
GET /api/bright/onetalk/accounts/:channelAccountId/conversations
?cursor=:opaqueCursor
&limit=50
&query=:optionalQuery
| 参数 | 类型 | 默认值 | 语义 |
|---|---|---|---|
| cursor | string | 无 | 不透明列表游标。只能原样回传同一条件的 nextCursor,客户端不得解析或构造。 |
| limit | integer | 50 | 1..100。 |
| query | string | 无 | 先 trim、规范化后匹配实时 name 或 conversationId。空白 query 等同未筛选。 |
type ConversationListResponse = {
scope: Scope;
plugin: Plugin;
conversations: CenterConversation[];
page: Page;
};
type Page = {
hasMore: boolean;
nextCursor: string | null;
};
type CenterConversation = {
channelAccountId: string;
conversationId: string;
conversationType: "direct";
name: string | null;
avatarUrl: string | null;
participantIds: [];
latestMessageId: string | null;
latestMessageAtMs: number | null;
unreadCount: 0;
messageCount: number;
historyComplete: boolean;
syncPhase: "initial" | "incremental";
syncResult: "incomplete" | "succeeded" | "succeeded_with_anomalies" | "failed";
};
列表按 latestMessageAtMs DESC NULLS LAST、conversationId ASC 排序。latestMessageId 与 latestMessageAtMs 必须来自同一条真实持久化消息;无消息时二者都为 null。participantIds 固定为空数组,unreadCount 固定为 0。
列表首次请求固定 asOf 事实快照,后续游标绑定账号、规范化 query、快照和 keyset。会话发现、消息入库或排序变化不会使同一 cursor 链重复或遗漏;服务端以 limit + 1 判断下一页。
联系人资料是唯一的明确例外:每一页都 left join 当前 profile table。因此资料 name、avatarUrl 的变化会立即显示,且资料名称变化可能改变后续页的 query 筛选结果;接口不保存 profile 历史或分页缓存。
单会话详情
GET /api/bright/onetalk/accounts/:channelAccountId/conversations/:conversationId
type ConversationResponse = {
scope: Scope;
plugin: Plugin;
conversation: CenterConversation;
};
详情和列表使用同一个 CenterConversation projection。若当前授权账号下没有这个 direct 会话:
404 Not Found
{
error: {
code: "conversation_not_found";
}
}
消息读取
GET /api/bright/onetalk/accounts/:channelAccountId/conversations/:conversationId/messages
?fromSentAtMs=:inclusiveEpochMs
&toSentAtMs=:exclusiveEpochMs
&cursor=:opaqueCursor
&limit=50
| 参数 | 类型 | 默认值 | 语义 |
|---|---|---|---|
| fromSentAtMs | safe integer | 无 | 可选下界,包含该毫秒。 |
| toSentAtMs | safe integer | 无 | 可选上界,不包含该毫秒。 |
| cursor | string | 无 | 不透明历史游标。只能原样回传前一页 nextCursor,客户端不得解析或构造。 |
| limit | integer | 50 | 1..100。 |
时间窗口是半开区间:fromSentAtMs <= sentAtMs < toSentAtMs。两端同时存在时必须 fromSentAtMs < toSentAtMs。历史游标独立于列表游标,并绑定账号、会话、时间窗、asOf 和 (sentAtMs, messageId) keyset;任一条件不一致都无效。
type MessagePageResponse = {
scope: Scope;
conversationId: string;
messages: CenterMessage[];
page: Page;
};
type CenterMessageBase = {
messageId: string;
conversationId: string;
senderId: string;
participantIds: string[];
direction: "received" | "sent";
sentAtMs: number;
readStatus: "read" | "unread";
};
type CenterMessage =
| (CenterMessageBase & {
contentType: "text";
content: { text: string | null };
})
| (CenterMessageBase & {
contentType: "img";
content: { url: string | null };
})
| (CenterMessageBase & {
contentType: "attachment";
content: { name: string | null; url: string | null };
})
| (CenterMessageBase & {
contentType: "unknown";
content: null;
});
消息按 sentAtMs ASC、messageId ASC 排序,首次读取固定事实 asOf,服务端以 limit + 1 判断下一页。任何未被验证为 text、img 或 attachment 的合法原始消息都保留为 unknown,不会猜测为另一种语义。
响应绝不暴露原始数字或原始顶层字段,包括原始 contentType、readStatus、messageStatus、unreadCount、messageRevision、conversationRevision、updatedAtMs、recalledAtMs、deliveryStatus、顶层 text、顶层 subject 和 attachmentSummaryText。
内部纪要读取
本页面 API 只接受 Mind Session/Cookie 授权;Authorization、X-Mind-Purpose 和 X-Mind-Workspace-Id 不会选择另一条读取路径。供 Mind 后台生成纪要的内部 Docker 网络接口、固定时间窗和 history_incomplete 规则见 OneTalk 内部纪要读取接口。
分页与错误
对所有页面,hasMore 为 true 时 nextCursor 必须是非空字符串;hasMore 为 false 时必须是 null。客户端只能保存并回传该 opaque 字符串,不能从会话 ID、消息 ID、同步 anchor 或任何响应字段推导游标。
| HTTP 状态 | error.code | 条件 |
|---|---|---|
| 400 | invalid_limit | limit 不是 1..100。 |
| 400 | invalid_cursor | cursor 非法或不匹配账号、query、会话、窗口或 snapshot。 |
| 400 | invalid_time_range | 时间不是安全整数,或窗口不是有效半开区间。 |
| 401 | auth_required | 缺少有效 Mind Session。 |
| 403 | scope_mismatch | 路径账号、Session scope 或 Origin 不匹配。 |
| 403 | authorization_rejected、binding_revoked、authorization_version_changed | Mind 授权拒绝。 |
| 404 | conversation_not_found | 授权范围内不存在该 direct 会话。 |
| 503 | authorization_unavailable | 授权依赖不可用或 Bright v2 被暂停。 |
| 503 | database_unavailable | Bright 读取数据库暂时不可用。 |
| 503 | history_incomplete | 纪要读取请求到尚未完整的历史;带 Retry-After: 30。 |
| 500 | internal_error | 未预期错误;不返回内部异常、Cookie、连接串或原始正文。 |