6 files changed
+109
-25
No files matched your search
@@ -3,6 +3,9 @@ package com.m57.hermescontrol.data.ws
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.m57.hermescontrol.data.model.SubagentListResponse
|
import com.m57.hermescontrol.data.model.SubagentListResponse
|
||||||
import com.m57.hermescontrol.data.model.SubagentTailResponse
|
import com.m57.hermescontrol.data.model.SubagentTailResponse
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.SessionIdParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.SubagentTailParams
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
@@ -33,12 +36,11 @@ object SubagentRepository {
|
|||||||
if (sessionId.isBlank()) return null
|
if (sessionId.isBlank()) return null
|
||||||
return try {
|
return try {
|
||||||
val result =
|
val result =
|
||||||
HermesWsClient
|
HermesWsClient.call(
|
||||||
.request(
|
RpcMethods.SUBAGENT_LIST,
|
||||||
WsMethods.SUBAGENT_LIST,
|
SessionIdParams(sessionId),
|
||||||
mapOf("session_id" to sessionId),
|
|
||||||
suppressErrorEvent = true,
|
suppressErrorEvent = true,
|
||||||
).await()
|
)
|
||||||
decode<SubagentListResponse>(result)
|
decode<SubagentListResponse>(result)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "subagent.list request failed for session $sessionId: ${e.message}")
|
Log.w(TAG, "subagent.list request failed for session $sessionId: ${e.message}")
|
||||||
@@ -58,18 +60,12 @@ object SubagentRepository {
|
|||||||
): SubagentTailResponse? {
|
): SubagentTailResponse? {
|
||||||
// Contract (SubagentIdParams): session_id and subagent_id are required, extra keys forbidden (#1379).
|
// Contract (SubagentIdParams): session_id and subagent_id are required, extra keys forbidden (#1379).
|
||||||
if (subagentId.isBlank() || sessionId.isBlank()) return null
|
if (subagentId.isBlank() || sessionId.isBlank()) return null
|
||||||
val params =
|
|
||||||
mapOf<String, Any>(
|
|
||||||
"session_id" to sessionId,
|
|
||||||
"subagent_id" to subagentId,
|
|
||||||
)
|
|
||||||
val result =
|
val result =
|
||||||
HermesWsClient
|
HermesWsClient.call(
|
||||||
.request(
|
RpcMethods.SUBAGENT_TAIL,
|
||||||
WsMethods.SUBAGENT_TAIL,
|
SubagentTailParams(sessionId, subagentId),
|
||||||
params,
|
|
||||||
suppressErrorEvent = true,
|
suppressErrorEvent = true,
|
||||||
).await()
|
)
|
||||||
return decode<SubagentTailResponse>(result)
|
return decode<SubagentTailResponse>(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,6 +331,22 @@ object RpcMethods {
|
|||||||
JsonElement.serializer(),
|
JsonElement.serializer(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy decoders.
|
||||||
|
val SUBAGENT_LIST: RpcMethod<SessionIdParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.SUBAGENT_LIST,
|
||||||
|
SessionIdParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Passthrough JsonElement results: callers keep their existing legacy decoders.
|
||||||
|
val SUBAGENT_TAIL: RpcMethod<SubagentTailParams, JsonElement> =
|
||||||
|
RpcMethod(
|
||||||
|
WsMethods.SUBAGENT_TAIL,
|
||||||
|
SubagentTailParams.serializer(),
|
||||||
|
JsonElement.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
/** Every typed method. GatewayContractTest iterates this; future migrations append here. */
|
/** Every typed method. GatewayContractTest iterates this; future migrations append here. */
|
||||||
val all: List<RpcMethod<*, *>> =
|
val all: List<RpcMethod<*, *>> =
|
||||||
listOf(
|
listOf(
|
||||||
@@ -374,5 +390,7 @@ object RpcMethods {
|
|||||||
CONNECTORS_TOOLS,
|
CONNECTORS_TOOLS,
|
||||||
CONNECTION_RESPOND,
|
CONNECTION_RESPOND,
|
||||||
CONNECTORS_OPERATION_WAKE,
|
CONNECTORS_OPERATION_WAKE,
|
||||||
|
SUBAGENT_LIST,
|
||||||
|
SUBAGENT_TAIL,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.m57.hermescontrol.data.ws.contract
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SubagentTailParams(
|
||||||
|
@SerialName("session_id") val sessionId: String,
|
||||||
|
@SerialName("subagent_id") val subagentId: String,
|
||||||
|
)
|
||||||
@@ -322,8 +322,6 @@ class RawRpcParamsRatchetTest {
|
|||||||
"data/ws/PluginManageRepository.kt::PLUGINS_MANAGE",
|
"data/ws/PluginManageRepository.kt::PLUGINS_MANAGE",
|
||||||
"data/ws/PluginRemovalRepository.kt::PLUGINS_MANAGE",
|
"data/ws/PluginRemovalRepository.kt::PLUGINS_MANAGE",
|
||||||
"data/ws/ProjectsSource.kt::PROJECTS_LIST",
|
"data/ws/ProjectsSource.kt::PROJECTS_LIST",
|
||||||
"data/ws/SubagentRepository.kt::SUBAGENT_LIST",
|
|
||||||
"data/ws/SubagentRepository.kt::SUBAGENT_TAIL",
|
|
||||||
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
||||||
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
||||||
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
"ui/bots/BotsViewModel.kt::PROFILES_CONFIGURE",
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package com.m57.hermescontrol.data.ws
|
|||||||
import com.m57.hermescontrol.data.model.SubagentListItem
|
import com.m57.hermescontrol.data.model.SubagentListItem
|
||||||
import com.m57.hermescontrol.data.model.SubagentListResponse
|
import com.m57.hermescontrol.data.model.SubagentListResponse
|
||||||
import com.m57.hermescontrol.data.model.SubagentTailResponse
|
import com.m57.hermescontrol.data.model.SubagentTailResponse
|
||||||
import io.mockk.every
|
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.SessionIdParams
|
||||||
|
import com.m57.hermescontrol.data.ws.contract.SubagentTailParams
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.mockkObject
|
import io.mockk.mockkObject
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import io.mockk.verify
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
@@ -24,17 +28,31 @@ class SubagentRepositoryTest {
|
|||||||
unmockkAll()
|
unmockkAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listSubagentsPreservesSuppressedErrorsAndDecodesResult() {
|
||||||
|
val captured = slot<SessionIdParams>()
|
||||||
|
mockkObject(HermesWsClient)
|
||||||
|
coEvery { HermesWsClient.call(RpcMethods.SUBAGENT_LIST, capture(captured), any(), true) } returns
|
||||||
|
buildJsonObject { put("subagents", kotlinx.serialization.json.buildJsonArray { }) }
|
||||||
|
|
||||||
|
val response = runBlocking { SubagentRepository.listSubagents("s1") }
|
||||||
|
|
||||||
|
assertEquals(SessionIdParams("s1"), captured.captured)
|
||||||
|
assertNotNull(response)
|
||||||
|
assertTrue(response?.subagents?.isEmpty() == true)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun tailSubagentSendsExactlySessionIdAndSubagentId() {
|
fun tailSubagentSendsExactlySessionIdAndSubagentId() {
|
||||||
// #1379: the contract forbids extra keys (max_bytes) and requires session_id.
|
// #1379: the contract forbids extra keys (max_bytes) and requires session_id.
|
||||||
val captured = slot<Map<String, Any>>()
|
val captured = slot<SubagentTailParams>()
|
||||||
mockkObject(HermesWsClient)
|
mockkObject(HermesWsClient)
|
||||||
every { HermesWsClient.request(WsMethods.SUBAGENT_TAIL, capture(captured), any(), any()) } returns
|
coEvery { HermesWsClient.call(RpcMethods.SUBAGENT_TAIL, capture(captured), any(), true) } returns
|
||||||
CompletableDeferred<Any?>(mapOf("subagent_id" to "sub-1"))
|
buildJsonObject { put("subagent_id", "sub-1") }
|
||||||
|
|
||||||
runBlocking { SubagentRepository.tailSubagent("s1", "sub-1") }
|
runBlocking { SubagentRepository.tailSubagent("s1", "sub-1") }
|
||||||
|
|
||||||
assertEquals(mapOf<String, Any>("session_id" to "s1", "subagent_id" to "sub-1"), captured.captured)
|
assertEquals(SubagentTailParams("s1", "sub-1"), captured.captured)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -44,7 +62,7 @@ class SubagentRepositoryTest {
|
|||||||
assertNull(SubagentRepository.tailSubagent("", "sub-1"))
|
assertNull(SubagentRepository.tailSubagent("", "sub-1"))
|
||||||
assertNull(SubagentRepository.tailSubagent("s1", " "))
|
assertNull(SubagentRepository.tailSubagent("s1", " "))
|
||||||
}
|
}
|
||||||
verify(exactly = 0) { HermesWsClient.request(any(), any(), any(), any()) }
|
coVerify(exactly = 0) { HermesWsClient.call(RpcMethods.SUBAGENT_TAIL, any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.m57.hermescontrol.data.ws.contract
|
||||||
|
|
||||||
|
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class SubagentTypesTest {
|
||||||
|
@Test
|
||||||
|
fun listEncodesOnlySessionId() {
|
||||||
|
val encoded = OkHttpProvider.json.encodeToJsonElement(SessionIdParams.serializer(), SessionIdParams("s1"))
|
||||||
|
assertEquals(buildJsonObject { put("session_id", "s1") }, encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun tailEncodesExactlyTheTwoRequiredIds() {
|
||||||
|
val encoded =
|
||||||
|
OkHttpProvider.json.encodeToJsonElement(
|
||||||
|
SubagentTailParams.serializer(),
|
||||||
|
SubagentTailParams("s1", "sub-1"),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
buildJsonObject {
|
||||||
|
put("session_id", "s1")
|
||||||
|
put("subagent_id", "sub-1")
|
||||||
|
},
|
||||||
|
encoded,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun descriptorsAreRegisteredAndPreserveRawResults() {
|
||||||
|
assertTrue(RpcMethods.SUBAGENT_LIST in RpcMethods.all)
|
||||||
|
assertTrue(RpcMethods.SUBAGENT_TAIL in RpcMethods.all)
|
||||||
|
assertEquals("subagent.list", RpcMethods.SUBAGENT_LIST.name)
|
||||||
|
assertEquals("subagent.tail", RpcMethods.SUBAGENT_TAIL.name)
|
||||||
|
val raw: JsonElement = buildJsonObject { put("future_field", "retained") }
|
||||||
|
assertEquals(raw, OkHttpProvider.json.decodeFromJsonElement(RpcMethods.SUBAGENT_LIST.result, raw))
|
||||||
|
assertEquals(raw, OkHttpProvider.json.decodeFromJsonElement(RpcMethods.SUBAGENT_TAIL.result, raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user