mirror of
https://github.com/openai/codex.git
synced 2026-04-25 15:15:15 +00:00
Implement framework-mediated Agent bridge for Genie
Add an internal AgentSDK question/answer bridge so Genie can reach Agent-owned codexd state from the paired app sandbox, keep the Android daemon on abstract unix sockets, and document the runtime constraint this proves on the emulator. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -1,19 +1,102 @@
|
||||
package com.openai.codexd
|
||||
|
||||
import android.app.agent.AgentManager
|
||||
import android.app.agent.AgentService
|
||||
import android.app.agent.AgentSessionEvent
|
||||
import android.app.agent.AgentSessionInfo
|
||||
import android.util.Log
|
||||
import org.json.JSONObject
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class CodexAgentService : AgentService() {
|
||||
companion object {
|
||||
private const val TAG = "CodexAgentService"
|
||||
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 val handledBridgeRequests = ConcurrentHashMap.newKeySet<String>()
|
||||
private val agentManager by lazy { getSystemService(AgentManager::class.java) }
|
||||
|
||||
override fun onSessionChanged(session: AgentSessionInfo) {
|
||||
Log.i(TAG, "onSessionChanged $session")
|
||||
handleInternalBridgeQuestion(session.sessionId)
|
||||
}
|
||||
|
||||
override fun onSessionRemoved(sessionId: String) {
|
||||
Log.i(TAG, "onSessionRemoved sessionId=$sessionId")
|
||||
}
|
||||
|
||||
private fun handleInternalBridgeQuestion(sessionId: String) {
|
||||
val manager = agentManager ?: return
|
||||
val events = manager.getSessionEvents(sessionId)
|
||||
val question = events.lastOrNull { event ->
|
||||
event.type == AgentSessionEvent.TYPE_QUESTION && event.message != null
|
||||
}?.message ?: return
|
||||
if (!question.startsWith(BRIDGE_REQUEST_PREFIX)) {
|
||||
return
|
||||
}
|
||||
val requestJson = runCatching {
|
||||
JSONObject(question.removePrefix(BRIDGE_REQUEST_PREFIX))
|
||||
}.getOrElse { err ->
|
||||
Log.w(TAG, "Ignoring malformed bridge question for $sessionId", err)
|
||||
return
|
||||
}
|
||||
val requestId = requestJson.optString("requestId")
|
||||
val method = requestJson.optString("method")
|
||||
if (requestId.isBlank() || method.isBlank()) {
|
||||
return
|
||||
}
|
||||
val requestKey = "$sessionId:$requestId"
|
||||
if (hasAnswerForRequest(events, requestId) || !handledBridgeRequests.add(requestKey)) {
|
||||
return
|
||||
}
|
||||
|
||||
val response = when (method) {
|
||||
METHOD_GET_AUTH_STATUS -> runCatching { CodexdLocalClient.waitForAuthStatus(this) }
|
||||
.fold(
|
||||
onSuccess = { status ->
|
||||
JSONObject()
|
||||
.put("requestId", requestId)
|
||||
.put("ok", true)
|
||||
.put("authenticated", status.authenticated)
|
||||
.put("accountEmail", status.accountEmail)
|
||||
.put("clientCount", status.clientCount)
|
||||
},
|
||||
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)
|
||||
.put("error", "Unknown bridge method: $method")
|
||||
}
|
||||
|
||||
runCatching {
|
||||
manager.answerQuestion(sessionId, "$BRIDGE_RESPONSE_PREFIX$response")
|
||||
}.onFailure { err ->
|
||||
handledBridgeRequests.remove(requestKey)
|
||||
Log.w(TAG, "Failed to answer bridge question for $sessionId", err)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasAnswerForRequest(events: List<AgentSessionEvent>, requestId: String): Boolean {
|
||||
return events.any { event ->
|
||||
if (event.type != AgentSessionEvent.TYPE_ANSWER || event.message == null) {
|
||||
return@any false
|
||||
}
|
||||
val message = event.message
|
||||
if (!message.startsWith(BRIDGE_RESPONSE_PREFIX)) {
|
||||
return@any false
|
||||
}
|
||||
runCatching {
|
||||
JSONObject(message.removePrefix(BRIDGE_RESPONSE_PREFIX)).optString("requestId")
|
||||
}.getOrNull() == requestId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user