package ai.nomyo; import ai.nomyo.errors.*; import lombok.Getter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; /** * High-level OpenAI-compatible entrypoint with automatic hybrid encryption (AES-256-GCM + RSA-4096). */ @Getter public class SecureChatCompletion { private final SecureCompletionClient client; private final String apiKey; private final String keyDir; /** * Default settings: {@code https://api.nomyo.ai}, HTTPS-only, secure memory, ephemeral keys, 2 retries. */ public SecureChatCompletion() { this(Constants.DEFAULT_BASE_URL, false, null, true, null, Constants.DEFAULT_MAX_RETRIES); } 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); } /** * @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) */ public SecureChatCompletion(String baseUrl, boolean allowHttp, String apiKey, boolean secureMemory, String keyDir, int maxRetries) { this.client = new SecureCompletionClient(baseUrl, allowHttp, secureMemory, maxRetries); this.apiKey = apiKey; this.keyDir = keyDir; } /** * Main entrypoint — same signature as {@code openai.ChatCompletion.create()}. * All kwargs are passed through to the OpenAI-compatible API. *

Streaming is not supported (server rejects with HTTP 400). * Security tiers: "standard", "high", "maximum". * * @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 */ @SuppressWarnings("unchecked") public Map create(String model, List> messages, Map kwargs) { // 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 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(); // Send secure request 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); } } } /** * Convenience variant with no additional parameters. */ public Map create(String model, List> messages) { return create(model, messages, null); } /** * Async alias for {@link #create(String, List, Map)}. */ public Map acreate(String model, List> messages, Map kwargs) { return create(model, messages, kwargs); } /** * Async alias for {@link #create(String, List)}. */ public Map acreate(String model, List> messages) { return create(model, messages); } /** * Delegates to {@link SecureCompletionClient#close()}. */ public void close() { client.close(); } }