跳转到内容

输入关键词开始搜索文档。

FlueClient

最后更新 查看 Markdown 源文件

FlueClient

interface FlueClient {
  readonly url: string;
  send(options: AgentPromptOptions): Promise<AgentSendResult>;
  read(target: AgentSendResult | string, options?: AgentReadOptions): Promise<AgentReadResult>;
  wait(admission: AgentSendResult, options?: AgentWaitOptions): Promise<void>;
  abort(options?: { signal?: AbortSignal }): Promise<AgentAbortResult>;
  history(options?: FlueConversationHistoryOptions): Promise<FlueConversationSnapshot>;
  historyBefore(
    cursor: string,
    options: FlueConversationHistoryBeforeOptions,
  ): Promise<FlueConversationHistoryPage>;
  observe(options?: AgentConversationObserveOptions): AgentConversationObservation;
  attachmentUrl(attachmentId: string): string;
}

单个智能体会话的客户端,由 createFlueClient(...) 创建。每个 HTTP 方法在非 2xx 响应时以 FlueApiError 拒绝;基于流的方法(wait()、observe())还会暴露流错误。

字段描述
url此客户端寻址的完整解析后的会话 URL,不带尾部斜杠。

对于聊天 UI,@flue/react 的 useFlueAgent({ url }) 用维护的 React 状态封装了此客户端;参见 React。

send()

send(options: AgentPromptOptions): Promise<AgentSendResult>;

POST <conversation url>。开始一次消息投递,在接收时解析(HTTP 202):消息已被持久接受,但尚未处理。结果不包含智能体的回复 — 将它传给 read() 获取回复,传给 wait() 仅获取结果,或用 observe() 渲染进度。线路请求体是 DeliveredMessage 的原样,initialData 和 uid 作为保留的顶级同级字段。

AgentPromptOptions

interface AgentPromptOptions {
  message: DeliveredMessage;
  initialData?: unknown;
  uid?: string | null;
  signal?: AbortSignal;
}
字段描述
message要投递的消息。
initialData实例创建数据,仅在此发送创建会话时被查询:当智能体声明了 initialData schema 静态时会进行验证,记录一次,并在智能体内部通过 useInitialData() 读取(参见向智能体传递数据)。当发送继续已有会话时被忽略;配合 uid: null 可改为报错。
uid发送条件。发送是条件请求,实例 uid 充当 ETag。省略:无条件 — 继续实例或创建它。字符串(之前结果的 uid):仅继续该化身。实例不存在或 uid 不匹配时以 404 FlueApiError(agent_instance_not_found)拒绝,不投递任何内容。不能与 initialData 组合使用。null:仅创建。已有实例以 409 FlueApiError(agent_instance_exists,已有 uid 在 body.error.meta.uid 中)拒绝。
signal中止 HTTP 请求。它不会中止智能体的工作;那是 abort() 的职责。

DeliveredMessage

type DeliveredMessage =
  | { kind: 'user'; body: string; attachments?: DeliveredAttachment[] }
  | {
      kind: 'signal';
      type: string;
      body: string;
      attributes?: Record<string, string>;
      tagName?: string;
    };

投递到智能体会话中的消息 — 与服务器从 dispatch() 接受的统一格式相同。kind: 'user' 是直接的用户聊天轮次,1:1 寻址智能体。kind: 'signal' 是结构化事件 — webhook、定时任务和多用户场景(Slack 线程、GitHub issue),智能体作为其中一个成员参与,而 user 消息会将其他参与者与智能体自身的用户混淆。

Signal 字段:

字段描述
type调用方定义的信号类型('slack.message');必须非空。
body信号内容。纯字符串;结构化负载需要自行 JSON-stringify。
attributes结构化字符串元数据(发送者身份等),随 body 一起传入模型上下文。
tagName覆盖信号在模型上下文中渲染时的 XML 标签名;服务器会拒绝不是有效 XML 名称的值。

DeliveredAttachment

type DeliveredAttachment = DeliveredImageAttachment | DeliveredDocumentAttachment;

interface DeliveredImageAttachment {
  type: 'image';
  data: string;
  mimeType: string;
  filename?: string;
}

interface DeliveredDocumentAttachment {
  type: 'document';
  data: string;
  mimeType: 'application/pdf';
  filename?: string;
}

