Files
trade-message-center/docs/bright-conversation-list-api.md
T

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 授权;AuthorizationX-Mind-PurposeX-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、连接串或原始正文。