mirror of
https://github.com/openai/codex.git
synced 2026-04-25 07:05:38 +00:00
Move Agent bridge responses off the AgentService callback thread and filter machine bridge question/answer events out of the user-facing session snapshot so the Agent UI only shows the real Genie prompt. Co-authored-by: Codex <noreply@openai.com>
106 lines
4.2 KiB
Kotlin
106 lines
4.2 KiB
Kotlin
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
|
|
import kotlin.concurrent.thread
|
|
|
|
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
|
|
}
|
|
|
|
thread(name = "CodexAgentBridge-$requestId") {
|
|
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
|
|
}
|
|
}
|
|
}
|