kind: 'user' 消息上的一个附件:图片或文档(PDF),作为原生文档内容转发给模型。原生文档输入支持 Anthropic Messages、Google Generative AI / Vertex 和 OpenAI(及 Azure OpenAI)Responses 模型;在任何其他模型 API 上,文档在模型上下文中被替换为表示已省略的文本占位符(运行时每个 API 记录一次警告)。

FieldDescription
type'image' or 'document'.
dataBase64-encoded bytes. The server rejects data longer than 14 MiB of characters.
mimeTypeThe image MIME type (image/png, image/jpeg, …), or application/pdf for a document.
filenameOptional original filename, surfaced on the projected file part (and passed to the provider for documents).

AgentSendResult

interface AgentSendResult {
  streamUrl: string;
  offset: string;
  submissionId: string;
  uid: string;
}

所有字段由服务器提供。

字段描述
streamUrl用于观察会话事件的完整解析后的 Durable Streams URL。
offset在接收时捕获的不透明流偏移量。从该偏移量读取 streamUrl 会精确得到此提示的事件;wait() 消费它。
submissionId将已接收的提示与其消息和结算关联起来(FlueConversationMessage 和 FlueConversationSettlement 上的 submissionId 字段)。
uid被联系实例的 uid:当此发送创建会话时铸造,继续已有会话时回显。将其作为 uid 选项回传可保证后续发送到达同一化身。

read()

read(target: AgentSendResult | string, options?: AgentReadOptions): Promise<AgentReadResult>;

type AgentReadOptions = AgentWaitOptions;

interface AgentReadResult {
  text: string;
  data: Record<string, unknown[]>;
  metadata?: Record<string, unknown>;
  submissionId: string;
  uid?: string;
}

等待一个提交的结算并以其回复解析 — 组合的一次性往返操作:

const admission = await conversation.send({ message });
const reply = await conversation.read(admission);

目标是 send() 解析出的接收结果,或一个裸提交 ID。裸形式是重新附加路径:它从流的起始处跟随会话,因此任何只持有该 ID 的进程都可以在稍后读取回复,已结算的提交会立即解析 — 持久化小的接收对象(或仅其 submissionId),崩溃脚本的替代者可以从丢失的 await 停止处继续。

  • 当提交结算为失败或中止时以 FlueExecutionError 拒绝,与 wait() 完全相同;回复仅在成功结算后才获取。
  • 回复字段是 readSubmissionReply() 的投影(文本、命名数据部分、元数据)加上已结算的 submissionId;当目标接收结果有 uid 时会携带过来。
  • options 是 AgentWaitOptions — signal 在本地停止读取(提交继续运行;持久停止用 abort()),onEvent 在等待时流式传输块。
  • 内部实现是 wait() 加一次 history() 读取。当你只需要结果,或已经通过 observe() 持有了物化会话时,可以直接使用这些原语 — readSubmissionReply() 适用于其状态而无需额外获取。

wait()

wait(admission: AgentSendResult, options?: AgentWaitOptions): Promise<void>;

通过在 admission.offset 处跟随 admission.streamUrl 的会话更新流来等待已接收提交的完成(通过客户端的 fetch 和 headers,因此自定义传输和认证生效)。同一会话上其他提交的结算块会被忽略。

等待是观察者而非驱动者:如果等待进程消失,提交仍会在服务端结算,结算会被持久记录在会话上。用相同的接收结果再次调用 wait(),或从 history()/observe() 读取,即可恢复结果。

  • 当提交结算为 completed 时解析为 void。
  • 当结算为失败或中止时以 FlueExecutionError(failure: 'failed' 或 'aborted')拒绝;当服务器记录了序列化的失败详情时,错误的 error 属性携带它。
  • 当流结束但没有此提交的结算时以 FlueExecutionError(failure: 'terminal_event_missing')拒绝。
  • 当 options.signal 中止时以信号的原因拒绝(当信号没有原因时是名为 AbortError 的 DOMException)。

智能体的回复不会返回 — 结算块只携带结果,这使得 wait() 在你只需要结果时是更廉价的调用。要获取回复,请使用 read(),或自行用 readSubmissionReply() 从快照中提取。

AgentWaitOptions

interface AgentWaitOptions {
  signal?: AbortSignal;
  backoffOptions?: BackoffOptions;
  onEvent?: (event: ConversationStreamChunk) => void | Promise<void>;
}
字段描述
signal取消等待及其流连接。提交本身继续运行;停止工作用 abort()。
backoffOptions流连接尝试的重试行为(BackoffOptions 来自 @durable-streams/client,由 SDK 重新导出)。
onEvent在等待期间为每个会话流块调用,并在处理下一块之前被 await。适合脚本中的进度输出;对于维护的 UI 状态建议使用 observe()。ConversationStreamChunk 为第一方展示者导出,但不是稳定的应用 API — 参见事件和记录。

