uncloseai.com/languages/lua/uncloseai.lua
Russell Ballestrini 9e3eccb1b9 modified: .gitignore
modified:   CLAUDE.md
	modified:   Makefile
	new file:   languages/awk/Dockerfile
	new file:   languages/awk/uncloseai.awk
	modified:   languages/bash/Dockerfile
	deleted:    languages/bash/examples.sh
	new file:   languages/bash/uncloseai.sh
	new file:   languages/c/curl/Dockerfile
	new file:   languages/c/curl/Makefile
	new file:   languages/c/curl/index.html
	new file:   languages/c/curl/uncloseai.c
	new file:   languages/c/libh2o/Dockerfile
	new file:   languages/c/libh2o/Makefile
	new file:   languages/c/libh2o/uncloseai.c
	new file:   languages/c/nghttp2/Dockerfile
	new file:   languages/c/nghttp2/Makefile
	new file:   languages/c/nghttp2/uncloseai.c
	new file:   languages/clojure/Dockerfile
	new file:   languages/clojure/deps.edn
	new file:   languages/clojure/uncloseai.clj
	new file:   languages/cobol/Dockerfile
	new file:   languages/cobol/discover.sh
	new file:   languages/cobol/hermes.sh
	new file:   languages/cobol/qwen.sh
	new file:   languages/cobol/tts.sh
	new file:   languages/cobol/uncloseai.cob
	new file:   languages/cpp/boost-beast/Dockerfile
	new file:   languages/cpp/boost-beast/Makefile
	new file:   languages/cpp/boost-beast/uncloseai.cpp
	new file:   languages/cpp/cpp-httplib/Dockerfile
	new file:   languages/cpp/cpp-httplib/Makefile
	new file:   languages/cpp/cpp-httplib/uncloseai.cpp
	new file:   languages/cpp/libcurl/Dockerfile
	new file:   languages/cpp/libcurl/Makefile
	new file:   languages/cpp/libcurl/uncloseai.cpp
	new file:   languages/crystal/Dockerfile
	new file:   languages/crystal/uncloseai.cr
	new file:   languages/csharp/Dockerfile
	new file:   languages/csharp/Uncloseai.cs
	new file:   languages/csharp/csharp.csproj
	new file:   languages/dart/Dockerfile
	new file:   languages/dart/bin/uncloseai.dart
	new file:   languages/dart/pubspec.yaml
	new file:   languages/deno/Dockerfile
	new file:   languages/deno/uncloseai.ts
	new file:   languages/elixir/Dockerfile
	new file:   languages/elixir/lib/uncloseai.ex
	new file:   languages/elixir/mix.exs
	new file:   languages/elixir/run.exs
	new file:   languages/erlang/Dockerfile
	new file:   languages/erlang/rebar.config
	new file:   languages/erlang/src/uncloseai.app.src
	new file:   languages/erlang/src/uncloseai.erl
	new file:   languages/fortran/Dockerfile
	new file:   languages/fortran/uncloseai.f90
	new file:   languages/fsharp/Dockerfile
	new file:   languages/fsharp/Uncloseai.fs
	new file:   languages/fsharp/fsharp.fsproj
	new file:   languages/go/Dockerfile
	new file:   languages/go/README.md
	new file:   languages/go/examples/basic.go
	new file:   languages/go/go.mod
	new file:   languages/go/uncloseai.go
	new file:   languages/go/uncloseai/uncloseai.go
	new file:   languages/haskell/Dockerfile
	new file:   languages/haskell/UncloseAI.hs
	new file:   languages/haskell/uncloseai.cabal
	new file:   languages/java/Dockerfile
	new file:   languages/java/UncloseAI.java
	new file:   languages/javascript/bun/Dockerfile
	new file:   languages/javascript/bun/uncloseai.ts
	new file:   languages/javascript/nodejs/Dockerfile
	new file:   languages/javascript/nodejs/README.md
	new file:   languages/javascript/nodejs/package.json
	new file:   languages/javascript/nodejs/uncloseai.js
	new file:   languages/javascript/typescript/Dockerfile
	new file:   languages/javascript/typescript/package.json
	new file:   languages/javascript/typescript/tsconfig.json
	new file:   languages/javascript/typescript/uncloseai.ts
	new file:   languages/javascript/vanilla/Dockerfile
	new file:   languages/javascript/vanilla/uncloseai.html
	new file:   languages/julia/Dockerfile
	new file:   languages/julia/Project.toml
	new file:   languages/julia/src/uncloseai.jl
	new file:   languages/kotlin/Dockerfile
	new file:   languages/kotlin/build.gradle.kts
	new file:   languages/kotlin/src/main/kotlin/UncloseAI.kt
	new file:   languages/kotlin/uncloseai.kt
	new file:   languages/lua/Dockerfile
	new file:   languages/lua/uncloseai.lua
	new file:   languages/nim/Dockerfile
	new file:   languages/nim/uncloseai.nim
	new file:   languages/ocaml/Dockerfile
	new file:   languages/ocaml/dune
	new file:   languages/ocaml/dune-project
	new file:   languages/ocaml/uncloseai.ml
	new file:   languages/odin/Dockerfile
	new file:   languages/odin/uncloseai.odin
	new file:   languages/perl/Dockerfile
	new file:   languages/perl/uncloseai.pl
	new file:   languages/php/Dockerfile
	new file:   languages/php/uncloseai.php
	new file:   languages/powershell/Dockerfile
	new file:   languages/powershell/uncloseai.ps1
	new file:   languages/prolog/Dockerfile
	new file:   languages/prolog/uncloseai.pl
	new file:   languages/python/aiohttp/Dockerfile
	new file:   languages/python/aiohttp/requirements.txt
	new file:   languages/python/aiohttp/uncloseai.py
	modified:   languages/python/httpx-async/Dockerfile
	deleted:    languages/python/httpx-async/examples.py
	new file:   languages/python/httpx-async/uncloseai.py
	modified:   languages/python/openai-client/Dockerfile
	deleted:    languages/python/openai-client/examples.py
	new file:   languages/python/openai-client/uncloseai.py
	modified:   languages/python/requests/Dockerfile
	deleted:    languages/python/requests/examples.py
	new file:   languages/python/requests/uncloseai.py
	new file:   languages/r/Dockerfile
	new file:   languages/r/uncloseai.R
	new file:   languages/ruby/Dockerfile
	new file:   languages/ruby/README.md
	new file:   languages/ruby/uncloseai.rb
	new file:   languages/rust/Cargo.toml
	new file:   languages/rust/Dockerfile
	new file:   languages/rust/README.md
	new file:   languages/rust/examples/basic.rs
	new file:   languages/rust/src/lib.rs
	new file:   languages/rust/src/uncloseai.rs
	new file:   languages/scala/Dockerfile
	new file:   languages/scala/build.sbt
	new file:   languages/scala/project/build.properties
	new file:   languages/scala/project/plugins.sbt
	new file:   languages/scala/src/main/scala/UncloseAI.scala
	new file:   languages/tcl/Dockerfile
	new file:   languages/tcl/uncloseai.tcl
	new file:   languages/v/Dockerfile
	new file:   languages/v/uncloseai.v
	new file:   languages/v/v.mod
	new file:   languages/vbnet/Dockerfile
	new file:   languages/vbnet/UncloseAI.vb
	new file:   languages/vbnet/UncloseAI.vbproj
	modified:   languages/zig/Dockerfile
	modified:   languages/zig/build.zig
	deleted:    languages/zig/src/main.zig
	new file:   languages/zig/src/uncloseai.zig
