mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Improve Android responses proxy diagnostics
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.SocketException
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import org.json.JSONObject
|
||||
@@ -38,8 +39,12 @@ object AgentResponsesProxy {
|
||||
upstreamBaseUrl = "provider-default",
|
||||
authSnapshot.authMode,
|
||||
)
|
||||
Log.i(TAG, "Proxying /v1/responses -> $upstreamUrl (auth_mode=${authSnapshot.authMode})")
|
||||
return executeRequest(upstreamUrl, requestBody, authSnapshot)
|
||||
val requestBodyBytes = requestBody.toByteArray(StandardCharsets.UTF_8)
|
||||
Log.i(
|
||||
TAG,
|
||||
"Proxying /v1/responses -> $upstreamUrl (auth_mode=${authSnapshot.authMode}, bytes=${requestBodyBytes.size})",
|
||||
)
|
||||
return executeRequest(upstreamUrl, requestBodyBytes, authSnapshot)
|
||||
}
|
||||
|
||||
internal fun buildResponsesUrl(
|
||||
@@ -94,19 +99,34 @@ object AgentResponsesProxy {
|
||||
|
||||
private fun executeRequest(
|
||||
upstreamUrl: String,
|
||||
requestBody: String,
|
||||
requestBodyBytes: ByteArray,
|
||||
authSnapshot: AuthSnapshot,
|
||||
): HttpResponse {
|
||||
val connection = openConnection(upstreamUrl, authSnapshot)
|
||||
return try {
|
||||
connection.outputStream.use { output ->
|
||||
output.write(requestBody.toByteArray(StandardCharsets.UTF_8))
|
||||
output.flush()
|
||||
try {
|
||||
connection.outputStream.use { output ->
|
||||
output.write(requestBodyBytes)
|
||||
output.flush()
|
||||
}
|
||||
} catch (err: IOException) {
|
||||
throw wrapRequestFailure("write request body", upstreamUrl, err)
|
||||
}
|
||||
val statusCode = connection.responseCode
|
||||
val stream = if (statusCode >= 400) connection.errorStream else connection.inputStream
|
||||
val responseBody = stream?.bufferedReader(StandardCharsets.UTF_8)?.use { it.readText() }
|
||||
.orEmpty()
|
||||
val statusCode = try {
|
||||
connection.responseCode
|
||||
} catch (err: IOException) {
|
||||
throw wrapRequestFailure("read response status", upstreamUrl, err)
|
||||
}
|
||||
val responseBody = try {
|
||||
val stream = if (statusCode >= 400) connection.errorStream else connection.inputStream
|
||||
stream?.bufferedReader(StandardCharsets.UTF_8)?.use { it.readText() }.orEmpty()
|
||||
} catch (err: IOException) {
|
||||
throw wrapRequestFailure("read response body", upstreamUrl, err)
|
||||
}
|
||||
Log.i(
|
||||
TAG,
|
||||
"Responses proxy completed status=$statusCode response_bytes=${responseBody.toByteArray(StandardCharsets.UTF_8).size}",
|
||||
)
|
||||
HttpResponse(
|
||||
statusCode = statusCode,
|
||||
body = responseBody,
|
||||
@@ -120,25 +140,52 @@ object AgentResponsesProxy {
|
||||
upstreamUrl: String,
|
||||
authSnapshot: AuthSnapshot,
|
||||
): HttpURLConnection {
|
||||
return (URL(upstreamUrl).openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "POST"
|
||||
connectTimeout = CONNECT_TIMEOUT_MS
|
||||
readTimeout = READ_TIMEOUT_MS
|
||||
doInput = true
|
||||
doOutput = true
|
||||
instanceFollowRedirects = true
|
||||
setRequestProperty("Authorization", "Bearer ${authSnapshot.bearerToken}")
|
||||
setRequestProperty("Content-Type", "application/json")
|
||||
setRequestProperty("Accept", "text/event-stream")
|
||||
setRequestProperty("Accept-Encoding", "identity")
|
||||
setRequestProperty("originator", DEFAULT_ORIGINATOR)
|
||||
setRequestProperty("User-Agent", DEFAULT_USER_AGENT)
|
||||
if (authSnapshot.authMode == "chatgpt" && !authSnapshot.accountId.isNullOrBlank()) {
|
||||
setRequestProperty("ChatGPT-Account-ID", authSnapshot.accountId)
|
||||
return try {
|
||||
(URL(upstreamUrl).openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "POST"
|
||||
connectTimeout = CONNECT_TIMEOUT_MS
|
||||
readTimeout = READ_TIMEOUT_MS
|
||||
doInput = true
|
||||
doOutput = true
|
||||
instanceFollowRedirects = true
|
||||
setRequestProperty("Authorization", "Bearer ${authSnapshot.bearerToken}")
|
||||
setRequestProperty("Content-Type", "application/json")
|
||||
setRequestProperty("Accept", "text/event-stream")
|
||||
setRequestProperty("Accept-Encoding", "identity")
|
||||
setRequestProperty("originator", DEFAULT_ORIGINATOR)
|
||||
setRequestProperty("User-Agent", DEFAULT_USER_AGENT)
|
||||
if (authSnapshot.authMode == "chatgpt" && !authSnapshot.accountId.isNullOrBlank()) {
|
||||
setRequestProperty("ChatGPT-Account-ID", authSnapshot.accountId)
|
||||
}
|
||||
}
|
||||
} catch (err: IOException) {
|
||||
throw wrapRequestFailure("open connection", upstreamUrl, err)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun describeRequestFailure(
|
||||
phase: String,
|
||||
upstreamUrl: String,
|
||||
err: IOException,
|
||||
): String {
|
||||
val reason = err.message?.ifBlank { err::class.java.simpleName } ?: err::class.java.simpleName
|
||||
return "Responses proxy failed during $phase for $upstreamUrl: ${err::class.java.simpleName}: $reason"
|
||||
}
|
||||
|
||||
private fun wrapRequestFailure(
|
||||
phase: String,
|
||||
upstreamUrl: String,
|
||||
err: IOException,
|
||||
): IOException {
|
||||
val wrapped = IOException(describeRequestFailure(phase, upstreamUrl, err), err)
|
||||
if (err is SocketException) {
|
||||
Log.w(TAG, wrapped.message, err)
|
||||
} else {
|
||||
Log.e(TAG, wrapped.message, err)
|
||||
}
|
||||
return wrapped
|
||||
}
|
||||
|
||||
private fun JSONObject.stringOrNull(key: String): String? {
|
||||
if (!has(key) || isNull(key)) {
|
||||
return null
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.openai.codex.agent
|
||||
|
||||
import java.io.File
|
||||
import java.net.SocketException
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
@@ -72,6 +73,20 @@ class AgentResponsesProxyTest {
|
||||
assertNull(snapshot.accountId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun describeRequestFailureIncludesPhaseUrlAndCause() {
|
||||
val message = AgentResponsesProxy.describeRequestFailure(
|
||||
phase = "read response body",
|
||||
upstreamUrl = "https://chatgpt.com/backend-api/codex/responses",
|
||||
err = SocketException("Software caused connection abort"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"Responses proxy failed during read response body for https://chatgpt.com/backend-api/codex/responses: SocketException: Software caused connection abort",
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
||||
private fun writeTempAuthJson(contents: String): File {
|
||||
return File.createTempFile("agent-auth", ".json").apply {
|
||||
writeText(contents)
|
||||
|
||||
Reference in New Issue
Block a user