错误
Flue Agent SDK(@flue/sdk)导出两个自有的错误类 — FlueApiError(失败的 HTTP 请求)和 FlueExecutionError(已接收但结算为失败或中止的提交)— 以及四个由 @durable-streams/client 拥有的重新导出的流错误类。这些错误携带的线路格式(HTTP 错误信封和序列化的结算错误)记录在本页。
每个类将 name 设置为类名。使用 instanceof 进行区分;消息字符串是为日志记录而组合的,不是 API。
import { createFlueClient, FlueApiError, FlueExecutionError } from '@flue/sdk';
const conversation = createFlueClient({ url: 'https://example.com/agents/triage/123456' });
try {
const admission = await conversation.send({ message: { kind: 'user', body: 'Hello' } });
await conversation.wait(admission);
} catch (error) {
if (error instanceof FlueApiError) {
// HTTP 请求被拒绝:error.status、error.body。
} else if (error instanceof FlueExecutionError) {
// 消息已被接收,但提交结算为失败或中止。
} else {
throw error;
}
}
FlueApiError
class FlueApiError extends Error {
readonly status: number;
readonly body: unknown;
readonly ref: string | undefined;
constructor(status: number, body: unknown, headerRef?: string);
}
SDK 中每个返回非 2xx 响应的 JSON 请求(send()、abort() 和 history())的拒绝值。SDK 对每个 JSON 请求只执行一次 fetch — 无重试 — 因此 FlueApiError 反映的是单个服务器响应。
| 字段 | 描述 |
|---|---|
status | HTTP 响应状态码。 |
body | 解析成功时的 JSON 响应体;解析失败时的原始响应文本;响应无 body 时的空字符串。故意为 unknown:部署前的代理和网关可以返回任意 body,因此 SDK 不做规范化。 |
ref | 服务器的错误关联引用(err_…),在运行时在服务端记录了失败时存在(500 级内部错误)。从信封的 error.ref 读取,以 flue-error-ref 响应头部作为回退。报告生产环境 500 错误时引用它 — 它命名了精确的服务端日志行(参见关联生产环境 500)。 |
message 由状态码、body 携带 Flue 错误信封时的 type 和 message、以及响应携带时的 ref 组合而成 — 例如 Flue API error 404 [agent_instance_not_found]: Agent instance "123456" was not found. 或 Flue API error 500 [internal_error] (ref err_…): An internal error occurred. — 因此记录被捕获的 FlueApiError 会将 ref 带入你自己的日志而无需额外代码。按 status 和 body 匹配,而非按字符串。
两个方法永远不会产生 FlueApiError:
wait()读取持久流而非发起 JSON 请求;其传输失败是下面的流错误。observe()根本不会抛出;其内部历史读取的FlueApiError会落到观察快照上(参见observe()中的错误)。
HTTP 错误信封
Flue 运行时自身渲染的每个错误响应都携带一个 JSON 信封 — { "error": ... },格式如下:
{
type: string;
message: string;
details: string;
dev?: string;
meta?: Record<string, unknown>;
}
| 字段 | 描述 |
|---|---|
type | 稳定的、机器可读的标识符(snake_case,如 agent_instance_not_found)。这是分支判断的字段;message 和 details 的措辞不是 API。 |
message | 一句话摘要,可安全展示给任何调用方。 |
details | 更长的调用方安全说明。始终存在(可能为空)。 |
dev | 面向开发者的指导(修复说明、配置提示)。仅在服务器以本地开发模式运行且错误有仅限开发者的指导时存在;其缺失不是可靠的生产信号。 |
meta | 可选的结构化数据,仅在下游工具受益时设置。 |
FlueApiError.body 持有整个信封;读取字段前请检查格式(body 保持 unknown,因为非 Flue 基础设施也可能响应)。服务端错误词汇 — 哪些 type 值存在及其状态 — 记录在运行时错误参考中。两个与 send() 的 uid 条件相关的拒绝值得在此了解:
404agent_instance_not_found— 带uid条件的发送命名了一个不存在或 uid 不再匹配的实例(实例被重新创建,uid 命名的是其之前的化身);未投递任何内容。这两种情况故意无法区分。409agent_instance_exists— 仅创建的发送(uid: null)命名了一个已有实例;meta.uid返回已有的 uid(在details正文中重复),因此调用方可以继续该化身而无需单独查找。
_应用_中间件或非 Flue 基础设施抛出的错误不会被信封化;这就是为什么 body 保持 unknown。
FlueExecutionError
type FlueExecutionTarget = 'agent_submission';
type FlueExecutionFailure = 'failed' | 'aborted' | 'terminal_event_missing';
class FlueExecutionError extends Error {
readonly target: FlueExecutionTarget;
readonly targetId: string;
readonly failure: FlueExecutionFailure;
readonly error: unknown;
constructor(options: {
target: FlueExecutionTarget;
targetId: string;
failure: FlueExecutionFailure;
error?: unknown;
});
}
wait() 的拒绝值:消息已被接收并执行,但提交结算的结果不是 completed。FlueApiError 意味着请求从未进入;FlueExecutionError 意味着智能体运行了但未成功完成。
| 字段 | 描述 |
|---|---|
target | 被等待的内容。该联合类型目前只有一个成员 'agent_submission'。 |
targetId | 被等待提交的 submissionId(来自 AgentSendResult 接收结果)。 |
failure | 提交结算为失败时为 'failed';结算为中止时为 'aborted'(例如 abort() 之后);会话流结束但没有此提交的终止事件时为 'terminal_event_missing'。 |
error | 结算的错误负载(当结算携带时);terminal_event_missing 时为 undefined。类型为 unknown,因为该值跨线路传输,但运行时自身序列化的错误遵循此格式: |
{
name?: string;
message: string;
type?: string;
details?: string;
dev?: string;
meta?: Record<string, unknown>;
}
当下层失败是类型化的运行时错误(与运行时错误参考相同的标识词汇)时,type、details 和 meta 存在;普通错误仅序列化为 name 和 message。
wait() 区分服务端中止和调用方取消:通过 abort() 中止的会话以 FlueExecutionError(failure: 'aborted')拒绝,而触发调用方自身的 AbortSignal 以信号的原因拒绝 — 当信号没有原因时是名为 AbortError 的 DOMException — 且不是 FlueExecutionError。参见中止不是 SDK 错误。
会话线路(wait()、observe())根据物化会话协议验证每个块,协议不匹配会引发内部的 ConversationStreamError(name: 'ConversationStreamError',未导出)。observe() 通过重填全新快照来恢复;在 wait() 中它作为拒绝传播。
流错误
wait()、observe() 和 FlueEventStream 迭代读取持久流。这些读取上的传输和协议失败来自 @durable-streams/client;SDK 重新导出了通过其读取路径可达的类,以便应用无需直接依赖该包即可区分它们。它们的格式由该包拥有并跟踪其发布。
DurableStreamError
class DurableStreamError extends Error {
code:
| 'NOT_FOUND'
| 'CONFLICT_SEQ'
| 'CONFLICT_EXISTS'
| 'BAD_REQUEST'
| 'BUSY'
| 'SSE_NOT_SUPPORTED'
| 'UNAUTHORIZED'
| 'FORBIDDEN'
| 'RATE_LIMITED'
| 'ALREADY_CONSUMED'
| 'ALREADY_CLOSED'
| 'PARSE_ERROR'
| 'STREAM_CLOSED'
| 'UNKNOWN';
status?: number;
details?: unknown;
}
协议级流失败 — 格式错误或无法解析的流响应、服务器不支持的 SSE 升级,或映射到结构化代码的 HTTP 状态。
| 字段 | 描述 |
|---|---|
code | 用于程序化处理的结构化代码(上面的联合类型;DurableStreamErrorCode 类型别名本身未重新导出)。 |
status | HTTP 状态(当失败映射到响应时)。 |
details | 附加数据,通常是原始响应体。 |
StreamClosedError
class StreamClosedError extends DurableStreamError {
readonly code = 'STREAM_CLOSED';
readonly status = 409;
readonly streamClosed = true;
readonly finalOffset?: string;
}
DurableStreamError 子类,报告对已关闭流的操作。当响应提供了最终偏移量时,finalOffset 携带流的最终偏移量。
FetchError
class FetchError extends Error {
status: number;
text?: string;
json?: object;
headers: Record<string, string>;
url: string;
}
没有协议级分类的流 HTTP 请求失败。流层以指数退避重试瞬态失败(429、503、所有 5xx 和网络错误;默认无限制 — 通过 wait() 和 observe() 接受的 backoffOptions 来限制),因此使用默认选项时,FetchError 出现在不可重试的客户端错误上:除 429 外的 4xx。
| 字段 | 描述 |
|---|---|
status | 失败请求的 HTTP 状态。 |
text / json | 响应体,根据响应内容类型为文本或解析后的 JSON。 |
headers | 响应头部。 |
url | 请求的 URL。 |
FetchBackoffAbortError
class FetchBackoffAbortError extends Error {}
流请求因信号在重试退避期间中止而被放弃。没有额外字段。大多数调用方发起的中止不会暴露此类 — SDK 流迭代在调用方自身的信号上安静结束,wait() 然后以信号的原因拒绝 — 但它可能在流层的中止与重试循环竞争时出现。
observe() 中的错误
observe() 既不抛出也不拒绝。失败在观察快照(getSnapshot().phase 和 .error)上暴露:
- 初始历史读取返回
404时设置phase: 'absent'且无错误 — 会话尚不存在。 400、401或403(来自任何携带数字status的错误值,如FlueApiError或FetchError)是致命的:phase: 'error',错误在snapshot.error上。无进一步重试。- 所有其他失败 — 网络错误、5xx、意外结束的流 — 以指数延迟(1 秒翻倍,上限 30 秒)安排重填:
phase: 'connecting',待处理的错误在snapshot.error上。 - 关闭(通过
close()或signal选项)设置phase: 'closed'。
完整的观察契约在客户端页面上。
中止不是 SDK 错误
取消表现为中止原因,而不是 Flue 错误类:
send()、abort()和history()以 fetch 实现为中止请求抛出的任何内容拒绝 — 标准fetch下是名为AbortError的DOMException。wait()以signal.reason拒绝,当信号没有原因时是名为AbortError的DOMException。FlueEventStream迭代在取消时不抛出:cancel()(或跳出for await)以done: true结束迭代。
在将拒绝视为失败之前,检查 error.name === 'AbortError'(或你自己的 signal.reason)。
构造错误
createFlueClient() 在无法解析会话 URL 时同步抛出 — 使用原生错误而非 SDK 类:
- 在浏览器外使用相对
url抛出TypeError: relative url requires a browser; pass an absolute URL。在浏览器中,相对 URL 相对于location.origin解析。 - 不是有效 URL 的
url抛出来自URL构造函数的原生TypeError。
构造时不会发生网络活动;其他所有失败在调用时通过上述类抛出。