Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ data class OpenCodeAgent(
val description: String? = null,
val mode: String? = null,
val native: Boolean = false,
val hidden: Boolean = false,
/**
* Nullable on purpose: OpenCode sends `"hidden": null` for plugin agents (oh-my-opencode-slim's
* `orchestrator`, `fixer`, ...), and a non-null Boolean made the whole `GET /agent` list fail to
* decode, leaving the mode picker with nothing to offer. Only `true` hides an agent.
*/
val hidden: Boolean? = null,
)

/**
Expand All @@ -178,7 +183,7 @@ data class OpenCodeAgent(
* hard-coding `build`.
*/
fun List<OpenCodeAgent>.selectablePrimaryAgents(): List<OpenCodeAgent> =
filter { (it.mode == null || it.mode == "primary" || it.mode == "all") && !it.hidden }
filter { (it.mode == null || it.mode == "primary" || it.mode == "all") && it.hidden != true }

/** The server's default agent: the first selectable primary it lists, or null when it offers none. */
fun List<OpenCodeAgent>.defaultAgentName(): String? = selectablePrimaryAgents().firstOrNull()?.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,12 @@ class OpenCodeV2ApiClient(

/**
* Internal V2 agents (compaction, title, summary) are reported as `mode: primary` with
* `hidden: true`; the app's agent model has no hidden flag yet, so they are dropped here to
* keep them out of the mode picker and settings.
* `hidden: true`. V2 does not copy the flag onto [OpenCodeAgent]; it drops those agents here so
* they stay out of the mode picker and settings. Only `true` hides: a missing or `null` flag
* (as plugin agents send it) is visible.
*/
override suspend fun agents(): List<OpenCodeAgent> =
getData<List<V2Agent>>("api/agent").filterNot { it.hidden }.map { agent ->
getData<List<V2Agent>>("api/agent").filterNot { it.hidden == true }.map { agent ->
OpenCodeAgent(
name = agent.id,
description = agent.description,
Expand Down Expand Up @@ -1565,7 +1566,8 @@ private data class V2Agent(
val id: String,
val description: String? = null,
val mode: String? = null,
val hidden: Boolean = false,
/** Nullable: an explicit `null` must decode as visible rather than fail the whole list. */
val hidden: Boolean? = null,
)

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.yugahashimoto.andcode.core.api
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test

class OpenCodeAgentSelectionTest {
Expand Down Expand Up @@ -71,7 +70,28 @@ class OpenCodeAgentSelectionTest {
"""[{"name":"title","mode":"primary","hidden":true,"native":true},{"name":"orchestrator","mode":"primary"}]""",
)

assertTrue(agents[0].hidden)
assertEquals(true, agents[0].hidden)
assertEquals(listOf("orchestrator"), agents.selectablePrimaryAgents().map { it.name })
}

@Test
fun `decodes plugin agents that send an explicit null hidden flag`() {
// Shape of OpenCode 1.19.3's GET /agent with oh-my-opencode-slim installed: plugin agents
// carry "hidden": null, which used to fail decoding of the whole list.
val agents =
OpenCodeApiClient.defaultJson.decodeFromString<List<OpenCodeAgent>>(
"""
[
{"name":"orchestrator","mode":"primary","native":false,"hidden":null,"variant":"high"},
{"name":"build","description":"Default agent","mode":"primary","native":true},
{"name":"compaction","mode":"primary","native":true,"hidden":true},
{"name":"fixer","description":"Fast implementation","mode":"subagent","native":false,"hidden":null},
{"name":"plan","description":"Plan mode","mode":"primary","native":true}
]
""".trimIndent(),
)

assertEquals(listOf("orchestrator", "build", "plan"), agents.selectablePrimaryAgents().map { it.name })
assertEquals("orchestrator", agents.defaultAgentName())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ class OpenCodeV2ApiClientTest {

val agents = client().agents()

assertEquals(listOf("build"), agents.map { it.name })
assertEquals(listOf("build", "orchestrator"), agents.map { it.name })
assertEquals("primary", agents[0].mode)
}

Expand Down Expand Up @@ -1139,6 +1139,7 @@ class OpenCodeV2ApiClientTest {

val AGENT_LIST =
"""{"data":[{"id":"build","name":"Build","description":"The default agent.","mode":"primary","hidden":false,"permissions":[]},""" +
"""{"id":"orchestrator","name":"Orchestrator","mode":"primary","hidden":null,"permissions":[]},""" +
"""{"id":"compaction","name":"Compaction","description":"internal","mode":"primary","hidden":true,"permissions":[]}]}"""

val INTEGRATION_LIST =
Expand Down
Loading