"use strict";
/**
 * Cursor Daemon Entry
 *
 * Dedicated child-process entrypoint for local OpenAI-compatible Cursor proxy.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startCursorDaemonServer = void 0;
const http = __importStar(require("http"));
const stream_1 = require("stream");
const cursor_executor_1 = require("./cursor-executor");
const cursor_anthropic_response_1 = require("./cursor-anthropic-response");
const cursor_anthropic_translator_1 = require("./cursor-anthropic-translator");
const cursor_auth_1 = require("./cursor-auth");
const cursor_models_1 = require("./cursor-models");
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
function writeJson(res, statusCode, payload) {
    res.writeHead(statusCode, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(payload));
}
function readJsonBody(req) {
    return new Promise((resolve, reject) => {
        const chunks = [];
        let total = 0;
        let settled = false;
        const resolveOnce = (payload) => {
            if (settled)
                return;
            settled = true;
            resolve(payload);
        };
        const rejectOnce = (error) => {
            if (settled)
                return;
            settled = true;
            reject(error);
        };
        req.on('data', (chunk) => {
            total += chunk.length;
            if (total > MAX_BODY_SIZE) {
                // Stop processing body, but avoid force-closing socket so caller can return 413 cleanly.
                req.pause();
                rejectOnce(new Error('Request body too large (max 10MB)'));
                return;
            }
            chunks.push(chunk);
        });
        req.on('end', () => {
            const raw = Buffer.concat(chunks).toString('utf8').trim();
            if (!raw) {
                resolveOnce({});
                return;
            }
            try {
                resolveOnce(JSON.parse(raw));
            }
            catch {
                rejectOnce(new Error('Invalid JSON in request body'));
            }
        });
        req.on('error', (error) => {
            rejectOnce(error instanceof Error ? error : new Error(String(error)));
        });
    });
}
function normalizeMessages(raw) {
    if (!Array.isArray(raw)) {
        throw new Error('messages must be an array');
    }
    return raw.map((message, index) => {
        if (typeof message !== 'object' || message === null) {
            throw new Error(`messages[${index}] must be an object`);
        }
        const m = message;
        if (typeof m.role !== 'string' || !m.role) {
            throw new Error(`messages[${index}].role must be a non-empty string`);
        }
        const content = m.content;
        if (content !== undefined &&
            content !== null &&
            typeof content !== 'string' &&
            !Array.isArray(content)) {
            throw new Error(`messages[${index}].content must be string, array, or null`);
        }
        return {
            role: m.role,
            content: (content ?? ''),
            name: typeof m.name === 'string' ? m.name : undefined,
            tool_call_id: typeof m.tool_call_id === 'string' ? m.tool_call_id : undefined,
            tool_calls: Array.isArray(m.tool_calls)
                ? m.tool_calls
                : undefined,
        };
    });
}
async function pipeWebResponseToNode(response, res) {
    res.statusCode = response.status;
    response.headers.forEach((value, key) => {
        res.setHeader(key, value);
    });
    if (!response.body) {
        res.end();
        return;
    }
    const nodeStream = stream_1.Readable.fromWeb(response.body);
    await new Promise((resolve, reject) => {
        nodeStream.on('error', reject);
        nodeStream.on('end', resolve);
        nodeStream.pipe(res);
    });
}
function parseArgs(argv) {
    let port = 20129;
    let ghostMode = true;
    for (let i = 0; i < argv.length; i++) {
        const arg = argv[i];
        if (arg === '--port' && argv[i + 1]) {
            const parsed = parseInt(argv[i + 1], 10);
            if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
                port = parsed;
            }
            i++;
            continue;
        }
        if (arg === '--ghost-mode' && argv[i + 1]) {
            const value = argv[i + 1];
            ghostMode = value !== 'false' && value !== '0';
            i++;
            continue;
        }
    }
    return { port, ghostMode };
}
function startCursorDaemonServer(options) {
    const executor = new cursor_executor_1.CursorExecutor();
    const server = http.createServer(async (req, res) => {
        const method = req.method || 'GET';
        const requestUrl = req.url || '/';
        const isOpenAiRoute = method === 'POST' && requestUrl === '/v1/chat/completions';
        const isAnthropicRoute = method === 'POST' && requestUrl === '/v1/messages';
        try {
            if (method === 'GET' && requestUrl === '/health') {
                writeJson(res, 200, { ok: true, service: 'cursor-daemon' });
                return;
            }
            if (method === 'GET' && requestUrl === '/v1/models') {
                const authStatus = (0, cursor_auth_1.checkAuthStatus)();
                const models = await (0, cursor_models_1.getModelsForDaemon)({
                    credentials: authStatus.authenticated && !authStatus.expired && authStatus.credentials
                        ? {
                            accessToken: authStatus.credentials.accessToken,
                            machineId: authStatus.credentials.machineId,
                            ghostMode: options.ghostMode,
                        }
                        : null,
                });
                const data = models.map((model) => ({
                    id: model.id,
                    object: 'model',
                    created: 0,
                    owned_by: model.provider,
                }));
                writeJson(res, 200, { object: 'list', data });
                return;
            }
            if (!isOpenAiRoute && !isAnthropicRoute) {
                writeJson(res, 404, { error: 'Not found' });
                return;
            }
            const rawBody = await readJsonBody(req);
            const anthropicBody = isAnthropicRoute ? (0, cursor_anthropic_translator_1.translateAnthropicRequest)(rawBody) : undefined;
            const parsedBody = anthropicBody ?? (rawBody || {});
            const messages = anthropicBody
                ? anthropicBody.messages
                : normalizeMessages(parsedBody.messages);
            const requestedModel = typeof parsedBody.model === 'string' && parsedBody.model.trim().length > 0
                ? parsedBody.model.trim()
                : undefined;
            const stream = parsedBody.stream === true;
            const authStatus = (0, cursor_auth_1.checkAuthStatus)();
            if (!authStatus.authenticated || !authStatus.credentials) {
                const message = 'Cursor credentials not found. Run `ccs legacy cursor auth` first.';
                if (isAnthropicRoute) {
                    await pipeWebResponseToNode((0, cursor_anthropic_response_1.createAnthropicErrorResponse)(401, 'authentication_error', message), res);
                }
                else {
                    writeJson(res, 401, {
                        error: {
                            type: 'authentication_error',
                            message,
                        },
                    });
                }
                return;
            }
            if (authStatus.expired) {
                const message = 'Cursor credentials expired. Run `ccs legacy cursor auth` again.';
                if (isAnthropicRoute) {
                    await pipeWebResponseToNode((0, cursor_anthropic_response_1.createAnthropicErrorResponse)(401, 'authentication_error', message), res);
                }
                else {
                    writeJson(res, 401, {
                        error: {
                            type: 'authentication_error',
                            message,
                        },
                    });
                }
                return;
            }
            const daemonCredentials = {
                accessToken: authStatus.credentials.accessToken,
                machineId: authStatus.credentials.machineId,
                ghostMode: options.ghostMode,
            };
            const availableModels = await (0, cursor_models_1.getModelsForDaemon)({
                credentials: daemonCredentials,
            });
            const model = (0, cursor_models_1.resolveCursorRequestModel)(requestedModel, availableModels);
            if (requestedModel &&
                requestedModel !== model &&
                (process.env.CCS_DEBUG === '1' || process.env.CCS_DEBUG === 'true')) {
                console.error(`[cursor] Requested model "${requestedModel}" is unavailable; falling back to "${model}".`);
            }
            const abortController = new AbortController();
            const abortOnDisconnect = () => {
                if (!abortController.signal.aborted && !res.writableEnded) {
                    abortController.abort();
                }
            };
            req.on('aborted', abortOnDisconnect);
            req.on('close', abortOnDisconnect);
            res.on('close', abortOnDisconnect);
            const result = await executor.execute({
                model,
                stream,
                signal: abortController.signal,
                credentials: daemonCredentials,
                body: {
                    messages,
                    tools: Array.isArray(parsedBody.tools) ? parsedBody.tools : undefined,
                    reasoning_effort: typeof parsedBody.reasoning_effort === 'string'
                        ? parsedBody.reasoning_effort
                        : undefined,
                },
            });
            const outgoingResponse = isAnthropicRoute
                ? await (0, cursor_anthropic_response_1.createAnthropicProxyResponse)(result.response)
                : result.response;
            await pipeWebResponseToNode(outgoingResponse, res);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : 'Unknown error';
            const isPayloadTooLarge = message.includes('Request body too large');
            const status = isPayloadTooLarge ? 413 : 400;
            if (isAnthropicRoute) {
                await pipeWebResponseToNode((0, cursor_anthropic_response_1.createAnthropicErrorResponse)(status, 'invalid_request_error', message), res);
            }
            else {
                writeJson(res, status, {
                    error: {
                        type: 'invalid_request_error',
                        message,
                    },
                });
            }
        }
    });
    server.listen(options.port, '127.0.0.1');
    return server;
}
exports.startCursorDaemonServer = startCursorDaemonServer;
if (require.main === module) {
    const options = parseArgs(process.argv.slice(2));
    const server = startCursorDaemonServer(options);
    const shutdown = () => {
        server.close();
    };
    process.on('SIGTERM', shutdown);
    process.on('SIGINT', shutdown);
}
//# sourceMappingURL=cursor-daemon-entry.js.map