This commit is contained in:
8 files changed
+263
-65
No files matched your search
@@ -830,6 +830,17 @@ object HermesWsClient {
|
||||
return deferred
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-suspending typed twin of [request] for callers that must enqueue inside a
|
||||
* non-suspending owner check and await the raw result themselves.
|
||||
*/
|
||||
fun <P, R> requestTyped(
|
||||
method: RpcMethod<P, R>,
|
||||
params: P,
|
||||
timeoutMs: Long = REQUEST_TIMEOUT_MS,
|
||||
suppressErrorEvent: Boolean = false,
|
||||
): CompletableDeferred<Any?> = request(method.name, encodeParams(method, params), timeoutMs, suppressErrorEvent)
|
||||
|
||||
/**
|
||||
* Send a typed JSON-RPC request and await its deserialized result.
|
||||
*
|
||||
@@ -844,8 +855,7 @@ object HermesWsClient {
|
||||
timeoutMs: Long = REQUEST_TIMEOUT_MS,
|
||||
suppressErrorEvent: Boolean = false,
|
||||
): R {
|
||||
val encoded = encodeParams(method, params)
|
||||
val deferred = request(method.name, encoded, timeoutMs, suppressErrorEvent)
|
||||
val deferred = requestTyped(method, params, timeoutMs, suppressErrorEvent)
|
||||
val result =
|
||||
try {
|
||||
deferred.await()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.m57.hermescontrol.data.ws.contract
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** `commands.catalog` is sent without explicit params; the active profile is injected by WsProfileParams. */
|
||||
@Serializable
|
||||
data object CommandsCatalogParams
|
||||
|
||||
/** `arg` is sent whenever non-null, including an empty string, exactly like the old map payload. */
|
||||
@Serializable
|
||||
data class CommandDispatchParams(
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("arg") val arg: String? = null,
|
||||
@SerialName("session_id") val sessionId: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SlashExecParams(
|
||||
@SerialName("session_id") val sessionId: String,
|
||||
@SerialName("command") val command: String,
|
||||
)
|
||||
|
||||
/** `data_url` carries the bytes for an uploaded file; `path` is for gateway-visible files. */
|
||||
@Serializable
|
||||
data class FileAttachParams(
|
||||
@SerialName("session_id") val sessionId: String,
|
||||
@SerialName("path") val path: String? = null,
|
||||
@SerialName("data_url") val dataUrl: String? = null,
|
||||
@SerialName("name") val name: String? = null,
|
||||
)
|
||||
|
||||
/** The mobile client always sends the payload as `content_base64`; the `data` alias is not modelled. */
|
||||
@Serializable
|
||||
data class ImageAttachBytesParams(
|
||||
@SerialName("session_id") val sessionId: String,
|
||||
@SerialName("content_base64") val contentBase64: String? = null,
|
||||
@SerialName("filename") val filename: String? = null,
|
||||
@SerialName("ext") val ext: String? = null,
|
||||
)
|
||||
@@ -427,6 +427,46 @@ object RpcMethods {
|
||||
JsonElement.serializer(),
|
||||
)
|
||||
|
||||
// Passthrough JsonElement results: callers keep their existing legacy decoders.
|
||||
val COMMANDS_CATALOG: RpcMethod<CommandsCatalogParams, JsonElement> =
|
||||
RpcMethod(
|
||||
WsMethods.COMMANDS_CATALOG,
|
||||
CommandsCatalogParams.serializer(),
|
||||
JsonElement.serializer(),
|
||||
)
|
||||
|
||||
// Passthrough JsonElement results: callers keep their existing legacy decoders.
|
||||
val COMMAND_DISPATCH: RpcMethod<CommandDispatchParams, JsonElement> =
|
||||
RpcMethod(
|
||||
WsMethods.COMMAND_DISPATCH,
|
||||
CommandDispatchParams.serializer(),
|
||||
JsonElement.serializer(),
|
||||
)
|
||||
|
||||
// Passthrough JsonElement results: callers keep their existing legacy decoders.
|
||||
val SLASH_EXEC: RpcMethod<SlashExecParams, JsonElement> =
|
||||
RpcMethod(
|
||||
WsMethods.SLASH_EXEC,
|
||||
SlashExecParams.serializer(),
|
||||
JsonElement.serializer(),
|
||||
)
|
||||
|
||||
// Passthrough JsonElement results: callers keep their existing legacy decoders.
|
||||
val FILE_ATTACH: RpcMethod<FileAttachParams, JsonElement> =
|
||||
RpcMethod(
|
||||
WsMethods.FILE_ATTACH,
|
||||
FileAttachParams.serializer(),
|
||||
JsonElement.serializer(),
|
||||
)
|
||||
|
||||
// Passthrough JsonElement results: callers keep their existing legacy decoders.
|
||||
val IMAGE_ATTACH_BYTES: RpcMethod<ImageAttachBytesParams, JsonElement> =
|
||||
RpcMethod(
|
||||
WsMethods.IMAGE_ATTACH_BYTES,
|
||||
ImageAttachBytesParams.serializer(),
|
||||
JsonElement.serializer(),
|
||||
)
|
||||
|
||||
/** Every typed method. GatewayContractTest iterates this; future migrations append here. */
|
||||
val all: List<RpcMethod<*, *>> =
|
||||
listOf(
|
||||
@@ -482,5 +522,10 @@ object RpcMethods {
|
||||
CONFIG_GET,
|
||||
MODEL_OPTIONS,
|
||||
CLIENT_CAPABILITIES,
|
||||
COMMANDS_CATALOG,
|
||||
COMMAND_DISPATCH,
|
||||
SLASH_EXEC,
|
||||
FILE_ATTACH,
|
||||
IMAGE_ATTACH_BYTES,
|
||||
)
|
||||
}
|
||||
@@ -37,8 +37,12 @@ import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||
import com.m57.hermescontrol.data.ws.JsonRpcError
|
||||
import com.m57.hermescontrol.data.ws.WsEvent
|
||||
import com.m57.hermescontrol.data.ws.WsMethods
|
||||
import com.m57.hermescontrol.data.ws.contract.CommandDispatchParams
|
||||
import com.m57.hermescontrol.data.ws.contract.CommandsCatalogParams
|
||||
import com.m57.hermescontrol.data.ws.contract.ConfigGetParams
|
||||
import com.m57.hermescontrol.data.ws.contract.ConfigSetParams
|
||||
import com.m57.hermescontrol.data.ws.contract.FileAttachParams
|
||||
import com.m57.hermescontrol.data.ws.contract.ImageAttachBytesParams
|
||||
import com.m57.hermescontrol.data.ws.contract.PromptBtwParams
|
||||
import com.m57.hermescontrol.data.ws.contract.RpcMethod
|
||||
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||
@@ -51,6 +55,7 @@ import com.m57.hermescontrol.data.ws.contract.SessionIdParams
|
||||
import com.m57.hermescontrol.data.ws.contract.SessionInterruptParams
|
||||
import com.m57.hermescontrol.data.ws.contract.SessionListParams
|
||||
import com.m57.hermescontrol.data.ws.contract.SessionResumeParams
|
||||
import com.m57.hermescontrol.data.ws.contract.SlashExecParams
|
||||
import com.m57.hermescontrol.data.ws.contract.TypedRpcSender
|
||||
import com.m57.hermescontrol.data.ws.toAny
|
||||
import com.m57.hermescontrol.data.ws.toJsonElement
|
||||
@@ -3400,13 +3405,13 @@ class ChatViewModel(
|
||||
if (attachment.isImage) {
|
||||
val deferred =
|
||||
enqueueOwned {
|
||||
wsClient.request(
|
||||
WsMethods.IMAGE_ATTACH_BYTES,
|
||||
mapOf(
|
||||
"session_id" to owner.agentSessionId,
|
||||
"content_base64" to "data:${attachment.mimeType};base64,$b64",
|
||||
"filename" to attachment.name,
|
||||
"ext" to attachment.fileExtension,
|
||||
wsClient.requestTyped(
|
||||
RpcMethods.IMAGE_ATTACH_BYTES,
|
||||
ImageAttachBytesParams(
|
||||
sessionId = owner.agentSessionId,
|
||||
contentBase64 = "data:${attachment.mimeType};base64,$b64",
|
||||
filename = attachment.name,
|
||||
ext = attachment.fileExtension,
|
||||
),
|
||||
)
|
||||
} ?: return@launch
|
||||
@@ -3419,12 +3424,12 @@ class ChatViewModel(
|
||||
} else {
|
||||
val deferred =
|
||||
enqueueOwned {
|
||||
wsClient.request(
|
||||
WsMethods.FILE_ATTACH,
|
||||
mapOf(
|
||||
"session_id" to owner.agentSessionId,
|
||||
"data_url" to "data:${attachment.mimeType};base64,$b64",
|
||||
"name" to attachment.name,
|
||||
wsClient.requestTyped(
|
||||
RpcMethods.FILE_ATTACH,
|
||||
FileAttachParams(
|
||||
sessionId = owner.agentSessionId,
|
||||
dataUrl = "data:${attachment.mimeType};base64,$b64",
|
||||
name = attachment.name,
|
||||
),
|
||||
)
|
||||
} ?: return@launch
|
||||
@@ -4076,12 +4081,11 @@ class ChatViewModel(
|
||||
// #576). For those we fall back to slash.exec, which runs the
|
||||
// full COMMAND_REGISTRY through the worker.
|
||||
val result =
|
||||
wsClient
|
||||
.request(
|
||||
WsMethods.COMMAND_DISPATCH,
|
||||
mapOf("name" to name, "arg" to arg, "session_id" to sessionId),
|
||||
).await()
|
||||
handleDispatchResult(result)
|
||||
wsClient.call(
|
||||
RpcMethods.COMMAND_DISPATCH,
|
||||
CommandDispatchParams(name = name, arg = arg, sessionId = sessionId),
|
||||
)
|
||||
handleDispatchResult(result.toAny())
|
||||
} catch (e: HermesWsClient.HermesRpcException) {
|
||||
val msg = e.message.orEmpty()
|
||||
// Registry miss on command.dispatch: the backend emits exactly
|
||||
@@ -4093,15 +4097,14 @@ class ChatViewModel(
|
||||
// which routes the full CLI command set through the worker.
|
||||
try {
|
||||
val result =
|
||||
wsClient
|
||||
.request(
|
||||
WsMethods.SLASH_EXEC,
|
||||
mapOf(
|
||||
"command" to "/$name${if (arg.isNotEmpty()) " $arg" else ""}",
|
||||
"session_id" to sessionId,
|
||||
),
|
||||
).await()
|
||||
val output = (result as? Map<*, *>)?.get("output") as? String
|
||||
wsClient.call(
|
||||
RpcMethods.SLASH_EXEC,
|
||||
SlashExecParams(
|
||||
sessionId = sessionId,
|
||||
command = "/$name${if (arg.isNotEmpty()) " $arg" else ""}",
|
||||
),
|
||||
)
|
||||
val output = (result.toAny() as? Map<*, *>)?.get("output") as? String
|
||||
if (!output.isNullOrBlank()) addAssistantMessage(output)
|
||||
} catch (e2: HermesWsClient.HermesRpcException) {
|
||||
addAssistantMessage("/$name: ${e2.message}")
|
||||
@@ -4173,12 +4176,11 @@ class ChatViewModel(
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
try {
|
||||
val result =
|
||||
wsClient
|
||||
.request(
|
||||
WsMethods.COMMAND_DISPATCH,
|
||||
mapOf("name" to "undo", "arg" to count, "session_id" to sessionId),
|
||||
).await()
|
||||
handleDispatchResult(result)
|
||||
wsClient.call(
|
||||
RpcMethods.COMMAND_DISPATCH,
|
||||
CommandDispatchParams(name = "undo", arg = count, sessionId = sessionId),
|
||||
)
|
||||
handleDispatchResult(result.toAny())
|
||||
} catch (e: HermesWsClient.HermesRpcException) {
|
||||
addAssistantMessage(e.message ?: "Failed to undo.")
|
||||
} catch (e: Exception) {
|
||||
@@ -4474,7 +4476,8 @@ class ChatViewModel(
|
||||
private fun fetchCommandCatalog() {
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
wsClient.send(
|
||||
WsMethods.COMMANDS_CATALOG,
|
||||
RpcMethods.COMMANDS_CATALOG,
|
||||
CommandsCatalogParams,
|
||||
onSent = { id -> trackRequest(id, WsMethods.COMMANDS_CATALOG) },
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user