mirror of
https://github.com/openai/codex.git
synced 2026-04-29 17:06:51 +00:00
Teach the Agent runtime and UI to resolve target packages from installed apps and start one child Genie session per selected package, with an optional package override for debugging. Co-authored-by: Codex <noreply@openai.com>
137 lines
4.9 KiB
Kotlin
137 lines
4.9 KiB
Kotlin
package com.openai.codexd
|
|
|
|
import java.io.IOException
|
|
import org.json.JSONArray
|
|
import org.json.JSONObject
|
|
|
|
object CodexResponsesClient {
|
|
fun requestText(
|
|
context: android.content.Context,
|
|
model: String,
|
|
instructions: String,
|
|
prompt: String,
|
|
): String {
|
|
val requestBody = buildRequest(
|
|
model = model,
|
|
instructions = instructions,
|
|
prompt = prompt,
|
|
)
|
|
val response = CodexdLocalClient.waitForResponse(
|
|
context = context,
|
|
method = "POST",
|
|
path = "/v1/responses",
|
|
body = requestBody.toString(),
|
|
)
|
|
if (response.statusCode != 200) {
|
|
throw IOException("HTTP ${response.statusCode}: ${response.body}")
|
|
}
|
|
return parseResponsesOutputText(response.body)
|
|
}
|
|
|
|
fun buildRequest(
|
|
model: String,
|
|
instructions: String,
|
|
prompt: String,
|
|
): JSONObject {
|
|
return JSONObject()
|
|
.put("model", model)
|
|
.put("store", false)
|
|
.put("stream", true)
|
|
.put("instructions", instructions)
|
|
.put(
|
|
"input",
|
|
JSONArray().put(
|
|
JSONObject()
|
|
.put("role", "user")
|
|
.put(
|
|
"content",
|
|
JSONArray().put(
|
|
JSONObject()
|
|
.put("type", "input_text")
|
|
.put("text", prompt),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
fun parseResponsesOutputText(body: String): String {
|
|
val trimmedBody = body.trim()
|
|
if (trimmedBody.startsWith("event:") || trimmedBody.startsWith("data:")) {
|
|
return parseResponsesStreamOutputText(trimmedBody)
|
|
}
|
|
return parseResponsesJsonOutputText(JSONObject(trimmedBody))
|
|
}
|
|
|
|
private fun parseResponsesJsonOutputText(data: JSONObject): String {
|
|
val directOutput = data.optString("output_text")
|
|
if (directOutput.isNotBlank()) {
|
|
return directOutput
|
|
}
|
|
val output = data.optJSONArray("output")
|
|
?: throw IOException("Responses payload missing output")
|
|
val combined = buildString {
|
|
for (outputIndex in 0 until output.length()) {
|
|
val item = output.optJSONObject(outputIndex) ?: continue
|
|
val content = item.optJSONArray("content") ?: continue
|
|
for (contentIndex in 0 until content.length()) {
|
|
val part = content.optJSONObject(contentIndex) ?: continue
|
|
if (part.optString("type") == "output_text") {
|
|
append(part.optString("text"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (combined.isBlank()) {
|
|
throw IOException("Responses payload missing output_text content")
|
|
}
|
|
return combined
|
|
}
|
|
|
|
private fun parseResponsesStreamOutputText(body: String): String {
|
|
val deltaText = StringBuilder()
|
|
val completedItems = mutableListOf<String>()
|
|
body.split("\n\n").forEach { rawEvent ->
|
|
val lines = rawEvent.lineSequence().map(String::trimEnd).toList()
|
|
if (lines.isEmpty()) {
|
|
return@forEach
|
|
}
|
|
val dataPayload = lines
|
|
.filter { it.startsWith("data:") }
|
|
.joinToString("\n") { it.removePrefix("data:").trimStart() }
|
|
.trim()
|
|
if (dataPayload.isEmpty() || dataPayload == "[DONE]") {
|
|
return@forEach
|
|
}
|
|
val event = JSONObject(dataPayload)
|
|
when (event.optString("type")) {
|
|
"response.output_text.delta" -> deltaText.append(event.optString("delta"))
|
|
"response.output_item.done" -> {
|
|
val item = event.optJSONObject("item") ?: return@forEach
|
|
val content = item.optJSONArray("content") ?: return@forEach
|
|
val text = buildString {
|
|
for (index in 0 until content.length()) {
|
|
val part = content.optJSONObject(index) ?: continue
|
|
if (part.optString("type") == "output_text") {
|
|
append(part.optString("text"))
|
|
}
|
|
}
|
|
}
|
|
if (text.isNotBlank()) {
|
|
completedItems += text
|
|
}
|
|
}
|
|
"response.failed" -> throw IOException(event.toString())
|
|
}
|
|
}
|
|
if (deltaText.isNotBlank()) {
|
|
return deltaText.toString()
|
|
}
|
|
val completedText = completedItems.joinToString("")
|
|
if (completedText.isNotBlank()) {
|
|
return completedText
|
|
}
|
|
throw IOException("Responses stream missing output_text content")
|
|
}
|
|
}
|