abort()

abort(options?: { signal?: AbortSignal }): Promise<AgentAbortResult>;

POST <conversation url>/abort。中止会话中所有进行中和排队的持久工作 — 当前正在运行的提交及其后面排队的任何提交。在中止意图被记录后解析;工作异步结算为独立的 aborted 结果。通过 wait()(以 failure: 'aborted' 拒绝)、observe() 或 history() 观察该结算。已结算的工作不受影响 — 如果中止竞争输了已完成的响应,它仍保持 completed 结算。

字段描述
signal中止 HTTP 请求。

AgentAbortResult

interface AgentAbortResult {
  aborted: boolean;
}
字段描述
aborted当有进行中或排队的工作正在被中止时为 true;当会话空闲(没有可中止的内容)时为 false。

history()

history(options?: FlueConversationHistoryOptions): Promise<FlueConversationSnapshot>;

GET <conversation url>?view=history。读取一个物化会话快照 — 没有实时更新的时点读取(实时更新用 observe())。不存在的会话以 404 FlueApiError 拒绝。返回之前,SDK 会在每个持久记录的 file 部分上解析一个可直接使用的 url(参见 attachmentUrl())。

interface FlueConversationHistoryOptions {
  limit?: number;
  signal?: AbortSignal;
}
FieldDescription
limitRead only the newest limit messages (a positive integer). The snapshot keeps the head offset and every settlement, and adds a before cursor for historyBefore(). limit counts every message, hidden and diagnostic ones included, so it is not a count of rendered rows. The window can hold more than limit messages: it always reaches back to include a response that is still streaming above messages delivered after it. Omit to read the whole conversation.
signalAborts the request.

A bounded read bounds the message (transcript) portion of the response, which dominates its size in long-running conversations. It is not a fixed size bound: settlements always ship whole, since clients derive whether a submission is still active from them, and they grow with the conversation. Nor does it reduce the runtime’s own work, which materializes the conversation either way. A runtime that predates bounded history ignores limit and returns the whole conversation without a before field.

FlueConversationSnapshot

interface FlueConversationSnapshot {
  v: 1;
  conversationId: string;
  offset: string;
  messages: FlueConversationMessage[];
  settlements: FlueConversationSettlement[];
  before?: string | null;
}

A materialized conversation read at a durable-stream offset — the whole transcript, or its newest messages on a bounded read.

FieldDescription
vSnapshot format version.
offsetOpaque durable-stream checkpoint at which the snapshot was materialized. Pass it back only through Flue’s own observation machinery; observe() manages it internally.
messagesThe conversation transcript, in order.
settlementsTerminal outcomes of the conversation’s tracked submissions. Always the whole conversation’s settlements, even on a bounded read.
beforePresent only on bounded reads: an opaque cursor for historyBefore(), or null when messages starts at the beginning of the conversation. Do not construct or parse it: a cursor names one message in one stream generation.

FlueConversationMessage

interface FlueConversationMessage {
  id: string;
  role: 'user' | 'assistant' | 'system';
  purpose: 'user' | 'assistant' | 'dispatch' | 'advisory';
  display: 'visible' | 'hidden' | 'diagnostic';
  submissionId?: string;
  turnId?: string;
  signal?: { tagName?: string; attributes?: Record<string, string> };
  settlement?: { outcome: 'failed' | 'aborted' };
  timestamp?: string;
  parts: FlueConversationPart[];
  metadata?: Record<string, unknown>;
}

物化会话中的一条消息。一条助手消息是一个完整响应:被跟踪提交的每个模型步骤(文本、工具调用、工具结果、更多文本)作为单条消息的部分按流顺序累积 — 与 AI SDK 的 UIMessage 相同的每响应一条消息的格式。

