import { logger } from "./globalLogger.js";
export class OpenAIClient {
    constructor(config) {
        this.config = config;
    }
    get chat() {
        return {
            completions: {
                create: (params, options) => {
                    const responsePromise = this._create(params, options);
                    const promise = responsePromise.then((res) => res.data);
                    promise.withResponse = () => responsePromise;
                    // Prevent unhandled rejection if only withResponse() is used
                    promise.catch((e) => {
                        if (!(e instanceof Error && e.name === "AbortError")) {
                            logger.error("Unhandled OpenAI promise rejection:", e);
                        }
                    });
                    return promise;
                },
            },
        };
    }
    async _create(params, options) {
        const { baseURL, apiKey, defaultHeaders, fetchOptions, fetch: customFetch, } = this.config;
        const url = `${baseURL}/chat/completions`;
        const headers = {
            "Content-Type": "application/json",
            ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
            ...defaultHeaders,
        };
        const fetchFn = customFetch || fetch;
        let lastError;
        const maxRetries = 5;
        const initialDelay = 2000;
        for (let attempt = 0; attempt <= maxRetries; attempt++) {
            if (attempt > 0) {
                const delay = initialDelay * Math.pow(2, attempt - 1);
                await new Promise((resolve) => setTimeout(resolve, delay));
            }
            let response;
            try {
                response = await fetchFn(url, {
                    method: "POST",
                    headers,
                    body: JSON.stringify(params),
                    signal: options?.signal,
                    ...fetchOptions,
                });
            }
            catch (e) {
                if (e instanceof Error && e.name === "AbortError") {
                    throw e;
                }
                if (attempt < maxRetries) {
                    logger.warn("OpenAI API network error, retrying...", {
                        attempt: attempt + 1,
                        error: e,
                    });
                    lastError = e;
                    continue;
                }
                throw e;
            }
            if (response.ok) {
                if (params.stream) {
                    return {
                        data: this.streamChatCompletion(response),
                        response,
                    };
                }
                else {
                    const data = await response.json();
                    return {
                        data,
                        response,
                    };
                }
            }
            let errorBody;
            try {
                const text = await response.text();
                try {
                    errorBody = JSON.parse(text);
                }
                catch (e) {
                    logger.error("Failed to parse error response body as JSON", {
                        error: e,
                        text,
                    });
                    errorBody = text;
                }
            }
            catch (e) {
                logger.error("Failed to read error response text", { error: e });
                errorBody = {};
            }
            const error = new Error(typeof errorBody === "object" &&
                errorBody !== null &&
                "error" in errorBody &&
                typeof errorBody.error === "object" &&
                errorBody.error !== null &&
                "message" in errorBody.error
                ? String(errorBody.error.message)
                : typeof errorBody === "string"
                    ? errorBody
                    : response.statusText);
            error.status = response.status;
            error.body = errorBody;
            if (response.status === 429 && attempt < maxRetries) {
                const responseHeaders = {};
                response.headers.forEach((value, key) => {
                    responseHeaders[key] = value;
                });
                logger.warn("OpenAI API 429 Too Many Requests, retrying...", {
                    attempt: attempt + 1,
                    status: response.status,
                    responseHeaders,
                });
                lastError = error;
                continue;
            }
            const responseHeaders = {};
            response.headers.forEach((value, key) => {
                responseHeaders[key] = value;
            });
            logger.error("OpenAI API Error:", {
                status: response.status,
                statusText: response.statusText,
                requestHeaders: headers,
                responseHeaders,
                errorBody,
            });
            throw error;
        }
        throw lastError;
    }
    async *streamChatCompletion(response) {
        const reader = response.body?.getReader();
        if (!reader)
            return;
        const decoder = new TextDecoder();
        let buffer = "";
        try {
            while (true) {
                const { done, value } = await reader.read();
                if (done)
                    break;
                buffer += decoder.decode(value, { stream: true });
                const lines = buffer.split("\n");
                buffer = lines.pop() || "";
                for (const line of lines) {
                    const trimmedLine = line.trim();
                    if (!trimmedLine || !trimmedLine.startsWith("data:"))
                        continue;
                    const data = trimmedLine.slice(5).trim();
                    if (data === "[DONE]")
                        return;
                    try {
                        const json = JSON.parse(data);
                        yield json;
                    }
                    catch (e) {
                        logger.error("Failed to parse stream chunk", { error: e, data });
                    }
                }
            }
        }
        finally {
            reader.releaseLock();
        }
    }
}
