2026-04-21 17:24:11 +02:00
|
|
|
package ai.nomyo;
|
|
|
|
|
|
|
|
|
|
import ai.nomyo.errors.*;
|
|
|
|
|
import lombok.Getter;
|
|
|
|
|
|
2026-04-26 18:21:05 +02:00
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
2026-04-21 17:24:11 +02:00
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
2026-04-26 18:21:05 +02:00
|
|
|
import java.util.UUID;
|
|
|
|
|
import java.util.concurrent.ExecutionException;
|
2026-04-21 17:24:11 +02:00
|
|
|
|
|
|
|
|
/**
|
2026-04-23 13:36:46 +02:00
|
|
|
* High-level OpenAI-compatible entrypoint with automatic hybrid encryption (AES-256-GCM + RSA-4096).
|
2026-04-21 17:24:11 +02:00
|
|
|
*/
|
|
|
|
|
@Getter
|
|
|
|
|
public class SecureChatCompletion {
|
|
|
|
|
|
|
|
|
|
private final SecureCompletionClient client;
|
|
|
|
|
private final String apiKey;
|
|
|
|
|
private final String keyDir;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-23 13:36:46 +02:00
|
|
|
* Default settings: {@code https://api.nomyo.ai}, HTTPS-only, secure memory, ephemeral keys, 2 retries.
|
2026-04-21 17:24:11 +02:00
|
|
|
*/
|
|
|
|
|
public SecureChatCompletion() {
|
|
|
|
|
this(Constants.DEFAULT_BASE_URL, false, null, true, null, Constants.DEFAULT_MAX_RETRIES);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 18:21:05 +02:00
|
|
|
public SecureChatCompletion(String baseUrl, String apiKey) {
|
|
|
|
|
this(baseUrl, false, apiKey, true, null, Constants.DEFAULT_MAX_RETRIES);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SecureChatCompletion(String baseUrl) {
|
|
|
|
|
this(baseUrl, false, null, true, null, Constants.DEFAULT_MAX_RETRIES);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 17:24:11 +02:00
|
|
|
/**
|
2026-04-23 13:36:46 +02:00
|
|
|
* @param baseUrl NOMYO Router base URL (HTTPS enforced unless {@code allowHttp})
|
|
|
|
|
* @param allowHttp permit {@code http://} URLs (development only)
|
|
|
|
|
* @param apiKey Bearer token (can also be passed per-call via {@link #create})
|
|
|
|
|
* @param secureMemory enable memory locking/zeroing
|
|
|
|
|
* @param keyDir RSA key directory; {@code null} = ephemeral
|
|
|
|
|
* @param maxRetries retries on 429/500/502/503/504 + network errors (exponential backoff)
|
2026-04-21 17:24:11 +02:00
|
|
|
*/
|
2026-04-23 13:36:46 +02:00
|
|
|
public SecureChatCompletion(String baseUrl, boolean allowHttp, String apiKey, boolean secureMemory, String keyDir, int maxRetries) {
|
2026-04-21 17:24:11 +02:00
|
|
|
this.client = new SecureCompletionClient(baseUrl, allowHttp, secureMemory, maxRetries);
|
|
|
|
|
this.apiKey = apiKey;
|
|
|
|
|
this.keyDir = keyDir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-23 13:36:46 +02:00
|
|
|
* Main entrypoint — same signature as {@code openai.ChatCompletion.create()}.
|
|
|
|
|
* All kwargs are passed through to the OpenAI-compatible API.
|
|
|
|
|
* <p>Streaming is <b>not supported</b> (server rejects with HTTP 400).
|
|
|
|
|
* Security tiers: "standard", "high", "maximum".
|
2026-04-21 17:24:11 +02:00
|
|
|
*
|
2026-04-23 13:36:46 +02:00
|
|
|
* @param model model identifier (required)
|
|
|
|
|
* @param messages OpenAI-format message list (required)
|
|
|
|
|
* @param kwargs additional OpenAI-compatible params (temperature, maxTokens, etc.)
|
|
|
|
|
* @return decrypted response map
|
|
|
|
|
* @throws SecurityError encryption/decryption failure
|
|
|
|
|
* @throws APIConnectionError network error
|
|
|
|
|
* @throws InvalidRequestError HTTP 400
|
|
|
|
|
* @throws AuthenticationError HTTP 401
|
|
|
|
|
* @throws ForbiddenError HTTP 403
|
|
|
|
|
* @throws RateLimitError HTTP 429
|
|
|
|
|
* @throws ServerError HTTP 500
|
|
|
|
|
* @throws ServiceUnavailableError HTTP 503
|
|
|
|
|
* @throws APIError other errors
|
2026-04-21 17:24:11 +02:00
|
|
|
*/
|
2026-04-26 18:21:05 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
2026-04-21 17:24:11 +02:00
|
|
|
public Map<String, Object> create(String model, List<Map<String, Object>> messages, Map<String, Object> kwargs) {
|
2026-04-26 18:21:05 +02:00
|
|
|
// Validate required parameters
|
|
|
|
|
if (model == null || model.isEmpty()) {
|
|
|
|
|
throw new IllegalArgumentException("model is required");
|
|
|
|
|
}
|
|
|
|
|
if (messages == null || messages.isEmpty()) {
|
|
|
|
|
throw new IllegalArgumentException("messages is required and cannot be empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build payload
|
|
|
|
|
Map<String, Object> payload = new HashMap<>();
|
|
|
|
|
payload.put("model", model);
|
|
|
|
|
payload.put("messages", messages);
|
|
|
|
|
|
|
|
|
|
// Add kwargs
|
|
|
|
|
if (kwargs != null) {
|
|
|
|
|
// Check for stream parameter
|
|
|
|
|
if (kwargs.containsKey("stream")) {
|
|
|
|
|
Object streamValue = kwargs.get("stream");
|
|
|
|
|
boolean stream = streamValue instanceof Boolean ? (Boolean) streamValue : Boolean.parseBoolean(streamValue.toString());
|
|
|
|
|
if (stream) {
|
|
|
|
|
throw new IllegalArgumentException("Streaming is not supported");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for security_tier
|
|
|
|
|
if (kwargs.containsKey("security_tier")) {
|
|
|
|
|
Object tier = kwargs.get("security_tier");
|
|
|
|
|
if (tier != null && !Constants.VALID_SECURITY_TIERS.contains(tier.toString())) {
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
"Invalid security_tier: '" + tier + "'. Must be one of: " + Constants.VALID_SECURITY_TIERS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
payload.putAll(kwargs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine API key (per-call override or instance key)
|
|
|
|
|
String apiKey = this.apiKey;
|
|
|
|
|
if (kwargs != null && kwargs.containsKey("api_key")) {
|
|
|
|
|
Object key = kwargs.get("api_key");
|
|
|
|
|
if (key != null) {
|
|
|
|
|
apiKey = key.toString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine security tier
|
|
|
|
|
String securityTier = null;
|
|
|
|
|
if (kwargs != null && kwargs.containsKey("security_tier")) {
|
|
|
|
|
securityTier = kwargs.get("security_tier").toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate payload ID
|
|
|
|
|
String payloadId = UUID.randomUUID().toString();
|
|
|
|
|
|
2026-04-21 17:24:11 +02:00
|
|
|
// Send secure request
|
2026-04-26 18:21:05 +02:00
|
|
|
try {
|
|
|
|
|
return client.sendSecureRequest(payload, payloadId, apiKey, securityTier).get();
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
throw new RuntimeException("Request interrupted", e);
|
|
|
|
|
} catch (ExecutionException e) {
|
|
|
|
|
Throwable cause = e.getCause();
|
|
|
|
|
switch (cause) {
|
|
|
|
|
case SecurityError securityError -> throw new RuntimeException(cause);
|
|
|
|
|
case InvalidRequestError invalidRequestError -> throw new RuntimeException(cause);
|
|
|
|
|
case AuthenticationError authenticationError -> throw new RuntimeException(cause);
|
|
|
|
|
case ForbiddenError forbiddenError -> throw new RuntimeException(cause);
|
|
|
|
|
case RateLimitError rateLimitError -> throw new RuntimeException(cause);
|
|
|
|
|
case ServerError serverError -> throw new RuntimeException(cause);
|
|
|
|
|
case ServiceUnavailableError serviceUnavailableError -> throw new RuntimeException(cause);
|
|
|
|
|
case APIError apiError -> throw new RuntimeException(cause);
|
|
|
|
|
case APIConnectionError apiConnectionError -> throw new RuntimeException(cause);
|
|
|
|
|
case SecureCompletionClient.ValueError valueError -> throw new IllegalArgumentException(cause);
|
|
|
|
|
default ->
|
|
|
|
|
throw new RuntimeException("Request failed: " + cause.getMessage(), cause);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-21 17:24:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convenience variant with no additional parameters.
|
|
|
|
|
*/
|
|
|
|
|
public Map<String, Object> create(String model, List<Map<String, Object>> messages) {
|
|
|
|
|
return create(model, messages, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-23 13:36:46 +02:00
|
|
|
* Async alias for {@link #create(String, List, Map)}.
|
2026-04-21 17:24:11 +02:00
|
|
|
*/
|
|
|
|
|
public Map<String, Object> acreate(String model, List<Map<String, Object>> messages, Map<String, Object> kwargs) {
|
|
|
|
|
return create(model, messages, kwargs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-23 13:36:46 +02:00
|
|
|
* Async alias for {@link #create(String, List)}.
|
2026-04-21 17:24:11 +02:00
|
|
|
*/
|
|
|
|
|
public Map<String, Object> acreate(String model, List<Map<String, Object>> messages) {
|
|
|
|
|
return create(model, messages);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-23 13:36:46 +02:00
|
|
|
* Delegates to {@link SecureCompletionClient#close()}.
|
2026-04-21 17:24:11 +02:00
|
|
|
*/
|
|
|
|
|
public void close() {
|
|
|
|
|
client.close();
|
|
|
|
|
}
|
|
|
|
|
}
|