refactor(#1375): type profiles.list and profiles.configure RPC params (#1392)

This commit is contained in:
m57 authored and GitHub committed 2026-09-29 23:42:01 +03:00
1 parent 445da9fe14
commit cb0463aefc
7 files changed
+164 -82

No files matched your search

@@ -0,0 +1,16 @@
package com.m57.hermescontrol.data.ws.contract
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
/**
* `profiles.configure` params for the sections the app writes: `ui_meta` and the bot `soul`.
* `profile` is not a field: WsProfileParams injects it when the method is profile scoped.
*/
@Serializable
data class ProfilesConfigureParams(
@SerialName("name") val name: String? = null,
@SerialName("ui_meta") val uiMeta: JsonObject? = null,
@SerialName("soul") val soul: String? = null,
)
@@ -0,0 +1,7 @@
package com.m57.hermescontrol.data.ws.contract
import kotlinx.serialization.Serializable
/** `profiles.list` takes no explicit params here; the active profile is injected by WsProfileParams. */
@Serializable
data object ProfilesListParams
@@ -379,6 +379,22 @@ object RpcMethods {
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy decoders.
val PROFILES_LIST: RpcMethod<ProfilesListParams, JsonElement> =
RpcMethod(
WsMethods.PROFILES_LIST,
ProfilesListParams.serializer(),
JsonElement.serializer(),
)
// Passthrough JsonElement results: callers keep their existing legacy decoders.
val PROFILES_CONFIGURE: RpcMethod<ProfilesConfigureParams, JsonElement> =
RpcMethod(
WsMethods.PROFILES_CONFIGURE,
ProfilesConfigureParams.serializer(),
JsonElement.serializer(),
)
/** Every typed method. GatewayContractTest iterates this; future migrations append here. */
val all: List<RpcMethod<*, *>> =
listOf(
@@ -428,5 +444,7 @@ object RpcMethods {
PROCESS_KILL,
PLUGINS_MANAGE,
PROJECTS_LIST,
PROFILES_LIST,
PROFILES_CONFIGURE,
)
}
@@ -16,7 +16,9 @@ import com.m57.hermescontrol.data.remote.OkHttpProvider
import com.m57.hermescontrol.data.remote.safeApiCall
import com.m57.hermescontrol.data.session.ProfileSwitchCoordinator
import com.m57.hermescontrol.data.ws.HermesWsClient
import com.m57.hermescontrol.data.ws.WsMethods
import com.m57.hermescontrol.data.ws.contract.ProfilesConfigureParams
import com.m57.hermescontrol.data.ws.contract.ProfilesListParams
import com.m57.hermescontrol.data.ws.contract.RpcMethods
import com.m57.hermescontrol.data.ws.toJsonElement
import com.m57.hermescontrol.ui.common.ToastHost
import kotlinx.coroutines.CoroutineDispatcher
@@ -29,6 +31,8 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
@@ -243,14 +247,8 @@ class BotsViewModel(
// First try fetching profiles via WebSocket RPC (profiles.list) which includes ui_meta (groups, custom avatars).
var profilesWithMeta: List<ProfileInfo>? = null
try {
val rpcResult = HermesWsClient.request(WsMethods.PROFILES_LIST).await()
val jsonElement =
when (rpcResult) {
is JsonElement -> rpcResult
null -> null
else -> rpcResult.toJsonElement()
}
if (jsonElement != null) {
val jsonElement = HermesWsClient.call(RpcMethods.PROFILES_LIST, ProfilesListParams)
if (jsonElement !is JsonNull) {
val resp = OkHttpProvider.json.decodeFromJsonElement<ProfilesResponse>(jsonElement)
if (!resp.profiles.isNullOrEmpty()) {
profilesWithMeta = resp.profiles
@@ -362,7 +360,13 @@ class BotsViewModel(
),
)
val botSoul = composeBotSoul(name, title, description)
wsClientConfigureBot(name, botMeta, soul = botSoul)
// Fire-and-forget as before: the metadata write never gated the reload or onSuccess.
launch {
try {
wsClientConfigureBot(name, botMeta, soul = botSoul)
} catch (_: Exception) {
}
}
loadBots()
onSuccess()
} else {
@@ -396,7 +400,7 @@ class BotsViewModel(
),
)
try {
wsClientConfigureBot(name, updatedMeta).await()
wsClientConfigureBot(name, updatedMeta)
} catch (_: Exception) {
}
loadBots()
@@ -440,7 +444,7 @@ class BotsViewModel(
group = existingGroups.firstOrNull() ?: groupName,
)
try {
wsClientConfigureBot(name, updatedMeta).await()
wsClientConfigureBot(name, updatedMeta)
} catch (_: Exception) {
}
}
@@ -475,14 +479,13 @@ class BotsViewModel(
rooms = updatedRooms,
deleted = updatedDeleted,
)
HermesWsClient
.request(
WsMethods.PROFILES_CONFIGURE,
mapOf(
"name" to defaultProfile.name,
"ui_meta" to mapOf("hermes-bots-groups" to newSnapshot.toMap()),
),
).await()
HermesWsClient.call(
RpcMethods.PROFILES_CONFIGURE,
ProfilesConfigureParams(
name = defaultProfile.name,
uiMeta = mapOf("hermes-bots-groups" to newSnapshot.toMap()).toJsonObject(),
),
)
}
} catch (_: Exception) {
}
@@ -509,7 +512,7 @@ class BotsViewModel(
group = filtered.firstOrNull(),
)
try {
wsClientConfigureBot(bot.name, updatedMeta).await()
wsClientConfigureBot(bot.name, updatedMeta)
} catch (_: Exception) {
}
}
@@ -540,14 +543,13 @@ class BotsViewModel(
rooms = updatedRooms,
deleted = deletedMap,
)
HermesWsClient
.request(
WsMethods.PROFILES_CONFIGURE,
mapOf(
"name" to defaultProfile.name,
"ui_meta" to mapOf("hermes-bots-groups" to newSnapshot.toMap()),
),
).await()
HermesWsClient.call(
RpcMethods.PROFILES_CONFIGURE,
ProfilesConfigureParams(
name = defaultProfile.name,
uiMeta = mapOf("hermes-bots-groups" to newSnapshot.toMap()).toJsonObject(),
),
)
}
}
} catch (_: Exception) {
@@ -575,11 +577,11 @@ class BotsViewModel(
return lines.joinToString("\n")
}
private fun wsClientConfigureBot(
private suspend fun wsClientConfigureBot(
name: String,
meta: BotRosterMeta,
soul: String? = null,
): kotlinx.coroutines.CompletableDeferred<Any?> {
) {
val metaMap =
buildMap<String, Any> {
meta.title?.let { put("title", it) }
@@ -599,16 +601,15 @@ class BotsViewModel(
}
}
val params =
buildMap<String, Any> {
put("name", name)
put("ui_meta", mapOf("hermes-bots" to metaMap))
soul?.let { put("soul", it) }
}
return HermesWsClient.request(
WsMethods.PROFILES_CONFIGURE,
params,
HermesWsClient.call(
RpcMethods.PROFILES_CONFIGURE,
ProfilesConfigureParams(
name = name,
uiMeta = mapOf("hermes-bots" to metaMap).toJsonObject(),
soul = soul,
),
)
}
private fun Map<String, Any?>.toJsonObject(): JsonObject = toJsonElement() as JsonObject
}
@@ -16,7 +16,8 @@ import com.m57.hermescontrol.data.remote.safeApiCall
import com.m57.hermescontrol.data.session.ActiveSessionHolder
import com.m57.hermescontrol.data.ws.HermesWsClient
import com.m57.hermescontrol.data.ws.WsEvent
import com.m57.hermescontrol.data.ws.WsMethods
import com.m57.hermescontrol.data.ws.contract.ProfilesConfigureParams
import com.m57.hermescontrol.data.ws.contract.ProfilesListParams
import com.m57.hermescontrol.data.ws.contract.PromptSubmitParams
import com.m57.hermescontrol.data.ws.contract.RpcMethods
import com.m57.hermescontrol.data.ws.contract.SessionCreateParams
@@ -34,6 +35,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
import java.util.UUID
@@ -327,14 +330,8 @@ class GroupChatViewModel(
private suspend fun fetchProfiles(): List<ProfileInfo> {
try {
val rpcResult = HermesWsClient.request(WsMethods.PROFILES_LIST).await()
val jsonElement =
when (rpcResult) {
is JsonElement -> rpcResult
null -> null
else -> rpcResult.toJsonElement()
}
if (jsonElement != null) {
val jsonElement = HermesWsClient.call(RpcMethods.PROFILES_LIST, ProfilesListParams)
if (jsonElement !is JsonNull) {
val resp = json.decodeFromJsonElement<ProfilesResponse>(jsonElement)
if (!resp.profiles.isNullOrEmpty()) {
return resp.profiles
@@ -534,14 +531,13 @@ class GroupChatViewModel(
val uiMetaPayload = mapOf("hermes-bots-groups" to newSnapshot.toMap())
HermesWsClient
.request(
WsMethods.PROFILES_CONFIGURE,
mapOf(
"name" to defaultProfile.name,
"ui_meta" to uiMetaPayload,
),
).await()
HermesWsClient.call(
RpcMethods.PROFILES_CONFIGURE,
ProfilesConfigureParams(
name = defaultProfile.name,
uiMeta = uiMetaPayload.toJsonElement() as JsonObject,
),
)
} catch (e: Exception) {
Log.w("GroupChatViewModel", "updateGroupLimits failed: ${e.message}")
}
@@ -636,14 +632,13 @@ class GroupChatViewModel(
val newSnapshot = existingSnapshot.copy(version = 3, updatedAt = now, rooms = updatedRooms)
val uiMetaPayload = mapOf("hermes-bots-groups" to newSnapshot.toMap())
HermesWsClient
.request(
WsMethods.PROFILES_CONFIGURE,
mapOf(
"name" to defaultProfile.name,
"ui_meta" to uiMetaPayload,
),
).await()
HermesWsClient.call(
RpcMethods.PROFILES_CONFIGURE,
ProfilesConfigureParams(
name = defaultProfile.name,
uiMeta = uiMetaPayload.toJsonElement() as JsonObject,
),
)
return true
} catch (e: Exception) {
@@ -1193,14 +1188,13 @@ class GroupChatViewModel(
val uiMetaPayload = mapOf("hermes-bots-groups" to newSnapshot.toMap())
HermesWsClient
.request(
WsMethods.PROFILES_CONFIGURE,
mapOf(
"name" to defaultProfile.name,
"ui_meta" to uiMetaPayload,
),
).await()
HermesWsClient.call(
RpcMethods.PROFILES_CONFIGURE,
ProfilesConfigureParams(
name = defaultProfile.name,
uiMeta = uiMetaPayload.toJsonElement() as JsonObject,
),
)
} catch (e: Exception) {
Log.w("GroupChatViewModel", "persistSyncSnapshot failed: ${e.message}")
}