package com.gzzm.lobster.llm;

import com.gzzm.lobster.common.LobsterException;

/**
 * RunCancelledException —— 运行时取消异常 / Thrown when a run is cancelled.
 *
 * <p>携带 {@link CancelReason} 让上层能区分：
 * <ul>
 *   <li>{@link CancelReason#USER} / {@link CancelReason#TIMEOUT} / {@link CancelReason#BUDGET}
 *       —— LlmRuntime 必须硬终止，不进 fallback 链</li>
 *   <li>{@link CancelReason#UPSTREAM} —— 允许走 fallback</li>
 * </ul>
 *
 * <p>code 固定为 {@code "llm.cancelled"} —— 与旧 adapter 抛的 {@link LobsterException} 兼容。
 * 通过 {@link #getReason()} 拿到结构化原因而不用 parse message。
 */
public class RunCancelledException extends LobsterException {

    private static final long serialVersionUID = 1L;

    private final CancelReason reason;

    public RunCancelledException(CancelReason reason) {
        super("llm.cancelled", "cancelled: " + (reason == null ? "UNKNOWN" : reason.name()));
        this.reason = reason == null ? CancelReason.USER : reason;
    }

    public RunCancelledException(CancelReason reason, String message) {
        super("llm.cancelled", message == null ? ("cancelled: " + reason) : message);
        this.reason = reason == null ? CancelReason.USER : reason;
    }

    public CancelReason getReason() {
        return reason;
    }

    /**
     * 判断任意异常是否代表"用户/超时/预算"类硬取消 /
     * Whether a throwable represents a hard (non-upstream) cancel.
     *
     * <p>LlmRuntime 的 fallback 判定用这个：true 直接抛出，不降级。
     */
    public static boolean isHardCancel(Throwable t) {
        if (t instanceof RunCancelledException) {
            CancelReason r = ((RunCancelledException) t).getReason();
            return r != CancelReason.UPSTREAM;
        }
        if (t instanceof LobsterException) {
            String code = ((LobsterException) t).getCode();
            return "llm.cancelled".equals(code);
        }
        return false;
    }
}