FieldDescription
idStable message identity; for an assistant response, the first step’s message id.
roleCoarse render lane, following the standard chat convention so a generic renderer can lay out a transcript. system covers every non-chat, non-answer message (internal control input and runtime advisories).
purposeStable semantic classification, independent of rendered text: user is public chat, assistant is an answer, dispatch is internal dispatch/control input, advisory is a runtime advisory. The union may widen as the runtime grows typed agent-activity signals.
displayHow a transcript UI should treat the message: visible for primary chat, diagnostic for an activity/diagnostics panel, hidden for runtime plumbing that should not normally be shown.
submissionIdPresent on messages produced by a tracked submission.
turnIdPer-turn grouping identity, shared by every message recorded within one model round-trip; absent on messages recorded outside a turn.
signalTyped detail for a message projected from an internal runtime signal; present only on system-role messages.
settlementStructured settlement marker; present only on the terminal advisory the runtime appends when a submission settles failed or aborted, with the message’s submissionId naming the settled submission. Completed submissions get no marker — the assistant reply is the marker — and the snapshot’s settlements remains the programmatic outcome index.
timestampServer-authored capture time (ISO 8601) of the durable record behind the message, present for every role and for existing conversations. For a user or system message it is when the input or signal was applied to the conversation (not when the submission was accepted); for an assistant response it is when the response’s first step started. Absent on the optimistic echo useFlueAgent renders before the server confirms a send. Server wall-clock: not guaranteed unique or monotonic, so order by array position, never by timestamp.
metadataEntirely agent-authored: whatever the agent’s useResponseStart/useResponseFinish hooks return, deep-merged in call order. The runtime stamps nothing into it — keys like usage or model are app conventions, present only when the agent attaches them. The server’s capture time lives on timestamp.

FlueConversationPart

type FlueConversationPart =
  | { type: 'text'; text: string; state: 'streaming' | 'done' }
  | { type: 'reasoning'; text: string; state: 'streaming' | 'done' }
  | { type: `data-${string}`; data: unknown }
  | {
      type: 'file';
      mediaType: string;
      id?: string;
      size?: number;
      url?: string;
      filename?: string;
    }
  | ({ type: 'dynamic-tool'; toolName: string; toolCallId: string } & (
      | { state: 'input-available'; input: unknown }
      | { state: 'output-available'; input: unknown; output: unknown; durationMs?: number }
      | { state: 'output-error'; input: unknown; errorText: string; durationMs?: number }
    ));

消息中一个可渲染的部分。一个部分只携带物化内容加一个生命周期 state;流式组装细节(delta 排序、活跃块)永远不会暴露。

部分描述
text / reasoning流式模型输出。state 在 delta 仍在到达时为 'streaming',块关闭后为 'done'。
data-<name>由智能体的 useDataWriter 写入器流式传输的命名客户端数据部分(AI SDK 约定:data-<name> 类型,负载在 data 上)。名称是部分在响应内的标识 — 后续写入就地更新该部分,保持其位置。参见向客户端流式传输数据。
file一个附件。id 是稳定的附件 ID,在附件持久记录后存在,在本地乐观回显时不存在。url 可直接用作 <img>/<a> 的源:SDK 为已记录的附件填充它,乐观回显携带 data: URL 预览;当字节尚未可解析时可能不存在。size 以字节为单位(已知时)。filename 在上传者提供时存在。
dynamic-tool一个工具调用,以 toolCallId 为键。input-available 表示调用已记录且结果待定;output-available 在 output 上携带结果;output-error 在 errorText 上携带失败文本。durationMs 是工具处理器执行时间,在结果已知时存在(在该字段发布之前记录的结果上不存在)。

FlueConversationSettlement

interface FlueConversationSettlement {
  submissionId: string;
  outcome: 'completed' | 'failed' | 'aborted';
  error?: unknown;
  answeredBySubmissionId?: string;
  timestamp?: string;
}

对话中一个被追踪的智能体提交的最终结果。当服务端记录了失败时,error 携带序列化的失败详情。answeredBySubmissionId 指明响应了此提交的提交(对于加入实时响应的投递,即宿主提交)。timestamp 是结算的服务端捕获时间(ISO 8601)。

historyBefore()

historyBefore(
  cursor: string,
  options: FlueConversationHistoryBeforeOptions,
): Promise<FlueConversationHistoryPage>;

interface FlueConversationHistoryBeforeOptions {
  limit: number;
  signal?: AbortSignal;
}

interface FlueConversationHistoryPage {
  v: 1;
  conversationId: string;
  messages: FlueConversationMessage[];
  before: string | null;
}

GET <conversation url>?view=history&before=<cursor>. Reads the messages older than a before cursor — from a bounded history() snapshot, a bounded observe() state, or a previous page — oldest first. limit (required) is the page size: at most that many messages, the newest of them. The page’s own before continues backward; null means the start of the conversation was reached. Pages carry no offset and no settlements: they are not checkpoints, and the snapshot or observation that produced the cursor already holds every settlement.