2025-10-14 10:24:16 -04:00

381 lines
11 KiB
Lua

local http = require("socket.http")
local https = require("ssl.https")
local ltn12 = require("ltn12")
local json = require("cjson")
local socket = require("socket")
-- UncloseAI - Lua client for OpenAI-compatible APIs with streaming support
local UncloseAI = {}
UncloseAI.__index = UncloseAI
function UncloseAI.new(opts)
opts = opts or {}
local self = setmetatable({}, UncloseAI)
self.models = {}
self.tts_endpoints = {}
self.api_key = opts.api_key
self.timeout = opts.timeout or 30
self.debug = opts.debug or false
-- Discover endpoints from environment
local model_endpoints = opts.model_endpoints or self:_discover_env_endpoints("MODEL_ENDPOINT")
local tts_endpoints = opts.tts_endpoints or self:_discover_env_endpoints("TTS_ENDPOINT")
if self.debug then
print(string.format("[DEBUG] Initialized with %d endpoint(s)", #model_endpoints))
end
-- Discover models
self:_discover_models(model_endpoints)
self.tts_endpoints = tts_endpoints
return self
end
function UncloseAI:_discover_env_endpoints(prefix)
local endpoints = {}
for i = 1, 9999 do
local endpoint = os.getenv(prefix .. "_" .. tostring(i))
if not endpoint then break end
table.insert(endpoints, endpoint)
end
return endpoints
end
function UncloseAI:_discover_models(endpoints)
for _, endpoint in ipairs(endpoints) do
if self.debug then
print("[DEBUG] Discovering from: " .. endpoint)
end
local success, err = pcall(function()
local response_body = {}
local res, code = https.request{
url = endpoint .. "/models",
method = "GET",
sink = ltn12.sink.table(response_body)
}
if code == 200 then
local response = json.decode(table.concat(response_body))
if response.data then
for _, model in ipairs(response.data) do
table.insert(self.models, {
id = model.id,
endpoint = endpoint,
max_tokens = model.max_model_len or 8192
})
if self.debug then
print("[DEBUG] Discovered: " .. model.id)
end
end
end
end
end)
if not success and self.debug then
print("[DEBUG] Error: " .. tostring(err))
end
end
end
function UncloseAI:list_models()
local result = {}
for _, model in ipairs(self.models) do
table.insert(result, {
id = model.id,
endpoint = model.endpoint,
max_tokens = model.max_tokens
})
end
return result
end
function UncloseAI:_resolve_model(model)
if #self.models == 0 then
error("No models available")
end
if not model then
return self.models[1]
end
for _, m in ipairs(self.models) do
if m.id == model then
return m
end
end
error("Model '" .. model .. "' not found")
end
function UncloseAI:chat(messages, opts)
opts = opts or {}
local model_info = self:_resolve_model(opts.model)
local max_tokens = opts.max_tokens or 100
local temperature = opts.temperature or 0.7
local payload = {
model = model_info.id,
messages = messages,
max_tokens = max_tokens,
temperature = temperature,
stream = false
}
local request_body = json.encode(payload)
local response_body = {}
local headers = {
["Content-Type"] = "application/json",
["Content-Length"] = tostring(#request_body)
}
if self.api_key then
headers["Authorization"] = "Bearer " .. self.api_key
end
local res, code = https.request{
url = model_info.endpoint .. "/chat/completions",
method = "POST",
headers = headers,
source = ltn12.source.string(request_body),
sink = ltn12.sink.table(response_body)
}
if code == 200 then
return json.decode(table.concat(response_body))
else
error("Request failed with code: " .. tostring(code))
end
end
function UncloseAI:chat_stream(messages, opts, callback)
opts = opts or {}
local model_info = self:_resolve_model(opts.model)
local max_tokens = opts.max_tokens or 500
local temperature = opts.temperature or 0.7
local payload = {
model = model_info.id,
messages = messages,
max_tokens = max_tokens,
temperature = temperature,
stream = true
}
local request_body = json.encode(payload)
-- Parse URL
local protocol, host, port, path = model_info.endpoint:match("^(https?)://([^:/]+):?(%d*)(.*)$")
port = port and tonumber(port) or (protocol == "https" and 443 or 80)
path = (path == "" and "/v1" or path) .. "/chat/completions"
-- Create socket connection
local sock = socket.tcp()
sock:settimeout(self.timeout)
local success, err = pcall(function()
assert(sock:connect(host, port))
-- For HTTPS, wrap socket with SSL
if protocol == "https" then
local ssl = require("ssl")
sock = assert(ssl.wrap(sock, {mode = "client", protocol = "tlsv1_2"}))
assert(sock:dohandshake())
end
-- Send HTTP request
local headers = {
"POST " .. path .. " HTTP/1.1",
"Host: " .. host,
"Content-Type: application/json",
"Content-Length: " .. tostring(#request_body),
"Connection: close"
}
if self.api_key then
table.insert(headers, "Authorization: Bearer " .. self.api_key)
end
local request = table.concat(headers, "\r\n") .. "\r\n\r\n" .. request_body
assert(sock:send(request))
-- Read response headers
local line = sock:receive("*l")
while line and line ~= "" do
line = sock:receive("*l")
end
-- Read streaming response
local buffer = ""
while true do
local chunk, err = sock:receive(1024)
if not chunk then break end
buffer = buffer .. chunk
local lines = {}
for line in buffer:gmatch("([^\n]*)\n") do
table.insert(lines, line)
end
-- Keep incomplete line in buffer
local last_newline = buffer:find("\n[^\n]*$")
if last_newline then
buffer = buffer:sub(last_newline + 1)
end
-- Process complete lines
for i = 1, #lines - 1 do
local line = lines[i]:gsub("\r", "")
if line:match("^data: ") then
local data = line:sub(7)
if data == "[DONE]" then
return
end
local success, chunk_data = pcall(json.decode, data)
if success and chunk_data.choices and chunk_data.choices[1] then
local delta = chunk_data.choices[1].delta
if delta and delta.content then
callback(delta.content)
end
end
end
end
end
end)
sock:close()
if not success and self.debug then
print("[DEBUG] Stream error: " .. tostring(err))
end
end
function UncloseAI:tts(text, opts)
opts = opts or {}
if #self.tts_endpoints == 0 then
error("No TTS endpoints available")
end
local endpoint = self.tts_endpoints[1]
local voice = opts.voice or "alloy"
local model = opts.model or "tts-1"
local response_format = opts.response_format or "mp3"
local payload = {
model = model,
voice = voice,
input = text,
response_format = response_format
}
local request_body = json.encode(payload)
local response_body = {}
local headers = {
["Content-Type"] = "application/json",
["Content-Length"] = tostring(#request_body)
}
if self.api_key then
headers["Authorization"] = "Bearer " .. self.api_key
end
local res, code = https.request{
url = endpoint .. "/audio/speech",
method = "POST",
headers = headers,
source = ltn12.source.string(request_body),
sink = ltn12.sink.table(response_body)
}
if code == 200 then
return table.concat(response_body)
else
error("Request failed with code: " .. tostring(code))
end
end
-- Demo when run as script
if not pcall(debug.getlocal, 4, 1) then
print("=== UncloseAI Lua Client (with Streaming) ===\n")
local client = UncloseAI.new({debug = true})
local models = client:list_models()
if #models == 0 then
print("ERROR: No models discovered. Set environment variables:")
print(" MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.")
os.exit(1)
end
print(string.format("\nDiscovered %d model(s):", #models))
for _, model in ipairs(models) do
print(string.format(" - %s (max_tokens: %d)", model.id, model.max_tokens))
end
print()
-- Non-streaming chat
print("=== Non-Streaming Chat ===")
local success, response = pcall(function()
return client:chat({
{role = "system", content = "You are a helpful AI assistant."},
{role = "user", content = "Explain quantum computing in one sentence."}
}, {max_tokens = 100})
end)
if success then
local content = response.choices[1].message.content
print("Response: " .. content .. "\n")
else
print("Error: " .. tostring(response) .. "\n")
end
-- Streaming chat
print("=== Streaming Chat ===")
local model_id = #models > 1 and models[2].id or nil
print("Model: " .. (model_id or models[1].id))
io.write("Response: ")
io.flush()
local success, err = pcall(function()
client:chat_stream({
{role = "system", content = "You are a coding assistant."},
{role = "user", content = "Write a Lua function to check if a number is prime"}
}, {model = model_id, max_tokens = 200}, function(content)
io.write(content)
io.flush()
end)
end)
if not success then
print("\nError: " .. tostring(err))
end
print("\n")
-- TTS
if #client.tts_endpoints > 0 then
print("=== TTS Speech Generation ===")
local success, audio_data = pcall(function()
return client:tts("Hello from UncloseAI Lua client! This demonstrates streaming support.")
end)
if success then
local file = io.open("speech.mp3", "wb")
file:write(audio_data)
file:close()
print(string.format("✓ Speech file created: speech.mp3 (%d bytes)\n", #audio_data))
else
print("✗ TTS Error: " .. tostring(audio_data) .. "\n")
end
end
print("=== Examples Complete ===")
end
return UncloseAI