[content] /fast/home/franz.srambical/crowd-pilot-extension/out/extension.js (javascript)
const https = __importStar(require("https"));
const http = __importStar(require("http"));
const buffer_1 = require("buffer");
const SGLANG_HOSTNAME = 'hai001';
const SGLANG_PORT = 30000;
const SGLANG_BASE_PATH = '/v1/chat/completions';
const SGLANG_MODEL_NAME = 'qwen/qwen3-0.6b';
const GEMINI_HOSTNAME = 'generativelanguage.googleapis.com';
const GEMINI_PORT = 443;
const GEMINI_BASE_PATH = '/v1beta/openai/chat/completions';
const GEMINI_MODEL_NAME = 'gemini-2.5-flash';
const USE_GEMINI = true;
function activate(context) {
    console.log('[crowd-pilot] Extension activated');
    // Configure terminal to allow tab keybinding to work
    (async () => {
        const config = vscode.workspace.getConfiguration('terminal.integrated');
        const commandsToSkipShell = config.get('commandsToSkipShell', []);
        let updated = false;
        if (!commandsToSkipShell.includes('crowd-pilot.modelRun')) {
            commandsToSkipShell.push('crowd-pilot.modelRun');
            updated = true;
        }
        if (!commandsToSkipShell.includes('crowd-pilot.hideUi')) {
            commandsToSkipShell.push('crowd-pilot.hideUi');
            updated = true;
        }
        if (updated) {
            await config.update('commandsToSkipShell', commandsToSkipShell, vscode.ConfigurationTarget.Global);
        }
    })().catch((err) => console.error('[crowd-pilot] Startup initialization error:', err));
    const hideUi = vscode.commands.registerCommand('crowd-pilot.hideUi', () => {
        hidePreviewUI(true);
    });
    const modelRun = vscode.commands.registerCommand('crowd-pilot.modelRun', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) {
            return;
        }
        try {
            // Confirm only when a suggestion is visible
            if (!previewVisible) {
                return;
            }
            let action = currentAction;
            if (!action) {
                const single = await requestModelActions(editor);
                currentAction = single;
                action = single;
            }
            if (!action) {
                hidePreviewUI();
                return;
            }
            hidePreviewUI(false);
            await executeAction(action);
            autoShowNextAction();
        }
        catch (err) {
            const errorMessage = err instanceof Error ? err.message : String(err);
            vscode.window.showErrorMessage(`Model run failed: ${errorMessage}`);
        }
    });
    const sglangTest = vscode.commands.registerCommand('crowd-pilot.sglangTest', async () => {
        try {
            await callSGLangChat();
        }
        catch (err) {
            const errorMessage = err instanceof Error ? err.message : String(err);
            vscode.window.showErrorMessage(`SGLang test failed: ${errorMessage}`);
        }
    });
    // Auto-preview listeners
    const onSelChange = vscode.window.onDidChangeTextEditorSelection((e) => {
        if (e.textEditor === vscode.window.activeTextEditor) {
            suppressAutoPreview = false;
            autoShowNextAction();
        }
    });
    const onActiveChange = vscode.window.onDidChangeActiveTextEditor(() => {
        suppressAutoPreview = false;
        autoShowNextAction();
    });
    const onDocChange = vscode.workspace.onDidChangeTextDocument((e) => {
        if (vscode.window.activeTextEditor?.document === e.document) {
            suppressAutoPreview = false;
            autoShowNextAction();
        }
    });
    context.subscriptions.push(hideUi, sglangTest, modelRun, onSelChange, onActiveChange, onDocChange);
}
function deactivate() { }
let currentAction;
async function executeAction(action) {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        return;
    }
    const doc = editor.document;
    const term = vscode.window.terminals[0] ?? vscode.window.createTerminal('Test');
    if (action.kind === 'showTextDocument') {
        await vscode.window.showTextDocument(doc);
        return;
    }
    if (action.kind === 'setSelections') {
        editor.selections = action.selections.map(s => new vscode.Selection(new vscode.Position(s.start[0], s.start[1]), new vscode.Position(s.end[0], s.end[1])));
        if (editor.selections.length > 0) {
            editor.revealRange(editor.selections[0], vscode.TextEditorRevealType.InCenterIfOutsideViewport);
        }
        return;
    }
    if (action.kind === 'editInsert') {
        await editor.edit((e) => e.insert(new vscode.Position(action.position[0], action.position[1]), action.text));
        return;
    }
    if (action.kind === 'editDelete') {
        const range = new vscode.Range(new vscode.Position(action.range.start[0], action.range.start[1]), new vscode.Position(action.range.end[0], action.range.end[1]));
        await editor.edit((e) => e.delete(range));
        return;
    }
    if (action.kind === 'editReplace') {
        const range = new vscode.Range(new vscode.Position(action.range.start[0], action.range.start[1]), new vscode.Position(action.range.end[0], action.range.end[1]));
        await editor.edit((e) => e.replace(range, action.text));
        return;
    }
    if (action.kind === 'terminalShow') {
        term.show();
        return;
    }
    if (action.kind === 'terminalSendText') {
        term.sendText(action.text);
        return;
    }
}
// -------------------- UI State & Helpers --------------------
const UI_CONTEXT_KEY = 'crowdPilot.uiVisible';
let previewVisible = false;
let decorationDeleteType;
let decorationReplaceType;
let decorationReplaceBlockType;
let mockStep = 0;
let suppressAutoPreview = false;
function disposePreviewDecorations() {
    try {
        decorationDeleteType?.dispose();
    }
    catch { }
    try {
        decorationReplaceType?.dispose();
    }
    catch { }
    try {
        decorationReplaceBlockType?.dispose();
    }
    catch { }
    decorationDeleteType = undefined;
    decorationReplaceType = undefined;
    decorationReplaceBlockType = undefined;
}
function getDynamicMargin(editor, anchorLine, text) {
    // Count lines in the preview text
    const lines = text.split(/
?
/);
    const height = lines.length;
    // We need to check the document lines that will be covered by this panel.
    // The panel starts at 'anchorLine' and extends downwards by 'height' lines.
    // However, visually, since it's 'after', it sits to the right of 'anchorLine',
    // and then flows down.
    // So we check document lines from anchorLine to anchorLine + height - 1.
    const doc = editor.document;
    let maxLen = 0;
    const startLine = anchorLine;
    const endLine = Math.min(doc.lineCount - 1, anchorLine + height - 1);
    for (let i = startLine; i <= endLine; i++) {
        const lineText = doc.lineAt(i).text;
        // Simple approximation: assume tabs are 4 spaces if we can't get config easily, 
        // or just treat them as 1 char (which might underestimate). 
        // Better to overestimate: treat tab as 4 chars.
        const len = lineText.replace(/	/g, '    ').length;
        if (len > maxLen) {
            maxLen = len;
        }
    }
    // Length of the anchor line itself
    const anchorLineText = doc.lineAt(anchorLine).text;
    const anchorLen = anchorLineText.replace(/	/g, '    ').length;
    // The offset needed is maxLen - anchorLen.
    // If maxLen <= anchorLen, offset is 0 (margin is just base padding).
    // If maxLen > anchorLen, we need to push right by (maxLen - anchorLen).
    const diff = Math.max(0, maxLen - anchorLen);
    // Base margin 2rem is roughly 4ch. Let's use ch units for everything to be consistent.
    // 1ch is width of '0'. In monospace, mostly consistent.
    // Add 3ch extra padding for safety/visual gap.
    const margin = diff + 4;
    return `${margin}ch`;
}
function showPreviewUI(action) {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        return;
    }
    disposePreviewDecorations();
    // FIXME (f.srambical): add file switch 
    const next = (action.kind === 'editInsert' || action.kind === 'editDelete' || action.kind === 'editReplace' || action.kind === 'terminalSendText' || action.kind === 'setSelections') ? action : undefined;
    if (!next) {
        previewVisible = false;
        vscode.commands.executeCommand('setContext', UI_CONTEXT_KEY, false);
        currentAction = action;
        return;
    }
    const trimText = (t) => {
        const oneLine = t.replace(/
?
/g, '\
');
        return oneLine.length > 80 ? oneLine.slice(0, 77) + '…' : oneLine;
    };
    if (next.kind === 'setSelections') {
        // For setSelections, we only preview the primary selection's start/active position
        const selection = next.selections[0];
        const targetPos = new vscode.Position(selection.start[0], selection.start[1]);
        // Check if the target position is visible
        const isVisible = editor.visibleRanges.some(r => r.contains(targetPos));
        let anchorPos = targetPos;
        let label = "↳ Move Cursor Here";
        if (!isVisible && editor.visibleRanges.length > 0) {
            const firstVisible = editor.visibleRanges[0].start;
            const lastVisible = editor.visibleRanges[editor.visibleRanges.length - 1].end;
            if (targetPos.isBefore(firstVisible)) {
                anchorPos = editor.document.lineAt(firstVisible.line).range.end;
            }
            else {
                anchorPos = editor.document.lineAt(lastVisible.line).range.end;
            }
            if (targetPos.line < anchorPos.line) {
                label = `↑ Move Cursor to Line ${targetPos.line + 1}`;
            }
            else {
                label = `↓ Move Cursor to Line ${targetPos.line + 1}`;
            }
        }
        const margin = getDynamicMargin(editor, anchorPos.line, label);
        decorationReplaceBlockType = vscode.window.createTextEditorDecorationType({
            after: {
                contentText: '',
                color: new vscode.ThemeColor('charts.purple'),
                backgroundColor: new vscode.ThemeColor('editor.background'),
                fontStyle: 'italic',
                fontWeight: '600',
                margin: `0 0 0 ${margin}`,
                textDecoration: `none; display: inline-block; white-space: pre; content: "${label}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top;`
            }
        });
        editor.setDecorations(decorationReplaceBlockType, [{ range: new vscode.Range(anchorPos, anchorPos) }]);
    }
    else if (next.kind === 'terminalSendText') {
        const cursor = editor.selection.active;
        const lineEnd = editor.document.lineAt(cursor.line).range.end;
        const cmd = next.text.replace(/"/g, '\\"').replace(/
?
/g, '\\A ');
        const margin = getDynamicMargin(editor, cursor.line, "↳ Execute in Terminal:
" + next.text);
        decorationReplaceBlockType = vscode.window.createTextEditorDecorationType({
            after: {
                contentText: '',
                color: new vscode.ThemeColor('charts.purple'),
                backgroundColor: new vscode.ThemeColor('editor.background'),
                fontStyle: 'italic',
                fontWeight: '600',
                margin: `0 0 0 ${margin}`,
                textDecoration: `none; display: inline-block; white-space: pre; content: "↳ Execute in Terminal:\\A ${cmd}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top;`
            }
        });
        editor.setDecorations(decorationReplaceBlockType, [{ range: new vscode.Range(lineEnd, lineEnd) }]);
    }
    else if (next.kind === 'editInsert') {
        const posLine = next.position[0];
        const fullBlock = next.text;
        const cssContent = fullBlock
            .replace(/"/g, '\\"')
            .replace(/
?
/g, '\\A ');
        const docLineCount = editor.document.lineCount;
        // If inserting at EOF (or beyond), attach to the last line.
        // Otherwise, attach to the line AT the insertion point and shift visually UP into the gap.
        let anchorLine = posLine;
        let shiftUp = true;
        if (anchorLine >= docLineCount) {
            anchorLine = docLineCount - 1;
            shiftUp = false; // At EOF, we just append below or to the right
        }
        const anchorPos = new vscode.Position(anchorLine, Number.MAX_VALUE);
        // We attach to the line AT the insertion point.
        // The panel floats to the right of this line.
        // The dashed line connects the start of this line to the panel.
        // This indicates that the new text will be inserted at this line position (pushing the current line down).
        const marginCheckLine = anchorLine;
        const margin = getDynamicMargin(editor, marginCheckLine, fullBlock);
        const topOffset = '0';
        // Dashed line style
        // We use 'before' decoration for the line.
        // It needs to be absolute, full width (or enough to reach left), 
        // and aligned with the panel top.
        const beforeDecoration = {
            contentText: '',
            textDecoration: `none; position: absolute; left: 0; width: 100vw; border-top: 1px dashed var(--vscode-charts-purple); top: 0; height: 0; z-index: 99; pointer-events: none;`
        };
        decorationReplaceBlockType = vscode.window.createTextEditorDecorationType({
            before: beforeDecoration,
            after: {
                contentText: '',
                color: new vscode.ThemeColor('charts.purple'),
                backgroundColor: new vscode.ThemeColor('editor.background'),
                fontStyle: 'italic',
                fontWeight: '600',
                margin: `0 0 0 ${margin}`,
                textDecoration: `none; display: inline-block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top; top: ${topOffset};`
            }
        });
        editor.setDecorations(decorationReplaceBlockType, [{ range: new vscode.Range(anchorPos, anchorPos) }]);
    }
    else if (next.kind === 'editDelete') {
        const range = new vscode.Range(new vscode.Position(next.range.start[0], next.range.start[1]), new vscode.Position(next.range.end[0], next.range.end[1]));
        decorationDeleteType = vscode.window.createTextEditorDecorationType({
            backgroundColor: 'rgba(255, 60, 60, 0.18)',
            border: '1px solid rgba(255, 60, 60, 0.35)',
            textDecoration: 'line-through'
        });
        editor.setDecorations(decorationDeleteType, [{ range }]);
    }
    else if (next.kind === 'editReplace') {
        const range = new vscode.Range(new vscode.Position(next.range.start[0], next.range.start[1]), new vscode.Position(next.range.end[0], next.range.end[1]));
        // Highlight original range (to be replaced)
        decorationReplaceType = vscode.window.createTextEditorDecorationType({
            backgroundColor: 'rgba(255,165,0,0.15)',
            border: '1px dashed rgba(255,165,0,0.45)',
            color: new vscode.ThemeColor('disabledForeground'),
            textDecoration: 'line-through'
        });
        editor.setDecorations(decorationReplaceType, [{ range }]);
        // Show replacement block to the right of the first replaced line
        const fullBlock = next.text;
        // CSS-escape the text for the 'content' property:
        // - Escape double quotes
        // - Replace newlines with \A (CSS newline)
        const cssContent = fullBlock
            .replace(/"/g, '\\"')
            .replace(/
?
/g, '\\A ');
        // Attach 'after' decoration to the start of the replacement range
        // (Actually, attaching to the end of the first line is safer for 'after')
        const anchorLine = range.start.line;
        const anchorPos = new vscode.Position(anchorLine, Number.MAX_VALUE);
        const margin = getDynamicMargin(editor, anchorLine, fullBlock);
        decorationReplaceBlockType = vscode.window.createTextEditorDecorationType({
            after: {
                contentText: '', // Handled by CSS content
                color: new vscode.ThemeColor('charts.purple'),
                backgroundColor: new vscode.ThemeColor('editor.background'),
                fontStyle: 'italic',
                fontWeight: '600',
                margin: `0 0 0 ${margin}`,
                textDecoration: `none; display: inline-block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top;`
            }
        });
        editor.setDecorations(decorationReplaceBlockType, [{ range: new vscode.Range(anchorPos, anchorPos) }]);
    }
    previewVisible = true;
    vscode.commands.executeCommand('setContext', UI_CONTEXT_KEY, true);
    currentAction = action;
}
function hidePreviewUI(suppress) {
    disposePreviewDecorations();
    previewVisible = false;
    vscode.commands.executeCommand('setContext', UI_CONTEXT_KEY, false);
    if (suppress) {
        suppressAutoPreview = true;
    }
}
// -------------------- Hardcoded single-step actions --------------------
function getHardcodedNextAction(editor) {
    const cursor = editor.selection.active;
    const doc = editor.document;
    const lineCount = doc.lineCount;
    const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
    // Step 0: Insert multiline content two lines below the cursor (start of target line)
    if (mockStep === 0) {
        const targetLine = clamp(cursor.line + 2, 0, Math.max(0, lineCount - 1));
        return {
            kind: 'editInsert',
            position: [targetLine, 0],
            text: '/* crowd-pilot: insert start */
line A
line B
/* crowd-pilot: insert end */
'
        };
    }
    // Step 1: Replace a two-line range three and four lines below the cursor
    if (mockStep === 1) {
        const startLine = clamp(cursor.line + 3, 0, Math.max(0, lineCount - 1));
        const endLine = clamp(startLine + 1, 0, Math.max(0, lineCount - 1));
        const endChar = doc.lineAt(endLine).range.end.character;
        const range = {
            start: [startLine, 0],
            end: [endLine, endChar]
        };
        const replacement = [
            '/* crowd-pilot: replacement */',
            'REPLACED LINE 1',
            'REPLACED LINE 2'
        ].join('
');
        return { kind: 'editReplace', range, text: replacement };
    }
    // Step 2: Delete a three-line range six to eight lines below the cursor
    if (mockStep === 2) {
        const startLine = clamp(cursor.line + 6, 0, Math.max(0, lineCount - 1));
        const endLine = clamp(startLine + 2, 0, Math.max(0, lineCount - 1));
        // To fully delete the lines including the newline, we target the start of the next line.
        let endPosLine = endLine + 1;
        let endPosChar = 0;
        if (endPosLine >= lineCount) {
            // If deleting the last line(s), just go to the end of the document
            endPosLine = lineCount - 1;
            endPosChar = doc.lineAt(endPosLine).range.end.character;
        }
        const range = {
            start: [startLine, 0],
            end: [endPosLine, endPosChar]
        };
        return { kind: 'editDelete', range };
    }
    // Step 3: Execute in Terminal
    if (mockStep === 3) {
        return { kind: 'terminalSendText', text: 'echo "Hello World"' };
    }
    // Step 4: Move Cursor to End of File
    if (mockStep === 4) {
        const lastLine = doc.lineCount - 1;
        const lastChar = doc.lineAt(lastLine).range.end.character;
        return {
            kind: 'setSelections',
            selections: [{ start: [lastLine, lastChar], end: [lastLine, lastChar] }]
        };
    }
    return undefined;
}
function advanceMockStep() {
    mockStep = (mockStep + 1) % 5;
}
async function autoShowNextAction() {
    if (suppressAutoPreview) {
        return;
    }
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        return;
    }
    try {
        const next = await requestModelActions(editor);
        if (next) {
            showPreviewUI(next);
        }
        else {
            hidePreviewUI();
        }
    }
    catch (err) {
        hidePreviewUI();
    }
}
// -------------------- SGLang Client (simple test) --------------------
async function callSGLangChat() {
    const config = vscode.workspace.getConfiguration();
    let hostname;
    let port;
    let path;
    let useHttps = true;
    let modelName;
    const headers = {
        'Content-Type': 'application/json'
    };
    if (!USE_GEMINI) {
        // SGLang
        hostname = SGLANG_HOSTNAME;
        port = SGLANG_PORT;
        path = SGLANG_BASE_PATH;
        useHttps = false;
        modelName = SGLANG_MODEL_NAME;
    }
    else {
        // Gemini
        const apiKey = config.get('crowd-pilot.apiKey');
        if (!apiKey) {
            vscode.window.showErrorMessage('Crowd Pilot: Please set your API Key in settings (crowd-pilot.apiKey).');
            return;
        }
        hostname = GEMINI_HOSTNAME;
        port = GEMINI_PORT;
        path = GEMINI_BASE_PATH;
        headers['Authorization'] = `Bearer ${apiKey}`;
        modelName = GEMINI_MODEL_NAME;
    }
    const requestBody = {
        model: modelName,
        messages: [
            { role: 'user', content: 'What is the capital of France?' }
        ]
    };
    const postData = JSON.stringify(requestBody);
    headers['Content-Length'] = buffer_1.Buffer.byteLength(postData);
    const options = {
        hostname,
        port,
        path,
        method: 'POST',
        headers
    };
    const requestModule = useHttps ? https : http;
    try {
        const json = await new Promise((resolve, reject) => {
            const req = requestModule.request(options, (res) => {
                let data = '';
                res.on('data', (chunk) => {
                    data += chunk.toString();
                });
                res.on('end', () => {
                    try {
                        resolve(JSON.parse(data));
                    }
                    catch (err) {
                        reject(new Error(`Failed to parse response: ${err instanceof Error ? err.message : String(err)}`));
                    }
                });
            });
            req.on('error', (err) => {
                reject(err);
            });
            req.write(postData);
            req.end();
        });
        vscode.window.showInformationMessage(`Response: ${JSON.stringify(json, null, 2)}`);
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        vscode.window.showErrorMessage(`Request failed: ${errorMessage}`);
    }
}
// -------------------- Model-planned Actions --------------------
async function requestModelActions(editor) {
    const config = vscode.workspace.getConfiguration();
    let hostname;
    let port;
    let path;
    let useHttps = true;
    let modelName;
    const headers = {
        'Content-Type': 'application/json'
    };
    if (!USE_GEMINI) {
        // SGLang
        hostname = SGLANG_HOSTNAME;
        port = SGLANG_PORT;
        path = SGLANG_BASE_PATH;
        useHttps = false;
        modelName = SGLANG_MODEL_NAME;
    }
    else {
        // Gemini
        const apiKey = config.get('crowd-pilot.apiKey');
        if (!apiKey) {
            vscode.window.showErrorMessage('Crowd Pilot: Please set your API Key in settings (crowd-pilot.apiKey).');
            throw new Error('API key not set');
        }
        hostname = GEMINI_HOSTNAME;
        port = GEMINI_PORT;
        path = GEMINI_BASE_PATH;
        headers['Authorization'] = `Bearer ${apiKey}`;
        modelName = GEMINI_MODEL_NAME;
    }
    const schemaDescription = [
        'Role: You suggest the next VS Code editor/terminal action to progress the current task.',
        'Output ONLY a JSON object (no prose, no code fences).',
        'Coordinates are zero-based [line, column].',
        'Allowed actions (JSON schema-like):',
        '{ kind: "showTextDocument" }',
        '{ kind: "setSelections", selections: Array<{ start: [number, number], end: [number, number] }> }',
        '{ kind: "editInsert", position: [number, number], text: string }',
        '{ kind: "editDelete", range: { start: [number, number], end: [number, number] } }',
        '{ kind: "editReplace", range: { start: [number, number], end: [number, number] }, text: string }',
        '{ kind: "terminalShow" }',
        '{ kind: "terminalSendText", text: string }',
        'Guidelines:',
        '- If you you insert text, insert until the logical end of the current statement or block.',
        '- When inserting text, make sure to not repeat existing text (except when replacing existing text).',
        '- Use double-quoted JSON strings.'
    ].join('
');
    const doc = editor.document;
    const cursor = editor.selection.active;
    const fullText = doc.getText();
    const numberedContext = fullText.split(/
?
/).map((line, i) => `${i}: ${line}`).join('
');
    const tabbingPrompt = [
        'Your role: Propose the single next action according to the schema to help the developer progress.',
        '',
        'Available context:',
        `- File: ${doc.fileName}`,
        `- Language: ${doc.languageId}`,
        `- Cursor: (${cursor.line}, ${cursor.character})`,
        '',
        'Full file content (zero-based line numbers):',
        '```',
        numberedContext,
        '```',
        '',
        'Respond with ONLY a JSON object containing exactly one action.'
    ].join('
');
    const requestBody = {
        model: modelName,
        messages: [
            { role: 'system', content: schemaDescription },
            { role: 'user', content: tabbingPrompt }
        ]
    };
    const postData = JSON.stringify(requestBody);
    headers['Content-Length'] = buffer_1.Buffer.byteLength(postData);
    const options = {
        hostname,
        port,
        path,
        method: 'POST',
        headers
    };
    const requestModule = useHttps ? https : http;
    const json = await new Promise((resolve, reject) => {
        const req = requestModule.request(options, (res) => {