LocalAiFeedback.kt
src/jvmMain/kotlin/afield/ai/models/LocalAiFeedback.kt · 49 lines
package afield.ai.models
import com.fasterxml.jackson.databind.ObjectMapper
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
private val feedbackMapper = ObjectMapper()
private val feedbackClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2))
.followRedirects(HttpClient.Redirect.NEVER).build()
internal fun localAiMonitorUri(baseUrl: String, override: String? = null): URI
The optional monitor lives beside the model server, one port above its API.
internal fun localAiMonitorUri(baseUrl: String, override: String? = null): URI {
val uri = if (!override.isNullOrBlank()) URI.create(override) else {
val base = URI.create(normalizeLocalAiBaseUrl(baseUrl))
val port = if (base.port >= 0) base.port else if (base.scheme == "https") 443 else 80
require(port < 65535) { "Set AFIELD_LOCAL_AI_MONITOR_URL for this endpoint." }
URI(base.scheme, null, base.host, port + 1, "/status", null, null)
}
require(uri.scheme in setOf("http", "https") && uri.host != null && uri.userInfo == null) {
"The local monitor URL must be an HTTP or HTTPS address."
}
return uri
}
internal fun localAiFeedback(monitorUrl: String? = System.getenv("AFIELD_LOCAL_AI_MONITOR_URL")): String
Remote hardware feedback is available only for Local; cloud credentials never leave Field here.
internal fun localAiFeedback(monitorUrl: String? = System.getenv("AFIELD_LOCAL_AI_MONITOR_URL")): String {
if (configuredAiProvider() != "local") return "{\"enabled\":false}"
return try {
val uri = localAiMonitorUri(configuredAiBaseUrl("local"), monitorUrl)
val request = HttpRequest.newBuilder(uri).timeout(Duration.ofSeconds(3))
.header("Accept", "application/json").GET().build()
val response = feedbackClient.send(request, HttpResponse.BodyHandlers.ofInputStream())
val body = response.body().use { it.readNBytes(65537) }
require(response.statusCode() == 200 && body.size <= 65536)
val data = feedbackMapper.readTree(body)
require(data.isObject && data.path("schemaVersion").asInt() == 1)
val result = feedbackMapper.createObjectNode()
result.put("enabled", true)
result.put("available", true)
result.set<com.fasterxml.jackson.databind.JsonNode>("sample", data)
result.toString()
} catch (_: Exception) {
"{\"enabled\":true,\"available\":false}"
}
}