Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- ============================================================
- -- ChatGPT for CC:Tweaked
- -- GPT-5.6 Luna + Flex
- -- ============================================================
- -- =========================
- -- CONFIG
- -- =========================
- local API_KEY = "PUT_YOUR_OPENAI_API_KEY_HERE"
- local MODEL = "gpt-5.6-luna"
- local SERVICE_TIER = "flex"
- local REASONING_EFFORT = "low"
- local MAX_OUTPUT_TOKENS = 4096
- -- CC:Tweaked HTTP timeout.
- -- Your version accepts a maximum of 60 seconds.
- local HTTP_TIMEOUT = 60
- -- How often to check whether the background response is finished.
- local POLL_INTERVAL = 2
- -- Overall time we will allow OpenAI to work on one response.
- -- This is NOT an HTTP timeout.
- local MAX_BACKGROUND_WAIT = 900
- -- Retry initial submission if Flex temporarily has no capacity.
- local MAX_SUBMIT_RETRIES = 4
- local API_URL = "https://api.openai.com/v1/responses"
- local SYSTEM_PROMPT = [[
- You are ChatGPT running inside a Minecraft CC:Tweaked computer.
- Answer clearly and concisely.
- The user is interacting through a small text terminal.
- Prefer plain text over Markdown.
- Avoid tables unless genuinely useful.
- Keep formatting suitable for a narrow ComputerCraft terminal.
- ]]
- -- =========================
- -- STATE
- -- =========================
- local previousResponseId = nil
- -- =========================
- -- COLOURS
- -- =========================
- local function colour(c)
- if term.isColor() then
- term.setTextColor(c)
- end
- end
- local function resetColour()
- if term.isColor() then
- term.setTextColor(colors.white)
- end
- end
- -- =========================
- -- UI
- -- =========================
- local function showCommands()
- colour(colors.gray)
- print("Commands:")
- print(" /clear - start a new conversation")
- print(" /help - show commands")
- print(" /exit - quit")
- print("")
- resetColour()
- end
- local function clearScreen()
- term.clear()
- term.setCursorPos(1, 1)
- showCommands()
- end
- -- =========================
- -- JSON HELPERS
- -- =========================
- local function decodeJSON(body)
- if not body or body == "" then
- return nil
- end
- local ok, data = pcall(
- textutils.unserializeJSON,
- body
- )
- if ok then
- return data
- end
- return nil
- end
- local function extractError(body)
- if not body or body == "" then
- return nil
- end
- local data = decodeJSON(body)
- if data and data.error then
- if type(data.error) == "table" then
- return data.error.message
- or data.error.code
- or "Unknown OpenAI error"
- end
- return tostring(data.error)
- end
- return body
- end
- local function extractOutputText(data)
- if not data then
- return nil
- end
- -- Some API representations may expose this directly.
- if data.output_text
- and type(data.output_text) == "string"
- and data.output_text ~= "" then
- return data.output_text
- end
- local outputParts = {}
- if data.output then
- for _, item in ipairs(data.output) do
- if item.type == "message"
- and item.content then
- for _, content in ipairs(item.content) do
- if content.type == "output_text"
- and content.text then
- table.insert(
- outputParts,
- content.text
- )
- elseif content.type == "refusal"
- and content.refusal then
- table.insert(
- outputParts,
- content.refusal
- )
- end
- end
- end
- end
- end
- if #outputParts > 0 then
- return table.concat(
- outputParts,
- "\n"
- )
- end
- return nil
- end
- -- =========================
- -- HTTP HELPERS
- -- =========================
- local function getErrorResponse(errorResponse)
- if not errorResponse then
- return nil, nil
- end
- local code = nil
- local body = nil
- pcall(function()
- code = errorResponse.getResponseCode()
- end)
- pcall(function()
- body = errorResponse.readAll()
- end)
- pcall(function()
- errorResponse.close()
- end)
- return code, body
- end
- local function authHeaders()
- return {
- ["Authorization"] = "Bearer " .. API_KEY,
- ["Content-Type"] = "application/json"
- }
- end
- -- =========================
- -- CREATE BACKGROUND RESPONSE
- -- =========================
- local function createResponse(userMessage)
- local payload = {
- model = MODEL,
- input = userMessage,
- instructions = SYSTEM_PROMPT,
- service_tier = SERVICE_TIER,
- reasoning = {
- effort = REASONING_EFFORT
- },
- max_output_tokens = MAX_OUTPUT_TOKENS,
- -- Critical for CC:Tweaked:
- -- OpenAI returns a response ID without requiring us
- -- to hold this HTTP connection open until generation finishes.
- background = true,
- -- Required so we can retrieve it later and continue
- -- conversations with previous_response_id.
- store = true
- }
- if previousResponseId then
- payload.previous_response_id =
- previousResponseId
- end
- local body = textutils.serializeJSON(payload)
- for attempt = 1, MAX_SUBMIT_RETRIES + 1 do
- local ok,
- response,
- errorMessage,
- errorResponse = pcall(
- http.post,
- {
- url = API_URL,
- body = body,
- headers = authHeaders(),
- timeout = HTTP_TIMEOUT
- }
- )
- -- CC itself threw an error.
- if not ok then
- return nil,
- "HTTP error: "
- .. tostring(response)
- end
- -- Successful HTTP response.
- if response then
- local responseBody =
- response.readAll()
- local responseCode =
- response.getResponseCode()
- response.close()
- local data =
- decodeJSON(responseBody)
- if responseCode >= 200
- and responseCode < 300 then
- if not data then
- return nil,
- "OpenAI returned invalid JSON."
- end
- if not data.id then
- return nil,
- "OpenAI did not return a response ID."
- end
- return data
- end
- return nil,
- "HTTP "
- .. tostring(responseCode)
- .. ": "
- .. tostring(
- extractError(responseBody)
- or "Unknown error"
- )
- end
- -- Failed HTTP request.
- local statusCode,
- errorBody =
- getErrorResponse(errorResponse)
- local retryable =
- statusCode == 408
- or statusCode == 429
- or statusCode == 500
- or statusCode == 502
- or statusCode == 503
- or statusCode == 504
- if retryable
- and attempt <= MAX_SUBMIT_RETRIES then
- local delay =
- math.min(
- 2 ^ (attempt - 1),
- 16
- )
- sleep(delay)
- else
- local message =
- extractError(errorBody)
- or errorMessage
- or "Unknown HTTP error"
- if statusCode then
- return nil,
- "HTTP "
- .. tostring(statusCode)
- .. ": "
- .. tostring(message)
- end
- return nil,
- tostring(message)
- end
- end
- return nil,
- "Could not submit request."
- end
- -- =========================
- -- RETRIEVE RESPONSE
- -- =========================
- local function retrieveResponse(responseId)
- local url =
- API_URL
- .. "/"
- .. responseId
- local ok,
- response,
- errorMessage,
- errorResponse = pcall(
- http.get,
- {
- url = url,
- headers = authHeaders(),
- timeout = HTTP_TIMEOUT
- }
- )
- if not ok then
- return nil,
- "HTTP error: "
- .. tostring(response)
- end
- if response then
- local body =
- response.readAll()
- local code =
- response.getResponseCode()
- response.close()
- if code >= 200
- and code < 300 then
- local data =
- decodeJSON(body)
- if not data then
- return nil,
- "OpenAI returned invalid JSON."
- end
- return data
- end
- return nil,
- "HTTP "
- .. tostring(code)
- .. ": "
- .. tostring(
- extractError(body)
- or "Unknown error"
- )
- end
- local statusCode,
- errorBody =
- getErrorResponse(errorResponse)
- local message =
- extractError(errorBody)
- or errorMessage
- or "Unknown HTTP error"
- if statusCode then
- return nil,
- "HTTP "
- .. tostring(statusCode)
- .. ": "
- .. tostring(message)
- end
- return nil,
- tostring(message)
- end
- -- =========================
- -- WAIT FOR BACKGROUND RESPONSE
- -- =========================
- local function waitForResponse(initialData)
- local responseId =
- initialData.id
- -- It may occasionally finish immediately.
- if initialData.status == "completed" then
- return initialData
- end
- local started =
- os.clock()
- local consecutiveErrors = 0
- while true do
- if os.clock() - started
- >= MAX_BACKGROUND_WAIT then
- return nil,
- "Response timed out after "
- .. tostring(MAX_BACKGROUND_WAIT)
- .. " seconds."
- end
- sleep(POLL_INTERVAL)
- local data, err =
- retrieveResponse(responseId)
- if not data then
- consecutiveErrors =
- consecutiveErrors + 1
- -- Allow temporary polling/network failures.
- if consecutiveErrors >= 5 then
- return nil, err
- end
- else
- consecutiveErrors = 0
- local status =
- data.status
- if status == "completed" then
- return data
- elseif status == "failed" then
- local message = nil
- if data.error then
- if type(data.error)
- == "table" then
- message =
- data.error.message
- or data.error.code
- else
- message =
- tostring(data.error)
- end
- end
- return nil,
- message
- or "OpenAI response failed."
- elseif status == "cancelled" then
- return nil,
- "OpenAI response was cancelled."
- elseif status == "incomplete" then
- local reason = nil
- if data.incomplete_details then
- reason =
- data.incomplete_details.reason
- end
- return nil,
- "Response incomplete"
- .. (
- reason
- and ": " .. tostring(reason)
- or "."
- )
- elseif status == "queued"
- or status == "in_progress" then
- -- Still running. Keep polling.
- else
- return nil,
- "Unknown response status: "
- .. tostring(status)
- end
- end
- end
- end
- -- =========================
- -- CHAT
- -- =========================
- local function ask(userMessage)
- local initialData, createError =
- createResponse(userMessage)
- if not initialData then
- return nil, createError
- end
- local finalData, waitError =
- waitForResponse(initialData)
- if not finalData then
- return nil, waitError
- end
- local answer =
- extractOutputText(finalData)
- if not answer then
- return nil,
- "OpenAI returned no text."
- end
- -- Only advance conversation state once the response
- -- has successfully completed.
- previousResponseId =
- finalData.id
- return answer
- end
- -- =========================
- -- STARTUP
- -- =========================
- if API_KEY == ""
- or API_KEY
- == "PUT_YOUR_OPENAI_API_KEY_HERE" then
- colour(colors.red)
- print("Set API_KEY at the top of the script.")
- resetColour()
- return
- end
- if not http then
- colour(colors.red)
- print("HTTP is disabled.")
- resetColour()
- return
- end
- local allowed, reason =
- http.checkURL(API_URL)
- if not allowed then
- colour(colors.red)
- print("OpenAI API is blocked.")
- print(tostring(reason))
- resetColour()
- return
- end
- clearScreen()
- -- =========================
- -- MAIN LOOP
- -- =========================
- while true do
- colour(colors.lime)
- write("You > ")
- resetColour()
- local input =
- read()
- if input == "/exit" then
- break
- elseif input == "/clear" then
- previousResponseId = nil
- print("")
- colour(colors.yellow)
- print("Conversation cleared.")
- resetColour()
- print("")
- elseif input == "/help" then
- print("")
- showCommands()
- elseif input ~= "" then
- print("")
- local _, thinkingY =
- term.getCursorPos()
- colour(colors.gray)
- write("Thinking...")
- resetColour()
- local answer, err =
- ask(input)
- -- Remove Thinking... from the terminal.
- term.setCursorPos(
- 1,
- thinkingY
- )
- term.clearLine()
- if answer then
- colour(colors.cyan)
- write("GPT > ")
- resetColour()
- print(answer)
- print("")
- else
- colour(colors.red)
- print("Error: " .. tostring(err))
- resetColour()
- print("")
- end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment