import { APICallError } from "ai";
import { safeJsonStringify } from "../../util/log-sanitizer.js";
const ERROR_MAPPINGS = {
    // Anthropic errors
    rate_limit_error: "rate_limit",
    request_too_large: "validation_error",
    authentication_error: "auth_error",
    permission_error: "auth_error",
    not_found_error: "not_found",
    api_error: "api_error",
    invalid_request_error: "validation_error",
    overloaded_error: "overloaded",
    // Bedrock errors
    ThrottlingException: "rate_limit",
    AccessDeniedException: "auth_error",
    NotAuthorized: "auth_error",
    InvalidClientTokenId: "auth_error",
    UnrecognizedClientException: "auth_error",
    ResourceNotFound: "not_found",
    InternalFailure: "api_error",
    ValidationError: "validation_error",
    InvalidAction: "validation_error",
    IncompleteSignature: "validation_error",
    RequestExpired: "timeout",
    ServiceUnavailable: "overloaded",
    // Clark/Gateway errors
    clark_auth_error: "auth_error",
    clark_rate_limit: "rate_limit",
    clark_not_found: "not_found",
    clark_timeout: "timeout",
    clark_overloaded: "overloaded",
    clark_api_error: "api_error",
    // Legacy
    server_error: "api_error",
};
const name = "SB_LLMProviderError";
const marker = `superblocks.ai.error.${name}`;
const symbol = Symbol.for(marker);
export class LLMProviderError extends Error {
    [symbol] = true; // used in isInstance
    messages = ["Failed to get response from LLM provider"];
    name = "LLMProviderError";
    provider;
    type = "api_error";
    url;
    statusCode;
    isRetryable = true;
    data;
    cause;
    constructor({ provider, message, type, isRetryable, aborted, cause, }) {
        super(message, { cause });
        this.provider = provider;
        if (message) {
            this.messages[0] = message;
        }
        if (aborted) {
            this.isRetryable = false;
            this.type = "aborted";
            return;
        }
        if (!cause) {
            return;
        }
        this.cause = cause;
        if (isAPICallErrorLike(cause)) {
            this.messages.push(cause.message);
            this.statusCode = cause.statusCode;
            this.url = cause.url;
            this.isRetryable = cause.isRetryable;
            if (cause.data) {
                this.data = normalizeProviderErrorData(cause.data);
                this.messages.push(this.data.message);
                if (this.data.type) {
                    this.type =
                        ERROR_MAPPINGS[this.data.type] ||
                            "api_error";
                }
            }
            // Extract messages from nested cause chain
            this.extractNestedCauseMessages(cause.cause);
        }
        else if (cause instanceof Error) {
            this.messages.push(cause.message);
            // Extract messages from nested cause chain
            this.extractNestedCauseMessages(cause.cause);
        }
        if (type) {
            this.type = type;
        }
        if (isRetryable !== undefined) {
            this.isRetryable = isRetryable;
        }
    }
    /**
     * Recursively extracts messages from nested error causes.
     *
     * This method walks the entire cause chain and extracts messages
     * from each level, ensuring that no error information is lost.
     *
     * @param cause - The cause to extract messages from
     */
    extractNestedCauseMessages(cause) {
        if (!cause) {
            return;
        }
        if (cause instanceof Error) {
            this.messages.push(cause.message);
            // Recursively process nested cause
            const errorWithCause = cause;
            if (errorWithCause.cause) {
                this.extractNestedCauseMessages(errorWithCause.cause);
            }
        }
        else if (typeof cause === "string") {
            // Handle string causes
            this.messages.push(cause);
        }
        else if (typeof cause === "object" && cause !== null) {
            // Handle objects with message property
            const causeObj = cause;
            if (causeObj.message) {
                this.messages.push(causeObj.message);
            }
            if (causeObj.cause) {
                this.extractNestedCauseMessages(causeObj.cause);
            }
        }
    }
    set message(message) {
        this.messages[0] = message;
    }
    get message() {
        return this.messages.join(": ");
    }
    toJSON() {
        return {
            name: this.name,
            type: this.type,
            message: this.message,
            url: this.url,
            statusCode: this.statusCode,
            isRetryable: this.isRetryable,
            data: this.data,
            cause: this.cause,
        };
    }
    static hasMarker(error, marker) {
        const markerSymbol = Symbol.for(marker);
        return (error != null &&
            typeof error === "object" &&
            markerSymbol in error &&
            typeof error[markerSymbol] === "boolean" &&
            error[markerSymbol] === true);
    }
    static isInstance(error) {
        return LLMProviderError.hasMarker(error, marker);
    }
}
function normalizeProviderErrorData(data) {
    // Use type guards to narrow parsed.data type
    if (isAnthropicErrorData(data)) {
        // Anthropic/Vertex: { data: { error: { message, type } } }
        return { type: data.error.type, message: data.error.message };
    }
    else if (isBedrockErrorData(data)) {
        // Bedrock: { data: { message, type } }
        return { type: data.type || undefined, message: data.message };
    }
    else if (isClarkErrorData(data)) {
        // Clark: { data: { responseMetadata: { message, ... }
        const meta = data.responseMetadata;
        const status = typeof meta.status === "number" ? meta.status : undefined;
        const type = getClarkErrorType(status);
        const message = (typeof meta.message === "string" && meta.message) ||
            (typeof meta.error?.message === "string" && meta.error.message) ||
            `Unknown Clark error: ${safeJsonStringify(data)}`;
        return { type, message, isClark: true };
    }
    return { message: `Unknown error format: ${safeJsonStringify(data)}` };
}
function getClarkErrorType(status) {
    if (status === 401 || status === 403)
        return "clark_auth_error";
    if (status === 404)
        return "clark_not_found";
    if (status === 429)
        return "clark_rate_limit";
    if (status === 408 || status === 504)
        return "clark_timeout";
    if (status === 503)
        return "clark_overloaded";
    return "clark_api_error";
}
/**
 * Type guard for objects that have the shape of an APICallError.
 *
 * This is a fallback for when APICallError.isInstance() returns false
 * but the object still has the expected APICallError structure.
 */
