From 6baade0f7f6748116a73c974306e148cb7464f1f Mon Sep 17 00:00:00 2001 From: m57 <41645758+Hy4ri@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:22:39 +0300 Subject: [PATCH] refactor(#1375): make untyped RPC request and send internal (#1395) --- AGENTS.md | 15 +++++++++++++++ .../m57/hermescontrol/data/ws/HermesWsClient.kt | 10 ++++++++-- .../data/ws/RawRpcParamsRatchetTest.kt | 7 +++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d0f603b3..a9f19934 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,6 +137,21 @@ explicitly disconnected on logout and reconnected after login: The singleton's `connect()` has a guard (`if connected → skip`) so it's safe to call unconditionally. +### Typed Gateway RPC + +Every gateway method in the contract (`app/src/test/resources/gateway-contract/openrpc.json`) is called through a +typed descriptor: `HermesWsClient.call(RpcMethods.X, params)` (suspending), `requestTyped(...)`, or the typed +`send(RpcMethods.X, params, onSent)`. Params are `@Serializable` classes under `data/ws/contract/`; results are +`JsonElement` passthrough or typed. To add a method: add the params class, register it in `RpcMethods` (and +`RpcMethods.all`), and `GatewayContractTest` checks it against the contract. + +- The untyped `request(String, Map)` / `send(String, Map)` are `internal`. Use them only for `gateway.ping` and the + legacy `*.respond` fallbacks (`clarify`, `sudo`, `secret`, `vault.*`) that have no contract entry. +- Optional params are `T? = null`: the client Json has `encodeDefaults = false`, so unset fields are omitted and an + explicit `false`/`""` is still sent. Do not add a `profile` field unless callers pass an explicit override; + `WsProfileParams.decorate` injects it for profile-scoped methods. +- `RawRpcParamsRatchetTest.BASELINE` must stay empty. + ### Shared Components - **`HermesScaffold`** — drawer-aware Scaffold + TopAppBar with refresh slot, diff --git a/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt b/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt index 71e219b0..fe87906b 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/ws/HermesWsClient.kt @@ -800,8 +800,11 @@ object HermesWsClient { * The deferred is completed on the matching [WsEvent.RpcResult] / * [WsEvent.RpcError], rejected after [timeoutMs] (no response), or * rejected by [rejectAllPending] when the socket closes. + * + * Untyped escape hatch, module-internal on purpose: gateway methods in the contract go through + * [call] / [requestTyped] with an `RpcMethods` descriptor. Only `gateway.ping` and tests use this. */ - fun request( + internal fun request( method: String, params: Map = emptyMap(), timeoutMs: Long = REQUEST_TIMEOUT_MS, @@ -975,9 +978,12 @@ object HermesWsClient { /** * Send a JSON-RPC request with the given [method] and optional [params]. + * + * Untyped escape hatch, module-internal on purpose: contract methods use the typed [send] overload. + * Kept for the legacy `*.respond` fallbacks (clarify, sudo, secret, vault) that have no contract entry. * @return the request id used (can be matched against [WsEvent.RpcResult]). */ - fun send( + internal fun send( method: String, params: Map = emptyMap(), onSent: ((String) -> Unit)? = null, diff --git a/app/src/test/java/com/m57/hermescontrol/data/ws/RawRpcParamsRatchetTest.kt b/app/src/test/java/com/m57/hermescontrol/data/ws/RawRpcParamsRatchetTest.kt index 271b4c35..3c9fae18 100644 --- a/app/src/test/java/com/m57/hermescontrol/data/ws/RawRpcParamsRatchetTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/data/ws/RawRpcParamsRatchetTest.kt @@ -309,10 +309,9 @@ class RawRpcParamsRatchetTest { } /** - * Multiset of remaining unmigrated raw RPC call sites in `app/src/main/java/com/m57/hermescontrol/`. - * Excludes `data/ws/HermesWsClient.kt::SESSION_EVENTS_SINCE` which is migrated to typed `RpcMethods`. - * Notice HermesWsClient.kt retains its other raw uses: `CLIENT_CAPABILITIES`, `PROMPT_SUBMIT`, - * and `SESSION_REDIRECT`. + * Multiset of remaining raw RPC call sites for contract methods in `app/src/main/java/com/m57/hermescontrol/`. + * Empty: every contract method goes through `RpcMethods`. Keep it empty; new contract methods must be typed. + * The untyped `request`/`send` stay module-internal for `gateway.ping` and the legacy `*.respond` fallbacks. */ val BASELINE: List = emptyList() }