mirror of
https://github.com/openai/codex.git
synced 2026-04-29 08:56:38 +00:00
Generalize Genie bridge to HTTP envelopes
Carry small HTTP request/response envelopes over the framework-mediated Agent bridge, switch the Genie auth probe to the real /internal/auth/status response body, and update the Android refactor doc to reflect the current bridge shape. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -15,6 +15,7 @@ class CodexAgentService : AgentService() {
|
||||
private const val BRIDGE_REQUEST_PREFIX = "__codex_bridge__ "
|
||||
private const val BRIDGE_RESPONSE_PREFIX = "__codex_bridge_result__ "
|
||||
private const val METHOD_GET_AUTH_STATUS = "get_auth_status"
|
||||
private const val METHOD_HTTP_REQUEST = "http_request"
|
||||
}
|
||||
|
||||
private val handledBridgeRequests = ConcurrentHashMap.newKeySet<String>()
|
||||
@@ -70,9 +71,38 @@ class CodexAgentService : AgentService() {
|
||||
JSONObject()
|
||||
.put("requestId", requestId)
|
||||
.put("ok", false)
|
||||
.put("error", err.message ?: err::class.java.simpleName)
|
||||
},
|
||||
)
|
||||
.put("error", err.message ?: err::class.java.simpleName)
|
||||
},
|
||||
)
|
||||
METHOD_HTTP_REQUEST -> {
|
||||
val httpMethod = requestJson.optString("httpMethod")
|
||||
val path = requestJson.optString("path")
|
||||
val body = if (requestJson.isNull("body")) null else requestJson.optString("body")
|
||||
if (httpMethod.isBlank() || path.isBlank()) {
|
||||
JSONObject()
|
||||
.put("requestId", requestId)
|
||||
.put("ok", false)
|
||||
.put("error", "Missing httpMethod or path")
|
||||
} else {
|
||||
runCatching {
|
||||
CodexdLocalClient.waitForResponse(this, httpMethod, path, body)
|
||||
}.fold(
|
||||
onSuccess = { httpResponse ->
|
||||
JSONObject()
|
||||
.put("requestId", requestId)
|
||||
.put("ok", true)
|
||||
.put("statusCode", httpResponse.statusCode)
|
||||
.put("body", httpResponse.body)
|
||||
},
|
||||
onFailure = { err ->
|
||||
JSONObject()
|
||||
.put("requestId", requestId)
|
||||
.put("ok", false)
|
||||
.put("error", err.message ?: err::class.java.simpleName)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> JSONObject()
|
||||
.put("requestId", requestId)
|
||||
.put("ok", false)
|
||||
|
||||
@@ -9,13 +9,23 @@ import java.io.BufferedInputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
object CodexdLocalClient {
|
||||
data class HttpResponse(
|
||||
val statusCode: Int,
|
||||
val body: String,
|
||||
)
|
||||
|
||||
data class AuthStatus(
|
||||
val authenticated: Boolean,
|
||||
val accountEmail: String?,
|
||||
val clientCount: Int,
|
||||
)
|
||||
|
||||
fun waitForAuthStatus(context: Context): AuthStatus {
|
||||
fun waitForResponse(
|
||||
context: Context,
|
||||
method: String,
|
||||
path: String,
|
||||
body: String?,
|
||||
): HttpResponse {
|
||||
context.startForegroundService(
|
||||
android.content.Intent(context, CodexdForegroundService::class.java).apply {
|
||||
action = CodexdForegroundService.ACTION_START
|
||||
@@ -25,60 +35,93 @@ object CodexdLocalClient {
|
||||
)
|
||||
|
||||
repeat(30) {
|
||||
fetchAuthStatus(CodexSocketConfig.DEFAULT_SOCKET_PATH)?.let { return it }
|
||||
runCatching {
|
||||
executeRequest(CodexSocketConfig.DEFAULT_SOCKET_PATH, method, path, body)
|
||||
}.getOrNull()?.let { return it }
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
throw IOException("codexd unavailable")
|
||||
}
|
||||
|
||||
fun waitForAuthStatus(context: Context): AuthStatus {
|
||||
val response = waitForResponse(context, "GET", "/internal/auth/status", null)
|
||||
if (response.statusCode != 200) {
|
||||
throw IOException("HTTP ${response.statusCode}: ${response.body}")
|
||||
}
|
||||
return parseAuthStatus(response.body)
|
||||
}
|
||||
|
||||
fun fetchAuthStatus(socketPath: String): AuthStatus? {
|
||||
return try {
|
||||
val socket = LocalSocket()
|
||||
val address = CodexSocketConfig.toLocalSocketAddress(socketPath)
|
||||
socket.connect(address)
|
||||
val request = buildString {
|
||||
append("GET /internal/auth/status HTTP/1.1\r\n")
|
||||
append("Host: localhost\r\n")
|
||||
append("Connection: close\r\n")
|
||||
append("\r\n")
|
||||
}
|
||||
val output = socket.outputStream
|
||||
output.write(request.toByteArray(StandardCharsets.UTF_8))
|
||||
output.flush()
|
||||
|
||||
val responseBytes = BufferedInputStream(socket.inputStream).use { it.readBytes() }
|
||||
socket.close()
|
||||
|
||||
val responseText = responseBytes.toString(StandardCharsets.UTF_8)
|
||||
val splitIndex = responseText.indexOf("\r\n\r\n")
|
||||
if (splitIndex == -1) {
|
||||
val response = executeRequest(socketPath, "GET", "/internal/auth/status", null)
|
||||
if (response.statusCode != 200) {
|
||||
return null
|
||||
}
|
||||
val statusLine = responseText.substring(0, splitIndex)
|
||||
.lineSequence()
|
||||
.firstOrNull()
|
||||
.orEmpty()
|
||||
val statusCode = statusLine.split(" ").getOrNull(1)?.toIntOrNull() ?: return null
|
||||
if (statusCode != 200) {
|
||||
return null
|
||||
}
|
||||
val body = responseText.substring(splitIndex + 4)
|
||||
val json = JSONObject(body)
|
||||
val accountEmail =
|
||||
if (json.isNull("accountEmail")) null else json.optString("accountEmail")
|
||||
val clientCount = if (json.has("clientCount")) {
|
||||
json.optInt("clientCount", 0)
|
||||
} else {
|
||||
json.optInt("client_count", 0)
|
||||
}
|
||||
AuthStatus(
|
||||
authenticated = json.optBoolean("authenticated", false),
|
||||
accountEmail = accountEmail,
|
||||
clientCount = clientCount,
|
||||
)
|
||||
parseAuthStatus(response.body)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun executeRequest(
|
||||
socketPath: String,
|
||||
method: String,
|
||||
path: String,
|
||||
body: String?,
|
||||
): HttpResponse {
|
||||
val socket = LocalSocket()
|
||||
val address = CodexSocketConfig.toLocalSocketAddress(socketPath)
|
||||
socket.connect(address)
|
||||
val payload = body ?: ""
|
||||
val request = buildString {
|
||||
append("$method $path HTTP/1.1\r\n")
|
||||
append("Host: localhost\r\n")
|
||||
append("Connection: close\r\n")
|
||||
if (body != null) {
|
||||
append("Content-Type: application/json\r\n")
|
||||
}
|
||||
append("Content-Length: ${payload.toByteArray(StandardCharsets.UTF_8).size}\r\n")
|
||||
append("\r\n")
|
||||
append(payload)
|
||||
}
|
||||
val output = socket.outputStream
|
||||
output.write(request.toByteArray(StandardCharsets.UTF_8))
|
||||
output.flush()
|
||||
|
||||
val responseBytes = BufferedInputStream(socket.inputStream).use { it.readBytes() }
|
||||
socket.close()
|
||||
|
||||
val responseText = responseBytes.toString(StandardCharsets.UTF_8)
|
||||
val splitIndex = responseText.indexOf("\r\n\r\n")
|
||||
if (splitIndex == -1) {
|
||||
throw IOException("Invalid HTTP response")
|
||||
}
|
||||
val statusLine = responseText.substring(0, splitIndex)
|
||||
.lineSequence()
|
||||
.firstOrNull()
|
||||
.orEmpty()
|
||||
val statusCode = statusLine.split(" ").getOrNull(1)?.toIntOrNull()
|
||||
?: throw IOException("Missing status code")
|
||||
return HttpResponse(
|
||||
statusCode = statusCode,
|
||||
body = responseText.substring(splitIndex + 4),
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseAuthStatus(body: String): AuthStatus {
|
||||
val json = JSONObject(body)
|
||||
val accountEmail =
|
||||
if (json.isNull("accountEmail")) null else json.optString("accountEmail")
|
||||
val clientCount = if (json.has("clientCount")) {
|
||||
json.optInt("clientCount", 0)
|
||||
} else {
|
||||
json.optInt("client_count", 0)
|
||||
}
|
||||
return AuthStatus(
|
||||
authenticated = json.optBoolean("authenticated", false),
|
||||
accountEmail = accountEmail,
|
||||
clientCount = clientCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user