const page = await client.history({ limit: 50 });
if (page.before) {
  const older = await client.historyBefore(page.before, { limit: 50 });
}

A cursor from an earlier generation of the conversation stream (it was reset and regrown since the cursor was issued), or one whose message is gone, rejects with a 410 FlueApiError (history_cursor_not_found). This holds even when the new generation reuses the message’s id. Discard pages read with it and re-read the newest window. Rejects with an Error when the runtime predates bounded history.

readSubmissionReply()

import { readSubmissionReply } from '@flue/sdk';

function readSubmissionReply(
  conversation: {
    messages: FlueConversationMessage[];
    settlements: FlueConversationSettlement[];
    before?: string | null;
  },
  submissionId: string,
): AgentSubmissionReply;

interface AgentSubmissionReply {
  text: string;
  data: Record<string, unknown[]>;
  metadata?: Record<string, unknown>;
}

一个纯函数(无 I/O,不是客户端方法),从物化会话中提取一个提交的回复 — history() 快照或 observe() 状态。它是 read() 背后的投影;当你已经持有会话且不想额外获取时直接使用它:

const state = observation.getSnapshot().conversation;
const reply = readSubmissionReply(state, admission.submissionId);

回复是标记了给定 submissionId 的最终助手消息。加入繁忙响应的提交在宿主的响应下结算,因此当提交自身没有产生助手消息时,会话的最后一条助手消息就是回答它的合并回复 — 优先使用此辅助方法而非手动选取 messages.at(-1),后者在繁忙会话上会静默读取错误的消息。

  • text — 回复的文本部分用空行连接;没有时为 ''。
  • data — 回复消息上的命名客户端数据部分(useDataWriter),按键名索引,每个按发射顺序排列。
  • metadata — 智能体编写的响应元数据(存在时)。

settlements is required, so a historyBefore() page (which carries none) is not accepted: a page is a slice of older history, not a conversation to read a reply from. The last-assistant-message fallback for legacy settlements applies only to a complete conversation: an unbounded snapshot or state, or a bounded one whose before is null. On a bounded window with older messages unloaded (before is a cursor), only replies inside the window resolve. The fallback is skipped, since that message may belong to another submission, and the reply resolves empty instead. read() always reads the whole conversation.

相同的投影支持运行时的 init().read(),因此通过 HTTP 读取的回复和进程内读取的回复是一致的。

observe()

observe(options?: AgentConversationObserveOptions): AgentConversationObservation;

跨历史追赶和实时更新观察物化会话。同步返回,无网络活动;观察在第一次 subscribe() 调用时开始。

开始时,观察读取一个历史快照并发布它,然后从快照的偏移量跟随会话的更新流,将每个块归约到维护的 FlueConversationState。失败处理:

  • 流失败或意外结束时发布 phase connecting(带错误)并以指数退避重试 — 每次尝试翻倍 1 秒,上限 30 秒 — 重新填充全新快照而非增量恢复。尝试计数器在每个已应用的块和成功填充时重置。
  • HTTP 400、401 和 403 是致命的:phase error,无自动重试。refresh() 会再次尝试。
  • 历史读取返回 404 时发布 phase absent(会话尚不存在)。观察不会轮询它的出现;调用 refresh() 重新检查。
  • 中止 options.signal 或调用 close() 发布最终 phase closed。

使用 limit 时,观察仅加载最新的 limit 条消息。观察到的窗口随后随实时更新向前增长且永不缩小:重新连接后的重新水合从窗口最旧的消息读取到头部,因此不会在其下方产生间隙,conversation-reset 更新(例如压缩后发送的)由运行时裁剪到同一窗口,或由无法做到的运行时在客户端裁剪。窗口可能包含超过 limit 条消息,因为它总是包含仍能接收实时更新的每条消息。状态的 before 游标使用 historyBefore() 读取更早的消息;将这些页面与观察一起保存并在其消息之前渲染。游标在窗口生命周期内保持不变。如果它发生变化,观察已重新基于全新的最新窗口,使用先前游标加载的页面应被丢弃。这发生在流被重置并重新增长时(即使新一代重用消息 ID 也能检测到),当重置不再包含窗口时,或当调用 refresh() 时。结算始终覆盖整个对话。

const observation = client.observe({ limit: 50, live: 'sse' });
let older: FlueConversationMessage[] = [];
let cursor: string | null | undefined;

