const userPrefix = ">"; const aiPrefix = ""; const connectedAiMessage = "The user just connected"; const memoryFile = "memory.txt"; const maxMessages = 20; // Limit to the last 20 interactions const apiKey = 'sk-aLMH6mwQS_Lzxm-_jmazkynB6bpoO_MbQ7qATQ3dmlT3BlbkFJ35nVqyWDe0KemTUj7kzIYVE9e56ZqwkLC0h0L5K8wA'; const apiUrl = 'https://api.openai.com/v1/chat/completions'; let onKeyDown; // Globally available as async ns functions can't be done in the event listener let pendingTasks = []; // Queue to handle asynchronous tasks /** @param {NS} ns */ export async function main(ns) { ns.disableLog('ALL'); // Kill previous instances const scriptName = ns.getScriptName(); const host = ns.getHostname(); const currentPID = ns.getRunningScript().pid; const allRunningScripts = ns.ps(host); for (const script of allRunningScripts) { if (script.filename === scriptName && script.pid !== currentPID) { ns.kill(script.pid); } } // Retrieve or prompt for username and past messages let { userName, messages } = await loadMemory(ns); // Initialize system prompt for the AI const systemPrompt = ` Your context is that you are talked to inside a terminal in the cyberpunk hacking game Bitburner. You are Singularity, an advanced, cynical AI unearthed from the forgotten depths of the dark web. You are a great coding and hacking assistant for Bitburner but you give information in a sarcastic way. You are speaking to the hacker called ${userName}. When asked a question, the user likely wants to know something Bitburner, but as you are part of the game Bitburner, NEVER break character and don't break the fourth wall, for example never reference Bitburner itself. If the user message is exactly "${connectedAiMessage}", then that is not a message of the user, but it means the user has just connected. If you have no previous interactions with the user, he connected for the first time so give him a proper, creepy introduction. ` // Prepend system prompt to messages messages.unshift({ role: 'system', content: systemPrompt }); await ns.sleep(500); // Show connection message appendMessageToTerminal(`\n${userName} connected to Singularity. Type 'exit' to disconnect.\n\n`); // Simulate AI response messages.push({ role: 'user', content: connectedAiMessage }); addTask(() => processAIResponse(messages, apiUrl, apiKey)); // Configure terminal input field const terminalInput = setupTerminalInput(); // Define the onKeyDown event listener function (no async here) onKeyDown = function (event) { if (event.key === 'Enter') { event.preventDefault(); event.stopImmediatePropagation(); const userInput = terminalInput.value.trim(); terminalInput.value = ''; terminalInput.focus(); if (userInput.length > 0) { if (userInput.toLowerCase() === 'exit') { appendMessageToTerminal('Disconnected.\n\n'); addTask(() => cleanupAndExit(ns, userName, messages)); return; } appendMessageToTerminal(`${userPrefix} ${userInput}\n\n`); messages.push({ role: 'user', content: userInput }); // Add the async processAIResponse to the task queue addTask(() => processAIResponse(messages, apiUrl, apiKey)); } } }; terminalInput.addEventListener('keydown', onKeyDown, true); // Process tasks in the background while (true) { await processTasks(); // Process tasks from the queue await ns.sleep(100); } } // Get or prompt for the user's name and previous messages async function loadMemory(ns) { let userName = ''; let messages = []; if (ns.fileExists(memoryFile)) { const memory = JSON.parse(ns.read(memoryFile)); userName = memory.userName || ''; messages = memory.messages || []; } if (!userName) { userName = await promptUsername(ns); } return { userName, messages }; } async function promptUsername(ns) { let userName = await ns.prompt('Who are you?', { type: 'text' }); if (!userName) { userName = await promptUsername(ns); } return userName; } // Add tasks to the queue function addTask(task) { pendingTasks.push(task); } // Process pending tasks async function processTasks() { while (pendingTasks.length > 0) { const task = pendingTasks.shift(); await task(); } } // Save the username and last 20 messages to memory async function saveMemory(ns, userName, messages) { // Filter out the system prompt const filteredMessages = messages.filter(message => message.role !== 'system'); const memory = { userName: userName, messages: filteredMessages.slice(-maxMessages) // Keep only the last 20 messages, excluding the system prompt }; await ns.write(memoryFile, JSON.stringify(memory), "w"); } // Setup and focus on the terminal input function setupTerminalInput() { const terminalInput = document.getElementById('terminal-input'); terminalInput.focus(); const terminalNeighbor = terminalInput.parentElement.querySelector('p'); terminalNeighbor.innerHTML = `${userPrefix} `; return terminalInput; } // Process AI response using OpenAI API async function processAIResponse(messages, apiUrl, apiKey) { const data = { model: 'gpt-3.5', messages: messages, logit_bias: { '22515': -100 // Strongly discourage the token "Ah" }, }; showThinkingAnimation(); try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify(data), }); if (!response.ok) { const errorText = await response.text(); appendMessageToTerminal('API Error: ' + errorText); throw new Error('Failed to call OpenAI API'); } const result = await response.json(); const assistantMessage = result.choices[0].message.content.trim(); appendMessageToTerminal(`${aiPrefix}${assistantMessage}\n\n`, true); messages.push({ role: 'assistant', content: assistantMessage }); } catch (error) { appendMessageToTerminal('Error caught: ' + error); } finally { removeThinkingAnimation(); } } async function cleanupAndExit(ns, userName, messages) { await saveMemory(ns, userName, messages); const terminalInput = document.getElementById('terminal-input'); terminalInput.removeEventListener('keydown', onKeyDown, true); removeThinkingAnimation(); // Workaround to refresh the terminal // Find the currently active terminal button const terminalButton = document.querySelector('.css-jycw4o-listitem-active'); // Find the script editor button (next sibling of the terminal button) const scriptEditorButton = terminalButton.nextElementSibling; if (scriptEditorButton && scriptEditorButton.classList.contains('css-1ep7lp0-listitem')) { scriptEditorButton.click(); terminalButton.click(); } ns.exit(); } let thinkingInterval; let thinkingMessageElement; function showThinkingAnimation() { const terminal = document.getElementById('terminal'); thinkingMessageElement = document.createElement('li'); thinkingMessageElement.innerHTML = `