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
366 lines
14 KiB
Java
366 lines
14 KiB
Java
import java.io.*;
|
|
import java.net.*;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.*;
|
|
import java.util.*;
|
|
import java.util.function.Consumer;
|
|
|
|
class ModelInfo {
|
|
String id;
|
|
String endpoint;
|
|
int maxTokens;
|
|
|
|
ModelInfo(String id, String endpoint, int maxTokens) {
|
|
this.id = id;
|
|
this.endpoint = endpoint;
|
|
this.maxTokens = maxTokens;
|
|
}
|
|
}
|
|
|
|
public class UncloseAI {
|
|
private List<ModelInfo> models = new ArrayList<>();
|
|
private List<String> ttsEndpoints = new ArrayList<>();
|
|
private String apiKey;
|
|
private int timeout = 30000;
|
|
private boolean debug = false;
|
|
|
|
public UncloseAI() {
|
|
this(null, null, null, 30000, false);
|
|
}
|
|
|
|
public UncloseAI(List<String> endpoints, List<String> ttsEndpoints, String apiKey, int timeout, boolean debug) {
|
|
this.apiKey = apiKey;
|
|
this.timeout = timeout;
|
|
this.debug = debug;
|
|
|
|
if (endpoints == null) {
|
|
endpoints = discoverEndpointsFromEnv("MODEL_ENDPOINT");
|
|
}
|
|
if (ttsEndpoints == null) {
|
|
ttsEndpoints = discoverEndpointsFromEnv("TTS_ENDPOINT");
|
|
}
|
|
|
|
if (debug) {
|
|
System.out.println("[DEBUG] Initialized with " + endpoints.size() + " endpoint(s)");
|
|
}
|
|
|
|
discoverModels(endpoints);
|
|
this.ttsEndpoints = ttsEndpoints;
|
|
}
|
|
|
|
public List<ModelInfo> listModels() {
|
|
return new ArrayList<>(models);
|
|
}
|
|
|
|
public String chat(List<Map<String, String>> messages, String model, int maxTokens) throws IOException {
|
|
ModelInfo modelInfo = resolveModel(model);
|
|
String jsonRequest = buildChatRequest(modelInfo.id, messages, maxTokens, false);
|
|
String response = postJSON(modelInfo.endpoint + "/chat/completions", jsonRequest);
|
|
return extractContent(response);
|
|
}
|
|
|
|
public void chatStream(List<Map<String, String>> messages, String model, int maxTokens, Consumer<String> callback) throws IOException {
|
|
ModelInfo modelInfo = resolveModel(model);
|
|
String jsonRequest = buildChatRequest(modelInfo.id, messages, maxTokens, true);
|
|
|
|
URL url = new URL(modelInfo.endpoint + "/chat/completions");
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
conn.setRequestMethod("POST");
|
|
conn.setRequestProperty("Content-Type", "application/json");
|
|
conn.setDoOutput(true);
|
|
conn.setConnectTimeout(timeout);
|
|
conn.setReadTimeout(timeout);
|
|
|
|
try (OutputStream os = conn.getOutputStream()) {
|
|
os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
|
|
try (BufferedReader br = new BufferedReader(
|
|
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
|
String line;
|
|
while ((line = br.readLine()) != null) {
|
|
if (line.startsWith("data: ")) {
|
|
String data = line.substring(6).trim();
|
|
if ("[DONE]".equals(data)) {
|
|
break;
|
|
}
|
|
String content = extractStreamContent(data);
|
|
if (content != null && !content.isEmpty()) {
|
|
callback.accept(content);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public byte[] tts(String text, String voice, String model) throws IOException {
|
|
if (ttsEndpoints.isEmpty()) {
|
|
throw new IOException("No TTS endpoints available");
|
|
}
|
|
|
|
String jsonRequest = String.format(
|
|
"{\"model\":\"%s\",\"voice\":\"%s\",\"input\":\"%s\"}",
|
|
model, voice, text.replace("\"", "\\\"")
|
|
);
|
|
|
|
return postJSONBinary(ttsEndpoints.get(0) + "/audio/speech", jsonRequest);
|
|
}
|
|
|
|
private List<String> discoverEndpointsFromEnv(String prefix) {
|
|
List<String> endpoints = new ArrayList<>();
|
|
for (int i = 1; i < 10000; i++) {
|
|
String endpoint = System.getenv(prefix + "_" + i);
|
|
if (endpoint == null || endpoint.isEmpty()) {
|
|
break;
|
|
}
|
|
endpoints.add(endpoint);
|
|
}
|
|
return endpoints;
|
|
}
|
|
|
|
private void discoverModels(List<String> endpoints) {
|
|
for (String endpoint : endpoints) {
|
|
if (debug) {
|
|
System.out.println("[DEBUG] Discovering from: " + endpoint);
|
|
}
|
|
|
|
try {
|
|
String response = getJSON(endpoint + "/models");
|
|
parseModels(response, endpoint);
|
|
} catch (Exception e) {
|
|
if (debug) {
|
|
System.out.println("[DEBUG] Error: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void parseModels(String jsonResponse, String endpoint) {
|
|
int dataIndex = jsonResponse.indexOf("\"data\":[");
|
|
if (dataIndex == -1) return;
|
|
|
|
String dataSection = jsonResponse.substring(dataIndex + 8);
|
|
int pos = 0;
|
|
while (pos < dataSection.length()) {
|
|
int idIndex = dataSection.indexOf("\"id\":\"", pos);
|
|
if (idIndex == -1) break;
|
|
|
|
int idStart = idIndex + 6;
|
|
int idEnd = dataSection.indexOf("\"", idStart);
|
|
String modelId = dataSection.substring(idStart, idEnd);
|
|
|
|
if (modelId.startsWith("modelperm-")) {
|
|
pos = idEnd + 1;
|
|
continue;
|
|
}
|
|
|
|
int maxTokens = 8192;
|
|
int maxLenIndex = dataSection.indexOf("\"max_model_len\":", idEnd);
|
|
if (maxLenIndex != -1 && maxLenIndex < dataSection.indexOf("}", idEnd)) {
|
|
int maxLenStart = maxLenIndex + 16;
|
|
int maxLenEnd = dataSection.indexOf(",", maxLenStart);
|
|
if (maxLenEnd == -1) maxLenEnd = dataSection.indexOf("}", maxLenStart);
|
|
if (maxLenEnd != -1) {
|
|
try {
|
|
maxTokens = Integer.parseInt(dataSection.substring(maxLenStart, maxLenEnd).trim());
|
|
} catch (NumberFormatException ignored) {}
|
|
}
|
|
}
|
|
|
|
models.add(new ModelInfo(modelId, endpoint, maxTokens));
|
|
if (debug) {
|
|
System.out.println("[DEBUG] Discovered: " + modelId);
|
|
}
|
|
|
|
pos = idEnd + 1;
|
|
}
|
|
}
|
|
|
|
private ModelInfo resolveModel(String model) throws IOException {
|
|
if (models.isEmpty()) {
|
|
throw new IOException("No models available");
|
|
}
|
|
if (model == null || model.isEmpty()) {
|
|
return models.get(0);
|
|
}
|
|
for (ModelInfo m : models) {
|
|
if (m.id.equals(model)) {
|
|
return m;
|
|
}
|
|
}
|
|
throw new IOException("Model '" + model + "' not found");
|
|
}
|
|
|
|
private String buildChatRequest(String modelId, List<Map<String, String>> messages, int maxTokens, boolean stream) {
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append("{\"model\":\"").append(modelId).append("\",");
|
|
sb.append("\"messages\":[");
|
|
for (int i = 0; i < messages.size(); i++) {
|
|
if (i > 0) sb.append(",");
|
|
Map<String, String> msg = messages.get(i);
|
|
sb.append("{\"role\":\"").append(msg.get("role")).append("\",");
|
|
sb.append("\"content\":\"").append(msg.get("content").replace("\"", "\\\"")).append("\"}");
|
|
}
|
|
sb.append("],\"max_tokens\":").append(maxTokens);
|
|
if (stream) {
|
|
sb.append(",\"stream\":true");
|
|
}
|
|
sb.append("}");
|
|
return sb.toString();
|
|
}
|
|
|
|
private String getJSON(String urlString) throws IOException {
|
|
URL url = new URL(urlString);
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
conn.setRequestMethod("GET");
|
|
conn.setConnectTimeout(10000);
|
|
conn.setReadTimeout(10000);
|
|
|
|
try (BufferedReader br = new BufferedReader(
|
|
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
|
StringBuilder response = new StringBuilder();
|
|
String line;
|
|
while ((line = br.readLine()) != null) {
|
|
response.append(line.trim());
|
|
}
|
|
return response.toString();
|
|
}
|
|
}
|
|
|
|
private String postJSON(String urlString, String jsonRequest) throws IOException {
|
|
URL url = new URL(urlString);
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
conn.setRequestMethod("POST");
|
|
conn.setRequestProperty("Content-Type", "application/json");
|
|
conn.setDoOutput(true);
|
|
conn.setConnectTimeout(timeout);
|
|
conn.setReadTimeout(timeout);
|
|
|
|
try (OutputStream os = conn.getOutputStream()) {
|
|
os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
|
|
try (BufferedReader br = new BufferedReader(
|
|
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
|
StringBuilder response = new StringBuilder();
|
|
String line;
|
|
while ((line = br.readLine()) != null) {
|
|
response.append(line.trim());
|
|
}
|
|
return response.toString();
|
|
}
|
|
}
|
|
|
|
private byte[] postJSONBinary(String urlString, String jsonRequest) throws IOException {
|
|
URL url = new URL(urlString);
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
conn.setRequestMethod("POST");
|
|
conn.setRequestProperty("Content-Type", "application/json");
|
|
conn.setDoOutput(true);
|
|
conn.setConnectTimeout(timeout);
|
|
conn.setReadTimeout(timeout);
|
|
|
|
try (OutputStream os = conn.getOutputStream()) {
|
|
os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
|
|
}
|
|
|
|
try (InputStream is = conn.getInputStream()) {
|
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
|
byte[] data = new byte[1024];
|
|
int nRead;
|
|
while ((nRead = is.read(data, 0, data.length)) != -1) {
|
|
buffer.write(data, 0, nRead);
|
|
}
|
|
return buffer.toByteArray();
|
|
}
|
|
}
|
|
|
|
private String extractContent(String jsonResponse) {
|
|
int contentIndex = jsonResponse.indexOf("\"content\":\"");
|
|
if (contentIndex == -1) return jsonResponse;
|
|
|
|
int startIndex = contentIndex + 11;
|
|
int endIndex = jsonResponse.indexOf("\"", startIndex);
|
|
while (endIndex > 0 && jsonResponse.charAt(endIndex - 1) == '\\') {
|
|
endIndex = jsonResponse.indexOf("\"", endIndex + 1);
|
|
}
|
|
if (endIndex == -1) return jsonResponse.substring(startIndex);
|
|
|
|
String content = jsonResponse.substring(startIndex, endIndex);
|
|
return content.replace("\\n", "\n").replace("\\\"", "\"").replace("\\\\", "\\");
|
|
}
|
|
|
|
private String extractStreamContent(String jsonChunk) {
|
|
int contentIndex = jsonChunk.indexOf("\"content\":\"");
|
|
if (contentIndex == -1) return null;
|
|
|
|
int startIndex = contentIndex + 11;
|
|
int endIndex = jsonChunk.indexOf("\"", startIndex);
|
|
if (endIndex == -1) return null;
|
|
|
|
return jsonChunk.substring(startIndex, endIndex)
|
|
.replace("\\n", "\n").replace("\\\"", "\"").replace("\\\\", "\\");
|
|
}
|
|
|
|
// Demo when run as application
|
|
public static void main(String[] args) {
|
|
System.out.println("=== UncloseAI Java Client (with Streaming) ===\n");
|
|
|
|
UncloseAI client = new UncloseAI(null, null, null, 30000, true);
|
|
|
|
if (client.models.isEmpty()) {
|
|
System.out.println("ERROR: No models discovered. Set environment variables:");
|
|
System.out.println(" MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.");
|
|
System.exit(1);
|
|
}
|
|
|
|
System.out.println("\nDiscovered " + client.models.size() + " model(s):");
|
|
for (ModelInfo m : client.models) {
|
|
System.out.println(" - " + m.id + " (max_tokens: " + m.maxTokens + ")");
|
|
}
|
|
System.out.println();
|
|
|
|
// Non-streaming chat
|
|
System.out.println("=== Non-Streaming Chat ===");
|
|
try {
|
|
List<Map<String, String>> messages = Arrays.asList(
|
|
new HashMap<String, String>() {{ put("role", "system"); put("content", "You are a helpful AI assistant."); }},
|
|
new HashMap<String, String>() {{ put("role", "user"); put("content", "Explain quantum computing in one sentence."); }}
|
|
);
|
|
String response = client.chat(messages, null, 100);
|
|
System.out.println("Response: " + response + "\n");
|
|
} catch (IOException e) {
|
|
System.out.println("Error: " + e.getMessage() + "\n");
|
|
}
|
|
|
|
// Streaming chat
|
|
System.out.println("=== Streaming Chat ===");
|
|
String modelId = client.models.size() > 1 ? client.models.get(1).id : null;
|
|
System.out.println("Model: " + (modelId != null ? modelId : client.models.get(0).id));
|
|
System.out.print("Response: ");
|
|
try {
|
|
List<Map<String, String>> messages = Arrays.asList(
|
|
new HashMap<String, String>() {{ put("role", "system"); put("content", "You are a coding assistant."); }},
|
|
new HashMap<String, String>() {{ put("role", "user"); put("content", "Write a Java function to check if a number is prime"); }}
|
|
);
|
|
client.chatStream(messages, modelId, 200, content -> System.out.print(content));
|
|
System.out.println("\n");
|
|
} catch (IOException e) {
|
|
System.out.println("\nError: " + e.getMessage() + "\n");
|
|
}
|
|
|
|
// TTS
|
|
if (!client.ttsEndpoints.isEmpty()) {
|
|
System.out.println("=== TTS Speech Generation ===");
|
|
try {
|
|
byte[] audio = client.tts("Hello from UncloseAI Java client! This demonstrates streaming support.", "alloy", "tts-1");
|
|
Files.write(Paths.get("speech.mp3"), audio);
|
|
System.out.println("✓ Speech file created: speech.mp3 (" + audio.length + " bytes)\n");
|
|
} catch (IOException e) {
|
|
System.out.println("✗ TTS Error: " + e.getMessage() + "\n");
|
|
}
|
|
}
|
|
|
|
System.out.println("=== Examples Complete ===");
|
|
}
|
|
}
|