/** * LLMError —— 全包唯一的结构化错误类型。 * * 单列一文件(而非塞进 index.js)以避免 base.js / llm.js 与公共面 index.js 形成 * 循环 import。index.js 再 re-export 它,对外仍是契约 §3 声明的 `LLMError`。 */ /** @typedef {import('./map-error.js').LLMErrorReason} LLMErrorReason */ const ERROR_RESPONSE = { contentFilter: { code: 'CONTENT_FILTER', statusCode: 400 }, } /** * 结构化 LLM 错误。永不吞错、永不 console 兜底——失败一律抛它。 */ export class LLMError extends Error { /** * @param {object} info * @param {string} info.provider 厂商标识(api_type)。 * @param {LLMErrorReason} info.reason 归一后的失败类别。 * @param {boolean} info.retryable 是否属于可重试类。 * @param {unknown} [info.cause] 原始异常 / 响应体。 * @param {string} [info.message] 可读消息(缺省由 reason 生成)。 * @param {Record} [info.detail] 可持久化的结构化失败详情。 * @param {string|null} [info.provider_request_id] 供应商失败响应中的请求 ID。 */ constructor({ provider, reason, retryable, cause, message, detail, provider_request_id = null }) { const errorMessage = message ?? `[${provider}] LLM request failed: ${reason}` super(errorMessage) this.name = 'LLMError' /** @type {string} */ this.provider = provider /** @type {LLMErrorReason} */ this.reason = reason /** @type {boolean} */ this.retryable = retryable /** @type {Record} */ this.detail = detail ?? { type: 'message', message: errorMessage } /** @type {string|null} */ this.provider_request_id = provider_request_id const response = ERROR_RESPONSE[reason] if (response) { this.code = response.code this.statusCode = response.statusCode } if (cause !== undefined) this.cause = cause } }