errors.js 1.95 KB
/**
 * 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<string, any>} [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<string, any>} */
    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
  }
}