Agent Sandbox Registry

awesome-providers

opensandbox/src/errors.mjs

无中文版

opensandbox/src/errors.mjs

// EPP/1 error codes and the translation of OpenSandbox HTTP failures into them.

export const CODES = Object.freeze({
  AUTH: -32001,
  NOT_FOUND: -32002,
  NO_FEATURE: -32003,
  QUOTA: -32004,
  TIMEOUT: -32005,
  BAD_PARAMS: -32006,
  FAIL: -32000,
  METHOD_NOT_FOUND: -32601,
});

export class RpcError extends Error {
  constructor(code, message, data = {}) {
    super(message);
    this.name = "RpcError";
    this.code = code;
    this.data = data;
  }

  toJSON() {
    return { code: this.code, message: this.message, data: this.data };
  }
}

export const badParams = (message, data = {}) => new RpcError(CODES.BAD_PARAMS, message, data);
export const notFound = (message, data = {}) => new RpcError(CODES.NOT_FOUND, message, data);
export const failure = (message, data = {}) => new RpcError(CODES.FAIL, message, data);

export function unsupported(feature, message) {
  return new RpcError(
    CODES.NO_FEATURE,
    message ?? `OpenSandbox does not support '${feature}'.`,
    { feature, details: { feature } },
  );
}

/** Replace every secret (4+ chars) in `text` with `***`. */
export function redact(text, secrets = []) {
  let out = String(text);
  for (const secret of secrets) {
    if (secret && secret.length >= 4) out = out.split(secret).join("***");
  }
  return out;
}

/**
 * Normalise the three error body shapes OpenSandbox produces, plus non-JSON bodies:
 *   lifecycle   {"code": "DOCKER::SANDBOX_NOT_FOUND", "message": "..."}
 *   FastAPI     {"detail": [{"type", "loc", "msg", "input"}]}   (422)
 *   execd 401   {"error": "Unauthorized: ..."}
 */
export function parseErrorBody(raw) {
  const text = typeof raw === "string" ? raw : "";
  let json;
  try {
    json = text ? JSON.parse(text) : undefined;
  } catch {
    json = undefined;
  }
  if (json && typeof json === "object" && !Array.isArray(json)) {
    if (Array.isArray(json.detail)) {
      const msgs = json.detail.map((d) => {
        const where = Array.isArray(d.loc) ? d.loc.filter((p) => p !== "body").join(".") : "";
        return where ? `${where}: ${d.msg}` : String(d.msg ?? JSON.stringify(d));
      });
      return { code: "VALIDATION_ERROR", message: msgs.join("; "), details: json.detail };
    }
    if (typeof json.detail === "string") return { code: json.code ?? "", message: json.detail };
    if (typeof json.error === "string") return { code: json.code ?? "", message: json.error };
    if (typeof json.message === "string") return { code: String(json.code ?? ""), message: json.message };
  }
  const plain = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
  return { code: "", message: plain.slice(0, 300) };
}

/**
 * Map an HTTP failure to an RpcError.
 * `ctx.what` is "sandbox" | "file" | "command" | "endpoint" (selects the not-found flavour).
 */
export function mapHttpError(status, rawBody, ctx = {}) {
  const parsed = parseErrorBody(rawBody);
  const where = ctx.operation ? ` (${ctx.operation})` : "";
  const detail = parsed.message ? `: ${parsed.message}` : "";
  const base = `OpenSandbox HTTP ${status}${where}${detail}`;
  const data = { http_status: status };
  if (parsed.code) data.code = parsed.code;
  if (parsed.details) data.details = parsed.details;
  const make = (code, message, extra = {}) => {
    const err = new RpcError(code, message, { ...data, ...extra });
    err.httpStatus = status;
    err.rawMessage = parsed.message;
    err.rawCode = parsed.code;
    return err;
  };
  const upper = String(parsed.code).toUpperCase();
  const lower = parsed.message.toLowerCase();

  if (status === 401) {
    return make(CODES.AUTH, `OpenSandbox rejected the API key${where}${detail}`, {
      sdk_code: "E1001",
      suggestion: "Set ABX_OPENSANDBOX_API_KEY (or OPEN_SANDBOX_API_KEY) to a valid key.",
    });
  }
  if (lower.includes("runtime server not configured")) {
    return make(CODES.NO_FEATURE, `${base}. The sandbox image has no code-interpreter runtime.`, {
      feature: "code.interpret",
      details: { feature: "code.interpret" },
    });
  }
  if (status === 501 || upper.includes("NOT_SUPPORTED")) {
    return make(CODES.NO_FEATURE, base, { feature: ctx.feature ?? "unknown", details: { feature: ctx.feature ?? "unknown" } });
  }
  if (status === 404) {
    if (ctx.what === "file" || upper.includes("FILE_NOT_FOUND")) {
      return make(CODES.NOT_FOUND, base, { sdk_code: "E4001" });
    }
    // Lifecycle 404s name a missing sandbox. The host maps sdk_code E2009 to SandboxNotFoundError.
    if (ctx.what === "sandbox" || upper.includes("SANDBOX_NOT_FOUND")) {
      return make(CODES.NOT_FOUND, base, { sdk_code: "E2009" });
    }
    return make(CODES.NOT_FOUND, base);
  }
  if (upper.includes("IMAGE_PULL") || (upper.includes("IMAGE") && lower.includes("not found"))) {
    return make(CODES.NOT_FOUND, base, { sdk_code: "E2001", suggestion: "Check the image URI used as template." });
  }
  if (status === 429) return make(CODES.QUOTA, base, { retryable: true, sdk_code: "E2002" });
  if (status === 403 && (upper.includes("QUOTA") || lower.includes("quota"))) {
    return make(CODES.QUOTA, base, { sdk_code: "E2002" });
  }
  if (status === 400 || status === 422) return make(CODES.BAD_PARAMS, base);
  if (status === 502 || status === 503 || status === 504) return make(CODES.FAIL, base, { retryable: true });
  return make(CODES.FAIL, base);
}