async function loadOlder() {
  cursor ??= observation.getSnapshot().conversation?.before;
  if (!cursor) return;
  const page = await client.historyBefore(cursor, { limit: 50 });
  older = [...page.messages, ...older];
  cursor = page.before;
}

块应用在至少一次重递送下是安全的:每个块携带单调递增的位置,位于或低于最后应用位置的块会被丢弃,因此重放的批次(SSE 重连)永远不会重复应用。

getSnapshot/subscribe 匹配 React 的 useSyncExternalStore 契约,每次发布的更新都是新的快照对象标识。@flue/react 的 useFlueAgent 基于此方法构建;参见 React。

AgentConversationObserveOptions

interface AgentConversationObserveOptions {
  live?: ConversationLiveMode;
  signal?: AbortSignal;
  backoffOptions?: BackoffOptions;
  limit?: number;
}
FieldDescription
liveLive update mode; defaults to 'long-poll'.
signalCloses the observation when aborted.
backoffOptionsRetry behavior for individual stream connection attempts (BackoffOptions from @durable-streams/client), beneath the observation’s own rehydrate loop.
limitHydrate only the newest limit messages (a positive integer); see observe(). Omit to observe the whole conversation.

ConversationLiveMode

type ConversationLiveMode = 'long-poll' | 'sse';
模式描述
long-poll基于偏移量恢复的轮询。
sse长连接流,用于更低延迟的逐 token 更新。

两种模式在重发下都是安全的。对于没有实时更新的单次时点读取,使用 history()。

AgentConversationObservation

interface AgentConversationObservation {
  getSnapshot(): AgentConversationObservationSnapshot;
  subscribe(listener: () => void): () => void;
  refresh(): void;
  close(reason?: unknown): void;
}
方法描述
getSnapshot()当前快照;每次发布更新都是新对象。
subscribe(listener)注册变更监听器;第一次调用开始观察。返回取消订阅函数。取消订阅会移除监听器但不停止底层流 — 只有 close() 或 signal 会停止。
refresh()丢弃当前连接并从新的历史快照重新填充。用于重新检查 absent 会话或在致命 error 后重试。关闭后为空操作。
close(reason)终止停止观察并发布 phase closed;提供的 reason 会被规范化为 Error 并发布在快照的 error 上。

AgentConversationObservationSnapshot

interface AgentConversationObservationSnapshot {
  conversation: FlueConversationState | undefined;
  offset: string | undefined;
  phase: AgentConversationObservationPhase;
  error: Error | undefined;
}
字段描述
conversation物化状态;在第一次填充完成前和会话为 absent 时为 undefined。
offset状态对应的不透明持久流检查点。
phase连接生命周期;见下文。
error在 connecting 期间最近的连接失败,在 error 时的致命失败,或规范化的 close(reason);否则为 undefined。

AgentConversationObservationPhase

type AgentConversationObservationPhase =
  'loading' | 'connecting' | 'live' | 'absent' | 'error' | 'closed';
阶段描述
loading第一次填充进行中;尚无状态。
connecting状态可能存在;实时连接正在建立或在失败后重新建立。
live跟随实时更新。
absent历史读取返回 404:会话不存在。
error致命 HTTP 状态(400、401、403);无自动重试。
closed终止:signal 中止或 close() 被调用。

FlueConversationState

interface FlueConversationState {
  conversationId: string;
  messages: FlueConversationMessage[];
  settlements: FlueConversationSettlement[];
  before?: string | null;
}

observe() 维护的实时物化对话——没有 v/offset 信封的 FlueConversationSnapshot(偏移量在观察快照上)。before 仅在有界的 observe({ limit }) 上存在:指向比观察窗口更早消息的游标,当窗口到达对话开头时为 null。

attachmentUrl()

attachmentUrl(attachmentId: string): string;

返回一个 file 部分附件字节的绝对 URL — <conversation url>/attachments/<attachmentId>,ID 经过 URL 编码 — 适合作为 <img>/<a> 的源。该路由是每个已挂载智能体路由的一部分,仅接受 GET,对未知附件 ID 返回 404。

不会发起请求也不会附加认证:返回的字符串只是 URL,因此使用它发起的请求本身必须满足挂载点的中间件守卫。你很少需要直接使用此方法 — history() 和 observe() 已经在每个持久记录的 file 部分上解析了 url。通过 Cloudflare 服务绑定时,URL 的主机是占位符源;通过相同的 fetcher 转发它(参见服务绑定指南)。