export function isAPICallErrorLike(cause) {
    if (APICallError.isInstance(cause)) {
        return true;
    }
    if (typeof cause !== "object" || cause === null) {
        return false;
    }
    if (!("url" in cause) || typeof cause.url !== "string") {
        return false;
    }
    if (!("name" in cause) || cause.name !== "AI_APICallError") {
        return false;
    }
    if (!("message" in cause) || typeof cause.message !== "string") {
        return false;
    }
    if ("statusCode" in cause &&
        !(typeof cause.statusCode == "number" || cause.statusCode == undefined)) {
        return false;
    }
    if (!("isRetryable" in cause) || typeof cause.isRetryable !== "boolean") {
        return false;
    }
    return true;
}
// Type guard for Anthropic/Vertex error format
function isAnthropicErrorData(data) {
    return (typeof data === "object" &&
        data !== null &&
        "error" in data &&
        typeof data.error === "object" &&
        data.error !== null &&
        "message" in data.error &&
        "type" in data.error);
}
// Type guard for Bedrock error format
function isBedrockErrorData(data) {
    return (typeof data === "object" &&
        data !== null &&
        "message" in data &&
        typeof data.message === "string");
}
// Type guard for Clark error format
function isClarkErrorData(data) {
    return (typeof data === "object" &&
        data !== null &&
        "responseMetadata" in data &&
        typeof data.responseMetadata === "object" &&
        data.responseMetadata !== null);
}
export function isProviderErrorRetryable(error) {
    if (LLMProviderError.isInstance(error)) {
        return error.isRetryable;
    }
    // For thrown errors or unstructured errors, be permissive and retry
    // These might be network issues, timeouts, or other transient problems
    return true;
}
/**
 * Checks if an error is the Vercel AI SDK's AI_NoOutputGeneratedError.
 *
 * This error occurs when a stream completes without generating any output,
 * which can happen due to provider issues or model behavior. This is
 * considered retryable and should trigger provider fallback.
 */
export function isNoOutputError(error) {
    if (!error || typeof error !== "object") {
        return false;
    }
    // Check by error name (Vercel AI SDK error class)
    if ("name" in error && error.name === "AI_NoOutputGeneratedError") {
        return true;
    }
    // Check by message content (fallback for serialized errors)
    if ("message" in error && typeof error.message === "string") {
        return (error.message.includes("No output generated") ||
            error.message.includes("Check the stream for errors"));
    }
    return false;
}
/**
 * Checks if an error indicates the prompt exceeded the model's token limit.
 */
export function isTokenLimitError(error) {
    if (!error || typeof error !== "object")
        return false;
    const isTokenLimitMessage = (msg) => msg.toLowerCase().includes("prompt is too long") ||
        msg.toLowerCase().includes("input is too long");
    const err = error;
    // Check top-level message
    if (err.message && isTokenLimitMessage(err.message)) {
        return true;
    }
    // Check nested data using normalized provider error data
    if (err.data) {
        const normalized = normalizeProviderErrorData(err.data);
        if (isTokenLimitMessage(normalized.message)) {
            return true;
        }
    }
    // Check cause chain
    if (err.cause && isTokenLimitError(err.cause)) {
        return true;
    }
    return false;
}
//# sourceMappingURL=error.js.map